feat (yarn): Add topvoice, upload, and topmoomin

This commit is contained in:
2026-08-04 11:53:07 +03:00
parent d4e3c49ef2
commit 25c64bb338
+194 -2
View File
@@ -1,4 +1,5 @@
from typing import Literal from pathlib import Path
from typing import Literal, Optional
from collections import defaultdict from collections import defaultdict
import datetime as dt import datetime as dt
from datetime import datetime, timedelta, UTC from datetime import datetime, timedelta, UTC
@@ -6,7 +7,7 @@ from datetime import datetime, timedelta, UTC
from data.queries import ORDER from data.queries import ORDER
import discord import discord
from discord.ext import commands as cmds from discord.ext import commands as cmds
from discord import app_commands as appcmds from discord import Attachment, app_commands as appcmds
from meta import LionBot, LionCog, LionContext from meta import LionBot, LionCog, LionContext
from meta.logger import log_wrap from meta.logger import log_wrap
@@ -132,3 +133,194 @@ class YarnCog(LionCog):
pages = paginate_list(lb_strings, block_length=page_len, title=title) pages = paginate_list(lb_strings, block_length=page_len, title=title)
await pager(ctx, pages) await pager(ctx, pages)
@cmds.hybrid_command(name="topvoice")
async def topvoice_cmd(self, ctx):
"""
Show top voice members by total time.
"""
target_channelid = 1383707078740279366
since_stamp = 1769832959
voicelogger = ctx.bot.get_cog("VoiceLogCog")
session_data = voicelogger.data.voicelog_sessions
query = (
session_data.select_where(
VoiceLogSession.joined_at
>= datetime.fromtimestamp(since_stamp, tz=UTC),
guildid=ctx.guild.id,
channelid=target_channelid,
)
.select(
userid="userid",
total_time="SUM(COALESCE(duration, EXTRACT(EPOCH FROM (NOW() - joined_at))))",
)
.order_by("total_time", ORDER.DESC)
.group_by("userid")
.with_no_adapter()
)
leaderboard = [(row["userid"], int(row["total_time"])) for row in await query]
# Format for display and pager
# First collect names
names = {}
for uid, _ in leaderboard:
user = ctx.guild.get_member(uid)
if user is None:
try:
user = await ctx.guild.fetch_member(uid)
except discord.NotFound:
user = None
names[uid] = user.display_name if user else str(uid)
lb_strings = []
max_name_len = min((30, max(len(name) for name in names.values())))
for i, (uid, total) in enumerate(leaderboard):
lb_strings.append(
"{:<{}}\t{:<9}".format(
names[uid], max_name_len, strfdur(total, short=False)
)
)
page_len = 20
title = "Voice Leaderboard"
pages = paginate_list(lb_strings, block_length=page_len, title=title)
await pager(ctx, pages)
@cmds.hybrid_command(name="topmoomin")
async def topmoomin_cmd(self, ctx):
target_channelid = 1383707078740279366
since_stamp = 1777960800
# Build timestamps of all moomin times since the start
# Reverse time order
moomin_periods = []
last_moomin_start = 1785823200
moomin_dur = 60 * 30
mstart = last_moomin_start
while mstart >= since_stamp:
period = (mstart, mstart + moomin_dur)
moomin_periods.append(period)
mstart -= 24 * 60 * 60
# Get all voice sessions
voicelogger = ctx.bot.get_cog("VoiceLogCog")
session_data = voicelogger.data.voicelog_sessions
query = (
session_data.select_where(
VoiceLogSession.joined_at
>= datetime.fromtimestamp(since_stamp, tz=UTC),
guildid=ctx.guild.id,
channelid=target_channelid,
)
.select("userid", "joined_at", "duration")
.order_by("joined_at", ORDER.DESC)
.with_no_adapter()
)
rows = await query
# For each session, intersect it with all the moomin times, sum the intersection seconds. Key by userid
moomin_leaderboard = defaultdict(int)
for row in rows:
start_stamp = row["joined_at"].timestamp()
if row["duration"]:
end_stamp = start_stamp + row["duration"]
else:
end_stamp = utc_now().timestamp()
# Intersection
for mstart, mend in moomin_periods:
if mend < start_stamp:
# Moomin periods go backwards
# Stop looking if the period ended before this voice session started
break
# Guarantee: mend >= start_stamp
if mstart <= end_stamp:
# Period starts before the end of the voice session
# and ends after the start of the session
intersection_start = max(start_stamp, mstart)
intersection_end = min(end_stamp, mend)
diff = intersection_end - intersection_start
moomin_leaderboard[row["userid"]] += int(diff)
else:
# Period starts after the end of the voice session, ignore
pass
# Format for display and pager
leaderboard = sorted(
moomin_leaderboard.items(), key=lambda p: p[1], reverse=True
)
# First collect names
names = {}
users = {}
for uid, _ in leaderboard:
user = ctx.guild.get_member(uid)
if user is None:
try:
user = await ctx.guild.fetch_member(uid)
except discord.NotFound:
user = None
names[uid] = user.display_name if user else str(uid)
users[uid] = user
lb_strings = []
max_name_len = min((30, max(len(name) for name in names.values())))
for i, (uid, total) in enumerate(leaderboard):
lb_strings.append(
"{:<{}}\t{:<9}".format(
names[uid], max_name_len, strfdur(total, short=False)
)
)
page_len = 20
title = "Moomin Leaderboard"
pages = paginate_list(lb_strings, block_length=page_len, title=title)
await pager(ctx, pages)
if ctx.author.guild_permissions.administrator:
threshold = 20 * 60
role = ctx.guild.get_role(1534089417612857424)
if not role:
await ctx.reply("Couldn't find the role!")
return
earned_users = []
for uid, dur in leaderboard:
if dur >= threshold:
if user := users.get(uid):
earned_users.append(user)
if role not in user.roles:
await user.add_roles(role, reason=f"Watched moomin for {dur}")
await ctx.reply(
f"Added moomin role to: {', '.join(u.mention for u in earned_users)} ",
allowed_mentions=None,
)
async def _upload_file(self, file: discord.Attachment, filename: str):
filedir = Path("/extension/www/lilac")
target = filedir / filename
await file.save(target)
return f"https://lilac.thewisewolf.dev/provides/{filename}"
@cmds.hybrid_group(name="upload")
@appcmds.default_permissions(administrator=True)
async def upload_group(self, ctx):
pass
@upload_group.command(name="file")
@appcmds.default_permissions(administrator=True)
async def upload_file_group(
self, ctx, file: discord.Attachment, filename: Optional[str] = None
):
await ctx.interaction.response.defer(thinking=True, ephemeral=True)
url = await self._upload_file(file, filename or file.filename)
await ctx.reply(
embed=discord.Embed(description=f"File now available at: {url}")
)