1
0
mirror of https://github.com/osmarks/autobotrobot synced 2025-11-23 10:44:48 +00:00

I forgot what most of these changes are. Also autopraise mechanism.

This commit is contained in:
osmarks
2025-10-16 08:31:22 +01:00
parent ea7fac4274
commit 31e9a31c61
15 changed files with 117 additions and 56 deletions

View File

@@ -1,9 +1,8 @@
# TODO # TODO
pytio==0.3.1 pytio==0.3.1
aiohttp==3.9.3 aiosqlite
aiosqlite==0.19.0 discord.py
nextcord==2.3.2 numpy<2
numpy==1.26
prometheus-async==19.2.0 prometheus-async==19.2.0
prometheus-client==0.15.0 prometheus-client==0.15.0
pydot==1.4.2 pydot==1.4.2
@@ -11,4 +10,4 @@ toml==0.10.2
requests==2.28.1 requests==2.28.1
python-dateutil==2.8.2 python-dateutil==2.8.2
irc==20.1.0 irc==20.1.0
parsedatetime parsedatetime

View File

@@ -15,7 +15,7 @@ Achievement = collections.namedtuple("Achievement", ["name", "condition", "descr
achievements = { achievements = {
"spectre_of_communism": Achievement("Containment Efforts Ongoing", "Refer to the 'spectre of communism' in a message.", "A spectre is haunting Europe. The spectre of communism. Containment efforts are ongoing and full containment is projected by 2036."), "spectre_of_communism": Achievement("Containment Efforts Ongoing", "Refer to the 'spectre of communism' in a message.", "A spectre is haunting Europe. The spectre of communism. Containment efforts are ongoing and full containment is projected by 2036."),
"test": Achievement("Test", "Test achievement. Obtained for testing.", "Great job, you ran the test command!"), "test": Achievement("Test", "Test achievement. Obtained for testing.", "Great job, you ran the test command!"),
"spam": Achievement("beesbeesbeesbeesbeesbeesbeesbeesbees", "Send a long message containing the same thing repeatedly.", "You should probably not do this, nobody* likes spam!"), "spam": Achievement("beesbeesbeesbeesbeesbeesbeesbeesbees", "Send a long message containing the same thing repeatedly.", "You should probably not do this; nobody* likes spam!"),
"unicode_abuse": Achievement("Anomalous Unicode", "Send a high proportion of weird Unicode characters in a message.", "h̵͖̻̮̗̹̆͛͆̎ͮͤͫ͛ͦ̓̅ͤ́͢é͒ͧ̌̀ͪ̈͂̈́̉ͣ̅̿̄̌̋̿̽̚͏̛͏͚̯͉̟͇̼̹͎ͅa̠̹̘͎̫̜̞̩͖̟̟͍͇͈͍̝͕͛ͥ͊̾̈́ͩͯͩͭ̆̋͐͗̉͋̓̀͝v͎͖̜͎͔̞͚͉̺̞̘̥͖̝͚̺̍ͤ̌͂ͨ̃̅ͫ̿͛ͯ̓̉̆̎͊̀̚̕͟s̪̠̟̣̝̹̭̻̈́ͤ͗̏ͮ̂ͯ̈́̊ͩ̓̆̌̆͌̽̓̈́̚͢͞e̛̞̙̜̗̰͕͕͎̺͍̭̲̟̭̲̫̬͓ͯ̅̓̆̂̔̃͟r̷̛̮̮͇̳̳̾ͯͮͩ̏͂ͤ̿̽ͧ͒͋́̕ͅͅv̴̠͉̼̮̭̘ͪͯͦ͌́ͯ̒̃̀́̃͜͝ͅe̵̷̢͕̣̻̥̲͓̼͍̱͕̮̯̱̤̹̱̝̎̓̈́̿ͤ̔̍ͭͭ͐ͅŗ̔ͮͯ͂́͏̻͈̱ͅ ̣͇̼͊̄ͫ̆̍̄̀̀̓͊͐͋̌͘͠į̱͔̰̭̫̱̫̊ͪ̅ͥ̈́ͥ̐͌̅ͪ̅ͨ̎̀͘͝s̍͑̌̋̅͌͂ͨͬͯ̇͊҉̛̱̺͕̰͓̗̖̬͡͡ ̥̤̺̖̪̪́ͯͣ̏̅̈ͣ̿̀͠͠͞i̢̛̭̰̻͈̦̣̮̞̤̩̊̌̾͛ͭͦ̆ͮ̃̎ͪ̔ͬ͊̆͂ͫͅn̸̖͚̣̪̩̏ͥ̈́̅ͯ̔͆́ͦ͗͛͒̃̃ͫ͟͜͝͠ȩ̸͎̟̣̞͉̫̗̙̻̯͍̰̣̌ͪͨ͛̆̕͡v̙͙̲͕͔̦̣̺͔̖͉̜̲̩̈̿ͥ̎͊̈́̊ͯͯ͒ͭ̊̀͢i̪͈̣̱̞̥̰̟̣̩̼̻̪̳̤͇̻̹͉͗ͭ͆̆̎̀͑͑̆͋̏̏͊ͣͦ͆ͣ̈́̓͟͢ţ̵̘̫̯͓̻̗͕̘͙̯̞̪̪̲̤̬̜͕ͫ̄̌̓̎͌ͧ̔͟͢ͅa̸̧̭̲̯̳̔́͋̐͂̇ͪ̔̐́̚͢b͐̅̔ͭ͗̊̂̾̀̓ͭͭ͑ͤ̏̐̃ͩͬ҉̞̼̮̤̝̲̳͓̗̤̫̭̝̹̙͘͟͝ļ̷͈̭̖͓̜̬͔̻͔̀̎ͯ͗̐̽̏ͦ̊͗ͧ́͘ͅe̢͍̦̗̬̝̠͔̳̣̯̮̣̹͍͙̞̜ͣ̉͆̊̀̎ͦ͌̂̋̊ͨ͛́"), "unicode_abuse": Achievement("Anomalous Unicode", "Send a high proportion of weird Unicode characters in a message.", "h̵͖̻̮̗̹̆͛͆̎ͮͤͫ͛ͦ̓̅ͤ́͢é͒ͧ̌̀ͪ̈͂̈́̉ͣ̅̿̄̌̋̿̽̚͏̛͏͚̯͉̟͇̼̹͎ͅa̠̹̘͎̫̜̞̩͖̟̟͍͇͈͍̝͕͛ͥ͊̾̈́ͩͯͩͭ̆̋͐͗̉͋̓̀͝v͎͖̜͎͔̞͚͉̺̞̘̥͖̝͚̺̍ͤ̌͂ͨ̃̅ͫ̿͛ͯ̓̉̆̎͊̀̚̕͟s̪̠̟̣̝̹̭̻̈́ͤ͗̏ͮ̂ͯ̈́̊ͩ̓̆̌̆͌̽̓̈́̚͢͞e̛̞̙̜̗̰͕͕͎̺͍̭̲̟̭̲̫̬͓ͯ̅̓̆̂̔̃͟r̷̛̮̮͇̳̳̾ͯͮͩ̏͂ͤ̿̽ͧ͒͋́̕ͅͅv̴̠͉̼̮̭̘ͪͯͦ͌́ͯ̒̃̀́̃͜͝ͅe̵̷̢͕̣̻̥̲͓̼͍̱͕̮̯̱̤̹̱̝̎̓̈́̿ͤ̔̍ͭͭ͐ͅŗ̔ͮͯ͂́͏̻͈̱ͅ ̣͇̼͊̄ͫ̆̍̄̀̀̓͊͐͋̌͘͠į̱͔̰̭̫̱̫̊ͪ̅ͥ̈́ͥ̐͌̅ͪ̅ͨ̎̀͘͝s̍͑̌̋̅͌͂ͨͬͯ̇͊҉̛̱̺͕̰͓̗̖̬͡͡ ̥̤̺̖̪̪́ͯͣ̏̅̈ͣ̿̀͠͠͞i̢̛̭̰̻͈̦̣̮̞̤̩̊̌̾͛ͭͦ̆ͮ̃̎ͪ̔ͬ͊̆͂ͫͅn̸̖͚̣̪̩̏ͥ̈́̅ͯ̔͆́ͦ͗͛͒̃̃ͫ͟͜͝͠ȩ̸͎̟̣̞͉̫̗̙̻̯͍̰̣̌ͪͨ͛̆̕͡v̙͙̲͕͔̦̣̺͔̖͉̜̲̩̈̿ͥ̎͊̈́̊ͯͯ͒ͭ̊̀͢i̪͈̣̱̞̥̰̟̣̩̼̻̪̳̤͇̻̹͉͗ͭ͆̆̎̀͑͑̆͋̏̏͊ͣͦ͆ͣ̈́̓͟͢ţ̵̘̫̯͓̻̗͕̘͙̯̞̪̪̲̤̬̜͕ͫ̄̌̓̎͌ͧ̔͟͢ͅa̸̧̭̲̯̳̔́͋̐͂̇ͪ̔̐́̚͢b͐̅̔ͭ͗̊̂̾̀̓ͭͭ͑ͤ̏̐̃ͩͬ҉̞̼̮̤̝̲̳͓̗̤̫̭̝̹̙͘͟͝ļ̷͈̭̖͓̜̬͔̻͔̀̎ͯ͗̐̽̏ͦ̊͗ͧ́͘ͅe̢͍̦̗̬̝̠͔̳̣̯̮̣̹͍͙̞̜ͣ̉͆̊̀̎ͦ͌̂̋̊ͨ͛́"),
"rtfm": Achievement("RTFM", "Tell someone to read the documentation.", "Apparently, people won't do this without prompting half the time."), "rtfm": Achievement("RTFM", "Tell someone to read the documentation.", "Apparently, people won't do this without prompting half the time."),
"error": Achievement("You broke it", "Cause an internal error in the bot", "I should probably fix this.") "error": Achievement("You broke it", "Cause an internal error in the bot", "I should probably fix this.")
@@ -48,7 +48,7 @@ async def achieve(bot: commands.Bot, message: discord.Message, achievement):
await bot.database.commit() await bot.database.commit()
logging.info("Awarded achievement %s to %s", achievement, message.author.name) logging.info("Awarded achievement %s to %s", achievement, message.author.name)
def setup(bot): async def setup(bot):
@bot.group(name="achievements", aliases=["ach", "achieve", "achievement"], brief="Achieve a wide variety of fun achievements!", help=f""" @bot.group(name="achievements", aliases=["ach", "achieve", "achievement"], brief="Achieve a wide variety of fun achievements!", help=f"""
Do things and get arbitrary achievements for them! Do things and get arbitrary achievements for them!
Note that due to reasons messages for achievements will not be shown except in opted-in servers, although achievements will be gained regardless. Note that due to reasons messages for achievements will not be shown except in opted-in servers, although achievements will be gained regardless.
@@ -73,4 +73,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("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 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 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).{,8}(document|man(page|ual)s?|instruction))", content, re.IGNORECASE): await achieve(bot, msg, "rtfm") if re.match("(RTFM|(read|look|view|use).{,8}(document|man(page|ual)s?|instruction))", content, re.IGNORECASE): await achieve(bot, msg, "rtfm")

View File

@@ -101,7 +101,7 @@ class GeneralCommands(commands.Cog):
await ctx.send(embed=util.error_embed(util.gen_codeblock(result), "Execution failed")) await ctx.send(embed=util.error_embed(util.gen_codeblock(result), "Execution failed"))
else: else:
out = result out = result
if flags.verbose: if flags.verbose:
debug_block = "\n" + util.gen_codeblock(f"""{debug}\nLanguage: {real_lang}""") debug_block = "\n" + util.gen_codeblock(f"""{debug}\nLanguage: {real_lang}""")
out = out[:2000 - len(debug_block)] + debug_block out = out[:2000 - len(debug_block)] + debug_block
else: else:
@@ -195,5 +195,5 @@ AutoBotRobot is operated by gollark/osmarks.
await ctx.send("\n".join(map(lambda x: f"{x[0]} x{x[1]}", results))) await ctx.send("\n".join(map(lambda x: f"{x[0]} x{x[1]}", results)))
def setup(bot): async def setup(bot):
bot.add_cog(GeneralCommands(bot)) await bot.add_cog(GeneralCommands(bot))

View File

@@ -6,7 +6,7 @@ from discord.ext import commands
import util import util
import eventbus import eventbus
def setup(bot): async def setup(bot):
@bot.group(help="Debug/random messing around utilities. Owner-only.") @bot.group(help="Debug/random messing around utilities. Owner-only.")
@commands.check(util.admin_check) @commands.check(util.admin_check)
async def magic(ctx): async def magic(ctx):
@@ -76,6 +76,6 @@ def setup(bot):
@magic.command(help="Reload extensions (all or the specified one).") @magic.command(help="Reload extensions (all or the specified one).")
async def reload_ext(ctx, ext="all"): async def reload_ext(ctx, ext="all"):
if ext == "all": if ext == "all":
for ext in util.extensions: bot.reload_extension(ext) for ext in util.extensions: await bot.reload_extension(ext)
else: bot.reload_extension(ext) else: await bot.reload_extension(ext)
await ctx.send("Done!") await ctx.send("Done!")

View File

@@ -8,7 +8,7 @@ import metrics
role_transfer_lock = asyncio.Lock() role_transfer_lock = asyncio.Lock()
def setup(bot): async def setup(bot):
@bot.listen() @bot.listen()
async def on_message(message): async def on_message(message):
if message.guild and message.guild.id == util.config["esoserver"]["id"]: if message.guild and message.guild.id == util.config["esoserver"]["id"]:
@@ -26,4 +26,4 @@ def setup(bot):
await user.remove_roles(role, reason="untransfer unrole") await user.remove_roles(role, reason="untransfer unrole")
await message.author.add_roles(role, reason="transfer role") await message.author.add_roles(role, reason="transfer role")
metrics.role_transfers.inc() metrics.role_transfers.inc()
return return

View File

@@ -5,11 +5,11 @@ import discord
import metrics import metrics
def setup(bot): async def setup(bot):
@bot.listen() @bot.listen()
async def on_member_join(member): async def on_member_join(member):
if member.guild and member.guild.id == util.config["heavserver"]["id"]: if member.guild and member.guild.id == util.config["heavserver"]["id"]:
logging.info("%s (%d) joined heavserver", member.display_name, member.id) logging.info("%s (%d) joined heavserver", member.display_name, member.id)
if member.bot: if member.bot:
await member.add_roles(discord.utils.get(member.guild.roles, id=util.config["heavserver"]["quarantine_role"])) await member.add_roles(discord.utils.get(member.guild.roles, id=util.config["heavserver"]["quarantine_role"]))
await member.add_roles(discord.utils.get(member.guild.roles, id=random.choice(util.config["heavserver"]["moderator_roles"][:]))) await member.add_roles(discord.utils.get(member.guild.roles, id=random.choice(util.config["heavserver"]["moderator_roles"][:])))

View File

@@ -113,10 +113,10 @@ async def initialize():
global unlisten global unlisten
unlisten = eventbus.add_listener(util.config["irc"]["name"], on_bridge_message) unlisten = eventbus.add_listener(util.config["irc"]["name"], on_bridge_message)
def setup(bot): async def setup(bot):
asyncio.create_task(initialize()) asyncio.create_task(initialize())
def teardown(bot=None): async def teardown(bot=None):
if global_conn: if global_conn:
global_conn.planned_disconnection = True global_conn.planned_disconnection = True
global_conn.disconnect() global_conn.disconnect()

View File

@@ -74,7 +74,7 @@ async def on_command_error(ctx, err):
@bot.check @bot.check
async def andrew_bad(ctx): async def andrew_bad(ctx):
return ctx.message.author.id != 543131534685765673 return ctx.message.author.id != 543131534685765673 or ctx.message.author.id != 739032871087374408 or ctx.message.author.id in config.get("bans", [])
@bot.event @bot.event
async def on_ready(): async def on_ready():
@@ -108,7 +108,7 @@ async def run_bot():
await eventbus.initial_load(bot.database) await eventbus.initial_load(bot.database)
for ext in util.extensions: for ext in util.extensions:
logging.info("Loaded %s", ext) logging.info("Loaded %s", ext)
bot.load_extension(ext) await bot.load_extension(ext)
asyncio.create_task(autogollark.run_bot()) asyncio.create_task(autogollark.run_bot())
await bot.start(config["token"]) await bot.start(config["token"])

View File

@@ -71,7 +71,7 @@ class Reminders(commands.Cog):
await ctx.send(embed=util.error_embed("Invalid time (wrong format/too large months or years)")) await ctx.send(embed=util.error_embed("Invalid time (wrong format/too large months or years)"))
return return
utc_time, local_time = util.in_timezone(time, tz) utc_time, local_time = util.in_timezone(time, tz)
id = (await self.bot.database.execute_insert("INSERT INTO reminders (remind_timestamp, created_timestamp, reminder, expired, extra) VALUES (?, ?, ?, ?, ?)", id = (await self.bot.database.execute_insert("INSERT INTO reminders (remind_timestamp, created_timestamp, reminder, expired, extra) VALUES (?, ?, ?, ?, ?)",
(utc_time.timestamp(), now.timestamp(), reminder, 0, util.json_encode(extra_data))))["last_insert_rowid()"] (utc_time.timestamp(), now.timestamp(), reminder, 0, util.json_encode(extra_data))))["last_insert_rowid()"]
await self.bot.database.commit() await self.bot.database.commit()
await ctx.send(f"Reminder scheduled for {util.format_time(local_time)} ({util.format_timedelta(now, utc_time)}).") await ctx.send(f"Reminder scheduled for {util.format_time(local_time)} ({util.format_timedelta(now, utc_time)}).")
@@ -178,10 +178,10 @@ class Reminders(commands.Cog):
self.reminder_queue.pop(0) self.reminder_queue.pop(0)
await self.fire_reminder(next_id) await self.fire_reminder(next_id)
def setup(bot): async def setup(bot):
cog = Reminders(bot) cog = Reminders(bot)
await bot.add_cog(cog)
asyncio.create_task(cog.init_reminders()) asyncio.create_task(cog.init_reminders())
bot.add_cog(cog)
def teardown(bot): async def teardown(bot):
bot.rloop_task.cancel() bot.rloop_task.cancel()

View File

@@ -18,7 +18,7 @@ class Parser(html.parser.HTMLParser):
attrs = dict(attrs) attrs = dict(attrs)
if tag == "a" and attrs.get("class") == "result__a" and "https://duckduckgo.com/y.js?ad_provider" not in attrs["href"]: if tag == "a" and attrs.get("class") == "result__a" and "https://duckduckgo.com/y.js?ad_provider" not in attrs["href"]:
self.links.append(attrs["href"]) self.links.append(attrs["href"])
class Search(commands.Cog): class Search(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
@@ -43,7 +43,7 @@ class Search(commands.Cog):
return await ctx.send(p.links[0], reference=ctx.message) return await ctx.send(p.links[0], reference=ctx.message)
except IndexError: except IndexError:
return await ctx.send("No results.", reference=ctx.message) return await ctx.send("No results.", reference=ctx.message)
async def wp_search(self, query): async def wp_search(self, query):
async with self.session.get("https://en.wikipedia.org/w/api.php", async with self.session.get("https://en.wikipedia.org/w/api.php",
params={ "action": "query", "list": "search", "srsearch": query, "utf8": "1", "format": "json", "srlimit": 1 }) as resp: params={ "action": "query", "list": "search", "srsearch": query, "utf8": "1", "format": "json", "srlimit": 1 }) as resp:
@@ -62,10 +62,10 @@ class Search(commands.Cog):
return await self.wp_fetch(new_page, fallback=False) return await self.wp_fetch(new_page, fallback=False)
if page in self.wp_cache: return self.wp_cache[page] if page in self.wp_cache: return self.wp_cache[page]
if page in self.wp_search_cache: if page in self.wp_search_cache:
if self.wp_search_cache[page] is None: return None if self.wp_search_cache[page] is None: return None
return await self.wp_fetch(self.wp_search_cache[page], fallback=False) return await self.wp_fetch(self.wp_search_cache[page], fallback=False)
async with self.session.get("https://en.wikipedia.org/w/api.php", async with self.session.get("https://en.wikipedia.org/w/api.php",
params={ "action": "query", "format": "json", "titles": page, "prop": "extracts", "exintro": 1, "explaintext": 1 }) as resp: params={ "action": "query", "format": "json", "titles": page, "prop": "extracts", "exintro": 1, "explaintext": 1 }) as resp:
data = (await resp.json())["query"] data = (await resp.json())["query"]
if "-1" in data["pages"]: if "-1" in data["pages"]:
@@ -94,6 +94,6 @@ class Search(commands.Cog):
if self.pool is not None: if self.pool is not None:
self.pool.shutdown() self.pool.shutdown()
def setup(bot): async def setup(bot):
cog = Search(bot) cog = Search(bot)
bot.add_cog(cog) await bot.add_cog(cog)

View File

@@ -1,16 +1,14 @@
import asyncio
import argparse
import random import random
from numpy.random import default_rng
import re
import aiohttp import aiohttp
import subprocess from collections import defaultdict, deque
import discord.ext.commands as commands import discord.ext.commands as commands
import discord import discord
from datetime import datetime from datetime import datetime, timedelta, timezone
from pathlib import Path from pathlib import Path
import asyncio
import logging
import tio
import util import util
cleaner = commands.clean_content() cleaner = commands.clean_content()
@@ -20,7 +18,11 @@ def clean(ctx, text):
class Sentience(commands.Cog): class Sentience(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
self.timeouts = {}
self.session = aiohttp.ClientSession() self.session = aiohttp.ClientSession()
self.autopraise_spontaneous_times = {}
self.autopraise_triggered_times = {}
self.praise_context_buffers = defaultdict(deque)
async def serialize_history(self, ctx, n=20): async def serialize_history(self, ctx, n=20):
PREFIXES = [ ctx.prefix + "ai", ctx.prefix + "ag", ctx.prefix + "autogollark", ctx.prefix + "gollark" ] PREFIXES = [ ctx.prefix + "ai", ctx.prefix + "ag", ctx.prefix + "autogollark", ctx.prefix + "gollark" ]
@@ -52,11 +54,19 @@ class Sentience(commands.Cog):
@commands.command(help="Highly advanced AI Assistant.") @commands.command(help="Highly advanced AI Assistant.")
async def ai(self, ctx, *, query=None): async def ai(self, ctx, *, query=None):
if timeout := self.timeouts.get(ctx.channel.id):
if timeout > datetime.now():
return
prompt = await self.serialize_history(ctx) prompt = await self.serialize_history(ctx)
prompt.append(f'[{util.render_time(datetime.utcnow())}] {util.config["ai"]["own_name"]}:') prompt.append(f'[{util.render_time(datetime.now(timezone.utc))}] {util.config["ai"]["own_name"]}:')
generation = await util.generate(self.session, util.config["ai"]["prompt_start"] + "".join(prompt)) generation = await util.generate(self.session, util.config["ai"]["prompt_start"] + "".join(prompt))
if generation.strip(): assert generation, "backend failed"
await ctx.send(generation.strip()) generation = generation.strip()
if generation:
await ctx.send(generation)
if generation.endswith("/quit"):
await ctx.send("Disconnecting AI as requested.")
self.timeouts[ctx.channel.id] = datetime.now() + timedelta(seconds=1200)
@commands.command(help="Search meme library.", aliases=["memes"]) @commands.command(help="Search meme library.", aliases=["memes"])
async def meme(self, ctx, *, query=None): async def meme(self, ctx, *, query=None):
@@ -74,5 +84,46 @@ class Sentience(commands.Cog):
o_files = [ discord.File(Path(util.config["memetics"]["memes_local"]) / util.meme_thumbnail(results, m)) for m in mat ] o_files = [ discord.File(Path(util.config["memetics"]["memes_local"]) / util.meme_thumbnail(results, m)) for m in mat ]
await ctx.send(files=o_files) await ctx.send(files=o_files)
def setup(bot): async def spontaneous_praise(self, target, delay):
bot.add_cog(Sentience(bot)) await asyncio.sleep(delay)
del self.autopraise_spontaneous_times[target["user"]]
await self.praise(target, target["spontaneous_channel"], util.config["autopraise"]["spontaneous_prompt"])
async def praise(self, target, channel, prompt):
chan = self.bot.get_channel(channel)
if chan:
context = "\n".join(self.praise_context_buffers[target["user"]])
praise_message = await util.generate_raw_chatcompletion(self.session, util.config["ai"]["chat_completions"], prompt + "\n" + context)
praise_message = praise_message.strip()
if praise_message and praise_message != util.config["autopraise"]["no_praise"]:
await chan.send(praise_message)
else:
# if no praise occurred, reset the timer
del self.autopraise_triggered_times[target["user"]]
@commands.Cog.listener("on_message")
async def auto_praise(self, msg):
now = util.timestamp()
# if anyone uses this, rearrange to dict users → spec, for efficiency
for target in util.config["autopraise"]["targets"]:
if target["guild"] == msg.guild.id and target["user"] == msg.author.id:
if msg.channel.id in target["channels"]:
if msg.content and msg.content.strip(): self.praise_context_buffers[msg.author.id].append(f"{msg.author.name}: {msg.content.strip()}")
if len(self.praise_context_buffers[msg.author.id]) >= target["context_length"]:
self.praise_context_buffers[msg.author.id].popleft()
# no spontaneous praise event within window: dispatch
if msg.author.id not in self.autopraise_spontaneous_times:
logging.info("Scheduling spontaneous praise for %d", msg.author.id)
spontaneous_praise_delay = random.expovariate(target["spontaneous_interval"] / 2) + target["spontaneous_interval"] / 2
self.autopraise_spontaneous_times[msg.author.id] = now + spontaneous_praise_delay
asyncio.create_task(self.spontaneous_praise(target, spontaneous_praise_delay))
may_praise_at = self.autopraise_triggered_times.get(msg.author.id)
if may_praise_at is None or may_praise_at < now:
logging.info("Triggered praise event for %d", msg.author.id)
self.autopraise_triggered_times[msg.author.id] = now + target["triggered_interval"]
await self.praise(target, msg.channel.id, util.config["autopraise"]["triggered_prompt"])
async def setup(bot):
await bot.add_cog(Sentience(bot))

View File

@@ -135,7 +135,7 @@ class Telephone(commands.Cog):
reply = (eventbus.AuthorInfo(replying_to.author.name, replying_to.author.id, str(replying_to.author.display_avatar.url), replying_to.author.bot), parse_formatting(self.bot, replying_to.content)) reply = (eventbus.AuthorInfo(replying_to.author.name, replying_to.author.id, str(replying_to.author.display_avatar.url), replying_to.author.bot), parse_formatting(self.bot, replying_to.content))
else: else:
reply = (None, None) reply = (None, None)
msg = eventbus.Message(eventbus.AuthorInfo(msg.author.name, msg.author.id, str(msg.author.display_avatar.url), msg.author.bot), msg = eventbus.Message(eventbus.AuthorInfo(msg.author.name, msg.author.id, str(msg.author.display_avatar.url), msg.author.bot),
parse_formatting(self.bot, msg.content), ("discord", channel_id), msg.id, [ at for at in msg.attachments if not at.is_spoiler() ], reply=reply) parse_formatting(self.bot, msg.content), ("discord", channel_id), msg.id, [ at for at in msg.attachments if not at.is_spoiler() ], reply=reply)
await eventbus.push(msg) await eventbus.push(msg)
@@ -315,7 +315,7 @@ When you want to end a call, use hangup.
return await ctx.send(embed=util.error_embed("Target channel no longer exists.")) return await ctx.send(embed=util.error_embed("Target channel no longer exists."))
_, call_message = await asyncio.gather( _, call_message = await asyncio.gather(
ctx.send(embed=util.info_embed("Outgoing call", f"Dialing {address}...")), ctx.send(embed=util.info_embed("Outgoing call", f"Dialing {address}...")),
recv_channel.send(embed=util.info_embed("Incoming call", recv_channel.send(embed=util.info_embed("Incoming call",
f"Call from {originating_address}. Click :white_check_mark: to accept or :negative_squared_cross_mark: to decline.")) f"Call from {originating_address}. Click :white_check_mark: to accept or :negative_squared_cross_mark: to decline."))
) )
# add clickable reactions to it # add clickable reactions to it
@@ -429,7 +429,7 @@ When you want to end a call, use hangup.
finally: finally:
os.unlink(tmppath) os.unlink(tmppath)
def setup(bot): async def setup(bot):
cog = Telephone(bot) cog = Telephone(bot)
bot.add_cog(cog) await bot.add_cog(cog)
asyncio.create_task(cog.initial_load_webhooks()) asyncio.create_task(cog.initial_load_webhooks())

View File

@@ -87,5 +87,5 @@ class Userdata(commands.Cog):
await self.bot.database.commit() await self.bot.database.commit()
await ctx.send(f"**{key}** deleted") await ctx.send(f"**{key}** deleted")
def setup(bot): async def setup(bot):
bot.add_cog(Userdata(bot)) await bot.add_cog(Userdata(bot))

View File

@@ -378,7 +378,8 @@ async def generate(sess: aiohttp.ClientSession, prompt, stop=["\n"]):
# high to low # high to low
def sort_key(backend): def sort_key(backend):
failure_stats = last_failures[backend["url"]] failure_stats = last_failures[backend["url"]]
return (failure_stats.avoid_until is None or failure_stats.avoid_until < now), backend["priority"], -failure_stats.consecutive_failures currently_ok = failure_stats.avoid_until is None or failure_stats.avoid_until < now
return currently_ok, -(not currently_ok and failure_stats.consecutive_failures), backend["priority"]
backends = sorted(backends, key=sort_key, reverse=True) backends = sorted(backends, key=sort_key, reverse=True)
@@ -396,6 +397,16 @@ async def generate(sess: aiohttp.ClientSession, prompt, stop=["\n"]):
failure_stats.avoid_until = now + datetime.timedelta(seconds=2 ** failure_stats.consecutive_failures) failure_stats.avoid_until = now + datetime.timedelta(seconds=2 ** failure_stats.consecutive_failures)
failure_stats.consecutive_failures += 1 failure_stats.consecutive_failures += 1
async def generate_raw_chatcompletion(sess: aiohttp.ClientSession, backend, prompt):
async with sess.post(backend["url"], json={
"messages": [{"role": "user", "content": prompt}],
"client": "abr",
**backend.get("params", {})
}, headers=backend.get("headers", {}), timeout=aiohttp.ClientTimeout(total=30)) as res:
data = await res.json()
print(data)
return data["choices"][0]["message"]["content"]
filesafe_charset = string.ascii_letters + string.digits + "-" filesafe_charset = string.ascii_letters + string.digits + "-"
TARGET_FORMAT = "jpegh" TARGET_FORMAT = "jpegh"

View File

@@ -16,7 +16,7 @@ class HTTPSource(discord.AudioSource):
def read(self): return next(self.packets, b"") def read(self): return next(self.packets, b"")
def is_opus(self): return True def is_opus(self): return True
def setup(bot): async def setup(bot):
# experimental, thus limit to me only # experimental, thus limit to me only
@bot.group() @bot.group()
@commands.check(util.admin_check) @commands.check(util.admin_check)
@@ -43,11 +43,11 @@ def setup(bot):
@radio.command() @radio.command()
async def disconnect(ctx): async def disconnect(ctx):
if ctx.guild.voice_client: if ctx.guild.voice_client:
ctx.guild.voice_client.stop() ctx.guild.voice_client.stop()
await ctx.guild.voice_client.disconnect() await ctx.guild.voice_client.disconnect()
def teardown(bot): async def teardown(bot):
for guild in bot.guilds: for guild in bot.guilds:
if guild.voice_client: if guild.voice_client:
guild.voice_client.stop() guild.voice_client.stop()