Files

249 lines
6.1 KiB
Python
Raw Permalink Normal View History

2026-03-14 22:54:52 -04:00
import discord
from discord.ext import commands
from datetime import datetime, timedelta
import json
import os
# CONFIG
LEADERBOARD_FILE = "leaderboard.json"
EMBED_COLOR = discord.Color.gold()
TOP_EMOJIS = ["🥇", "🥈", "🥉"]
USERS_PER_PAGE = 5
# DATA
class LeaderboardData:
def __init__(self, filepath=LEADERBOARD_FILE):
self.filepath = filepath
self.data = {}
self.load()
def load(self):
if os.path.exists(self.filepath):
try:
with open(self.filepath, "r", encoding="utf-8") as f:
self.data = json.load(f)
except Exception:
self.data = {}
else:
self.data = {}
def save(self):
with open(self.filepath, "w", encoding="utf-8") as f:
json.dump(self.data, f, indent=4)
# RECORD POST
def record_post(self, user_id: int):
uid = str(user_id)
now = datetime.utcnow().isoformat()
if uid not in self.data:
self.data[uid] = {
"count": 0,
"last_post": now,
"posts": []
}
self.data[uid]["count"] += 1
self.data[uid]["last_post"] = now
self.data[uid]["posts"].append(now)
self.save()
# SORT
def get_sorted(self):
return sorted(
self.data.items(),
key=lambda item: (-item[1]["count"], item[1]["last_post"])
)
# USER STATS
def user_30_days(self, posts):
cutoff = datetime.utcnow() - timedelta(days=30)
return sum(
1 for p in posts
if datetime.fromisoformat(p) >= cutoff
)
def user_year(self, posts):
year = datetime.utcnow().year
return sum(
1 for p in posts
if datetime.fromisoformat(p).year == year
)
# GLOBAL STATS
def global_stats(self):
cutoff = datetime.utcnow() - timedelta(days=30)
year = datetime.utcnow().year
total30 = 0
totalYear = 0
totalAll = 0
for user in self.data.values():
posts = user.get("posts", [])
totalAll += user.get("count", 0)
for p in posts:
dt = datetime.fromisoformat(p)
if dt >= cutoff:
total30 += 1
if dt.year == year:
totalYear += 1
return total30, totalYear, totalAll
leaderboard_data = LeaderboardData()
# VIEW
class LeaderboardView(discord.ui.View):
def __init__(self, ctx, users):
super().__init__(timeout=180)
self.ctx = ctx
self.users = users
self.page = 0
self.max_page = (len(users) - 1) // USERS_PER_PAGE
def build_embed(self):
embed = discord.Embed(
title="📊 Announcement Leaderboard",
color=EMBED_COLOR,
timestamp=datetime.utcnow()
)
start = self.page * USERS_PER_PAGE
end = start + USERS_PER_PAGE
page_users = self.users[start:end]
for i, (uid, stats) in enumerate(page_users, start=start+1):
member = self.ctx.guild.get_member(int(uid))
name = member.display_name if member else f"User {uid}"
posts = stats.get("posts", [])
alltime = stats["count"]
last30 = leaderboard_data.user_30_days(posts)
year = leaderboard_data.user_year(posts)
last_post = datetime.fromisoformat(stats["last_post"]).strftime("%Y-%m-%d %H:%M UTC")
if i <= 3:
title = f"{TOP_EMOJIS[i-1]} {name}"
else:
title = name
value = (
f"**All Time:** {alltime}\n"
f"**30 Days:** {last30}\n"
f"**Year:** {year}\n"
f"**Last:** {last_post}"
)
if int(uid) == self.ctx.author.id:
value += " 👈 You"
embed.add_field(name=title, value=value, inline=False)
# GLOBAL STATS
total30, totalYear, totalAll = leaderboard_data.global_stats()
embed.add_field(
name="📈 Server Announcement Stats",
value=(
f"**Last 30 Days:** {total30}\n"
f"**This Year:** {totalYear}\n"
f"**All Time:** {totalAll}"
),
inline=False
)
embed.set_footer(text=f"Page {self.page+1}/{self.max_page+1}")
return embed
# BUTTONS
@discord.ui.button(label="⬅ Previous", style=discord.ButtonStyle.secondary)
async def previous(self, interaction: discord.Interaction, button: discord.ui.Button):
if self.page > 0:
self.page -= 1
await interaction.response.edit_message(embed=self.build_embed(), view=self)
@discord.ui.button(label="🧑 My Rank", style=discord.ButtonStyle.primary)
async def my_rank(self, interaction: discord.Interaction, button: discord.ui.Button):
uid = str(interaction.user.id)
for index, (user_id, _) in enumerate(self.users):
if user_id == uid:
self.page = index // USERS_PER_PAGE
break
await interaction.response.edit_message(embed=self.build_embed(), view=self)
@discord.ui.button(label="Next ➡", style=discord.ButtonStyle.secondary)
async def next(self, interaction: discord.Interaction, button: discord.ui.Button):
if self.page < self.max_page:
self.page += 1
await interaction.response.edit_message(embed=self.build_embed(), view=self)
# COG
class Leaderboard(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.command()
async def leaderboard(self, ctx):
users = leaderboard_data.get_sorted()
if not users:
await ctx.send("No announcements have been posted yet.")
return
view = LeaderboardView(ctx, users)
await ctx.send(embed=view.build_embed(), view=view)
# HELPER
def record_announcement_post(user_id: int):
leaderboard_data.record_post(user_id)
# SETUP
async def setup(bot):
await bot.add_cog(Leaderboard(bot))