Initial option to replace Matrix puppet of own Telegram account
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
"""Add access_token and custom_mxid fields for puppets
|
||||
|
||||
Revision ID: d5f7b8b4b456
|
||||
Revises: 6ca3d74d51e4
|
||||
Create Date: 2018-07-20 12:09:30.277960
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "d5f7b8b4b456"
|
||||
down_revision = "6ca3d74d51e4"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column("puppet", sa.Column("access_token", sa.String(), nullable=True))
|
||||
op.add_column("puppet", sa.Column("custom_mxid", sa.String(), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
with op.batch_alter_table("puppet") as batch_op:
|
||||
batch_op.drop_column("custom_mxid")
|
||||
batch_op.drop_column("access_token")
|
||||
@@ -229,9 +229,9 @@ class AbstractUser:
|
||||
async def update_status(self, update):
|
||||
puppet = pu.Puppet.get(update.user_id)
|
||||
if isinstance(update.status, UserStatusOnline):
|
||||
await puppet.intent.set_presence("online")
|
||||
await puppet.default_mxid_intent.set_presence("online")
|
||||
elif isinstance(update.status, UserStatusOffline):
|
||||
await puppet.intent.set_presence("offline")
|
||||
await puppet.default_mxid_intent.set_presence("offline")
|
||||
else:
|
||||
self.log.warning("Unexpected user status update: %s", update)
|
||||
return
|
||||
|
||||
@@ -49,6 +49,26 @@ async def ping_bot(evt: CommandEvent):
|
||||
"To use the bot, simply invite it to a portal room.")
|
||||
|
||||
|
||||
@command_handler(needs_auth=True, management_only=True,
|
||||
help_section=SECTION_AUTH,
|
||||
help_args="<_token_>",
|
||||
help_text="Replace your Telegram account's Matrix puppet with your own Matrix "
|
||||
"account")
|
||||
async def login_matrix(evt: CommandEvent):
|
||||
puppet = pu.Puppet.get(evt.sender.tgid)
|
||||
prev_info = puppet.custom_mxid, puppet.access_token
|
||||
puppet.custom_mxid = evt.sender.mxid
|
||||
puppet.access_token = " ".join(evt.args)
|
||||
puppet.refresh_intents()
|
||||
if not await puppet.get_profile():
|
||||
puppet.custom_mxid, puppet.access_token = prev_info
|
||||
puppet.refresh_intents()
|
||||
return await evt.reply("Failed to verify access token.")
|
||||
puppet.save()
|
||||
return await evt.reply(
|
||||
f"Replaced your Telegram account's Matrix puppet with {puppet.custom_mxid}.")
|
||||
|
||||
|
||||
@command_handler(needs_auth=False, management_only=True,
|
||||
help_section=SECTION_AUTH,
|
||||
help_args="<_phone_> <_full name_>",
|
||||
|
||||
@@ -137,6 +137,8 @@ class Puppet(Base):
|
||||
__tablename__ = "puppet"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
custom_mxid = Column(String, nullable=True)
|
||||
access_token = Column(String, nullable=True)
|
||||
displayname = Column(String, nullable=True)
|
||||
displayname_source = Column(Integer, nullable=True)
|
||||
username = Column(String, nullable=True)
|
||||
|
||||
+16
-14
@@ -39,7 +39,8 @@ class MatrixHandler:
|
||||
displayname = self.config["appservice.bot_displayname"]
|
||||
if displayname:
|
||||
try:
|
||||
await self.az.intent.set_display_name(displayname if displayname != "remove" else "")
|
||||
await self.az.intent.set_display_name(
|
||||
displayname if displayname != "remove" else "")
|
||||
except asyncio.TimeoutError:
|
||||
self.log.exception("TimeoutError when trying to set displayname")
|
||||
|
||||
@@ -51,19 +52,20 @@ class MatrixHandler:
|
||||
self.log.exception("TimeoutError when trying to set avatar")
|
||||
|
||||
async def handle_puppet_invite(self, room, puppet, inviter):
|
||||
intent = puppet.default_mxid_intent
|
||||
self.log.debug(f"{inviter} invited puppet for {puppet.tgid} to {room}")
|
||||
if not await inviter.is_logged_in():
|
||||
await puppet.intent.error_and_leave(
|
||||
await intent.error_and_leave(
|
||||
room, text="Please log in before inviting Telegram puppets.")
|
||||
return
|
||||
portal = Portal.get_by_mxid(room)
|
||||
if portal:
|
||||
if portal.peer_type == "user":
|
||||
await puppet.intent.error_and_leave(
|
||||
await intent.error_and_leave(
|
||||
room, text="You can not invite additional users to private chats.")
|
||||
return
|
||||
await portal.invite_telegram(inviter, puppet)
|
||||
await puppet.intent.join_room(room)
|
||||
await intent.join_room(room)
|
||||
return
|
||||
try:
|
||||
members = await self.az.intent.get_room_members(room)
|
||||
@@ -71,34 +73,34 @@ class MatrixHandler:
|
||||
members = []
|
||||
if self.az.bot_mxid not in members:
|
||||
if len(members) > 1:
|
||||
await puppet.intent.error_and_leave(room, text=None, html=(
|
||||
await intent.error_and_leave(room, text=None, html=(
|
||||
f"Please invite "
|
||||
f"<a href='https://matrix.to/#/{self.az.bot_mxid}'>the bridge bot</a> "
|
||||
f"first if you want to create a Telegram chat."))
|
||||
return
|
||||
|
||||
await puppet.intent.join_room(room)
|
||||
await intent.join_room(room)
|
||||
portal = Portal.get_by_tgid(puppet.tgid, inviter.tgid, "user")
|
||||
if portal.mxid:
|
||||
try:
|
||||
await puppet.intent.invite(portal.mxid, inviter.mxid)
|
||||
await puppet.intent.send_notice(room, text=None, html=(
|
||||
await intent.invite(portal.mxid, inviter.mxid)
|
||||
await intent.send_notice(room, text=None, html=(
|
||||
"You already have a private chat with me: "
|
||||
f"<a href='https://matrix.to/#/{portal.mxid}'>"
|
||||
"Link to room"
|
||||
"</a>"))
|
||||
await puppet.intent.leave_room(room)
|
||||
await intent.leave_room(room)
|
||||
return
|
||||
except MatrixRequestError:
|
||||
pass
|
||||
portal.mxid = room
|
||||
portal.save()
|
||||
inviter.register_portal(portal)
|
||||
await puppet.intent.send_notice(room, "Portal to private chat created.")
|
||||
await intent.send_notice(room, "Portal to private chat created.")
|
||||
else:
|
||||
await puppet.intent.join_room(room)
|
||||
await puppet.intent.send_notice(room, "This puppet will remain inactive until a "
|
||||
"Telegram chat is created for this room.")
|
||||
await intent.join_room(room)
|
||||
await intent.send_notice(room, "This puppet will remain inactive until a "
|
||||
"Telegram chat is created for this room.")
|
||||
|
||||
async def accept_bot_invite(self, room, inviter):
|
||||
tries = 0
|
||||
@@ -215,7 +217,7 @@ class MatrixHandler:
|
||||
await portal.handle_matrix_message(sender, message, event_id)
|
||||
return
|
||||
|
||||
if not sender.whitelisted or message["msgtype"] != "m.text":
|
||||
if not sender.whitelisted or message.get("msgtype", "m.unknown") != "m.text":
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
+40
-15
@@ -15,10 +15,12 @@
|
||||
# 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 difflib import SequenceMatcher
|
||||
from typing import Optional
|
||||
import re
|
||||
import logging
|
||||
|
||||
from telethon.tl.types import UserProfilePhoto
|
||||
from mautrix_appservice import MatrixError, IntentAPI
|
||||
|
||||
from .db import Puppet as DBPuppet
|
||||
from . import util
|
||||
@@ -35,10 +37,15 @@ class Puppet:
|
||||
hs_domain = None
|
||||
cache = {}
|
||||
|
||||
def __init__(self, id=None, username=None, displayname=None, displayname_source=None,
|
||||
photo_id=None, is_bot=None, is_registered=False, db_instance=None):
|
||||
def __init__(self, id=None, access_token=None, custom_mxid=None, username=None,
|
||||
displayname=None, displayname_source=None, photo_id=None, is_bot=None,
|
||||
is_registered=False, db_instance=None):
|
||||
self.id = id
|
||||
self.mxid = self.get_mxid_from_id(self.id)
|
||||
self.access_token = access_token
|
||||
self.custom_mxid = custom_mxid
|
||||
self.is_real_user = self.custom_mxid and self.access_token
|
||||
self.default_mxid = self.get_mxid_from_id(self.id)
|
||||
self.mxid = self.custom_mxid or self.default_mxid
|
||||
|
||||
self.username = username
|
||||
self.displayname = displayname
|
||||
@@ -48,10 +55,23 @@ class Puppet:
|
||||
self.is_registered = is_registered
|
||||
self._db_instance = db_instance
|
||||
|
||||
self.intent = self.az.intent.user(self.mxid)
|
||||
self.default_mxid_intent = self.az.intent.user(self.default_mxid)
|
||||
self.intent = None # type: IntentAPI
|
||||
self.refresh_intents()
|
||||
|
||||
self.cache[id] = self
|
||||
|
||||
def refresh_intents(self):
|
||||
self.is_real_user = self.custom_mxid and self.access_token
|
||||
self.intent = (self.az.intent.user(self.custom_mxid, self.access_token)
|
||||
if self.is_real_user else self.default_mxid_intent)
|
||||
|
||||
async def get_profile(self):
|
||||
try:
|
||||
return await self.intent.get_profile(self.custom_mxid)
|
||||
except MatrixError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def tgid(self):
|
||||
return self.id
|
||||
@@ -66,17 +86,21 @@ class Puppet:
|
||||
return self._db_instance
|
||||
|
||||
def new_db_instance(self):
|
||||
return DBPuppet(id=self.id, username=self.username, displayname=self.displayname,
|
||||
return DBPuppet(id=self.id, access_token=self.access_token, custom_mxid=self.custom_mxid,
|
||||
username=self.username, displayname=self.displayname,
|
||||
displayname_source=self.displayname_source, photo_id=self.photo_id,
|
||||
is_bot=self.is_bot, matrix_registered=self.is_registered)
|
||||
|
||||
@classmethod
|
||||
def from_db(cls, db_puppet):
|
||||
return Puppet(db_puppet.id, db_puppet.username, db_puppet.displayname,
|
||||
db_puppet.displayname_source, db_puppet.photo_id, db_puppet.is_bot,
|
||||
db_puppet.matrix_registered, db_instance=db_puppet)
|
||||
return Puppet(db_puppet.id, db_puppet.access_token, db_puppet.custom_mxid,
|
||||
db_puppet.username, db_puppet.displayname, db_puppet.displayname_source,
|
||||
db_puppet.photo_id, db_puppet.is_bot, db_puppet.matrix_registered,
|
||||
db_instance=db_puppet)
|
||||
|
||||
def save(self):
|
||||
self.db_instance.access_token = self.access_token
|
||||
self.db_instance.custom_mxid = self.custom_mxid
|
||||
self.db_instance.username = self.username
|
||||
self.db_instance.displayname = self.displayname
|
||||
self.db_instance.displayname_source = self.displayname_source
|
||||
@@ -145,7 +169,7 @@ class Puppet:
|
||||
|
||||
displayname = self.get_displayname(info)
|
||||
if displayname != self.displayname:
|
||||
await self.intent.set_display_name(displayname)
|
||||
await self.default_mxid_intent.set_display_name(displayname)
|
||||
self.displayname = displayname
|
||||
self.displayname_source = source.tgid
|
||||
return True
|
||||
@@ -156,15 +180,16 @@ class Puppet:
|
||||
async def update_avatar(self, source, photo):
|
||||
photo_id = f"{photo.volume_id}-{photo.local_id}"
|
||||
if self.photo_id != photo_id:
|
||||
file = await util.transfer_file_to_matrix(self.db, source.client, self.intent, photo)
|
||||
file = await util.transfer_file_to_matrix(self.db, source.client,
|
||||
self.default_mxid_intent, photo)
|
||||
if file:
|
||||
await self.intent.set_avatar(file.mxc)
|
||||
await self.default_mxid_intent.set_avatar(file.mxc)
|
||||
self.photo_id = photo_id
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def get(cls, id, create=True):
|
||||
def get(cls, id, create=True) -> "Optional[Puppet]":
|
||||
try:
|
||||
return cls.cache[id]
|
||||
except KeyError:
|
||||
@@ -183,7 +208,7 @@ class Puppet:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_by_mxid(cls, mxid, create=True):
|
||||
def get_by_mxid(cls, mxid, create=True) -> "Optional[Puppet]":
|
||||
tgid = cls.get_id_from_mxid(mxid)
|
||||
return cls.get(tgid, create) if tgid else None
|
||||
|
||||
@@ -199,7 +224,7 @@ class Puppet:
|
||||
return f"@{cls.username_template.format(userid=id)}:{cls.hs_domain}"
|
||||
|
||||
@classmethod
|
||||
def find_by_username(cls, username):
|
||||
def find_by_username(cls, username) -> "Optional[Puppet]":
|
||||
if not username:
|
||||
return None
|
||||
|
||||
@@ -214,7 +239,7 @@ class Puppet:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def find_by_displayname(cls, displayname):
|
||||
def find_by_displayname(cls, displayname) -> "Optional[Puppet]":
|
||||
if not displayname:
|
||||
return None
|
||||
|
||||
|
||||
@@ -111,14 +111,14 @@ class User(AbstractUser):
|
||||
|
||||
def new_db_instance(self):
|
||||
return DBUser(mxid=self.mxid, tgid=self.tgid, tg_username=self.username,
|
||||
contacts=self.db_contacts, saved_contacts=self.saved_contacts,
|
||||
contacts=self.db_contacts, saved_contacts=self.saved_contacts or 0,
|
||||
portals=self.db_portals)
|
||||
|
||||
def save(self):
|
||||
self.db_instance.tgid = self.tgid
|
||||
self.db_instance.username = self.username
|
||||
self.db_instance.contacts = self.db_contacts
|
||||
self.db_instance.saved_contacts = self.saved_contacts
|
||||
self.db_instance.saved_contacts = self.saved_contacts or 0
|
||||
self.db_instance.portals = self.db_portals
|
||||
self.db.commit()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user