mirror of
https://github.com/mautrix/telegram.git
synced 2026-05-17 07:25:46 +03:00
Add provisioning API for listing contacts and starting DMs
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -2,6 +2,8 @@
|
||||
|
||||
### Added
|
||||
* Added `list-invite-links` command to list invite links in a chat.
|
||||
* Added option to use [MSC2246] async media uploads.
|
||||
* Provisioning API for listing contacts and starting private chats.
|
||||
|
||||
### Improved
|
||||
* Dropped Python 3.7 support.
|
||||
@@ -30,6 +32,8 @@
|
||||
to user mentions otherwise.
|
||||
* Fixed newlines being lost in unformatted forwarded messages.
|
||||
|
||||
[MSC2246]: https://github.com/matrix-org/matrix-spec-proposals/pull/2246
|
||||
|
||||
# v0.11.2 (2022-02-14)
|
||||
|
||||
**N.B.** This will be the last release to support Python 3.7. Future versions
|
||||
@@ -261,8 +265,8 @@ path.
|
||||
* Bridging events of a user whose power level is malformed (i.e. a string
|
||||
instead of an integer) now works.
|
||||
|
||||
[MSC2409]: https://github.com/matrix-org/matrix-doc/pull/2409
|
||||
[MSC2778]: https://github.com/matrix-org/matrix-doc/pull/2778
|
||||
[MSC2409]: https://github.com/matrix-org/matrix-spec-proposals/pull/2409
|
||||
[MSC2778]: https://github.com/matrix-org/matrix-spec-proposals/pull/2778
|
||||
|
||||
# v0.8.2 (2020-07-27)
|
||||
|
||||
@@ -310,7 +314,7 @@ update (v0.5.8) and a fix to the Docker image.
|
||||
* Fixed `sync_direct_chats` option creating non-working portals.
|
||||
* Fixed video thumbnailing sometimes leaving behind downloaded videos in `/tmp`.
|
||||
|
||||
[MSC2346]: https://github.com/matrix-org/matrix-doc/pull/2346
|
||||
[MSC2346]: https://github.com/matrix-org/matrix-spec-proposals/pull/2346
|
||||
|
||||
## rc1 (2020-04-25)
|
||||
|
||||
|
||||
@@ -129,6 +129,14 @@ class Puppet(DBPuppet, BasePuppet):
|
||||
PeerChannel(channel_id=self.tgid) if self.is_channel else PeerUser(user_id=self.tgid)
|
||||
)
|
||||
|
||||
@property
|
||||
def contact_info(self) -> dict:
|
||||
return {
|
||||
"name": self.displayname,
|
||||
"username": self.username,
|
||||
"is_bot": self.is_bot,
|
||||
}
|
||||
|
||||
@property
|
||||
def plain_displayname(self) -> str:
|
||||
return self.displayname_template.parse(self.displayname) or self.displayname
|
||||
|
||||
@@ -659,20 +659,28 @@ class User(DBUser, AbstractUser, BaseUser):
|
||||
acc = (acc * 20261 + contact) & 0xFFFFFFFF
|
||||
return acc & 0x7FFFFFFF
|
||||
|
||||
async def sync_contacts(self) -> None:
|
||||
async def sync_contacts(self, get_info: bool = False) -> dict[TelegramID, dict]:
|
||||
existing_contacts = await self.get_contacts()
|
||||
contact_hash = self._hash_contacts(self.saved_contacts, existing_contacts)
|
||||
response = await self.client(GetContactsRequest(hash=contact_hash))
|
||||
if isinstance(response, ContactsNotModified):
|
||||
return
|
||||
if get_info:
|
||||
return {
|
||||
tgid: (await pu.Puppet.get_by_tgid(tgid)).contact_info
|
||||
for tgid in existing_contacts
|
||||
}
|
||||
return {}
|
||||
self.log.debug(f"Updating contacts of {self.name}...")
|
||||
if self.saved_contacts != response.saved_count:
|
||||
self.saved_contacts = response.saved_count
|
||||
await self.save()
|
||||
contacts = {}
|
||||
for user in response.users:
|
||||
puppet = await pu.Puppet.get_by_tgid(user.id)
|
||||
puppet: pu.Puppet = await pu.Puppet.get_by_tgid(user.id)
|
||||
await puppet.update_info(self, user)
|
||||
await self.set_contacts(user.id for user in response.users)
|
||||
contacts[user.id] = puppet.contact_info
|
||||
await self.set_contacts(contacts.keys())
|
||||
return contacts
|
||||
|
||||
# endregion
|
||||
# region Class instance lookup
|
||||
|
||||
@@ -21,7 +21,7 @@ import json
|
||||
import logging
|
||||
|
||||
from aiohttp import web
|
||||
from telethon.tl.types import ChannelForbidden, ChatForbidden, TypeChat
|
||||
from telethon.tl.types import ChannelForbidden, ChatForbidden, TypeChat, User as TLUser
|
||||
from telethon.utils import get_peer_id, resolve_id
|
||||
|
||||
from mautrix.appservice import AppService
|
||||
@@ -65,6 +65,8 @@ class ProvisioningAPI(AuthAPI):
|
||||
user_prefix = "/user/{mxid:@[^:]*:[^/]+}"
|
||||
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("GET", f"{user_prefix}/contacts", self.get_contacts)
|
||||
self.app.router.add_route("POST", f"{user_prefix}/pm/{{identifier}}", self.start_dm)
|
||||
|
||||
self.app.router.add_route("POST", f"{user_prefix}/logout", self.logout)
|
||||
self.app.router.add_route("POST", f"{user_prefix}/login/bot_token", self.send_bot_token)
|
||||
@@ -393,6 +395,62 @@ class ProvisioningAPI(AuthAPI):
|
||||
]
|
||||
)
|
||||
|
||||
async def get_contacts(self, request: web.Request) -> web.Response:
|
||||
data, user, err = await self.get_user_request_info(request, expect_logged_in=True)
|
||||
if err is not None:
|
||||
return err
|
||||
return web.json_response(data=await user.sync_contacts())
|
||||
|
||||
async def start_dm(self, request: web.Request) -> web.Response:
|
||||
data, user, err = await self.get_user_request_info(request, expect_logged_in=True)
|
||||
if err is not None:
|
||||
return err
|
||||
try:
|
||||
identifier: str | int = request.match_info["identifier"]
|
||||
if isinstance(identifier, str) and identifier.isdecimal():
|
||||
identifier = int(identifier)
|
||||
target = await user.client.get_entity(identifier)
|
||||
except ValueError:
|
||||
return web.json_response(
|
||||
{
|
||||
"error": "Invalid user identifier or user not found.",
|
||||
"errcode": "M_NOT_FOUND",
|
||||
},
|
||||
status=404,
|
||||
)
|
||||
|
||||
if not target:
|
||||
return web.json_response(
|
||||
{
|
||||
"error": "User not found.",
|
||||
"errcode": "M_NOT_FOUND",
|
||||
},
|
||||
status=404,
|
||||
)
|
||||
elif not isinstance(target, TLUser):
|
||||
return web.json_response(
|
||||
{
|
||||
"error": "Identifier is not a user.",
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
portal = await Portal.get_by_entity(target, tg_receiver=user.tgid)
|
||||
puppet = await portal.get_dm_puppet()
|
||||
if portal.mxid:
|
||||
just_created = False
|
||||
else:
|
||||
await portal.create_matrix_room(user, target, [user.mxid])
|
||||
just_created = True
|
||||
return web.json_response(
|
||||
{
|
||||
"room_id": portal.mxid,
|
||||
"just_created": just_created,
|
||||
"id": portal.tgid,
|
||||
"contact_info": puppet.contact_info,
|
||||
},
|
||||
status=201 if just_created else 200,
|
||||
)
|
||||
|
||||
async def send_bot_token(self, request: web.Request) -> web.Response:
|
||||
data, user, err = await self.get_user_request_info(request)
|
||||
if err is not None:
|
||||
@@ -574,7 +632,7 @@ class ProvisioningAPI(AuthAPI):
|
||||
data = None
|
||||
if want_data and (request.method == "POST" or request.method == "PUT"):
|
||||
data = await self.get_data(request)
|
||||
if not data:
|
||||
if data is None:
|
||||
return (
|
||||
None,
|
||||
None,
|
||||
|
||||
Reference in New Issue
Block a user