Compare commits
	
		
			No commits in common. "8a331e0c7b509a36b5e32720f8486c197a744534" and "ff20e2682148b2fa0cfd80c4fb029332185ca1bb" have entirely different histories.
		
	
	
		
			8a331e0c7b
			...
			ff20e26821
		
	
		
					 5 changed files with 73 additions and 134 deletions
				
			
		|  | @ -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, timeout=30) | ||||
|         response = requests.get(image_url, stream=True) | ||||
|         if response.status_code != 200: | ||||
|             raise RuntimeError(f"Failed to download image from {image_url}") | ||||
| 
 | ||||
|  | @ -55,6 +55,9 @@ 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() | ||||
|             conn.close() | ||||
|  | @ -4,20 +4,22 @@ config = configparser.ConfigParser() | |||
| config.read('config.ini') | ||||
| 
 | ||||
| # Username for the bot | ||||
| USER     = config['credentials']['User'] | ||||
| USER     = config['application']['BotUser'] | ||||
| 
 | ||||
| # API key for the bot | ||||
| KEY      = config['credentials']['Token'] | ||||
| KEY      = config['application']['ApiKey'] | ||||
| # Bot's Misskey instance URL | ||||
| INSTANCE = config['credentials']['Instance'] | ||||
| INSTANCE = config['application']['InstanceUrl'] | ||||
| 
 | ||||
| # SQLite Database location | ||||
| DB_PATH = config['application']['DatabaseLocation'] | ||||
| 
 | ||||
| # Extra stuff for control of the bot | ||||
| 
 | ||||
| # 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'] | ||||
| # SQLite Database location | ||||
| DB_PATH       = config['application']['DatabaseLocation'] | ||||
| # to use extra admin exclusive commands with the bot''' | ||||
| ADMINS = config['application']['DefaultAdmins'] | ||||
| 
 | ||||
| NOTIFICATION_POLL_INTERVAL = int(config['notification']['PollInterval']) | ||||
| NOTIFICATION_BATCH_SIZE    = int(config['notification']['BatchSize']) | ||||
| 
 | ||||
| GACHA_ROLL_INTERVAL = int(config['gacha']['RollInterval']) | ||||
| NOTIFICATION_POLL_INTERVAL = int(config['application']['NotificationPollInterval']) | ||||
| NOTIFICATION_BATCH_SIZE    = int(config['application']['NotificationBatchSize']) | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import sqlite3 | ||||
| import random | ||||
| import config | ||||
| 
 | ||||
| DB_PATH = config.DB_PATH | ||||
|  | @ -39,17 +40,6 @@ 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() | ||||
|  |  | |||
							
								
								
									
										130
									
								
								bot/response.py
									
										
									
									
									
								
							
							
						
						
									
										130
									
								
								bot/response.py
									
										
									
									
									
								
							|  | @ -1,8 +1,6 @@ | |||
| import random | ||||
| from datetime import datetime, timedelta, timezone | ||||
| from db_utils import get_or_create_user, add_pull, get_db_connection, get_last_rolled_at | ||||
| from db_utils import get_or_create_user, add_pull, get_db_connection | ||||
| from add_character import add_character | ||||
| from config import GACHA_ROLL_INTERVAL | ||||
| 
 | ||||
| def get_character(): | ||||
|     ''' Gets a random character from the database''' | ||||
|  | @ -20,104 +18,54 @@ 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 <name> <rarity> <weight>` 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 | ||||
|     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) | ||||
|      | ||||
|     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 | ||||
|  |  | |||
|  | @ -1,27 +1,23 @@ | |||
| ; 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 | ||||
| ; More can be added through the application | ||||
| DefaultAdmins    = ['admin@example.tld'] | ||||
| DefaultAdmins = ['admin@example.tld'] | ||||
| 
 | ||||
| ; SQLite Database location | ||||
| DatabaseLocation = ./gacha_game.db | ||||
| 
 | ||||
| [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 | ||||
| NotificationPollInterval = 5 | ||||
| 
 | ||||
| ; Number of notifications to process at once (limit 100) | ||||
| NotificationBatchSize = 10 | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue