Files
erika/lib/profile/guild-profile.ts
T

90 lines
3.1 KiB
TypeScript

import type { DiscordMemberProfile } from "@/lib/discord/discord";
import type { DiscordVoiceChannel } from "@/lib/leaderboard/discord";
import type {
getMemberVoiceStats,
getMemberXpStats,
} from "@/lib/leaderboard/database";
import { buildLevelProgress, type LevelProgress } from "@/lib/leaderboard/level-progress";
export type GuildProfileXp = LevelProgress & { rank: number | null };
export type GuildProfileVoice = Awaited<ReturnType<typeof getMemberVoiceStats>>;
export type GuildProfileRead = {
profile: DiscordMemberProfile | null;
profileUnavailable: boolean;
xp: GuildProfileXp | null;
voice: GuildProfileVoice | null;
};
export type GuildProfileSources = {
getProfile: (discordId: string) => Promise<DiscordMemberProfile | null>;
getXp: (discordId: string) => ReturnType<typeof getMemberXpStats>;
getVoice: (
discordId: string,
currentChannelIds: string[],
) => ReturnType<typeof getMemberVoiceStats>;
getCurrentVoiceChannels: () => Promise<DiscordVoiceChannel[]>;
reportFailure?: (source: "profile" | "xp" | "voice", error: unknown) => void;
};
function defaultReportFailure(source: string, error: unknown) {
console.error(`Failed to load own Guild profile ${source} data`, error);
}
export function createGuildProfileReader(
sources: GuildProfileSources,
) {
return async function loadGuildProfile(discordId: string): Promise<GuildProfileRead> {
const reportFailure = sources.reportFailure ?? defaultReportFailure;
const [profileResult, xpResult, voiceResult] = await Promise.allSettled([
sources.getProfile(discordId),
sources.getXp(discordId),
sources.getCurrentVoiceChannels().then((channels) =>
sources.getVoice(discordId, channels.map((channel) => channel.id)),
),
]);
if (profileResult.status === "rejected") {
reportFailure("profile", profileResult.reason);
}
if (xpResult.status === "rejected") {
reportFailure("xp", xpResult.reason);
}
if (voiceResult.status === "rejected") {
reportFailure("voice", voiceResult.reason);
}
const profile = profileResult.status === "fulfilled" ? profileResult.value : null;
const xpStats = xpResult.status === "fulfilled" ? xpResult.value : null;
const voice = voiceResult.status === "fulfilled" ? voiceResult.value : null;
return {
profile,
profileUnavailable: !profile,
xp: xpStats
? {
...buildLevelProgress(xpStats.level, xpStats.totalXp),
rank: xpStats.rank,
}
: null,
voice,
};
};
}
export async function loadGuildProfile(discordId: string) {
const [discord, leaderboardDiscord, leaderboardDatabase] = await Promise.all([
import("@/lib/discord/discord"),
import("@/lib/leaderboard/discord"),
import("@/lib/leaderboard/database"),
]);
return createGuildProfileReader({
getProfile: discord.getDiscordMemberProfile,
getXp: leaderboardDatabase.getMemberXpStats,
getVoice: leaderboardDatabase.getMemberVoiceStats,
getCurrentVoiceChannels: leaderboardDiscord.getCurrentDiscordVoiceChannels,
})(discordId);
}