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 { CreateMessageOptions, InteractionCallbackOptions } from "discordeno";
|
||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { TabWriter } from "#/lib/util/tabwriter";
|
import { TabWriter } from "#/lib/util/tabwriter";
|
||||||
|
import { RANK_EMOJIS, getRankEmoji, formatNumber } from "#/lib/util/wynnfmt";
|
||||||
import * as md from 'ts-markdown-builder';
|
import * as md from 'ts-markdown-builder';
|
||||||
|
|
||||||
export async function formGuildInfoMessage(guild_id: string): Promise<CreateMessageOptions & InteractionCallbackOptions> {
|
export async function formGuildInfoMessage(guild_id: string): Promise<CreateMessageOptions & InteractionCallbackOptions> {
|
||||||
@ -28,22 +29,22 @@ where ranked.uid = ${guild_id}
|
|||||||
|
|
||||||
if (result.length == 0) {
|
if (result.length == 0) {
|
||||||
return {
|
return {
|
||||||
content: "no guild found",
|
content: "No guild found.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const guild = result[0];
|
const guild = result[0];
|
||||||
|
|
||||||
const output = [
|
const output = [
|
||||||
md.heading("[wip] guild info"),
|
`# 🏰 Guild Information`,
|
||||||
md.heading("overview"),
|
`## **[${guild.prefix}] ${guild.name}**\n`,
|
||||||
`[${guild.prefix}] ${guild.name}
|
`### 📊 Statistics`,
|
||||||
level: ${guild.level}
|
`> **Level:** \`${guild.level}\``,
|
||||||
guild xp rank: ${guild.xp_rank >= 1000 ? "1000+" : guild.xp_rank}
|
`> **Total XP:** \`${formatNumber(guild.xp)}\``,
|
||||||
xp: ${guild.xp}
|
`> **XP Rank:** \`#${guild.xp_rank >= 1000 ? "1000+" : guild.xp_rank}\``,
|
||||||
territories: ${guild.territories}
|
`> **Territories:** \`${guild.territories}\``,
|
||||||
wars: ${guild.wars}`,
|
`> **Wars:** \`${guild.wars.toLocaleString()}\``,
|
||||||
].join("\n\n");
|
].join("\n");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: output,
|
content: output,
|
||||||
@ -54,17 +55,24 @@ export async function formGuildOnlineMessage(guild_id: string): Promise<CreateMe
|
|||||||
const { sql } = await c.getAsync(PG);
|
const { sql } = await c.getAsync(PG);
|
||||||
|
|
||||||
const result = await sql`select
|
const result = await sql`select
|
||||||
name,
|
gi.name as guild_name,
|
||||||
rank,
|
gi.prefix as guild_prefix,
|
||||||
contributed,
|
minecraft.user.name as name,
|
||||||
|
gm.rank,
|
||||||
|
gm.contributed,
|
||||||
minecraft.user.server as server
|
minecraft.user.server as server
|
||||||
from wynn.guild_members inner join minecraft.user
|
from wynn.guild_members gm
|
||||||
on minecraft.user.uid = wynn.guild_members.member_id
|
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
|
where minecraft.user.server is not null
|
||||||
and wynn.guild_members.guild_id = ${guild_id}
|
and gm.guild_id = ${guild_id}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const members = type({
|
const members = type({
|
||||||
|
guild_name: "string",
|
||||||
|
guild_prefix: "string",
|
||||||
name: "string",
|
name: "string",
|
||||||
rank: "string",
|
rank: "string",
|
||||||
contributed: "string",
|
contributed: "string",
|
||||||
@ -73,13 +81,18 @@ export async function formGuildOnlineMessage(guild_id: string): Promise<CreateMe
|
|||||||
|
|
||||||
if (members.length == 0) {
|
if (members.length == 0) {
|
||||||
return {
|
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));
|
members.sort((a, b) => Number(b.contributed) - Number(a.contributed));
|
||||||
|
|
||||||
// group members by server
|
// Group members by server
|
||||||
const membersByServer = members.reduce((acc, member) => {
|
const membersByServer = members.reduce((acc, member) => {
|
||||||
if (acc[member.server] == undefined) {
|
if (acc[member.server] == undefined) {
|
||||||
acc[member.server] = [];
|
acc[member.server] = [];
|
||||||
@ -88,12 +101,29 @@ export async function formGuildOnlineMessage(guild_id: string): Promise<CreateMe
|
|||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, typeof members>);
|
}, {} as Record<string, typeof members>);
|
||||||
|
|
||||||
const output = Object.entries(membersByServer).map(([server, mx]) => {
|
// Sort servers by player count
|
||||||
return `**[${server}]** (${mx.length}): ${mx.map(m => m.name).join(", ")}`;
|
const sortedServers = Object.entries(membersByServer)
|
||||||
}).join(", ");
|
.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 {
|
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 { sql } = await c.getAsync(PG);
|
||||||
|
|
||||||
const result = await sql`select
|
const result = await sql`select
|
||||||
name,
|
gi.name as guild_name,
|
||||||
rank,
|
gi.prefix as guild_prefix,
|
||||||
contributed
|
minecraft.user.name as name,
|
||||||
from wynn.guild_members inner join minecraft.user
|
gm.rank,
|
||||||
on minecraft.user.uid = wynn.guild_members.member_id
|
gm.contributed
|
||||||
where wynn.guild_members.guild_id = ${guild_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 gm.guild_id = ${guild_id}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const members = type({
|
const members = type({
|
||||||
|
guild_name: "string",
|
||||||
|
guild_prefix: "string",
|
||||||
name: "string",
|
name: "string",
|
||||||
rank: "string",
|
rank: "string",
|
||||||
contributed: "string",
|
contributed: "string",
|
||||||
}).array().assert(result);
|
}).array().assert(result);
|
||||||
|
|
||||||
const tw = new TabWriter();
|
if (members.length === 0) {
|
||||||
members.sort((a, b) => Number(b.contributed) - Number(a.contributed));
|
return {
|
||||||
let idx = 1;
|
content: "No guild members found.",
|
||||||
for (const member of members.slice(0, 10)) {
|
};
|
||||||
tw.add([
|
|
||||||
`${idx}.`,
|
|
||||||
member.rank,
|
|
||||||
member.name,
|
|
||||||
Number(member.contributed).toLocaleString(),
|
|
||||||
]);
|
|
||||||
idx = idx + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = [
|
const output = [
|
||||||
md.heading("Guild Exp:"),
|
`# 📊 Guild XP Leaderboard`,
|
||||||
md.codeBlock(built),
|
`**[${guildPrefix}] ${guildName}**\n`,
|
||||||
].join("\n\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 {
|
return {
|
||||||
content: output,
|
content: output,
|
||||||
|
|||||||
@ -28,12 +28,11 @@ export class TabWriter {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
const columnWidths = this.columns.map(col => col.reduce((a, b) => Math.max(a, b.length+this.spacing), 0));
|
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++) {
|
for(let i = 0; i < this.columns[0].length; i++) {
|
||||||
|
if (i > 0) out += "\n";
|
||||||
for(let j = 0; j < this.columns.length; j++) {
|
for(let j = 0; j < this.columns.length; j++) {
|
||||||
out+= this.columns[j][i].padEnd(columnWidths[j]);
|
out+= this.columns[j][i].padEnd(columnWidths[j]);
|
||||||
}
|
}
|
||||||
out+= "\n";
|
|
||||||
}
|
}
|
||||||
return out;
|
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