noot
This commit is contained in:
parent
a8295b09d6
commit
d5f3ea5848
1
ts/migrations
Symbolic link
1
ts/migrations
Symbolic link
@ -0,0 +1 @@
|
||||
../migrations
|
||||
@ -3,6 +3,7 @@ import { PG } from "#/services/pg";
|
||||
import { CreateMessageOptions, InteractionCallbackOptions } from "discordeno";
|
||||
import { type } from "arktype";
|
||||
import { TabWriter } from "#/lib/util/tabwriter";
|
||||
import { RANK_EMOJIS, getRankEmoji, formatNumber } from "#/lib/util/wynnfmt";
|
||||
import * as md from 'ts-markdown-builder';
|
||||
|
||||
export async function formGuildInfoMessage(guild_id: string): Promise<CreateMessageOptions & InteractionCallbackOptions> {
|
||||
@ -28,22 +29,22 @@ where ranked.uid = ${guild_id}
|
||||
|
||||
if (result.length == 0) {
|
||||
return {
|
||||
content: "no guild found",
|
||||
content: "No guild found.",
|
||||
};
|
||||
}
|
||||
|
||||
const guild = result[0];
|
||||
|
||||
const output = [
|
||||
md.heading("[wip] guild info"),
|
||||
md.heading("overview"),
|
||||
`[${guild.prefix}] ${guild.name}
|
||||
level: ${guild.level}
|
||||
guild xp rank: ${guild.xp_rank >= 1000 ? "1000+" : guild.xp_rank}
|
||||
xp: ${guild.xp}
|
||||
territories: ${guild.territories}
|
||||
wars: ${guild.wars}`,
|
||||
].join("\n\n");
|
||||
`# 🏰 Guild Information`,
|
||||
`## **[${guild.prefix}] ${guild.name}**\n`,
|
||||
`### 📊 Statistics`,
|
||||
`> **Level:** \`${guild.level}\``,
|
||||
`> **Total XP:** \`${formatNumber(guild.xp)}\``,
|
||||
`> **XP Rank:** \`#${guild.xp_rank >= 1000 ? "1000+" : guild.xp_rank}\``,
|
||||
`> **Territories:** \`${guild.territories}\``,
|
||||
`> **Wars:** \`${guild.wars.toLocaleString()}\``,
|
||||
].join("\n");
|
||||
|
||||
return {
|
||||
content: output,
|
||||
@ -54,17 +55,24 @@ export async function formGuildOnlineMessage(guild_id: string): Promise<CreateMe
|
||||
const { sql } = await c.getAsync(PG);
|
||||
|
||||
const result = await sql`select
|
||||
name,
|
||||
rank,
|
||||
contributed,
|
||||
gi.name as guild_name,
|
||||
gi.prefix as guild_prefix,
|
||||
minecraft.user.name as name,
|
||||
gm.rank,
|
||||
gm.contributed,
|
||||
minecraft.user.server as server
|
||||
from wynn.guild_members inner join minecraft.user
|
||||
on minecraft.user.uid = wynn.guild_members.member_id
|
||||
from wynn.guild_members gm
|
||||
inner join minecraft.user
|
||||
on minecraft.user.uid = gm.member_id
|
||||
inner join wynn.guild_info gi
|
||||
on gi.uid = gm.guild_id
|
||||
where minecraft.user.server is not null
|
||||
and wynn.guild_members.guild_id = ${guild_id}
|
||||
and gm.guild_id = ${guild_id}
|
||||
`;
|
||||
|
||||
const members = type({
|
||||
guild_name: "string",
|
||||
guild_prefix: "string",
|
||||
name: "string",
|
||||
rank: "string",
|
||||
contributed: "string",
|
||||
@ -73,13 +81,18 @@ export async function formGuildOnlineMessage(guild_id: string): Promise<CreateMe
|
||||
|
||||
if (members.length == 0) {
|
||||
return {
|
||||
content: "nobody is online :(",
|
||||
content: "😴 No guild members are currently online.",
|
||||
};
|
||||
}
|
||||
|
||||
// Get guild info
|
||||
const guildName = members[0].guild_name;
|
||||
const guildPrefix = members[0].guild_prefix;
|
||||
|
||||
// Sort by contribution
|
||||
members.sort((a, b) => Number(b.contributed) - Number(a.contributed));
|
||||
|
||||
// group members by server
|
||||
// Group members by server
|
||||
const membersByServer = members.reduce((acc, member) => {
|
||||
if (acc[member.server] == undefined) {
|
||||
acc[member.server] = [];
|
||||
@ -88,12 +101,29 @@ export async function formGuildOnlineMessage(guild_id: string): Promise<CreateMe
|
||||
return acc;
|
||||
}, {} as Record<string, typeof members>);
|
||||
|
||||
const output = Object.entries(membersByServer).map(([server, mx]) => {
|
||||
return `**[${server}]** (${mx.length}): ${mx.map(m => m.name).join(", ")}`;
|
||||
// Sort servers by player count
|
||||
const sortedServers = Object.entries(membersByServer)
|
||||
.sort(([, a], [, b]) => b.length - a.length);
|
||||
|
||||
// Build server sections
|
||||
const serverSections = sortedServers.map(([server, serverMembers]) => {
|
||||
const memberList = serverMembers.map(m => {
|
||||
const emoji = getRankEmoji(m.rank);
|
||||
return `${emoji} ${m.name}`;
|
||||
}).join(", ");
|
||||
|
||||
return `### 🌐 ${server} (${serverMembers.length} player${serverMembers.length > 1 ? 's' : ''})\n> ${memberList}`;
|
||||
});
|
||||
|
||||
const output = [
|
||||
`# 🟢 Online Guild Members`,
|
||||
`**[${guildPrefix}] ${guildName}**\n`,
|
||||
`📊 **Total Online:** \`${members.length}\` members across \`${sortedServers.length}\` servers\n`,
|
||||
...serverSections
|
||||
].join("\n");
|
||||
|
||||
return {
|
||||
content: `**total**: ${members.length} \n` + output,
|
||||
content: output,
|
||||
};
|
||||
}
|
||||
|
||||
@ -101,38 +131,89 @@ export async function formGuildLeaderboardMessage(guild_id: string): Promise<Cre
|
||||
const { sql } = await c.getAsync(PG);
|
||||
|
||||
const result = await sql`select
|
||||
name,
|
||||
rank,
|
||||
contributed
|
||||
from wynn.guild_members inner join minecraft.user
|
||||
on minecraft.user.uid = wynn.guild_members.member_id
|
||||
where wynn.guild_members.guild_id = ${guild_id}
|
||||
gi.name as guild_name,
|
||||
gi.prefix as guild_prefix,
|
||||
minecraft.user.name as name,
|
||||
gm.rank,
|
||||
gm.contributed
|
||||
from wynn.guild_members gm
|
||||
inner join minecraft.user
|
||||
on minecraft.user.uid = gm.member_id
|
||||
inner join wynn.guild_info gi
|
||||
on gi.uid = gm.guild_id
|
||||
where gm.guild_id = ${guild_id}
|
||||
`;
|
||||
|
||||
const members = type({
|
||||
guild_name: "string",
|
||||
guild_prefix: "string",
|
||||
name: "string",
|
||||
rank: "string",
|
||||
contributed: "string",
|
||||
}).array().assert(result);
|
||||
|
||||
const tw = new TabWriter();
|
||||
members.sort((a, b) => Number(b.contributed) - Number(a.contributed));
|
||||
let idx = 1;
|
||||
for (const member of members.slice(0, 10)) {
|
||||
tw.add([
|
||||
`${idx}.`,
|
||||
member.rank,
|
||||
member.name,
|
||||
Number(member.contributed).toLocaleString(),
|
||||
]);
|
||||
idx = idx + 1;
|
||||
if (members.length === 0) {
|
||||
return {
|
||||
content: "No guild members found.",
|
||||
};
|
||||
}
|
||||
|
||||
const built = tw.build();
|
||||
// Sort by contribution
|
||||
members.sort((a, b) => Number(b.contributed) - Number(a.contributed));
|
||||
const topMembers = members.slice(0, 15);
|
||||
|
||||
// Get guild info from first member (all have same guild info)
|
||||
const guildName = members[0].guild_name;
|
||||
const guildPrefix = members[0].guild_prefix;
|
||||
|
||||
// Calculate total guild XP
|
||||
const totalXP = members.reduce((sum, m) => sum + Number(m.contributed), 0);
|
||||
|
||||
// Build the leaderboard with proper alignment
|
||||
const tw = new TabWriter(2);
|
||||
|
||||
// Add header row
|
||||
tw.add(["#", "Rank", "Player", "XP", "%"]);
|
||||
tw.add(["───", "────────────", "────────────────", "──────────", "──────"]); // Separator line
|
||||
|
||||
topMembers.forEach((member, index) => {
|
||||
const position = index + 1;
|
||||
const posStr = position === 1 ? "🥇" : position === 2 ? "🥈" : position === 3 ? "🥉" : `${position}.`;
|
||||
const rankEmoji = getRankEmoji(member.rank);
|
||||
const contribution = Number(member.contributed);
|
||||
const percentage = ((contribution / totalXP) * 100).toFixed(1);
|
||||
|
||||
// Use formatNumber for consistent formatting
|
||||
const contribFormatted = contribution >= 10_000
|
||||
? formatNumber(contribution)
|
||||
: contribution.toLocaleString();
|
||||
|
||||
tw.add([
|
||||
posStr,
|
||||
`${rankEmoji} ${member.rank}`,
|
||||
member.name,
|
||||
contribFormatted,
|
||||
`${percentage}%`
|
||||
]);
|
||||
});
|
||||
|
||||
const leaderboardTable = tw.build();
|
||||
|
||||
// Create summary stats
|
||||
const avgContribution = Math.floor(totalXP / members.length);
|
||||
|
||||
const output = [
|
||||
md.heading("Guild Exp:"),
|
||||
md.codeBlock(built),
|
||||
].join("\n\n");
|
||||
`# 📊 Guild XP Leaderboard`,
|
||||
`**[${guildPrefix}] ${guildName}**\n`,
|
||||
`📈 **Total Guild XP:** \`${totalXP.toLocaleString()}\``,
|
||||
`👥 **Total Members:** \`${members.length}\``,
|
||||
`📊 **Average Contribution:** \`${avgContribution.toLocaleString()}\`\n`,
|
||||
`### Top Contributors`,
|
||||
"```",
|
||||
leaderboardTable,
|
||||
"```",
|
||||
`*Showing top 15 of ${members.length} members*`
|
||||
].join("\n");
|
||||
|
||||
return {
|
||||
content: output,
|
||||
|
||||
@ -28,12 +28,11 @@ export class TabWriter {
|
||||
return "";
|
||||
}
|
||||
const columnWidths = this.columns.map(col => col.reduce((a, b) => Math.max(a, b.length+this.spacing), 0));
|
||||
console.log(columnWidths)
|
||||
for(let i = 0; i < this.columns[0].length; i++) {
|
||||
if (i > 0) out += "\n";
|
||||
for(let j = 0; j < this.columns.length; j++) {
|
||||
out+= this.columns[j][i].padEnd(columnWidths[j]);
|
||||
}
|
||||
out+= "\n";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
35
ts/src/lib/util/wynnfmt.ts
Normal file
35
ts/src/lib/util/wynnfmt.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Wynncraft formatting utilities
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mapping of Wynncraft guild ranks to their corresponding emojis
|
||||
*/
|
||||
export const RANK_EMOJIS = {
|
||||
"OWNER": "👑",
|
||||
"CHIEF": "⭐",
|
||||
"STRATEGIST": "🎯",
|
||||
"CAPTAIN": "⚔️",
|
||||
"RECRUITER": "📢",
|
||||
"RECRUIT": "🌱",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Get the emoji for a given guild rank
|
||||
* @param rank - The guild rank name
|
||||
* @returns The corresponding emoji or a default bullet point
|
||||
*/
|
||||
export function getRankEmoji(rank: string): string {
|
||||
return RANK_EMOJIS[rank as keyof typeof RANK_EMOJIS] || "•";
|
||||
}
|
||||
|
||||
/**
|
||||
* Format large numbers with K/M suffixes
|
||||
* @param num - The number to format
|
||||
* @returns Formatted string with appropriate suffix
|
||||
*/
|
||||
export function formatNumber(num: number): string {
|
||||
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M`;
|
||||
if (num >= 1_000) return `${(num / 1_000).toFixed(0)}K`;
|
||||
return num.toLocaleString();
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user