async def who_cmd(client, message): try: peer = None if len(message.command) > 1 and message.command[1] != "-no": arg = message.command[1] if arg.isnumeric(): peer = await client.get_users(int(arg)) else: peer = await client.get_users(arg) elif message.reply_to_message is not None \ and message.reply_to_message.from_user is not None: peer = message.reply_to_message.from_user else: peer = await client.get_me() logger.info("Getting info of user") if is_me(message): await message.edit(message.text.markdown + f"\n` → ` Getting data of user `{peer.id}`") if len(message.command) < 3 or message.command[2] != "-no": out = io.BytesIO((str(peer)).encode('utf-8')) out.name = f"user-{peer.id}.json" await client.send_document(message.chat.id, out) except Exception as e: traceback.print_exc() await edit_or_reply(message, "`[!] → ` " + str(e)) await client.set_offline()
async def randomcase(client, message): logger.info(f"Making message randomly capitalized") text = message.command["arg"] if text == "": return msg = "" # omg this part is done so badly val = 0 # but I want a kinda imbalanced random upper = False for c in text: last = val val = secrets.randbelow(4) if val > 2: msg += c.upper() upper = True elif val < 1: msg += c upper = False else: if upper: msg += c upper = False else: msg += c.upper() upper = True if is_me(message): await message.edit(msg) else: await message.reply(msg) await client.set_offline()
async def meme_cmd(client, message): """get a meme from collection If a name was specified, get meme matching requested name (regex). Otherwise, get random meme. Use flag `-list` to get all meme names and flag `-stats` to get count and disk usage. You can send a bunch of random memes together by specifying how many in the `-b` (batch) option \ (only photos will be sent if a batch is requested). Memes can be any filetype. """ batchsize = max(min(int(message.command["batch"] or 10), 10), 2) reply_to = message.message_id if is_me(message) and message.reply_to_message is not None: reply_to = message.reply_to_message.message_id if message.command["-stats"]: memenumber = len(os.listdir("plugins/alemibot-tricks/data/meme")) proc_meme = await asyncio.create_subprocess_exec( # ewww this is not cross platform but will do for now "du", "-b", "plugins/alemibot-tricks/data/meme/", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) stdout, _stderr = await proc_meme.communicate() memesize = float(stdout.decode('utf-8').split("\t")[0]) await edit_or_reply(message, f"` → ` **{memenumber}** memes collected\n` → ` folder size **{order_suffix(memesize)}**") elif message.command["-list"]: memes = os.listdir("plugins/alemibot-tricks/data/meme") memes.sort() out = f"` → ` **Meme list** ({len(memes)} total) :\n[ " out += ", ".join(memes) out += "]" await edit_or_reply(message, out) elif len(message.command) > 0 and (len(message.command) > 1 or message.command[0] != "-delme"): search = re.compile(message.command[0]) found = [] for meme in os.listdir("plugins/alemibot-tricks/data/meme"): if search.match(meme): found.append(meme) if len(found) > 1: await edit_or_reply(message, "`[!] → ` multiple memes match query\n" + "\n".join(f"` → ` {meme}" for meme in found)) elif len(found) == 1: meme = found[0] await send_media(client, message.chat.id, 'plugins/alemibot-tricks/data/meme/' + meme, reply_to_message_id=reply_to, caption=f"` → ` **{meme}**") elif len(found) < 1: await edit_or_reply(message, f"`[!] → ` no meme matching `{message.command[0]}`") else: if "batch" in message.command: with ProgressChatAction(client, message.chat.id, action="upload_photo") as prog: pool = [ x for x in filter(lambda x: x.endswith((".jpg", ".jpeg", ".png")), os.listdir("plugins/alemibot-tricks/data/meme")) ] def pick(pool): pick = secrets.choice(pool) pool.remove(pick) return pick memes = [InputMediaPhoto("plugins/alemibot-tricks/data/meme/" + pick(pool)) for _ in range(batchsize)] await client.send_media_group(message.chat.id, memes) else: fname = secrets.choice(os.listdir("plugins/alemibot-tricks/data/meme")) await send_media(client, message.chat.id, 'plugins/alemibot-tricks/data/meme/' + fname, reply_to_message_id=reply_to, caption=f"` → ` [--random--] **{fname}**")
async def voice_cmd(client, message): """convert text to voice Create a voice message using Google Text to Speech. By default, english will be used as lang, but another one can be specified with `-l`. You can add `-slow` flag to make the generated speech slower. TTS result will be converted to `.ogg`. You can skip this step and send as mp3 by adding the `-mp3` flag. You can add the `-file` flag to make tts of a replied to or attached text file. """ text = "" opts = {} from_file = bool(message.command["-file"]) if message.reply_to_message is not None: if from_file and message.reply_to_message.media: fpath = await client.download_media(message.reply_to_message) with open(fpath) as f: text = f.read() os.remove(fpath) else: text = get_text(message.reply_to_message) elif from_file and message.media: fpath = await client.download_media(message) with open(fpath) as f: text = f.read() os.remove(fpath) elif len(message.command) > 0: text = re.sub(r"-delme(?: |)(?:[0-9]+|)", "", message.command.text) else: return await edit_or_reply(message, "`[!] → ` No text given") prog = ProgressChatAction(client, message.chat.id, action="record_audio") lang = message.command["lang"] or "en" slow = bool(message.command["-slow"]) if message.reply_to_message is not None: opts["reply_to_message_id"] = message.reply_to_message.message_id elif not is_me(message): opts["reply_to_message_id"] = message.message_id await prog.tick() gTTS(text=text, lang=lang, slow=slow).save("data/tts.mp3") if message.command["-mp3"]: await client.send_audio(message.chat.id, "data/tts.mp3", progress=prog.tick, **opts) else: AudioSegment.from_mp3("data/tts.mp3").export("data/tts.ogg", format="ogg", codec="libopus") await client.send_voice(message.chat.id, "data/tts.ogg", progress=prog.tick, **opts)
async def search_triggers(client, message): if is_me( message ) or message.edit_date is not None: # TODO allow triggers for self? return # pyrogram gets edit events as message events! if message.chat is None: return # wtf messages with no chat??? if message.chat.type != "private" and not message.mentioned: return # in groups only get triggered in mentions msg_txt = get_text(message).lower() if msg_txt == "": return for trg in triggers: if trg.lower() in msg_txt: await message.reply(triggers[trg]) await client.set_offline() logger.info("T R I G G E R E D")
async def bully(client, message): if message.edit_date is not None: return # pyrogram gets edit events as message events! if message.chat is None or is_me(message): return # can't censor messages outside of chats or from self if message.from_user is None: return # Don't censory anonymous msgs if message.chat.id in censoring["MASS"] \ and message.from_user.id not in censoring["FREE"]: await message.delete() logger.info("Get bullied") else: if message.chat.id not in censoring["SPEC"] \ or message.from_user.id not in censoring["SPEC"][message.chat.id]: return # Don't censor innocents! await message.delete() logger.info("Get bullied noob") await client.set_offline()
async def deleted_cmd(client, message): # This is a mess omg args = message.command show_time = "-t" in args["flags"] target_group = message.chat include_system = "-sys" in args["flags"] offset = int(args["offset"]) if "offset" in args else 0 if is_me(message): if "-all" in args["flags"]: target_group = None elif "group" in args: target_group = await client.get_chat(int(args["group"])) limit = 1 if "arg" in args: limit = int(args["arg"]) lgr.info(f"Peeking {limit} messages") asyncio.get_event_loop( ).create_task( # launch the task async because it may take some time lookup_deleted_messages(client, message, target_group, limit, show_time, include_system, offset))
async def getmeme(client, message): args = message.command try: await client.send_chat_action(message.chat.id, "upload_photo") reply_to = message.message_id if is_me(message) and message.reply_to_message is not None: reply_to = message.reply_to_message.message_id if "-list" in args["flags"]: logger.info("Getting meme list") memes = os.listdir("data/memes") memes.sort() out = f"` → ` **Meme list** ({len(memes)} total) :\n[ " out += ", ".join(memes) out += "]" await edit_or_reply(message, out) elif "cmd" in args: memes = [ s for s in os.listdir("data/memes") # I can't decide if this if s.lower().startswith(args["cmd"][0]) ] # is nice or horrible if len(memes) > 0: fname = memes[0] logger.info(f"Getting specific meme : \"{fname}\"") await send_media_appropriately(client, message, fname, reply_to) else: await edit_or_reply( message, f"`[!] → ` no meme named {args['cmd'][0]}") else: fname = secrets.choice(os.listdir("data/memes")) logger.info(f"Getting random meme : \"{fname}\"") await send_media_appropriately(client, message, fname, reply_to, extra_text="Random meme : ") except Exception as e: traceback.print_exc() await edit_or_reply(message, "`[!] → ` " + str(e)) await client.send_chat_action(message.chat.id, "cancel") await client.set_offline()
async def where_cmd(client, message): try: tgt = message.chat if len(message.command) > 1 and message.command[1] != "-no": arg = message.command[1] if arg.isnumeric(): tgt = await client.get_chat(int(arg)) else: tgt = await client.get_chat(arg) logger.info(f"Getting info of chat") if is_me(message): await message.edit(message.text.markdown + f"\n` → ` Getting data of chat `{tgt.id}`") if len(message.command) < 3 or message.command[2] != "-no": out = io.BytesIO((str(tgt)).encode('utf-8')) out.name = f"chat-{message.chat.id}.json" await client.send_document(message.chat.id, out) except Exception as e: traceback.print_exc() await edit_or_reply(message, "`[!] → ` " + str(e)) await client.set_offline()
async def countdown(client, message): if is_me(message): tgt_msg = message else: tgt_msg = await message.reply("` → `") end = time.time() + 5 if len(message.command) > 1: try: end = time.time() + float(message.command[1]) except ValueError: return await tgt_msg.edit("`[!] → ` argument must be a float") msg = tgt_msg.text + "\n` → Countdown ` **{:.1f}**" last = "" logger.info(f"Countdown") while time.time() < end: curr = msg.format(time.time() - end) if curr != last: # with fast counting down at the end it may try to edit with same value await tgt_msg.edit(msg.format(time.time() - end)) last = curr await asyncio.sleep(interval(end - time.time())) await tgt_msg.edit(msg.format(0)) await client.set_offline()
async def what_cmd(client, message): msg = message try: if message.reply_to_message is not None: msg = await client.get_messages( message.chat.id, message.reply_to_message.message_id) elif len(message.command) > 1 and message.command[1].isnumeric(): chat_id = message.chat.id if len(message.command) > 2 and message.command[2].isnumeric(): chat_id = int(message.command[2]) msg = await client.get_messages(chat_id, int(message.command[1])) logger.info("Getting info of msg") if is_me(message): await message.edit( message.text.markdown + f"\n` → ` Getting data of msg `{msg.message_id}`") if message.command[len(message.command) - 1] != "-no": out = io.BytesIO((str(msg)).encode('utf-8')) out.name = f"msg-{msg.message_id}.json" await client.send_document(message.chat.id, out) except Exception as e: traceback.print_exc() await edit_or_reply(message, "`[!] → ` " + str(e)) await client.set_offline()
async def plugin_remove_cmd(client, message): """remove an installed plugin. alemiBot plugins are git repos, cloned into the `plugins` folder as git submodules. This will call `git submodule deinit -f`, then remove the related folder in `.git/modules` and last remove \ plugin folder and all its content. If flag `-lib` is added, libraries installed with pip will be removed too (may break dependancies of other plugins!) """ if not alemiBot.allow_plugin_install: return await edit_or_reply(message, "`[!] → ` Plugin management is disabled") out = message.text.markdown if is_me( message ) else f"`→ ` {get_username(message.from_user)} requested plugin removal" msg = message if is_me(message) else await message.reply(out) try: if len(message.command) < 1: out += "\n`[!] → ` No input" return await msg.edit(out) plugin = message.command[0] out += f"\n`→ ` Uninstalling `{plugin}`" if "/" in plugin: # If user passes <user>/<repo> here too, get just repo name plugin = plugin.split("/")[1] logger.info(f"Removing plugin \"{plugin}\"") if message.command["-lib"]: out += "\n` → ` Removing libraries" await msg.edit(out) if os.path.isfile(f"plugins/{plugin}/requirements.txt"): proc = await asyncio.create_subprocess_exec( "pip", "uninstall", "-y", "-r", f"plugins/{plugin}/requirements.txt", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) stdout, _stderr = await proc.communicate() logger.info(stdout.decode()) if b"ERROR" in stdout: out += " [`WARN`]" else: out += f" [`{stdout.count(b'Uninstalling')} del`]" out += "\n` → ` Removing source code" await msg.edit(out) proc = await asyncio.create_subprocess_shell( f"git submodule deinit -f plugins/{plugin} && rm -rf .git/modules/plugins/{plugin} && git rm -f plugins/{plugin}", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) stdout, _stderr = await proc.communicate() res = cleartermcolor(stdout.decode()) logger.info(res) if not res.startswith("Cleared"): logger.error(res) out += f" [`FAIL`]\n`[!] → ` Could not deinit `{plugin}`" return await msg.edit(out) if f"rm 'plugins/{plugin}'" not in res: logger.error(res) out += f" [`FAIL`]\n`[!] → ` Could not delete `{plugin}`" return await msg.edit(out) out += f" [`OK`]\n` → ` Restarting process" await msg.edit(out) with open("data/lastmsg.json", "w") as f: json.dump({ "message_id": msg.message_id, "chat_id": msg.chat.id }, f) asyncio.get_event_loop().create_task(client.restart()) except Exception as e: logger.exception("Error while installing plugin") out += " [`FAIL`]\n`[!] → ` " + str(e) await msg.edit(out)
async def plugin_add_cmd(client, message): """install a plugin alemiBot plugins are git repos, cloned into the `plugins` folder as git submodules. You can specify which extension to install by giving `user/repo` (will default to github.com), or specify the entire url. For example, `alemidev/alemibot-tricks` is the same as `https://github.com/alemidev/alemibot-tricks.git` By default, https will be used (meaning that if you try to clone a private repo, it will just fail). You can make it clone using ssh with `-ssh` flag, or by adding `useSsh = True` to your config (under `[customization]`). You can also include your GitHub credentials in the clone url itself: `https://username:[email protected]/author/repo.git` Your github credentials will be stored in plain text inside project folder. \ Because of this, it is --not recommended-- to include credentials in the clone url. Set up an ssh key for private plugins. You can specify which branch to clone with `-b` option. You can also specify a custom folder to clone into with `-d` option (this may break plugins relying on data stored in their directory!) """ if not alemiBot.allow_plugin_install: return await edit_or_reply(message, "`[!] → ` Plugin management is disabled") out = message.text.markdown if is_me( message ) else f"`→ ` {get_username(message.from_user)} requested plugin install" msg = message if is_me(message) else await message.reply(out) try: if len(message.command) < 1: out += "\n`[!] → ` No input" return await msg.edit(out) user_input = message.command[0] branch = message.command["branch"] folder = message.command["dir"] plugin, author = split_url(user_input) # clear url or stuff around if folder is None: folder = plugin if user_input.startswith("http") or user_input.startswith("git@"): link = user_input else: if alemiBot.use_ssh or message.command["-ssh"]: link = f"[email protected]:{author}/{plugin}.git" else: link = f"https://github.com/{author}/{plugin}.git" out += f"\n`→ ` Installing `{author}/{plugin}`" logger.info(f"Installing \"{author}/{plugin}\"") if os.path.isfile(".gitmodules"): with open(".gitmodules") as f: modules = f.read() matches = re.findall(r"url = [email protected]:(?P<p>.*).git", modules) for match in matches: if match == plugin: out += "`[!] → ` Plugin already installed" return await msg.edit(out) if branch is None: out += "\n` → ` Checking branches" await msg.edit(out) proc = await asyncio.create_subprocess_shell( f"GIT_TERMINAL_PROMPT=0 git ls-remote --symref {link} HEAD", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) stdout, _sterr = await proc.communicate() res = cleartermcolor(stdout.decode()) logger.info(res) if res.startswith(("ERROR", "fatal", "remote: Not Found")): out += f" [`FAIL`]\n`[!] → ` Could not find `{link}`" return await msg.edit(out) out += " [`OK`]" branch = re.search(r"refs\/heads\/(?P<branch>[^\s]+)(?:\s+)HEAD", res)["branch"] out += "\n` → ` Fetching source code" await msg.edit(out) proc = await asyncio.create_subprocess_shell( f"GIT_TERMINAL_PROMPT=0 git submodule add -b {branch} {link} plugins/{folder}", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) stdout, _sterr = await proc.communicate() res = cleartermcolor(stdout.decode()) logger.info(res) if not res.startswith("Cloning"): out += f" [`FAIL`]\n`[!] → ` Plugin `{author}/{plugin}` was wrongly uninstalled" return await msg.edit(out) if "ERROR: Repository not found" in res: out += f" [`FAIL`]\n`[!] → ` No plugin `{author}/{plugin}` could be found" return await msg.edit(out) if re.search(r"fatal: '(.*)' is not a commit", res): out += f" [`FAIL`]\n`[!] → ` Non existing branch `{branch}` for `{author}/{plugin}`" return await msg.edit(out) out += f" [`OK`]\n` → ` Checking dependancies" await msg.edit(out) if os.path.isfile(f"plugins/{plugin}/requirements.txt"): proc = await asyncio.create_subprocess_exec( "pip", "install", "-r", f"plugins/{plugin}/requirements.txt", "--upgrade", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) stdout, _stderr = await proc.communicate() logger.info(stdout.decode()) if b"ERROR" in stdout: logger.warn(stdout.decode()) out += " [`WARN`]" else: out += f" [`{stdout.count(b'Uninstalling')} new`]" else: out += " [`N/A`]" out += f"\n` → ` Restarting process" await msg.edit(out) with open("data/lastmsg.json", "w") as f: json.dump({ "message_id": msg.message_id, "chat_id": msg.chat.id }, f) asyncio.get_event_loop().create_task(client.restart()) except Exception as e: logger.exception("Error while installing plugin") out += " [`FAIL`]\n`[!] → ` " + str(e) await msg.edit(out)
async def update_cmd(client, message): """fetch updates and restart client Will pull changes from git (`git pull`), install requirements (`pip install -r requirements.txt --upgrade`) \ and then restart process with an `execv` call. If nothing gets pulled from `git`, update will stop unless the `-force` flag was given. """ out = message.text.markdown if is_me( message ) else f"`→ ` {get_username(message.from_user)} requested update" msg = message if is_me(message) else await message.reply(out) uptime = str(datetime.now() - client.start_time) out += f"\n`→ ` --runtime-- `{uptime}`" try: out += "\n` → ` Fetching updates" pulled = False await msg.edit(out) proc = await asyncio.create_subprocess_exec( "git", "pull", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) stdout, _stderr = await proc.communicate() logger.info(stdout.decode()) if b"Aborting" in stdout: out += " [`FAIL`]\n" if not message.command["-force"]: return await msg.edit(out) elif b"Already up to date" in stdout: out += " [`N/A`]" else: pulled = True out += " [`OK`]" if os.path.isfile(".gitmodules"): # Also update plugins out += "\n` → ` Submodules" await msg.edit(out) sub_proc = await asyncio.create_subprocess_exec( "git", "submodule", "update", "--remote", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) sub_stdout, _sub_stderr = await sub_proc.communicate() logger.info(sub_stdout.decode()) sub_count = sub_stdout.count(b"checked out") if sub_count > 0: out += f" [`{sub_count}`]" pulled = True else: out += " [`N/A`]" if not pulled and not message.command["-force"]: return await msg.edit(out) out += "\n` → ` Checking libraries" await msg.edit(out) proc = await asyncio.create_subprocess_exec( "pip", "install", "-r", "requirements.txt", "--upgrade", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) stdout, _stderr = await proc.communicate() logger.info(stdout.decode()) if b"ERROR" in stdout: out += " [`WARN`]" else: out += f" [`{stdout.count(b'Collecting')} new`]" if os.path.isfile( ".gitmodules"): # Also install dependancies from plugins out += "\n` → ` Submodules" await msg.edit(out) with open(".gitmodules") as f: modules = f.read() matches = re.findall(r"path = (?P<path>plugins/[^ ]+)\n", modules) count = 0 for match in matches: if os.path.isfile(f"{match}/requirements.txt"): proc = await asyncio.create_subprocess_exec( "pip", "install", "-r", f"{match}/requirements.txt", "--upgrade", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) stdout, _stderr = await proc.communicate() logger.info(stdout.decode()) if b"ERROR" in stdout: out += " [`WARN`]" else: count += stdout.count(b'Collecting') out += f" [`{count} new`]" out += "\n` → ` Restarting process" await msg.edit(out) with open("data/lastmsg.json", "w") as f: json.dump({ "message_id": msg.message_id, "chat_id": msg.chat.id }, f) asyncio.get_event_loop().create_task(client.restart()) except Exception as e: logger.exception("Error while updating") out += " [`FAIL`]\n`[!] → ` " + str(e) await msg.edit(out)