feature (setprofile): Profile tag editor.
This commit is contained in:
216
bot/modules/stats/setprofile.py
Normal file
216
bot/modules/stats/setprofile.py
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
"""
|
||||||
|
Provides a command to update a member's profile badges.
|
||||||
|
"""
|
||||||
|
import string
|
||||||
|
import discord
|
||||||
|
|
||||||
|
from cmdClient.lib import SafeCancellation
|
||||||
|
from cmdClient.checks import in_guild
|
||||||
|
from wards import guild_moderator
|
||||||
|
|
||||||
|
from .data import profile_tags
|
||||||
|
from .module import module
|
||||||
|
|
||||||
|
|
||||||
|
MAX_TAGS = 5
|
||||||
|
MAX_LENGTH = 30
|
||||||
|
|
||||||
|
|
||||||
|
@module.cmd(
|
||||||
|
"setprofile",
|
||||||
|
group="Personal Settings",
|
||||||
|
desc="Set or update your study profile tags.",
|
||||||
|
aliases=('editprofile', 'mytags'),
|
||||||
|
flags=('clear', 'for')
|
||||||
|
)
|
||||||
|
@in_guild()
|
||||||
|
async def cmd_setprofile(ctx, flags):
|
||||||
|
"""
|
||||||
|
Usage``:
|
||||||
|
{prefix}setprofile <tag>, <tag>, <tag>, ...
|
||||||
|
{prefix}setprofile <id> <new tag>
|
||||||
|
{prefix}setprofile --clear [--for @user]
|
||||||
|
Description:
|
||||||
|
Set or update the tags appearing in your study server profile.
|
||||||
|
|
||||||
|
You can have at most `5` tags at once.
|
||||||
|
|
||||||
|
Moderators can clear a user's tags with `--clear --for @user`.
|
||||||
|
Examples``:
|
||||||
|
{prefix}setprofile Mathematics, Bioloyg, Medicine, Undergraduate, Europe
|
||||||
|
{prefix}setprofile 2 Biology
|
||||||
|
{prefix}setprofile --clear
|
||||||
|
"""
|
||||||
|
if flags['clear']:
|
||||||
|
if flags['for']:
|
||||||
|
# Moderator-clearing a user's tags
|
||||||
|
# First check moderator permissions
|
||||||
|
if not await guild_moderator.run(ctx):
|
||||||
|
return await ctx.error_reply(
|
||||||
|
"You need to be a server moderator to use this!"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check input and extract users to clear for
|
||||||
|
if not (users := ctx.msg.mentions):
|
||||||
|
# Show moderator usage
|
||||||
|
return await ctx.error_reply(
|
||||||
|
f"**Usage:** `{ctx.best_prefix}setprofile --clear --for @user`\n"
|
||||||
|
f"**Example:** {ctx.best_prefix}setprofile --clear --for {ctx.author.mention}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clear the tags
|
||||||
|
profile_tags.delete_where(
|
||||||
|
guildid=ctx.guild.id,
|
||||||
|
userid=[user.id for user in users]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ack the moderator
|
||||||
|
await ctx.embed_reply(
|
||||||
|
"Profile tags cleared!"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# The author wants to clear their own tags
|
||||||
|
|
||||||
|
# First delete the tags, save the rows for reporting
|
||||||
|
rows = profile_tags.delete_where(
|
||||||
|
guildid=ctx.guild.id,
|
||||||
|
userid=ctx.author.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ack the user
|
||||||
|
if not rows:
|
||||||
|
await ctx.embed_reply(
|
||||||
|
"You don't have any profile tags to clear!"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
embed = discord.Embed(
|
||||||
|
colour=discord.Colour.green(),
|
||||||
|
description="Successfully cleared your profile!"
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name="Removed tags",
|
||||||
|
value='\n'.join(row['tag'].upper() for row in rows)
|
||||||
|
)
|
||||||
|
await ctx.reply(embed=embed)
|
||||||
|
elif ctx.args:
|
||||||
|
if len(splits := ctx.args.split(maxsplit=1)) > 1 and splits[0].isdigit():
|
||||||
|
# Assume we are editing the provided id
|
||||||
|
tagid = int(splits[0])
|
||||||
|
if tagid > MAX_TAGS:
|
||||||
|
return await ctx.error_reply(
|
||||||
|
f"Sorry, you can have a maximum of `{MAX_TAGS}` tags!"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Retrieve the user's current taglist
|
||||||
|
rows = profile_tags.select_where(
|
||||||
|
guildid=ctx.guild.id,
|
||||||
|
userid=ctx.author.id,
|
||||||
|
_extra="ORDER BY tagid ASC"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Parse and validate provided new content
|
||||||
|
content = splits[1].strip().upper()
|
||||||
|
validate_tag(content)
|
||||||
|
|
||||||
|
if tagid > len(rows):
|
||||||
|
# Trying to edit a tag that doesn't exist yet
|
||||||
|
# Just create it instead
|
||||||
|
profile_tags.insert(
|
||||||
|
guildid=ctx.guild.id,
|
||||||
|
userid=ctx.author.id,
|
||||||
|
tag=content
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ack user
|
||||||
|
await ctx.reply(
|
||||||
|
embed=discord.Embed(title="Tag created!", colour=discord.Colour.green())
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Get the row id to update
|
||||||
|
to_edit = rows[tagid - 1]['tagid']
|
||||||
|
|
||||||
|
# Update the tag
|
||||||
|
profile_tags.update_where(
|
||||||
|
{'tag': content},
|
||||||
|
tagid=to_edit
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ack user
|
||||||
|
embed = discord.Embed(
|
||||||
|
colour=discord.Colour.green(),
|
||||||
|
title="Tag updated!"
|
||||||
|
)
|
||||||
|
await ctx.reply(embed=embed)
|
||||||
|
else:
|
||||||
|
# Assume the arguments are a comma separated list of badges
|
||||||
|
# Parse and validate
|
||||||
|
to_add = [split.strip().upper() for split in ctx.args.split(',')]
|
||||||
|
validate_tag(*to_add)
|
||||||
|
|
||||||
|
# Remove the existing badges
|
||||||
|
deleted_rows = profile_tags.delete_where(
|
||||||
|
guildid=ctx.guild.id,
|
||||||
|
userid=ctx.author.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Insert the new tags
|
||||||
|
profile_tags.insert_many(
|
||||||
|
*((ctx.guild.id, ctx.author.id, tag) for tag in to_add),
|
||||||
|
insert_keys=('guildid', 'userid', 'tag')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ack with user
|
||||||
|
embed = discord.Embed(
|
||||||
|
colour=discord.Colour.green(),
|
||||||
|
description="Profile tags updated!"
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name="New tags",
|
||||||
|
value='\n'.join(to_add)
|
||||||
|
)
|
||||||
|
if deleted_rows:
|
||||||
|
embed.add_field(
|
||||||
|
name="Previous tags",
|
||||||
|
value='\n'.join(row['tag'].upper() for row in deleted_rows),
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
await ctx.reply(embed=embed)
|
||||||
|
else:
|
||||||
|
# No input was provided
|
||||||
|
# Show usage and exit
|
||||||
|
embed = discord.Embed(
|
||||||
|
colour=discord.Colour.red(),
|
||||||
|
description=(
|
||||||
|
"Use this command to edit your study profile "
|
||||||
|
"tags so other people can see what you do!"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name="Usage",
|
||||||
|
value=(
|
||||||
|
f"`{ctx.best_prefix}setprofile <tag>, <tag>, <tag>, ...`\n"
|
||||||
|
f"`{ctx.best_prefix}setprofile <id> <new tag>`"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name="Examples",
|
||||||
|
value=(
|
||||||
|
f"`{ctx.best_prefix}setprofile Mathematics, Bioloyg, Medicine, Undergraduate, Europe`\n"
|
||||||
|
f"`{ctx.best_prefix}setprofile 2 Biology`"
|
||||||
|
),
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
await ctx.reply(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_tag(*content):
|
||||||
|
for content in content:
|
||||||
|
if not set(content).issubset(string.printable):
|
||||||
|
raise SafeCancellation(
|
||||||
|
f"Invalid tag `{content}`!\n"
|
||||||
|
"Tags may only contain alphanumeric and punctuation characters."
|
||||||
|
)
|
||||||
|
if len(content) > MAX_LENGTH:
|
||||||
|
raise SafeCancellation(
|
||||||
|
f"Provided tag is too long! Please keep your tags shorter than {MAX_LENGTH} characters."
|
||||||
|
)
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
-- Add deletion column to tasklist entries
|
-- Improved tasklist statistics
|
||||||
-- Add completed_at column to the tasklist entries, replacing complete
|
|
||||||
|
|
||||||
|
|
||||||
ALTER TABLE tasklist
|
ALTER TABLE tasklist
|
||||||
ADD COLUMN completed_at TIMESTAMPTZ,
|
ADD COLUMN completed_at TIMESTAMPTZ,
|
||||||
ADD COLUMN deleted_at TIMESTAMPTZ,
|
ADD COLUMN deleted_at TIMESTAMPTZ,
|
||||||
@@ -15,4 +12,15 @@ ALTER TABLE tasklist
|
|||||||
DROP COLUMN complete;
|
DROP COLUMN complete;
|
||||||
|
|
||||||
|
|
||||||
-- Mark all tasklist entries older than a day as deleted
|
-- New member profile tags
|
||||||
|
CREATE TABLE member_profile_tags(
|
||||||
|
tagid SERIAL PRIMARY KEY,
|
||||||
|
guildid BIGINT NOT NULL,
|
||||||
|
userid BIGINT NOT NULL,
|
||||||
|
tag TEXT NOT NULL,
|
||||||
|
_timestamp TIMESTAMPTZ DEFAULT now(),
|
||||||
|
FOREIGN KEY (guildid, userid) REFERENCES members (guildid, userid)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO VersionHistory (version, author) VALUES (7, 'v6-v7 migration');
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ CREATE TABLE VersionHistory(
|
|||||||
time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
author TEXT
|
author TEXT
|
||||||
);
|
);
|
||||||
INSERT INTO VersionHistory (version, author) VALUES (6, 'Initial Creation');
|
INSERT INTO VersionHistory (version, author) VALUES (7, 'Initial Creation');
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION update_timestamp_column()
|
CREATE OR REPLACE FUNCTION update_timestamp_column()
|
||||||
@@ -683,4 +683,19 @@ CREATE TABLE past_member_roles(
|
|||||||
CREATE INDEX member_role_persistence_members ON past_member_roles (guildid, userid);
|
CREATE INDEX member_role_persistence_members ON past_member_roles (guildid, userid);
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
|
-- Member profile tags {{{
|
||||||
|
CREATE TABLE member_profile_tags(
|
||||||
|
tagid SERIAL PRIMARY KEY,
|
||||||
|
guildid BIGINT NOT NULL,
|
||||||
|
userid BIGINT NOT NULL,
|
||||||
|
tag TEXT NOT NULL,
|
||||||
|
_timestamp TIMESTAMPTZ DEFAULT now(),
|
||||||
|
FOREIGN KEY (guildid, userid) REFERENCES members (guildid, userid)
|
||||||
|
);
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- Member goals {{{
|
||||||
|
|
||||||
|
-- }}}
|
||||||
|
|
||||||
-- vim: set fdm=marker:
|
-- vim: set fdm=marker:
|
||||||
|
|||||||
Reference in New Issue
Block a user