├── .gitignore ├── LICENSE ├── README.md ├── assistant ├── __main__.py ├── assistant.py ├── plugins │ ├── commands.py │ ├── inline.py │ ├── private.py │ ├── repaste.py │ └── welcome.py └── utils │ └── docs.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Personal files 2 | *.session 3 | *.ini 4 | 5 | # PyCharm stuff 6 | .idea/ 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | wheels/ 30 | pip-wheel-metadata/ 31 | share/python-wheels/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | MANIFEST 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .nox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *.cover 57 | .hypothesis/ 58 | .pytest_cache/ 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | local_settings.py 67 | db.sqlite3 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | .python-version 91 | 92 | # celery beat schedule file 93 | celerybeat-schedule 94 | 95 | # SageMath parsed files 96 | *.sage.py 97 | 98 | # Environments 99 | .env 100 | .venv 101 | env/ 102 | venv/ 103 | ENV/ 104 | env.bak/ 105 | venv.bak/ 106 | 107 | # Spyder project settings 108 | .spyderproject 109 | .spyproject 110 | 111 | # Rope project settings 112 | .ropeproject 113 | 114 | # mkdocs documentation 115 | /site 116 | 117 | # mypy 118 | .mypy_cache/ 119 | .dmypy.json 120 | dmypy.json 121 | 122 | # Pyre type checker 123 | .pyre/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-present Dan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pyrogram Assistant 2 | 3 | > The assistant bot that helps people with [Pyrogram](//github.com/pyrogram/pyrogram) directly on Telegram. 4 | 5 | This repository contains the source code of [@PyrogramBot](//t.me/pyrogrambot) and the instructions for running a 6 | copy yourself. Beside its main purpose, the bot is featuring [**Pyrogram Asyncio**](//github.com/pyrogram/pyrogram/issues/181), 7 | [**Smart Plugins**](//docs.pyrogram.org/topics/smart-plugins) and **Inline Mode**; feel free to explore the source code to 8 | learn more about these topics. 9 | 10 | ## Requirements 11 | 12 | - Python 3.6 or higher. 13 | - A [Telegram API key](//docs.pyrogram.org/intro/setup#api-keys). 14 | - A [Telegram bot token](//t.me/botfather). 15 | 16 | ## Run 17 | 18 | 1. `git clone https://github.com/pyrogram/assistant`, to download the source code. 19 | 2. `cd assistant`, to enter the directory. 20 | 3. `python3 -m venv venv && . venv/bin/activate` to create and activate a virtual environment. 21 | 3. `pip install -U -r requirements.txt`, to install the requirements. 22 | 4. Create a new `assistant.ini` file, copy-paste the following and replace the values with your own: 23 | ```ini 24 | [pyrogram] 25 | api_id = 12345 26 | api_hash = 0123456789abcdef0123456789abcdef 27 | bot_token = 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11 28 | ``` 29 | 5. Run with `python -m assistant`. 30 | 6. Stop with CTRL+C and `deactivate` the virtual environment. 31 | 32 | ## License 33 | 34 | MIT © 2019-present [Dan](//github.com/delivrance) 35 | -------------------------------------------------------------------------------- /assistant/__main__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2019-present Dan 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from .assistant import Assistant 24 | 25 | if __name__ == "__main__": 26 | Assistant().run() 27 | -------------------------------------------------------------------------------- /assistant/assistant.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2019-present Dan 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import time 24 | from datetime import datetime 25 | from configparser import ConfigParser 26 | from pyrogram import Client, Message 27 | from pyrogram import __version__ 28 | from pyrogram.raw.all import layer 29 | from pyrogram.types import Message 30 | 31 | 32 | class Assistant(Client): 33 | CREATOR_ID = 23122162 # Dan (haskell) 34 | ASSISTANT_ID = 483849041 35 | config = ConfigParser() 36 | chats = [] 37 | 38 | def __init__(self): 39 | name = self.__class__.__name__.lower() 40 | 41 | self.config.read(f"{name}.ini") 42 | Assistant.chats = [int(c) for c in self.config[name]["chats"].split()] 43 | 44 | super().__init__( 45 | name, 46 | config_file=f"{name}.ini", 47 | workers=16, 48 | plugins=dict( 49 | root=f"{name}.plugins", 50 | exclude=["welcome"] 51 | ), 52 | sleep_threshold=180 53 | ) 54 | 55 | self.admins = { 56 | chat: {Assistant.CREATOR_ID} 57 | for chat in Assistant.chats 58 | } 59 | 60 | self.uptime_reference = time.monotonic_ns() 61 | self.start_datetime = datetime.utcnow() 62 | 63 | async def start(self): 64 | await super().start() 65 | 66 | me = await self.get_me() 67 | print(f"Assistant for Pyrogram v{__version__} (Layer {layer}) started on @{me.username}. Hi.") 68 | 69 | # Fetch current admins from chats 70 | for chat, admins in self.admins.items(): 71 | async for admin in self.iter_chat_members(chat, filter="administrators"): 72 | admins.add(admin.user.id) 73 | 74 | async def stop(self, *args): 75 | await super().stop() 76 | print("Pyrogram Assistant stopped. Bye.") 77 | 78 | def is_admin(self, message: Message) -> bool: 79 | user_id = message.from_user.id 80 | chat_id = message.chat.id 81 | 82 | return user_id in self.admins[chat_id] 83 | -------------------------------------------------------------------------------- /assistant/plugins/commands.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2019-present Dan 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import asyncio 24 | import time 25 | from functools import partial, wraps 26 | 27 | import aiohttp 28 | from num2words import num2words 29 | from pyrogram import filters, emoji 30 | from pyrogram.types import CallbackQuery, ChatPermissions, InlineKeyboardButton, InlineKeyboardMarkup, Message 31 | 32 | from ..assistant import Assistant 33 | from ..utils import docs 34 | 35 | command = partial(filters.command, prefixes=list("#!")) 36 | 37 | 38 | async def reply_and_delete(message: Message, text: str): 39 | await asyncio.gather( 40 | message.delete(), 41 | message.reply( 42 | text, 43 | quote=False, 44 | reply_to_message_id=getattr( 45 | message.reply_to_message, 46 | "message_id", None 47 | ), 48 | disable_web_page_preview=True 49 | ) 50 | ) 51 | 52 | 53 | def admins_only(func): 54 | @wraps(func) 55 | async def decorator(bot: Assistant, message: Message): 56 | if bot.is_admin(message): 57 | await func(bot, message) 58 | 59 | await message.delete() 60 | 61 | decorator.admin = True 62 | 63 | return decorator 64 | 65 | 66 | ################################ 67 | 68 | PING_TTL = 5 69 | 70 | 71 | @Assistant.on_message(command("ping")) 72 | async def ping(_, message: Message): 73 | """Ping the assistant""" 74 | start = time.time() 75 | reply = await message.reply_text("...") 76 | delta_ping = time.time() - start 77 | await reply.edit_text(f"**Pong!** `{delta_ping * 1000:.3f} ms`") 78 | 79 | 80 | ################################ 81 | 82 | 83 | SCHEMA = "https" 84 | BASE = "nekobin.com" 85 | ENDPOINT = f"{SCHEMA}://{BASE}/api/documents" 86 | ANSWER = "**Long message from** {}\n{}" 87 | TIMEOUT = 3 88 | MESSAGE_ID_DIFF = 100 89 | 90 | 91 | @Assistant.on_message(command("neko")) 92 | @admins_only 93 | async def neko(_, message: Message): 94 | """Paste very long code""" 95 | reply = message.reply_to_message 96 | 97 | if not reply: 98 | return 99 | 100 | # Ignore messages that are too old 101 | if message.message_id - reply.message_id > MESSAGE_ID_DIFF: 102 | return 103 | 104 | async with aiohttp.ClientSession() as session: 105 | async with session.post( 106 | ENDPOINT, 107 | json={"content": reply.text}, 108 | timeout=TIMEOUT 109 | ) as response: 110 | key = (await response.json())["result"]["key"] 111 | 112 | await reply_and_delete(reply, ANSWER.format(reply.from_user.mention, f"{BASE}/{key}.py")) 113 | 114 | 115 | ################################ 116 | 117 | 118 | LOG = """ 119 | Enable Logging: add the following on the top of your script and run it again: 120 | 121 | ```import logging 122 | logging.basicConfig(level=logging.INFO)``` 123 | 124 | For a more verbose logging, use `level=logging.DEBUG` instead. 125 | """ 126 | 127 | 128 | @Assistant.on_message(command("log")) 129 | async def log(_, message: Message): 130 | """Enable debug logging""" 131 | await reply_and_delete(message, LOG) 132 | 133 | 134 | ################################ 135 | 136 | 137 | EX = """ 138 | Please, provide us a **minimal** and **reproducible** example in order to easily understand and reproduce the problem. 139 | 140 | [How do I create a minimal, reproducible example?](https://stackoverflow.com/help/minimal-reproducible-example) 141 | """ 142 | 143 | 144 | @Assistant.on_message(command("ex")) 145 | async def ex(_, message: Message): 146 | """Ask for minimal example""" 147 | await reply_and_delete(message, EX) 148 | 149 | 150 | ################################ 151 | 152 | 153 | OT = [ 154 | "This argument is off-topic and not related to Pyrogram. Please, move the discussion to @PyrogramLounge", 155 | "Looks like this topic is related to Pyrogram. You can discuss at @PyrogramChat" 156 | ] 157 | 158 | 159 | @Assistant.on_message(command("ot")) 160 | async def ot(_, message: Message): 161 | """offtopic conversation""" 162 | answer = OT[0] if message.chat.id == -1001387666944 else OT[1] # @PyrogramChat id 163 | await reply_and_delete(message, answer) 164 | 165 | 166 | ################################ 167 | 168 | 169 | ASK = """ 170 | Sorry, your question is not well formulated. Please, be clear and try to follow the guidelines in the link below. 171 | 172 | [How do I ask a good question?](https://stackoverflow.com/help/how-to-ask) 173 | """ 174 | 175 | 176 | @Assistant.on_message(command("ask")) 177 | async def ask(_, message: Message): 178 | """How to ask questions""" 179 | await reply_and_delete(message, ASK) 180 | 181 | 182 | ################################ 183 | 184 | 185 | RES = """ 186 | **Good Python resources for learning** 187 | 188 | • [Official Tutorial](https://docs.python.org/3/tutorial/index.html) - Book 189 | • [Dive Into Python 3](https://www.diveinto.org/python3/table-of-contents.html) - Book 190 | • [Hitchhiker's Guide!](https://docs.python-guide.org) - Book 191 | • [Learn Python](https://www.learnpython.org/) - Interactive 192 | • [Project Python](http://projectpython.net) - Interactive 193 | • [Python Video Tutorials](https://www.youtube.com/playlist?list=PL-osiE80TeTt2d9bfVyTiXJA-UTHn6WwU) - Video 194 | • [MIT OpenCourseWare](http://ocw.mit.edu/6-0001F16) - Course 195 | • @PythonRes - Channel 196 | """ 197 | 198 | 199 | @Assistant.on_message(command("res")) 200 | async def res(_, message: Message): 201 | """Good Python resources""" 202 | await reply_and_delete(message, RES) 203 | 204 | 205 | ################################ 206 | 207 | 208 | LEARN = "Your issue is not related to Pyrogram. Please, learn more Python and try again.\n" + RES 209 | 210 | 211 | @Assistant.on_message(command("learn")) 212 | async def learn(_, message: Message): 213 | """Tell to learn Python""" 214 | await reply_and_delete(message, LEARN) 215 | 216 | 217 | ################################ 218 | 219 | 220 | # One place for all rules, the docs. 221 | RULES = docs.rules 222 | 223 | 224 | @Assistant.on_message(command("rules")) 225 | async def rules(_, message: Message): 226 | """Show Pyrogram rules""" 227 | # Still ugly. Patching Colin's code. 228 | # noinspection PyBroadException 229 | try: 230 | index = int(message.command[1]) 231 | split = RULES.strip().split("\n") 232 | text = f"{split[0]}\n\n{split[index + 2]}" 233 | except Exception: 234 | text = RULES 235 | 236 | await reply_and_delete(message, text) 237 | 238 | 239 | @Assistant.on_message( 240 | filters.via_bot 241 | & filters.regex(r"^Pyrogram Rules\n") 242 | & ~filters.regex(r"^Pyrogram Rules[\s\S]+notice\.$") 243 | ) # I know this is ugly, but this way we don't filter the full ruleset lol 244 | async def repost_rules(_, message: Message): 245 | code = message.entities[-1] 246 | index = int(message.text[code.offset:code.offset + code.length][:-1]) 247 | split = RULES.split("\n") 248 | text = f"{split[1]}\n\n{split[3:-3][index]}" 249 | # First split index is a newline, thus using 1, 250 | # also -1 because we have a literal in the message, not a list index :D 251 | await reply_and_delete(message, text) 252 | 253 | 254 | ################################ 255 | 256 | 257 | GROUPS = f""" 258 | **Pyrogram group chats** 259 | 260 | __Main groups__ 261 | [{emoji.GLOBE_WITH_MERIDIANS} International (English)](t.me/PyrogramChat) 262 | [{emoji.SPEECH_BALLOON} Offtopic group](t.me/PyrogramLounge) 263 | 264 | __Other groups__ 265 | [{emoji.FLAG_ITALY} Italian](t.me/joinchat/AWDQ8lDPvwpWu3UH4Bx9Uw) 266 | [{emoji.FLAG_IRAN} Farsi](t.me/PyrogramIR) 267 | [{emoji.FLAG_BRAZIL} Portuguese](t.me/PyrogramBR) 268 | [{emoji.FLAG_INDONESIA} Indonesian](t.me/PyrogramID) 269 | [{emoji.FLAG_RUSSIA} Russian](t.me/RuPyrogram) 270 | [{emoji.FLAG_ISRAEL} Hebrew](t.me/PyrogramHe) 271 | 272 | __If you want to host and maintain a group dedicated to your language, let us know!__ 273 | """ 274 | 275 | 276 | @Assistant.on_message(command("groups")) 277 | async def groups(_, message: Message): 278 | """Show all groups""" 279 | await reply_and_delete(message, GROUPS) 280 | 281 | 282 | ################################ 283 | 284 | 285 | FAQ = ( 286 | "Your question has already been answered in the FAQ section of the documentation. " 287 | "Please, head on to [Pyrogram FAQs](https://docs.pyrogram.org/faq) now" 288 | ) 289 | 290 | 291 | @Assistant.on_message(command("faq")) 292 | async def faq(_, message: Message): 293 | """Answer is in the FAQ""" 294 | await reply_and_delete(message, FAQ) 295 | 296 | 297 | ################################ 298 | 299 | 300 | RTD = "Please, read the docs: https://docs.pyrogram.org" 301 | 302 | 303 | @Assistant.on_message(command("rtd")) 304 | async def rtd(_, message: Message): 305 | """Tell to RTD (gentle)""" 306 | await reply_and_delete(message, RTD) 307 | 308 | 309 | ################################ 310 | 311 | 312 | RTFD = "You seem to be lost...\n\nGo read the **fscking docs**: https://docs.pyrogram.org" 313 | 314 | 315 | @Assistant.on_message(command("rtfd")) 316 | @admins_only 317 | async def rtfd(_, message: Message): 318 | """Tell to RTFD (rude)""" 319 | await asyncio.gather( 320 | message.delete(), 321 | message.reply_photo( 322 | "https://i.imgur.com/a08Ju2e.png", 323 | quote=False, 324 | caption=RTFD, 325 | reply_to_message_id=getattr( 326 | message.reply_to_message, 327 | "message_id", None 328 | ) 329 | ) 330 | ) 331 | 332 | 333 | ################################ 334 | 335 | 336 | FMT = ( 337 | "Please format your code with triple backticks to make it more readable.\n" 338 | "```your code here```" 339 | ) 340 | 341 | 342 | @Assistant.on_message(command("fmt")) 343 | @admins_only 344 | async def fmt(_, message: Message): 345 | """Tell to format code""" 346 | await asyncio.gather( 347 | message.delete(), 348 | message.reply( 349 | FMT, 350 | quote=False, 351 | parse_mode="html", 352 | disable_web_page_preview=True, 353 | reply_to_message_id=getattr( 354 | message.reply_to_message, 355 | "message_id", None 356 | ), 357 | ) 358 | ) 359 | 360 | 361 | ################################ 362 | 363 | 364 | DEV = ( 365 | "The fix for this issue has already been pushed to the `master` branch on " 366 | "[GitHub](https://github.com/pyrogram/pyrogram). You can now upgrade Pyrogram with:\n\n" 367 | "`pip3 install -U https://github.com/pyrogram/pyrogram/archive/master.zip`" 368 | ) 369 | 370 | 371 | @Assistant.on_message(command("dev")) 372 | async def dev(_, message: Message): 373 | """Fixed in dev branch""" 374 | await reply_and_delete(message, DEV) 375 | 376 | 377 | ################################ 378 | 379 | MESSAGE_DATE_DIFF = 43200 # 12h 380 | 381 | 382 | @Assistant.on_message(command("delete")) 383 | @admins_only 384 | async def delete(bot: Assistant, message: Message): 385 | """Delete messages""" 386 | reply = message.reply_to_message 387 | 388 | if not reply: 389 | return 390 | 391 | # Don't delete admins messages 392 | if bot.is_admin(reply): 393 | m = await message.reply("Sorry, I don't delete administrators' messages.") 394 | await asyncio.sleep(5) 395 | await m.delete() 396 | return 397 | 398 | # Don't delete messages that are too old 399 | if message.date - reply.date > MESSAGE_DATE_DIFF: 400 | m = await message.reply("Sorry, I don't delete messages that are too old.") 401 | await asyncio.sleep(5) 402 | await m.delete() 403 | return 404 | 405 | cmd = message.command 406 | 407 | # No args, delete the mentioned message alone 408 | if len(cmd) == 1: 409 | await reply.delete() 410 | return 411 | 412 | # Delete the last N messages of the mentioned user, up to 200 413 | 414 | arg = int(cmd[1]) 415 | 416 | # Min 1 max 200 417 | arg = max(arg, 1) 418 | arg = min(arg, 200) 419 | 420 | last_200 = range(reply.message_id, reply.message_id - 200, -1) 421 | 422 | message_ids = [ 423 | m.message_id for m in filter( 424 | lambda m: m.from_user and m.from_user.id == reply.from_user.id, 425 | await bot.get_messages(message.chat.id, last_200, replies=0) 426 | ) 427 | ] 428 | 429 | await bot.delete_messages(message.chat.id, message_ids[:arg]) 430 | 431 | 432 | ################################ 433 | 434 | @Assistant.on_message(command("ban")) 435 | @admins_only 436 | async def ban(bot: Assistant, message: Message): 437 | """Ban a user in chat""" 438 | reply = message.reply_to_message 439 | 440 | if not reply: 441 | return 442 | 443 | # Don't ban admins 444 | if bot.is_admin(reply): 445 | m = await message.reply("Sorry, I don't ban administrators") 446 | await asyncio.sleep(5) 447 | await m.delete() 448 | return 449 | 450 | await bot.restrict_chat_member(message.chat.id, reply.from_user.id, ChatPermissions()) 451 | 452 | await message.reply( 453 | f"__Banned {reply.from_user.mention} indefinitely__", 454 | quote=False, 455 | reply_markup=InlineKeyboardMarkup([[ 456 | InlineKeyboardButton("Give grace", f"unban.{reply.from_user.id}") 457 | ]]) 458 | ) 459 | 460 | 461 | ################################ 462 | 463 | @Assistant.on_message(command("kick")) 464 | @admins_only 465 | async def kick(bot: Assistant, message: Message): 466 | """Kick (they can rejoin)""" 467 | reply = message.reply_to_message 468 | 469 | if not reply: 470 | return 471 | 472 | # Don't kick admins 473 | if bot.is_admin(reply): 474 | m = await message.reply("Sorry, I don't kick administrators") 475 | await asyncio.sleep(5) 476 | await m.delete() 477 | return 478 | 479 | # Default ban until_time 60 seconds later as failsafe in case unban doesn't work 480 | # (can happen in case the server processes unban before ban and thus ignoring unban) 481 | await bot.kick_chat_member(message.chat.id, reply.from_user.id, int(time.time()) + 60) 482 | 483 | await message.reply( 484 | f"__Kicked {reply.from_user.mention}. They can rejoin__", 485 | quote=False 486 | ) 487 | 488 | await asyncio.sleep(5) # Sleep to allow the server some time to process the kick 489 | await bot.unban_chat_member(message.chat.id, reply.from_user.id) 490 | 491 | 492 | ################################ 493 | 494 | @Assistant.on_message(command("nab")) 495 | @admins_only 496 | async def nab(bot: Assistant, message: Message): 497 | reply = message.reply_to_message 498 | 499 | if not reply: 500 | return 501 | 502 | target = reply.from_user 503 | 504 | if target.id in [bot.CREATOR_ID, bot.ASSISTANT_ID]: 505 | target = message.from_user 506 | 507 | await message.reply( 508 | f"__Banned {target.mention} indefinitely__", 509 | quote=False 510 | ) 511 | 512 | 513 | ################################ 514 | 515 | LOCKED = f"{emoji.LOCKED} Chat has been locked. Send #unlock to unlock." 516 | UNLOCKED = f"{emoji.UNLOCKED} Chat has been unlocked." 517 | 518 | PERMISSIONS = { 519 | -1001387666944: ChatPermissions( # Inn 520 | can_send_messages=True, 521 | can_send_media_messages=True, 522 | can_use_inline_bots=True 523 | ), 524 | -1001221450384: ChatPermissions( # Lounge 525 | can_send_messages=True, 526 | can_send_media_messages=True, 527 | can_send_stickers=True, 528 | can_send_animations=True, 529 | can_send_games=True, 530 | can_use_inline_bots=True, 531 | can_send_polls=True 532 | ), 533 | -1001355792138: ChatPermissions( # Italian Group 534 | can_send_messages=True, 535 | can_send_media_messages=True, 536 | can_send_stickers=True, 537 | can_send_animations=True, 538 | can_send_games=True, 539 | can_use_inline_bots=True 540 | ) 541 | } 542 | 543 | 544 | @Assistant.on_message(command("lock")) 545 | @admins_only 546 | async def lock(bot: Assistant, message: Message): 547 | """Lock the Chat""" 548 | await bot.set_chat_permissions(message.chat.id, ChatPermissions(can_send_messages=False)) 549 | await reply_and_delete(message, LOCKED) 550 | 551 | 552 | @Assistant.on_message(command("unlock")) 553 | @admins_only 554 | async def unlock(bot: Assistant, message: Message): 555 | """Unlock the Chat""" 556 | await bot.set_chat_permissions(message.chat.id, PERMISSIONS[message.chat.id]) 557 | await reply_and_delete(message, UNLOCKED) 558 | 559 | 560 | ################################ 561 | 562 | EVIL = ( 563 | "Pyrogram is free, open-source and community driven software; " 564 | "this means you are completely free to use it for any purpose whatsoever. " 565 | "However, help and support is a privilege and nobody is obligated to assist you, " 566 | "especially if you want to misbehave or harm Telegram with evil actions." 567 | ) 568 | 569 | 570 | @Assistant.on_message(command("evil")) 571 | async def evil(_, message: Message): 572 | """No help for evil actions""" 573 | await reply_and_delete(message, EVIL) 574 | 575 | 576 | ################################ 577 | 578 | # Pattern: https://regex101.com/r/6xdeRf/3 579 | @Assistant.on_callback_query(filters.regex(r"^(?Premove|unban)\.(?P\d+)")) 580 | async def cb_query(bot: Assistant, query: CallbackQuery): 581 | match = query.matches[0] 582 | action = match.group("action") 583 | user_id = int(match.group("uid")) 584 | text = query.message.text 585 | 586 | if action == "unban": 587 | if query.from_user.id != Assistant.CREATOR_ID: 588 | await query.answer("Only Dan can pardon banned users", show_alert=True) 589 | return 590 | 591 | await bot.restrict_chat_member( 592 | query.message.chat.id, 593 | user_id, 594 | ChatPermissions( 595 | can_send_messages=True, 596 | can_send_media_messages=True, 597 | can_send_stickers=True, 598 | can_send_animations=True, 599 | can_send_games=True, 600 | can_use_inline_bots=True, 601 | can_add_web_page_previews=True, 602 | can_send_polls=True, 603 | can_change_info=True, 604 | can_invite_users=True, 605 | can_pin_messages=True 606 | ) 607 | ) 608 | 609 | await query.edit_message_text(f"~~{text.markdown}~~\n\nPardoned") 610 | 611 | if action == "remove": 612 | # Dummy Message object to check if the user is admin. 613 | # Re: https://t.me/pyrogramlounge/324583 614 | dummy = Message( 615 | message_id=0, 616 | from_user=query.from_user, 617 | chat=query.message.chat 618 | ) 619 | if query.from_user.id == user_id or bot.is_admin(dummy): 620 | await query.answer() 621 | await query.message.delete() 622 | else: 623 | await query.answer("Only Admins can remove the help messages.") 624 | 625 | 626 | ################################ 627 | 628 | @Assistant.on_message(command("up")) 629 | async def up(bot: Assistant, message: Message): 630 | """Show Assistant's uptime""" 631 | uptime = time.monotonic_ns() - bot.uptime_reference 632 | 633 | us, ns = divmod(uptime, 1000) 634 | ms, us = divmod(us, 1000) 635 | s, ms = divmod(ms, 1000) 636 | m, s = divmod(s, 60) 637 | h, m = divmod(m, 60) 638 | d, h = divmod(h, 24) 639 | 640 | try: 641 | arg = message.command[1] 642 | except IndexError: 643 | await reply_and_delete(message, f"**Uptime**: `{d}d {h}h {m}m {s}s`") 644 | else: 645 | if arg == "-v": 646 | await reply_and_delete( 647 | message, 648 | f"**Uptime**: `{d}d {h}h {m}m {s}s {ms}ms {us}μs {ns}ns`\n" 649 | f"**Since**: `{bot.start_datetime} UTC`" 650 | ) 651 | elif arg == "-p": 652 | await reply_and_delete( 653 | message, 654 | f"**Uptime**: " 655 | f"`{num2words(d)} days, {num2words(h)} hours, {num2words(m)} minutes, " 656 | f"{num2words(s)} seconds, {num2words(ms)} milliseconds, " 657 | f"{num2words(us)} microseconds, {num2words(ns)} nanoseconds`\n" 658 | f"" 659 | f"**Since**: `year {num2words(bot.start_datetime.year)}, " 660 | f"month {bot.start_datetime.strftime('%B').lower()}, day {num2words(bot.start_datetime.day)}, " 661 | f"hour {num2words(bot.start_datetime.hour)}, minute {num2words(bot.start_datetime.minute)}, " 662 | f"second {num2words(bot.start_datetime.second)}, " 663 | f"microsecond {num2words(bot.start_datetime.microsecond)}, Coordinated Universal Time`" 664 | ) 665 | else: 666 | await message.delete() 667 | 668 | 669 | ################################ 670 | 671 | # noinspection PyShadowingBuiltins 672 | @Assistant.on_message(command("help")) 673 | async def help(bot: Assistant, message: Message): 674 | """Show this message""" 675 | await asyncio.gather( 676 | message.delete(), 677 | message.reply( 678 | HELP, 679 | quote=False, 680 | reply_to_message_id=getattr( 681 | message.reply_to_message, 682 | "message_id", None 683 | ), 684 | reply_markup=InlineKeyboardMarkup([[ 685 | InlineKeyboardButton("Remove Help", f"remove.{message.from_user.id}") 686 | ]]), 687 | ) 688 | ) 689 | 690 | 691 | ################################ 692 | 693 | nl = "\n" 694 | 695 | HELP = f""" 696 | **List of available commands** 697 | 698 | {nl.join( 699 | f"• #{fn[0]}{'`*`' if hasattr(fn[1], 'admin') else ''} - {fn[1].__doc__}" 700 | for fn in locals().items() 701 | if hasattr(fn[1], "handler") 702 | and fn[0] not in ["cb_query", "repost_rules", "nab"])} 703 | 704 | `*` Administrators only 705 | """ 706 | -------------------------------------------------------------------------------- /assistant/plugins/inline.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2019-present Dan 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from pyrogram import emoji, __version__ 24 | from pyrogram.types import (InlineQuery, InlineQueryResultArticle, InlineQueryResultPhoto, InputTextMessageContent, 25 | InlineKeyboardButton, InlineKeyboardMarkup) 26 | 27 | from ..assistant import Assistant 28 | from ..utils import docs 29 | 30 | NEXT_OFFSET = 25 31 | CACHE_TIME = 5 32 | 33 | FIRE_THUMB = "https://i.imgur.com/qhYYqZa.png" 34 | ROCKET_THUMB = "https://i.imgur.com/PDaYHd8.png" 35 | OPEN_BOOK_THUMB = "https://i.imgur.com/v1XSJ1D.png" 36 | SCROLL_THUMB = "https://i.imgur.com/L1u0VlX.png" 37 | 38 | VERSION = __version__.split("-")[0] 39 | 40 | 41 | @Assistant.on_inline_query() 42 | async def inline(_, query: InlineQuery): 43 | string = query.query.lower() 44 | 45 | if string == "": 46 | await query.answer( 47 | results=docs.DEFAULT_RESULTS, 48 | cache_time=CACHE_TIME, 49 | switch_pm_text=f"{emoji.MAGNIFYING_GLASS_TILTED_RIGHT} Type to search Pyrogram Docs", 50 | switch_pm_parameter="start", 51 | ) 52 | 53 | return 54 | 55 | results = [] 56 | offset = int(query.offset or 0) 57 | switch_pm_text = f"{emoji.OPEN_BOOK} Pyrogram Docs" 58 | 59 | if string == "!m": 60 | switch_pm_text = f"{emoji.CLOSED_BOOK} Pyrogram Methods ({len(docs.METHODS)})" 61 | 62 | if offset == 0: 63 | results.append( 64 | InlineQueryResultArticle( 65 | title="Methods", 66 | description="Pyrogram Methods online documentation page", 67 | input_message_content=InputTextMessageContent( 68 | f"{emoji.FIRE} **Pyrogram Methods**\n\n" 69 | f"`This page contains all available high-level Methods existing in Pyrogram v{VERSION}.`" 70 | ), 71 | reply_markup=InlineKeyboardMarkup([[ 72 | InlineKeyboardButton( 73 | f"{emoji.OPEN_BOOK} Online docs", 74 | url="https://docs.pyrogram.org/api/methods" 75 | ) 76 | ]]), 77 | thumb_url=FIRE_THUMB, 78 | ) 79 | ) 80 | 81 | for i in docs.METHODS[offset: offset + NEXT_OFFSET]: 82 | results.append(i[1]) 83 | elif string == "!t": 84 | switch_pm_text = f"{emoji.GREEN_BOOK} Pyrogram Types ({len(docs.TYPES)})" 85 | 86 | if offset == 0: 87 | results.append( 88 | InlineQueryResultArticle( 89 | title="Types", 90 | description="Pyrogram Types online documentation page", 91 | input_message_content=InputTextMessageContent( 92 | f"{emoji.FIRE} **Pyrogram Types**\n\n" 93 | f"`This page contains all available high-level Types existing in Pyrogram v{VERSION}.`" 94 | ), 95 | reply_markup=InlineKeyboardMarkup([[ 96 | InlineKeyboardButton( 97 | f"{emoji.OPEN_BOOK} Online docs", 98 | url="https://docs.pyrogram.org/api/types" 99 | ) 100 | ]]), 101 | thumb_url=FIRE_THUMB, 102 | ) 103 | ) 104 | 105 | for i in docs.TYPES[offset: offset + NEXT_OFFSET]: 106 | results.append(i[1]) 107 | elif string == "!b": 108 | switch_pm_text = f"{emoji.CLOSED_BOOK} Pyrogram Bound Methods ({len(docs.BOUND_METHODS)})" 109 | 110 | if offset == 0: 111 | results.append( 112 | InlineQueryResultArticle( 113 | title="Types", 114 | description="Pyrogram Bound Methods online documentation page", 115 | input_message_content=InputTextMessageContent( 116 | f"{emoji.FIRE} **Pyrogram Bound Methods**\n\n" 117 | f"`This page contains all available bound methods existing in Pyrogram v{VERSION}.`" 118 | ), 119 | reply_markup=InlineKeyboardMarkup([[ 120 | InlineKeyboardButton( 121 | f"{emoji.OPEN_BOOK} Online docs", 122 | url="https://docs.pyrogram.org/api/bound-methods" 123 | )]] 124 | ), 125 | thumb_url=FIRE_THUMB, 126 | ) 127 | ) 128 | 129 | for i in docs.BOUND_METHODS[offset: offset + NEXT_OFFSET]: 130 | results.append(i[1]) 131 | elif string == "!d": 132 | switch_pm_text = f"{emoji.CLOSED_BOOK} Pyrogram Decorators ({len(docs.DECORATORS)})" 133 | 134 | if offset == 0: 135 | results.append( 136 | InlineQueryResultArticle( 137 | title="Decorators", 138 | description="Pyrogram Decorators online documentation page", 139 | input_message_content=InputTextMessageContent( 140 | f"{emoji.FIRE} **Pyrogram Decorators**\n\n" 141 | f"`This page contains all available decorators existing in Pyrogram v{VERSION}.`" 142 | ), 143 | reply_markup=InlineKeyboardMarkup([[ 144 | InlineKeyboardButton( 145 | f"{emoji.OPEN_BOOK} Online docs", 146 | url="https://docs.pyrogram.org/api/decorators" 147 | )]] 148 | ), 149 | thumb_url=FIRE_THUMB, 150 | ) 151 | ) 152 | 153 | for i in docs.DECORATORS[offset: offset + NEXT_OFFSET]: 154 | results.append(i[1]) 155 | elif string == "!f": 156 | switch_pm_text = f"{emoji.CONTROL_KNOBS} Pyrogram Filters ({len(docs.FILTERS)})" 157 | 158 | if offset == 0: 159 | results.append( 160 | InlineQueryResultArticle( 161 | title="Filters", 162 | description="Pyrogram Filters online documentation page", 163 | input_message_content=InputTextMessageContent( 164 | f"{emoji.FIRE} **Pyrogram Filters**\n\n" 165 | f"`This page contains all library-defined Filters available in Pyrogram v{VERSION}.`" 166 | ), 167 | reply_markup=InlineKeyboardMarkup([[ 168 | InlineKeyboardButton( 169 | f"{emoji.OPEN_BOOK} Online docs", 170 | url="https://docs.pyrogram.org/api/filters" 171 | ) 172 | ]]), 173 | thumb_url=FIRE_THUMB, 174 | ) 175 | ) 176 | 177 | for i in docs.FILTERS[offset: offset + NEXT_OFFSET]: 178 | results.append(i[1]) 179 | elif string == "!rm": 180 | switch_pm_text = f"{emoji.BLUE_BOOK} Raw Methods ({len(docs.RAW_METHODS)})" 181 | 182 | if offset == 0: 183 | results.append( 184 | InlineQueryResultArticle( 185 | title="Raw Methods", 186 | description="Pyrogram Raw Methods online documentation page", 187 | input_message_content=InputTextMessageContent( 188 | f"{emoji.FIRE} **Pyrogram Raw Methods**\n\n" 189 | f"`This page contains all available Raw Methods existing in the Telegram Schema, Layer `" 190 | f"`{docs.layer}.`" 191 | ), 192 | reply_markup=InlineKeyboardMarkup([[ 193 | InlineKeyboardButton( 194 | f"{emoji.OPEN_BOOK} Online docs", 195 | url="https://docs.pyrogram.org/telegram/functions" 196 | ), 197 | InlineKeyboardButton( 198 | f"{emoji.SCROLL} TL Schema", 199 | url="https://github.com/pyrogram/pyrogram/blob/develop/compiler/api/source/main_api.tl" 200 | ), 201 | ]]), 202 | thumb_url=FIRE_THUMB, 203 | ) 204 | ) 205 | 206 | for i in docs.RAW_METHODS[offset: offset + NEXT_OFFSET]: 207 | results.append(i[1]) 208 | elif string == "!rt": 209 | switch_pm_text = f"{emoji.ORANGE_BOOK} Raw Types ({len(docs.RAW_TYPES)})" 210 | 211 | if offset == 0: 212 | results.append( 213 | InlineQueryResultArticle( 214 | title="Raw Types", 215 | description="Pyrogram Raw Types online documentation page", 216 | input_message_content=InputTextMessageContent( 217 | f"{emoji.FIRE} **Pyrogram Raw Types**\n\n" 218 | f"`This page contains all available Raw Types existing in the Telegram Schema, Layer " 219 | f"{docs.layer}.`" 220 | ), 221 | reply_markup=InlineKeyboardMarkup([[ 222 | InlineKeyboardButton( 223 | f"{emoji.OPEN_BOOK} Online docs", 224 | url="https://docs.pyrogram.org/telegram/types" 225 | ), 226 | InlineKeyboardButton( 227 | f"{emoji.SCROLL} TL Schema", 228 | url="https://github.com/pyrogram/pyrogram/blob/develop/compiler/api/source/main_api.tl", 229 | ), 230 | ]]), 231 | thumb_url=FIRE_THUMB, 232 | ) 233 | ) 234 | 235 | for i in docs.RAW_TYPES[offset: offset + NEXT_OFFSET]: 236 | results.append(i[1]) 237 | elif string == "rules": 238 | switch_pm_text = f"{emoji.SCROLL} Chat Rules" 239 | 240 | if offset == 0: 241 | results.append( 242 | InlineQueryResultArticle( 243 | title="Chat Rules", 244 | description="These are the rules for the Pyrogram Inn and the chats for other languages.", 245 | input_message_content=InputTextMessageContent(docs.rules), 246 | thumb_url=FIRE_THUMB, 247 | ) 248 | ) 249 | 250 | for i in docs.RULES[offset: offset + NEXT_OFFSET]: 251 | results.append(i) 252 | elif string == "colin": 253 | switch_pm_text = f"{emoji.SHARK} Hidden Shark" 254 | 255 | if offset == 0: 256 | results.append( 257 | InlineQueryResultPhoto( 258 | photo_url="https://i.imgur.com/f32hngs.jpg", 259 | # thumb_url="https://i.imgur.com/f32hngs.jpg", 260 | title="You found the secret Sharkception :O", 261 | description="You might not get anything from it, but you can feel proud to have found me!", 262 | caption=f"Hey, I found @ColinShark {emoji.SHARK}", 263 | # input_message_content=InputTextMessageContent(f"Hey, I found @ColinShark {emoji.SHARK}"), 264 | ) 265 | ) 266 | 267 | if results: 268 | await query.answer( 269 | results=results, 270 | cache_time=CACHE_TIME, 271 | switch_pm_text=switch_pm_text, 272 | switch_pm_parameter="start", 273 | next_offset=str(offset + NEXT_OFFSET), 274 | is_gallery=False 275 | ) 276 | else: 277 | if offset: 278 | await query.answer( 279 | results=[], 280 | cache_time=CACHE_TIME, 281 | switch_pm_text=switch_pm_text, 282 | switch_pm_parameter="start", 283 | next_offset="", 284 | ) 285 | 286 | if string.startswith("!r"): 287 | string = " ".join(string.split(" ")[1:]) 288 | 289 | if string == "": 290 | await query.answer( 291 | results=[], 292 | cache_time=CACHE_TIME, 293 | switch_pm_text=f"{emoji.MAGNIFYING_GLASS_TILTED_RIGHT} Type to search Raw Docs", 294 | switch_pm_parameter="start", 295 | ) 296 | 297 | for i in docs.RAW_METHODS: 298 | if string in i[0].lower(): 299 | results.append(i[1]) 300 | 301 | for i in docs.RAW_TYPES: 302 | if string in i[0].lower(): 303 | results.append(i[1]) 304 | 305 | else: 306 | for i in docs.METHODS: 307 | if string in i[0].lower(): 308 | results.append(i[1]) 309 | 310 | for i in docs.TYPES: 311 | if string in i[0].lower(): 312 | results.append(i[1]) 313 | 314 | for i in docs.BOUND_METHODS: 315 | if string in i[0].lower(): 316 | results.append(i[1]) 317 | 318 | for i in docs.DECORATORS: 319 | if string in i[0].lower(): 320 | results.append(i[1]) 321 | 322 | for i in docs.FILTERS: 323 | if string in i[0].lower(): 324 | results.append(i[1]) 325 | 326 | if results: 327 | count = len(results) 328 | switch_pm_text = f"{emoji.OPEN_BOOK} {count} Result{'s' if count > 1 else ''} for \"{string}\"" 329 | 330 | await query.answer( 331 | results=results[:50], 332 | cache_time=CACHE_TIME, 333 | switch_pm_text=switch_pm_text, 334 | switch_pm_parameter="start" 335 | ) 336 | else: 337 | await query.answer( 338 | results=[], 339 | cache_time=CACHE_TIME, 340 | switch_pm_text=f'{emoji.CROSS_MARK} No results for "{string}"', 341 | switch_pm_parameter="okay", 342 | ) 343 | -------------------------------------------------------------------------------- /assistant/plugins/private.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2019-present Dan 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from pyrogram import filters, emoji 24 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton 25 | 26 | from ..assistant import Assistant 27 | from ..utils import docs 28 | 29 | 30 | @Assistant.on_message(filters.private) 31 | async def go(_, message: Message): 32 | await message.reply( 33 | docs.HELP, 34 | disable_web_page_preview=True, 35 | parse_mode="markdown", 36 | reply_markup=InlineKeyboardMarkup([[ 37 | InlineKeyboardButton( 38 | f"{emoji.CARD_INDEX_DIVIDERS} Source Code", 39 | url="https://github.com/pyrogram/assistant" 40 | ), 41 | InlineKeyboardButton( 42 | f"{emoji.FIRE} Go!", 43 | switch_inline_query="" 44 | ) 45 | ]]) 46 | ) 47 | -------------------------------------------------------------------------------- /assistant/plugins/repaste.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2019-present Dan 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import aiohttp 24 | from pyrogram import Client, filters 25 | from pyrogram.types import Message 26 | 27 | NEKO = "> nekobin.com/{}.py" 28 | ENDPOINT = "https://nekobin.com/api/documents" 29 | ANSWER = "**Please use Nekobin for pastes.**\n" 30 | TIMEOUT = 3 31 | 32 | CHAT = filters.chat("PyrogramChat") 33 | REGEX = filters.regex( 34 | r"(https?://)?(www\.)?(?P(p|h)asteb(\.?in|in\.com)|del\.dog|haste.thevillage.chat)/(raw/)?(?P\w+)" 35 | # https://regex101.com/r/cl5iGU/3 36 | ) 37 | FILTER = REGEX & CHAT & ~filters.edited 38 | 39 | 40 | async def get_and_post(paste_url: str): 41 | """Get the pasted content from a paste service and repaste it to nekobin. 42 | 43 | Returns a `str` of the key saved on nekobin or an error code as `int`. 44 | """ 45 | async with aiohttp.ClientSession() as session: 46 | async with session.get(paste_url) as response: 47 | try: 48 | response.raise_for_status() 49 | except aiohttp.ClientResponseError as error: 50 | return error.status 51 | payload = {"content": await response.text()} 52 | post = await session.post(ENDPOINT, data=payload, timeout=TIMEOUT) 53 | return (await post.json())["result"]["key"] 54 | 55 | 56 | async def reply_pastes(new_pastes: str, message: Message): 57 | """Format a list of keys to a string and reply it.""" 58 | new_pastes = [ 59 | NEKO.format(paste) for paste in new_pastes if not isinstance(paste, int) 60 | ] 61 | if len(new_pastes) > 0: 62 | await message.reply_text( 63 | text=ANSWER + "\n".join(new_pastes), disable_web_page_preview=True, 64 | ) 65 | 66 | 67 | @Client.on_message(FILTER) 68 | async def catch_paste(app: Client, message: Message): 69 | """Catch incoming pastes not on nekobin""" 70 | new_pastes = [ 71 | await get_and_post(f"https://{match.group('service')}/raw/{match.group('tag')}") 72 | for match in message.matches 73 | ] 74 | await reply_pastes(new_pastes, message) 75 | -------------------------------------------------------------------------------- /assistant/plugins/welcome.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2019-present Dan 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | from pyrogram import filters, emoji 24 | from pyrogram.types import Message 25 | 26 | from ..assistant import Assistant 27 | 28 | 29 | @Assistant.on_message(Filters.chat(Assistant.chats) & Filters.new_chat_members) 30 | async def welcome(_, message: Message): 31 | new_members = [f"{u.mention}" for u in message.new_chat_members] 32 | text = f"{emoji.SPARKLES} Welcome to [Pyrogram](https://docs.pyrogram.org/)'s group chat {', '.join(new_members)}!" 33 | 34 | await message.reply_text(text, disable_web_page_preview=True) 35 | -------------------------------------------------------------------------------- /assistant/utils/docs.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2019-present Dan 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import re 24 | 25 | from pyrogram import filters, emoji, types 26 | from pyrogram.types import (InlineQueryResultArticle, InputTextMessageContent, InlineKeyboardMarkup, 27 | InlineKeyboardButton, Object) 28 | from pyrogram.raw import types as raw_types, functions as raw_methods 29 | from pyrogram.raw.all import layer 30 | from pyrogram import Client 31 | 32 | 33 | class Result: 34 | DESCRIPTION_MAX_LEN = 60 35 | 36 | @staticmethod 37 | def get_description(item): 38 | full = item.__doc__.split("\n")[0] 39 | short = full[: Result.DESCRIPTION_MAX_LEN].strip() 40 | 41 | if len(short) >= Result.DESCRIPTION_MAX_LEN - 1: 42 | short += "…" 43 | 44 | return short, full 45 | 46 | @staticmethod 47 | def snek(s: str): 48 | s = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", s) 49 | return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s).lower() 50 | 51 | class Method: 52 | DOCS = "https://docs.pyrogram.org/api/methods/{}" 53 | THUMB = "https://i.imgur.com/S5lY8fy.png" 54 | 55 | def __new__(cls, item): 56 | short, full = Result.get_description(item) 57 | 58 | return InlineQueryResultArticle( 59 | title=f"{item.__name__}", 60 | description="Method - " + short, 61 | input_message_content=InputTextMessageContent( 62 | f"{emoji.CLOSED_BOOK} **Pyrogram Docs**\n\n" 63 | f"[{item.__name__}]({cls.DOCS.format(item.__name__)}) - Method\n\n" 64 | f"`{full}`\n", 65 | disable_web_page_preview=True, 66 | ), 67 | thumb_url=cls.THUMB, 68 | ) 69 | 70 | class Decorator: 71 | DOCS = "https://docs.pyrogram.org/api/decorators#pyrogram.Client.{}" 72 | THUMB = "https://i.imgur.com/xp3jld1.png" 73 | 74 | def __new__(cls, item): 75 | short, full = Result.get_description(item) 76 | 77 | return InlineQueryResultArticle( 78 | title=f"{item.__name__}", 79 | description="Decorator - " + short, 80 | input_message_content=InputTextMessageContent( 81 | f"{emoji.ARTIST_PALETTE} **Pyrogram Docs**\n\n" 82 | f"[{item.__name__}]({cls.DOCS.format(item.__name__)}) - Decorator\n\n" 83 | f"`{full}`\n", 84 | disable_web_page_preview=True, 85 | ), 86 | thumb_url=cls.THUMB, 87 | ) 88 | 89 | class Type: 90 | DOCS = "https://docs.pyrogram.org/api/types/{}" 91 | THUMB = "https://i.imgur.com/dw1lLBX.png" 92 | 93 | def __new__(cls, item): 94 | short, full = Result.get_description(item) 95 | 96 | return InlineQueryResultArticle( 97 | title=f"{item.__name__}", 98 | description="Type - " + short, 99 | input_message_content=InputTextMessageContent( 100 | f"{emoji.GREEN_BOOK} **Pyrogram Docs**\n\n" 101 | f"[{item.__name__}]({cls.DOCS.format(item.__name__)}) - Type\n\n" 102 | f"`{full}`", 103 | disable_web_page_preview=True, 104 | ), 105 | thumb_url=cls.THUMB, 106 | ) 107 | 108 | class Filter: 109 | DOCS = "https://docs.pyrogram.org/api/filters#pyrogram.filters.{}" 110 | THUMB = "https://i.imgur.com/YRe6cKU.png" 111 | 112 | def __new__(cls, item): 113 | return InlineQueryResultArticle( 114 | title=f"{item.__class__.__name__}", 115 | description=f"Filter - {item.__class__.__name__}", 116 | input_message_content=InputTextMessageContent( 117 | f"{emoji.CONTROL_KNOBS} **Pyrogram Docs**\n\n" 118 | f"[{item.__class__.__name__}]({cls.DOCS.format(item.__class__.__name__.lower())}) - Filter", 119 | disable_web_page_preview=True, 120 | ), 121 | thumb_url=cls.THUMB, 122 | ) 123 | 124 | class BoundMethod: 125 | DOCS = "https://docs.pyrogram.org/api/bound-methods/{}.{}" 126 | THUMB = "https://i.imgur.com/GxFuuks.png" 127 | 128 | def __new__(cls, item): 129 | a, b = item.__qualname__.split(".") 130 | 131 | return InlineQueryResultArticle( 132 | title=f"{item.__qualname__}", 133 | description=f'Bound Method "{b}" of {a}', 134 | input_message_content=InputTextMessageContent( 135 | f"{emoji.LEDGER} **Pyrogram Docs**\n\n" 136 | f"[{item.__qualname__}]({cls.DOCS.format(a, b)}) - Bound Method", 137 | disable_web_page_preview=True, 138 | ), 139 | thumb_url=cls.THUMB, 140 | ) 141 | 142 | class RawMethod: 143 | DOCS = "https://docs.pyrogram.org/telegram/functions/{}" 144 | THUMB = "https://i.imgur.com/NY4uasQ.png" 145 | 146 | def __new__(cls, item): 147 | constructor_id = hex(item[1].ID) 148 | path = cls.DOCS.format(Result.snek(item[0]).replace("_", "-").replace(".-", "/")) 149 | 150 | return InlineQueryResultArticle( 151 | title=f"{item[0]}", 152 | description=f"Raw Method - {constructor_id}\nSchema: Layer {layer}", 153 | input_message_content=InputTextMessageContent( 154 | f"{emoji.BLUE_BOOK} **Pyrogram Docs**\n\n" 155 | f"[{item[0]}]({path}) - Raw Method\n\n" 156 | f"`ID`: **{constructor_id}**\n" 157 | f"`Schema`: **Layer {layer}**", 158 | disable_web_page_preview=True, 159 | ), 160 | thumb_url=cls.THUMB, 161 | ) 162 | 163 | class RawType: 164 | DOCS = "https://docs.pyrogram.org/telegram/types/{}" 165 | THUMB = "https://i.imgur.com/b33rM21.png" 166 | 167 | def __new__(cls, item): 168 | constructor_id = hex(item[1].ID) 169 | path = cls.DOCS.format(Result.snek(item[0]).replace("_", "-").replace(".-", "/")) 170 | 171 | return InlineQueryResultArticle( 172 | title=f"{item[0]}", 173 | description=f"Raw Type - {constructor_id}\nSchema: Layer {layer}", 174 | input_message_content=InputTextMessageContent( 175 | f"{emoji.ORANGE_BOOK} **Pyrogram Docs**\n\n" 176 | f"[{item[0]}]({path}) - Raw Type\n\n" 177 | f"`ID`: **{constructor_id}**\n" 178 | f"`Schema`: **Layer {layer}**", 179 | disable_web_page_preview=True, 180 | ), 181 | thumb_url=cls.THUMB, 182 | ) 183 | 184 | 185 | METHODS = [] 186 | 187 | for a in dir(Client): 188 | m = getattr(Client, a) 189 | 190 | try: 191 | if not a.startswith("_") and a[0].islower() and m.__doc__ and not a.startswith("on_"): 192 | METHODS.append((a.lower(), Result.Method(m))) 193 | except AttributeError: 194 | pass 195 | 196 | DECORATORS = [] 197 | 198 | for a in dir(Client): 199 | m = getattr(Client, a) 200 | 201 | try: 202 | if not a.startswith("_") and a[0].islower() and m.__doc__ and a.startswith("on_"): 203 | DECORATORS.append((a.lower(), Result.Decorator(m))) 204 | except AttributeError: 205 | pass 206 | 207 | TYPES = [] 208 | 209 | for a in dir(types): 210 | t = getattr(types, a) 211 | 212 | if not a.startswith("_") and a[0].isupper() and t.__doc__: 213 | TYPES.append((a, Result.Type(t))) 214 | 215 | FILTERS = [ 216 | (i.lower(), Result.Filter(getattr(filters, i))) 217 | for i in filter( 218 | lambda x: not x.startswith("_") 219 | and x[0].islower(), 220 | dir(filters) 221 | ) 222 | ] 223 | 224 | BOUND_METHODS = [] 225 | 226 | for a in dir(types): 227 | try: 228 | c = getattr(types, a) 229 | if issubclass(c, Object): 230 | for m in dir(c): 231 | if ( 232 | not m.startswith("_") 233 | and callable(getattr(c, m)) 234 | and m not in ["default", "read", "write", "with_traceback", "continue_propagation", 235 | "stop_propagation"] 236 | ): 237 | BOUND_METHODS.append((f"{a}.{m}", Result.BoundMethod(getattr(c, m)))) 238 | except TypeError: 239 | pass 240 | 241 | RAW_METHODS = [] 242 | 243 | for i in filter(lambda x: not x.startswith("_"), dir(raw_methods)): 244 | if i[0].isupper(): 245 | RAW_METHODS.append((i, Result.RawMethod((i, getattr(raw_methods, i))))) 246 | else: 247 | if "Int" not in dir(getattr(raw_methods, i)): 248 | for j in filter(lambda x: not x.startswith("_") and x[0].isupper(), dir(getattr(raw_methods, i))): 249 | RAW_METHODS.append((f"{i}.{j}", Result.RawMethod((f"{i}.{j}", getattr(getattr(raw_methods, i), j))))) 250 | 251 | for i in RAW_METHODS[:]: 252 | if "." not in i[0]: 253 | RAW_METHODS.remove(i) 254 | RAW_METHODS.append(i) 255 | 256 | RAW_TYPES = [] 257 | 258 | for i in filter(lambda x: not x.startswith("_"), dir(raw_types)): 259 | if i[0].isupper(): 260 | RAW_TYPES.append((i, Result.RawType((i, getattr(raw_types, i))))) 261 | else: 262 | if "Int" not in dir(getattr(raw_types, i)): 263 | for j in filter(lambda x: not x.startswith("_") and x[0].isupper(), dir(getattr(raw_types, i))): 264 | RAW_TYPES.append((f"{i}.{j}", Result.RawType((f"{i}.{j}", getattr(getattr(raw_types, i), j))))) 265 | 266 | for i in RAW_TYPES[:]: 267 | if "." not in i[0]: 268 | RAW_TYPES.remove(i) 269 | RAW_TYPES.append(i) 270 | 271 | FIRE_THUMB = "https://i.imgur.com/qhYYqZa.png" 272 | ROCKET_THUMB = "https://i.imgur.com/PDaYHd8.png" 273 | ABOUT_BOT_THUMB = "https://i.imgur.com/zRglRz3.png" 274 | OPEN_BOOK_THUMB = "https://i.imgur.com/v1XSJ1D.png" 275 | RED_HEART_THUMB = "https://i.imgur.com/66FVFXz.png" 276 | SCROLL_THUMB = "https://i.imgur.com/L1u0VlX.png" 277 | 278 | HELP = ( 279 | f"{emoji.ROBOT} **Pyrogram Assistant**\n\n" 280 | f"You can use this bot in inline mode to search for Pyrogram methods, types and other resources from " 281 | f"https://docs.pyrogram.org.\n\n" 282 | 283 | f"**__Search__**\n" 284 | f"`@pyrogrambot ` – Pyrogram API\n" 285 | f"`@pyrogrambot !r ` – Telegram Raw API\n\n" 286 | 287 | f"**__List__**\n" 288 | f"`@pyrogrambot !m` – Methods\n" 289 | f"`@pyrogrambot !t` – Types\n" 290 | f"`@pyrogrambot !f` – Filters\n" 291 | f"`@pyrogrambot !b` – Bound Methods\n" 292 | f"`@pyrogrambot !d` – Decorators\n" 293 | f"`@pyrogrambot !rm` – Raw Methods\n" 294 | f"`@pyrogrambot !rt` – Raw Types\n\n" 295 | ) 296 | 297 | DEFAULT_RESULTS = [ 298 | InlineQueryResultArticle( 299 | title="About Pyrogram", 300 | input_message_content=InputTextMessageContent( 301 | f"{emoji.FIRE} **Pyrogram**\n\n" 302 | f"Pyrogram is an elegant, easy-to-use Telegram client library and framework written from the ground up in " 303 | f"Python and C. It enables you to easily create custom apps using both user and bot identities (bot API " 304 | f"alternative) via the MTProto API.", 305 | disable_web_page_preview=True, 306 | ), 307 | reply_markup=InlineKeyboardMarkup( 308 | [ 309 | [ 310 | InlineKeyboardButton(f"{emoji.BUSTS_IN_SILHOUETTE} Community", url="https://t.me/pyrogram") 311 | ], 312 | [ 313 | InlineKeyboardButton(f"{emoji.CARD_INDEX_DIVIDERS} GitHub", url="https://github.com/pyrogram"), 314 | InlineKeyboardButton(f"{emoji.OPEN_BOOK} Docs", url="https://docs.pyrogram.org") 315 | ] 316 | ] 317 | ), 318 | description="Pyrogram is an elegant, easy-to-use Telegram client library and framework.", 319 | thumb_url=FIRE_THUMB, 320 | ), 321 | InlineQueryResultArticle( 322 | title="About this Bot", 323 | input_message_content=InputTextMessageContent(HELP, disable_web_page_preview=True, parse_mode="markdown"), 324 | reply_markup=InlineKeyboardMarkup([[ 325 | InlineKeyboardButton( 326 | f"{emoji.CARD_INDEX_DIVIDERS} Source Code", 327 | url="https://github.com/pyrogram/assistant" 328 | ), 329 | InlineKeyboardButton( 330 | f"{emoji.FIRE} Go!", 331 | switch_inline_query="" 332 | ) 333 | ]]), 334 | description="How to use Pyrogram Assistant Bot.", 335 | thumb_url=ABOUT_BOT_THUMB, 336 | ), 337 | InlineQueryResultArticle( 338 | title="Quick Start", 339 | input_message_content=InputTextMessageContent( 340 | f"{emoji.ROCKET} **Pyrogram Docs**\n\n" 341 | f"[Quick Start](https://docs.pyrogram.org/intro/quickstart) - Introduction\n\n" 342 | f"`Quick overview to get you started as fast as possible`", 343 | disable_web_page_preview=True, 344 | ), 345 | description="Quick overview to get you started as fast as possible.", 346 | thumb_url=ROCKET_THUMB, 347 | ), 348 | InlineQueryResultArticle( 349 | title="Support", 350 | input_message_content=InputTextMessageContent( 351 | f"{emoji.RED_HEART} **Support Pyrogram**\n\n" 352 | f"[Support](https://docs.pyrogram.org/support-pyrogram) - Meta\n\n" 353 | f"`Ways to show your appreciation.`", 354 | disable_web_page_preview=True, 355 | ), 356 | description="Ways to show your appreciation.", 357 | thumb_url=RED_HEART_THUMB, 358 | ), 359 | ] 360 | 361 | rules = """ 362 | **Pyrogram Rules** 363 | 364 | ` 0.` Follow rules; improve chances of getting answers. 365 | ` 1.` English only. Other groups by language: #groups. 366 | ` 2.` Spam, flood and hate speech is strictly forbidden. 367 | ` 3.` Talks unrelated to Pyrogram (ot) are not allowed. 368 | ` 4.` Keep unrelated media and emojis to a minimum. 369 | ` 5.` Be nice, respect people and use common sense. 370 | ` 6.` Ask before sending PMs and respect the answers. 371 | ` 7.` "Doesn't work" means nothing. Explain in details. 372 | ` 8.` Ask if you get any error, not if the code is correct. 373 | ` 9.` Make use of nekobin.com for sharing long code. 374 | `10.` No photos unless they are meaningful and small. 375 | 376 | __Rules are subject to change without notice.__ 377 | """ 378 | 379 | RULES = [ 380 | InlineQueryResultArticle( 381 | title=f"Rule {i}", 382 | description=re.sub(r"` ?\d+.` ", "", rule), 383 | input_message_content=InputTextMessageContent("**Pyrogram Rules**\n\n" + rule), 384 | thumb_url=SCROLL_THUMB, 385 | ) 386 | for i, rule in enumerate(rules.split("\n")[3:-3]) 387 | ] 388 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyrogram 2 | tgcrypto 3 | aiohttp 4 | num2words --------------------------------------------------------------------------------