diff --git a/mautrix_telegram/config.py b/mautrix_telegram/config.py index facb4806..77cc6bfb 100644 --- a/mautrix_telegram/config.py +++ b/mautrix_telegram/config.py @@ -250,7 +250,7 @@ class Config(DictWithRecursion): puppeting = level == "full" or admin user = level == "user" or puppeting relaybot = level == "relaybot" or user - return relaybot, user, puppeting, admin + return relaybot, user, puppeting, admin, level def get_permissions(self, mxid): permissions = self["bridge.permissions"] or {} diff --git a/mautrix_telegram/user.py b/mautrix_telegram/user.py index 917cbf73..1163e503 100644 --- a/mautrix_telegram/user.py +++ b/mautrix_telegram/user.py @@ -55,7 +55,8 @@ class User(AbstractUser): (self.relaybot_whitelisted, self.whitelisted, self.puppet_whitelisted, - self.is_admin) = config.get_permissions(self.mxid) + self.is_admin, + self.permissions) = config.get_permissions(self.mxid) self.by_mxid[mxid] = self if tgid: diff --git a/mautrix_telegram/web/provisioning/__init__.py b/mautrix_telegram/web/provisioning/__init__.py index becee3a2..dc8feace 100644 --- a/mautrix_telegram/web/provisioning/__init__.py +++ b/mautrix_telegram/web/provisioning/__init__.py @@ -36,14 +36,15 @@ class ProvisioningAPI(AuthAPI): self.app = web.Application(loop=loop, middlewares=[self.error_middleware]) portal_prefix = "/portal/{mxid:![^/]+}" - self.app.router.add_route("GET", f"{portal_prefix}", self.get_portal) + self.app.router.add_route("GET", f"{portal_prefix}", self.get_portal_by_mxid) + self.app.router.add_route("GET", "/portal/{tgid:-[0-9]+}", self.get_portal_by_tgid) # self.app.router.add_route("POST", portal_prefix + "/connect/{chat_id:[0-9]+}", # self.connect_chat) # self.app.router.add_route("POST", f"{portal_prefix}/create", self.create_chat) # self.app.router.add_route("POST", f"{portal_prefix}/disconnect", self.disconnect_chat) user_prefix = "/user/{mxid:@[^:]*:[^/]+}" - self.app.router.add_route("GET", f"{user_prefix}", self.get_me) + self.app.router.add_route("GET", f"{user_prefix}", self.get_user_info) self.app.router.add_route("GET", f"{user_prefix}/chats", self.get_chats) self.app.router.add_route("POST", f"{user_prefix}/send_bot_token", self.send_bot_token) @@ -51,11 +52,11 @@ class ProvisioningAPI(AuthAPI): self.app.router.add_route("POST", f"{user_prefix}/send_code", self.send_code) self.app.router.add_route("POST", f"{user_prefix}/send_password", self.send_password) - async def get_portal(self, request: web.Request) -> web.Response: + async def get_portal_by_mxid(self, request: web.Request) -> web.Response: mxid = request.match_info["mxid"] portal = Portal.get_by_mxid(mxid) if not portal: - return self.get_error_response(404, "room_not_found", + return self.get_error_response(404, "portal_not_found", "Portal with given Matrix ID not found.") return web.json_response({ "mxid": portal.mxid, @@ -67,22 +68,53 @@ class ProvisioningAPI(AuthAPI): "megagroup": portal.megagroup, }) - async def get_me(self, request: web.Request) -> web.Response: - data, user, err = await self.get_user_request_info(request, require_logged_in=True) + async def get_portal_by_tgid(self, request: web.Request) -> web.Response: + try: + tgid = int(request.match_info["tgid"]) + except ValueError: + return self.get_error_response(400, "tgid_invalid", + "Given chat ID is not an integer.") + + portal = Portal.get_by_tgid(tgid) + if not portal: + return self.get_error_response(404, "portal_not_found", + "Portal to given Telegram chat not found.") + return web.json_response({ + "mxid": portal.mxid, + "chat_id": get_peer_id(portal.peer), + "peer_type": portal.peer_type, + "title": portal.title, + "about": portal.about, + "username": portal.username, + "megagroup": portal.megagroup, + }) + + async def get_user_info(self, request: web.Request) -> web.Response: + data, user, err = await self.get_user_request_info(request, expect_logged_in=None, + require_puppeting=False) if err is not None: return err - me = await user.client.get_me() + user_data = None + if await user.is_logged_in(): + me = await user.client.get_me() + await user.update_info(me) + user_data = { + "id": user.tgid, + "username": user.username, + "first_name": me.first_name, + "last_name": me.last_name, + "phone": me.phone, + "is_bot": user.is_bot, + } return web.json_response({ - "username": me.username, - "first_name": me.first_name, - "last_name": me.last_name, - "phone": me.phone, - "is_bot": me.bot, + "telegram": user_data, + "mxid": user.mxid, + "permissions": user.permissions, }) async def get_chats(self, request: web.Request) -> web.Response: - data, user, err = await self.get_user_request_info(request, require_logged_in=True) + data, user, err = await self.get_user_request_info(request, expect_logged_in=True) if err is not None: return err @@ -172,23 +204,27 @@ class ProvisioningAPI(AuthAPI): except json.JSONDecodeError: return None - async def get_user(self, mxid: str, require_logged_in: bool = False + async def get_user(self, mxid: str, expect_logged_in: Optional[bool] = False, + require_puppeting: bool = True, ) -> Tuple[Optional[User], Optional[web.Response]]: user = await User.get_by_mxid(mxid).ensure_started(even_if_no_session=True) - if not user.puppet_whitelisted: + if require_puppeting and not user.puppet_whitelisted: return user, self.get_login_response(error="You are not whitelisted.", errcode="mxid_not_whitelisted", status=403) - logged_in = await user.is_logged_in() - if not require_logged_in and logged_in: - return user, self.get_login_response(username=user.username, status=409, - error="You are already logged in.", - errcode="already_logged_in") - elif require_logged_in and not logged_in: - return user, self.get_login_response(status=403, error="You are not logged in.", - errcode="not_logged_in") + if expect_logged_in is not None: + logged_in = await user.is_logged_in() + if not expect_logged_in and logged_in: + return user, self.get_login_response(username=user.username, status=409, + error="You are already logged in.", + errcode="already_logged_in") + elif expect_logged_in and not logged_in: + return user, self.get_login_response(status=403, error="You are not logged in.", + errcode="not_logged_in") return user, None - async def get_user_request_info(self, request: web.Request, require_logged_in: bool = False + async def get_user_request_info(self, request: web.Request, + expect_logged_in: Optional[bool] = False, + require_puppeting: bool = False, ) -> (Tuple[Optional[dict], Optional[User], Optional[web.Response]]): @@ -206,6 +242,6 @@ class ProvisioningAPI(AuthAPI): errcode="json_invalid", status=400) mxid = request.match_info["mxid"] - user, err = await self.get_user(mxid, require_logged_in) + user, err = await self.get_user(mxid, expect_logged_in, require_puppeting) return data, user, err diff --git a/mautrix_telegram/web/provisioning/spec.yaml b/mautrix_telegram/web/provisioning/spec.yaml index d3bd2ba8..775821b9 100644 --- a/mautrix_telegram/web/provisioning/spec.yaml +++ b/mautrix_telegram/web/provisioning/spec.yaml @@ -56,6 +56,52 @@ paths: description: The Matrix ID of the room whose bridging status to get required: true type: string + pattern: "![^/]+" + /portal/{chat_id}: + get: + operationId: get_portal_by_tgid + summary: Get the bridging status and info of the connected Telegram chat + tags: [Bridging] + responses: + 200: + description: Chat is bridged + schema: + $ref: "#/definitions/PortalInfo" + 400: + description: Invalid Telegram chat ID + schema: + type: object + title: Error + properties: + errcode: + type: string + title: Error code + description: A machine-readable error code + enum: + - tgid_invalid + error: + $ref: "#/definitions/HumanReadableError" + 404: + description: Unknown portal + schema: + type: object + title: Error + properties: + errcode: + type: string + title: Error code + description: A machine-readable error code + enum: + - portal_not_found + error: + $ref: "#/definitions/HumanReadableError" + parameters: + - name: chat_id + in: path + description: The Matrix ID of the room whose bridging status to get + required: true + type: integer + pattern: "-[0-9]+" /portal/{room_id}/connect/{chat_id}: post: operationId: connect_portal @@ -72,12 +118,20 @@ paths: description: The ID of the Telegram chat to connect required: true type: integer - format: int32 + pattern: "-[0-9]+" + - name: force + in: query + description: Set to force bridging by unbridging or deleting existing portal rooms. + required: false + type: string + enum: + - delete + - unbridge responses: 400: $ref: "#/responses/MissingMXIDError" 409: - description: Room is already bridged + description: Matrix room or Telegram chat is already bridged schema: type: object title: Error @@ -86,8 +140,10 @@ paths: type: string title: Error code description: A machine-readable error code + example: _already_bridged enum: - room_already_bridged + - chat_already_bridged error: $ref: "#/definitions/HumanReadableError" /portal/{room_id}/create: @@ -156,26 +212,13 @@ paths: tags: [User info] responses: 200: - description: User is logged in + description: User found schema: - $ref: "#/definitions/AuthInfo" + $ref: "#/definitions/UserInfo" 400: $ref: "#/responses/MissingMXIDError" 403: - description: User is not logged in or not whitelisted - schema: - type: object - title: Error - properties: - errcode: - type: string - title: Error code - description: A machine-readable error code - enum: - - not_logged_in - - mxid_not_whitelisted - error: - $ref: "#/definitions/HumanReadableError" + $ref: "#/responses/NotWhitelistedError" 500: $ref: "#/responses/UnknownError" parameters: @@ -582,24 +625,42 @@ definitions: type: string description: A human-readable description of the error example: A human-readable description of the error - AuthInfo: + UserInfo: type: object properties: - username: + mxid: type: string - example: username - first_name: + example: "@usern:example.com" + permissions: type: string - example: Usern - last_name: - type: string - example: A. - phone: - type: string - example: +123456789 - is_bot: - type: boolean - example: false + example: user + enum: + - none + - relaybot + - user + - full + - admin + telegram: + type: object + properties: + id: + type: integer + example: 123456789 + username: + type: string + example: username + first_name: + type: string + example: Usern + last_name: + type: string + example: A. + phone: + type: string + example: +123456789 + is_bot: + type: boolean + example: false UserChats: type: array items: