Add comprehensive "soaking" easter egg feature that detects mentions and responds with GIFs showing escalating disappointment based on recency (reversed from legacy - more recent = more disappointed). Features: - Detects "soak", "soaking", "soaked", "soaker" (case-insensitive) - 7 disappointment tiers with 5 varied search phrases each - Giphy API integration with Trump filter and fallback handling - JSON-based persistence tracking all mentions with history - /lastsoak command showing detailed information - 25 comprehensive unit tests (all passing) Architecture: - commands/soak/giphy_service.py - Tiered GIF fetching - commands/soak/tracker.py - JSON persistence with history - commands/soak/listener.py - Message detection and response - commands/soak/info.py - /lastsoak info command - tests/test_commands_soak.py - Full test coverage Uses existing Giphy API key from legacy implementation. Zero new dependencies, follows established patterns. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
128 lines
4.4 KiB
Python
128 lines
4.4 KiB
Python
"""
|
|
Soak Info Commands
|
|
|
|
Provides information about soak mentions without triggering the easter egg.
|
|
"""
|
|
import discord
|
|
from discord import app_commands
|
|
from discord.ext import commands
|
|
|
|
from utils.decorators import logged_command
|
|
from utils.logging import get_contextual_logger
|
|
from views.embeds import EmbedTemplate, EmbedColors
|
|
from .tracker import SoakTracker
|
|
from .giphy_service import get_tier_for_seconds, get_tier_description
|
|
|
|
|
|
class SoakInfoCommands(commands.Cog):
|
|
"""Soak information command handlers."""
|
|
|
|
def __init__(self, bot: commands.Bot):
|
|
self.bot = bot
|
|
self.logger = get_contextual_logger(f'{__name__}.SoakInfoCommands')
|
|
self.tracker = SoakTracker()
|
|
self.logger.info("SoakInfoCommands cog initialized")
|
|
|
|
@app_commands.command(name="lastsoak", description="Get information about the last soak mention")
|
|
@logged_command("/lastsoak")
|
|
async def last_soak(self, interaction: discord.Interaction):
|
|
"""Show information about the last soak mention."""
|
|
await interaction.response.defer(ephemeral=True)
|
|
|
|
last_soak = self.tracker.get_last_soak()
|
|
|
|
# Handle case where soak has never been mentioned
|
|
if not last_soak:
|
|
embed = EmbedTemplate.info(
|
|
title="📊 Last Soak",
|
|
description="No one has said the forbidden word yet. 🤫"
|
|
)
|
|
embed.add_field(
|
|
name="Total Mentions",
|
|
value="0",
|
|
inline=False
|
|
)
|
|
await interaction.followup.send(embed=embed)
|
|
return
|
|
|
|
# Calculate time since last soak
|
|
time_since = self.tracker.get_time_since_last_soak()
|
|
total_count = self.tracker.get_soak_count()
|
|
|
|
# Determine disappointment tier
|
|
tier_key = get_tier_for_seconds(int(time_since.total_seconds()) if time_since else None)
|
|
tier_description = get_tier_description(tier_key)
|
|
|
|
# Create embed
|
|
embed = EmbedTemplate.create_base_embed(
|
|
title="📊 Last Soak",
|
|
description="Information about the most recent soak mention",
|
|
color=EmbedColors.INFO
|
|
)
|
|
|
|
# Parse timestamp for Discord formatting
|
|
try:
|
|
from datetime import datetime
|
|
timestamp_str = last_soak["timestamp"]
|
|
if timestamp_str.endswith('Z'):
|
|
timestamp_str = timestamp_str[:-1] + '+00:00'
|
|
timestamp = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
|
|
unix_timestamp = int(timestamp.timestamp())
|
|
|
|
# Add relative time with warning if very recent
|
|
time_field_value = f"<t:{unix_timestamp}:R>"
|
|
if time_since and time_since.total_seconds() < 1800: # Less than 30 minutes
|
|
time_field_value += "\n\n😤 Way too soon!"
|
|
|
|
embed.add_field(
|
|
name="Last Mentioned",
|
|
value=time_field_value,
|
|
inline=False
|
|
)
|
|
except Exception as e:
|
|
self.logger.error(f"Error parsing timestamp: {e}")
|
|
embed.add_field(
|
|
name="Last Mentioned",
|
|
value="Error parsing timestamp",
|
|
inline=False
|
|
)
|
|
|
|
# Add user info
|
|
user_mention = f"<@{last_soak['user_id']}>"
|
|
display_name = last_soak.get('display_name', last_soak.get('username', 'Unknown'))
|
|
embed.add_field(
|
|
name="By",
|
|
value=f"{user_mention} ({display_name})",
|
|
inline=True
|
|
)
|
|
|
|
# Add message link
|
|
try:
|
|
guild_id = interaction.guild_id
|
|
channel_id = last_soak['channel_id']
|
|
message_id = last_soak['message_id']
|
|
jump_url = f"https://discord.com/channels/{guild_id}/{channel_id}/{message_id}"
|
|
embed.add_field(
|
|
name="Message",
|
|
value=f"[Jump to message]({jump_url})",
|
|
inline=True
|
|
)
|
|
except Exception as e:
|
|
self.logger.error(f"Error creating jump URL: {e}")
|
|
|
|
# Add total count
|
|
embed.add_field(
|
|
name="Total Mentions",
|
|
value=str(total_count),
|
|
inline=True
|
|
)
|
|
|
|
# Add disappointment level
|
|
embed.add_field(
|
|
name="Disappointment Level",
|
|
value=f"{tier_key.replace('_', ' ').title()}: {tier_description}",
|
|
inline=False
|
|
)
|
|
|
|
await interaction.followup.send(embed=embed)
|