Add initial power level bridging
This commit is contained in:
@@ -70,7 +70,7 @@ does not do this automatically.~~
|
|||||||
* [ ] Presence (currently always shown as online on Telegram)
|
* [ ] Presence (currently always shown as online on Telegram)
|
||||||
* [ ] Typing notifications (may not be possible)
|
* [ ] Typing notifications (may not be possible)
|
||||||
* [ ] Pinning messages
|
* [ ] Pinning messages
|
||||||
* [ ] Power level
|
* [x] Power level
|
||||||
* [ ] Membership actions
|
* [ ] Membership actions
|
||||||
* [ ] Inviting
|
* [ ] Inviting
|
||||||
* [ ] Kicking
|
* [ ] Kicking
|
||||||
@@ -96,7 +96,7 @@ does not do this automatically.~~
|
|||||||
* [x] Presence
|
* [x] Presence
|
||||||
* [x] Typing notifications
|
* [x] Typing notifications
|
||||||
* [ ] Pinning messages
|
* [ ] Pinning messages
|
||||||
* [ ] Admin status
|
* [x] Admin/chat creator status
|
||||||
* [x] Membership actions
|
* [x] Membership actions
|
||||||
* [x] Inviting
|
* [x] Inviting
|
||||||
* [x] Kicking
|
* [x] Kicking
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ class StateStore:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.memberships = {}
|
self.memberships = {}
|
||||||
self.power_levels = {}
|
self.power_levels = {}
|
||||||
self.power_level_requirements = {}
|
|
||||||
|
|
||||||
def _get_membership(self, room, user):
|
def _get_membership(self, room, user):
|
||||||
return self.memberships.get(room, {}).get(user, "left")
|
return self.memberships.get(room, {}).get(user, "left")
|
||||||
@@ -50,13 +49,29 @@ class StateStore:
|
|||||||
def left(self, room, user):
|
def left(self, room, user):
|
||||||
return self._set_membership(room, user, "left")
|
return self._set_membership(room, user, "left")
|
||||||
|
|
||||||
|
def has_power_level_data(self, room):
|
||||||
|
return room in self.power_levels
|
||||||
|
|
||||||
def has_power_level(self, room, user, event):
|
def has_power_level(self, room, user, event):
|
||||||
return True
|
room_levels = self.power_levels.get(room, {})
|
||||||
|
required = room_levels["events"].get(event, 95)
|
||||||
|
has = room_levels["users"].get(user, 0)
|
||||||
|
return has >= required
|
||||||
|
|
||||||
def set_power_level(self, room, user, level):
|
def set_power_level(self, room, user, level):
|
||||||
if not room in self.power_levels:
|
if not room in self.power_levels:
|
||||||
self.power_levels[room] = {}
|
self.power_levels[room] = {
|
||||||
self.power_levels[room][user] = level
|
"users": {},
|
||||||
|
"events": {},
|
||||||
|
}
|
||||||
|
self.power_levels[room]["users"][user] = level
|
||||||
|
|
||||||
|
def set_power_levels(self, room, content):
|
||||||
|
if "events" not in content:
|
||||||
|
content["events"] = {}
|
||||||
|
if "users" not in content:
|
||||||
|
content["users"] = {}
|
||||||
|
self.power_levels[room] = content
|
||||||
|
|
||||||
|
|
||||||
class AppService:
|
class AppService:
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class HTTPAPI(MatrixHttpApi):
|
|||||||
return super()._send(method, path, content, query_params, headers, api_path=api_path)
|
return super()._send(method, path, content, query_params, headers, api_path=api_path)
|
||||||
|
|
||||||
def create_room(self, alias=None, is_public=False, name=None, topic=None, is_direct=False,
|
def create_room(self, alias=None, is_public=False, name=None, topic=None, is_direct=False,
|
||||||
invitees=()):
|
invitees=(), initial_state=[]):
|
||||||
"""Perform /createRoom.
|
"""Perform /createRoom.
|
||||||
Args:
|
Args:
|
||||||
alias (str): Optional. The room alias name to set for this room.
|
alias (str): Optional. The room alias name to set for this room.
|
||||||
@@ -79,6 +79,8 @@ class HTTPAPI(MatrixHttpApi):
|
|||||||
content["name"] = name
|
content["name"] = name
|
||||||
if topic:
|
if topic:
|
||||||
content["topic"] = topic
|
content["topic"] = topic
|
||||||
|
if initial_state:
|
||||||
|
content["initial_state"] = initial_state
|
||||||
content["is_direct"] = is_direct
|
content["is_direct"] = is_direct
|
||||||
|
|
||||||
return self._send("POST", "/createRoom", content)
|
return self._send("POST", "/createRoom", content)
|
||||||
@@ -212,6 +214,17 @@ class IntentAPI:
|
|||||||
self._ensure_has_power_level_for(room_id, "m.room.name")
|
self._ensure_has_power_level_for(room_id, "m.room.name")
|
||||||
return self.client.set_room_name(room_id, name)
|
return self.client.set_room_name(room_id, name)
|
||||||
|
|
||||||
|
def get_power_levels(self, room_id):
|
||||||
|
self._ensure_joined(room_id)
|
||||||
|
levels = self.client.get_power_levels(room_id)
|
||||||
|
self.state_store.set_power_levels(room_id, levels)
|
||||||
|
return levels
|
||||||
|
|
||||||
|
def set_power_levels(self, room_id, content):
|
||||||
|
response = self.send_state_event(room_id, "m.room.power_levels", content)
|
||||||
|
self.state_store.set_power_levels(room_id, content)
|
||||||
|
return response
|
||||||
|
|
||||||
def set_typing(self, room_id, is_typing=True, timeout=5000):
|
def set_typing(self, room_id, is_typing=True, timeout=5000):
|
||||||
self._ensure_joined(room_id)
|
self._ensure_joined(room_id)
|
||||||
return self.client.set_typing(room_id, is_typing, timeout)
|
return self.client.set_typing(room_id, is_typing, timeout)
|
||||||
@@ -311,8 +324,13 @@ class IntentAPI:
|
|||||||
self.registered = True
|
self.registered = True
|
||||||
|
|
||||||
def _ensure_has_power_level_for(self, room_id, event_type):
|
def _ensure_has_power_level_for(self, room_id, event_type):
|
||||||
|
if not self.state_store.has_power_level_data(room_id):
|
||||||
|
self.get_power_levels(room_id)
|
||||||
if self.state_store.has_power_level(room_id, self.mxid, event_type):
|
if self.state_store.has_power_level(room_id, self.mxid, event_type):
|
||||||
return
|
return
|
||||||
|
elif not self.bot:
|
||||||
|
pass
|
||||||
|
# raise IntentError(f"Power level of {self.mxid} is not enough for {event_type} in {room_id}")
|
||||||
# TODO implement
|
# TODO implement
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|||||||
@@ -93,6 +93,12 @@ class MatrixHandler:
|
|||||||
if portal:
|
if portal:
|
||||||
portal.handle_matrix_deletion(sender, event_id)
|
portal.handle_matrix_deletion(sender, event_id)
|
||||||
|
|
||||||
|
def handle_power_levels(self, room, sender, new, old):
|
||||||
|
portal = Portal.get_by_mxid(room)
|
||||||
|
if portal:
|
||||||
|
sender = User.get_by_mxid(sender)
|
||||||
|
portal.handle_matrix_power_levels(sender, new["users"], old["users"])
|
||||||
|
|
||||||
def filter_matrix_event(self, event):
|
def filter_matrix_event(self, event):
|
||||||
return event["sender"] == self.az.bot_mxid or self.is_puppet(event["sender"])
|
return event["sender"] == self.az.bot_mxid or self.is_puppet(event["sender"])
|
||||||
|
|
||||||
@@ -114,3 +120,6 @@ class MatrixHandler:
|
|||||||
self.handle_message(evt["room_id"], evt["sender"], content, evt["event_id"])
|
self.handle_message(evt["room_id"], evt["sender"], content, evt["event_id"])
|
||||||
elif type == "m.room.redaction":
|
elif type == "m.room.redaction":
|
||||||
self.handle_redaction(evt["room_id"], evt["sender"], evt["redacts"])
|
self.handle_redaction(evt["room_id"], evt["sender"], evt["redacts"])
|
||||||
|
elif type == "m.room.power_levels":
|
||||||
|
self.handle_power_levels(evt["room_id"], evt["sender"], evt["content"],
|
||||||
|
evt["prev_content"])
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
from telethon.tl.functions.messages import GetFullChatRequest
|
from telethon.tl.functions.messages import GetFullChatRequest, EditChatAdminRequest
|
||||||
from telethon.tl.functions.channels import GetParticipantsRequest
|
from telethon.tl.functions.channels import GetParticipantsRequest
|
||||||
from telethon.errors.rpc_error_list import ChatAdminRequiredError
|
from telethon.errors.rpc_error_list import ChatAdminRequiredError
|
||||||
from telethon.tl.types import *
|
from telethon.tl.types import *
|
||||||
@@ -79,8 +79,9 @@ class Portal:
|
|||||||
if self.mxid:
|
if self.mxid:
|
||||||
if update_if_exists:
|
if update_if_exists:
|
||||||
self.update_info(user, entity)
|
self.update_info(user, entity)
|
||||||
users = self.get_users(user, entity)
|
users, participants = self.get_users(user, entity)
|
||||||
self.sync_telegram_users(user, users)
|
self.sync_telegram_users(user, users)
|
||||||
|
self.update_telegram_participants(participants)
|
||||||
self.invite_matrix(invites)
|
self.invite_matrix(invites)
|
||||||
return self.mxid
|
return self.mxid
|
||||||
|
|
||||||
@@ -94,9 +95,23 @@ class Portal:
|
|||||||
direct = self.peer_type == "user"
|
direct = self.peer_type == "user"
|
||||||
puppet = p.Puppet.get(self.tgid) if direct else None
|
puppet = p.Puppet.get(self.tgid) if direct else None
|
||||||
intent = puppet.intent if direct else self.az.intent
|
intent = puppet.intent if direct else self.az.intent
|
||||||
|
|
||||||
|
power_level_requirement = 0 if self.peer_type == "chat" else 50
|
||||||
|
initial_power_levels = {
|
||||||
|
"ban": 100,
|
||||||
|
"events": {
|
||||||
|
"m.room.name": power_level_requirement,
|
||||||
|
"m.room.avatar": power_level_requirement,
|
||||||
|
"m.room.topic": 50,
|
||||||
|
"m.room.power_levels": 50,
|
||||||
|
"invite": power_level_requirement,
|
||||||
|
},
|
||||||
|
"users_default": 0,
|
||||||
|
}
|
||||||
|
|
||||||
# TODO set room alias if public channel.
|
# TODO set room alias if public channel.
|
||||||
room = intent.create_room(invitees=invites, name=title,
|
room = intent.create_room(invitees=invites, name=title, is_direct=direct,
|
||||||
is_direct=direct)
|
initial_state=[initial_power_levels])
|
||||||
if not room:
|
if not room:
|
||||||
raise Exception(f"Failed to create room for {self.tgid}")
|
raise Exception(f"Failed to create room for {self.tgid}")
|
||||||
|
|
||||||
@@ -105,8 +120,9 @@ class Portal:
|
|||||||
self.save()
|
self.save()
|
||||||
if not direct:
|
if not direct:
|
||||||
self.update_info(user, entity)
|
self.update_info(user, entity)
|
||||||
users = self.get_users(user, entity)
|
users, participants = self.get_users(user, entity)
|
||||||
self.sync_telegram_users(user, users)
|
self.sync_telegram_users(user, users)
|
||||||
|
self.update_telegram_participants(participants)
|
||||||
else:
|
else:
|
||||||
puppet.update_info(user, entity)
|
puppet.update_info(user, entity)
|
||||||
puppet.intent.join_room(self.mxid)
|
puppet.intent.join_room(self.mxid)
|
||||||
@@ -185,17 +201,18 @@ class Portal:
|
|||||||
|
|
||||||
def get_users(self, user, entity):
|
def get_users(self, user, entity):
|
||||||
if self.peer_type == "chat":
|
if self.peer_type == "chat":
|
||||||
return user.client(GetFullChatRequest(chat_id=self.tgid)).users
|
chat = user.client(GetFullChatRequest(chat_id=self.tgid))
|
||||||
|
return chat.users, chat.full_chat.participants.participants
|
||||||
elif self.peer_type == "channel":
|
elif self.peer_type == "channel":
|
||||||
try:
|
try:
|
||||||
participants = user.client(GetParticipantsRequest(
|
participants = user.client(GetParticipantsRequest(
|
||||||
entity, ChannelParticipantsRecent(), offset=0, limit=100, hash=0
|
entity, ChannelParticipantsRecent(), offset=0, limit=100, hash=0
|
||||||
))
|
))
|
||||||
return participants.users
|
return participants.users, participants.participants
|
||||||
except ChatAdminRequiredError:
|
except ChatAdminRequiredError:
|
||||||
return []
|
return []
|
||||||
elif self.peer_type == "user":
|
elif self.peer_type == "user":
|
||||||
return [entity]
|
return [entity], []
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
# region Matrix event handling
|
# region Matrix event handling
|
||||||
@@ -246,6 +263,20 @@ class Portal:
|
|||||||
return
|
return
|
||||||
deleter.client.delete_messages(self.peer, [message.tgid])
|
deleter.client.delete_messages(self.peer, [message.tgid])
|
||||||
|
|
||||||
|
def handle_matrix_power_levels(self, sender, new_users, old_users):
|
||||||
|
for user, level in new_users.items():
|
||||||
|
puppet_match = p.Puppet.mxid_regex.search(user)
|
||||||
|
if puppet_match:
|
||||||
|
user_id = int(puppet_match.group(1))
|
||||||
|
else:
|
||||||
|
mx_user = u.User.get_by_mxid(user, create=False)
|
||||||
|
if not mx_user or not mx_user.tgid:
|
||||||
|
continue
|
||||||
|
user_id = mx_user.tgid
|
||||||
|
if user not in old_users or level != old_users[user]:
|
||||||
|
sender.client(
|
||||||
|
EditChatAdminRequest(chat_id=self.tgid, user_id=user_id, is_admin=level >= 50))
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
# region Telegram event handling
|
# region Telegram event handling
|
||||||
|
|
||||||
@@ -374,6 +405,40 @@ class Portal:
|
|||||||
else:
|
else:
|
||||||
self.log.debug("Unhandled Telegram action in %s: %s", self.title, action)
|
self.log.debug("Unhandled Telegram action in %s: %s", self.title, action)
|
||||||
|
|
||||||
|
def set_telegram_admin(self, puppet, user):
|
||||||
|
levels = self.main_intent.get_power_levels(self.mxid)
|
||||||
|
if user:
|
||||||
|
levels["users"][user.mxid] = 50
|
||||||
|
if puppet:
|
||||||
|
levels["users"][puppet.mxid] = 50
|
||||||
|
self.main_intent.set_power_levels(self.mxid, levels)
|
||||||
|
|
||||||
|
def update_telegram_participants(self, participants):
|
||||||
|
levels = self.main_intent.get_power_levels(self.mxid)
|
||||||
|
levels["events"]["m.room.power_levels"] = 50
|
||||||
|
for participant in participants:
|
||||||
|
puppet = p.Puppet.get(participant.user_id)
|
||||||
|
user = u.User.get_by_tgid(participant.user_id)
|
||||||
|
new_level = 0
|
||||||
|
if isinstance(participant, ChatParticipantAdmin):
|
||||||
|
new_level = 50
|
||||||
|
elif isinstance(participant, ChatParticipantCreator):
|
||||||
|
new_level = 95
|
||||||
|
if user:
|
||||||
|
levels["users"][user.mxid] = new_level
|
||||||
|
if puppet:
|
||||||
|
levels["users"][puppet.mxid] = new_level
|
||||||
|
self.main_intent.set_power_levels(self.mxid, levels)
|
||||||
|
|
||||||
|
def set_telegram_admins_enabled(self, enabled):
|
||||||
|
level = 50 if enabled else 10
|
||||||
|
levels = self.main_intent.get_power_levels(self.mxid)
|
||||||
|
print(levels)
|
||||||
|
levels["invite"] = level
|
||||||
|
levels["events"]["m.room.name"] = level
|
||||||
|
levels["events"]["m.room.avatar"] = level
|
||||||
|
self.main_intent.set_power_levels(self.mxid, levels)
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
# region Database conversion
|
# region Database conversion
|
||||||
|
|
||||||
|
|||||||
+26
-12
@@ -190,22 +190,23 @@ class User:
|
|||||||
return self.update_typing(update)
|
return self.update_typing(update)
|
||||||
elif isinstance(update, UpdateUserStatus):
|
elif isinstance(update, UpdateUserStatus):
|
||||||
return self.update_status(update)
|
return self.update_status(update)
|
||||||
|
elif isinstance(update, (UpdateChatAdmins, UpdateChatParticipantAdmin)):
|
||||||
|
return self.update_admin(update)
|
||||||
|
elif isinstance(update, UpdateChatParticipants):
|
||||||
|
portal = po.Portal.get_by_tgid(update.participants.chat_id, "chat")
|
||||||
|
portal.update_telegram_participants(update.participants.participants)
|
||||||
else:
|
else:
|
||||||
self.log.debug("Unhandled update: %s", update)
|
self.log.debug("Unhandled update: %s", update)
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_message_details(self, update):
|
def update_admin(self, update):
|
||||||
if isinstance(update, UpdateShortChatMessage):
|
portal = po.Portal.get_by_tgid(update.chat_id, "chat")
|
||||||
portal = po.Portal.get_by_tgid(update.chat_id, "chat")
|
if isinstance(update, UpdateChatAdmins):
|
||||||
sender = pu.Puppet.get(update.from_id)
|
portal.set_telegram_admins_enabled(update.enabled)
|
||||||
elif isinstance(update, UpdateShortMessage):
|
elif isinstance(update, UpdateChatParticipantAdmin):
|
||||||
portal = po.Portal.get_by_tgid(update.user_id, "user")
|
puppet = pu.Puppet.get(update.user_id)
|
||||||
sender = pu.Puppet.get(self.tgid if update.out else update.user_id)
|
user = User.get_by_tgid(update.user_id)
|
||||||
elif isinstance(update, (UpdateNewMessage, UpdateNewChannelMessage)):
|
portal.set_telegram_admin(puppet, user)
|
||||||
update = update.message
|
|
||||||
sender = pu.Puppet.get(update.from_id)
|
|
||||||
portal = po.Portal.get_by_entity(update.to_id)
|
|
||||||
return update, sender, portal
|
|
||||||
|
|
||||||
def update_typing(self, update):
|
def update_typing(self, update):
|
||||||
if isinstance(update, UpdateUserTyping):
|
if isinstance(update, UpdateUserTyping):
|
||||||
@@ -223,6 +224,19 @@ class User:
|
|||||||
puppet.intent.set_presence("offline")
|
puppet.intent.set_presence("offline")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def get_message_details(self, update):
|
||||||
|
if isinstance(update, UpdateShortChatMessage):
|
||||||
|
portal = po.Portal.get_by_tgid(update.chat_id, "chat")
|
||||||
|
sender = pu.Puppet.get(update.from_id)
|
||||||
|
elif isinstance(update, UpdateShortMessage):
|
||||||
|
portal = po.Portal.get_by_tgid(update.user_id, "user")
|
||||||
|
sender = pu.Puppet.get(self.tgid if update.out else update.user_id)
|
||||||
|
elif isinstance(update, (UpdateNewMessage, UpdateNewChannelMessage)):
|
||||||
|
update = update.message
|
||||||
|
sender = pu.Puppet.get(update.from_id)
|
||||||
|
portal = po.Portal.get_by_entity(update.to_id)
|
||||||
|
return update, sender, portal
|
||||||
|
|
||||||
def update_message(self, update):
|
def update_message(self, update):
|
||||||
update, sender, portal = self.get_message_details(update)
|
update, sender, portal = self.get_message_details(update)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user