autobotrobot/src/userdata.py

91 lines
4.4 KiB
Python

import util
import discord.ext.commands as commands
def check_key(key):
if len(key) > 128: raise ValueError("Key too long")
def preprocess_value(value):
value = value.strip()
if len(value) > 1024: raise ValueError("Value too long")
return value
class Userdata(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.group(name="userdata", aliases=["data"], help="""Store per-user data AND retrieve it later! Note that, due to the nature of storing things, it is necessary to set userdata before getting it.
Data can either be localized to a guild (guild scope) or shared between guilds (global scope), but is always tied to a user.""")
async def userdata(self, ctx): pass
async def get_userdata(self, user, guild, key):
return (await self.bot.database.execute_fetchone("SELECT * FROM user_data WHERE user_id = ? AND guild_id = ? AND key = ?", (user, guild, key))
or await self.bot.database.execute_fetchone("SELECT * FROM user_data WHERE user_id = ? AND guild_id = '_global' AND key = ?", (user, key)))
async def set_userdata(self, user, guild, key, value):
await self.bot.database.execute("INSERT OR REPLACE INTO user_data VALUES (?, ?, ?, ?)", (user, guild, key, value))
await self.bot.database.commit()
@userdata.command(help="Get a userdata key. Checks guild first, then global.")
async def get(self, ctx, *, key):
row = await self.get_userdata(ctx.author.id, ctx.guild and ctx.guild.id, key)
if not row:
raise ValueError("No such key")
await ctx.send(row["value"])
@userdata.command(name="list", brief="List userdata keys in a given scope matching a query.")
async def list_cmd(self, ctx, query="%", scope="guild", show_values: bool = False):
"List userdata keys in a given scope (guild/global) matching your query (LIKE syntax). Can also show the associated values."
if scope == "global":
rows = await self.bot.database.execute_fetchall("SELECT * FROM user_data WHERE user_id = ? AND guild_id = '_global' AND key LIKE ?", (ctx.author.id, query))
else:
rows = await self.bot.database.execute_fetchall("SELECT * FROM user_data WHERE user_id = ? AND guild_id = ? AND key LIKE ?", (ctx.author.id, ctx.guild and ctx.guild.id, query))
out = []
for row in rows:
if show_values:
out.append(f"**{row['key']}**: {row['value']}")
else:
out.append(row["key"])
if len(out) == 0: return await ctx.send("No data")
await ctx.send(("\n" if show_values else ", ").join(out)[:2000]) # TODO: split better
@userdata.command(name="set", help="Set a userdata key in the guild scope.")
async def set_cmd(self, ctx, key, *, value):
check_key(key)
value = preprocess_value(value)
await self.set_userdata(ctx.author.id, ctx.guild and ctx.guild.id, key, value)
await ctx.send(f"**{key}** set (scope guild)")
@userdata.command(help="Set a userdata key in the global scope.")
async def set_global(self, ctx, key, *, value):
check_key(key)
value = preprocess_value(value)
await self.set_userdata(ctx.author.id, "_global", key, value)
await ctx.send(f"**{key}** set (scope global)")
@userdata.command()
async def inc(self, ctx, key, by: int = 1):
"Increase the integer value of a userdata key."
check_key(key)
row = await self.get_userdata(ctx.author.id, ctx.guild.id, key)
if not row:
value = 0
guild = ctx.guild and ctx.guild.id
else:
value = int(row["value"])
guild = row["guild_id"]
new_value = value + by
await self.set_userdata(ctx.author.id, guild, key, preprocess_value(str(new_value)))
await ctx.send(f"**{key}** set to {new_value}")
@userdata.command()
async def delete(self, ctx, *keys):
"Delete the specified keys (smallest scope first)."
for key in keys:
row = await self.get_userdata(ctx.author.id, ctx.guild and ctx.guild.id, key)
if not row:
return await ctx.send(embed=util.error_embed(f"No such key {key}"))
await self.bot.database.execute("DELETE FROM user_data WHERE user_id = ? AND guild_id = ? AND key = ?", (ctx.author.id, row["guild_id"], key))
await self.bot.database.commit()
await ctx.send(f"**{key}** deleted")
def setup(bot):
bot.add_cog(Userdata(bot))