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

@@ -0,0 +1,7 @@
# flake8: noqa
from .module import module
from . import help
from . import links
from . import nerd
from . import join_message

View File

@@ -0,0 +1,237 @@
import discord
from cmdClient.checks import is_owner
from utils.lib import prop_tabulate
from utils import interactive, ctx_addons # noqa
from wards import is_guild_admin
from .module import module
from .lib import guide_link
new_emoji = " 🆕"
new_commands = {'botconfig', 'sponsors'}
# Set the command groups to appear in the help
group_hints = {
'Pomodoro': "*Stay in sync with your friends using our timers!*",
'Productivity': "*Use these to help you stay focused and productive!*",
'Statistics': "*StudyLion leaderboards and study statistics.*",
'Economy': "*Buy, sell, and trade with your hard-earned coins!*",
'Personal Settings': "*Tell me about yourself!*",
'Guild Admin': "*Dangerous administration commands!*",
'Guild Configuration': "*Control how I behave in your server.*",
'Meta': "*Information about me!*",
'Support Us': "*Support the team and keep the project alive by using LionGems!*"
}
standard_group_order = (
('Pomodoro', 'Productivity', 'Support Us', 'Statistics', 'Economy', 'Personal Settings', 'Meta'),
)
mod_group_order = (
('Moderation', 'Meta'),
('Pomodoro', 'Productivity', 'Support Us', 'Statistics', 'Economy', 'Personal Settings')
)
admin_group_order = (
('Guild Admin', 'Guild Configuration', 'Moderation', 'Meta'),
('Pomodoro', 'Productivity', 'Support Us', 'Statistics', 'Economy', 'Personal Settings')
)
bot_admin_group_order = (
('Bot Admin', 'Guild Admin', 'Guild Configuration', 'Moderation', 'Meta'),
('Pomodoro', 'Productivity', 'Support Us', 'Statistics', 'Economy', 'Personal Settings')
)
# Help embed format
# TODO: Add config fields for this
title = "StudyLion Command List"
header = """
[StudyLion](https://bot.studylions.com/) is a fully featured study assistant \
that tracks your study time and offers productivity tools \
such as to-do lists, task reminders, private study rooms, group accountability sessions, and much much more.\n
Use `{{ctx.best_prefix}}help <command>` (e.g. `{{ctx.best_prefix}}help send`) to learn how to use each command, \
or [click here]({guide_link}) for a comprehensive tutorial.
""".format(guide_link=guide_link)
@module.cmd("help",
group="Meta",
desc="StudyLion command list.",
aliases=('man', 'ls', 'list'))
async def cmd_help(ctx):
"""
Usage``:
{prefix}help [cmdname]
Description:
When used with no arguments, displays a list of commands with brief descriptions.
Otherwise, shows documentation for the provided command.
Examples:
{prefix}help
{prefix}help top
{prefix}help timezone
"""
if ctx.arg_str:
# Attempt to fetch the command
command = ctx.client.cmd_names.get(ctx.arg_str.strip(), None)
if command is None:
return await ctx.error_reply(
("Command `{}` not found!\n"
"Write `{}help` to see a list of commands.").format(ctx.args, ctx.best_prefix)
)
smart_help = getattr(command, 'smart_help', None)
if smart_help is not None:
return await smart_help(ctx)
help_fields = command.long_help.copy()
help_map = {field_name: i for i, (field_name, _) in enumerate(help_fields)}
if not help_map:
return await ctx.reply("No documentation has been written for this command yet!")
field_pages = [[]]
page_fields = field_pages[0]
for name, pos in help_map.items():
if name.endswith("``"):
# Handle codeline help fields
page_fields.append((
name.strip("`"),
"`{}`".format('`\n`'.join(help_fields[pos][1].splitlines()))
))
elif name.endswith(":"):
# Handle property/value help fields
lines = help_fields[pos][1].splitlines()
names = []
values = []
for line in lines:
split = line.split(":", 1)
names.append(split[0] if len(split) > 1 else "")
values.append(split[-1])
page_fields.append((
name.strip(':'),
prop_tabulate(names, values)
))
elif name == "Related":
# Handle the related field
names = [cmd_name.strip() for cmd_name in help_fields[pos][1].split(',')]
names.sort(key=len)
values = [
(getattr(ctx.client.cmd_names.get(cmd_name, None), 'desc', '') or '').format(ctx=ctx)
for cmd_name in names
]
page_fields.append((
name,
prop_tabulate(names, values)
))
elif name == "PAGEBREAK":
page_fields = []
field_pages.append(page_fields)
else:
page_fields.append((name, help_fields[pos][1]))
# Build the aliases
aliases = getattr(command, 'aliases', [])
alias_str = "(Aliases `{}`.)".format("`, `".join(aliases)) if aliases else ""
# Build the embeds
pages = []
for i, page_fields in enumerate(field_pages):
embed = discord.Embed(
title="`{}` command documentation. {}".format(
command.name,
alias_str
),
colour=discord.Colour(0x9b59b6)
)
for fieldname, fieldvalue in page_fields:
embed.add_field(
name=fieldname,
value=fieldvalue.format(ctx=ctx, prefix=ctx.best_prefix),
inline=False
)
embed.set_footer(
text="{}\n[optional] and <required> denote optional and required arguments, respectively.".format(
"Page {} of {}".format(i + 1, len(field_pages)) if len(field_pages) > 1 else '',
)
)
pages.append(embed)
# Post the embed
await ctx.pager(pages)
else:
# Build the command groups
cmd_groups = {}
for command in ctx.client.cmds:
# Get the command group
group = getattr(command, 'group', "Misc")
cmd_group = cmd_groups.get(group, [])
if not cmd_group:
cmd_groups[group] = cmd_group
# Add the command name and description to the group
cmd_group.append(
(command.name, (getattr(command, 'desc', '') + (new_emoji if command.name in new_commands else '')))
)
# Add any required aliases
for alias, desc in getattr(command, 'help_aliases', {}).items():
cmd_group.append((alias, desc))
# Turn the command groups into strings
stringy_cmd_groups = {}
for group_name, cmd_group in cmd_groups.items():
cmd_group.sort(key=lambda tup: len(tup[0]))
if ctx.alias == 'ls':
stringy_cmd_groups[group_name] = ', '.join(
f"`{name}`" for name, _ in cmd_group
)
else:
stringy_cmd_groups[group_name] = prop_tabulate(*zip(*cmd_group))
# Now put everything into a bunch of embeds
if await is_owner.run(ctx):
group_order = bot_admin_group_order
elif ctx.guild:
if is_guild_admin(ctx.author):
group_order = admin_group_order
elif ctx.guild_settings.mod_role.value in ctx.author.roles:
group_order = mod_group_order
else:
group_order = standard_group_order
else:
group_order = admin_group_order
help_embeds = []
for page_groups in group_order:
embed = discord.Embed(
description=header.format(ctx=ctx),
colour=discord.Colour(0x9b59b6),
title=title
)
for group in page_groups:
group_hint = group_hints.get(group, '').format(ctx=ctx)
group_str = stringy_cmd_groups.get(group, None)
if group_str:
embed.add_field(
name=group,
value="{}\n{}".format(group_hint, group_str).format(ctx=ctx),
inline=False
)
help_embeds.append(embed)
# Add the page numbers
for i, embed in enumerate(help_embeds):
embed.set_footer(text="Page {}/{}".format(i+1, len(help_embeds)))
# Send the embeds
if help_embeds:
await ctx.pager(help_embeds)
else:
await ctx.reply(
embed=discord.Embed(description=header, colour=discord.Colour(0x9b59b6))
)

View File

@@ -0,0 +1,50 @@
import discord
from cmdClient import cmdClient
from meta import client, conf
from .lib import guide_link, animation_link
message = """
Thank you for inviting me to your community.
Get started by typing `{prefix}help` to see my commands, and `{prefix}config info` \
to read about my configuration options!
To learn how to configure me and use all of my features, \
make sure to [click here]({guide_link}) to read our full setup guide.
Remember, if you need any help configuring me, \
want to suggest a feature, report a bug and stay updated, \
make sure to join our main support and study server by [clicking here]({support_link}).
Best of luck with your studies!
""".format(
guide_link=guide_link,
support_link=conf.bot.get('support_link'),
prefix=client.prefix
)
@client.add_after_event('guild_join', priority=0)
async def post_join_message(client: cmdClient, guild: discord.Guild):
try:
await guild.me.edit(nick="Leo")
except discord.HTTPException:
pass
if (channel := guild.system_channel) and channel.permissions_for(guild.me).embed_links:
embed = discord.Embed(
description=message
)
embed.set_author(
name="Hello everyone! My name is Leo, the StudyLion!",
icon_url="https://cdn.discordapp.com/emojis/933610591459872868.webp"
)
embed.set_image(url=animation_link)
try:
await channel.send(embed=embed)
except discord.HTTPException:
# Something went wrong sending the hi message
# Not much we can do about this
pass

View File

@@ -0,0 +1,5 @@
guide_link = "https://discord.studylions.com/tutorial"
animation_link = (
"https://media.discordapp.net/attachments/879412267731542047/926837189814419486/ezgif.com-resize.gif"
)

View File

@@ -0,0 +1,57 @@
import discord
from meta import conf
from LionContext import LionContext as Context
from .module import module
from .lib import guide_link
@module.cmd(
"support",
group="Meta",
desc=f"Have a question? Join my [support server]({conf.bot.get('support_link')})"
)
async def cmd_support(ctx: Context):
"""
Usage``:
{prefix}support
Description:
Replies with an invite link to my support server.
"""
await ctx.reply(
f"Click here to join my support server: {conf.bot.get('support_link')}"
)
@module.cmd(
"invite",
group="Meta",
desc=f"[Invite me]({conf.bot.get('invite_link')}) to your server so I can help your members stay productive!"
)
async def cmd_invite(ctx: Context):
"""
Usage``:
{prefix}invite
Description:
Replies with my invite link so you can add me to your server.
"""
embed = discord.Embed(
colour=discord.Colour.orange(),
description=f"Click here to add me to your server: {conf.bot.get('invite_link')}"
)
embed.add_field(
name="Setup tips",
value=(
"Remember to check out `{prefix}help` for the full command list, "
"and `{prefix}config info` for the configuration options.\n"
"[Click here]({guide}) for our comprehensive setup tutorial, and if you still have questions you can "
"join our support server [here]({support}) to talk to our friendly support team!"
).format(
prefix=ctx.best_prefix,
support=conf.bot.get('support_link'),
guide=guide_link
)
)
await ctx.reply(embed=embed)

View File

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

View File

@@ -0,0 +1,144 @@
import datetime
import asyncio
import discord
import psutil
import sys
import gc
from data import NOTNULL
from data.queries import select_where
from utils.lib import prop_tabulate, utc_now
from LionContext import LionContext as Context
from .module import module
process = psutil.Process()
process.cpu_percent()
@module.cmd(
"nerd",
group="Meta",
desc="Information and statistics about me!"
)
async def cmd_nerd(ctx: Context):
"""
Usage``:
{prefix}nerd
Description:
View nerdy information and statistics about me!
"""
# Create embed
embed = discord.Embed(
colour=discord.Colour.orange(),
title="Nerd Panel",
description=(
"Hi! I'm [StudyLion]({studylion}), a study management bot owned by "
"[Ari Horesh]({ari}) and developed by [Conatum#5317]({cona}), with [contributors]({github})."
).format(
studylion="http://studylions.com/",
ari="https://arihoresh.com/",
cona="https://github.com/Intery",
github="https://github.com/StudyLions/StudyLion"
)
)
# ----- Study stats -----
# Current studying statistics
current_students, current_channels, current_guilds= (
ctx.client.data.current_sessions.select_one_where(
select_columns=(
"COUNT(*) AS studying_count",
"COUNT(DISTINCT(channelid)) AS channel_count",
"COUNT(DISTINCT(guildid)) AS guild_count"
)
)
)
# Past studying statistics
past_sessions, past_students, past_duration, past_guilds = ctx.client.data.session_history.select_one_where(
select_columns=(
"COUNT(*) AS session_count",
"COUNT(DISTINCT(userid)) AS user_count",
"SUM(duration) / 3600 AS total_hours",
"COUNT(DISTINCT(guildid)) AS guild_count"
)
)
# Tasklist statistics
tasks = ctx.client.data.tasklist.select_one_where(
select_columns=(
'COUNT(*)'
)
)[0]
tasks_completed = ctx.client.data.tasklist.select_one_where(
completed_at=NOTNULL,
select_columns=(
'COUNT(*)'
)
)[0]
# Timers
timer_count, timer_guilds = ctx.client.data.timers.select_one_where(
select_columns=("COUNT(*)", "COUNT(DISTINCT(guildid))")
)
study_fields = {
"Currently": f"`{current_students}` people working in `{current_channels}` rooms of `{current_guilds}` guilds",
"Recorded": f"`{past_duration}` hours from `{past_students}` people across `{past_sessions}` sessions",
"Tasks": f"`{tasks_completed}` out of `{tasks}` tasks completed",
"Timers": f"`{timer_count}` timers running in `{timer_guilds}` communities"
}
study_table = prop_tabulate(*zip(*study_fields.items()))
# ----- Shard statistics -----
shard_number = ctx.client.shard_id
shard_count = ctx.client.shard_count
guilds = len(ctx.client.guilds)
member_count = sum(guild.member_count for guild in ctx.client.guilds)
commands = len(ctx.client.cmds)
aliases = len(ctx.client.cmd_names)
dpy_version = discord.__version__
py_version = sys.version.split()[0]
data_version, data_time, _ = select_where(
"VersionHistory",
_extra="ORDER BY time DESC LIMIT 1"
)[0]
data_timestamp = int(data_time.replace(tzinfo=datetime.timezone.utc).timestamp())
shard_fields = {
"Shard": f"`{shard_number}` of `{shard_count}`",
"Guilds": f"`{guilds}` servers with `{member_count}` members (on this shard)",
"Commands": f"`{commands}` commands with `{aliases}` keywords",
"Version": f"`v{data_version}`, last updated <t:{data_timestamp}:F>",
"Py version": f"`{py_version}` running discord.py `{dpy_version}`"
}
shard_table = prop_tabulate(*zip(*shard_fields.items()))
# ----- Execution statistics -----
running_commands = len(ctx.client.active_contexts)
tasks = len(asyncio.all_tasks())
objects = len(gc.get_objects())
cpu_percent = process.cpu_percent()
mem_percent = int(process.memory_percent())
uptime = int(utc_now().timestamp() - process.create_time())
execution_fields = {
"Running": f"`{running_commands}` commands",
"Waiting for": f"`{tasks}` tasks to complete",
"Objects": f"`{objects}` loaded in memory",
"Usage": f"`{cpu_percent}%` CPU, `{mem_percent}%` MEM",
"Uptime": f"`{uptime // (24 * 3600)}` days, `{uptime // 3600 % 24:02}:{uptime // 60 % 60:02}:{uptime % 60:02}`"
}
execution_table = prop_tabulate(*zip(*execution_fields.items()))
# ----- Combine and output -----
embed.add_field(name="Study Stats", value=study_table, inline=False)
embed.add_field(name=f"Shard Info", value=shard_table, inline=False)
embed.add_field(name=f"Process Stats", value=execution_table, inline=False)
await ctx.reply(embed=embed)