rewrite: Initial rewrite skeleton.

Remove modules that will no longer be required.
Move pending modules to pending-rewrite folders.
This commit is contained in:
2022-09-17 17:06:13 +10:00
parent a7f7dd6e7b
commit a5147323b5
162 changed files with 1 additions and 866 deletions

View File

@@ -1,7 +0,0 @@
from .module import module
from . import exec_cmds
from . import guild_log
from . import status
from . import blacklist
from . import botconfig

View File

@@ -1,319 +0,0 @@
"""
System admin submodule providing an interface for managing the globally blacklisted guilds and users.
NOTE: Not shard-safe, and will not update across shards.
"""
import discord
from cmdClient.checks import is_owner
from cmdClient.lib import ResponseTimedOut
from meta.sharding import sharded
from .module import module
@module.cmd(
"guildblacklist",
desc="View/add/remove blacklisted guilds.",
group="Bot Admin",
flags=('remove',)
)
@is_owner()
async def cmd_guildblacklist(ctx, flags):
"""
Usage``:
{prefix}guildblacklist
{prefix}guildblacklist guildid, guildid, guildid
{prefix}guildblacklist --remove guildid, guildid, guildid
Description:
View, add, or remove guilds from the blacklist.
"""
blacklist = ctx.client.guild_blacklist()
if ctx.args:
# guildid parsing
items = [item.strip() for item in ctx.args.split(',')]
if any(not item.isdigit() for item in items):
return await ctx.error_reply(
"Please provide guilds as comma separated guild ids."
)
guildids = set(int(item) for item in items)
if flags['remove']:
# Handle removing from the blacklist
# First make sure that all the guildids are in the blacklist
difference = [guildid for guildid in guildids if guildid not in blacklist]
if difference:
return await ctx.error_reply(
"The following guildids are not in the blacklist! No guilds were removed.\n`{}`".format(
'`, `'.join(str(guildid) for guildid in difference)
)
)
# Remove the guilds from the data blacklist
ctx.client.data.global_guild_blacklist.delete_where(
guildid=list(guildids)
)
# Ack removal
await ctx.embed_reply(
"You have removed the following guilds from the guild blacklist.\n`{}`".format(
"`, `".join(str(guildid) for guildid in guildids)
)
)
else:
# Handle adding to the blacklist
to_add = [guildid for guildid in guildids if guildid not in blacklist]
if not to_add:
return await ctx.error_reply(
"All of the provided guilds are already blacklisted!"
)
# Prompt for reason
try:
reason = await ctx.input("Please enter the reasons these guild(s) are being blacklisted:")
except ResponseTimedOut:
raise ResponseTimedOut("Reason prompt timed out, no guilds were blacklisted.")
# Add to the blacklist
ctx.client.data.global_guild_blacklist.insert_many(
*((guildid, ctx.author.id, reason) for guildid in to_add),
insert_keys=('guildid', 'ownerid', 'reason')
)
# Leave freshly blacklisted guilds, accounting for shards
to_leave = []
for guildid in to_add:
guild = ctx.client.get_guild(guildid)
if not guild and sharded:
try:
guild = await ctx.client.fetch_guild(guildid)
except discord.HTTPException:
pass
if guild:
to_leave.append(guild)
for guild in to_leave:
await guild.leave()
if to_leave:
left_str = "\nConsequently left the following guild(s):\n**{}**".format(
'**\n**'.join(guild.name for guild in to_leave)
)
else:
left_str = ""
# Ack the addition
await ctx.embed_reply(
"Added the following guild(s) to the blacklist:\n`{}`\n{}".format(
'`, `'.join(str(guildid) for guildid in to_add),
left_str
)
)
# Refresh the cached blacklist after modification
ctx.client.guild_blacklist.cache_clear()
ctx.client.guild_blacklist()
else:
# Display the current blacklist
# First fetch the full blacklist data
rows = ctx.client.data.global_guild_blacklist.select_where()
if not rows:
await ctx.reply("There are no blacklisted guilds!")
else:
# Text blocks for each blacklisted guild
lines = [
"`{}` blacklisted by <@{}> at <t:{:.0f}>\n**Reason:** {}".format(
row['guildid'],
row['ownerid'],
row['created_at'].timestamp(),
row['reason']
) for row in sorted(rows, key=lambda row: row['created_at'].timestamp(), reverse=True)
]
# Split lines across pages
blocks = []
block_len = 0
block_lines = []
i = 0
while i < len(lines):
line = lines[i]
line_len = len(line)
if block_len + line_len > 2000:
if block_lines:
# Flush block, run line again on next page
blocks.append('\n'.join(block_lines))
block_lines = []
block_len = 0
else:
# Too long for the block, but empty block!
# Truncate
blocks.append(line[:2000])
i += 1
else:
block_lines.append(line)
i += 1
if block_lines:
# Flush block
blocks.append('\n'.join(block_lines))
# Build embed pages
pages = [
discord.Embed(
title="Blacklisted Guilds",
description=block,
colour=discord.Colour.orange()
) for block in blocks
]
page_count = len(blocks)
if page_count > 1:
for i, page in enumerate(pages):
page.set_footer(text="Page {}/{}".format(i + 1, page_count))
# Finally, post
await ctx.pager(pages)
@module.cmd(
"userblacklist",
desc="View/add/remove blacklisted users.",
group="Bot Admin",
flags=('remove',)
)
@is_owner()
async def cmd_userblacklist(ctx, flags):
"""
Usage``:
{prefix}userblacklist
{prefix}userblacklist userid, userid, userid
{prefix}userblacklist --remove userid, userid, userid
Description:
View, add, or remove users from the blacklist.
"""
blacklist = ctx.client.user_blacklist()
if ctx.args:
# userid parsing
items = [item.strip('<@!&> ') for item in ctx.args.split(',')]
if any(not item.isdigit() for item in items):
return await ctx.error_reply(
"Please provide users as comma seprated user ids or mentions."
)
userids = set(int(item) for item in items)
if flags['remove']:
# Handle removing from the blacklist
# First make sure that all the userids are in the blacklist
difference = [userid for userid in userids if userid not in blacklist]
if difference:
return await ctx.error_reply(
"The following userids are not in the blacklist! No users were removed.\n`{}`".format(
'`, `'.join(str(userid) for userid in difference)
)
)
# Remove the users from the data blacklist
ctx.client.data.global_user_blacklist.delete_where(
userid=list(userids)
)
# Ack removal
await ctx.embed_reply(
"You have removed the following users from the user blacklist.\n{}".format(
", ".join('<@{}>'.format(userid) for userid in userids)
)
)
else:
# Handle adding to the blacklist
to_add = [userid for userid in userids if userid not in blacklist]
if not to_add:
return await ctx.error_reply(
"All of the provided users are already blacklisted!"
)
# Prompt for reason
try:
reason = await ctx.input("Please enter the reasons these user(s) are being blacklisted:")
except ResponseTimedOut:
raise ResponseTimedOut("Reason prompt timed out, no users were blacklisted.")
# Add to the blacklist
ctx.client.data.global_user_blacklist.insert_many(
*((userid, ctx.author.id, reason) for userid in to_add),
insert_keys=('userid', 'ownerid', 'reason')
)
# Ack the addition
await ctx.embed_reply(
"Added the following user(s) to the blacklist:\n{}".format(
', '.join('<@{}>'.format(userid) for userid in to_add)
)
)
# Refresh the cached blacklist after modification
ctx.client.user_blacklist.cache_clear()
ctx.client.user_blacklist()
else:
# Display the current blacklist
# First fetch the full blacklist data
rows = ctx.client.data.global_user_blacklist.select_where()
if not rows:
await ctx.reply("There are no blacklisted users!")
else:
# Text blocks for each blacklisted user
lines = [
"<@{}> blacklisted by <@{}> at <t:{:.0f}>\n**Reason:** {}".format(
row['userid'],
row['ownerid'],
row['created_at'].timestamp(),
row['reason']
) for row in sorted(rows, key=lambda row: row['created_at'].timestamp(), reverse=True)
]
# Split lines across pages
blocks = []
block_len = 0
block_lines = []
i = 0
while i < len(lines):
line = lines[i]
line_len = len(line)
if block_len + line_len > 2000:
if block_lines:
# Flush block, run line again on next page
blocks.append('\n'.join(block_lines))
block_lines = []
block_len = 0
else:
# Too long for the block, but empty block!
# Truncate
blocks.append(line[:2000])
i += 1
else:
block_lines.append(line)
i += 1
if block_lines:
# Flush block
blocks.append('\n'.join(block_lines))
# Build embed pages
pages = [
discord.Embed(
title="Blacklisted Users",
description=block,
colour=discord.Colour.orange()
) for block in blocks
]
page_count = len(blocks)
if page_count > 1:
for i, page in enumerate(pages):
page.set_footer(text="Page {}/{}".format(i + 1, page_count))
# Finally, post
await ctx.pager(pages)

View File

@@ -1,96 +0,0 @@
import difflib
import discord
from cmdClient.checks import is_owner
from settings import UserInputError
from utils.lib import prop_tabulate
from .module import module
@module.cmd("botconfig",
desc="Update global bot configuration.",
flags=('add', 'remove'),
group="Bot Admin")
@is_owner()
async def cmd_botconfig(ctx, flags):
"""
Usage``
{prefix}botconfig
{prefix}botconfig info
{prefix}botconfig <setting>
{prefix}botconfig <setting> <value>
Description:
Usage directly follows the `config` command for guild configuration.
"""
# Cache and map some info for faster access
setting_displaynames = {setting.display_name.lower(): setting for setting in ctx.client.settings.settings.values()}
appid = ctx.client.conf['data_appid']
if not ctx.args or ctx.args.lower() in ('info', 'help'):
# Fill the setting cats
cats = {}
for setting in ctx.client.settings.settings.values():
cat = cats.get(setting.category, [])
cat.append(setting)
cats[setting.category] = cat
# Format the cats
sections = {}
for catname, cat in cats.items():
catprops = {
setting.display_name: setting.get(appid).summary if not ctx.args else setting.desc
for setting in cat
}
# TODO: Add cat description here
sections[catname] = prop_tabulate(*zip(*catprops.items()))
# Build the cat page
embed = discord.Embed(
colour=discord.Colour.orange(),
title="App Configuration"
)
for name, section in sections.items():
embed.add_field(name=name, value=section, inline=False)
await ctx.reply(embed=embed)
else:
# Some args were given
parts = ctx.args.split(maxsplit=1)
name = parts[0]
setting = setting_displaynames.get(name.lower(), None)
if setting is None:
matches = difflib.get_close_matches(name, setting_displaynames.keys(), n=2)
match = "`{}`".format('` or `'.join(matches)) if matches else None
return await ctx.error_reply(
"Couldn't find a setting called `{}`!\n"
"{}"
"Use `{}botconfig info` to see all the available settings.".format(
name,
"Maybe you meant {}?\n".format(match) if match else "",
ctx.best_prefix
)
)
if len(parts) == 1 and not ctx.msg.attachments:
# config <setting>
# View config embed for provided setting
await setting.get(appid).widget(ctx, flags=flags)
else:
# config <setting> <value>
# Attempt to set config setting
try:
parsed = await setting.parse(appid, ctx, parts[1] if len(parts) > 1 else '')
parsed.write(add_only=flags['add'], remove_only=flags['remove'])
except UserInputError as e:
await ctx.reply(embed=discord.Embed(
description="{} {}".format('', e.msg),
colour=discord.Colour.red()
))
else:
await ctx.reply(embed=discord.Embed(
description="{} {}".format('', setting.get(appid).success_response),
colour=discord.Colour.green()
))

View File

@@ -1,135 +0,0 @@
import sys
from io import StringIO
import traceback
import asyncio
from cmdClient import cmd, checks
from core import Lion
from LionModule import LionModule
"""
Exec level commands to manage the bot.
Commands provided:
async:
Executes provided code in an async executor
eval:
Executes code and awaits it if required
"""
@cmd("shutdown",
desc="Sync data and shutdown.",
group="Bot Admin",
aliases=('restart', 'reboot'))
@checks.is_owner()
async def cmd_shutdown(ctx):
"""
Usage``:
reboot
Description:
Run unload tasks and shutdown/reboot.
"""
# Run module logout tasks
for module in ctx.client.modules:
if isinstance(module, LionModule):
await module.unload(ctx.client)
# Reply and logout
await ctx.reply("All modules synced. Shutting down!")
await ctx.client.close()
@cmd("async",
desc="Execute arbitrary code with `async`.",
group="Bot Admin")
@checks.is_owner()
async def cmd_async(ctx):
"""
Usage:
{prefix}async <code>
Description:
Runs <code> as an asynchronous coroutine and prints the output or error.
"""
if ctx.arg_str == "":
await ctx.error_reply("You must give me something to run!")
return
output, error = await _async(ctx)
await ctx.reply(
"**Async input:**\
\n```py\n{}\n```\
\n**Output {}:** \
\n```py\n{}\n```".format(ctx.arg_str,
"error" if error else "",
output))
@cmd("eval",
desc="Execute arbitrary code with `eval`.",
group="Bot Admin")
@checks.is_owner()
async def cmd_eval(ctx):
"""
Usage:
{prefix}eval <code>
Description:
Runs <code> in current environment using eval() and prints the output or error.
"""
if ctx.arg_str == "":
await ctx.error_reply("You must give me something to run!")
return
output, error = await _eval(ctx)
await ctx.reply(
"**Eval input:**\
\n```py\n{}\n```\
\n**Output {}:** \
\n```py\n{}\n```".format(ctx.arg_str,
"error" if error else "",
output)
)
async def _eval(ctx):
output = None
try:
output = eval(ctx.arg_str)
except Exception:
return (str(traceback.format_exc()), 1)
if asyncio.iscoroutine(output):
output = await output
return (output, 0)
async def _async(ctx):
env = {
'ctx': ctx,
'client': ctx.client,
'message': ctx.msg,
'arg_str': ctx.arg_str
}
env.update(globals())
old_stdout = sys.stdout
redirected_output = sys.stdout = StringIO()
result = None
exec_string = "async def _temp_exec():\n"
exec_string += '\n'.join(' ' * 4 + line for line in ctx.arg_str.split('\n'))
try:
exec(exec_string, env)
result = (redirected_output.getvalue(), 0)
except Exception:
result = (str(traceback.format_exc()), 1)
return result
_temp_exec = env['_temp_exec']
try:
returnval = await _temp_exec()
value = redirected_output.getvalue()
if returnval is None:
result = (value, 0)
else:
result = (value + '\n' + str(returnval), 0)
except Exception:
result = (str(traceback.format_exc()), 1)
finally:
sys.stdout = old_stdout
return result

View File

@@ -1,83 +0,0 @@
import datetime
import discord
from meta import client, conf
from utils.lib import mail
@client.add_after_event("guild_remove")
async def log_left_guild(client, guild):
# Build embed
embed = discord.Embed(title="`{0.name} (ID: {0.id})`".format(guild),
colour=discord.Colour.red(),
timestamp=datetime.datetime.utcnow())
embed.set_author(name="Left guild!")
embed.set_thumbnail(url=guild.icon_url)
# Add more specific information about the guild
embed.add_field(name="Owner", value="{0.name} (ID: {0.id})".format(guild.owner), inline=False)
embed.add_field(name="Members (cached)", value="{}".format(len(guild.members)), inline=False)
embed.add_field(name="Now studying in", value="{} guilds".format(len(client.guilds)), inline=False)
# Retrieve the guild log channel and log the event
log_chid = conf.bot.get("guild_log_channel")
if log_chid:
await mail(client, log_chid, embed=embed)
@client.add_after_event("guild_join")
async def log_joined_guild(client, guild):
owner = guild.owner
icon = guild.icon_url
bots = 0
known = 0
unknown = 0
other_members = set(mem.id for mem in client.get_all_members() if mem.guild != guild)
for member in guild.members:
if member.bot:
bots += 1
elif member.id in other_members:
known += 1
else:
unknown += 1
mem1 = "people I know" if known != 1 else "person I know"
mem2 = "new friends" if unknown != 1 else "new friend"
mem3 = "bots" if bots != 1 else "bot"
mem4 = "total members"
known = "`{}`".format(known)
unknown = "`{}`".format(unknown)
bots = "`{}`".format(bots)
total = "`{}`".format(guild.member_count)
mem_str = "{0:<5}\t{4},\n{1:<5}\t{5},\n{2:<5}\t{6}, and\n{3:<5}\t{7}.".format(
known,
unknown,
bots,
total,
mem1,
mem2,
mem3,
mem4
)
region = str(guild.region)
created = "<t:{}>".format(int(guild.created_at.timestamp()))
embed = discord.Embed(
title="`{0.name} (ID: {0.id})`".format(guild),
colour=discord.Colour.green(),
timestamp=datetime.datetime.utcnow()
)
embed.set_author(name="Joined guild!")
embed.add_field(name="Owner", value="{0} (ID: {0.id})".format(owner), inline=False)
embed.add_field(name="Region", value=region, inline=False)
embed.add_field(name="Created at", value=created, inline=False)
embed.add_field(name="Members", value=mem_str, inline=False)
embed.add_field(name="Now studying in", value="{} guilds".format(len(client.guilds)), inline=False)
# Retrieve the guild log channel and log the event
log_chid = conf.bot.get("guild_log_channel")
if log_chid:
await mail(client, log_chid, embed=embed)

View File

@@ -1,3 +0,0 @@
from LionModule import LionModule
module = LionModule("Bot_Admin")

View File

@@ -1,53 +0,0 @@
import time
import asyncio
import discord
from meta import client
from .module import module
_last_update = 0
async def update_status():
# TODO: Make globally configurable and saveable
global _last_update
if time.time() - _last_update < 60:
return
_last_update = time.time()
student_count, room_count = client.data.current_sessions.select_one_where(
select_columns=("COUNT(*) AS studying_count", "COUNT(DISTINCT(channelid)) AS channel_count"),
)
status = "{} students in {} study rooms!".format(student_count, room_count)
await client.change_presence(
activity=discord.Activity(
type=discord.ActivityType.watching,
name=status
)
)
@client.add_after_event("voice_state_update")
async def trigger_status_update(client, member, before, after):
if before.channel != after.channel:
await update_status()
async def _status_loop():
while not client.is_ready():
await asyncio.sleep(5)
while True:
try:
await update_status()
except discord.HTTPException:
pass
await asyncio.sleep(300)
@module.launch_task
async def launch_status_update(client):
asyncio.create_task(_status_loop())