async def _start(self, try_n: Optional[int] = 0) -> None: if not self.enabled: self.log.debug("Not starting disabled client") return elif self.started: self.log.warning("Ignoring start() call to started client") return try: user_id = await self.client.whoami() except MatrixInvalidToken as e: self.log.error(f"Invalid token: {e}. Disabling client") self.db_instance.enabled = False return except Exception as e: if try_n >= 8: self.log.exception( "Failed to get /account/whoami, disabling client") self.db_instance.enabled = False else: self.log.warning(f"Failed to get /account/whoami, " f"retrying in {(try_n + 1) * 10}s: {e}") _ = asyncio.ensure_future(self.start(try_n + 1), loop=self.loop) return if user_id != self.id: self.log.error( f"User ID mismatch: expected {self.id}, but got {user_id}") self.db_instance.enabled = False return if not self.filter_id: self.db_instance.edit(filter_id=await self.client.create_filter( Filter( room=RoomFilter(timeline=RoomEventFilter( limit=50, lazy_load_members=True, ), state=StateFilter(lazy_load_members=True, )), presence=EventFilter(not_types=[EventType.PRESENCE], ), ))) if self.displayname != "disable": await self.client.set_displayname(self.displayname) if self.avatar_url != "disable": await self.client.set_avatar_url(self.avatar_url) if self.crypto: self.log.debug("Enabling end-to-end encryption support") await self.crypto_store.open() crypto_device_id = await self.crypto_store.get_device_id() if crypto_device_id and crypto_device_id != self.device_id: self.log.warning( "Mismatching device ID in crypto store and main database. " "Encryption may not work.") await self.crypto.load() if not crypto_device_id: await self.crypto_store.put_device_id(self.device_id) self.start_sync() await self._update_remote_profile() self.started = True self.log.info("Client started, starting plugin instances...") await self.start_plugins()
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]), ), )
async def get_filter(self, filter_id: FilterID) -> Filter: """ Download a filter. See also: `API reference <https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-user-userid-filter-filterid>`__ Args: filter_id: The filter ID to download. Returns: The filter data. """ content = await self.api.request(Method.GET, Path.user[self.mxid].filter[filter_id]) return Filter.deserialize(content)
def _create_sync_filter(self) -> Awaitable[FilterID]: return self.intent.create_filter( Filter(account_data=EventFilter(types=[]), room=RoomFilter( include_leave=False, state=RoomEventFilter(types=[]), timeline=RoomEventFilter(types=[]), account_data=RoomEventFilter(types=[]), ephemeral=RoomEventFilter(types=[ EventType.TYPING, EventType.RECEIPT, ]), ), presence=EventFilter( types=[EventType.PRESENCE], senders=[self.custom_mxid], )))
async def create_filter(self, filter_params: Filter) -> FilterID: """ Upload a new filter definition to the homeserver. See also: `API reference <https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-user-userid-filter>`__ Args: filter_params: The filter data. Returns: A filter ID that can be used in future requests to refer to the uploaded filter. """ resp = await self.api.request(Method.POST, Path.user[self.mxid].filter, filter_params.serialize() if isinstance(filter_params, Serializable) else filter_params) try: return resp["filter_id"] except KeyError: raise MatrixResponseError("`filter_id` not in response.")
async def _start(self, try_n: Optional[int] = 0) -> None: if not self.enabled: self.log.debug("Not starting disabled client") return elif self.started: self.log.warning("Ignoring start() call to started client") return try: user_id = await self.client.whoami() except MatrixInvalidToken as e: self.log.error(f"Invalid token: {e}. Disabling client") self.db_instance.enabled = False return except MatrixRequestError: if try_n >= 5: self.log.exception("Failed to get /account/whoami, disabling client") self.db_instance.enabled = False else: self.log.exception(f"Failed to get /account/whoami, " f"retrying in {(try_n + 1) * 10}s") _ = asyncio.ensure_future(self.start(try_n + 1), loop=self.loop) return if user_id != self.id: self.log.error(f"User ID mismatch: expected {self.id}, but got {user_id}") self.db_instance.enabled = False return if not self.filter_id: self.db_instance.filter_id = await self.client.create_filter(Filter( room=RoomFilter( timeline=RoomEventFilter( limit=50, ), ), )) if self.displayname != "disable": await self.client.set_displayname(self.displayname) if self.avatar_url != "disable": await self.client.set_avatar_url(self.avatar_url) self.start_sync() await self._update_remote_profile() self.started = True self.log.info("Client started, starting plugin instances...") await self.start_plugins()
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, ]), ), ))
async def main(): http_client = ClientSession(loop=loop) global client, bot client = MaubotMatrixClient( mxid=user_id, base_url=homeserver, token=access_token, client_session=http_client, loop=loop, store=SyncStoreProxy(nb), log=logging.getLogger("maubot.client").getChild(user_id)) while True: try: whoami_user_id = await client.whoami() except Exception: log.exception( "Failed to connect to homeserver, retrying in 10 seconds...") await asyncio.sleep(10) continue if whoami_user_id != user_id: log.fatal( f"User ID mismatch: configured {user_id}, but server said {whoami_user_id}" ) sys.exit(1) break if config["user.sync"]: if not nb.filter_id: nb.edit(filter_id=await client.create_filter( Filter(room=RoomFilter(timeline=RoomEventFilter(limit=50)), ))) client.start(nb.filter_id) if config["user.autojoin"]: log.debug("Autojoin is enabled") @client.on(EventType.ROOM_MEMBER) async def _handle_invite(evt: StrippedStateEvent) -> None: if evt.state_key == client.mxid and evt.content.membership == Membership.INVITE: await client.join_room(evt.room_id) displayname, avatar_url = config["user.displayname"], config[ "user.avatar_url"] if avatar_url != "disable": await client.set_avatar_url(avatar_url) if displayname != "disable": await client.set_displayname(displayname) bot = plugin(client=client, loop=loop, http=http_client, instance_id="__main__", log=logging.getLogger("maubot.instance.__main__"), config=bot_config, database=db if meta.database else None, webapp=None, webapp_url=None) await bot.internal_start()
async def _start(self, try_n: int | None = 0) -> None: if not self.enabled: self.log.debug("Not starting disabled client") return elif self.started: self.log.warning("Ignoring start() call to started client") return try: await self.client.versions() whoami = await self.client.whoami() except MatrixInvalidToken as e: self.log.error(f"Invalid token: {e}. Disabling client") self.enabled = False await self.update() return except Exception as e: if try_n >= 8: self.log.exception( "Failed to get /account/whoami, disabling client") self.enabled = False await self.update() else: self.log.warning( f"Failed to get /account/whoami, retrying in {(try_n + 1) * 10}s: {e}" ) _ = asyncio.create_task(self.start(try_n + 1)) return if whoami.user_id != self.id: self.log.error( f"User ID mismatch: expected {self.id}, but got {whoami.user_id}" ) self.enabled = False await self.update() return elif whoami.device_id and self.device_id and whoami.device_id != self.device_id: self.log.error( f"Device ID mismatch: expected {self.device_id}, but got {whoami.device_id}" ) self.enabled = False await self.update() return if not self.filter_id: self.filter_id = await self.client.create_filter( Filter( room=RoomFilter( timeline=RoomEventFilter( limit=50, lazy_load_members=True, ), state=StateFilter(lazy_load_members=True, ), ), presence=EventFilter(not_types=[EventType.PRESENCE], ), )) await self.update() if self.displayname != "disable": await self.client.set_displayname(self.displayname) if self.avatar_url != "disable": await self.client.set_avatar_url(self.avatar_url) if self.crypto: await self._start_crypto() self.start_sync() await self._update_remote_profile() self.started = True self.log.info("Client started, starting plugin instances...") await self.start_plugins()
async def main(): http_client = ClientSession(loop=loop) global client, bot await db.start() nb = await NextBatch(db, user_id).load() client_log = logging.getLogger("maubot.client").getChild(user_id) client = MaubotMatrixClient( mxid=user_id, base_url=homeserver, token=access_token, client_session=http_client, loop=loop, log=client_log, sync_store=nb, state_store=state_store, device_id=device_id, ) client.ignore_first_sync = config["user.ignore_first_sync"] client.ignore_initial_sync = config["user.ignore_initial_sync"] if crypto_store: await crypto_store.upgrade_table.upgrade(db) await state_store.upgrade_table.upgrade(db) await crypto_store.open() client.crypto = OlmMachine(client, crypto_store, state_store) crypto_device_id = await crypto_store.get_device_id() if crypto_device_id and crypto_device_id != device_id: log.fatal("Mismatching device ID in crypto store and config " f"(store: {crypto_device_id}, config: {device_id})") sys.exit(10) await client.crypto.load() if not crypto_device_id: await crypto_store.put_device_id(device_id) log.debug("Enabled encryption support") if web_runner: await web_runner.setup() site = web.TCPSite(web_runner, config["server.hostname"], config["server.port"]) await site.start() log.info(f"Web server listening on {site.name}") while True: try: whoami = await client.whoami() except Exception as e: log.error( f"Failed to connect to homeserver: {type(e).__name__}: {e}" " - retrying in 10 seconds...") await asyncio.sleep(10) continue if whoami.user_id != user_id: log.fatal( f"User ID mismatch: configured {user_id}, but server said {whoami.user_id}" ) sys.exit(11) elif whoami.device_id and device_id and whoami.device_id != device_id: log.fatal(f"Device ID mismatch: configured {device_id}, " f"but server said {whoami.device_id}") sys.exit(12) log.debug( f"Confirmed connection as {whoami.user_id} / {whoami.device_id}") break if config["user.sync"]: if not nb.filter_id: filter_id = await client.create_filter( Filter(room=RoomFilter(timeline=RoomEventFilter(limit=50)))) await nb.put_filter_id(filter_id) _ = client.start(nb.filter_id) if config["user.autojoin"]: log.debug("Autojoin is enabled") @client.on(EventType.ROOM_MEMBER) async def _handle_invite(evt: StrippedStateEvent) -> None: if evt.state_key == client.mxid and evt.content.membership == Membership.INVITE: await client.join_room(evt.room_id) displayname, avatar_url = config["user.displayname"], config[ "user.avatar_url"] if avatar_url != "disable": await client.set_avatar_url(avatar_url) if displayname != "disable": await client.set_displayname(displayname) plugin_log = cast(TraceLogger, logging.getLogger("maubot.instance.__main__")) if meta.database: if meta.database_type == DatabaseType.SQLALCHEMY: import sqlalchemy as sql plugin_db = sql.create_engine(config["database"]) if db.scheme == Scheme.SQLITE: log.warning( "Using SQLite with legacy plugins in standalone mode can cause database " "locking issues. Switching to Postgres or updating the plugin to use the " "new asyncpg/aiosqlite database interface is recommended.") elif meta.database_type == DatabaseType.ASYNCPG: plugin_db = db upgrade_table = plugin.get_db_upgrade_table() if upgrade_table: await upgrade_table.upgrade(plugin_db) else: log.fatal(f"Unsupported database type {meta.database_type}") sys.exit(13) else: plugin_db = None bot = plugin( client=client, loop=loop, http=http_client, instance_id="__main__", log=plugin_log, config=bot_config, database=plugin_db, webapp=plugin_webapp, webapp_url=public_url, loader=loader, ) await bot.internal_start()