From 6d18a5c56e3d48432c8837d603b10ce892092794 Mon Sep 17 00:00:00 2001 From: osmarks Date: Thu, 14 Jan 2021 09:38:32 +0000 Subject: [PATCH] change things, somehow --- src/achievement.py | 4 ++-- src/debug.py | 35 +++++++++++++++++++++++++-------- src/main.py | 11 +++-------- src/telephone.py | 2 +- src/util.py | 20 ++++++++++++++----- src/voice.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 97 insertions(+), 24 deletions(-) create mode 100644 src/voice.py diff --git a/src/achievement.py b/src/achievement.py index 2f56403..2909fbe 100644 --- a/src/achievement.py +++ b/src/achievement.py @@ -52,7 +52,7 @@ def setup(bot): async def achievements(ctx): pass @achievements.command(help="Enable/disable achievement messages on this guild.") - @commands.check(util.server_mod_check(bot)) + @commands.check(util.server_mod_check) async def set_enabled(ctx, on: bool): await bot.database.execute("INSERT OR REPLACE INTO guild_config VALUES (?, ?)", (ctx.guild.id, int(on))) await bot.database.commit() @@ -69,4 +69,4 @@ def setup(bot): if re.match("spect(re|er).{,20}(communism|☭)", content, re.IGNORECASE): await achieve(bot, msg, "spectre_of_communism") if re.match(r"^(.+)\1+$", content) and len(content) >= 1950: await achieve(bot, msg, "spam") if content_len > 30 and (len(re.findall("[\u0300-\u036f\U00040000-\U0010FFFF]", content)) / content_len) > 0.35: await achieve(bot, msg, "unicode_abuse") - if re.match("(RTFM|(read|look|view|use).{,20}(doc|man|instruction))", content, re.IGNORECASE): await achieve(bot, msg, "rtfm") \ No newline at end of file + if re.match("(RTFM|(read|look|view|use).{,8}(document|man(page|ual)s?|instruction))", content, re.IGNORECASE): await achieve(bot, msg, "rtfm") \ No newline at end of file diff --git a/src/debug.py b/src/debug.py index a479932..280da2e 100644 --- a/src/debug.py +++ b/src/debug.py @@ -1,20 +1,24 @@ import util import asyncio import traceback +import re from discord.ext import commands +import util def setup(bot): - async def admin_check(ctx): - return await bot.is_owner(ctx.author) - @bot.group() - @commands.check(admin_check) + @commands.check(util.admin_check) async def magic(ctx): if ctx.invoked_subcommand == None: return await ctx.send("Invalid magic command.") @magic.command(rest_is_raw=True) async def py(ctx, *, code): + timeout = 5.0 + timeout_match = re.search("#timeout:([0-9]+)", code, re.IGNORECASE) + if timeout_match: + timeout = int(timeout_match.group(1)) + if timeout == 0: timeout = None code = util.extract_codeblock(code) try: loc = { @@ -23,13 +27,24 @@ def setup(bot): "ctx": ctx, "db": bot.database } - result = await asyncio.wait_for(util.async_exec(code, loc, globals()), timeout=5.0) + + def check(re, u): return str(re.emoji) == "❌" and u == ctx.author + + result = None + async def run(): + nonlocal result + result = await util.async_exec(code, loc, globals()) + halt_task = asyncio.create_task(bot.wait_for("reaction_add", check=check)) + exec_task = asyncio.create_task(run()) + done, pending = await asyncio.wait((exec_task, halt_task), timeout=timeout, return_when=asyncio.FIRST_COMPLETED) + for task in done: task.result() # get exceptions + for task in pending: task.cancel() if result != None: if isinstance(result, str): - await ctx.send(result[:1999]) + await ctx.send(result[:2000]) else: await ctx.send(util.gen_codeblock(repr(result))) - except TimeoutError: + except (TimeoutError, asyncio.CancelledError): await ctx.send(embed=util.error_embed("Timed out.")) except BaseException as e: await ctx.send(embed=util.error_embed(util.gen_codeblock(traceback.format_exc()))) @@ -51,4 +66,8 @@ def setup(bot): @magic.command() async def reload_config(ctx): util.load_config() - ctx.send("Done!") \ No newline at end of file + ctx.send("Done!") + + @magic.command() + async def reload_ext(ctx): + for ext in util.extensions: bot.reload_extension(ext) \ No newline at end of file diff --git a/src/main.py b/src/main.py index 1829932..4637713 100644 --- a/src/main.py +++ b/src/main.py @@ -27,7 +27,7 @@ logging.basicConfig(level=logging.INFO, format="%(levelname)s %(asctime)s %(mess #intents = discord.Intents.default() #intents.members = True -bot = commands.Bot(command_prefix=config["prefix"], description="AutoBotRobot, the most useless bot in the known universe.", +bot = commands.Bot(command_prefix=config["prefix"], description="AutoBotRobot, the most useless bot in the known universe." + util.config.get("description_suffix", ""), case_insensitive=True, allowed_mentions=discord.AllowedMentions(everyone=False, users=True, roles=True)) bot._skip_check = lambda x, y: False @@ -222,13 +222,8 @@ async def on_ready(): async def run_bot(): bot.database = await db.init(config["database"]) - for ext in ( - "reminders", - "debug", - "telephone", - "achievement", - "heavserver" - ): + for ext in util.extensions: + logging.info("loaded %s", ext) bot.load_extension(ext) await bot.start(config["token"]) diff --git a/src/telephone.py b/src/telephone.py index 3863388..28c48bf 100644 --- a/src/telephone.py +++ b/src/telephone.py @@ -66,7 +66,7 @@ def setup(bot): await asyncio.gather(*map(send_to, calls)) @telephone.command() - @commands.check(util.server_mod_check(bot)) + @commands.check(util.server_mod_check) async def setup(ctx): num = generate_address(ctx) await ctx.send(f"Your address is {num}.") diff --git a/src/util.py b/src/util.py index e0a6f5d..01eee85 100644 --- a/src/util.py +++ b/src/util.py @@ -235,10 +235,11 @@ def gen_codeblock(content): def json_encode(x): return json.dumps(x, separators=(',', ':')) -def server_mod_check(bot): - async def check(ctx): - return ctx.author.permissions_in(ctx.channel).manage_channels or (await bot.is_owner(ctx.author)) - return check +async def server_mod_check(ctx): + return ctx.author.permissions_in(ctx.channel).manage_channels or (await ctx.bot.is_owner(ctx.author)) + +async def admin_check(ctx): + return await ctx.bot.is_owner(ctx.author) async def get_asset(bot: commands.Bot, identifier): safe_ident = re.sub("[^A-Za-z0-9_.-]", "_", identifier) @@ -252,4 +253,13 @@ async def get_asset(bot: commands.Bot, identifier): return url def hashbow(thing): - return int.from_bytes(hashlib.blake2b(thing.encode("utf-8")).digest()[:3], "little") \ No newline at end of file + return int.from_bytes(hashlib.blake2b(thing.encode("utf-8")).digest()[:3], "little") + +extensions = ( + "reminders", + "debug", + "telephone", + "achievement", + "heavserver", + "voice" +) \ No newline at end of file diff --git a/src/voice.py b/src/voice.py new file mode 100644 index 0000000..9d8a27a --- /dev/null +++ b/src/voice.py @@ -0,0 +1,49 @@ +import util +import discord +from discord.oggparse import OggStream +# requests is for synchronous HTTP. This would be quite awful to use in the rest of the code, but is probably okay since voice code is in a separate thread +# This should, arguably, run in a separate process given python's GIL etc. but this would involve significant effort probably +import requests +import io +from discord.ext import commands + + +class HTTPSource(discord.AudioSource): + def __init__(self, url): + self.url = url + async def start(self): + bytestream = requests.get(self.url, stream=True).raw + self.packets = OggStream(io.BufferedReader(bytestream, buffer_size=2**10)).iter_packets() + def read(self): return next(self.packets, b"") + def is_opus(self): return True + +def setup(bot): + # experimental, thus limit to me only + @bot.group() + @commands.check(util.admin_check) + async def radio(ctx): pass + + @radio.command() + async def connect(ctx, thing="main"): + voice = ctx.author.voice + if not voice: return await ctx.send(embed=util.error_embed("You are not in a voice channel.")) + if voice.mute: return await ctx.send(embed=util.error_embed("You are muted.")) + thing_url = util.config["radio_urls"].get(thing, None) + if thing_url == None: return await ctx.send(embed=util.error_embed("No such radio thing.")) + existing = ctx.guild.voice_client + if existing: await existing.disconnect() + vc = await voice.channel.connect() + src = HTTPSource(thing_url) + await src.start() + vc.play(src) + + @radio.command() + async def disconnect(ctx): + if ctx.guild.voice_client: + ctx.guild.voice_client.stop() + await ctx.guild.voice_client.disconnect() + +def teardown(bot): + for guild in bot.guilds: + if guild.voice_client: + guild.voice_client.stop() \ No newline at end of file