feat: Implement command usage tracking

Introduces a new `command_usage_counters` table to track how many times specific commands are used between two users.

- Adds `_ensure_usage_table_exists` to create the table if it doesn't exist.
- Implements `_increment_usage_counter` to update or insert usage counts.
- Adds `_get_usage_count` to retrieve usage counts.
- Integrates usage tracking into the `molest` command (both slash and legacy).
- Adds logging for database operations related to usage tracking.
This commit is contained in:
Slipstream 2025-05-26 21:45:30 -06:00
parent 8eb52a925b
commit 1a6330537f
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD
4 changed files with 387 additions and 1 deletions

125
USAGE_COUNTERS_README.md Normal file
View File

@ -0,0 +1,125 @@
# Usage Counters Implementation
## Overview
This implementation adds usage counters to track command usage between two users in the Discord bot's message cogs. The system tracks how many times each command has been used between specific user pairs.
## Database Schema
A new table `command_usage_counters` has been added with the following structure:
```sql
CREATE TABLE IF NOT EXISTS command_usage_counters (
user1_id BIGINT NOT NULL, -- The user who initiated the command
user2_id BIGINT NOT NULL, -- The target user of the command
command_name TEXT NOT NULL, -- The name of the command
usage_count INTEGER NOT NULL DEFAULT 1, -- Number of times used
PRIMARY KEY (user1_id, user2_id, command_name)
);
```
## Modified Files
### 1. `cogs/message_cog.py`
- Added usage tracking to all commands that involve two users:
- `molest` (slash and legacy)
- `rape` (slash and legacy)
- `sex` (slash and legacy)
- Commands without target users (like `seals`, `notlikeus`, `pmo`) are not tracked
### 2. `cogs/neru_message_cog.py`
- Added usage tracking to all commands that involve two users:
- `sex` (slash and legacy) - tracked as `neru_sex`
- `rape` (slash command only) - tracked as `neru_rape`
- `kiss` (slash and legacy) - tracked as `neru_kiss`
- `hug` (slash and legacy) - tracked as `neru_hug`
- Commands without target users (like `seals`, `notlikeus`, `pmo`) are not tracked
## Implementation Details
### Helper Methods Added
Each cog now includes three helper methods:
1. **`_ensure_usage_table_exists()`**
- Creates the usage counters table if it doesn't exist
- Returns `True` if successful, `False` if database unavailable
2. **`_increment_usage_counter(user1_id, user2_id, command_name)`**
- Increments the usage counter for a specific user pair and command
- Uses PostgreSQL's `ON CONFLICT` to handle upserts efficiently
- Logs debug information about counter increments
3. **`_get_usage_count(user1_id, user2_id, command_name)`**
- Retrieves the current usage count for a specific combination
- Returns 0 if no record exists or if database is unavailable
### Command Naming Convention
To distinguish between commands from different cogs, the neru_message_cog commands are prefixed with `neru_`:
- `sex``neru_sex`
- `rape``neru_rape`
- `kiss``neru_kiss`
- `hug``neru_hug`
### Database Connection Pattern
The implementation follows the existing codebase pattern:
- Uses `self.bot.pg_pool` for database connections
- Handles connection failures gracefully
- Logs errors appropriately
- Uses async context managers for proper connection handling
## Usage Examples
### Querying Usage Data
```python
# Get all usage data
SELECT user1_id, user2_id, command_name, usage_count
FROM command_usage_counters
ORDER BY usage_count DESC;
# Get usage between specific users
SELECT command_name, usage_count
FROM command_usage_counters
WHERE user1_id = $1 AND user2_id = $2;
# Get most popular commands
SELECT command_name, SUM(usage_count) as total_usage
FROM command_usage_counters
GROUP BY command_name
ORDER BY total_usage DESC;
```
### Testing
A test script `test_usage_counters.py` is provided to:
- Verify database connectivity
- Check if the table exists
- Display sample usage data
- Query usage between specific users
## Error Handling
The implementation includes robust error handling:
- Database connection failures are logged but don't break commands
- Table creation errors are caught and logged
- Usage tracking failures don't prevent command execution
- Graceful degradation when database is unavailable
## Performance Considerations
- Table uses a composite primary key for efficient lookups
- Upsert operations use PostgreSQL's native `ON CONFLICT` clause
- Database operations are async and non-blocking
- Connection pooling is handled by the bot's existing pool
## Future Enhancements
Potential improvements could include:
- Adding timestamps for first/last usage
- Implementing usage analytics and reporting commands
- Adding rate limiting based on usage counts
- Creating usage leaderboards
- Exporting usage statistics for analysis

View File

@ -2,6 +2,9 @@ import discord
from discord.ext import commands
from discord import app_commands
import random
import logging
log = logging.getLogger(__name__)
class MessageCog(commands.Cog):
def __init__(self, bot):
@ -11,6 +14,61 @@ class MessageCog(commands.Cog):
{target} - Your pants are slowly and deliberately removed, leaving you feeling exposed and vulnerable. The sensation is both thrilling and terrifying as a presence looms over you, the only sound being the faint rustling of fabric as your clothes are discarded.
"""
async def _ensure_usage_table_exists(self):
"""Ensure the command usage counters table exists."""
if not hasattr(self.bot, 'pg_pool') or not self.bot.pg_pool:
log.warning("Database pool not available for usage tracking.")
return False
try:
async with self.bot.pg_pool.acquire() as conn:
await conn.execute("""
CREATE TABLE IF NOT EXISTS command_usage_counters (
user1_id BIGINT NOT NULL,
user2_id BIGINT NOT NULL,
command_name TEXT NOT NULL,
usage_count INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (user1_id, user2_id, command_name)
)
""")
return True
except Exception as e:
log.error(f"Error creating usage counters table: {e}")
return False
async def _increment_usage_counter(self, user1_id: int, user2_id: int, command_name: str):
"""Increment the usage counter for a command between two users."""
if not await self._ensure_usage_table_exists():
return
try:
async with self.bot.pg_pool.acquire() as conn:
await conn.execute("""
INSERT INTO command_usage_counters (user1_id, user2_id, command_name, usage_count)
VALUES ($1, $2, $3, 1)
ON CONFLICT (user1_id, user2_id, command_name)
DO UPDATE SET usage_count = command_usage_counters.usage_count + 1
""", user1_id, user2_id, command_name)
log.debug(f"Incremented usage counter for {command_name} between users {user1_id} and {user2_id}")
except Exception as e:
log.error(f"Error incrementing usage counter: {e}")
async def _get_usage_count(self, user1_id: int, user2_id: int, command_name: str) -> int:
"""Get the usage count for a command between two users."""
if not await self._ensure_usage_table_exists():
return 0
try:
async with self.bot.pg_pool.acquire() as conn:
count = await conn.fetchval("""
SELECT usage_count FROM command_usage_counters
WHERE user1_id = $1 AND user2_id = $2 AND command_name = $3
""", user1_id, user2_id, command_name)
return count if count is not None else 0
except Exception as e:
log.error(f"Error getting usage count: {e}")
return 0
# Helper method for the message logic
async def _message_logic(self, target):
"""Core logic for the message command."""
@ -19,19 +77,25 @@ class MessageCog(commands.Cog):
# --- RP Group ---
rp = app_commands.Group(name="rp", description="Roleplay commands")
@rp.command(name="molest", description="Send a hardcoded message to the mentioned user")
@app_commands.allowed_installs(guilds=True, users=True)
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
@app_commands.describe(member="The user to send the message to")
async def molest_slash(self, interaction: discord.Interaction, member: discord.User):
"""Slash command version of message."""
# Track usage between the two users
await self._increment_usage_counter(interaction.user.id, member.id, "molest")
response = await self._message_logic(member.mention)
await interaction.response.send_message(response)
@commands.command(name="molest")
async def molest_legacy(self, ctx: commands.Context, member: discord.User):
"""Legacy command version of molest."""
# Track usage between the two users
await self._increment_usage_counter(ctx.author.id, member.id, "molest")
response = await self._message_logic(member.mention)
await ctx.reply(response)
@ -41,6 +105,9 @@ class MessageCog(commands.Cog):
@app_commands.describe(member="The user to mention in the message")
async def rape_slash(self, interaction: discord.Interaction, member: discord.User):
"""Slash command version of rape."""
# Track usage between the two users
await self._increment_usage_counter(interaction.user.id, member.id, "rape")
rape_messages = [
f"{interaction.user.mention} raped {member.mention}.",
f"{interaction.user.mention} brutally raped {member.mention}.",
@ -56,6 +123,9 @@ class MessageCog(commands.Cog):
@commands.command(name="rape")
async def rape_legacy(self, ctx: commands.Context, member: discord.User):
"""Legacy command version of rape."""
# Track usage between the two users
await self._increment_usage_counter(ctx.author.id, member.id, "rape")
rape_messages = [
f"{ctx.author.mention} raped {member.mention}.",
f"{ctx.author.mention} brutally raped {member.mention}.",
@ -74,6 +144,9 @@ class MessageCog(commands.Cog):
@app_commands.describe(member="The user to send the message to")
async def sex_slash(self, interaction: discord.Interaction, member: discord.User):
"""Slash command version of sex."""
# Track usage between the two users
await self._increment_usage_counter(interaction.user.id, member.id, "sex")
sex_messages = [
f"{interaction.user.mention} and {member.mention} shared a tender kiss that deepened into a passionate embrace.",
f"{interaction.user.mention} gently caressed {member.mention}'s cheek before their lips met, igniting a spark.",
@ -109,6 +182,9 @@ class MessageCog(commands.Cog):
@commands.command(name="sex")
async def sex_legacy(self, ctx: commands.Context, member: discord.User):
"""Legacy command version of sex."""
# Track usage between the two users
await self._increment_usage_counter(ctx.author.id, member.id, "sex")
sex_messages = [
f"{ctx.author.mention} and {member.mention} shared a tender kiss that deepened into a passionate embrace.",
f"{ctx.author.mention} gently caressed {member.mention}'s cheek before their lips met, igniting a spark.",

View File

@ -2,11 +2,69 @@ import discord
from discord.ext import commands
from discord import app_commands
import random
import logging
log = logging.getLogger(__name__)
class MessageCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
async def _ensure_usage_table_exists(self):
"""Ensure the command usage counters table exists."""
if not hasattr(self.bot, 'pg_pool') or not self.bot.pg_pool:
log.warning("Database pool not available for usage tracking.")
return False
try:
async with self.bot.pg_pool.acquire() as conn:
await conn.execute("""
CREATE TABLE IF NOT EXISTS command_usage_counters (
user1_id BIGINT NOT NULL,
user2_id BIGINT NOT NULL,
command_name TEXT NOT NULL,
usage_count INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY (user1_id, user2_id, command_name)
)
""")
return True
except Exception as e:
log.error(f"Error creating usage counters table: {e}")
return False
async def _increment_usage_counter(self, user1_id: int, user2_id: int, command_name: str):
"""Increment the usage counter for a command between two users."""
if not await self._ensure_usage_table_exists():
return
try:
async with self.bot.pg_pool.acquire() as conn:
await conn.execute("""
INSERT INTO command_usage_counters (user1_id, user2_id, command_name, usage_count)
VALUES ($1, $2, $3, 1)
ON CONFLICT (user1_id, user2_id, command_name)
DO UPDATE SET usage_count = command_usage_counters.usage_count + 1
""", user1_id, user2_id, command_name)
log.debug(f"Incremented usage counter for {command_name} between users {user1_id} and {user2_id}")
except Exception as e:
log.error(f"Error incrementing usage counter: {e}")
async def _get_usage_count(self, user1_id: int, user2_id: int, command_name: str) -> int:
"""Get the usage count for a command between two users."""
if not await self._ensure_usage_table_exists():
return 0
try:
async with self.bot.pg_pool.acquire() as conn:
count = await conn.fetchval("""
SELECT usage_count FROM command_usage_counters
WHERE user1_id = $1 AND user2_id = $2 AND command_name = $3
""", user1_id, user2_id, command_name)
return count if count is not None else 0
except Exception as e:
log.error(f"Error getting usage count: {e}")
return 0
# --- RP Group ---
rp = app_commands.Group(name="rp", description="Roleplay commands")
@ -16,6 +74,9 @@ class MessageCog(commands.Cog):
@app_commands.describe(member="The user to send the message to")
async def sex_slash(self, interaction: discord.Interaction, member: discord.User):
"""Slash command version of sex."""
# Track usage between the two users
await self._increment_usage_counter(interaction.user.id, member.id, "neru_sex")
sex_messages = [
f"{interaction.user.mention} roughly pins {member.mention} against the wall, their lips crashing together in a desperate, hungry kiss.",
f"{interaction.user.mention}'s hands roam possessively over {member.mention}'s body, leaving a trail of heat in their wake.",
@ -50,6 +111,9 @@ class MessageCog(commands.Cog):
@commands.command(name="sex")
async def sex_legacy(self, ctx: commands.Context, member: discord.User):
"""Legacy command version of sex."""
# Track usage between the two users
await self._increment_usage_counter(ctx.author.id, member.id, "neru_sex")
sex_messages = [
f"{ctx.author.mention} roughly pins {member.mention} against the wall, their lips crashing together in a desperate, hungry kiss.",
f"{ctx.author.mention}'s hands roam possessively over {member.mention}'s body, leaving a trail of heat in their wake.",
@ -87,6 +151,9 @@ class MessageCog(commands.Cog):
@app_commands.describe(member="The user to mention in the message")
async def rape_slash(self, interaction: discord.Interaction, member: discord.User):
"""Slash command version of rape."""
# Track usage between the two users
await self._increment_usage_counter(interaction.user.id, member.id, "neru_rape")
rape_messages = [
f"{interaction.user.mention} raped {member.mention}.",
f"{interaction.user.mention} brutally raped {member.mention}.",
@ -105,6 +172,9 @@ class MessageCog(commands.Cog):
@app_commands.describe(member="The user to send the message to")
async def kiss_slash(self, interaction: discord.Interaction, member: discord.User):
"""Slash command version of kiss."""
# Track usage between the two users
await self._increment_usage_counter(interaction.user.id, member.id, "neru_kiss")
kiss_messages = [
f"{interaction.user.mention} gives {member.mention} a sweet kiss on the cheek.",
f"{interaction.user.mention} leans in and gives {member.mention} a gentle kiss.",
@ -133,6 +203,9 @@ class MessageCog(commands.Cog):
@commands.command(name="kiss")
async def kiss_legacy(self, ctx: commands.Context, member: discord.User):
"""Legacy command version of kiss."""
# Track usage between the two users
await self._increment_usage_counter(ctx.author.id, member.id, "neru_kiss")
kiss_messages = [
f"{ctx.author.mention} gives {member.mention} a sweet kiss on the cheek.",
f"{ctx.author.mention} leans in and gives {member.mention} a gentle kiss.",
@ -154,6 +227,9 @@ class MessageCog(commands.Cog):
@app_commands.describe(member="The user to send the message to")
async def hug_slash(self, interaction: discord.Interaction, member: discord.User):
"""Slash command version of hug."""
# Track usage between the two users
await self._increment_usage_counter(interaction.user.id, member.id, "neru_hug")
hug_messages = [
f"{interaction.user.mention} gives {member.mention} a warm hug.",
f"{interaction.user.mention} wraps their arms around {member.mention} in a comforting hug.",
@ -182,6 +258,9 @@ class MessageCog(commands.Cog):
@commands.command(name="hug")
async def hug_legacy(self, ctx: commands.Context, member: discord.User):
"""Legacy command version of hug."""
# Track usage between the two users
await self._increment_usage_counter(ctx.author.id, member.id, "neru_hug")
hug_messages = [
f"{ctx.author.mention} gives {member.mention} a warm hug.",
f"{ctx.author.mention} wraps their arms around {member.mention} in a comforting hug.",

106
test_usage_counters.py Normal file
View File

@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""
Test script to verify usage counter functionality.
This script demonstrates how to query the usage counters table.
"""
import asyncio
import asyncpg
import os
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
async def test_usage_counters():
"""Test the usage counters functionality."""
# Create database connection
try:
conn_string = f"postgresql://{os.getenv('POSTGRES_USER')}:{os.getenv('POSTGRES_PASSWORD')}@{os.getenv('POSTGRES_HOST')}:{os.getenv('POSTGRES_PORT')}/{os.getenv('POSTGRES_SETTINGS_DB')}"
conn = await asyncpg.connect(conn_string)
print("✅ Connected to database successfully")
except Exception as e:
print(f"❌ Failed to connect to database: {e}")
return
try:
# Check if the table exists
table_exists = await conn.fetchval("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_name = 'command_usage_counters'
)
""")
if table_exists:
print("✅ command_usage_counters table exists")
# Get some sample data
records = await conn.fetch("""
SELECT user1_id, user2_id, command_name, usage_count
FROM command_usage_counters
ORDER BY usage_count DESC
LIMIT 10
""")
if records:
print("\n📊 Top 10 command usages:")
print("User1 ID | User2 ID | Command | Count")
print("-" * 45)
for record in records:
print(f"{record['user1_id']} | {record['user2_id']} | {record['command_name']} | {record['usage_count']}")
else:
print("📝 No usage data found yet (table is empty)")
# Get total count
total_count = await conn.fetchval("SELECT COUNT(*) FROM command_usage_counters")
print(f"\n📈 Total unique user-command combinations: {total_count}")
else:
print("⚠️ command_usage_counters table does not exist yet")
print(" It will be created automatically when a command is first used")
except Exception as e:
print(f"❌ Error querying database: {e}")
finally:
await conn.close()
print("🔌 Database connection closed")
async def get_usage_for_users(user1_id: int, user2_id: int):
"""Get usage statistics for a specific pair of users."""
try:
conn_string = f"postgresql://{os.getenv('POSTGRES_USER')}:{os.getenv('POSTGRES_PASSWORD')}@{os.getenv('POSTGRES_HOST')}:{os.getenv('POSTGRES_PORT')}/{os.getenv('POSTGRES_SETTINGS_DB')}"
conn = await asyncpg.connect(conn_string)
records = await conn.fetch("""
SELECT command_name, usage_count
FROM command_usage_counters
WHERE user1_id = $1 AND user2_id = $2
ORDER BY usage_count DESC
""", user1_id, user2_id)
if records:
print(f"\n👥 Usage between users {user1_id} and {user2_id}:")
print("Command | Count")
print("-" * 20)
for record in records:
print(f"{record['command_name']} | {record['usage_count']}")
else:
print(f"📝 No usage data found between users {user1_id} and {user2_id}")
await conn.close()
except Exception as e:
print(f"❌ Error querying user data: {e}")
if __name__ == "__main__":
print("🧪 Testing Usage Counters Functionality")
print("=" * 40)
# Test basic functionality
asyncio.run(test_usage_counters())
# Example: Get usage for specific users (replace with actual user IDs)
# asyncio.run(get_usage_for_users(123456789, 987654321))