import discord
from discord.ext import commands
from discord import app_commands
import aiosqlite
from datetime import datetime, date
import pytz # fallback for zoneinfo because im sped
# Configuration
TOKEN = "YOUR_TOKEN_HERE" # replace with your bot token
ADMIN_LOG_CHANNEL_ID = None # Optional if you want
# Timezone fallback (US Central)
central_tz = pytz.timezone("America/Chicago")
# Intents setup
intents = discord.Intents.default()
intents.members = True
intents.guilds = True
intents.message_content = True
intents.guild_messages = True
intents.guild_reactions = True
bot = commands.Bot(command_prefix="!", intents=intents)
def current_central_date():
return datetime.now(central_tz).date().isoformat()
@bot.event
async def on_ready():
if not hasattr(bot, 'db'):
bot.db = await aiosqlite.connect('thanks.db')
await bot.db.execute(
"""
CREATE TABLE IF NOT EXISTS thread_thanks (
guild_id INTEGER, thread_id INTEGER, from_id INTEGER, to_id INTEGER, timestamp TEXT,
PRIMARY KEY (guild_id, thread_id, from_id, to_id)
)
"""
)
await bot.db.execute(
"""
CREATE TABLE IF NOT EXISTS daily_thanks (
guild_id INTEGER, from_id INTEGER, to_id INTEGER, date TEXT, timestamp TEXT,
PRIMARY KEY (guild_id, from_id, to_id, date)
)
"""
)
await bot.db.execute(
"""
CREATE TABLE IF NOT EXISTS totals (
guild_id INTEGER, user_id INTEGER, count INTEGER,
PRIMARY KEY (guild_id, user_id)
)
"""
)
await bot.db.commit()
print("Database initialized.")
if not hasattr(bot, 'synced'):
await bot.tree.sync()
bot.synced = True
print("Slash commands synced.")
print(f"Logged in as {bot.user} (ID: {bot.user.id})")
# Helpers
async def log_action(content):
if ADMIN_LOG_CHANNEL_ID:
channel = bot.get_channel(ADMIN_LOG_CHANNEL_ID)
if channel:
await channel.send(content)
async def has_thanked_in_thread(guild_id, thread_id, from_id, to_id):
cursor = await bot.db.execute(
"SELECT 1 FROM thread_thanks WHERE guild_id = ? AND thread_id = ? AND from_id = ? AND to_id = ?",
(guild_id, thread_id, from_id, to_id)
)
exists = await cursor.fetchone()
await cursor.close()
return exists is not None
async def has_thanked_today(guild_id, from_id, to_id, date_str):
cursor = await bot.db.execute(
"SELECT 1 FROM daily_thanks WHERE guild_id = ? AND from_id = ? AND to_id = ? AND date = ?",
(guild_id, from_id, to_id, date_str)
)
exists = await cursor.fetchone()
await cursor.close()
return exists is not None
async def user_participated(thread, user_id):
async for message in thread.history(limit=100):
if message.author.id == user_id:
return True
return False
async def record_thank(guild_id, from_id, to_id, context_id, is_thread):
timestamp = datetime.utcnow().isoformat()
if is_thread:
await bot.db.execute(
"INSERT OR IGNORE INTO thread_thanks VALUES (?, ?, ?, ?, ?)",
(guild_id, context_id, from_id, to_id, timestamp)
)
else:
today = current_central_date()
await bot.db.execute(
"INSERT OR IGNORE INTO daily_thanks VALUES (?, ?, ?, ?, ?)",
(guild_id, from_id, to_id, today, timestamp)
)
await bot.db.execute(
"INSERT INTO totals(guild_id, user_id, count) VALUES (?, ?, 1)"
" ON CONFLICT(guild_id, user_id) DO UPDATE SET count = count + 1",
(guild_id, to_id)
)
await bot.db.commit()
# Slash Command: Thank
@bot.tree.command(name="thank", description="Thank another user")
async def thank(interaction: discord.Interaction, user: discord.Member):
from_user = interaction.user
to_user = user
channel = interaction.channel
guild = interaction.guild
if guild is None:
await interaction.response.send_message("❌ Must be used in a server.", ephemeral=True)
return
if to_user.bot or to_user.id == from_user.id:
await interaction.response.send_message("❌ Cannot thank bots or yourself.", ephemeral=True)
return
is_thread = isinstance(channel, discord.Thread)
if is_thread:
if not await user_participated(channel, to_user.id):
await interaction.response.send_message(f"❌ {to_user.display_name} has not participated in this thread.", ephemeral=True)
return
if await has_thanked_in_thread(guild.id, channel.id, from_user.id, to_user.id):
await interaction.response.send_message("❌ Already thanked this user in this thread.", ephemeral=True)
return
else:
today = current_central_date()
if await has_thanked_today(guild.id, from_user.id, to_user.id, today):
await interaction.response.send_message("❌ Already thanked this user today.", ephemeral=True)
return
await record_thank(guild.id, from_user.id, to_user.id, channel.id if is_thread else None, is_thread)
await interaction.response.send_message(f"✅ You thanked {to_user.mention}!", ephemeral=True)
await log_action(f"{from_user} thanked {to_user} via /thank in {channel.mention}")
# Slash Command: Top
@bot.tree.command(name="topthanks", description="Show top 10 thanked users")
async def topthanks(interaction: discord.Interaction):
guild_id = interaction.guild.id
cursor = await bot.db.execute(
"SELECT user_id, count FROM totals WHERE guild_id = ? ORDER BY count DESC LIMIT 10", (guild_id,)
)
results = await cursor.fetchall()
await cursor.close()
lines = []
for i, (uid, count) in enumerate(results, 1):
member = interaction.guild.get_member(uid)
name = member.display_name if member else f"User ID {uid}"
lines.append(f"{i}. {name} — {count} thanks")
if lines:
await interaction.response.send_message("🏆 **Top 10 Thanked Users:**\n" + "\n".join(lines), ephemeral=True)
else:
await interaction.response.send_message("No data yet.", ephemeral=True)
# Slash Command: My Thanks
@bot.tree.command(name="mythanks", description="Show how many thanks you've received")
async def mythanks(interaction: discord.Interaction):
guild_id = interaction.guild.id
user_id = interaction.user.id
cursor = await bot.db.execute(
"SELECT count FROM totals WHERE guild_id = ? AND user_id = ?",
(guild_id, user_id)
)
result = await cursor.fetchone()
await cursor.close()
total = result[0] if result else 0
await interaction.response.send_message(f"You have been thanked **{total}** times.", ephemeral=True)
# Reaction Handler
@bot.event
async def on_reaction_add(reaction, user):
if str(reaction.emoji) != '✅' or user.bot:
return
msg = reaction.message
guild = msg.guild
if not guild or not msg.author:
return
to_user = msg.author
from_user = user
if to_user.bot or from_user.id == to_user.id:
try: await from_user.send("❌ You cannot thank bots or yourself.")
except: pass
return
channel = msg.channel
is_thread = isinstance(channel, discord.Thread)
if is_thread:
if not await user_participated(channel, to_user.id):
try: await from_user.send(f"❌ {to_user.display_name} has not posted in this thread.")
except: pass
await msg.remove_reaction(reaction.emoji, from_user)
return
if await has_thanked_in_thread(guild.id, channel.id, from_user.id, to_user.id):
try: await from_user.send(f"❌ Already thanked {to_user.display_name} in this thread.")
except: pass
await msg.remove_reaction(reaction.emoji, from_user)
return
else:
today = current_central_date()
if await has_thanked_today(guild.id, from_user.id, to_user.id, today):
try: await from_user.send(f"❌ Already thanked {to_user.display_name} today.")
except: pass
await msg.remove_reaction(reaction.emoji, from_user)
return
await record_thank(guild.id, from_user.id, to_user.id, channel.id if is_thread else None, is_thread)
await log_action(f"{from_user} thanked {to_user} via ✅ in {channel.mention}\n[Jump]({msg.jump_url})")
bot.run(TOKEN)