Add ignored users list to account settings
This commit is contained in:
		@@ -525,13 +525,10 @@ class MatrixClient(nio.AsyncClient):
 | 
			
		||||
            await asyncio.sleep(0.2)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    async def ignore_user(self, user_id: str, ignore: bool) -> None:
 | 
			
		||||
        ignored = self.ignored_user_ids.copy()
 | 
			
		||||
 | 
			
		||||
        if ignore:
 | 
			
		||||
            ignored.add(user_id)
 | 
			
		||||
        else:
 | 
			
		||||
            ignored.discard(user_id)
 | 
			
		||||
    async def set_ignored_users(self, *user_ids: str) -> None:
 | 
			
		||||
        previous_ignored  = self.ignored_user_ids
 | 
			
		||||
        now_ignored       = set(user_ids)
 | 
			
		||||
        no_longer_ignored = previous_ignored - now_ignored
 | 
			
		||||
 | 
			
		||||
        path   = ["user", self.user_id, "account_data", "m.ignored_user_list"]
 | 
			
		||||
        params = {"access_token": self.access_token}
 | 
			
		||||
@@ -540,18 +537,18 @@ class MatrixClient(nio.AsyncClient):
 | 
			
		||||
            nio.responses.EmptyResponse,
 | 
			
		||||
            "PUT",
 | 
			
		||||
            nio.Api._build_path(path, params),
 | 
			
		||||
            nio.Api.to_json({"ignored_users": {u: {} for u in ignored}}),
 | 
			
		||||
            nio.Api.to_json({"ignored_users": {u: {} for u in now_ignored}}),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Invites and messages from ignored users won't be returned anymore on
 | 
			
		||||
        # syncs, thus will be absent on client restart. Clean up immediatly,
 | 
			
		||||
        # and also update Member.ignored fields:
 | 
			
		||||
        # syncs, thus will be absent on client restart.
 | 
			
		||||
        # Clean up immediatly, and also update Member.ignored fields:
 | 
			
		||||
 | 
			
		||||
        room_model = self.models[self.user_id, "rooms"]
 | 
			
		||||
 | 
			
		||||
        with room_model.batch_remove():
 | 
			
		||||
            for room_id, room in room_model.copy().items():
 | 
			
		||||
                if ignore and room.inviter_id == user_id:
 | 
			
		||||
                if room.inviter_id in now_ignored:
 | 
			
		||||
                    self.ignored_rooms.add(room_id)
 | 
			
		||||
                    del room_model[room_id]
 | 
			
		||||
                    self.models.pop((self.user_id, room_id, "events"), None)
 | 
			
		||||
@@ -561,18 +558,28 @@ class MatrixClient(nio.AsyncClient):
 | 
			
		||||
                event_model  = self.models[self.user_id, room_id, "events"]
 | 
			
		||||
                member_model = self.models[self.user_id, room_id, "members"]
 | 
			
		||||
 | 
			
		||||
                if user_id in member_model:
 | 
			
		||||
                    member_model[user_id].ignored = ignore
 | 
			
		||||
                for user_id in now_ignored:
 | 
			
		||||
                    if user_id in member_model:
 | 
			
		||||
                        member_model[user_id].ignored = True
 | 
			
		||||
 | 
			
		||||
                if ignore:
 | 
			
		||||
                    with event_model.batch_remove():
 | 
			
		||||
                        for event_id, event in event_model.copy().items():
 | 
			
		||||
                            if event.sender_id == user_id:
 | 
			
		||||
                                del event_model[event_id]
 | 
			
		||||
                for user_id in no_longer_ignored:
 | 
			
		||||
                    if user_id in member_model:
 | 
			
		||||
                        member_model[user_id].ignored = False
 | 
			
		||||
 | 
			
		||||
                with event_model.batch_remove():
 | 
			
		||||
                    for event_id, event in event_model.copy().items():
 | 
			
		||||
                        if event.sender_id in now_ignored:
 | 
			
		||||
                            del event_model[event_id]
 | 
			
		||||
 | 
			
		||||
        await self.update_account_unread_counts()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    async def ignore_user(self, user_id: str, ignore: bool) -> None:
 | 
			
		||||
        current = self.ignored_user_ids
 | 
			
		||||
        new     = current | {user_id} if ignore else current - {user_id}
 | 
			
		||||
        await self.set_ignored_users(*new)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    async def can_kick(self, room_id: str, target_user_id: str) -> bool:
 | 
			
		||||
        """Return whether we can kick a certain user in a room."""
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ import json
 | 
			
		||||
from dataclasses import asdict, dataclass, field
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from typing import Any, Dict, List, Optional, Tuple, Type, Union
 | 
			
		||||
from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union
 | 
			
		||||
from uuid import UUID
 | 
			
		||||
 | 
			
		||||
import lxml  # nosec
 | 
			
		||||
@@ -79,6 +79,7 @@ class Account(ModelItem):
 | 
			
		||||
    total_unread:     int      = 0
 | 
			
		||||
    total_highlights: int      = 0
 | 
			
		||||
    local_unreads:    bool     = False
 | 
			
		||||
    ignored_users:    Set[str] = field(default_factory=set)
 | 
			
		||||
 | 
			
		||||
    # For some reason, Account cannot inherit Presence, because QML keeps
 | 
			
		||||
    # complaining type error on unknown file
 | 
			
		||||
 
 | 
			
		||||
@@ -861,8 +861,9 @@ class NioCallbacks:
 | 
			
		||||
    ) -> None:
 | 
			
		||||
 | 
			
		||||
        if ev.type == "m.ignored_user_list":
 | 
			
		||||
            user_ids = set(ev.content.get("ignored_users", {}))
 | 
			
		||||
            self.client.ignored_user_ids = user_ids
 | 
			
		||||
            users = set(ev.content.get("ignored_users", {}))
 | 
			
		||||
            self.client.ignored_user_ids = users
 | 
			
		||||
            self.models["accounts"][self.client.user_id].ignored_users = users
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    # Presence event callbacks
 | 
			
		||||
 
 | 
			
		||||
@@ -21,25 +21,6 @@ HFlickableColumnPage {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function applyChanges() {
 | 
			
		||||
        if (nameField.item.changed) {
 | 
			
		||||
            saveButton.nameChangeRunning = true
 | 
			
		||||
 | 
			
		||||
            py.callClientCoro(
 | 
			
		||||
                userId, "set_displayname", [nameField.item.text], () => {
 | 
			
		||||
                    py.callClientCoro(userId, "update_own_profile", [], () => {
 | 
			
		||||
                        saveButton.nameChangeRunning = false
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (aliasFieldItem.changed) {
 | 
			
		||||
            window.settings.Chat.Composer.Aliases[userId] =
 | 
			
		||||
                aliasFieldItem.text
 | 
			
		||||
 | 
			
		||||
            window.saveSettings()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (avatar.changed) {
 | 
			
		||||
            saveButton.avatarChangeRunning = true
 | 
			
		||||
 | 
			
		||||
@@ -50,11 +31,39 @@ HFlickableColumnPage {
 | 
			
		||||
                py.callClientCoro(userId, "update_own_profile", [], () => {
 | 
			
		||||
                    saveButton.avatarChangeRunning = false
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
            }, (errType, [httpCode]) => {
 | 
			
		||||
                console.error("Avatar upload failed:", httpCode, errType)
 | 
			
		||||
                saveButton.avatarChangeRunning = false
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (nameField.item.changed) {
 | 
			
		||||
            saveButton.nameChangeRunning = true
 | 
			
		||||
            const name                   = [nameField.item.text]
 | 
			
		||||
 | 
			
		||||
            py.callClientCoro(userId, "set_displayname", [name] , () => {
 | 
			
		||||
                py.callClientCoro(userId, "update_own_profile", [], () => {
 | 
			
		||||
                    saveButton.nameChangeRunning = false
 | 
			
		||||
                })
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (aliasFieldItem.changed) {
 | 
			
		||||
            window.settings.Chat.Composer.Aliases[userId] =
 | 
			
		||||
                aliasFieldItem.text
 | 
			
		||||
 | 
			
		||||
            window.saveSettings()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (ignoredUsersAreaItem.changed) {
 | 
			
		||||
            saveButton.ignoredUsersChangeRunning = true
 | 
			
		||||
            const users                          = ignoredUsersAreaItem.userIds
 | 
			
		||||
 | 
			
		||||
            py.callClientCoro(userId, "set_ignored_users", users, () => {
 | 
			
		||||
                saveButton.ignoredUsersChangeRunning = false
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function cancel() {
 | 
			
		||||
@@ -80,13 +89,19 @@ HFlickableColumnPage {
 | 
			
		||||
 | 
			
		||||
            property bool nameChangeRunning: false
 | 
			
		||||
            property bool avatarChangeRunning: false
 | 
			
		||||
            property bool ignoredUsersChangeRunning: false
 | 
			
		||||
 | 
			
		||||
            disableWhileLoading: false
 | 
			
		||||
            loading: nameChangeRunning || avatarChangeRunning
 | 
			
		||||
            loading:
 | 
			
		||||
                nameChangeRunning ||
 | 
			
		||||
                avatarChangeRunning ||
 | 
			
		||||
                ignoredUsersChangeRunning
 | 
			
		||||
 | 
			
		||||
            enabled:
 | 
			
		||||
                avatar.changed ||
 | 
			
		||||
                nameField.item.changed ||
 | 
			
		||||
                (aliasFieldItem.changed && ! aliasFieldItem.error)
 | 
			
		||||
                (aliasFieldItem.changed && ! aliasFieldItem.error) ||
 | 
			
		||||
                (ignoredUsersAreaItem.changed && ! ignoredUsersAreaItem.error)
 | 
			
		||||
 | 
			
		||||
            onClicked: applyChanges()
 | 
			
		||||
        }
 | 
			
		||||
@@ -314,4 +329,65 @@ HFlickableColumnPage {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HLabeledItem {
 | 
			
		||||
        id: ignoredUsers
 | 
			
		||||
 | 
			
		||||
        readonly property var userIds:
 | 
			
		||||
            ! ignoredUsersAreaItem.text.trim() ?
 | 
			
		||||
            [] :
 | 
			
		||||
            ignoredUsersAreaItem.text.trim().split(/\s+/)
 | 
			
		||||
 | 
			
		||||
        readonly property var invalidUserIds: {
 | 
			
		||||
            const result = []
 | 
			
		||||
 | 
			
		||||
            for (const user of userIds)
 | 
			
		||||
                if (! /@.+:.+/.test(user))
 | 
			
		||||
                    result.push(user)
 | 
			
		||||
 | 
			
		||||
            return result
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        loading: ! ready
 | 
			
		||||
        label.text: qsTr("Ignored users:")
 | 
			
		||||
        errorLabel.text:
 | 
			
		||||
            invalidUserIds.length ?
 | 
			
		||||
            qsTr("Incomplete user ID: %1").arg(invalidUserIds.join(", ")) :
 | 
			
		||||
            ""
 | 
			
		||||
 | 
			
		||||
        Layout.fillWidth: true
 | 
			
		||||
 | 
			
		||||
        HRowLayout {
 | 
			
		||||
            width: parent.width
 | 
			
		||||
 | 
			
		||||
            HTextArea {
 | 
			
		||||
                id: ignoredUsersAreaItem
 | 
			
		||||
                error: ignoredUsers.invalidUserIds.length > 0
 | 
			
		||||
                focusItemOnTab: ignoredUsersHelpButton
 | 
			
		||||
                placeholderText: qsTr("@user1:example.org @user2:ex.org")
 | 
			
		||||
                defaultText:
 | 
			
		||||
                    ready ?
 | 
			
		||||
                    JSON.parse(account.ignored_users).sort().join(" ") :
 | 
			
		||||
                    ""
 | 
			
		||||
 | 
			
		||||
                Layout.fillWidth: true
 | 
			
		||||
                Layout.fillHeight: true
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            FieldHelpButton {
 | 
			
		||||
                id: ignoredUsersHelpButton
 | 
			
		||||
                helpText: qsTr(
 | 
			
		||||
                    "List of user IDs, separated by a space, from which you " +
 | 
			
		||||
                    "will not receive messages or room invites.\n\n" +
 | 
			
		||||
 | 
			
		||||
                    "Their display name, avatar and online status will also " +
 | 
			
		||||
                    "be hidden from room member lists.\n\n" +
 | 
			
		||||
 | 
			
		||||
                    "When removing an user from the ignore list, restarting " +
 | 
			
		||||
                    "%1 is needed to receive anything they might have sent " +
 | 
			
		||||
                    "while being ignored."
 | 
			
		||||
                ).arg(Qt.application.displayName)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user