2018-07-24 22:47:57 +02:00
import logging
import re
from datetime import datetime
from typing import List
from urllib . parse import urlparse
2018-09-01 19:41:35 +02:00
from pluralkit . bot import utils , embeds
2018-07-24 22:47:57 +02:00
from pluralkit . bot . commands import *
logger = logging . getLogger ( " pluralkit.commands " )
@member_command ( cmd = " member " , description = " Shows information about a system member. " , system_only = False , category = " Member commands " )
async def member_info ( ctx : MemberCommandContext , args : List [ str ] ) :
await ctx . reply ( embed = await utils . generate_member_info_card ( ctx . conn , ctx . member ) )
@command ( cmd = " member new " , usage = " <name> " , description = " Adds a new member to your system. " , category = " Member commands " )
async def new_member ( ctx : MemberCommandContext , args : List [ str ] ) :
if len ( args ) == 0 :
raise InvalidCommandSyntax ( )
name = " " . join ( args )
bounds_error = utils . bounds_check_member_name ( name , ctx . system . tag )
if bounds_error :
2018-09-01 19:41:35 +02:00
return embeds . error ( bounds_error )
2018-07-24 22:47:57 +02:00
# TODO: figure out what to do if this errors out on collision on generate_hid
hid = utils . generate_hid ( )
# Insert member row
await db . create_member ( ctx . conn , system_id = ctx . system . id , member_name = name , member_hid = hid )
2018-09-01 19:41:35 +02:00
return embeds . success ( " Member \" {} \" (` {} `) registered! " . format ( name , hid ) )
2018-07-24 22:47:57 +02:00
@member_command ( cmd = " member set " , usage = " <name|description|color|pronouns|birthdate|avatar> [value] " , description = " Edits a member property. Leave [value] blank to clear. " , category = " Member commands " )
async def member_set ( ctx : MemberCommandContext , args : List [ str ] ) :
if len ( args ) == 0 :
raise InvalidCommandSyntax ( )
allowed_properties = [ " name " , " description " , " color " , " pronouns " , " birthdate " , " avatar " ]
db_properties = {
" name " : " name " ,
" description " : " description " ,
" color " : " color " ,
" pronouns " : " pronouns " ,
" birthdate " : " birthday " ,
" avatar " : " avatar_url "
}
prop = args [ 0 ]
if prop not in allowed_properties :
2018-09-01 19:41:35 +02:00
return embeds . error ( " Unknown property {} . Allowed properties are {} . " . format ( prop , " , " . join ( allowed_properties ) ) )
2018-07-24 22:47:57 +02:00
if len ( args ) > = 2 :
value = " " . join ( args [ 1 : ] )
# Sanity/validity checks and type conversions
if prop == " name " :
bounds_error = utils . bounds_check_member_name ( value , ctx . system . tag )
if bounds_error :
2018-09-01 19:41:35 +02:00
return embeds . error ( bounds_error )
2018-07-24 22:47:57 +02:00
if prop == " color " :
match = re . fullmatch ( " #?([0-9A-Fa-f] {6} ) " , value )
if not match :
2018-09-01 19:41:35 +02:00
return embeds . error ( " Color must be a valid hex color (eg. #ff0000) " )
2018-07-24 22:47:57 +02:00
value = match . group ( 1 ) . lower ( )
if prop == " birthdate " :
try :
value = datetime . strptime ( value , " % Y- % m- %d " ) . date ( )
except ValueError :
try :
# Try again, adding 0001 as a placeholder year
# This is considered a "null year" and will be omitted from the info card
# Useful if you want your birthday to be displayed yearless.
value = datetime . strptime ( " 0001- " + value , " % Y- % m- %d " ) . date ( )
except ValueError :
2018-09-01 19:41:35 +02:00
return embeds . error ( " Invalid date. Date must be in ISO-8601 format (eg. 1999-07-25). " )
2018-07-24 22:47:57 +02:00
if prop == " avatar " :
user = await utils . parse_mention ( ctx . client , value )
if user :
# Set the avatar to the mentioned user's avatar
# Discord doesn't like webp, but also hosts png alternatives
value = user . avatar_url . replace ( " .webp " , " .png " )
else :
# Validate URL
u = urlparse ( value )
if u . scheme in [ " http " , " https " ] and u . netloc and u . path :
value = value
else :
2018-09-01 19:41:35 +02:00
return embeds . error ( " Invalid URL. " )
2018-07-24 22:47:57 +02:00
else :
# Can't clear member name
if prop == " name " :
2018-09-01 19:41:35 +02:00
return embeds . error ( " Can ' t clear member name. " )
2018-07-24 22:47:57 +02:00
# Clear from DB
value = None
db_prop = db_properties [ prop ]
await db . update_member_field ( ctx . conn , member_id = ctx . member . id , field = db_prop , value = value )
2018-09-01 19:41:35 +02:00
response = embeds . success ( " {} {} ' s {} . " . format ( " Updated " if value else " Cleared " , ctx . member . name , prop ) )
2018-07-24 22:47:57 +02:00
if prop == " avatar " and value :
response . set_image ( url = value )
if prop == " color " and value :
response . colour = int ( value , 16 )
return response
@member_command ( cmd = " member proxy " , usage = " [example] " , description = " Updates a member ' s proxy settings. Needs an \" example \" proxied message containing the string \" text \" (eg. [text], |text|, etc). " , category = " Member commands " )
async def member_proxy ( ctx : MemberCommandContext , args : List [ str ] ) :
if len ( args ) == 0 :
prefix , suffix = None , None
else :
# Sanity checking
example = " " . join ( args )
if " text " not in example :
2018-09-01 19:41:35 +02:00
return embeds . error ( " Example proxy message must contain the string ' text ' . " )
2018-07-24 22:47:57 +02:00
if example . count ( " text " ) != 1 :
2018-09-01 19:41:35 +02:00
return embeds . error ( " Example proxy message must contain the string ' text ' exactly once. " )
2018-07-24 22:47:57 +02:00
# Extract prefix and suffix
prefix = example [ : example . index ( " text " ) ] . strip ( )
suffix = example [ example . index ( " text " ) + 4 : ] . strip ( )
logger . debug ( " Matched prefix ' {} ' and suffix ' {} ' " . format ( prefix , suffix ) )
# DB stores empty strings as None, make that work
if not prefix :
prefix = None
if not suffix :
suffix = None
async with ctx . conn . transaction ( ) :
await db . update_member_field ( ctx . conn , member_id = ctx . member . id , field = " prefix " , value = prefix )
await db . update_member_field ( ctx . conn , member_id = ctx . member . id , field = " suffix " , value = suffix )
2018-09-01 19:41:35 +02:00
return embeds . success ( " Proxy settings updated. " if prefix or suffix else " Proxy settings cleared. " )
2018-07-24 22:47:57 +02:00
@member_command ( " member delete " , description = " Deletes a member from your system ***permanently***. " , category = " Member commands " )
async def member_delete ( ctx : MemberCommandContext , args : List [ str ] ) :
await ctx . reply ( " Are you sure you want to delete {} ? If so, reply to this message with the member ' s ID (` {} `). " . format ( ctx . member . name , ctx . member . hid ) )
msg = await ctx . client . wait_for_message ( author = ctx . message . author , channel = ctx . message . channel , timeout = 60.0 )
2018-08-23 15:53:52 +02:00
if msg and msg . content . lower ( ) == ctx . member . hid . lower ( ) :
2018-07-24 22:47:57 +02:00
await db . delete_member ( ctx . conn , member_id = ctx . member . id )
2018-09-01 19:41:35 +02:00
return embeds . success ( " Member deleted. " )
2018-07-24 22:47:57 +02:00
else :
2018-09-01 19:41:35 +02:00
return embeds . success ( " Member deletion cancelled. " )