updated minesweeper timeout and made roletrack automatic

This commit is contained in:
Lilac-Rose
2026-02-11 17:43:27 +01:00
parent 7f5305ec4e
commit ad3fb39781
2 changed files with 235 additions and 99 deletions

View File

@@ -4,6 +4,7 @@ from discord import app_commands
import random
import asyncio
import re
from datetime import datetime, timedelta
class MinesweeperGame:
def __init__(self, rows: int = 13, cols: int = 13, mines: int = 20):
@@ -197,10 +198,64 @@ class MinesweeperGame:
class MinesweeperView(discord.ui.View):
def __init__(self, player: discord.Member, game: MinesweeperGame):
super().__init__(timeout=600)
super().__init__(timeout=None) # Disable built-in timeout, we'll manage it ourselves
self.player = player
self.game = game
self.message: discord.Message = None
self.timeout_seconds = 1800 # 30 minutes
self.timeout_task = None
self.timed_out = False
async def start_timeout(self):
"""Start or restart the timeout timer"""
# Cancel existing timeout task if any
if self.timeout_task and not self.timeout_task.done():
self.timeout_task.cancel()
# Create new timeout task
self.timeout_task = asyncio.create_task(self._timeout_handler())
async def _timeout_handler(self):
"""Handle the timeout after waiting"""
try:
await asyncio.sleep(self.timeout_seconds)
# If we reach here, the timeout expired
await self.handle_timeout()
except asyncio.CancelledError:
# Timeout was reset, this is expected
pass
async def handle_timeout(self):
"""Handle when the game times out"""
if self.timed_out or self.game.game_over:
return
self.timed_out = True
self.game.game_over = True
for child in self.children:
child.disabled = True
self.stop()
if self.message:
embed = self.create_embed()
embed.color = discord.Color.orange()
embed.title = "⏱️ Game Timed Out"
embed.description = f"{self.player.mention}'s game has ended due to inactivity (30 minutes)."
embed.set_footer(text="The game has been automatically closed.")
try:
await self.message.edit(embed=embed, view=self)
except discord.errors.NotFound:
# Message was deleted
pass
except Exception as e:
print(f"Error updating timed out game: {e}")
def reset_timeout(self):
"""Reset the timeout timer - called when a move is made"""
if not self.game.game_over and not self.timed_out:
asyncio.create_task(self.start_timeout())
@discord.ui.button(label="Forfeit", style=discord.ButtonStyle.danger, emoji="🏳️")
async def forfeit(self, interaction: discord.Interaction, button: discord.ui.Button):
@@ -209,6 +264,11 @@ class MinesweeperView(discord.ui.View):
return
self.game.game_over = True
# Cancel timeout task
if self.timeout_task and not self.timeout_task.done():
self.timeout_task.cancel()
for child in self.children:
child.disabled = True
self.stop()
@@ -295,23 +355,9 @@ class MinesweeperView(discord.ui.View):
embed.add_field(name="Stats", value=stats, inline=False)
if not self.game.game_over:
embed.set_footer(text="Send your move in chat: [column number] [row letter] [flag]")
embed.set_footer(text="Send your move in chat: [column number] [row letter] [flag] | Timeout resets with each move (30min)")
return embed
async def on_timeout(self):
self.game.game_over = True
for child in self.children:
child.disabled = True
if self.message:
embed = self.create_embed()
embed.color = discord.Color.greyple()
embed.title = "⏱️ Game Timed Out"
try:
await self.message.edit(embed=embed, view=self)
except:
pass
class Minesweeper(commands.Cog):
@@ -351,6 +397,9 @@ class Minesweeper(commands.Cog):
message = await interaction.followup.send(embed=embed, view=view)
view.message = message
# Start the timeout timer
await view.start_timeout()
self.active_games[interaction.channel_id] = (interaction.user.id, view)
# Wait for the game to finish
@@ -401,8 +450,15 @@ class Minesweeper(commands.Cog):
else:
view.game.reveal(row, col)
# Reset the timeout timer since a valid move was made
view.reset_timeout()
# Update the embed
if view.game.game_over:
# Cancel timeout task since game is over
if view.timeout_task and not view.timeout_task.done():
view.timeout_task.cancel()
for child in view.children:
child.disabled = True
view.stop()

View File

@@ -13,113 +13,193 @@ class RoleTrack(commands.Cog):
async def init_db(self):
"""Initialize the database tables if they don't exist"""
async with aiosqlite.connect(self.db_path) as db:
# Table for tracking opt-in status
await db.execute('''
CREATE TABLE IF NOT EXISTS role_tracking_users (
user_id INTEGER,
guild_id INTEGER,
opted_in INTEGER DEFAULT 1,
PRIMARY KEY (user_id, guild_id)
)
''')
# Table for storing role data
# Table for storing role data - one row per user per guild
# role_ids stored as comma-separated string
await db.execute('''
CREATE TABLE IF NOT EXISTS tracked_roles (
user_id INTEGER,
guild_id INTEGER,
role_id INTEGER,
role_name TEXT,
role_ids TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, guild_id, role_id)
PRIMARY KEY (user_id, guild_id)
)
''')
await db.commit()
async def is_opted_in(self, user_id: int, guild_id: int) -> bool:
"""Check if a user is opted into role tracking"""
async with aiosqlite.connect(self.db_path) as db:
async with db.execute('''
SELECT opted_in FROM role_tracking_users
WHERE user_id = ? AND guild_id = ?
''', (user_id, guild_id)) as cursor:
result = await cursor.fetchone()
return result[0] == 1 if result else False
async def save_user_roles(self, member: discord.Member):
"""Save all roles for a user (excluding @everyone)"""
if not await self.is_opted_in(member.id, member.guild.id):
return
# Get all role IDs except @everyone
role_ids = [str(role.id) for role in member.roles if role.id != member.guild.id]
role_ids_str = ",".join(role_ids) if role_ids else ""
async with aiosqlite.connect(self.db_path) as db:
# Clear existing roles for this user in this guild
# Insert or replace the user's roles
await db.execute('''
DELETE FROM tracked_roles
WHERE user_id = ? AND guild_id = ?
''', (member.id, member.guild.id))
# Save current roles (excluding @everyone)
for role in member.roles:
if role.id != member.guild.id: # Skip @everyone role
await db.execute('''
INSERT INTO tracked_roles (user_id, guild_id, role_id, role_name)
VALUES (?, ?, ?, ?)
''', (member.id, member.guild.id, role.id, role.name))
INSERT OR REPLACE INTO tracked_roles (user_id, guild_id, role_ids, updated_at)
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
''', (member.id, member.guild.id, role_ids_str))
await db.commit()
@app_commands.command(name="roletrack", description="Manage role tracking opt-in status")
@app_commands.describe(
setting="Choose to opt in or opt out of role tracking"
)
@app_commands.choices(setting=[
app_commands.Choice(name="opt_in", value="opt_in"),
app_commands.Choice(name="opt_out", value="opt_out")
])
async def roletrack(self, interaction: discord.Interaction, setting: app_commands.Choice[str]):
"""Command to opt in or opt out of role tracking"""
async def get_saved_roles(self, user_id: int, guild_id: int):
"""Get saved role IDs for a user"""
async with aiosqlite.connect(self.db_path) as db:
if setting.value == "opt_in":
# Opt the user in
await db.execute('''
INSERT OR REPLACE INTO role_tracking_users (user_id, guild_id, opted_in)
VALUES (?, ?, 1)
''', (interaction.user.id, interaction.guild.id))
await db.commit()
# Save current roles (outside the db context)
await self.save_user_roles(interaction.user)
async with db.execute('''
SELECT role_ids FROM tracked_roles
WHERE user_id = ? AND guild_id = ?
''', (user_id, guild_id)) as cursor:
result = await cursor.fetchone()
if result and result[0]:
# Convert comma-separated string back to list of ints
return [int(role_id) for role_id in result[0].split(",")]
return []
@app_commands.command(name="syncroles", description="Manually sync your current roles to the role tracking system")
async def syncroles(self, interaction: discord.Interaction):
"""Command to manually sync user's current roles"""
await interaction.response.defer(ephemeral=True)
try:
await self.save_user_roles(interaction.user)
# Count how many roles were saved (excluding @everyone)
role_count = len([r for r in interaction.user.roles if r.id != interaction.guild.id])
embed = discord.Embed(
title="✅ Roles Synced",
description=f"Successfully saved {role_count} role(s) to the tracking system.",
color=discord.Color.green()
)
embed.add_field(
name="What does this do?",
value="If you leave and rejoin this server, your roles will be automatically restored.",
inline=False
)
# Show current roles
if role_count > 0:
roles_list = ", ".join([r.name for r in interaction.user.roles if r.id != interaction.guild.id])
embed.add_field(
name="Your Current Roles",
value=roles_list,
inline=False
)
await interaction.followup.send(embed=embed, ephemeral=True)
except Exception as e:
embed = discord.Embed(
title="❌ Sync Failed",
description=f"An error occurred while syncing your roles: {str(e)}",
color=discord.Color.red()
)
await interaction.followup.send(embed=embed, ephemeral=True)
@app_commands.command(name="checkroles", description="Check what roles are saved in the database for you")
async def checkroles(self, interaction: discord.Interaction):
"""Debug command to check saved roles"""
await interaction.response.defer(ephemeral=True)
try:
saved_role_ids = await self.get_saved_roles(interaction.user.id, interaction.guild.id)
if not saved_role_ids:
embed = discord.Embed(
title="📋 Saved Roles",
description="No roles are currently saved for you in the database.",
color=discord.Color.orange()
)
else:
# Get role names
roles = []
for role_id in saved_role_ids:
role = interaction.guild.get_role(role_id)
if role:
roles.append(f"{role.name} (ID: {role_id})")
else:
roles.append(f"Deleted Role (ID: {role_id})")
embed = discord.Embed(
title="✅ Role Tracking Enabled",
description="You've been opted into role tracking. If you leave and rejoin this server, your roles will be automatically restored.",
color=discord.Color.green()
title="📋 Saved Roles",
description=f"Found {len(saved_role_ids)} role(s) in the database:",
color=discord.Color.blue()
)
await interaction.response.send_message(embed=embed, ephemeral=True)
elif setting.value == "opt_out":
# Opt the user out
await db.execute('''
INSERT OR REPLACE INTO role_tracking_users (user_id, guild_id, opted_in)
VALUES (?, ?, 0)
''', (interaction.user.id, interaction.guild.id))
# Delete their tracked roles
await db.execute('''
DELETE FROM tracked_roles
WHERE user_id = ? AND guild_id = ?
''', (interaction.user.id, interaction.guild.id))
await db.commit()
embed.add_field(
name="Roles",
value="\n".join(roles) if roles else "None",
inline=False
)
await interaction.followup.send(embed=embed, ephemeral=True)
except Exception as e:
embed = discord.Embed(
title="❌ Check Failed",
description=f"An error occurred: {str(e)}",
color=discord.Color.red()
)
await interaction.followup.send(embed=embed, ephemeral=True)
@commands.Cog.listener()
async def on_member_remove(self, member: discord.Member):
"""Save roles when a member leaves"""
await self.save_user_roles(member)
@commands.Cog.listener()
async def on_member_join(self, member: discord.Member):
"""Restore roles when a member rejoins, and save them for new members"""
saved_role_ids = await self.get_saved_roles(member.id, member.guild.id)
if not saved_role_ids:
# New member - just save their initial roles
await self.save_user_roles(member)
return
# Returning member - restore their roles
# Get the roles that still exist in the guild
roles_to_add = []
for role_id in saved_role_ids:
role = member.guild.get_role(role_id)
if role:
roles_to_add.append(role)
if not roles_to_add:
return # None of the saved roles exist anymore
try:
# Add the roles back
await member.add_roles(*roles_to_add, reason="Role tracking: Restoring previous roles")
# Send a DM to the user notifying them
try:
embed = discord.Embed(
title="❌ Role Tracking Disabled",
description="You've been opted out of role tracking. Your saved roles have been deleted.",
color=discord.Color.red()
title="🎭 Roles Restored",
description=f"Welcome back to **{member.guild.name}**! Your roles have been automatically restored.",
color=discord.Color.blue()
)
await interaction.response.send_message(embed=embed, ephemeral=True)
embed.add_field(
name="Restored Roles",
value=", ".join([role.name for role in roles_to_add]),
inline=False
)
await member.send(embed=embed)
except:
# User has DMs disabled, that's fine
pass
except discord.Forbidden:
# Bot doesn't have permission to add roles
pass
except Exception as e:
print(f"Error restoring roles for {member}: {e}")
@commands.Cog.listener()
async def on_member_update(self, before: discord.Member, after: discord.Member):
"""Track role changes in real-time"""
# Check if roles changed
if before.roles != after.roles:
await self.save_user_roles(after)
async def setup(bot):
await bot.add_cog(RoleTrack(bot))