118 lines
4.6 KiB
TypeScript
118 lines
4.6 KiB
TypeScript
import { Client, Partials, GatewayIntentBits, ActivityType } from "discord.js";
|
|
|
|
import { DISCORD_TOKEN, DISCORD_USER_ID, DISCORD_GUILD_ID } from "$env/static/private";
|
|
|
|
class Bot {
|
|
private client: Client;
|
|
|
|
// Normalize a discord.js Activity object into a simple POJO
|
|
private async mapActivity(a: any) {
|
|
const type = typeof a.type === 'number' ? a.type : (ActivityType[a.type] ?? a.type);
|
|
const application_id = a.applicationId ?? a.application_id ?? null;
|
|
|
|
const rawLarge = a.assets?.largeImage ?? a.assets?.large_image ?? null;
|
|
const rawSmall = a.assets?.smallImage ?? a.assets?.small_image ?? null;
|
|
|
|
const formatAsset = (raw: string | null) => {
|
|
if (!raw) return null;
|
|
if (raw.startsWith('mp:external/')) {
|
|
return `https://media.discordapp.net/${raw.replace(/^mp:/, '')}`;
|
|
}
|
|
if (/^https?:\/\//.test(raw)) return raw;
|
|
if (application_id) return `https://cdn.discordapp.com/app-assets/${application_id}/${raw}.webp`;
|
|
return raw;
|
|
};
|
|
|
|
let app_icon = null;
|
|
if (type === 0 && application_id) {
|
|
try {
|
|
const appUser = await this.client.users.fetch(application_id);
|
|
app_icon = appUser.displayAvatarURL({ extension: 'webp', size: 128 });
|
|
} catch (e) {
|
|
// Placeholder for apps that don't have a bot user
|
|
app_icon = "https://cdn.discordapp.com/embed/avatars/0.png";
|
|
}
|
|
}
|
|
|
|
return {
|
|
name: a.name ?? null,
|
|
type,
|
|
details: a.details ?? a.state ?? null,
|
|
url: a.url ?? null,
|
|
application_id,
|
|
assets: {
|
|
large_image: formatAsset(rawLarge),
|
|
small_image: formatAsset(rawSmall)
|
|
},
|
|
app_icon,
|
|
emoji: a.emoji ? { id: a.emoji.id ?? null, animated: !!a.emoji.animated } : null
|
|
};
|
|
}
|
|
|
|
constructor() {
|
|
// Need presence and members intents to read user presence/activity
|
|
this.client = new Client({
|
|
partials: [Partials.User],
|
|
intents: [
|
|
GatewayIntentBits.Guilds,
|
|
GatewayIntentBits.GuildMembers,
|
|
GatewayIntentBits.GuildPresences
|
|
]
|
|
});
|
|
|
|
this.client.login(DISCORD_TOKEN).catch((error) => {
|
|
console.error("Failed to login to Discord:", error);
|
|
});
|
|
|
|
this.client.once("clientReady", async () => {
|
|
console.log(`Logged in as ${this.client.user?.tag}`);
|
|
});
|
|
}
|
|
|
|
getClient(): Client {
|
|
return this.client;
|
|
}
|
|
|
|
/**
|
|
* Look up the user's presence specifically in the given guild.
|
|
* Returns null if guild/member/presence not available.
|
|
*/
|
|
async getUserActivityInGuild(guildId: string = DISCORD_GUILD_ID, userId: string = DISCORD_USER_ID) {
|
|
try {
|
|
// Fetch guild from cache or API
|
|
let guild = this.client.guilds.cache.get(guildId) || await this.client.guilds.fetch(guildId).catch(() => null);
|
|
if (!guild) return null;
|
|
|
|
// Fetch member (will populate presence if available)
|
|
const member = await guild.members.fetch(userId).catch(() => null);
|
|
if (!member) return null;
|
|
|
|
const presence = member.presence;
|
|
const user = member.user;
|
|
|
|
// Build avatar URL
|
|
const avatarHash = user.avatar;
|
|
const avatarUrl = avatarHash
|
|
? `https://cdn.discordapp.com/avatars/${user.id}/${avatarHash}.${avatarHash.startsWith('a_') ? 'gif' : 'webp'}?size=128`
|
|
: `https://cdn.discordapp.com/embed/avatars/${(BigInt(user.id) >> BigInt(22)) % BigInt(6)}.png`;
|
|
|
|
// Get guild/clan tag if available
|
|
const guildTag = (user as any).primaryGuild?.tag ?? null;
|
|
const guildTagBadgeImage = `https://cdn.discordapp.com/clan-badges/${(user as any).primaryGuild?.identityGuildId}/${(user as any).primaryGuild?.badge}.png?size=32`;
|
|
|
|
if (!presence) return { guildId, status: 'offline', activities: [], displayName: member.displayName, avatarUrl, guildTag, guildTagBadgeImage };
|
|
|
|
const status = presence.status;
|
|
const activities = await Promise.all((presence.activities || []).map(async (a: any) => await this.mapActivity(a)));
|
|
|
|
return { guildId, status, activities, displayName: member.displayName, avatarUrl, guildTag, guildTagBadgeImage };
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
export const bot = new Bot();
|
|
|
|
// Re-export image helper for convenience
|
|
export { fetchUserImages } from "./discord/fetchUserImages"; |