Add option to filter telegram chats from being bridged. Fixes #41

This commit is contained in:
Tulir Asokan
2018-05-19 19:34:35 +03:00
parent 1f5261ff8f
commit 7a373fa556
6 changed files with 123 additions and 3 deletions
+9 -1
View File
@@ -109,13 +109,21 @@ bridge:
public_portals: true
# Whether to send stickers as the new native m.sticker type or normal m.images.
# Old versions of Riot don't support the new type at all.
#
# Remember that proper sticker support always requires Pillow to convert webp into png.
native_stickers: true
# Whether or not to fetch and handle Telegram updates at startup from the time the bridge was down.
# WARNING: Probably buggy, might get stuck in infinite loop.
catch_up: false
filter:
# Filter mode to use. Either "blacklist" or "whitelist".
# If the mode is "blacklist", the listed chats will never be bridged. An empty blacklist disables the filter.
# If the mode is "whitelist", only the listed chats can be bridged.
# Direct chats are not affected.
mode: blacklist
# The list of group/channel IDs to filter.
list: []
# The prefix for commands. Only required in non-management rooms.
command_prefix: "!tg"
+3
View File
@@ -145,6 +145,9 @@ class Bot(AbstractUser):
if not config["bridge.relaybot.authless_portals"]:
return await reply("This bridge doesn't allow portal creation from Telegram.")
if not portal.allow_bridging():
return await reply("This bridge doesn't allow bridging this chat.")
await portal.create_matrix_room(self)
if portal.mxid:
if portal.username:
+4
View File
@@ -80,5 +80,9 @@ def help(evt):
**group-name** <_name_|`-`> - Change the username of a supergroup/channel. To disable, use a dash
(`-`) as the name.
**clean-rooms** - Clean up unused portal/management rooms.
**filter** <`whitelist`|`blacklist`> <_chat ID_> - Allow or disallow bridging
**filter-mode** <`whitelist`|`blacklist`> - Change whether the bridge will allow or disallow
bridging rooms by default.
"""
return evt.reply(management_status + help)
+71
View File
@@ -161,6 +161,10 @@ async def bridge(evt: CommandEvent):
"Bridging private chats to existing rooms is not allowed.")
portal = po.Portal.get_by_tgid(tgid, peer_type=peer_type)
if not portal.allow_bridging():
return await evt.reply("This bridge doesn't allow bridging that Telegram chat.\n"
"If you're the bridge admin, try"
"`$cmdprefix+sp whitelist <Telegram chat ID>` first.")
if portal.mxid:
has_portal_message = (
"That Telegram chat already has a portal at "
@@ -353,3 +357,70 @@ async def group_name(evt: CommandEvent):
return await evt.reply("That username is already in use.")
except UsernameInvalidError:
return await evt.reply("Invalid username")
@command_handler(needs_admin=True)
async def filter_mode(evt: CommandEvent):
try:
mode = evt.args[0]
if mode not in ("whitelist", "blacklist"):
raise ValueError()
except (IndexError, ValueError):
return await evt.reply("**Usage:** `$cmdprefix+sp filter-mode <whitelist/blacklist>`")
evt.config["bridge.filter.mode"] = mode
evt.config.save()
po.Portal.filter_mode = mode
if mode == "whitelist":
return await evt.reply("The bridge will now disallow bridging chats by default.\n"
"To allow bridging a specific chat, use"
"`!filter whitelist <chat ID>`.")
else:
return await evt.reply("The bridge will now allow bridging chats by default.\n"
"To disallow bridging a specific chat, use"
"`!filter blacklist <chat ID>`.")
@command_handler(needs_admin=True)
async def filter(evt: CommandEvent):
try:
action = evt.args[0]
if action not in ("whitelist", "blacklist", "add", "remove"):
raise ValueError()
id = evt.args[1]
if id.startswith("-100"):
id = int(id[4:])
elif id.startswith("-"):
id = int(id[1:])
else:
id = int(id)
except (IndexError, ValueError):
return await evt.reply("**Usage:** `$cmdprefix+sp filter <whitelist/blacklist> <chat ID>`")
mode = evt.config["bridge.filter.mode"]
if mode not in ("blacklist", "whitelist"):
return await evt.reply(f"Unknown filter mode \"{mode}\". Please fix the bridge config.")
list = evt.config["bridge.filter.list"]
if action in ("blacklist", "whitelist"):
action = "add" if mode == action else "remove"
def save():
evt.config["bridge.filter.list"] = list
evt.config.save()
po.Portal.filter_list = list
if action == "add":
if id in list:
return await evt.reply(f"That chat is already {mode}ed.")
list.append(id)
save()
return await evt.reply(f"Chat ID added to {mode}.")
elif action == "remove":
if id not in list:
return await evt.reply(f"That chat is not {mode}ed.")
list.remove(id)
save()
return await evt.reply(f"Chat ID removed from {mode}.")
+3
View File
@@ -181,6 +181,9 @@ class Config(DictWithRecursion):
copy("bridge.native_stickers")
copy("bridge.catch_up")
copy("bridge.filter.mode")
copy("bridge.filter.list")
copy("bridge.command_prefix")
migrate_permissions = ("bridge.permissions" not in self
+33 -2
View File
@@ -48,6 +48,8 @@ class Portal:
az = None
bot = None
loop = None
filter_mode = None
filter_list = None
bridge_notices = False
alias_template = None
mx_alias_regex = None
@@ -117,6 +119,26 @@ class Portal:
self._main_intent = puppet.intent if direct else self.az.intent
return self._main_intent
# endregion
# region Filtering
def allow_bridging(self, tgid=None):
tgid = tgid or self.tgid
if self.peer_type == "user":
self.log.debug(f"!!!!!!!!!!!!!!!!!!!!!!!!!!!! allow_bridging(User {tgid}) -> True")
return True
elif self.filter_mode == "whitelist":
self.log.debug(
f"!!!!!!!!!!!!!!!!!!!!!!!!!!!! allow_bridging(Chat {tgid}) -> {tgid in self.filter_list} (whitelist={self.filter_list})")
return tgid in self.filter_list
elif self.filter_mode == "blacklist":
self.log.debug(
f"!!!!!!!!!!!!!!!!!!!!!!!!!!!! allow_bridging(Chat {tgid}) -> {tgid not in self.filter_list} (blacklist={self.filter_list})")
return tgid not in self.filter_list
else:
self.log.debug("!!!!!!!!!!!!!!??????????????? Unknown filter mode", self.filter_mode)
return True
# endregion
# region Deduplication
@@ -233,6 +255,9 @@ class Portal:
if self.mxid:
return self.mxid
if not self.allow_bridging():
return None
if not entity:
entity = await user.client.get_entity(self.peer)
self.log.debug("Fetched data: %s", entity)
@@ -336,6 +361,10 @@ class Portal:
await puppet.intent.ensure_joined(self.mxid)
await puppet.update_info(source, entity)
user = u.User.get_by_tgid(entity.id)
if user:
await self.invite_to_matrix(user.mxid)
# We can't trust the member list if any of the following cases is true:
# * There are close to 10 000 users, because Telegram might not be sending all members.
# * The member sync count is limited, because then we might ignore some members.
@@ -371,7 +400,7 @@ class Portal:
user = u.User.get_by_tgid(user_id)
if user:
user.register_portal(self)
await self.main_intent.invite(self.mxid, user.mxid)
await self.invite_to_matrix(user.mxid)
async def delete_telegram_user(self, user_id, sender):
puppet = p.Puppet.get(user_id)
@@ -1592,7 +1621,9 @@ def init(context):
global config
Portal.az, Portal.db, config, Portal.loop, Portal.bot = context
Portal.bridge_notices = config["bridge.bridge_notices"]
Portal.filter_mode = config["bridge.filter.mode"]
Portal.filter_list = config["bridge.filter.list"]
Portal.alias_template = config.get("bridge.alias_template", "telegram_{groupname}")
Portal.hs_domain = config["homeserver"]["domain"]
Portal.hs_domain = config["homeserver.domain"]
localpart = Portal.alias_template.format(groupname="(.+)")
Portal.mx_alias_regex = re.compile(f"#{localpart}:{Portal.hs_domain}")