Пример #1
0
 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()
Пример #2
0
 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]),
         ),
     )
Пример #3
0
    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)
Пример #4
0
 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],
                )))
Пример #5
0
    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.")
Пример #6
0
 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()
Пример #7
0
 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,
                 ]),
             ),
         ))
Пример #8
0
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()
Пример #9
0
 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()
Пример #10
0
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()