def _filter_events(self, room_id: RoomID, events: List[Dict]) -> Iterator[Event]: # We only want events about the custom puppet user, but we can't use # filters for typing and read receipt events. for event in events: event["room_id"] = room_id evt_type = EventType.find(event.get("type", None)) event.setdefault("content", {}) if evt_type == EventType.TYPING: is_typing = self.custom_mxid in event["content"].get( "user_ids", []) event["content"]["user_ids"] = [self.custom_mxid ] if is_typing else [] elif evt_type == EventType.RECEIPT: val = None evt = None for event_id in event["content"]: try: val = event["content"][event_id]["m.read"][ self.custom_mxid] evt = event_id break except KeyError: pass if val and evt: event["content"] = { evt: { "m.read": { self.custom_mxid: val } } } else: continue yield event
def _filter_events(self, room_id: RoomID, events: List[Dict]) -> Iterator[Event]: for event in events: event["room_id"] = room_id if self.only_handle_own_synced_events: # We only want events about the custom puppet user, but we can't use # filters for typing and read receipt events. evt_type = EventType.find(event.get("type", None)) event.setdefault("content", {}) if evt_type == EventType.TYPING: is_typing = self.custom_mxid in event["content"].get( "user_ids", []) event["content"]["user_ids"] = [self.custom_mxid ] if is_typing else [] elif evt_type == EventType.RECEIPT: try: event_id, receipt = event["content"].popitem() data = receipt["m.read"][self.custom_mxid] event["content"] = { event_id: { "m.read": { self.custom_mxid: data } } } except KeyError: continue yield event
async def encrypt( self, room_id: RoomID, event_type: EventType, content: Union[Serializable, JSON]) -> Tuple[EventType, JSON]: serialized = content.serialize() if isinstance( content, Serializable) else content type_str = str(event_type) retries = 0 while True: try: type_str, encrypted = self.client.encrypt( room_id, type_str, serialized) break except GroupEncryptionError: if retries > 3: self.log.error("Got GroupEncryptionError again, giving up") raise retries += 1 self.log.debug( "Got GroupEncryptionError, sharing group session and trying again" ) await self.client.share_group_session( room_id, ignore_unverified_devices=True) event_type = EventType.find(type_str) try: encrypted["m.relates_to"] = serialized["m.relates_to"] except KeyError: pass return event_type, encrypted
async def _encrypt_megolm_event( self, room_id: RoomID, event_type: EventType, content: Any) -> EncryptedMegolmEventContent: self.log.debug(f"Encrypting event of type {event_type} for {room_id}") session = await self.crypto_store.get_outbound_group_session(room_id) if not session: raise EncryptionError("No group session created") ciphertext = session.encrypt( json.dumps({ "room_id": room_id, "type": event_type.serialize(), "content": content.serialize() if isinstance(content, Serializable) else content, })) try: relates_to = content.relates_to except AttributeError: try: relates_to = RelatesTo.deserialize(content["m.relates_to"]) except KeyError: relates_to = None await self.crypto_store.update_outbound_group_session(session) return EncryptedMegolmEventContent( sender_key=self.account.identity_key, device_id=self.client.device_id, ciphertext=ciphertext, session_id=SessionID(session.id), relates_to=relates_to, )
def _make_template(self, name: str, tpl: Dict[str, Any]) -> Template: try: return Template(type=EventType.find(tpl.get("type", "m.room.message")), variables=self._parse_variables(tpl), content=self._parse_content(tpl.get("content", None))).init() except Exception as e: raise ConfigError(f"Failed to load {name}") from e
def get_base_power_levels(portal: po.Portal, levels: PowerLevelContent = None, entity: TypeChat = None) -> PowerLevelContent: levels = levels or PowerLevelContent() if portal.peer_type == "user": overrides = portal.config["bridge.initial_power_level_overrides.user"] levels.ban = overrides.get("ban", 100) levels.kick = overrides.get("kick", 100) levels.invite = overrides.get("invite", 100) levels.redact = overrides.get("redact", 0) levels.events[EventType.ROOM_NAME] = 0 levels.events[EventType.ROOM_AVATAR] = 0 levels.events[EventType.ROOM_TOPIC] = 0 levels.state_default = overrides.get("state_default", 0) levels.users_default = overrides.get("users_default", 0) levels.events_default = overrides.get("events_default", 0) else: overrides = portal.config["bridge.initial_power_level_overrides.group"] dbr = entity.default_banned_rights if not dbr: portal.log.debug(f"default_banned_rights is None in {entity}") dbr = ChatBannedRights( invite_users=True, change_info=True, pin_messages=True, send_stickers=False, send_messages=False, until_date=None, ) levels.ban = overrides.get("ban", 50) levels.kick = overrides.get("kick", 50) levels.redact = overrides.get("redact", 50) levels.invite = overrides.get("invite", 50 if dbr.invite_users else 0) levels.events[ EventType.ROOM_ENCRYPTION] = 50 if portal.matrix.e2ee else 99 levels.events[EventType.ROOM_TOMBSTONE] = 99 levels.events[EventType.ROOM_NAME] = 50 if dbr.change_info else 0 levels.events[EventType.ROOM_AVATAR] = 50 if dbr.change_info else 0 levels.events[EventType.ROOM_TOPIC] = 50 if dbr.change_info else 0 levels.events[ EventType.ROOM_PINNED_EVENTS] = 50 if dbr.pin_messages else 0 levels.events[EventType.ROOM_POWER_LEVELS] = 75 levels.events[EventType.ROOM_HISTORY_VISIBILITY] = 75 levels.events[ EventType. STICKER] = 50 if dbr.send_stickers else levels.events_default levels.state_default = overrides.get("state_default", 50) levels.users_default = overrides.get("users_default", 0) levels.events_default = overrides.get( "events_default", 50 if (portal.peer_type == "channel" and not entity.megagroup or entity.default_banned_rights.send_messages) else 0, ) for evt_type, value in overrides.get("events", {}).items(): levels.events[EventType.find(evt_type)] = value levels.users = overrides.get("users", {}) if portal.main_intent.mxid not in levels.users: levels.users[portal.main_intent.mxid] = 100 return levels
async def user_has_power_level(room_id: RoomID, intent: IntentAPI, sender: u.User, event: str) -> bool: if sender.is_admin: return True # Make sure the state store contains the power levels. try: await intent.get_power_levels(room_id) except MatrixRequestError: return False event_type = EventType.find(f"net.maunium.telegram.{event}", t_class=EventType.Class.STATE) return await intent.state_store.has_power_level(room_id, sender.mxid, event_type)
def _make_rule(self, name: str, rule: Dict[str, Any]) -> Rule: try: return Rule(rooms=set(rule.get("rooms", [])), not_rooms=set(rule.get("not_rooms", [])), matches=self._compile_all(rule["matches"]), not_matches=self._compile_all(rule.get("not_matches", [])), type=EventType.find(rule["type"]) if "type" in rule else None, template=self.templates[rule["template"]], variables=self._parse_variables(rule)) except Exception as e: raise ConfigError(f"Failed to load {name}") from e
async def can_user_perform(self, user: '******', event: str) -> bool: if user.is_admin: return True if not self.mxid: # No room for anybody to perform actions in return False try: await self.main_intent.get_power_levels(self.mxid) except MatrixRequestError: return False evt_type = EventType.find(f"net.maunium.telegram.{event}", t_class=EventType.Class.STATE) return await self.main_intent.state_store.has_power_level(self.mxid, user.mxid, evt_type)
def _filter(self) -> Filter: all_events = EventType.find("*") return Filter( account_data=EventFilter(types=[all_events]), presence=EventFilter(not_types=[all_events]), room=RoomFilter( include_leave=False, state=StateFilter(not_types=[all_events]), timeline=RoomEventFilter(not_types=[all_events]), account_data=RoomEventFilter(not_types=[all_events]), ephemeral=RoomEventFilter(not_types=[all_events]), ), )
def _create_sync_filter(self) -> Awaitable[FilterID]: all_events = EventType.find("*") return self.intent.create_filter( Filter( account_data=EventFilter(types=[all_events]), presence=EventFilter( types=[EventType.PRESENCE], senders=[self.custom_mxid] if self.only_handle_own_synced_events else None, ), room=RoomFilter( include_leave=False, state=StateFilter(not_types=[all_events]), timeline=RoomEventFilter(not_types=[all_events]), account_data=RoomEventFilter(not_types=[all_events]), ephemeral=RoomEventFilter(types=[ EventType.TYPING, EventType.RECEIPT, ]), ), ))
from mautrix.errors import MatrixError, MForbidden from mautrix.util.simple_lock import SimpleLock from .db import Portal as DBPortal, Message as DBMessage, Reaction as DBReaction from .config import Config from . import user as u, puppet as p, matrix as m if TYPE_CHECKING: from .__main__ import TwitterBridge try: from mautrix.crypto.attachments import encrypt_attachment, decrypt_attachment except ImportError: encrypt_attachment = decrypt_attachment = None StateBridge = EventType.find("m.bridge", EventType.Class.STATE) StateHalfShotBridge = EventType.find("uk.half-shot.bridge", EventType.Class.STATE) BackfillEntryTypes = Union[MessageEntry, ReactionCreateEntry, ReactionDeleteEntry] ReuploadedMediaInfo = NamedTuple('ReuploadedMediaInfo', mxc=Optional[ContentURI], decryption_info=Optional[EncryptedFile], mime_type=str, file_name=str, size=int) class Portal(DBPortal, BasePortal): by_mxid: Dict[RoomID, 'Portal'] = {} by_twid: Dict[Tuple[str, int], 'Portal'] = {}
from string import Template import asyncio import aiohttp import hashlib import feedparser from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper from mautrix.types import (StateEvent, EventType, MessageType, RoomID, EventID, PowerLevelStateEventContent) from maubot import Plugin, MessageEvent from maubot.handlers import command, event from .db import Database, Feed, Entry, Subscription rss_change_level = EventType.find("xyz.maubot.rss", t_class=EventType.Class.STATE) class Config(BaseProxyConfig): def do_update(self, helper: ConfigUpdateHelper) -> None: helper.copy("update_interval") helper.copy("max_backoff") helper.copy("spam_sleep") helper.copy("command_prefix") helper.copy("admins") class BoolArgument(command.Argument): def __init__(self, name: str, label: str = None, *, required: bool = False) -> None: super().__init__(name, label, required=required, pass_raw=False)
from pkg_resources import resource_string import re import asyncio import json from aiohttp import web from jinja2 import Template from mautrix.client import Client from mautrix.types import RoomID, UserID, StateEvent, EventType from mautrix.errors import MNotFound from maubot import Plugin, MessageEvent from maubot.handlers import command, event STATE_SWITCH_FRIEND_CODES = EventType("xyz.maubot.switch.friendcodes", EventType.Class.STATE) Profile = NamedTuple("Profile", displayname=str, avatar_url=str) class SwitchFriendCodeBot(Plugin): cache: Dict[RoomID, Dict[UserID, List[int]]] profiles: Dict[RoomID, Dict[UserID, Profile]] cache_lock: Dict[RoomID, asyncio.Lock] widget_tpl: Template code_regex: Pattern = re.compile( r"^(?:SW-)?([0-9]{4})-?([0-9]{4})-?([0-9]{4})$") async def start(self) -> None: await super().start() self.cache = {}