Explicit account modification #42

Merged
waifu merged 7 commits from 35_Explicit_account_modification into dev 2025-06-03 19:06:14 -07:00
2 changed files with 91 additions and 32 deletions

View file

@ -37,22 +37,45 @@ def get_random_character() -> Character | None:
'image_url': chosen['file_id']
}
def get_or_create_user(username: str) -> int:
'''Retrieves an ID for a given user, if the user does not exist, it will be
created.'''
def get_player(username: str) -> int:
'''Retrieve a player ID by username, or return None if not found.'''
CURSOR.execute('SELECT id FROM users WHERE username = ?', (username,))
user = CURSOR.fetchone()
if user:
return int(user[0])
# New user starts with has_rolled = False
def insert_player(username: str) -> int:
'''Insert a new player with default has_rolled = False and return their user ID.'''
CURSOR.execute(
'INSERT INTO users (username, has_rolled) VALUES (?, ?)',
(username, False)
)
user_id = CURSOR.lastrowid
return user_id if user_id else 0
return CURSOR.lastrowid
def delete_player(username: str) -> bool:
'''Permanently deletes a user and all their pulls.'''
CURSOR.execute(
'SELECT id FROM users WHERE username = ?',
(username,)
)
user = CURSOR.fetchone()
user_id = user[0]
Review

I mean, you do expect this username to be deleted anyway, right. False if the user does exist and the operation failed for some reason. Though depends on the use-case

I mean, you do expect this username to be deleted anyway, right. False if the user does exist and the operation failed for some reason. Though depends on the use-case
# Delete pulls
CURSOR.execute(
'DELETE FROM pulls WHERE user_id = ?',
(user_id,)
)
# Delete user
CURSOR.execute(
'DELETE FROM users WHERE id = ?',
(user_id,)
)
return True
def insert_character(

View file

@ -1,16 +1,20 @@
from datetime import datetime, timedelta, timezone
from typing import TypedDict, Any, List, Dict
from db_utils import get_or_create_user, insert_pull, get_last_rolled_at, \
from db_utils import get_player, insert_player, delete_player, insert_pull, get_last_rolled_at, \
get_random_character
from add_character import add_character
from config import GACHA_ROLL_INTERVAL
from custom_types import BotResponse, ParsedNotification
def do_roll(full_user: str) -> BotResponse:
def do_roll(author: str) -> BotResponse:
Review

The function seems to grow too big too huge. Instead of piling every action in one file response.py, splitting every do_* into separate files could help. The file will be very heavy on a lot of unrelated to each other changes, hence every function belongs to a separate "domain", that could be expressed by splitting

The function seems to grow too big too huge. Instead of piling every action in one file `response.py`, splitting every do_* into separate files could help. The file will be very heavy on a lot of unrelated to each other changes, hence every function belongs to a separate "domain", that could be expressed by splitting
'''Determines whether the user can roll, then pulls a random character'''
user_id = get_or_create_user(full_user)
user_id = get_player(author)
if not user_id:
return {
'message':f'{author} 🛑 You havent signed up yet! Use the `signup` command to start playing.',
'attachment_urls': None
}
# Get date of user's last roll
date = get_last_rolled_at(user_id)
@ -36,7 +40,7 @@ def do_roll(full_user: str) -> BotResponse:
remaining_duration = f'{duration.seconds} seconds'
return {
'message': f'{full_user} ⏱️ Please wait another \
'message': f'{author} ⏱️ Please wait another \
{remaining_duration} before rolling again.',
'attachment_urls': None
}
@ -45,7 +49,7 @@ def do_roll(full_user: str) -> BotResponse:
if not character:
return {
'message': f'{full_user} Uwaaa... something went wrong! No \
'message': f'{author} Uwaaa... something went wrong! No \
characters found. 😿',
'attachment_urls': None
}
@ -53,11 +57,26 @@ characters found. 😿',
insert_pull(user_id, character['id'])
stars = '⭐️' * character['rarity']
return {
'message': f'@{full_user} 🎲 Congrats! You rolled {stars} \
'message': f'{author} 🎲 Congrats! You rolled {stars} \
**{character['name']}**\nShe\'s all yours now~ 💖✨',
'attachment_urls': [character['image_url']]
}
def do_signup(author: str) -> BotResponse:
'''Registers a new user if they havent signed up yet.'''
user_id = get_player(author)
if user_id:
return {
'message':f'{author} 👀 Youre already signed up! Let the rolling begin~ 🎲',
'attachment_urls': None
}
new_user_id = insert_player(author)
return {
'message': f'{author} ✅ Signed up successfully! Your gacha destiny begins now... ✨ Use the roll command to start!',
'attachment_urls': None
}
def is_float(val: Any) -> bool:
'''Returns true if `val` can be converted to a float'''
@ -69,7 +88,7 @@ def is_float(val: Any) -> bool:
def do_create(
full_user: str,
author: str,
arguments: List[str],
note_obj: Dict[str, Any]) -> BotResponse:
'''Creates a character'''
@ -79,27 +98,27 @@ def do_create(
if not image_url:
return {
'message': f'{full_user} You need an image to create a character, \
'message': f'{author} You need an image to create a character, \
dumbass.',
'attachment_urls': None
}
if len(arguments) != 3:
return {
'message': f'{full_user} Please specify the following attributes \
'message': f'{author} Please specify the following attributes \
in order: name, rarity, drop weighting',
'attachment_urls': None
}
if not (arguments[1].isnumeric() and 1 <= int(arguments[1]) <= 5):
return {
'message': f'{full_user} Invalid rarity: \'{arguments[1]}\' must \
'message': f'{author} Invalid rarity: \'{arguments[1]}\' must \
be a number between 1 and 5',
'attachment_urls': None
}
if not (is_float(arguments[2]) and 0.0 < float(arguments[2]) <= 1.0):
Review

it'd be more readable if you unpacked the array into variables that have names. is_float(arguments[2])) is too low-level if you are not someone who wrote this function.
For example, is_float(rarity) seems better and less bug prone, since you can't fuck up accessing by a wrong index

it'd be more readable if you unpacked the array into variables that have names. `is_float(arguments[2]))` is too low-level if you are not someone who wrote this function. For example, `is_float(rarity)` seems better and less bug prone, since you can't fuck up accessing by a wrong index
return {
'message': f'{full_user} Invalid drop weight: \'{arguments[2]}\' \
'message': f'{author} Invalid drop weight: \'{arguments[2]}\' \
must be a decimal value between 0.0 and 1.0',
'attachment_urls': None
}
@ -111,7 +130,7 @@ must be a decimal value between 0.0 and 1.0',
image_url=image_url
)
return {
'message': f'{full_user} Added {arguments[0]}, ID {character_id}.',
'message': f'{author} Added {arguments[0]}, ID {character_id}.',
'attachment_urls': [file_id]
}
@ -119,17 +138,30 @@ must be a decimal value between 0.0 and 1.0',
def do_help(author: str) -> BotResponse:
'''Provides a list of commands that the bot can do.'''
return {
'message': f'{author} Here\'s what I can do:\n\
- `roll` Pulls a random character.\n\
- `create <name> <rarity> <weight>` Creates a character using a given image.\n\
- `help` Shows this message.',
'attachment_urls': None
'message':f'{author} Here\'s what I can do:\n \
- `roll` Pulls a random character.\
- `create <name> <rarity> <weight>` Creates a character using a given image.\
- `signup` Registers your account.\
- `delete_account` Deletes your account.\
- `help` Shows this message',
'attachment_urls': None
}
def do_signup() -> BotResponse:
def delete_account(author: str) -> BotResponse:
return {
'message': '',
'message':f'{author} ⚠️ This will permanently delete your account and all your cards.\n'
'If youre sure, reply with `confirm_delete` to proceed.\n\n'
'**There is no undo.** Your gacha luck will be lost to the void... 💀✨',
'attachment_urls': None
}
def confirm_delete(author: str) -> BotResponse:
delete_player(author)
return {
'message':f'{author} 🧼 Your account and all your cards have been deleted. RIP your gacha history 🕊️✨',
'attachment_urls': None
}
@ -142,7 +174,7 @@ def generate_response(notification: ParsedNotification) -> BotResponse | None:
res: BotResponse | None = None
# TODO: Check if the user has an account
author = notification['author']
user_id = get_or_create_user(author)
user_id = get_player(author)
command = notification['command']
# Check if the user is an administrator
# user_is_administrator = user_is_administrator()
@ -150,9 +182,11 @@ def generate_response(notification: ParsedNotification) -> BotResponse | None:
# Unrestricted commands
match command:
case 'signup':
res = do_signup()
Outdated
Review

Oh, you already do have the mapping! now just move the functions away to their own separate places, to unload the file

Oh, you already do have the mapping! now just move the functions away to their own separate places, to unload the file
res = do_signup(author)
case 'help':
res = do_help(author)
case 'roll':
res = do_roll(author)
case _:
pass
@ -161,16 +195,18 @@ def generate_response(notification: ParsedNotification) -> BotResponse | None:
# User commands
match command:
case 'delete_account':
pass
case 'roll':
res = do_roll(author)
case 'create':
res = do_create(
author,
notification['arguments'],
notification['note_obj']
)
case 'signup':
res = do_signup(author)
case 'delete_account':
res = delete_account(author)
case 'confirm_delete':
res = confirm_delete(author)
case _:
pass
# if not user_is_administrator: