From 6b6777cb891c4d04be16aa9abbeed35d1e6d547f Mon Sep 17 00:00:00 2001 From: w Date: Sun, 18 May 2025 18:43:50 -0300 Subject: [PATCH 1/8] docs typo --- bot/parsing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/parsing.py b/bot/parsing.py index c17a276..2842e3c 100644 --- a/bot/parsing.py +++ b/bot/parsing.py @@ -3,7 +3,7 @@ import config from gacha_response import gacha_response def parse_notification(notification,client): - '''Oarses any notifications received by the bot and sends any commands to + '''Parses any notifications received by the bot and sends any commands to gacha_response()''' # We get the type of notification to filter the ones that we actually want -- 2.39.2 From 24309ce900c303e4acd0f64dbaee95107961d4f8 Mon Sep 17 00:00:00 2001 From: w Date: Mon, 19 May 2025 00:14:47 -0300 Subject: [PATCH 2/8] Gacha_response changed to response --- bot/bot_app.py | 45 +++++++++++++++++++++++--- bot/parsing.py | 34 ++----------------- bot/{gacha_response.py => response.py} | 8 +++-- 3 files changed, 47 insertions(+), 40 deletions(-) rename bot/{gacha_response.py => response.py} (92%) diff --git a/bot/bot_app.py b/bot/bot_app.py index 5806478..81c920c 100644 --- a/bot/bot_app.py +++ b/bot/bot_app.py @@ -4,6 +4,7 @@ import misskey from parsing import parse_notification from db_utils import get_or_create_user, add_pull, get_config, set_config from client import client_connection +from response import generate_response # Initialize the Misskey client client = client_connection() @@ -44,15 +45,49 @@ def stream_notifications(): instance = host if host else "local" if instance in whitelisted_instances or instance == "local": - note = notification.get("note", {}).get("text", "") + note = notification.get("note", {}) + note_text = note.get("text", "") + note_id = note.get("id") notif_type = notification.get("type", "unknown") + # We want the visibility to be related to the type that was received (so if + # people don't want to dump a bunch of notes on home they don't have to) + + visibility = notification["note"]["visibility"] + if visibility != "specified": + visibility = "home" + print(f"📨 [{notif_type}] from @{username}@{instance}") - print(f"💬 {note}") + print(f"💬 {note_text}") print("-" * 30) - # 🧠 Send to the parser - parse_notification(notification,client) + + # We get the type of notification to filter the ones that we actually want + # to parse + + notif_type = notification.get("type") + if notif_type in ('mention', 'reply'): + # 🧠 Send to the parser + parsed_command = parse_notification(notification,client) + # Get the response + response = generate_response(parsed_command) + if isinstance(response, str): + client.notes_create( + text=response, + reply_id=note_id, + visibility=visibility + ) + elif response: + client.notes_create( + text=response[0], + reply_id=note_id, + visibility=visibility, + file_ids=response[1] + #visible_user_ids=[] #todo: write actual visible users ids so pleromers can use the bot privately + ) + + + else: print(f"⚠️ Blocked notification from untrusted instance: {host}") @@ -70,7 +105,7 @@ def stream_notifications(): except Exception as e: print(f"An exception has occured: {e}\n{traceback.format_exc()}") - time.sleep(5) + time.sleep(2) diff --git a/bot/parsing.py b/bot/parsing.py index 2842e3c..bbefe70 100644 --- a/bot/parsing.py +++ b/bot/parsing.py @@ -1,23 +1,11 @@ import random, re import config -from gacha_response import gacha_response def parse_notification(notification,client): '''Parses any notifications received by the bot and sends any commands to gacha_response()''' - # We get the type of notification to filter the ones that we actually want - # to parse - - notif_type = notification.get("type") - if not notif_type in ('mention', 'reply'): - return # Ignore anything that isn't a mention - - # We want the visibility to be related to the type that was received (so if - # people don't want to dump a bunch of notes on home they don't have to) - visibility = notification["note"]["visibility"] - if visibility != "specified": - visibility = "home" + # Get the full Activitypub ID of the user user = notification.get("user", {}) @@ -50,22 +38,4 @@ def parse_notification(notification,client): command = parts[0].lower() if parts else None arguments = parts[1:] if len(parts) > 1 else [] - # TODO: move response generation to a different function - response = gacha_response(command.lower(),full_user, arguments, note_obj) - if not response: - return - - if isinstance(response, str): - client.notes_create( - text=response, - reply_id=note_id, - visibility=visibility - ) - else: - client.notes_create( - text=response[0], - reply_id=note_id, - visibility=visibility, - file_ids=response[1] - #visible_user_ids=[] #todo: write actual visible users ids so pleromers can use the bot privately - ) + return [command,full_user, arguments, note_obj] diff --git a/bot/gacha_response.py b/bot/response.py similarity index 92% rename from bot/gacha_response.py rename to bot/response.py index e703aff..2c58c43 100644 --- a/bot/gacha_response.py +++ b/bot/response.py @@ -26,11 +26,13 @@ def is_float(val): return False -# TODO: See issue #3, separate command parsing from game logic. -def gacha_response(command,full_user, arguments,note_obj): - '''Parses a given command with arguments, processes the game state and +def generate_response(parsed_command): + + '''Given a command with arguments, processes the game state and returns a response''' + command, full_user, arguments, note_obj = parsed_command + if command == "roll": user_id = get_or_create_user(full_user) character_id, character_name, file_id, rarity = get_character() -- 2.39.2 From d2a7e523e86787b7c5d04c5c074d63d8932b93e2 Mon Sep 17 00:00:00 2001 From: VD15 Date: Mon, 19 May 2025 19:53:59 +0100 Subject: [PATCH 3/8] Overhaul notification parsing --- bot/bot_app.py | 118 ++++---------------------------------------- bot/config.py | 3 ++ bot/notification.py | 106 +++++++++++++++++++++++++++++++++++++++ example_config.ini | 8 ++- 4 files changed, 125 insertions(+), 110 deletions(-) create mode 100644 bot/notification.py diff --git a/bot/bot_app.py b/bot/bot_app.py index 81c920c..b65ef3a 100644 --- a/bot/bot_app.py +++ b/bot/bot_app.py @@ -1,114 +1,14 @@ import time -import traceback -import misskey -from parsing import parse_notification -from db_utils import get_or_create_user, add_pull, get_config, set_config +import misskey as misskey from client import client_connection -from response import generate_response -# Initialize the Misskey client -client = client_connection() - -# Define your whitelist -# TODO: move to config -whitelisted_instances: list[str] = [] - -def stream_notifications(): - print("Starting filtered notification stream...") - - last_seen_id = get_config("last_seen_notif_id") +from config import NOTIFICATION_POLL_INTERVAL +from notification import process_notifications +if __name__ == '__main__': + # Initialize the Misskey client + client = client_connection() + print('Listening for notifications...') while True: - try: - # May be able to mark notifications as read using misskey.py and - # filter them out here. This function also takes a since_id we - # could use as well - notifications = client.i_notifications() - - if notifications: - # Oldest to newest - notifications.reverse() - - new_last_seen_id = last_seen_id - - for notification in notifications: - notif_id = notification.get("id") - - # Skip old or same ID notifications - if last_seen_id is not None and notif_id <= last_seen_id: - continue - - user = notification.get("user", {}) - username = user.get("username", "unknown") - host = user.get("host") # None if local user - - instance = host if host else "local" - - if instance in whitelisted_instances or instance == "local": - note = notification.get("note", {}) - note_text = note.get("text", "") - note_id = note.get("id") - notif_type = notification.get("type", "unknown") - - # We want the visibility to be related to the type that was received (so if - # people don't want to dump a bunch of notes on home they don't have to) - - visibility = notification["note"]["visibility"] - if visibility != "specified": - visibility = "home" - - print(f"📨 [{notif_type}] from @{username}@{instance}") - print(f"💬 {note_text}") - print("-" * 30) - - - # We get the type of notification to filter the ones that we actually want - # to parse - - notif_type = notification.get("type") - if notif_type in ('mention', 'reply'): - # 🧠 Send to the parser - parsed_command = parse_notification(notification,client) - # Get the response - response = generate_response(parsed_command) - if isinstance(response, str): - client.notes_create( - text=response, - reply_id=note_id, - visibility=visibility - ) - elif response: - client.notes_create( - text=response[0], - reply_id=note_id, - visibility=visibility, - file_ids=response[1] - #visible_user_ids=[] #todo: write actual visible users ids so pleromers can use the bot privately - ) - - - - - else: - print(f"⚠️ Blocked notification from untrusted instance: {host}") - - # Update only if this notif_id is greater - if new_last_seen_id is None or notif_id > new_last_seen_id: - new_last_seen_id = notif_id - - # Save the latest seen ID - if new_last_seen_id and new_last_seen_id != last_seen_id: - set_config("last_seen_notif_id", new_last_seen_id) - last_seen_id = new_last_seen_id - - time.sleep(5) - - except Exception as e: - print(f"An exception has occured: {e}\n{traceback.format_exc()}") - time.sleep(2) - - - - -if __name__ == "__main__": - stream_notifications() + if not process_notifications(client): + time.sleep(NOTIFICATION_POLL_INTERVAL) diff --git a/bot/config.py b/bot/config.py index b24293c..5b73661 100644 --- a/bot/config.py +++ b/bot/config.py @@ -20,3 +20,6 @@ DB_PATH = config['application']['DatabaseLocation'] # Fedi handles in the traditional 'user@domain.tld' style, allows these users # to use extra admin exclusive commands with the bot''' ADMINS = config['application']['DefaultAdmins'] + +NOTIFICATION_POLL_INTERVAL = int(config['application']['NotificationPollInterval']) +NOTIFICATION_BATCH_SIZE = int(config['application']['NotificationBatchSize']) diff --git a/bot/notification.py b/bot/notification.py new file mode 100644 index 0000000..0570cd8 --- /dev/null +++ b/bot/notification.py @@ -0,0 +1,106 @@ +import traceback +from misskey.exceptions import MisskeyAPIException + +from config import NOTIFICATION_BATCH_SIZE +from parsing import parse_notification +from db_utils import get_config, set_config +from response import generate_response + +# Define your whitelist +# TODO: move to config +WHITELISTED_INSTANCES: list[str] = [] + +def process_notification(client, notification): + '''Processes an individual notification''' + notif_id = notification.get('id') + + user = notification.get('user', {}) + username = user.get('username', 'unknown') + host = user.get('host') # None if local user + instance = host if host else 'local' + + if not (instance in WHITELISTED_INSTANCES or instance == 'local'): + print(f'⚠️ Blocked notification from untrusted instance: {instance}') + + # Copy visibility of the post that was received when replying (so if people + # don't want to dump a bunch of notes on home they don't have to) + visibility = notification['note']['visibility'] + if visibility != 'specified': + visibility = 'home' + + notif_type = notification.get('type', 'unknown') + print(f'📨 [{notif_type}] from @{username}@{instance}') + + # 🧠 Send to the parser + parsed_command = parse_notification(notification, client) + + # Get the note Id to reply to + note_id = notification.get('note', {}).get('id') + + # Get the response + # TODO: Formalize exactly *what* is returned by this. Ideally just want to + # handle two cases here: either we have a response, or we don't. + # TODO: Return dictionaries instead of tuples. They handle multiple + # elements a lot better as they're not position dependent + response = generate_response(parsed_command) + if isinstance(response, str): + client.notes_create( + text=response, + reply_id=note_id, + visibility=visibility + ) + elif response: + client.notes_create( + text=response[0], + reply_id=note_id, + visibility=visibility, + file_ids=response[1] + #visible_user_ids=[] #todo: write actual visible users ids so pleromers can use the bot privately + ) + return notif_id + +def process_notifications(client): + '''Processes a batch of unread notifications. Returns False if there are + no more notifications to process.''' + + last_seen_id = get_config('last_seen_notif_id') + # process_notification writes to last_seen_id, so make a copy + new_last_seen_id = last_seen_id + + try: + notifications = client.i_notifications( + # Fetch notifications we haven't seen yet + since_id=last_seen_id, + # Let misskey handle the filtering + include_types=['mention', 'reply'], + # And handle the batch size while we're at it + limit=NOTIFICATION_BATCH_SIZE + ) + + # No notifications. Wait the poll period. + if not notifications: + return False + + # Iterate oldest to newest + for notification in notifications: + try: + # Process notification and update new last seen id + new_last_seen_id = process_notification(client, notification) + except Exception as e: + print(f'An exception has occured while processing a notification: {e}') + print(traceback.format_exc()) + + # If we got as many notifications as we requested, there are probably + # more in the queue + return len(notifications) == NOTIFICATION_BATCH_SIZE + + except MisskeyAPIException as e: + print(f'An exception has occured while reading notifications: {e}\n') + print(traceback.format_exc()) + finally: + # Quality jank right here, but finally lets us update the last_seen_id + # even if we hit an exception or return early + if new_last_seen_id != last_seen_id: + set_config('last_seen_notif_id', new_last_seen_id) + + return False diff --git a/example_config.ini b/example_config.ini index 4412c92..44cc2f9 100644 --- a/example_config.ini +++ b/example_config.ini @@ -14,4 +14,10 @@ InstanceUrl = http://example.tld DefaultAdmins = ['admin@example.tld'] ; SQLite Database location -DatabaseLocation = ./gacha_game.db \ No newline at end of file +DatabaseLocation = ./gacha_game.db + +; Number of seconds to sleep while awaiting new notifications +NotificationPollInterval = 5 + +; Number of notifications to process at once (limit 100) +NotificationBatchSize = 10 -- 2.39.2 From 9be92afce35b52cb6d217477dad3e09cf8e1e437 Mon Sep 17 00:00:00 2001 From: VD15 Date: Tue, 20 May 2025 09:21:06 +0100 Subject: [PATCH 4/8] Fix notification loop --- bot/notification.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/bot/notification.py b/bot/notification.py index 0570cd8..71ec814 100644 --- a/bot/notification.py +++ b/bot/notification.py @@ -12,8 +12,6 @@ WHITELISTED_INSTANCES: list[str] = [] def process_notification(client, notification): '''Processes an individual notification''' - notif_id = notification.get('id') - user = notification.get('user', {}) username = user.get('username', 'unknown') host = user.get('host') # None if local user @@ -29,7 +27,8 @@ def process_notification(client, notification): visibility = 'home' notif_type = notification.get('type', 'unknown') - print(f'📨 [{notif_type}] from @{username}@{instance}') + notif_id = notification.get('id') + print(f'📨 <{notif_id}> [{notif_type}] from @{username}@{instance}') # 🧠 Send to the parser parsed_command = parse_notification(notification, client) @@ -57,7 +56,6 @@ def process_notification(client, notification): file_ids=response[1] #visible_user_ids=[] #todo: write actual visible users ids so pleromers can use the bot privately ) - return notif_id def process_notifications(client): '''Processes a batch of unread notifications. Returns False if there are @@ -69,7 +67,10 @@ def process_notifications(client): try: notifications = client.i_notifications( - # Fetch notifications we haven't seen yet + # Fetch notifications we haven't seen yet. This option is a bit + # tempermental, sometimes it'll include since_id, sometimes it + # won't. We need to keep track of what notifications we've + # already processed. since_id=last_seen_id, # Let misskey handle the filtering include_types=['mention', 'reply'], @@ -84,8 +85,15 @@ def process_notifications(client): # Iterate oldest to newest for notification in notifications: try: - # Process notification and update new last seen id - new_last_seen_id = process_notification(client, notification) + # Skip if we've processed already + notif_id = notification.get('id') + if notif_id <= last_seen_id: + continue + + # Update new_last_seen_id and process + new_last_seen_id = notif_id + process_notification(client, notification) + except Exception as e: print(f'An exception has occured while processing a notification: {e}') print(traceback.format_exc()) @@ -100,7 +108,7 @@ def process_notifications(client): finally: # Quality jank right here, but finally lets us update the last_seen_id # even if we hit an exception or return early - if new_last_seen_id != last_seen_id: + if new_last_seen_id > last_seen_id: set_config('last_seen_notif_id', new_last_seen_id) return False -- 2.39.2 From ff20e2682148b2fa0cfd80c4fb029332185ca1bb Mon Sep 17 00:00:00 2001 From: w Date: Thu, 22 May 2025 00:20:32 -0300 Subject: [PATCH 5/8] Whitelist fix --- bot/notification.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bot/notification.py b/bot/notification.py index 71ec814..e0ae020 100644 --- a/bot/notification.py +++ b/bot/notification.py @@ -19,6 +19,7 @@ def process_notification(client, notification): if not (instance in WHITELISTED_INSTANCES or instance == 'local'): print(f'⚠️ Blocked notification from untrusted instance: {instance}') + return # Copy visibility of the post that was received when replying (so if people # don't want to dump a bunch of notes on home they don't have to) -- 2.39.2 From 47272aee4fc3a7888c7a887869be1ce48fd34b2c Mon Sep 17 00:00:00 2001 From: VD15 Date: Fri, 23 May 2025 23:44:03 +0100 Subject: [PATCH 6/8] Add a roll timeout to the bot --- bot/add_character.py | 7 +-- bot/config.py | 24 ++++---- bot/db_utils.py | 12 +++- bot/response.py | 130 ++++++++++++++++++++++++++++++------------- example_config.ini | 36 ++++++------ 5 files changed, 135 insertions(+), 74 deletions(-) diff --git a/bot/add_character.py b/bot/add_character.py index 68528c3..aae3fb3 100644 --- a/bot/add_character.py +++ b/bot/add_character.py @@ -32,7 +32,7 @@ def add_character(name: str, rarity: int, weight: float, image_url: str) -> tupl raise ValueError("Image URL must be provided.") # Download image - response = requests.get(image_url, stream=True) + response = requests.get(image_url, stream=True, timeout=30) if response.status_code != 200: raise RuntimeError(f"Failed to download image from {image_url}") @@ -55,9 +55,6 @@ def add_character(name: str, rarity: int, weight: float, image_url: str) -> tupl character_id = cur.lastrowid return character_id, file_id - - except Exception as e: - raise finally: if 'conn' in locals(): - conn.close() \ No newline at end of file + conn.close() diff --git a/bot/config.py b/bot/config.py index 5b73661..643aeb1 100644 --- a/bot/config.py +++ b/bot/config.py @@ -4,22 +4,20 @@ config = configparser.ConfigParser() config.read('config.ini') # Username for the bot -USER = config['application']['BotUser'] - +USER = config['credentials']['User'] # API key for the bot -KEY = config['application']['ApiKey'] +KEY = config['credentials']['Token'] # Bot's Misskey instance URL -INSTANCE = config['application']['InstanceUrl'] - -# SQLite Database location -DB_PATH = config['application']['DatabaseLocation'] - -# Extra stuff for control of the bot +INSTANCE = config['credentials']['Instance'] # TODO: move this to db # Fedi handles in the traditional 'user@domain.tld' style, allows these users -# to use extra admin exclusive commands with the bot''' -ADMINS = config['application']['DefaultAdmins'] +# to use extra admin exclusive commands with the bot +ADMINS = config['application']['DefaultAdmins'] +# SQLite Database location +DB_PATH = config['application']['DatabaseLocation'] -NOTIFICATION_POLL_INTERVAL = int(config['application']['NotificationPollInterval']) -NOTIFICATION_BATCH_SIZE = int(config['application']['NotificationBatchSize']) +NOTIFICATION_POLL_INTERVAL = int(config['notification']['PollInterval']) +NOTIFICATION_BATCH_SIZE = int(config['notification']['BatchSize']) + +GACHA_ROLL_INTERVAL = int(config['gacha']['RollInterval']) diff --git a/bot/db_utils.py b/bot/db_utils.py index bb80d98..a521ec5 100644 --- a/bot/db_utils.py +++ b/bot/db_utils.py @@ -1,5 +1,4 @@ import sqlite3 -import random import config DB_PATH = config.DB_PATH @@ -40,6 +39,17 @@ def add_pull(user_id, character_id): conn.commit() conn.close() +def get_last_rolled_at(user_id): + '''Gets the timestamp when the user last rolled''' + conn = get_db_connection() + cur = conn.cursor() + cur.execute("SELECT timestamp FROM pulls WHERE user_id = ? ORDER BY timestamp DESC", \ + (user_id,)) + row = cur.fetchone() + conn.close() + return row[0] if row else None + + def get_config(key): '''Reads the value for a specified config key from the db''' conn = get_db_connection() diff --git a/bot/response.py b/bot/response.py index 2c58c43..b009344 100644 --- a/bot/response.py +++ b/bot/response.py @@ -1,6 +1,8 @@ import random -from db_utils import get_or_create_user, add_pull, get_db_connection +from datetime import datetime, timedelta, timezone +from db_utils import get_or_create_user, add_pull, get_db_connection, get_last_rolled_at from add_character import add_character +from config import GACHA_ROLL_INTERVAL def get_character(): ''' Gets a random character from the database''' @@ -18,54 +20,104 @@ def get_character(): return chosen['id'], chosen['name'], chosen['file_id'], chosen['rarity'] +def do_roll(full_user): + '''Determines whether the user can roll, then pulls a random character''' + user_id = get_or_create_user(full_user) + + # Get date of user's last roll + date = get_last_rolled_at(user_id) + + # No date means it's users first roll + if date: + # SQLite timestamps returned by the DB are always in UTC + # Below timestamps are to be converted to UTC + prev = datetime.strptime(date + '+0000', '%Y-%m-%d %H:%M:%S%z') + now = datetime.now(timezone.utc) + + time_since_last_roll = now - prev + roll_interval = timedelta(seconds=GACHA_ROLL_INTERVAL) + duration = roll_interval - time_since_last_roll + + # User needs to wait before they can roll again + if time_since_last_roll < roll_interval: + remaining_duration = None + if duration.seconds > 3600: + remaining_duration = f'{-(duration.seconds // -3600)} hours' + elif duration.seconds > 60: + remaining_duration = f'{-(duration.seconds // -60)} minutes' + else: + remaining_duration = f'{duration.seconds} seconds' + + return f'{full_user} ⏱️ Please wait another {remaining_duration} before rolling again.' + + character_id, character_name, file_id, rarity = get_character() + + if not character_id: + return f'{full_user} Uwaaa... something went wrong! No characters found. 😿' + + add_pull(user_id,character_id) + stars = '⭐️' * rarity + return([f"@{full_user} 🎲 Congrats! You rolled {stars} **{character_name}**\n\ + She's all yours now~ 💖✨",[file_id]]) + def is_float(val): + '''Returns true if `val` can be converted to a float''' try: float(val) return True except ValueError: return False +def do_create(full_user, arguments, note_obj): + '''Creates a character''' + # Example call from bot logic + image_url = note_obj.get('files', [{}])[0].get('url') if note_obj.get('files') else None + if not image_url: + return f'{full_user}{full_user} You need an image to create a character, dumbass.' + + if len(arguments) != 3: + return '{full_user}Please specify the following attributes in order: \ + name, rarity, drop weighting' + + if not (arguments[1].isnumeric() and 1 <= int(arguments[1]) <= 5): + return f'{full_user}Invalid rarity: \'{arguments[1]}\' must be a number between 1 and 5' + + if not (is_float(arguments[2]) and 0.0 < float(arguments[2]) <= 1.0): + return f'{full_user}Invalid drop weight: \'{arguments[2]}\' \ + must be a decimal value between 0.0 and 1.0' + + character_id, file_id = add_character( + name=arguments[0], + rarity=int(arguments[1]), + weight=float(arguments[2]), + image_url=image_url + ) + return([f'{full_user}Added {arguments[0]}, ID {character_id}.',[file_id]]) + + +def do_help(full_user): + '''Provides a list of commands that the bot can do.''' + return f'{full_user} Here\'s what I can do:\n \ + - `roll` Pulls a random character.\ + - `create ` Creates a character using a given image.\ + - `help` Shows this message' + +def do_invalid_command(command, full_user): + '''Generic response when an unknown or invalid command is sent''' + return f'{full_user} Unrecognised command: {command}\n\ + Message \'help\' to get a list of valid commands' def generate_response(parsed_command): - '''Given a command with arguments, processes the game state and returns a response''' command, full_user, arguments, note_obj = parsed_command - - if command == "roll": - user_id = get_or_create_user(full_user) - character_id, character_name, file_id, rarity = get_character() - - if not character_id: - #TODO: Can't have tuples of a single element - # Return these as a dict or object instead. - return(f"@{full_user} Uwaaa... something went wrong! No characters found. 😿") - - add_pull(user_id,character_id) - stars = '⭐️' * rarity - return([f"@{full_user} 🎲 Congrats! You rolled {stars} **{character_name}**\nShe's all yours now~ 💖✨",[file_id]]) - - if command == "create": - # Example call from bot logic - image_url = note_obj.get("files", [{}])[0].get("url") if note_obj.get("files") else None - if not image_url: - return "You need an image to create a character, dumbass." - - if len(arguments) != 3: - return "Please specify the following attributes in order: name, rarity, drop weighting" - - if not (arguments[1].isnumeric() and 1 <= int(arguments[1]) <= 5): - return f"Invalid rarity: '{arguments[1]}' must be a number between 1 and 5" - - if not (is_float(arguments[2]) and 0.0 < float(arguments[2]) <= 1.0): - return f"Invalid drop weight: '{arguments[2]}' must be a decimal value between 0.0 and 1.0" - - character_id, file_id = add_character( - name=arguments[0], - rarity=int(arguments[1]), - weight=float(arguments[2]), - image_url=image_url - ) - return([f"Added {arguments[0]}, ID {character_id}.",[file_id]]) - return None + match command: + case 'roll': + return do_roll(full_user) + case 'create': + return do_create(full_user, arguments, note_obj) + case 'help': + return do_help(command) + case _: + return do_invalid_command(command, full_user) diff --git a/example_config.ini b/example_config.ini index 44cc2f9..d7f1c14 100644 --- a/example_config.ini +++ b/example_config.ini @@ -1,23 +1,27 @@ ; Rename me to config.ini and put your values in here [application] -; Full fedi handle of the bot user -BotUser = @bot@example.tld - -; API key for the bot -; Generate one by going to Settings > API > Generate access token -ApiKey = abcdefghijklmnopqrstuvwxyz012345 - -; Fully qualified URL of the instance hosting the bot -InstanceUrl = http://example.tld - ; Comma separated list of fedi handles for any administrator users -DefaultAdmins = ['admin@example.tld'] - +; More can be added through the application +DefaultAdmins = ['admin@example.tld'] ; SQLite Database location DatabaseLocation = ./gacha_game.db -; Number of seconds to sleep while awaiting new notifications -NotificationPollInterval = 5 +[gacha] +; Number of seconds players have to wait between rolls +RollInterval = 72000 + +[notification] +; Number of seconds to sleep while awaiting new notifications +PollInterval = 5 +; Number of notifications to process at once (max 100) +BatchSize = 10 + +[credentials] +; Fully qualified URL of the instance hosting the bot +Instance = http://example.tld +; Full fedi handle of the bot user +User = @bot@example.tld +; API key for the bot +; Generate one by going to Settings > API > Generate access token +Token = abcdefghijklmnopqrstuvwxyz012345 -; Number of notifications to process at once (limit 100) -NotificationBatchSize = 10 -- 2.39.2 From 4a7c9239fcc74fcf57327e6eb865d5d5bea1aab6 Mon Sep 17 00:00:00 2001 From: VD15 Date: Sat, 24 May 2025 22:43:53 +0100 Subject: [PATCH 7/8] Remove 'Invalid command response --- bot/response.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/bot/response.py b/bot/response.py index b009344..55100ad 100644 --- a/bot/response.py +++ b/bot/response.py @@ -94,7 +94,6 @@ def do_create(full_user, arguments, note_obj): ) return([f'{full_user}Added {arguments[0]}, ID {character_id}.',[file_id]]) - def do_help(full_user): '''Provides a list of commands that the bot can do.''' return f'{full_user} Here\'s what I can do:\n \ @@ -102,11 +101,6 @@ def do_help(full_user): - `create ` Creates a character using a given image.\ - `help` Shows this message' -def do_invalid_command(command, full_user): - '''Generic response when an unknown or invalid command is sent''' - return f'{full_user} Unrecognised command: {command}\n\ - Message \'help\' to get a list of valid commands' - def generate_response(parsed_command): '''Given a command with arguments, processes the game state and returns a response''' @@ -120,4 +114,4 @@ def generate_response(parsed_command): case 'help': return do_help(command) case _: - return do_invalid_command(command, full_user) + return None -- 2.39.2 From e8774cb8bdc6c81baa0bb6fde1c9751fd74c880d Mon Sep 17 00:00:00 2001 From: chris Date: Sat, 24 May 2025 21:15:16 -0500 Subject: [PATCH 8/8] initialize last_seen_notif_id in db.py --- db.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/db.py b/db.py index 63d0b43..ffcfe2e 100644 --- a/db.py +++ b/db.py @@ -41,6 +41,9 @@ cursor.execute(""" ) """) +# Initialize essential config key +cursor.execute('INSERT INTO config VALUES ("last_seen_notif_id", 0)') + """ # Insert example characters into the database if they don't already exist characters = [ ('Murakami-san', 1, 0.35), -- 2.39.2