Add proper support for receiving messages sent as a channel. Fixes #740
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
* Added simple fallback message for live location and venue messages from Telegram.
|
* Added simple fallback message for live location and venue messages from Telegram.
|
||||||
* Added support for `t.me/+code` style invite links in `!tg join`.
|
* Added support for `t.me/+code` style invite links in `!tg join`.
|
||||||
|
* Added support for showing channel profile when users send messages as a channel.
|
||||||
* Fixed bug in v0.11.0 that broke `!tg create`.
|
* Fixed bug in v0.11.0 that broke `!tg create`.
|
||||||
|
|
||||||
# v0.11.1 (2021-01-10)
|
# v0.11.1 (2021-01-10)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
||||||
# Copyright (C) 2021 Tulir Asokan
|
# Copyright (C) 2022 Tulir Asokan
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any, Type, Union
|
from typing import TYPE_CHECKING, Any, Union
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
@@ -34,6 +34,7 @@ from telethon.tl.types import (
|
|||||||
Chat,
|
Chat,
|
||||||
MessageActionChannelMigrateFrom,
|
MessageActionChannelMigrateFrom,
|
||||||
MessageEmpty,
|
MessageEmpty,
|
||||||
|
PeerChannel,
|
||||||
PeerChat,
|
PeerChat,
|
||||||
PeerUser,
|
PeerUser,
|
||||||
TypeUpdate,
|
TypeUpdate,
|
||||||
@@ -147,7 +148,7 @@ class AbstractUser(ABC):
|
|||||||
return self.client and self.client.is_connected()
|
return self.client and self.client.is_connected()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _proxy_settings(self) -> tuple[Type[Connection], tuple[Any, ...] | None]:
|
def _proxy_settings(self) -> tuple[type[Connection], tuple[Any, ...] | None]:
|
||||||
proxy_type = self.config["telegram.proxy.type"].lower()
|
proxy_type = self.config["telegram.proxy.type"].lower()
|
||||||
connection = ConnectionTcpFull
|
connection = ConnectionTcpFull
|
||||||
connection_data = (
|
connection_data = (
|
||||||
@@ -385,7 +386,7 @@ class AbstractUser(ABC):
|
|||||||
if not message:
|
if not message:
|
||||||
return
|
return
|
||||||
|
|
||||||
puppet = await pu.Puppet.get_by_tgid(TelegramID(update.peer.user_id))
|
puppet = await pu.Puppet.get_by_peer(update.peer)
|
||||||
await puppet.intent.mark_read(portal.mxid, message.mxid)
|
await puppet.intent.mark_read(portal.mxid, message.mxid)
|
||||||
|
|
||||||
async def update_own_read_receipt(
|
async def update_own_read_receipt(
|
||||||
@@ -444,10 +445,7 @@ class AbstractUser(ABC):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(update, (UpdateChannelUserTyping, UpdateChatUserTyping)):
|
if isinstance(update, (UpdateChannelUserTyping, UpdateChatUserTyping)):
|
||||||
# Can typing notifications come from non-user peers?
|
sender = await pu.Puppet.get_by_peer(update.from_id)
|
||||||
if not update.from_id.user_id:
|
|
||||||
return
|
|
||||||
sender = await pu.Puppet.get_by_tgid(TelegramID(update.from_id.user_id))
|
|
||||||
|
|
||||||
if not sender or not portal or not portal.mxid:
|
if not sender or not portal or not portal.mxid:
|
||||||
return
|
return
|
||||||
@@ -456,8 +454,8 @@ class AbstractUser(ABC):
|
|||||||
|
|
||||||
async def _handle_entity_updates(self, entities: dict[int, User | Chat | Channel]) -> None:
|
async def _handle_entity_updates(self, entities: dict[int, User | Chat | Channel]) -> None:
|
||||||
try:
|
try:
|
||||||
users = (entity for entity in entities.values() if isinstance(entity, User))
|
users = (entity for entity in entities.values() if isinstance(entity, (User, Channel)))
|
||||||
puppets = ((await pu.Puppet.get_by_tgid(TelegramID(user.id)), user) for user in users)
|
puppets = ((await pu.Puppet.get_by_peer(user), user) for user in users)
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
*[puppet.try_update_info(self, info) async for puppet, info in puppets if puppet]
|
*[puppet.try_update_info(self, info) async for puppet, info in puppets if puppet]
|
||||||
)
|
)
|
||||||
@@ -515,8 +513,8 @@ class AbstractUser(ABC):
|
|||||||
portal = await po.Portal.get_by_entity(update.peer_id, tg_receiver=self.tgid)
|
portal = await po.Portal.get_by_entity(update.peer_id, tg_receiver=self.tgid)
|
||||||
if update.out:
|
if update.out:
|
||||||
sender = await pu.Puppet.get_by_tgid(self.tgid)
|
sender = await pu.Puppet.get_by_tgid(self.tgid)
|
||||||
elif isinstance(update.from_id, PeerUser):
|
elif isinstance(update.from_id, (PeerUser, PeerChannel)):
|
||||||
sender = await pu.Puppet.get_by_tgid(TelegramID(update.from_id.user_id))
|
sender = await pu.Puppet.get_by_peer(update.from_id)
|
||||||
else:
|
else:
|
||||||
sender = None
|
sender = None
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
||||||
# Copyright (C) 2021 Tulir Asokan
|
# Copyright (C) 2022 Tulir Asokan
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@@ -45,6 +45,7 @@ class Puppet:
|
|||||||
username: str | None
|
username: str | None
|
||||||
photo_id: str | None
|
photo_id: str | None
|
||||||
is_bot: bool | None
|
is_bot: bool | None
|
||||||
|
is_channel: bool
|
||||||
|
|
||||||
custom_mxid: UserID | None
|
custom_mxid: UserID | None
|
||||||
access_token: str | None
|
access_token: str | None
|
||||||
@@ -61,7 +62,7 @@ class Puppet:
|
|||||||
|
|
||||||
columns: ClassVar[str] = (
|
columns: ClassVar[str] = (
|
||||||
"id, is_registered, displayname, displayname_source, displayname_contact, "
|
"id, is_registered, displayname, displayname_source, displayname_contact, "
|
||||||
"displayname_quality, disable_updates, username, photo_id, is_bot, "
|
"displayname_quality, disable_updates, username, photo_id, is_bot, is_channel, "
|
||||||
"custom_mxid, access_token, next_batch, base_url"
|
"custom_mxid, access_token, next_batch, base_url"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -103,6 +104,7 @@ class Puppet:
|
|||||||
self.username,
|
self.username,
|
||||||
self.photo_id,
|
self.photo_id,
|
||||||
self.is_bot,
|
self.is_bot,
|
||||||
|
self.is_channel,
|
||||||
self.custom_mxid,
|
self.custom_mxid,
|
||||||
self.access_token,
|
self.access_token,
|
||||||
self.next_batch,
|
self.next_batch,
|
||||||
@@ -114,7 +116,7 @@ class Puppet:
|
|||||||
"UPDATE puppet "
|
"UPDATE puppet "
|
||||||
"SET is_registered=$2, displayname=$3, displayname_source=$4, displayname_contact=$5,"
|
"SET is_registered=$2, displayname=$3, displayname_source=$4, displayname_contact=$5,"
|
||||||
" displayname_quality=$6, disable_updates=$7, username=$8, photo_id=$9, is_bot=$10,"
|
" displayname_quality=$6, disable_updates=$7, username=$8, photo_id=$9, is_bot=$10,"
|
||||||
" custom_mxid=$11, access_token=$12, next_batch=$13, base_url=$14 "
|
" is_channel=$11, custom_mxid=$12, access_token=$13, next_batch=$14, base_url=$15 "
|
||||||
"WHERE id=$1"
|
"WHERE id=$1"
|
||||||
)
|
)
|
||||||
await self.db.execute(q, *self._values)
|
await self.db.execute(q, *self._values)
|
||||||
@@ -123,8 +125,8 @@ class Puppet:
|
|||||||
q = (
|
q = (
|
||||||
"INSERT INTO puppet ("
|
"INSERT INTO puppet ("
|
||||||
" id, is_registered, displayname, displayname_source, displayname_contact,"
|
" id, is_registered, displayname, displayname_source, displayname_contact,"
|
||||||
" displayname_quality, disable_updates, username, photo_id, is_bot,"
|
" displayname_quality, disable_updates, username, photo_id, is_bot, is_channel,"
|
||||||
" custom_mxid, access_token, next_batch, base_url"
|
" custom_mxid, access_token, next_batch, base_url"
|
||||||
") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)"
|
") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)"
|
||||||
)
|
)
|
||||||
await self.db.execute(q, *self._values)
|
await self.db.execute(q, *self._values)
|
||||||
|
|||||||
@@ -2,4 +2,10 @@ from mautrix.util.async_db import UpgradeTable
|
|||||||
|
|
||||||
upgrade_table = UpgradeTable()
|
upgrade_table = UpgradeTable()
|
||||||
|
|
||||||
from . import v01_initial_revision, v02_sponsored_events, v03_reactions, v04_disappearing_messages
|
from . import (
|
||||||
|
v01_initial_revision,
|
||||||
|
v02_sponsored_events,
|
||||||
|
v03_reactions,
|
||||||
|
v04_disappearing_messages,
|
||||||
|
v05_channel_ghosts,
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
||||||
|
# Copyright (C) 2022 Tulir Asokan
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
from asyncpg import Connection
|
||||||
|
|
||||||
|
from . import upgrade_table
|
||||||
|
|
||||||
|
|
||||||
|
@upgrade_table.register(description="Add separate ghost users for channel senders")
|
||||||
|
async def upgrade_v5(conn: Connection, scheme: str) -> None:
|
||||||
|
await conn.execute("ALTER TABLE puppet ADD COLUMN is_channel BOOLEAN NOT NULL DEFAULT false")
|
||||||
|
if scheme == "postgres":
|
||||||
|
await conn.execute("ALTER TABLE puppet ALTER COLUMN is_channel DROP DEFAULT")
|
||||||
@@ -94,9 +94,7 @@ async def _add_forward_header(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not fwd_from_text:
|
if not fwd_from_text:
|
||||||
puppet = await pu.Puppet.get_by_tgid(
|
puppet = await pu.Puppet.get_by_peer(fwd_from.from_id, create=False)
|
||||||
TelegramID(fwd_from.from_id.user_id), create=False
|
|
||||||
)
|
|
||||||
if puppet and puppet.displayname:
|
if puppet and puppet.displayname:
|
||||||
fwd_from_text = puppet.displayname or puppet.mxid
|
fwd_from_text = puppet.displayname or puppet.mxid
|
||||||
fwd_from_html = (
|
fwd_from_html = (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
||||||
# Copyright (C) 2021 Tulir Asokan
|
# Copyright (C) 2022 Tulir Asokan
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@@ -66,11 +66,19 @@ class MatrixHandler(BaseMatrixHandler):
|
|||||||
) -> None:
|
) -> None:
|
||||||
intent = puppet.default_mxid_intent
|
intent = puppet.default_mxid_intent
|
||||||
self.log.debug(f"{inviter.mxid} invited puppet for {puppet.tgid} to {room_id}")
|
self.log.debug(f"{inviter.mxid} invited puppet for {puppet.tgid} to {room_id}")
|
||||||
|
if puppet.is_channel:
|
||||||
|
self.log.debug(f"Rejecting invite for {puppet.tgid} to {room_id}: puppet is a channel")
|
||||||
|
await intent.leave_room(room_id, reason="Channels can't be invited to chats")
|
||||||
|
return
|
||||||
|
|
||||||
if not await inviter.is_logged_in():
|
if not await inviter.is_logged_in():
|
||||||
await intent.error_and_leave(
|
self.log.debug(f"Rejecting invite for {puppet.tgid} to {room_id}: user not logged in")
|
||||||
room_id, text="Please log in before inviting Telegram puppets."
|
await intent.leave_room(
|
||||||
|
room_id,
|
||||||
|
reason="Only users who are logged into the bridge can invite Telegram ghosts.",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
portal = await po.Portal.get_by_mxid(room_id)
|
portal = await po.Portal.get_by_mxid(room_id)
|
||||||
if portal:
|
if portal:
|
||||||
if portal.peer_type == "user":
|
if portal.peer_type == "user":
|
||||||
|
|||||||
+17
-15
@@ -1,5 +1,5 @@
|
|||||||
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
||||||
# Copyright (C) 2021 Tulir Asokan
|
# Copyright (C) 2022 Tulir Asokan
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@@ -561,6 +561,8 @@ class Portal(DBPortal, BasePortal):
|
|||||||
await self.update_bridge_info()
|
await self.update_bridge_info()
|
||||||
|
|
||||||
async def invite_telegram(self, source: u.User, puppet: p.Puppet | au.AbstractUser) -> None:
|
async def invite_telegram(self, source: u.User, puppet: p.Puppet | au.AbstractUser) -> None:
|
||||||
|
if puppet.is_channel:
|
||||||
|
raise ValueError("Can't invite channels to chats")
|
||||||
if self.peer_type == "chat":
|
if self.peer_type == "chat":
|
||||||
await source.client(
|
await source.client(
|
||||||
AddChatUserRequest(chat_id=self.tgid, user_id=puppet.tgid, fwd_limit=0)
|
AddChatUserRequest(chat_id=self.tgid, user_id=puppet.tgid, fwd_limit=0)
|
||||||
@@ -945,11 +947,12 @@ class Portal(DBPortal, BasePortal):
|
|||||||
if user_mxid == self.az.bot_mxid:
|
if user_mxid == self.az.bot_mxid:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
puppet_id = p.Puppet.get_id_from_mxid(user_mxid)
|
puppet = await p.Puppet.get_by_mxid(user_mxid)
|
||||||
if puppet_id:
|
if puppet:
|
||||||
if puppet_id in allowed_tgids:
|
# TODO figure out when/how to clean up channels from the member list
|
||||||
|
if puppet.id in allowed_tgids or puppet.is_channel:
|
||||||
continue
|
continue
|
||||||
if self.bot and puppet_id == self.bot.tgid:
|
if self.bot and puppet.id == self.bot.tgid:
|
||||||
await self.bot.remove_chat(self.tgid)
|
await self.bot.remove_chat(self.tgid)
|
||||||
try:
|
try:
|
||||||
await self.main_intent.kick_user(
|
await self.main_intent.kick_user(
|
||||||
@@ -2737,8 +2740,8 @@ class Portal(DBPortal, BasePortal):
|
|||||||
messages = client.iter_messages(entity, reverse=True, min_id=min_id)
|
messages = client.iter_messages(entity, reverse=True, min_id=min_id)
|
||||||
async for message in messages:
|
async for message in messages:
|
||||||
sender = (
|
sender = (
|
||||||
await p.Puppet.get_by_tgid(TelegramID(message.from_id.user_id))
|
await p.Puppet.get_by_peer(message.from_id)
|
||||||
if isinstance(message.from_id, PeerUser)
|
if isinstance(message.from_id, (PeerUser, PeerChannel))
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
# TODO handle service messages?
|
# TODO handle service messages?
|
||||||
@@ -2749,8 +2752,8 @@ class Portal(DBPortal, BasePortal):
|
|||||||
messages = await client.get_messages(entity, limit=limit)
|
messages = await client.get_messages(entity, limit=limit)
|
||||||
for message in reversed(messages):
|
for message in reversed(messages):
|
||||||
sender = (
|
sender = (
|
||||||
await p.Puppet.get_by_tgid(TelegramID(message.from_id.user_id))
|
await p.Puppet.get_by_peer(message.from_id)
|
||||||
if isinstance(message.from_id, PeerUser)
|
if isinstance(message.from_id, (PeerUser, PeerChannel))
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
await self.handle_telegram_message(source, sender, message)
|
await self.handle_telegram_message(source, sender, message)
|
||||||
@@ -2840,10 +2843,9 @@ class Portal(DBPortal, BasePortal):
|
|||||||
self, msg: DBMessage, reaction_list: list[MessagePeerReaction], total_count: int
|
self, msg: DBMessage, reaction_list: list[MessagePeerReaction], total_count: int
|
||||||
) -> None:
|
) -> None:
|
||||||
reactions = {
|
reactions = {
|
||||||
reaction.peer_id.user_id: reaction.reaction
|
p.Puppet.get_id_from_peer(reaction.peer_id): reaction.reaction
|
||||||
for reaction in reaction_list
|
for reaction in reaction_list
|
||||||
# TODO allow PeerChannel once channel senders are properly supported
|
if isinstance(reaction.peer_id, (PeerUser, PeerChannel))
|
||||||
if isinstance(reaction.peer_id, PeerUser)
|
|
||||||
}
|
}
|
||||||
is_full = len(reactions) == total_count
|
is_full = len(reactions) == total_count
|
||||||
|
|
||||||
@@ -2954,10 +2956,10 @@ class Portal(DBPortal, BasePortal):
|
|||||||
|
|
||||||
if sender and not sender.displayname:
|
if sender and not sender.displayname:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"Telegram user {sender.tgid} sent a message, but doesn't have a "
|
f"Telegram user {sender.tgid} sent a message, but doesn't have a displayname,"
|
||||||
"displayname, updating info..."
|
" updating info..."
|
||||||
)
|
)
|
||||||
entity = await source.client.get_entity(PeerUser(sender.tgid))
|
entity = await source.client.get_entity(sender.peer)
|
||||||
await sender.update_info(source, entity)
|
await sender.update_info(source, entity)
|
||||||
if not sender.displayname:
|
if not sender.displayname:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
|
|||||||
+69
-22
@@ -1,5 +1,5 @@
|
|||||||
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
||||||
# Copyright (C) 2021 Tulir Asokan
|
# Copyright (C) 2022 Tulir Asokan
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@@ -20,10 +20,18 @@ from difflib import SequenceMatcher
|
|||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
from telethon.tl.types import (
|
from telethon.tl.types import (
|
||||||
|
Channel,
|
||||||
|
ChatPhoto,
|
||||||
|
ChatPhotoEmpty,
|
||||||
InputPeerPhotoFileLocation,
|
InputPeerPhotoFileLocation,
|
||||||
|
PeerChannel,
|
||||||
|
PeerChat,
|
||||||
PeerUser,
|
PeerUser,
|
||||||
|
TypeChatPhoto,
|
||||||
TypeInputPeer,
|
TypeInputPeer,
|
||||||
TypeInputUser,
|
TypeInputUser,
|
||||||
|
TypePeer,
|
||||||
|
TypeUserProfilePhoto,
|
||||||
UpdateUserName,
|
UpdateUserName,
|
||||||
User,
|
User,
|
||||||
UserProfilePhoto,
|
UserProfilePhoto,
|
||||||
@@ -67,6 +75,7 @@ class Puppet(DBPuppet, BasePuppet):
|
|||||||
username: str | None = None,
|
username: str | None = None,
|
||||||
photo_id: str | None = None,
|
photo_id: str | None = None,
|
||||||
is_bot: bool = False,
|
is_bot: bool = False,
|
||||||
|
is_channel: bool = False,
|
||||||
custom_mxid: UserID | None = None,
|
custom_mxid: UserID | None = None,
|
||||||
access_token: str | None = None,
|
access_token: str | None = None,
|
||||||
next_batch: SyncToken | None = None,
|
next_batch: SyncToken | None = None,
|
||||||
@@ -83,6 +92,7 @@ class Puppet(DBPuppet, BasePuppet):
|
|||||||
username=username,
|
username=username,
|
||||||
photo_id=photo_id,
|
photo_id=photo_id,
|
||||||
is_bot=is_bot,
|
is_bot=is_bot,
|
||||||
|
is_channel=is_channel,
|
||||||
custom_mxid=custom_mxid,
|
custom_mxid=custom_mxid,
|
||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
next_batch=next_batch,
|
next_batch=next_batch,
|
||||||
@@ -109,7 +119,9 @@ class Puppet(DBPuppet, BasePuppet):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def peer(self) -> PeerUser:
|
def peer(self) -> PeerUser:
|
||||||
return PeerUser(user_id=self.tgid)
|
return (
|
||||||
|
PeerChannel(channel_id=self.tgid) if self.is_channel else PeerUser(user_id=self.tgid)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def plain_displayname(self) -> str:
|
def plain_displayname(self) -> str:
|
||||||
@@ -185,9 +197,12 @@ class Puppet(DBPuppet, BasePuppet):
|
|||||||
return name
|
return name
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_displayname(cls, info: User, enable_format: bool = True) -> tuple[str, int]:
|
def get_displayname(cls, info: User | Channel, enable_format: bool = True) -> tuple[str, int]:
|
||||||
fn = cls._filter_name(info.first_name)
|
if isinstance(info, Channel):
|
||||||
ln = cls._filter_name(info.last_name)
|
fn, ln = cls._filter_name(info.title), ""
|
||||||
|
else:
|
||||||
|
fn = cls._filter_name(info.first_name)
|
||||||
|
ln = cls._filter_name(info.last_name)
|
||||||
data = {
|
data = {
|
||||||
"phone number": info.phone if hasattr(info, "phone") else None,
|
"phone number": info.phone if hasattr(info, "phone") else None,
|
||||||
"username": info.username,
|
"username": info.username,
|
||||||
@@ -214,14 +229,20 @@ class Puppet(DBPuppet, BasePuppet):
|
|||||||
|
|
||||||
return (cls.displayname_template.format_full(name) if enable_format else name), quality
|
return (cls.displayname_template.format_full(name) if enable_format else name), quality
|
||||||
|
|
||||||
async def try_update_info(self, source: au.AbstractUser, info: User) -> None:
|
async def try_update_info(self, source: au.AbstractUser, info: User | Channel) -> None:
|
||||||
try:
|
try:
|
||||||
await self.update_info(source, info)
|
await self.update_info(source, info)
|
||||||
except Exception:
|
except Exception:
|
||||||
source.log.exception(f"Failed to update info of {self.tgid}")
|
source.log.exception(f"Failed to update info of {self.tgid}")
|
||||||
|
|
||||||
async def update_info(self, source: au.AbstractUser, info: User) -> None:
|
async def update_info(self, source: au.AbstractUser, info: User | Channel) -> None:
|
||||||
changed = False
|
is_bot = False if isinstance(info, Channel) else info.bot
|
||||||
|
is_channel = isinstance(info, Channel)
|
||||||
|
changed = is_bot != self.is_bot or is_channel != self.is_channel
|
||||||
|
|
||||||
|
self.is_bot = is_bot
|
||||||
|
self.is_channel = is_channel
|
||||||
|
|
||||||
if self.username != info.username:
|
if self.username != info.username:
|
||||||
self.username = info.username
|
self.username = info.username
|
||||||
changed = True
|
changed = True
|
||||||
@@ -233,32 +254,32 @@ class Puppet(DBPuppet, BasePuppet):
|
|||||||
except Exception:
|
except Exception:
|
||||||
self.log.exception(f"Failed to update info from source {source.tgid}")
|
self.log.exception(f"Failed to update info from source {source.tgid}")
|
||||||
|
|
||||||
self.is_bot = info.bot
|
|
||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
await self.save()
|
await self.save()
|
||||||
|
|
||||||
async def update_displayname(
|
async def update_displayname(
|
||||||
self, source: au.AbstractUser, info: User | UpdateUserName
|
self, source: au.AbstractUser, info: User | Channel | UpdateUserName
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if self.disable_updates:
|
if self.disable_updates:
|
||||||
return False
|
return False
|
||||||
if source.is_relaybot or source.is_bot:
|
if source.is_relaybot or source.is_bot:
|
||||||
allow_because = "user is bot"
|
allow_because = "source user is a bot"
|
||||||
elif self.displayname_source == source.tgid:
|
elif self.displayname_source == source.tgid:
|
||||||
allow_because = "user is the primary source"
|
allow_because = "source user is the primary source"
|
||||||
|
elif isinstance(info, Channel):
|
||||||
|
allow_because = "target user is a channel"
|
||||||
elif not isinstance(info, UpdateUserName) and not info.contact:
|
elif not isinstance(info, UpdateUserName) and not info.contact:
|
||||||
allow_because = "user is not a contact"
|
allow_because = "target user is not a contact"
|
||||||
elif not self.displayname_source:
|
elif not self.displayname_source:
|
||||||
allow_because = "no primary source set"
|
allow_because = "no primary source set"
|
||||||
elif not self.displayname:
|
elif not self.displayname:
|
||||||
allow_because = "user has no name"
|
allow_because = "target user has no name"
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if isinstance(info, UpdateUserName):
|
if isinstance(info, UpdateUserName):
|
||||||
info = await source.client.get_entity(PeerUser(self.tgid))
|
info = await source.client.get_entity(self.peer)
|
||||||
if not info.contact:
|
if isinstance(info, Channel) or not info.contact:
|
||||||
self.displayname_contact = False
|
self.displayname_contact = False
|
||||||
elif not self.displayname_contact:
|
elif not self.displayname_contact:
|
||||||
if not self.displayname:
|
if not self.displayname:
|
||||||
@@ -293,14 +314,14 @@ class Puppet(DBPuppet, BasePuppet):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def update_avatar(
|
async def update_avatar(
|
||||||
self, source: au.AbstractUser, photo: UserProfilePhoto | UserProfilePhotoEmpty
|
self, source: au.AbstractUser, photo: TypeUserProfilePhoto | TypeChatPhoto
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if self.disable_updates:
|
if self.disable_updates:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if photo is None or isinstance(photo, UserProfilePhotoEmpty):
|
if photo is None or isinstance(photo, (UserProfilePhotoEmpty, ChatPhotoEmpty)):
|
||||||
photo_id = ""
|
photo_id = ""
|
||||||
elif isinstance(photo, UserProfilePhoto):
|
elif isinstance(photo, (UserProfilePhoto, ChatPhoto)):
|
||||||
photo_id = str(photo.photo_id)
|
photo_id = str(photo.photo_id)
|
||||||
else:
|
else:
|
||||||
self.log.warning(f"Unknown user profile photo type: {type(photo)}")
|
self.log.warning(f"Unknown user profile photo type: {type(photo)}")
|
||||||
@@ -345,7 +366,9 @@ class Puppet(DBPuppet, BasePuppet):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_getter_lock
|
@async_getter_lock
|
||||||
async def get_by_tgid(cls, tgid: TelegramID, *, create: bool = True) -> Puppet | None:
|
async def get_by_tgid(
|
||||||
|
cls, tgid: TelegramID, *, create: bool = True, is_channel: bool = False
|
||||||
|
) -> Puppet | None:
|
||||||
if tgid is None:
|
if tgid is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -360,13 +383,37 @@ class Puppet(DBPuppet, BasePuppet):
|
|||||||
return puppet
|
return puppet
|
||||||
|
|
||||||
if create:
|
if create:
|
||||||
puppet = cls(tgid)
|
puppet = cls(tgid, is_channel=is_channel)
|
||||||
await puppet.insert()
|
await puppet.insert()
|
||||||
puppet._add_to_cache()
|
puppet._add_to_cache()
|
||||||
return puppet
|
return puppet
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_id_from_peer(peer: TypePeer | User | Channel) -> TelegramID:
|
||||||
|
if isinstance(peer, PeerUser):
|
||||||
|
return TelegramID(peer.user_id)
|
||||||
|
elif isinstance(peer, PeerChannel):
|
||||||
|
return TelegramID(peer.channel_id)
|
||||||
|
elif isinstance(peer, PeerChat):
|
||||||
|
return TelegramID(peer.chat_id)
|
||||||
|
elif isinstance(peer, (User, Channel)):
|
||||||
|
return TelegramID(peer.id)
|
||||||
|
raise TypeError(f"invalid type {type(peer).__name__!r} in _id_from_peer()")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_by_peer(
|
||||||
|
cls, peer: TypePeer | User | Channel, *, create: bool = True
|
||||||
|
) -> Puppet | None:
|
||||||
|
if isinstance(peer, PeerChat):
|
||||||
|
return None
|
||||||
|
return await cls.get_by_tgid(
|
||||||
|
cls.get_id_from_peer(peer),
|
||||||
|
create=create,
|
||||||
|
is_channel=isinstance(peer, (PeerChannel, Channel)),
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_by_mxid(cls, mxid: UserID, create: bool = True) -> Awaitable[Puppet | None]:
|
def get_by_mxid(cls, mxid: UserID, create: bool = True) -> Awaitable[Puppet | None]:
|
||||||
return cls.get_by_tgid(cls.get_id_from_mxid(mxid), create=create)
|
return cls.get_by_tgid(cls.get_id_from_mxid(mxid), create=create)
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ python-magic>=0.4,<0.5
|
|||||||
commonmark>=0.8,<0.10
|
commonmark>=0.8,<0.10
|
||||||
aiohttp>=3,<4
|
aiohttp>=3,<4
|
||||||
yarl>=1,<2
|
yarl>=1,<2
|
||||||
mautrix>=0.14.8,<0.15
|
mautrix>=0.14.9,<0.15
|
||||||
#telethon>=1.24,<1.25
|
#telethon>=1.24,<1.25
|
||||||
# Fork to make session storage async and update to layer 138
|
# Fork to make session storage async and update to layer 138
|
||||||
tulir-telethon==1.25.0a4
|
tulir-telethon==1.25.0a4
|
||||||
|
|||||||
Reference in New Issue
Block a user