Add ignored users list to account settings

This commit is contained in:
miruka 2021-04-14 13:53:07 -04:00
parent 719938d1db
commit 93404559b9
4 changed files with 127 additions and 42 deletions

View File

@ -525,13 +525,10 @@ class MatrixClient(nio.AsyncClient):
await asyncio.sleep(0.2) await asyncio.sleep(0.2)
async def ignore_user(self, user_id: str, ignore: bool) -> None: async def set_ignored_users(self, *user_ids: str) -> None:
ignored = self.ignored_user_ids.copy() previous_ignored = self.ignored_user_ids
now_ignored = set(user_ids)
if ignore: no_longer_ignored = previous_ignored - now_ignored
ignored.add(user_id)
else:
ignored.discard(user_id)
path = ["user", self.user_id, "account_data", "m.ignored_user_list"] path = ["user", self.user_id, "account_data", "m.ignored_user_list"]
params = {"access_token": self.access_token} params = {"access_token": self.access_token}
@ -540,18 +537,18 @@ class MatrixClient(nio.AsyncClient):
nio.responses.EmptyResponse, nio.responses.EmptyResponse,
"PUT", "PUT",
nio.Api._build_path(path, params), 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 # Invites and messages from ignored users won't be returned anymore on
# syncs, thus will be absent on client restart. Clean up immediatly, # syncs, thus will be absent on client restart.
# and also update Member.ignored fields: # Clean up immediatly, and also update Member.ignored fields:
room_model = self.models[self.user_id, "rooms"] room_model = self.models[self.user_id, "rooms"]
with room_model.batch_remove(): with room_model.batch_remove():
for room_id, room in room_model.copy().items(): 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) self.ignored_rooms.add(room_id)
del room_model[room_id] del room_model[room_id]
self.models.pop((self.user_id, room_id, "events"), None) 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"] event_model = self.models[self.user_id, room_id, "events"]
member_model = self.models[self.user_id, room_id, "members"] member_model = self.models[self.user_id, room_id, "members"]
if user_id in member_model: for user_id in now_ignored:
member_model[user_id].ignored = ignore if user_id in member_model:
member_model[user_id].ignored = True
if ignore: for user_id in no_longer_ignored:
with event_model.batch_remove(): if user_id in member_model:
for event_id, event in event_model.copy().items(): member_model[user_id].ignored = False
if event.sender_id == user_id:
del event_model[event_id] 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() 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: async def can_kick(self, room_id: str, target_user_id: str) -> bool:
"""Return whether we can kick a certain user in a room.""" """Return whether we can kick a certain user in a room."""

View File

@ -7,7 +7,7 @@ import json
from dataclasses import asdict, dataclass, field from dataclasses import asdict, dataclass, field
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pathlib import Path 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 from uuid import UUID
import lxml # nosec import lxml # nosec
@ -79,6 +79,7 @@ class Account(ModelItem):
total_unread: int = 0 total_unread: int = 0
total_highlights: int = 0 total_highlights: int = 0
local_unreads: bool = False local_unreads: bool = False
ignored_users: Set[str] = field(default_factory=set)
# For some reason, Account cannot inherit Presence, because QML keeps # For some reason, Account cannot inherit Presence, because QML keeps
# complaining type error on unknown file # complaining type error on unknown file

View File

@ -861,8 +861,9 @@ class NioCallbacks:
) -> None: ) -> None:
if ev.type == "m.ignored_user_list": if ev.type == "m.ignored_user_list":
user_ids = set(ev.content.get("ignored_users", {})) users = set(ev.content.get("ignored_users", {}))
self.client.ignored_user_ids = user_ids self.client.ignored_user_ids = users
self.models["accounts"][self.client.user_id].ignored_users = users
# Presence event callbacks # Presence event callbacks

View File

@ -21,25 +21,6 @@ HFlickableColumnPage {
} }
function applyChanges() { 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) { if (avatar.changed) {
saveButton.avatarChangeRunning = true saveButton.avatarChangeRunning = true
@ -50,11 +31,39 @@ HFlickableColumnPage {
py.callClientCoro(userId, "update_own_profile", [], () => { py.callClientCoro(userId, "update_own_profile", [], () => {
saveButton.avatarChangeRunning = false saveButton.avatarChangeRunning = false
}) })
}, (errType, [httpCode]) => { }, (errType, [httpCode]) => {
console.error("Avatar upload failed:", httpCode, errType) console.error("Avatar upload failed:", httpCode, errType)
saveButton.avatarChangeRunning = false 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() { function cancel() {
@ -80,13 +89,19 @@ HFlickableColumnPage {
property bool nameChangeRunning: false property bool nameChangeRunning: false
property bool avatarChangeRunning: false property bool avatarChangeRunning: false
property bool ignoredUsersChangeRunning: false
disableWhileLoading: false disableWhileLoading: false
loading: nameChangeRunning || avatarChangeRunning loading:
nameChangeRunning ||
avatarChangeRunning ||
ignoredUsersChangeRunning
enabled: enabled:
avatar.changed || avatar.changed ||
nameField.item.changed || nameField.item.changed ||
(aliasFieldItem.changed && ! aliasFieldItem.error) (aliasFieldItem.changed && ! aliasFieldItem.error) ||
(ignoredUsersAreaItem.changed && ! ignoredUsersAreaItem.error)
onClicked: applyChanges() 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)
}
}
}
} }