feat (yarn): Add topvoice, upload, and topmoomin
This commit is contained in:
+194
-2
@@ -1,4 +1,5 @@
|
||||
from typing import Literal
|
||||
from pathlib import Path
|
||||
from typing import Literal, Optional
|
||||
from collections import defaultdict
|
||||
import datetime as dt
|
||||
from datetime import datetime, timedelta, UTC
|
||||
@@ -6,7 +7,7 @@ from datetime import datetime, timedelta, UTC
|
||||
from data.queries import ORDER
|
||||
import discord
|
||||
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.logger import log_wrap
|
||||
@@ -132,3 +133,194 @@ class YarnCog(LionCog):
|
||||
pages = paginate_list(lb_strings, block_length=page_len, title=title)
|
||||
|
||||
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}")
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user