From d274f79d8781ca1e1ec30975faf7e6c06de7248e Mon Sep 17 00:00:00 2001 From: Dan Elkouby Date: Mon, 18 Feb 2019 17:44:42 +0000 Subject: [PATCH 01/20] Allow sending a snippet with ! --- stdplugins/snip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdplugins/snip.py b/stdplugins/snip.py index d78c645..afd3b14 100644 --- a/stdplugins/snip.py +++ b/stdplugins/snip.py @@ -16,7 +16,7 @@ TYPE_DOCUMENT = 2 snips = storage.snips or {} -@borg.on(events.NewMessage(pattern=r'\.snip (\S+)', outgoing=True)) +@borg.on(events.NewMessage(pattern=r'(?:\.snip +|!)(\w+)$', outgoing=True)) async def on_snip(event): loop.create_task(event.delete()) name = event.pattern_match.group(1) From 84e3c4bef8af4162aeed45b4c7eac07fd35d4abb Mon Sep 17 00:00:00 2001 From: Lonami Date: Thu, 21 Feb 2019 13:22:49 +0100 Subject: [PATCH 02/20] Support enclosing circle in markdown --- stdplugins/markdown.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/stdplugins/markdown.py b/stdplugins/markdown.py index b56e7ed..6fe940c 100644 --- a/stdplugins/markdown.py +++ b/stdplugins/markdown.py @@ -47,6 +47,10 @@ def parse_strikethrough(m): return ("\u0336".join(m[1]) + "\u0336"), None +def parse_enclosing_circle(m): + return ("\u20e0".join(m[1]) + "\u20e0"), None + + def parse_subreddit(m): text = '/' + m.group(3) entity = MessageEntityTextUrl( @@ -82,6 +86,7 @@ MATCHERS = [ (get_tag_parser('`', MessageEntityCode)), (re.compile(r'\+\+(.+?)\+\+'), parse_aesthetics), (re.compile(r'~~(.+?)~~'), parse_strikethrough), + (re.compile(r'@@(.+?)@@'), parse_enclosing_circle), (re.compile(r'([^/\w]|^)(/?(r/\w+))'), parse_subreddit), (re.compile(r'(!\w+)'), parse_snip) ] From dc2eefd58110cb1a300353426a74fd8c7df86095 Mon Sep 17 00:00:00 2001 From: Lonami Date: Tue, 26 Feb 2019 18:52:57 +0100 Subject: [PATCH 03/20] s/regex/re/g --- stdplugins/sed.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/stdplugins/sed.py b/stdplugins/sed.py index 04f7320..11bb96a 100644 --- a/stdplugins/sed.py +++ b/stdplugins/sed.py @@ -1,7 +1,6 @@ from collections import defaultdict, deque import re -import regex from telethon import events, utils from telethon.tl import types, functions @@ -34,7 +33,7 @@ def doit(chat_id, match, original): flags = 0 for f in fl: if f == 'i': - flags |= regex.IGNORECASE + flags |= re.IGNORECASE elif f == 'g': count = 0 else: @@ -45,7 +44,7 @@ def doit(chat_id, match, original): s = original.message if s.startswith(HEADER): s = s[len(HEADER):] - s, i = regex.subn(fr, to, s, count=count, flags=flags) + s, i = re.subn(fr, to, s, count=count, flags=flags) if i > 0: return original, s except Exception as e: From a53b5fb147651023134c2a2182944628f31718d5 Mon Sep 17 00:00:00 2001 From: Dan Elkouby Date: Tue, 26 Feb 2019 21:10:21 +0000 Subject: [PATCH 04/20] Revert "s/regex/re/g" This reverts commit dc2eefd58110cb1a300353426a74fd8c7df86095. --- stdplugins/sed.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stdplugins/sed.py b/stdplugins/sed.py index 11bb96a..04f7320 100644 --- a/stdplugins/sed.py +++ b/stdplugins/sed.py @@ -1,6 +1,7 @@ from collections import defaultdict, deque import re +import regex from telethon import events, utils from telethon.tl import types, functions @@ -33,7 +34,7 @@ def doit(chat_id, match, original): flags = 0 for f in fl: if f == 'i': - flags |= re.IGNORECASE + flags |= regex.IGNORECASE elif f == 'g': count = 0 else: @@ -44,7 +45,7 @@ def doit(chat_id, match, original): s = original.message if s.startswith(HEADER): s = s[len(HEADER):] - s, i = re.subn(fr, to, s, count=count, flags=flags) + s, i = regex.subn(fr, to, s, count=count, flags=flags) if i > 0: return original, s except Exception as e: From 88a8b4e0d3ffe11bd18046a25945b63dc729afdc Mon Sep 17 00:00:00 2001 From: Dan Elkouby Date: Tue, 26 Feb 2019 22:27:01 +0000 Subject: [PATCH 05/20] Add a timeout for sed --- stdplugins/sed.py | 3 +++ uniborg/util.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/stdplugins/sed.py b/stdplugins/sed.py index 04f7320..aeaa8e5 100644 --- a/stdplugins/sed.py +++ b/stdplugins/sed.py @@ -5,6 +5,8 @@ import regex from telethon import events, utils from telethon.tl import types, functions +from uniborg import util + HEADER = "「sed」\n" KNOWN_RE_BOTS = re.compile( r'(regex|moku|BananaButler_|rgx|l4mR)bot', @@ -17,6 +19,7 @@ KNOWN_RE_BOTS = re.compile( last_msgs = defaultdict(lambda: deque(maxlen=10)) +@util.sync_timeout(1) def doit(chat_id, match, original): fr = match.group(1) to = match.group(2) diff --git a/uniborg/util.py b/uniborg/util.py index 39cfc8c..1b93b26 100644 --- a/uniborg/util.py +++ b/uniborg/util.py @@ -2,7 +2,9 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +import functools import re +import signal from telethon import events from telethon.tl.functions.messages import GetPeerDialogsRequest @@ -28,3 +30,20 @@ async def is_read(borg, entity, message, is_out=None): dialog = (await borg(GetPeerDialogsRequest([entity]))).dialogs[0] max_id = dialog.read_outbox_max_id if is_out else dialog.read_inbox_max_id return message_id <= max_id + +def _handle_timeout(signum, frame): + raise TimeoutError("Execution took too long") + +def sync_timeout(seconds): + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + signal.signal(signal.SIGALRM, _handle_timeout) + signal.setitimer(signal.ITIMER_REAL, seconds) + try: + r = func(*args, **kwargs) + finally: + signal.alarm(0) + return r + return wrapper + return decorator From 59a0ebe9dc27319521e9712b633cc5d70f425def Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 27 Feb 2019 13:20:24 +0100 Subject: [PATCH 06/20] Add fixreply plugin --- stdplugins/fixreply.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 stdplugins/fixreply.py diff --git a/stdplugins/fixreply.py b/stdplugins/fixreply.py new file mode 100644 index 0000000..fded163 --- /dev/null +++ b/stdplugins/fixreply.py @@ -0,0 +1,27 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +import asyncio + +from telethon import events + + +_last_message = None + + +@borg.on(events.NewMessage(outgoing=True)) +async def _(event): + global _last_message + _last_message = event.message + + +@borg.on(events.NewMessage(pattern=r"\.(fix)?reply", outgoing=True)) +async def _(event): + if not event.is_reply or not _last_message: + return + + chat = await event.get_input_chat() + await asyncio.wait([ + borg.delete_messages(chat, [event.id, _last_message.id]), + borg.send_message(chat, _last_message, reply_to=event.reply_to_msg_id) + ]) From b4be66162e69ad5608c21996b5ae00875bd24790 Mon Sep 17 00:00:00 2001 From: Lonami Date: Mon, 4 Mar 2019 08:05:40 +0100 Subject: [PATCH 07/20] Fix fixreply on different chats --- stdplugins/fixreply.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/stdplugins/fixreply.py b/stdplugins/fixreply.py index fded163..997ad6e 100644 --- a/stdplugins/fixreply.py +++ b/stdplugins/fixreply.py @@ -6,22 +6,22 @@ import asyncio from telethon import events -_last_message = None +_last_messages = {} @borg.on(events.NewMessage(outgoing=True)) async def _(event): - global _last_message - _last_message = event.message + _last_messages[event.chat_id] = event.message @borg.on(events.NewMessage(pattern=r"\.(fix)?reply", outgoing=True)) async def _(event): - if not event.is_reply or not _last_message: + if not event.is_reply or event.chat_id not in _last_messages: return + message = _last_messages[event.chat_id] chat = await event.get_input_chat() await asyncio.wait([ - borg.delete_messages(chat, [event.id, _last_message.id]), - borg.send_message(chat, _last_message, reply_to=event.reply_to_msg_id) + borg.delete_messages(chat, [event.id, message.id]), + borg.send_message(chat, message, reply_to=event.reply_to_msg_id) ]) From ca5619b9cc7fd75e9c86201b31e7673994975d74 Mon Sep 17 00:00:00 2001 From: Dan Elkouby Date: Sun, 31 Mar 2019 21:58:24 +0000 Subject: [PATCH 08/20] Add fpost plugin --- stdplugins/fpost.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 stdplugins/fpost.py diff --git a/stdplugins/fpost.py b/stdplugins/fpost.py new file mode 100644 index 0000000..b587acd --- /dev/null +++ b/stdplugins/fpost.py @@ -0,0 +1,27 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import string + +from telethon import events +from telethon.tl import types + +msg_cache = {} + + +@borg.on(events.NewMessage(pattern=r"\.fpost\s+(.*)", outgoing=True)) +async def _(event): + await event.delete() + text = event.pattern_match.group(1) + destination = await event.get_input_chat() + + for c in text.lower(): + if c not in string.ascii_lowercase: + continue + if c not in msg_cache: + async for msg in borg.iter_messages(None, search=c): + if msg.raw_text.lower() == c and msg.media is None: + msg_cache[c] = msg + break + await borg.forward_messages(destination, msg_cache[c]) From c2197e52803f67e325b2df784a5087329c418fa5 Mon Sep 17 00:00:00 2001 From: Dan Elkouby Date: Sat, 27 Apr 2019 10:22:36 +0000 Subject: [PATCH 09/20] Disable markdown in sed --- stdplugins/sed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdplugins/sed.py b/stdplugins/sed.py index aeaa8e5..78d8f9d 100644 --- a/stdplugins/sed.py +++ b/stdplugins/sed.py @@ -103,7 +103,7 @@ async def on_regex(event): if m is not None: s = f"{HEADER}{s}" out = await borg.send_message( - await event.get_input_chat(), s, reply_to=m.id + await event.get_input_chat(), s, reply_to=m.id, parse_mode=None ) last_msgs[chat_id].appendleft(out) elif s is not None: From c8b5179cb56552a2e3b224711f1ac5295ca11410 Mon Sep 17 00:00:00 2001 From: Lonami Date: Tue, 30 Apr 2019 20:32:35 +0200 Subject: [PATCH 10/20] Allow plugins to define an unload function --- uniborg/uniborg.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/uniborg/uniborg.py b/uniborg/uniborg.py index 97465f1..0d556b4 100644 --- a/uniborg/uniborg.py +++ b/uniborg/uniborg.py @@ -3,6 +3,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. import asyncio import importlib.util +import inspect import logging from pathlib import Path @@ -79,7 +80,13 @@ class Uniborg(TelegramClient): if cb.__module__ == name: del self._event_builders[i] - del self._plugins[shortname] + plugin = self._plugins.pop(shortname) + if callable(getattr(plugin, 'unload', None)): + unload = plugin.unload() + if inspect.isawaitable(unload): + await unload + + del plugin self._logger.info(f"Removed plugin {shortname}") def await_event(self, event_matcher, filter=None): From febe3c4bac4dd6e705ef48d97107bd6de82c7cd8 Mon Sep 17 00:00:00 2001 From: Lonami Date: Tue, 30 Apr 2019 20:34:05 +0200 Subject: [PATCH 11/20] Be more careful when unloading plugin --- uniborg/uniborg.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/uniborg/uniborg.py b/uniborg/uniborg.py index 0d556b4..77b3406 100644 --- a/uniborg/uniborg.py +++ b/uniborg/uniborg.py @@ -82,9 +82,12 @@ class Uniborg(TelegramClient): plugin = self._plugins.pop(shortname) if callable(getattr(plugin, 'unload', None)): - unload = plugin.unload() - if inspect.isawaitable(unload): - await unload + try: + unload = plugin.unload() + if inspect.isawaitable(unload): + await unload + except Exception: + self._logger.exception(f'Unhandled exception unloading {shortname}') del plugin self._logger.info(f"Removed plugin {shortname}") From 93cbb44459c828c56f6cf66274c8b9c51c85c566 Mon Sep 17 00:00:00 2001 From: Lonami Date: Tue, 30 Apr 2019 20:42:32 +0200 Subject: [PATCH 12/20] Make unload_plugin async for reasons --- uniborg/uniborg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uniborg/uniborg.py b/uniborg/uniborg.py index 77b3406..3dd3447 100644 --- a/uniborg/uniborg.py +++ b/uniborg/uniborg.py @@ -72,7 +72,7 @@ class Uniborg(TelegramClient): self._plugins[shortname] = mod self._logger.info(f"Successfully loaded plugin {shortname}") - def remove_plugin(self, shortname): + async def remove_plugin(self, shortname): name = self._plugins[shortname].__name__ for i in reversed(range(len(self._event_builders))): From 168ee165b9c2eb0d4827c2a48e5e06db60da00fa Mon Sep 17 00:00:00 2001 From: Lonami Date: Tue, 30 Apr 2019 20:44:08 +0200 Subject: [PATCH 13/20] await the now async remove_plugin --- uniborg/_core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uniborg/_core.py b/uniborg/_core.py index 7475028..43d8bd1 100644 --- a/uniborg/_core.py +++ b/uniborg/_core.py @@ -17,7 +17,7 @@ async def load_reload(event): try: if shortname in borg._plugins: - borg.remove_plugin(shortname) + await borg.remove_plugin(shortname) borg.load_plugin(shortname) msg = await event.respond( @@ -39,7 +39,7 @@ async def remove(event): if shortname == "_core": msg = await event.respond(f"Not removing {shortname}") elif shortname in borg._plugins: - borg.remove_plugin(shortname) + await borg.remove_plugin(shortname) msg = await event.respond(f"Removed plugin {shortname}") else: msg = await event.respond(f"Plugin {shortname} is not loaded") From fe6b87806433f04f0390cfcb80b0656a6a15f2fa Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 19 May 2019 23:10:01 +0200 Subject: [PATCH 14/20] Fix info formatting in some cases (#8) --- stdplugins/info.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/stdplugins/info.py b/stdplugins/info.py index a22fc5a..9129ce2 100644 --- a/stdplugins/info.py +++ b/stdplugins/info.py @@ -37,7 +37,7 @@ def yaml_format(obj, indent=0): has_multiple_items = len(items) > 2 if has_multiple_items: result.append('\n') - indent += 2 + indent += 2 for k, v in items: if k == '_' or v is None: continue @@ -45,11 +45,14 @@ def yaml_format(obj, indent=0): if not formatted.strip(): continue result.append(' ' * (indent if has_multiple_items else 1)) - result.append(f'{k}: {formatted}') + result.append(f'{k}:') + if not formatted[0].isspace(): + result.append(' ') + result.append(f'{formatted}') result.append('\n') result.pop() - indent -= 2 - result.append(' ' * indent) + if has_multiple_items: + indent -= 2 elif isinstance(obj, str): # truncate long strings and display elipsis result = repr(obj[:STR_LEN_MAX]) @@ -75,7 +78,6 @@ def yaml_format(obj, indent=0): result.append('\n') result.pop() indent -= 2 - result.append(' ' * indent) else: return repr(obj) From 1dd6e63d6a3ab9fa42dc6f29ca3acd83d2734108 Mon Sep 17 00:00:00 2001 From: Lonami Date: Mon, 20 May 2019 09:53:16 +0200 Subject: [PATCH 15/20] Use HTML in who --- stdplugins/who.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/stdplugins/who.py b/stdplugins/who.py index e4f5349..cea0cd9 100644 --- a/stdplugins/who.py +++ b/stdplugins/who.py @@ -14,6 +14,7 @@ async def _(event): else: msg = await event.message.get_reply_message() if msg.forward: + # FIXME forward privacy memes who = await borg.get_entity( msg.forward.from_id or msg.forward.channel_id) else: @@ -21,7 +22,7 @@ async def _(event): who_string = utils.get_display_name(who) if isinstance(who, (types.User, types.Channel)) and who.username: - who_string += f" (@{who.username})" - who_string += f", [#{who.id}](tg://user?id={who.id})" + who_string += f" (@{who.username})" + who_string += f", #{who.id}" - await event.edit(who_string) + await event.edit(who_string, parse_mode='html') From 6d67ebad0685c55d87a1cbc8222f1f8db96f89d2 Mon Sep 17 00:00:00 2001 From: Lonami Date: Sat, 8 Jun 2019 10:15:51 +0200 Subject: [PATCH 16/20] Dab *has* to be a standard plugin --- stdplugins/randomsticker.py | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 stdplugins/randomsticker.py diff --git a/stdplugins/randomsticker.py b/stdplugins/randomsticker.py new file mode 100644 index 0000000..2aa47e5 --- /dev/null +++ b/stdplugins/randomsticker.py @@ -0,0 +1,39 @@ +import random + +from telethon import events, types, functions, utils + + +def choser(cmd, pack, blacklist={}): + docs = None + @borg.on(events.NewMessage(pattern=rf'\.{cmd}', outgoing=True)) + async def handler(event): + await event.delete() + + nonlocal docs + if docs is None: + docs = [ + utils.get_input_document(x) + for x in (await borg(functions.messages.GetStickerSetRequest(types.InputStickerSetShortName(pack)))).documents + if x.id not in blacklist + ] + + await event.respond(file=random.choice(docs)) + + +choser('brain', 'supermind') +choser('dab', 'DabOnHaters', { + 1653974154589768377, + 1653974154589768312, + 1653974154589767857, + 1653974154589768311, + 1653974154589767816, + 1653974154589767939, + 1653974154589767944, + 1653974154589767912, + 1653974154589767911, + 1653974154589767910, + 1653974154589767909, + 1653974154589767863, + 1653974154589767852, + 1653974154589768677 +}) \ No newline at end of file From 69ccfd12077731375cb74a4d6dd3bcc075a669e9 Mon Sep 17 00:00:00 2001 From: Lonami Date: Sat, 8 Jun 2019 10:18:28 +0200 Subject: [PATCH 17/20] Add license verbatim --- stdplugins/randomsticker.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stdplugins/randomsticker.py b/stdplugins/randomsticker.py index 2aa47e5..b48ed8d 100644 --- a/stdplugins/randomsticker.py +++ b/stdplugins/randomsticker.py @@ -1,3 +1,7 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + import random from telethon import events, types, functions, utils From 332ecdcaeac92daa61db3d0af041883a0d8259e6 Mon Sep 17 00:00:00 2001 From: Lonami Date: Tue, 3 Sep 2019 21:50:39 +0200 Subject: [PATCH 18/20] Escape display name in who --- stdplugins/who.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stdplugins/who.py b/stdplugins/who.py index cea0cd9..897ec1c 100644 --- a/stdplugins/who.py +++ b/stdplugins/who.py @@ -1,6 +1,7 @@ # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +import html from telethon import events from telethon import utils @@ -20,7 +21,7 @@ async def _(event): else: who = await msg.get_sender() - who_string = utils.get_display_name(who) + who_string = html.escape(utils.get_display_name(who)) if isinstance(who, (types.User, types.Channel)) and who.username: who_string += f" (@{who.username})" who_string += f", #{who.id}" From 40c398833d4c48054ef3e1c26b2070210b2dcb7e Mon Sep 17 00:00:00 2001 From: Dan Elkouby Date: Sat, 7 Sep 2019 07:31:43 +0000 Subject: [PATCH 19/20] Add member list command --- stdplugins/who.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/stdplugins/who.py b/stdplugins/who.py index 897ec1c..33c81b0 100644 --- a/stdplugins/who.py +++ b/stdplugins/who.py @@ -8,6 +8,14 @@ from telethon import utils from telethon.tl import types +def get_who_string(who): + who_string = html.escape(utils.get_display_name(who)) + if isinstance(who, (types.User, types.Channel)) and who.username: + who_string += f" (@{who.username})" + who_string += f", #{who.id}" + return who_string + + @borg.on(events.NewMessage(pattern=r"\.who", outgoing=True)) async def _(event): if not event.message.is_reply: @@ -21,9 +29,13 @@ async def _(event): else: who = await msg.get_sender() - who_string = html.escape(utils.get_display_name(who)) - if isinstance(who, (types.User, types.Channel)) and who.username: - who_string += f" (@{who.username})" - who_string += f", #{who.id}" + await event.edit(get_who_string(who), parse_mode='html') - await event.edit(who_string, parse_mode='html') + +@borg.on(events.NewMessage(pattern=r"\.members", outgoing=True)) +async def _(event): + members = [ + get_who_string(m) async for m in borg.iter_participants(event.chat_id) + ] + + await event.edit("\n".join(members), parse_mode='html') From 9fe2e80742080b6e60372c75134e069a63b1bff7 Mon Sep 17 00:00:00 2001 From: Dan Elkouby Date: Sat, 7 Sep 2019 08:08:07 +0000 Subject: [PATCH 20/20] Count messages and sort by top posters --- stdplugins/who.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/stdplugins/who.py b/stdplugins/who.py index 33c81b0..3982344 100644 --- a/stdplugins/who.py +++ b/stdplugins/who.py @@ -34,8 +34,19 @@ async def _(event): @borg.on(events.NewMessage(pattern=r"\.members", outgoing=True)) async def _(event): - members = [ - get_who_string(m) async for m in borg.iter_participants(event.chat_id) - ] + members = [] + async for member in borg.iter_participants(event.chat_id): + messages = await borg.get_messages( + event.chat_id, + from_user=member, + limit=0 + ) + members.append(( + messages.total, + f"{messages.total} - {get_who_string(member)}" + )) + members = ( + m[1] for m in sorted(members, key=lambda m: m[0], reverse=True) + ) await event.edit("\n".join(members), parse_mode='html')