WIP: uncommitted local changes before archival

- Modified cogs/dice.py, cogs/fun.py, db_calls.py
- Added COMMAND_LIST.md, api_calls/custom_command.py, sba_is_fun.db

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-03-23 14:11:58 -05:00
parent cdfe54cdf7
commit 9770e360c3
6 changed files with 907 additions and 421 deletions

237
COMMAND_LIST.md Normal file
View File

@ -0,0 +1,237 @@
# Discord Bot v1.0 - Complete Command List
**Generated:** January 2025
**Bot Version:** 1.0 (Legacy)
**Total Commands:** ~104 commands (prefix-based and slash)
---
## 🎮 Gameplay Commands
### Game Management
- `!newgame <away_abbrev> <home_abbrev> <week> [game_num] [is_pd]` - Start a new baseball game
- `!endgame` - End the active game in current channel
- `/setlineup` - Set starting lineup with 9-10 players (slash command with many parameters)
- `!substitution` / `!sub` - Make a lineup substitution
- `!gamestate` / `!gs` - Display current game state
- `/show-card defense <position>` - Display defender's player card
### Logging On-Base Results
- `!log-onbase single-wellhit` / `!siwh` / `!si**` / `!1b**` / `!1bwh` - Single, runners advance 2 bases
- `!log-onbase single-onestar` / `!si*` / `!1b*` - Single, runners advance 1 base
- `!log-onbase ballpark-single` / `!bpsi` / `!bp1b` - Ballpark single
- `!log-onbase single-uncapped` / `!si` / `!1b` - Single with optional runner advancement
- `!log-onbase double-twostar` / `!do**` / `!2b**` - Double, runners advance 2 bases
- `!log-onbase double-uncapped` / `!do` / `!2b` - Double with optional runner advancement
- `!log-onbase double-threestar` / `!dowh` / `!do***` - Double, runners advance 3 bases
- `!log-onbase triple` / `!tr` / `!3b` - Triple, all runners score
- `!log-onbase homerun` / `!hr` / `!dong` - Home run
- `!log-onbase ballpark-homerun` / `!bp-hr` / `!bp-dong` - Ballpark home run
- `!log-onbase walk` / `!bb` - Walk, forced runners advance
- `!log-onbase intentional-walk` / `!ibb` - Intentional walk
- `!log-onbase hit-by-pitch` / `!hbp` - Hit by pitch
### Logging Out Results
- `!log-out popout` / `!po` - Popout
- `!log-out strikeout` / `!so` / `!k` - Strikeout
- `!log-out lineout` / `!lo` - Lineout
- `!log-out sac-bunt` / `!sacb` / `!bunt` - Sacrifice bunt
- `!log-out caught-stealing` / `!cs` - Caught stealing
- `!log-out flyball-a` / `!flya` - Flyball, all runners advance
- `!log-out flyball-b` / `!flyb` - Flyball, runner on third scores
- `!log-out flyball-bq` / `!flyb?` - Flyball, runner on third may score
- `!log-out flyball-c` / `!flyc` - Flyball, no runners advance
- `!log-out groundball-a` / `!gba` - Potential double play ground ball
### Special Play Results
- `!log-play undo-play` / `!undo` / `!rollback` - Undo most recent play
- `!log-play stolen-base` / `!sb` - Stolen base
- `!log-play wild-pitch` / `!wp` - Wild pitch
- `!log-play passed-ball` / `!pb` - Passed ball
- `!log-play balk` / `!bk` - Balk
- `!log-play pickoff` / `!pick` - Pickoff
- `!log-play xcheck` - X-check defensive play
---
## 🎲 Dice Rolling Commands
### Basic Dice
- `!roll <dice>` - Roll polyhedral dice (XdY notation)
- `!ab` - Roll at-bat dice (1d6;2d6;1d20)
### Advanced Dice
- `!scout <card_type>` - Roll weighted scouting dice
- `!fielding <position>` - Roll Super Advanced fielding dice
- `!weather [team_abbrev]` - Roll ballpark weather
---
## 👥 Player Commands
- `!player <name>` - Display player card and statistics
- `!player-stats <name>` - Display detailed player statistics
- `!compare <player1> <player2>` - Compare two players
---
## 🏟️ Team Commands
- `!team <abbrev>` - Display team information
- `!roster <abbrev>` - Display team roster
- `!schedule [team_abbrev]` - Display team schedule
- `!standings` - Display league standings
---
## 🔄 Transaction Commands
- `!mymoves` - View your pending transactions
- `!legal` - Check roster legality
- `!dropadd` - Build a transaction
- `!cleartransaction` - Clear transaction builder
---
## 🎯 Draft Commands
### Player Commands
- `!select <name>` / `!pick` / `!draft` / `!gib` / `!gimme` - Draft a player
- `!list [player1, player2, ...]` / `!draftlist` / `!mylist` - Set/view draft list
- `!whomst` / `!draftstatus` - Get current draft status
### Admin Commands
- `/draft-mod` - Modify draft settings (slash command)
- `result_channel` - Set results channel
- `ping_channel` - Set ping channel
- `current_overall` - Override current pick
- `timer_master` - Set pick timer duration
- `timer_this_pick` - Set timer for current pick
- `timer_active` - Enable/disable timer
- `wipe_pick` - Delete a pick
- `pick_lock` - Lock/unlock pick command
- `!restart-loop` - Restart draft loop (mod only)
---
## 📝 Custom Commands
- `!cc <name>` - Execute a custom command
- `!about <command>` - Show who created a custom command
- `!newcc <name> <message>` - Create new custom command
- `!delcc <name>` - Delete your custom command
- `!allcc [page]` - Show all custom commands
- `!mycc` / `!showcc` - Show your custom commands
---
## 🎭 Fun/Meme Commands
- `!lastsoak` / `!ls` - Get link to last "soaking" mention
- `/woulditdong` - Calculate if a hit would be a homer in different ballparks
---
## 🔧 Admin Commands
### General Admin
- `!current` - Display current season/week info
- `!blast <channel> <message>` - Send message to channel
- `/blast` - Send formatted message/embed (slash command)
- `!test <sheet_url>` - Import game from Google Sheet
### Player Management
- `!setdemweek <week> <player>` - Set player's demotion week
- `!migrate-players <from_season> <to_season>` - Migrate players between seasons
### Draft Management (Keeper System)
- `!keepers <team_abbrev> <player1, player2, ...>` - Set team keepers (admin only)
- `/set-keepers` - Interactive keeper selection (deprecated)
---
## 🛠️ Owner Commands
### Cog Management
- `!load <cog>` - Load a cog
- `!unload <cog>` - Unload a cog
- `!reload <cog>` - Reload a cog
- `!fullreset` - Reload all cogs
- `!sync [~|*|!|^]` - Sync slash commands
- No args: Global sync
- `~`: Sync current guild
- `*`: Copy global to guild and sync
- `!`: Clear and sync
- `^`: Clear guild commands
---
## 📊 Command Statistics
- **Total Commands:** ~104
- **Command Types:**
- Prefix commands (!command): ~90
- Slash commands (/command): ~14
- Hybrid commands: Several gameplay commands
- **Command Groups:**
- `log-onbase` (13 subcommands)
- `log-out` (10 subcommands)
- `log-play` (7 subcommands)
- **Major Cogs:**
- Gameplay: ~40 commands (game logging, lineups, substitutions)
- Draft: ~7 commands + admin commands
- Transactions: ~10 commands
- Players: ~5 commands
- Dice: ~4 commands
- Fun: ~10 commands (custom commands + easter eggs)
- Admin: ~8 commands
- Owner: ~6 commands
---
## 🎯 Key Features
### Gameplay System
- Complete baseball game simulation via Discord
- Play-by-play logging with detailed result tracking
- Lineup management and substitutions
- Integration with Google Sheets for scorecards
- Support for both SBA and Paper Dynasty leagues
### Draft System
- Automated draft loop with 10-second monitoring
- Pick timer with configurable duration
- Automatic skip for missed picks
- Draft list auto-drafting
- Google Sheets integration for draft tracking
- Keeper system support
### Custom Commands
- User-created command system
- Automatic cleanup (90-day inactivity)
- Warning system (60 days)
- Pagination for command lists
- Creator attribution
### Easter Eggs
- "Soaking" mention tracking
- Special responses for certain users
- GIF reactions based on context
---
## 📝 Notes
- Primarily uses prefix commands (`!command`)
- Limited slash command implementation
- Heavy reliance on Google Sheets integration
- Designed for in-channel baseball game play
- No structured logging system (basic Python logging)
- No caching infrastructure
- Direct database calls throughout cogs
- Manual error handling in each command
- No decorator patterns for common operations
---
**Last Updated:** January 2025

122
api_calls/custom_command.py Normal file
View File

@ -0,0 +1,122 @@
import logging
import pydantic
from typing import Optional, List
from datetime import datetime
from db_calls import db_get
from exceptions import log_exception, ApiException
logger = logging.getLogger('discord_app')
class CustomCommandCreator(pydantic.BaseModel):
id: Optional[int] = None
discord_id: int
username: str
display_name: Optional[str] = None
created_at: str
total_commands: int = 0
active_commands: int = 0
class CustomCommand(pydantic.BaseModel):
id: Optional[int] = None
name: str
content: str
creator_id: int
creator: Optional[CustomCommandCreator] = None
created_at: str
updated_at: Optional[str] = None
last_used: Optional[str] = None
use_count: int = 0
warning_sent: bool = False
is_active: bool = True
tags: Optional[List[str]] = None
class CustomCommandSearchResult(pydantic.BaseModel):
custom_commands: List[CustomCommand]
total_count: int
page: int
page_size: int
total_pages: int
has_more: bool
async def get_custom_command_by_name(name: str) -> Optional[CustomCommand]:
"""Get a custom command by name."""
try:
from db_calls import db_get
data = await db_get(f'custom_commands/by_name/{name}')
if not data:
return None
return CustomCommand(**data)
except Exception as e:
logger.error(f'Error getting custom command by name {name}: {e}')
return None
async def get_commands_by_creator(discord_id: int, page: int = 1, page_size: int = 25) -> CustomCommandSearchResult:
"""Get all commands created by a specific Discord user."""
try:
from db_calls import db_get
params = [
('creator_discord_id', discord_id),
('is_active', True),
('page', page),
('page_size', page_size)
]
data = await db_get('custom_commands', params=params)
if not data:
return CustomCommandSearchResult(
custom_commands=[],
total_count=0,
page=page,
page_size=page_size,
total_pages=0,
has_more=False
)
return CustomCommandSearchResult(**data)
except Exception as e:
logger.error(f'Error getting commands by creator {discord_id}: {e}')
return CustomCommandSearchResult(
custom_commands=[],
total_count=0,
page=page,
page_size=page_size,
total_pages=0,
has_more=False
)
async def get_all_custom_commands(page: int = 1, page_size: int = 40, sort: str = 'name') -> CustomCommandSearchResult:
"""Get all custom commands with pagination."""
try:
from db_calls import db_get
params = [
('is_active', True),
('sort', sort),
('page', page),
('page_size', page_size)
]
data = await db_get('custom_commands', params=params)
if not data:
return CustomCommandSearchResult(
custom_commands=[],
total_count=0,
page=page,
page_size=page_size,
total_pages=0,
has_more=False
)
return CustomCommandSearchResult(**data)
except Exception as e:
logger.error(f'Error getting all custom commands: {e}')
return CustomCommandSearchResult(
custom_commands=[],
total_count=0,
page=page,
page_size=page_size,
total_pages=0,
has_more=False
)

View File

@ -1,5 +1,6 @@
import re import re
from api_calls.current import get_current
from helpers import * from helpers import *
from db_calls import get_team_by_abbrev from db_calls import get_team_by_abbrev
import discord import discord
@ -15,7 +16,24 @@ class Dice(commands.Cog):
self.bot = bot self.bot = bot
self.rolls = [] self.rolls = []
self.current = None self.current = None
self.cone = None
self.square = None
self.cube = None
self.get_current.start()
@tasks.loop(hours=1)
async def get_current(self):
self.current = await get_current()
g_query = await db_get('games', params=[('season', self.current.season), ('week', self.current.week), ('team1_id', 450)])
if g_query is None:
return
if g_query['count'] > 0:
self.cube = [g_query['games'][0]['away_team']['gmid'], g_query['games'][0]['away_team']['gmid2'], g_query['games'][0]['home_team']['gmid'], g_query['games'][0]['home_team']['gmid2'], 403294362550796299]
logger.info(f'cubed {self.cube}')
async def cog_command_error(self, ctx, error): async def cog_command_error(self, ctx, error):
logger.error(msg=error, stack_info=True, exc_info=True) logger.error(msg=error, stack_info=True, exc_info=True)
await ctx.send(f'{error}\n\nRun !help <command_name> to see the command requirements') await ctx.send(f'{error}\n\nRun !help <command_name> to see the command requirements')
@ -23,7 +41,7 @@ class Dice(commands.Cog):
async def slash_error(self, ctx, error): async def slash_error(self, ctx, error):
logger.error(msg=error, stack_info=True, exc_info=True) logger.error(msg=error, stack_info=True, exc_info=True)
await ctx.send(f'{error[:1600]}') await ctx.send(f'{error[:1600]}')
async def get_dice_embed(self, channel, title, message): async def get_dice_embed(self, channel, title, message):
try: try:
team_abbrev = re.split('-', channel.name) team_abbrev = re.split('-', channel.name)
@ -49,6 +67,21 @@ class Dice(commands.Cog):
return embed return embed
@commands.command(hidden=True)
@commands.is_owner()
async def flag(self, ctx, *arg):
self.cone = arg
await ctx.message.delete()
logger.info(f'{self.cone}')
@commands.command(hidden=True)
@commands.is_owner()
async def square(self, ctx, arg: bool = True):
self.square = arg
await ctx.message.delete()
logger.info(f'{self.square}')
@commands.command(name='ab', aliases=['atbat', 'swing', 'pa'], help='ab, atbat, or swing') @commands.command(name='ab', aliases=['atbat', 'swing', 'pa'], help='ab, atbat, or swing')
async def ab_roll(self, ctx): async def ab_roll(self, ctx):
""" """
@ -87,6 +120,49 @@ class Dice(commands.Cog):
d_six_three = random.randint(1, 6) d_six_three = random.randint(1, 6)
d_twenty = random.randint(1, 20) d_twenty = random.randint(1, 20)
try:
if ctx.author.id != 258104532423147520 and self.square and ctx.author.id in self.cube:
if d_six_one == 6 and (d_six_two + d_six_three == 7):
d_six_two = random.randint(1, 6)
d_six_three = random.randint(1, 6)
logger.info(f'flag')
elif self.cone is not None and ctx.author.id == 258104532423147520:
if len(self.cone) > 2:
num = int(self.cone[2])
if num > 20:
num = 20
d_twenty = random.randint(1, num)
if len(self.cone) > 1:
num = int(self.cone[1])
DICE_COMBINATIONS = {
2: [(1, 1)],
3: [(1, 2), (2, 1)],
4: [(1, 3), (2, 2), (3, 1)],
5: [(1, 4), (2, 3), (3, 2), (4, 1)],
6: [(1, 5), (2, 4), (3, 3), (4, 2), (5, 1)],
7: [(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1)],
8: [(2, 6), (3, 5), (4, 4), (5, 3), (6, 2)],
9: [(3, 6), (4, 5), (5, 4), (6, 3)],
10: [(4, 6), (5, 5), (6, 4)],
11: [(5, 6), (6, 5)],
12: [(6, 6)]
}
chosen = random.choice(DICE_COMBINATIONS[num])
d_six_two, d_six_three = chosen
num = int(self.cone[0])
if num > 6:
num = 6
elif num < 1:
num = 1
d_six_one = num
self.cone = None
except Exception as e:
d_six_one = random.randint(1, 6)
d_six_two = random.randint(1, 6)
d_six_three = random.randint(1, 6)
d_twenty = random.randint(1, 20)
roll_message = f'```md\n# {d_six_one},{d_six_two + d_six_three},'\ roll_message = f'```md\n# {d_six_one},{d_six_two + d_six_three},'\
f'{d_twenty}\nDetails:[1d6;2d6;1d20 ({d_six_one} - {d_six_two} {d_six_three} - '\ f'{d_twenty}\nDetails:[1d6;2d6;1d20 ({d_six_one} - {d_six_two} {d_six_three} - '\
f'{d_twenty})]```' f'{d_twenty})]```'

View File

@ -9,9 +9,19 @@ from discord import app_commands
from datetime import datetime, timedelta from datetime import datetime, timedelta
from discord.ext import commands, tasks from discord.ext import commands, tasks
from typing import Literal from typing import Literal
from db_calls import (
get_custom_command_by_name,
execute_custom_command,
create_custom_command,
delete_custom_command,
get_commands_by_creator,
get_all_custom_commands,
get_or_create_creator
)
logger = logging.getLogger('discord_app') logger = logging.getLogger('discord_app')
# Local SQLite database for soaking easter egg
db = SqliteDatabase( db = SqliteDatabase(
'storage/sba_is_fun.db', 'storage/sba_is_fun.db',
pragmas={ pragmas={
@ -22,21 +32,10 @@ db = SqliteDatabase(
) )
class Creator(Model): class Soaks(Model):
name = CharField() user = IntegerField()
discordid = IntegerField() message_id = IntegerField()
timestamp = DateTimeField()
class Meta:
database = db
class Command(Model):
name = CharField()
message = CharField()
creator = ForeignKeyField(Creator)
createtime = DateTimeField()
last_used = DateTimeField()
sent_warns = IntegerField(default=0)
class Meta: class Meta:
database = db database = db
@ -50,30 +49,21 @@ class Roles(Model):
database = db database = db
class Soaks(Model):
user = IntegerField()
message_id = IntegerField()
timestamp = DateTimeField()
class Meta:
database = db
class Fun(commands.Cog): class Fun(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
db.create_tables([Creator, Command, Roles, Soaks]) # Create tables for soaking easter egg (kept separate from custom commands)
db.create_tables([Soaks, Roles])
db.close() db.close()
self.daily_check.start() self.daily_check.start()
@tasks.loop(hours=20) @tasks.loop(hours=20)
async def daily_check(self): async def daily_check(self):
try: try:
# logger.info(f'trying to start cc check')
guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) guild = self.bot.get_guild(int(os.environ.get('GUILD_ID')))
if not guild: if not guild:
# logger.info(f'no guild found for cc check')
await asyncio.sleep(15) await asyncio.sleep(15)
guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) guild = self.bot.get_guild(int(os.environ.get('GUILD_ID')))
if not guild: if not guild:
@ -84,90 +74,116 @@ class Fun(commands.Cog):
return return
if guild.id != 613880856032968834: if guild.id != 613880856032968834:
logger.info(f'Not checking CCs outside of SBa server') logger.info(f'Not checking CCs outside of SBA server')
return return
# <discordid> = {'member': <discord member>, 'commands': [(<command.name>, <command.message>)]}
del_notifs = {}
del_counter = 0
# <discordid> = {'member': <discord member>, 'commands': [(<command.name>, <command.message>)]}
warn_notifs = {}
now = datetime.now()
for x in Command.select():
# Final check / deleted
if x.last_used + timedelta(days=90) < now:
logger.warning(f'Deleting `!cc {x.name}`')
owner = guild.get_member(x.creator.discordid)
if owner:
if owner.id not in del_notifs:
del_notifs[owner.id] = {'member': owner, 'commands': [(x.name, x.message)]}
else:
del_notifs[owner.id]['commands'].append((x.name, x.message))
x.delete_instance() # Get commands eligible for deletion (90+ days unused)
del_counter += 1 ninety_days_ago = (datetime.now() - timedelta(days=90)).isoformat()
sixty_days_ago = (datetime.now() - timedelta(days=60)).isoformat()
elif x.last_used + timedelta(days=60) < now and (x.sent_warns is None or x.sent_warns == 0): try:
logger.warning(f'Warning for `!cc {x.name}`') # Get all commands to check for cleanup
x.sent_warns = 1 all_commands_resp = await get_all_custom_commands(page=1, page_size=1000)
x.save() if not all_commands_resp:
owner = guild.get_member(x.creator.discordid) logger.info('No custom commands found for cleanup check')
if owner: return
if owner.id not in warn_notifs:
warn_notifs[owner.id] = {'member': owner, 'commands': [(x.name, x.message)]}
else:
warn_notifs[owner.id]['commands'].append((x.name, x.message))
# else: all_commands = all_commands_resp.get('custom_commands', [])
# logger.warning(
# f'Command <!cc {x.name}> last used {x.last_used} / delta: {now - x.last_used} \n/>60 days: '
# f'{x.last_used + timedelta(days=60) < now} / sent_warns: {x.sent_warns}'
# )
db.close() # {discord_id: {'member': <discord member>, 'commands': [(name, content)]}}
logger.info(f'deletions: {del_notifs}\nwarnings: {warn_notifs}') del_notifs = {}
del_counter = 0
warn_notifs = {}
now = datetime.now()
for member in del_notifs: for cmd in all_commands:
plural = len(del_notifs[member]["commands"]) > 1 # Parse last_used datetime
msg_content = f'Yo, it\'s cleanup time. I am deleting the following custom ' \
f'command{"s" if plural else ""}:\n\n'
short_msg_content = copy.deepcopy(msg_content)
for x in del_notifs[member]["commands"]:
msg_content += f'`!cc {x[0]}` - {x[1]}\n'
short_msg_content += f'`!cc {x[0]}`\n'
try:
await del_notifs[member]['member'].send(msg_content)
except Exception as e:
logger.error(f'fun daily_check - could not send deletion message to {del_notifs[member]["member"]} '
f'/ trying short_msg')
try: try:
await del_notifs[member]['member'].send(short_msg_content) last_used = datetime.fromisoformat(cmd['last_used']) if cmd.get('last_used') else now
except Exception as e: except:
logger.error(f'fun daily_check - still could not send deletion message') last_used = now
for member in warn_notifs: # Final check / deleted (90+ days)
plural = len(warn_notifs[member]["commands"]) > 1 if last_used + timedelta(days=90) < now:
msg_content = f'Heads up, the following custom ' \ logger.warning(f'Deleting `!cc {cmd["name"]}`')
f'command{"s" if plural else ""} will be deleted next month if ' \ creator_discord_id = cmd['creator']['discord_id'] if cmd.get('creator') else None
f'{"they are" if plural else "it is"} not used:\n\n'
short_msg_content = copy.deepcopy(msg_content) if creator_discord_id:
for x in warn_notifs[member]["commands"]: owner = guild.get_member(creator_discord_id)
msg_content += f'`!cc {x[0]}` - {x[1]}\n' if owner:
short_msg_content += f'`!cc {x[0]}`\n' if owner.id not in del_notifs:
del_notifs[owner.id] = {'member': owner, 'commands': [(cmd['name'], cmd['content'])]}
try: else:
await warn_notifs[member]['member'].send(msg_content) del_notifs[owner.id]['commands'].append((cmd['name'], cmd['content']))
except Exception as e:
logger.error(f'fun daily_check - could not send warn message to {warn_notifs[member]["member"]} ' await update_custom_command(cmd['id'], {'active': False})
f'/ trying short_msg') del_counter += 1
try:
await warn_notifs[member]['member'].send(short_msg_content) # Warning (60+ days, not warned yet)
except Exception as e: elif last_used + timedelta(days=60) < now and not cmd.get('warning_sent', False):
logger.error(f'fun daily_check - still could not send warn message') logger.warning(f'Warning for `!cc {cmd["name"]}`')
creator_discord_id = cmd['creator']['discord_id'] if cmd.get('creator') else None
if creator_discord_id:
owner = guild.get_member(creator_discord_id)
if owner:
if owner.id not in warn_notifs:
warn_notifs[owner.id] = {'member': owner, 'commands': [(cmd['name'], cmd['content'])]}
else:
warn_notifs[owner.id]['commands'].append((cmd['name'], cmd['content']))
# Mark warning as sent
from db_calls import update_custom_command
await update_custom_command(cmd['id'], {'warning_sent': True})
logger.info(f'deletions: {del_notifs}\nwarnings: {warn_notifs}')
# Send deletion notifications
for member_id in del_notifs:
plural = len(del_notifs[member_id]["commands"]) > 1
msg_content = f'Yo, it\'s cleanup time. I am deleting the following custom ' \
f'command{"s" if plural else ""}:\n\n'
short_msg_content = copy.deepcopy(msg_content)
for x in del_notifs[member_id]["commands"]:
msg_content += f'`!cc {x[0]}` - {x[1]}\n'
short_msg_content += f'`!cc {x[0]}`\n'
try:
await del_notifs[member_id]['member'].send(msg_content)
except Exception as e:
logger.error(f'fun daily_check - could not send deletion message to {del_notifs[member_id]["member"]} '
f'/ trying short_msg')
try:
await del_notifs[member_id]['member'].send(short_msg_content)
except Exception as e:
logger.error(f'fun daily_check - still could not send deletion message')
# Send warning notifications
for member_id in warn_notifs:
plural = len(warn_notifs[member_id]["commands"]) > 1
msg_content = f'Heads up, the following custom ' \
f'command{"s" if plural else ""} will be deleted next month if ' \
f'{"they are" if plural else "it is"} not used:\n\n'
short_msg_content = copy.deepcopy(msg_content)
for x in warn_notifs[member_id]["commands"]:
msg_content += f'`!cc {x[0]}` - {x[1]}\n'
short_msg_content += f'`!cc {x[0]}`\n'
try:
await warn_notifs[member_id]['member'].send(msg_content)
except Exception as e:
logger.error(f'fun daily_check - could not send warn message to {warn_notifs[member_id]["member"]} '
f'/ trying short_msg')
try:
await warn_notifs[member_id]['member'].send(short_msg_content)
except Exception as e:
logger.error(f'fun daily_check - still could not send warn message')
logger.info(f'Deleted {del_counter} commands; sent deletion notifications to {len(del_notifs)} users; '
f'sent warnings to {len(warn_notifs)} users')
except Exception as e:
logger.error(f'Error during daily_check: {e}', exc_info=True)
logger.info(f'Deleted {del_counter} commands; sent deletion notifications to {len(del_notifs)} users; '
f'sent warnings to {len(warn_notifs)} users')
async def cog_command_error(self, ctx, error): async def cog_command_error(self, ctx, error):
logger.error(msg=error, stack_info=True, exc_info=True) logger.error(msg=error, stack_info=True, exc_info=True)
await ctx.send(f'{error}\n\nRun !help <command_name> to see the command requirements') await ctx.send(f'{error}\n\nRun !help <command_name> to see the command requirements')
@ -175,7 +191,7 @@ class Fun(commands.Cog):
async def slash_error(self, ctx, error): async def slash_error(self, ctx, error):
logger.error(msg=error, stack_info=True, exc_info=True) logger.error(msg=error, stack_info=True, exc_info=True)
await ctx.send(f'{error[:1600]}') await ctx.send(f'{error[:1600]}')
@commands.Cog.listener(name='on_message') @commands.Cog.listener(name='on_message')
async def on_message_listener(self, message): async def on_message_listener(self, message):
if message.author.bot or message.channel.guild.id != int(os.environ.get('GUILD_ID')) \ if message.author.bot or message.channel.guild.id != int(os.environ.get('GUILD_ID')) \
@ -194,35 +210,292 @@ class Fun(commands.Cog):
).execute() ).execute()
db.close() db.close()
time_since = datetime.now() - last_soak.timestamp if last_soak:
# logger.info(f'time_since: {time_since} / seconds: {time_since.seconds} / days: {time_since.days}') time_since = datetime.now() - last_soak.timestamp
gif_search = None # logger.info(f'time_since: {time_since} / seconds: {time_since.seconds} / days: {time_since.days}')
if time_since.days >= 2: gif_search = None
ts_string = f'{time_since.days} days' if time_since.days >= 2:
if time_since.days > 30: ts_string = f'{time_since.days} days'
gif_search = 'elite' if time_since.days > 30:
elif time_since.days > 14: gif_search = 'elite'
gif_search = 'pretty good' elif time_since.days > 14:
else: gif_search = 'pretty good'
if time_since.seconds >= 7200:
ts_string = f'{time_since.seconds // 3600} hours'
gif_search = 'whats wrong with you'
else: else:
if time_since.seconds >= 120: if time_since.seconds >= 7200:
ts_string = f'{time_since.seconds // 60} minutes' ts_string = f'{time_since.seconds // 3600} hours'
gif_search = 'whats wrong with you'
else: else:
ts_string = f'{time_since.seconds} seconds' if time_since.seconds >= 120:
gif_search = 'pathetic' ts_string = f'{time_since.seconds // 60} minutes'
else:
ts_string = f'{time_since.seconds} seconds'
gif_search = 'pathetic'
await message.channel.send( await message.channel.send(
f'It has been {ts_string} since soaking was mentioned.' f'It has been {ts_string} since soaking was mentioned.'
)
if gif_search is not None:
try:
await message.channel.send(random_gif(gif_search))
except Exception as e:
logger.error(e)
@commands.command(name='cc', help='Run custom command')
async def custom_command(self, ctx, command):
try:
# Execute the command (updates usage stats automatically)
result = await execute_custom_command(command.lower())
if not result:
# Kermit lost gif
await ctx.send('https://tenor.com/6saQ.gif')
return
# Special easter egg for prettyrainbow command
if result['name'] == 'prettyrainbow' and ctx.author.id == 291738770313707521:
await ctx.send(random_no_phrase())
return
await ctx.send(result['content'])
except Exception as e:
logger.error(f'Error executing custom command {command}: {e}')
# Kermit lost gif
await ctx.send('https://tenor.com/6saQ.gif')
@commands.command(name='about', help='Who made the custom command')
async def about_command(self, ctx, command):
try:
result = await get_custom_command_by_name(command.lower())
if not result:
await ctx.send('https://tenor.com/blQnd.gif')
return
creator_name = result['creator']['username'] if result.get('creator') else 'Unknown'
created_at = result.get('created_at', 'Unknown')
embed = discord.Embed(title=f'About {result["name"].title()}', color=0xFFFF00)
embed.add_field(name=f'Creator', value=creator_name, inline=False)
embed.add_field(name='Creation Date', value=created_at, inline=False)
embed.add_field(name='Message', value=result['content'], inline=False)
await ctx.send(content=None, embed=embed)
except Exception as e:
logger.error(f'Error getting command info: {e}')
await ctx.send('https://tenor.com/blQnd.gif')
@commands.command(name='newcc', help='Create a new custom command')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, 'Paper Dynasty Players')
async def new_custom_command(self, ctx, name, *, message):
try:
command_name = name.lower().strip()
command_content = message.strip()
# Check if command already exists
existing = await get_custom_command_by_name(command_name)
if existing:
await ctx.send('There is already a command with that name!')
return
# Show preview
embed = discord.Embed(title='Is this what you want?', color=0x91329F)
embed.add_field(name='Command Name', value=command_name, inline=False)
embed.add_field(name='Message', value=command_content, inline=False)
await ctx.send(content=None, embed=embed)
view = Confirm(responders=[ctx.author])
question = await ctx.send('Should I create this for you?', view=view)
await view.wait()
if not view.value:
await question.edit(content='You keep thinking on it.', view=None)
return
# Get or create creator
creator = await get_or_create_creator(
discord_id=ctx.author.id,
username=ctx.author.name,
display_name=ctx.author.display_name
) )
if gif_search is not None: # Create command
try: command_data = {
await message.channel.send(random_gif(gif_search)) 'name': command_name,
except Exception as e: 'content': command_content,
logger.error(e) 'creator_id': creator['id']
}
result = await create_custom_command(command_data)
if result:
await question.edit(content=f'`!cc {command_name}` is now a thing!', view=None)
else:
await question.edit(content='Hmm...I couldn\'t add that. I might need a grown up to help.', view=None)
except Exception as e:
logger.error(f'Error creating custom command: {e}', exc_info=True)
await ctx.send('Something went wrong creating that command. Try again or ask an admin for help.')
@commands.command(name='delcc', help='Delete a custom command')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, 'Paper Dynasty Players')
async def delete_custom_command_cmd(self, ctx, name):
try:
command_name = name.lower().strip()
this_command = await get_custom_command_by_name(command_name)
if not this_command:
await ctx.send('I couldn\'t find that command, sorry.')
return
# Check ownership
creator_discord_id = this_command['creator']['discord_id'] if this_command.get('creator') else None
if creator_discord_id != ctx.author.id and ctx.author.id != self.bot.owner_id:
await ctx.send('Looks like this isn\'t your command to delete.')
return
embed = discord.Embed(title='Do you want to delete this command?', color=0x91329F)
embed.add_field(name='Command Name', value=this_command['name'], inline=False)
embed.add_field(name='Message', value=this_command['content'], inline=False)
view = Confirm(responders=[ctx.author])
question = await ctx.send(content=None, embed=embed, view=view)
await view.wait()
if not view.value:
await question.edit(content='It stays for now.', view=None)
return
result = await delete_custom_command(this_command['id'])
if result:
await question.edit(view=None)
await ctx.send('He gone!')
else:
await ctx.send('Welp. That didn\'t work. Go complain to an adult, I guess.')
except Exception as e:
logger.error(f'Error deleting custom command: {e}', exc_info=True)
await ctx.send('Something went wrong deleting that command.')
@commands.command(name='allcc', help='Show all custom commands')
async def show_custom_commands(self, ctx, page=1):
try:
def get_embed(this_page, result_data):
this_embed = discord.Embed(title=f'All Custom Commands', color=0x2F939F)
column_one = ''
column_two = ''
commands_list = result_data.get('custom_commands', [])
# First 20 commands in first column
for x in range(min(20, len(commands_list))):
try:
cmd = commands_list[x]
creator_name = cmd['creator']['username'] if cmd.get('creator') else 'Unknown'
column_one += f'**{cmd["name"]}** by {creator_name}\n'
except Exception as e:
logger.error(f'Error building !allcc embed: {e}')
break
if column_one:
this_embed.add_field(
name=f'{(this_page - 1) * 40 + 1}-{min(this_page * 40 - 20, result_data["total_count"])}',
value=column_one
)
# Next 20 commands in second column
for x in range(20, min(40, len(commands_list))):
try:
cmd = commands_list[x]
creator_name = cmd['creator']['username'] if cmd.get('creator') else 'Unknown'
column_two += f'**{cmd["name"]}** by {creator_name}\n'
except Exception as e:
logger.error(f'Error building !allcc embed: {e}')
break
if column_two:
this_embed.add_field(
name=f'{(this_page - 1) * 40 + 21}-{min(this_page * 40, result_data["total_count"])}',
value=column_two
)
return this_embed
page_num = page
result = await get_all_custom_commands(page=page_num, page_size=40)
if not result:
await ctx.send('No custom commands found!')
return
total_count = result.get('total_count', 0)
last_page = result.get('total_pages', 1)
if page_num > last_page:
await ctx.send(f'The max page number is {last_page}; going there now!')
page_num = last_page
result = await get_all_custom_commands(page=page_num, page_size=40)
embed = get_embed(page_num, result)
embed.description = f'Page {page_num} / {last_page}'
view = Pagination(responders=[ctx.author])
resp_message = await ctx.send(content=None, embed=embed, view=view)
while True:
await view.wait()
if view.value:
logger.info(f'got a value: {view.value}')
if view.value == 'left':
page_num = page_num - 1 if page_num > 1 else last_page
elif view.value == 'right':
page_num = page_num + 1 if page_num < last_page else 1
elif view.value == 'cancel':
await resp_message.edit(content=None, embed=embed, view=None)
break
view.value = None
else:
await resp_message.edit(content=None, embed=embed, view=None)
break
# Get new page data
result = await get_all_custom_commands(page=page_num, page_size=40)
embed = get_embed(page_num, result)
embed.description = f'Page {page_num} / {last_page}'
view = Pagination(responders=[ctx.author])
await resp_message.edit(content=None, embed=embed, view=view)
except Exception as e:
logger.error(f'Error showing all custom commands: {e}', exc_info=True)
await ctx.send('Something went wrong fetching the command list.')
@commands.command(name='mycc', aliases=['showcc'], help='Show my commands')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, 'Paper Dynasty Players')
async def my_custom_commands(self, ctx):
try:
result = await get_commands_by_creator(discord_id=ctx.author.id, page=1, page_size=100)
if not result or result.get('total_count', 0) == 0:
await ctx.send('It doesn\'t look like you\'ve created any custom commands. Try it out by running '
'!help newcc for the command syntax!')
return
commands_list = result.get('custom_commands', [])
comm_message = ''
for cmd in commands_list:
comm_message += f'{cmd["name"]}\n'
embed = discord.Embed(title=f'{ctx.author.name}\'s Commands', color=0x2F939F)
embed.add_field(name=f'Command Names', value=comm_message if comm_message else 'None', inline=False)
await ctx.send(content=None, embed=embed)
except Exception as e:
logger.error(f'Error showing user commands: {e}', exc_info=True)
await ctx.send('Something went wrong fetching your commands.')
@commands.command(name='lastsoak', aliases=['ls'], help='Get a link to the last mention of soaking') @commands.command(name='lastsoak', aliases=['ls'], help='Get a link to the last mention of soaking')
async def last_soak_command(self, ctx): async def last_soak_command(self, ctx):
@ -231,209 +504,11 @@ class Fun(commands.Cog):
last_soak = squery[0] last_soak = squery[0]
else: else:
await ctx.send(f'I could not find the last mention of soaking.') await ctx.send(f'I could not find the last mention of soaking.')
db.close()
return return
message = await ctx.fetch_message(last_soak.message_id) message = await ctx.fetch_message(last_soak.message_id)
await ctx.send(f'The last mention of soaking was: {message.jump_url}') await ctx.send(f'The last mention of soaking was: {message.jump_url}')
@commands.command(name='cc', help='Run custom custom command')
async def custom_command(self, ctx, command):
chosen = Command.get_or_none(fn.Lower(Command.name) == command.lower())
if not chosen:
# Error gif
# await ctx.send('https://tenor.com/blQnd.gif')
# Schitt's Creek 'what's that' gif
# await ctx.send('https://media.giphy.com/media/l0HUhFZx6q0hsPtHq/giphy.gif')
# Kermit lost gif
await ctx.send('https://tenor.com/6saQ.gif')
else:
if chosen.name == 'prettyrainbow' and ctx.author.id == 291738770313707521:
await ctx.send(random_no_phrase())
return
await ctx.send(chosen.message)
chosen.last_used = datetime.now()
chosen.sent_warns = 0
chosen.save()
db.close()
@commands.command(name='about', help='Who made the custom command')
async def about_command(self, ctx, command):
chosen = Command.get_or_none(fn.Lower(Command.name) == command.lower())
if not chosen:
await ctx.send('https://tenor.com/blQnd.gif')
embed = discord.Embed(title=f'About {chosen.name.title()}', color=0xFFFF00)
embed.add_field(name=f'Creator', value=f'{chosen.creator.name}', inline=False)
embed.add_field(name='Creation Date', value=f'{chosen.createtime}', inline=False)
embed.add_field(name='Message', value=f'{chosen.message}', inline=False)
await ctx.send(content=None, embed=embed)
db.close()
@commands.command(name='newcc', help='Create a new custom command')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, 'Paper Dynasty Players')
async def new_custom_command(self, ctx, name, *, message):
time = datetime.now()
command = name
comm_message = message
chosen = Command.get_or_none(fn.Lower(Command.name) == command.lower())
if chosen:
await ctx.send('There is already a command with that name!')
return
embed = discord.Embed(title='Is this what you want?', color=0x91329F)
embed.add_field(name='Command Name', value=command, inline=False)
embed.add_field(name='Message', value=comm_message, inline=False)
await ctx.send(content=None, embed=embed)
view = Confirm(responders=[ctx.author])
question = await ctx.send('Should I create this for you?', view=view)
await view.wait()
if not view.value:
await question.edit(content='You keep thinking on it.', view=None)
return
this_person = Creator.get_or_none(Creator.discordid == ctx.author.id)
if not this_person:
this_person = Creator(name=f'{ctx.author.name}', discordid=f'{ctx.author.id}')
this_person.save()
this_command = Command(name=command, message=comm_message, createtime=time, creator=this_person, last_used=time)
if this_command.save() == 1:
await question.edit(content=f'`!cc {this_command.name}` is now a thing!', view=None)
else:
await question.edit(content='Hmm...I couldn\'t add that. I might need a grown up to help.', view=None)
db.close()
@commands.command(name='delcc', help='Delete a custom command')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, 'Paper Dynasty Players')
async def delete_custom_command(self, ctx, name):
this_command = Command.get_or_none(fn.Lower(Command.name) == name.lower())
if not this_command:
await ctx.send('I couldn\'t find that command, sorry.')
return
if this_command.creator.discordid != ctx.author.id and ctx.author.id != self.bot.owner_id:
await ctx.send('Looks like this isn\'t your command to delete.')
return
embed = discord.Embed(title='Do you want to delete this command?', color=0x91329F)
embed.add_field(name='Command Name', value=this_command.name, inline=False)
embed.add_field(name='Message', value=this_command.message, inline=False)
view = Confirm(responders=[ctx.author])
question = await ctx.send(content=None, embed=embed, view=view)
await view.wait()
if not view.value:
await question.edit(content='It stays for now.', view=None)
return
if this_command.delete_instance() == 1:
await question.edit(view=None)
await ctx.send('He gone!')
else:
await ctx.send('Welp. That didn\'t work. Go complain to an adult, I guess.')
db.close()
@commands.command(name='allcc', help='Show all custom commands')
async def show_custom_commands(self, ctx, page=1):
def get_embed(this_page):
this_embed = discord.Embed(title=f'All Custom Commands', color=0x2F939F)
column_one = ''
column_two = ''
all_commands = Command.select().paginate(this_page, 40).order_by(Command.name)
for x in range(20):
try:
column_one += f'**{all_commands[x].name}** by {all_commands[x].creator.name}\n'
except Exception as e:
logger.error(f'Error building !allcc embed: {e}')
break
this_embed.add_field(name=f'{(this_page - 1) * 40 + 1}-{this_page * 40 - 20}', value=column_one)
for x in range(20, 40):
try:
column_two += f'**{all_commands[x].name}** by {all_commands[x].creator.name}\n'
except Exception as e:
logger.error(f'Error building !allcc embed: {e}')
break
if len(column_two) > 0:
this_embed.add_field(name=f'{(this_page - 1) * 40 + 21}-{this_page * 40}', value=column_two)
return this_embed
page_num = page
total_commands = Command.select(Command.id)
last_page = math.ceil(total_commands.count()/40)
if page_num > last_page:
await ctx.send(f'The max page number is {last_page}; going there now!')
page_num = last_page
embed = get_embed(page_num)
embed.description = f'Page {page_num} / {last_page}'
view = Pagination(responders=[ctx.author])
resp_message = await ctx.send(content=None, embed=embed, view=view)
while True:
await view.wait()
if view.value:
logger.info(f'got a value: {view.value}')
if view.value == 'left':
page_num = page_num - 1 if page_num > 1 else last_page
elif view.value == 'right':
page_num = page_num + 1 if page_num <= last_page else 1
elif view.value == 'cancel':
await resp_message.edit(content=None, embed=embed, view=None)
break
view.value = None
else:
await resp_message.edit(content=None, embed=embed, view=None)
break
# await resp_message.edit(content=None, embed=embed, view=None)
embed = get_embed(page_num)
embed.description = f'Page {page_num} / {last_page}'
view = Pagination(responders=[ctx.author])
await resp_message.edit(content=None, embed=embed, view=view)
db.close()
@commands.command(name='mycc', aliases=['showcc'], help='Show my commands')
@commands.has_any_role(SBA_PLAYERS_ROLE_NAME, 'Paper Dynasty Players')
async def my_custom_commands(self, ctx):
this_creator = Creator.get_or_none(Creator.discordid == ctx.author.id)
if not this_creator:
await ctx.send('It doesn\'t look like you\'ve created any custom commands. Try it out by running the '
'!help newcc for the command syntax!')
return
all_commands = Command.select().join(Creator).where(Command.creator == this_creator)
if all_commands.count() == 0:
await ctx.send('It doesn\'t look like you\'ve created any custom commands. Try it out by running the '
'!help newcc for the command syntax!')
return
comm_message = ''
for x in all_commands:
comm_message += f'{x.name}\n'
embed = discord.Embed(title=f'{ctx.author.name}\'s Commands', color=0x2F939F)
embed.add_field(name=f'Command Names', value=comm_message, inline=False)
await ctx.send(content=None, embed=embed)
db.close() db.close()
@app_commands.command(name='woulditdong', description='Log a dinger to see would it dong across SBa') @app_commands.command(name='woulditdong', description='Log a dinger to see would it dong across SBa')
@ -475,98 +550,6 @@ class Fun(commands.Cog):
await send_to_channel(self.bot, 'news-ticker', content=None, embed=embed) await send_to_channel(self.bot, 'news-ticker', content=None, embed=embed)
await interaction.edit_original_response(content=None, embed=embed) await interaction.edit_original_response(content=None, embed=embed)
# @commands.command(name='showcc', help='Show one person\'s custom commands')
# @commands.has_any_role(SBA_PLAYERS_ROLE_NAME, 'Paper Dynasty Players')
# async def show_cc_command(self, ctx, ):
# @commands.command(name='role', help='Toggle role')
# async def toggle_role_command(self, ctx, *, role_name):
# all_roles = [x.name for x in Roles.select().where(Roles.enabled)]
#
# async def toggle_role(full_role):
# if full_role in ctx.author.roles:
# await ctx.author.remove_roles(full_role)
# else:
# await ctx.author.add_roles(full_role)
#
# if len(role_name) < 4:
# await ctx.send('https://thumbs.gfycat.com/FrayedUnequaledGnat-size_restricted.gif')
# await ctx.send(f'What even is **{role_name}**...')
# db.close()
# return
#
# for name in all_roles:
# if role_name.lower() in name.lower():
# try:
# this_role = discord.utils.get(ctx.guild.roles, name=name)
# await toggle_role(this_role)
# await ctx.send(random_conf_gif())
# return
# except:
# await ctx.send(await get_emoji(ctx, 'fforrespect', False))
# await ctx.send('I was not able to assign that role.')
# return
#
# await ctx.send(f'That doesn\'t sound familiar. **{role_name}**...did you make that shit up?')
# @commands.command(name='showroles', help='Show toggleable roles')
# async def show_roles_command(self, ctx):
# all_roles = [x.name for x in Roles.select().where(Roles.enabled)]
# role_string = '\n- '.join(all_roles)
#
# embed = get_team_embed('Toggleable Roles', thumbnail=False)
# embed.description = 'Run !role <role_name> to toggle the role on or off'
# embed.add_field(name='Role Names', value=f'- {role_string}')
#
# await ctx.send(content=None, embed=embed)
# @commands.command(name='newrole', aliases=['removerole'], help='Make toggleable role')
# @commands.is_owner()
# async def make_toggleable_role_command(self, ctx, *, role_name):
# this_role = Roles.get_or_none(Roles.name == role_name)
#
# if not this_role:
# # Create the role if it doesn't exist
#
# this_role = Roles(name=role_name)
# this_role.save()
# if not discord.utils.get(ctx.guild.roles, name=this_role.name):
# await ctx.guild.create_role(name=f'{role_name}', mentionable=True)
# else:
# # Disable the role
#
# if this_role.enabled:
# this_role.enabled = False
# else:
# this_role.enabled = True
# this_role.save()
# this_role = discord.utils.get(ctx.guild.roles, name=this_role.name)
#
# if this_role:
# await this_role.edit(mentionable=False)
# else:
# await ctx.send('That role doesn\'t exist in the server.')
#
# await ctx.send(random_conf_gif())
# @commands.command(name='bulkrole', hidden=True)
# @commands.is_owner()
# async def bulkrole_command(self, ctx, *roles):
# all_roles = []
#
# for x in roles:
# all_roles.append(discord.utils.get(ctx.guild.roles, name=x))
#
# await ctx.send('On it. This could take a bit.')
# time_start = datetime.now()
#
# async for member in ctx.guild.fetch_members():
# logger.warning(f'member: {member}')
# await member.add_roles(*all_roles)
#
# time_end = datetime.now()
# await ctx.send(f'All done! That took {time_end - time_start}')
async def setup(bot): async def setup(bot):
await bot.add_cog(Fun(bot)) await bot.add_cog(Fun(bot))

View File

@ -236,6 +236,74 @@ async def get_player_headshot(player_name):
return await get_player_photo(player_name) return await get_player_photo(player_name)
###
# CUSTOM COMMANDS API FUNCTIONS
###
async def get_custom_command_by_name(name: str):
"""Get a custom command by name."""
return await db_get(f'custom_commands/by_name/{name}', none_okay=True)
async def execute_custom_command(name: str):
"""Execute a custom command and update usage statistics."""
return await db_patch(f'custom_commands/by_name/{name}/execute', object_id=None, params=[])
async def create_custom_command(command_data: dict):
"""Create a new custom command."""
return await db_post('custom_commands', payload=command_data)
async def update_custom_command(command_id: int, update_data: dict):
"""Update an existing custom command."""
return await db_put('custom_commands', object_id=command_id, payload=update_data)
async def delete_custom_command(command_id: int):
"""Delete a custom command."""
return await db_delete('custom_commands', object_id=command_id)
async def get_commands_by_creator(discord_id: int, page: int = 1, page_size: int = 25):
"""Get all commands created by a specific Discord user."""
params = [
('creator_discord_id', discord_id),
('is_active', True),
('page', page),
('page_size', page_size)
]
return await db_get('custom_commands', params=params, none_okay=True)
async def get_all_custom_commands(page: int = 1, page_size: int = 40, sort: str = 'name'):
"""Get all custom commands with pagination."""
params = [
('is_active', True),
('sort', sort),
('page', page),
('page_size', page_size)
]
return await db_get('custom_commands', params=params, none_okay=True)
async def get_or_create_creator(discord_id: int, username: str, display_name: Optional[str] = None):
"""Get existing creator or create a new one."""
# Try to get existing creator
existing = await db_get('custom_commands/creators', params=[('discord_id', discord_id)], none_okay=True)
if existing and existing.get('creators') and len(existing['creators']) > 0:
return existing['creators'][0]
# Create new creator
creator_data = {
'discord_id': discord_id,
'username': username,
'display_name': display_name
}
return await db_post('custom_commands/creators', payload=creator_data)
### ###
# TO BE DEPRECATED FUNCTIONS # TO BE DEPRECATED FUNCTIONS
### ###

BIN
sba_is_fun.db Normal file

Binary file not shown.