From fc65d5f0f55f8c0f4dd05213c724e8af1a90b5b0 Mon Sep 17 00:00:00 2001 From: Max Sandholm Date: Mon, 3 Apr 2017 20:56:56 +0300 Subject: [PATCH 1/9] Telegram profile pictures and display names are updated The bridge updates the displayname and/or the avatar of the bridged Matrix user when the Telegram user changes them. This is done by checking the name and profile pic of the sender when a message is received from Telegram, and comparing to the database to see if they have changed. If they have, it will update the database and send the updates to the Matrix homeserver. --- telematrix/__init__.py | 39 +++++++++++++++++++++++++++++++++++++++ telematrix/database.py | 4 ++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/telematrix/__init__.py b/telematrix/__init__.py index 39524f2..f3663b6 100644 --- a/telematrix/__init__.py +++ b/telematrix/__init__.py @@ -200,6 +200,8 @@ async def matrix_transaction(request): group = TG_BOT.group(link.tg_room) try: + response = None + if event['type'] == 'm.room.message': user_id = event['user_id'] if matrix_is_telegram(user_id): @@ -448,6 +450,39 @@ async def register_join_matrix(chat, room_id, user_id): user_id, {'displayname': name}) await matrix_post('client', 'join/{}'.format(room_id), user_id, {}) +async def update_matrix_displayname_avatar(tg_user): + name = tg_user['first_name'] + if 'last_name' in tg_user: + name += ' ' + tg_user['last_name'] + name += ' (Telegram)' + user_id = USER_ID_FORMAT.format(tg_user['id']) + + db_user = db.session.query(db.TgUser).filter_by(tg_id=tg_user['id']).first() + + profile_photos = await TG_BOT.get_user_profile_photos(tg_user['id']) + pp_file_id = None + try: + pp_file_id = profile_photos['result']['photos'][0][-1]['file_id'] + except: + pp_file_id = None + + if db_user: + if db_user.name != name: + await matrix_put('client', 'profile/{}/displayname'.format(user_id), user_id, {'displayname': name}) + db_user.name = name + if db_user.profile_pic_id != pp_file_id: + pp_uri, _ = await upload_tgfile_to_matrix(pp_file_id, user_id) + await matrix_put('client', 'profile/{}/avatar_url'.format(user_id), user_id, {'avatar_url':pp_uri}) + db_user.profile_pic_id = pp_file_id + else: + db_user = db.TgUser(tg_user['id'], name, pp_file_id) + await matrix_put('client', 'profile/{}/displayname'.format(user_id), user_id, {'displayname': name}) + pp_uri, _ = await upload_tgfile_to_matrix(pp_file_id, user_id) + await matrix_put('client', 'profile/{}/avatar_url'.format(user_id), user_id, {'avatar_url':pp_uri}) + db.session.add(db_user) + db.session.commit() + + @TG_BOT.handle('sticker') async def aiotg_sticker(chat, sticker): link = db.session.query(db.ChatLink).filter_by(tg_room=chat.id).first() @@ -455,6 +490,8 @@ async def aiotg_sticker(chat, sticker): print('Unknown telegram chat {}: {}'.format(chat, chat.id)) return + await update_matrix_displayname_avatar(chat.sender); + room_id = link.matrix_room user_id = USER_ID_FORMAT.format(chat.sender['id']) txn_id = quote('{}{}'.format(chat.message['message_id'], chat.id)) @@ -501,6 +538,7 @@ async def aiotg_photo(chat, photo): print('Unknown telegram chat {}: {}'.format(chat, chat.id)) return + await update_matrix_displayname_avatar(chat.sender); room_id = link.matrix_room user_id = USER_ID_FORMAT.format(chat.sender['id']) txn_id = quote('{}{}'.format(chat.message['message_id'], chat.id)) @@ -555,6 +593,7 @@ async def aiotg_message(chat, match): print('Unknown telegram chat {}: {}'.format(chat, chat.id)) return + await update_matrix_displayname_avatar(chat.sender); user_id = USER_ID_FORMAT.format(chat.sender['id']) txn_id = quote('{}:{}'.format(chat.message['message_id'], chat.id)) diff --git a/telematrix/database.py b/telematrix/database.py index 9d65099..b4bab21 100644 --- a/telematrix/database.py +++ b/telematrix/database.py @@ -32,9 +32,9 @@ class TgUser(Base): id = sa.Column(sa.Integer, primary_key=True) tg_id = sa.Column(sa.BigInteger) name = sa.Column(sa.String) - profile_pic_id = sa.Column(sa.String) + profile_pic_id = sa.Column(sa.String, nullable=True) - def __init__(self, tg_id, name, profile_pic_id): + def __init__(self, tg_id, name, profile_pic_id=None): self.tg_id = tg_id self.name = name self.profile_pic_id = profile_pic_id From 15e84f68617cb6e2778d335272048edf4c85dc59 Mon Sep 17 00:00:00 2001 From: Max Sandholm Date: Tue, 4 Apr 2017 00:07:07 +0300 Subject: [PATCH 2/9] Handle users without profile pictures correctly --- telematrix/__init__.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/telematrix/__init__.py b/telematrix/__init__.py index f3663b6..c6b9ea9 100644 --- a/telematrix/__init__.py +++ b/telematrix/__init__.py @@ -471,14 +471,20 @@ async def update_matrix_displayname_avatar(tg_user): await matrix_put('client', 'profile/{}/displayname'.format(user_id), user_id, {'displayname': name}) db_user.name = name if db_user.profile_pic_id != pp_file_id: - pp_uri, _ = await upload_tgfile_to_matrix(pp_file_id, user_id) - await matrix_put('client', 'profile/{}/avatar_url'.format(user_id), user_id, {'avatar_url':pp_uri}) + if pp_file_id: + pp_uri, _ = await upload_tgfile_to_matrix(pp_file_id, user_id) + await matrix_put('client', 'profile/{}/avatar_url'.format(user_id), user_id, {'avatar_url':pp_uri}) + else: + await matrix_put('client', 'profile/{}/avatar_url'.format(user_id), user_id, {'avatar_url':None}) db_user.profile_pic_id = pp_file_id else: db_user = db.TgUser(tg_user['id'], name, pp_file_id) await matrix_put('client', 'profile/{}/displayname'.format(user_id), user_id, {'displayname': name}) - pp_uri, _ = await upload_tgfile_to_matrix(pp_file_id, user_id) - await matrix_put('client', 'profile/{}/avatar_url'.format(user_id), user_id, {'avatar_url':pp_uri}) + if pp_file_id: + pp_uri, _ = await upload_tgfile_to_matrix(pp_file_id, user_id) + await matrix_put('client', 'profile/{}/avatar_url'.format(user_id), user_id, {'avatar_url':pp_uri}) + else: + await matrix_put('client', 'profile/{}/avatar_url'.format(user_id), user_id, {'avatar_url':None}) db.session.add(db_user) db.session.commit() From fea8d8949353e630678b96ae586c5ff25dd7a9ee Mon Sep 17 00:00:00 2001 From: Max Sandholm Date: Sat, 8 Apr 2017 13:32:19 +0300 Subject: [PATCH 3/9] Fix aliases-related breakage --- telematrix/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telematrix/__init__.py b/telematrix/__init__.py index c6b9ea9..bea6220 100644 --- a/telematrix/__init__.py +++ b/telematrix/__init__.py @@ -171,7 +171,7 @@ async def matrix_transaction(request): except KeyError: pass - if event['type'] == 'm.room.aliases': + if event['type'] == 'm.room.aliases' and event['state_key'] == MATRIX_HOST_BARE: aliases = event['content']['aliases'] links = db.session.query(db.ChatLink)\ From 09945d75ccf1253f24f4e04da54835bf206a622b Mon Sep 17 00:00:00 2001 From: Max Sandholm Date: Thu, 21 Sep 2017 13:50:17 +0300 Subject: [PATCH 4/9] Replace Matrix mentions with Telegram mentions (ping) --- .gitignore | 55 ++++++++++++++++++++++-------------------- telematrix/__init__.py | 2 ++ 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 69815ce..b9bca15 100644 --- a/.gitignore +++ b/.gitignore @@ -122,33 +122,36 @@ Session.vim tags +#VSCode +.vscode + ### macOS ### -*.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk # Config config.json diff --git a/telematrix/__init__.py b/telematrix/__init__.py index bea6220..bdb7154 100644 --- a/telematrix/__init__.py +++ b/telematrix/__init__.py @@ -12,6 +12,7 @@ from datetime import datetime from time import time from urllib.parse import unquote, quote, urlparse, parse_qs from io import BytesIO +import re from PIL import Image from aiohttp import web, ClientSession @@ -97,6 +98,7 @@ def format_matrix_msg(form, username, content): :return: The formatted string. """ if 'format' in content and content['format'] == 'org.matrix.custom.html': + re.sub("(.+?)".format(MATRIX_HOST_BARE), "\\2", content['formatted_body']) sanitized = sanitize_html(content['formatted_body']) return html.escape(form).format(username, sanitized), 'HTML' else: From d537cfb1b37de67b598d79aadcc1fec2b28fa8a7 Mon Sep 17 00:00:00 2001 From: Max Sandholm Date: Thu, 21 Sep 2017 13:29:26 +0200 Subject: [PATCH 5/9] Fix quoting formatting and change Matrix->Telegram username formatting --- telematrix/__init__.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/telematrix/__init__.py b/telematrix/__init__.py index c6b9ea9..02ef5a4 100644 --- a/telematrix/__init__.py +++ b/telematrix/__init__.py @@ -81,26 +81,25 @@ def sanitize_html(string): soup = BeautifulSoup(string, 'html.parser') for tag in soup.find_all(True): if tag.name == 'blockquote': - tag.string = ('\n' + tag.text).replace('\n', '\n> ').rstrip('\n>') + tag.string = ('\n' + tag.text).replace('\n', '\n> ')[3:-3] if tag.name not in VALID_TAGS: tag.hidden = True return soup.renderContents().decode('utf-8') -def format_matrix_msg(form, username, content): +def format_matrix_msg(form, content): """ Formats a matrix message for sending to Telegram :param form: The format string of the message, where the first parameter is the username and the second one the message. - :param username: The username of the user. :param content: The content to be sent. :return: The formatted string. """ if 'format' in content and content['format'] == 'org.matrix.custom.html': sanitized = sanitize_html(content['formatted_body']) - return html.escape(form).format(username, sanitized), 'HTML' + return html.escape(form).format(sanitized), 'HTML' else: - return form.format(username, content['body']), None + return form.format(html.escape(content['body'])), None async def download_matrix_file(url, filename): @@ -171,7 +170,7 @@ async def matrix_transaction(request): except KeyError: pass - if event['type'] == 'm.room.aliases': + if event['type'] == 'm.room.aliases' and event['state_key'] == MATRIX_HOST_BARE: aliases = event['content']['aliases'] links = db.session.query(db.ChatLink)\ @@ -228,14 +227,14 @@ async def matrix_transaction(request): continue if content['msgtype'] == 'm.text': - msg, mode = format_matrix_msg('<{}> {}', displayname, content) - response = await group.send_text(msg, parse_mode=mode) + msg, mode = format_matrix_msg('{}', content) + response = await group.send_text("{}: {}".format(displayname, msg), parse_mode='HTML') elif content['msgtype'] == 'm.notice': - msg, mode = format_matrix_msg('[{}] {}', displayname, content) - response = await group.send_text(msg, parse_mode=mode) + msg, mode = format_matrix_msg('{}', content) + response = await group.send_text("[{}] {}".format(displayname, msg), parse_mode=mode) elif content['msgtype'] == 'm.emote': - msg, mode = format_matrix_msg('* {} {}', displayname, content) - response = await group.send_text(msg, parse_mode=mode) + msg, mode = format_matrix_msg('{}', content) + response = await group.send_text("* {} {}".format(displayname, msg), parse_mode=mode) elif content['msgtype'] == 'm.image': try: url = urlparse(content['url']) @@ -254,8 +253,7 @@ async def matrix_transaction(request): .format(url.netloc, quote(url.path)) url_str = await shorten_url(url_str) - caption = '<{}> {} ({})'.format(displayname, - content['body'], url_str) + caption = '{} sent an image'.format(displayname) response = await group.send_photo(img_file, caption=caption) except: pass From 828f92e37b4140c5a6175cecdeec97e1a2941452 Mon Sep 17 00:00:00 2001 From: Max Sandholm Date: Thu, 21 Sep 2017 14:36:37 +0300 Subject: [PATCH 6/9] Fix escaping error --- telematrix/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telematrix/__init__.py b/telematrix/__init__.py index bdb7154..f4e92c3 100644 --- a/telematrix/__init__.py +++ b/telematrix/__init__.py @@ -98,7 +98,7 @@ def format_matrix_msg(form, username, content): :return: The formatted string. """ if 'format' in content and content['format'] == 'org.matrix.custom.html': - re.sub("(.+?)".format(MATRIX_HOST_BARE), "\\2", content['formatted_body']) + re.sub("(.+?)".format(MATRIX_HOST_BARE), "\\2", content['formatted_body']) sanitized = sanitize_html(content['formatted_body']) return html.escape(form).format(username, sanitized), 'HTML' else: From e98b7312f62d033d0e785e23f92180cc0c8a0f67 Mon Sep 17 00:00:00 2001 From: Max Sandholm Date: Thu, 21 Sep 2017 14:39:50 +0300 Subject: [PATCH 7/9] Correctly use the re.sub function --- telematrix/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/telematrix/__init__.py b/telematrix/__init__.py index 76bd032..8d3992e 100644 --- a/telematrix/__init__.py +++ b/telematrix/__init__.py @@ -97,8 +97,8 @@ def format_matrix_msg(form, content): :return: The formatted string. """ if 'format' in content and content['format'] == 'org.matrix.custom.html': - re.sub("(.+?)".format(MATRIX_HOST_BARE), "\\2", content['formatted_body']) - sanitized = sanitize_html(content['formatted_body']) + sanitized = re.sub("(.+?)".format(MATRIX_HOST_BARE), "\\2", content['formatted_body']) + sanitized = sanitize_html(sanitized) return html.escape(form).format(sanitized), 'HTML' else: return form.format(html.escape(content['body'])), None From 9e003f8ff97a1962f483c10875a7c2d84c71207c Mon Sep 17 00:00:00 2001 From: Max Sandholm Date: Thu, 21 Sep 2017 14:42:12 +0300 Subject: [PATCH 8/9] Remove "(Telegram)" from mentions --- telematrix/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telematrix/__init__.py b/telematrix/__init__.py index 8d3992e..5b069a9 100644 --- a/telematrix/__init__.py +++ b/telematrix/__init__.py @@ -97,7 +97,7 @@ def format_matrix_msg(form, content): :return: The formatted string. """ if 'format' in content and content['format'] == 'org.matrix.custom.html': - sanitized = re.sub("(.+?)".format(MATRIX_HOST_BARE), "\\2", content['formatted_body']) + sanitized = re.sub("(.+?) \(Telegram\)".format(MATRIX_HOST_BARE), "\\2", content['formatted_body']) sanitized = sanitize_html(sanitized) return html.escape(form).format(sanitized), 'HTML' else: From cd1d66ffeecb56c344dce1b67f65348e6a56e8b6 Mon Sep 17 00:00:00 2001 From: Max Sandholm Date: Thu, 2 Nov 2017 15:36:17 +0200 Subject: [PATCH 9/9] Revert .gitignore to correct version --- .gitignore | 55 ++++++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index b9bca15..69815ce 100644 --- a/.gitignore +++ b/.gitignore @@ -122,36 +122,33 @@ Session.vim tags -#VSCode -.vscode - ### macOS ### -*.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk # Config config.json