diff --git a/src/main.py b/src/main.py index 65da3a5..80f262e 100644 --- a/src/main.py +++ b/src/main.py @@ -9,6 +9,8 @@ import asyncio import json import argparse import traceback +import random +import rolldice from datetime import timezone, datetime import tio @@ -45,10 +47,35 @@ cleaner = discord.ext.commands.clean_content() def clean(ctx, text): return cleaner.convert(ctx, text) +@bot.event +async def on_message(message): + words = message.content.split(" ") + if len(words) == 10 and message.author.id == 435756251205468160: + await message.channel.send(util.unlyric(message.content)) + else: + if message.author.id == bot.user.id: return + ctx = await bot.get_context(message) + if not ctx.valid: return + await bot.invoke(ctx) + +@bot.event +async def on_command_error(ctx, err): + print(ctx, err) + if isinstance(err, commands.CommandNotFound, commands.CheckFailure): return + try: + trace = re.sub("\n\n+", "\n", "\n".join(traceback.format_exception(err, err, err.__traceback__))) + print(trace) + await ctx.send(embed=error_embed(gen_codeblock(trace), title="Internal error")) + except Exception as e: print("meta-error:", e) + @bot.command(help="Gives you a random fortune as generated by `fortune`.") async def fortune(ctx): await ctx.send(subprocess.run(["fortune"], stdout=subprocess.PIPE, encoding="UTF-8").stdout) +@bot.command(help="Generates an apioform type.") +async def apioform(ctx): + await ctx.send(util.apioform()) + @bot.command(help="Says Pong.") async def ping(ctx): await ctx.send("Pong.") @@ -97,6 +124,9 @@ exec_flag_parser = NonExitingArgumentParser(add_help=False) exec_flag_parser.add_argument("--verbose", "-v", action="store_true") exec_flag_parser.add_argument("--language", "-L") +def gen_codeblock(content): + return "```\n" + content.replace("```", "\\`\\`\\`")[:1900] + "\n```" + @bot.command(rest_is_raw=True, help="Execute provided code (in a codeblock) using TIO.run.") async def exec(ctx, *, arg): match = re.match(EXEC_REGEX, arg, flags=re.DOTALL) @@ -115,11 +145,11 @@ async def exec(ctx, *, arg): async with ctx.typing(): ok, real_lang, result, debug = await tio.run(lang, code) if not ok: - await ctx.send(embed=error_embed(f"""```{result}```""", "Execution error")) + await ctx.send(embed=error_embed(gen_codeblock(result), "Execution failed")) else: out = result if flags.verbose: - debug_block = f"""\n```{debug}\nLanguage: {real_lang}```""" + debug_block = "\n" + gen_codeblock(f"""{debug}\nLanguage: {real_lang}""") out = out[:2000 - len(debug_block)] + debug_block else: out = out[:2000] @@ -143,11 +173,12 @@ Reminders are checked every minute, so while precise times are not guaranteed, r async def remind(ctx, time, *, reminder): reminder = reminder.strip() if len(reminder) > 512: - await ctx.send(embed=error_embed("Maximum reminder length is 512 characters")) + await ctx.send(embed=error_embed("Maximum reminder length is 512 characters", "Foolish user error")) return extra_data = { "author_id": ctx.author.id, "channel_id": ctx.message.channel.id, + "message_id": ctx.message.id, "original_time_spec": time } try: @@ -192,12 +223,59 @@ AutoBotRobot is open source - the code is available at """) +@bot.command(help="Randomly generate an integer using dice syntax.", name="random", rest_is_raw=True) +async def random_int(ctx, *, dice): + await ctx.send(rolldice.roll_dice(dice)[0]) + +bad_things = ["lyric", "endos", "solarflame", "lyric", "319753218592866315", "andrew", "6", "c++"] +good_things = ["potato", "heav", "gollark", "helloboi", "bees", "hellboy", "rust", "ferris", "crab", "transistor"] +negations = ["not", "bad", "un", "kill", "n't"] +def weight(thing): + lthing = thing.lower() + weight = 1.0 + if lthing == "c": weight *= 0.3 + for bad_thing in bad_things: + if bad_thing in lthing: weight *= 0.5 + for good_thing in good_things: + if good_thing in lthing: weight *= 2.0 + for negation in negations: + for _ in range(lthing.count(negation)): weight = 1 / weight + return weight + +@bot.command(help="'Randomly' choose between the specified options.", name="choice", aliases=["choose"]) +async def random_choice(ctx, *choices): + choicelist = list(choices) + samples = 1 + try: + samples = int(choices[0]) + choicelist.pop(0) + except: pass + + if samples > 1e5: + await ctx.send("No.") + return + + choices = random.choices(choicelist, weights=map(weight, choicelist), k=samples) + + if len(choices) == 1: + await ctx.send(choices[0]) + else: + counts = {} + for choice in choices: + counts[choice] = counts.get(choice, 0) + 1 + await ctx.send("\n".join(map(lambda x: f"{x[0]} x{x[1]}", counts.items()))) + async def admin_check(ctx): if not await bot.is_owner(ctx.author): - await ctx.send(embed=error_embed(f"{ctx.author.name} is not in the sudoers file. This incident has been reported.")) + # apparently this has to be a pure function because ++help calls it for some reason because of course + #await ctx.send(embed=error_embed(f"{ctx.author.name} is not in the sudoers file. This incident has been reported.")) return False return True +@bot.check +async def andrew_bad(ctx): + return ctx.message.author.id != 543131534685765673 + @bot.group() @commands.check(admin_check) async def magic(ctx): @@ -212,14 +290,18 @@ async def py(ctx, *, code): **locals(), "bot": bot, "ctx": ctx, + "db": database } result = await asyncio.wait_for(util.async_exec(code, loc, globals()), timeout=5.0) if result != None: - await ctx.send("```\n" + repr(result).replace("```", "\\`\\`\\`")[:1900] + "\n```") + if isinstance(result, str): + await ctx.send(result[:1999]) + else: + await ctx.send(gen_codeblock(repr(result))) except TimeoutError: await ctx.send(embed=error_embed("Timed out.")) except BaseException as e: - await ctx.send("Error:\n```\n" + traceback.format_exc().replace("```", "\\`\\`\\`")[:1900] + "\n```") + await ctx.send(embed=error_embed(gen_codeblock(traceback.format_exc()))) @magic.command(rest_is_raw=True) async def sql(ctx, *, code): @@ -230,10 +312,10 @@ async def sql(ctx, *, code): async with csr as cursor: async for row in cursor: out += " ".join(map(repr, row)) + "\n" - await ctx.send("```\n" + out[:1990] + "```") + await ctx.send(gen_codeblock(out)) await database.commit() except Exception as e: - await ctx.send(embed=error_embed("```\n" + traceback.format_exc() + "```")) + await ctx.send(embed=error_embed(gen_codeblock(traceback.format_exc()))) @bot.event async def on_ready(): diff --git a/src/util.py b/src/util.py index 99df028..9046ac7 100644 --- a/src/util.py +++ b/src/util.py @@ -3,17 +3,18 @@ import datetime import parsedatetime import ast import copy +import random from dateutil.relativedelta import relativedelta # from here: https://github.com/Rapptz/RoboDanny/blob/18b92ae2f53927aedebc25fb5eca02c8f6d7a874/cogs/utils/time.py short_timedelta_regex = re.compile(""" -(?:(?P[0-9]{1,8})(?:years?|y))? # e.g. 2y -(?:(?P[0-9]{1,8})(?:months?|mo))? # e.g. 2months -(?:(?P[0-9]{1,8})(?:weeks?|w))? # e.g. 10w -(?:(?P[0-9]{1,8})(?:days?|d))? # e.g. 14d -(?:(?P[0-9]{1,8})(?:hours?|h))? # e.g. 12h -(?:(?P[0-9]{1,8})(?:minutes?|m))? # e.g. 10m -(?:(?P[0-9]{1,8})(?:seconds?|s))? # e.g. 15s """, re.VERBOSE) +(?:(?P[0-9]{1,12})(?:years?|y))? # e.g. 2y +(?:(?P[0-9]{1,12})(?:months?|mo))? # e.g. 2months +(?:(?P[0-9]{1,12})(?:weeks?|w))? # e.g. 10w +(?:(?P[0-9]{1,12})(?:days?|d))? # e.g. 14d +(?:(?P[0-9]{1,12})(?:hours?|h))? # e.g. 12h +(?:(?P[0-9]{1,12})(?:minutes?|m))? # e.g. 10m +(?:(?P[0-9]{1,12})(?:seconds?|s))? # e.g. 15s """, re.VERBOSE) def parse_short_timedelta(text): match = short_timedelta_regex.fullmatch(text) @@ -70,4 +71,38 @@ async def async_exec(code, loc, glob): ast.fix_missing_locations(wrapper) exec(compile(wrapper, "", "exec"), loc, glob) - return await (loc.get("repl_coroutine") or glob.get("repl_coroutine"))() \ No newline at end of file + return await (loc.get("repl_coroutine") or glob.get("repl_coroutine"))() + +# https://github.com/LyricLy/Esobot/blob/bcc9e548c84ea9b23fc832d0b0aaa8288de64886/cogs/general.py +lyrictable_raw = { + "a": "а", + "c": "с", + "e": "е", + "s": "ѕ", + "i": "і", + "j": "ј", + "o": "о", + "p": "р", + "y": "у", + "x": "х" + } +lyrictable = str.maketrans({v: k for k, v in lyrictable_raw.items()}) + +apioprefixes = ["cryo", "meta", "chrono", "contra", "ortho", "macro", "micro"] +apioinfixes = ["cryo", "pyro", "chrono", "meta", "anarcho", "arachno", "aqua", + "hydro", "radio", "xeno", "morto", "thanato", "memeto", "contra", "umbra", "macrono"] +apiosuffixes = ["hazard", "form"] + +def apioform(): + out = "" + if random.randint(0, 4) == 0: + out += random.choice(apioprefixes) + out += "apio" + while True: + out += random.choice(apioinfixes) + if random.randint(0, 1) == 0: break + out += random.choice(apiosuffixes) + return out + +def unlyric(text): + return text.translate(lyrictable).replace("\u200b", "") \ No newline at end of file