Info / profile bot — user, server, role, and guild member profile helpers.
info-bot.js · 425 lines
Info / profile bot — user, server, role, and guild member profile helpers.
View on GitHubnode examples/info-bot.js
javascript
/**
* Info / profile bot — user, server, role, and guild member profile helpers.
*
* Commands:
* !userinfo [@user|id] — profile (global + server when in a guild)
* !serverinfo [guild_id] — this server (or by ID)
* !roleinfo <@Role|id|name> — role details
* !setnick [nickname] — change the bot's nickname here
* !setavatar [url|clear] — change the bot's guild avatar
* !bme — dump guild.members.me fields
*
* Usage:
* FLUXER_BOT_TOKEN=your_token node examples/info-bot.js
*
* Optional: SETAVATAR_DEBUG=1
*/
import {
Client,
Events,
EmbedBuilder,
PermissionFlags,
cdnBannerURL,
cdnMemberAvatarURL,
parsePrefixCommand,
parseUserMention,
} from '@fluxerjs/core';
const PREFIX = '!';
const BRAND_COLOR = 0x4641d9;
const SETAVATAR_DEBUG =
process.env.SETAVATAR_DEBUG === '1' || process.env.SETAVATAR_DEBUG === 'true';
const VERIFICATION_LEVELS = ['None', 'Low', 'Medium', 'High', 'Very High'];
const MFA_LEVELS = ['None', 'Elevated'];
const EXPLICIT_CONTENT_FILTERS = ['Disabled', 'Members without roles', 'All members'];
const DEFAULT_NOTIFICATION_LEVELS = ['All messages', 'Only mentions'];
function resolveRoleArg(arg) {
if (!arg) return null;
const mention = arg.match(/^<@&(\d+)>$/);
if (mention) return { type: 'id', value: mention[1] };
if (/^\d{17,19}$/.test(arg)) return { type: 'id', value: arg };
return { type: 'name', value: arg };
}
function addProfileFields(fields, profile, profileData, prefix = '') {
if (!profile || typeof profile !== 'object') return;
if (profile.pronouns) {
fields.push({
name: `${prefix}Pronouns`,
value: String(profile.pronouns).slice(0, 40),
inline: true,
});
}
if (profile.bio) {
fields.push({ name: `${prefix}Bio`, value: String(profile.bio).slice(0, 1024) });
}
if (profile.banner) {
fields.push({ name: `${prefix}Banner`, value: 'Set', inline: true });
}
const accent = profile.accentColor ?? profile.bannerColor;
if (accent != null) {
fields.push({
name: `${prefix}Accent`,
value: `#${Number(accent).toString(16).padStart(6, '0')}`,
inline: true,
});
}
const mutual = profileData?.mutualGuilds?.length ?? profileData?.mutualGuildIds?.length;
if (mutual) {
fields.push({ name: `${prefix}Mutual servers`, value: String(mutual), inline: true });
}
}
async function requireGuild(message, client) {
const guildId = message.guildId;
if (!guildId) {
await message.reply('Use this command in a server.');
return null;
}
const guild = client.guilds.get(guildId) ?? (await client.guilds.fetch(guildId));
if (!guild) {
await message.reply('Could not find this server.');
return null;
}
return guild;
}
const token = process.env.FLUXER_BOT_TOKEN;
if (!token) {
console.error('Error: Set FLUXER_BOT_TOKEN environment variable');
process.exit(1);
}
const client = new Client({
intents: 0,
rest: process.env.FLUXER_API_URL ? { api: process.env.FLUXER_API_URL } : undefined,
});
client.on(Events.Ready, () => {
console.log(`Logged in as ${client.user?.username}`);
client.user?.setPresence({
status: 'online',
customStatus: { text: 'Try !userinfo' },
});
});
client.on(Events.MessageCreate, async (message) => {
if (message.author.bot || !message.content) return;
const parsed = parsePrefixCommand(message.content, PREFIX);
if (!parsed) return;
const { command, args } = parsed;
try {
if (command === 'setnick') {
const guild = await requireGuild(message, client);
if (!guild) return;
const me = guild.members.me ?? (await guild.members.fetchMe());
const newNick = args.join(' ').trim() || null;
try {
await me.edit({ nick: newNick });
await message.reply(
newNick
? `Nickname set to \`${newNick}\`.`
: 'Nickname cleared (showing username again).',
);
} catch {
await message
.reply('Failed to change nickname. The bot may need Change Nickname permission.')
.catch(() => {});
}
return;
}
if (command === 'setavatar') {
const guild = await requireGuild(message, client);
if (!guild) return;
const me = guild.members.me ?? (await guild.members.fetchMe());
const arg = args[0]?.toLowerCase();
if (arg === 'clear' || arg === 'reset') {
try {
await me.edit({ avatar: null });
await message.reply('Guild avatar cleared.');
} catch (err) {
if (SETAVATAR_DEBUG) console.error('[setavatar]', err);
await message.reply('Failed to clear guild avatar.').catch(() => {});
}
return;
}
const url = args[0]?.trim();
if (!url || !/^https?:\/\//i.test(url)) {
await message.reply(
'Provide an image URL: `!setavatar https://example.com/image.png` or `!setavatar clear`.',
);
return;
}
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000);
const res = await fetch(url, { signal: controller.signal });
clearTimeout(timeout);
if (!res.ok) {
await message.reply(`Could not fetch image: ${res.status}`);
return;
}
const mime = (res.headers.get('content-type') ?? 'image/png').split(';')[0].trim();
if (!mime.startsWith('image/')) {
await message.reply('URL must point to an image (png, jpeg, gif, webp).');
return;
}
const buf = Buffer.from(await res.arrayBuffer());
const dataUri = `data:${mime};base64,${buf.toString('base64')}`;
await me.edit({ avatar: dataUri });
const updated = await guild.fetchMember(me.id);
if (dataUri && !updated.avatar) {
await message.reply(
'Request succeeded but the avatar was not applied. Guild avatars may require premium on Fluxer.',
);
} else {
await message.reply('Guild avatar updated!');
}
} catch (err) {
if (SETAVATAR_DEBUG) console.error('[setavatar]', err);
if (err?.name === 'AbortError') {
await message.reply('Timed out fetching image (30s).');
} else {
await message.reply('Failed to set guild avatar.').catch(() => {});
}
}
return;
}
if (command === 'bme') {
const guild = await requireGuild(message, client);
if (!guild) return;
const me = guild.members.me ?? (await guild.members.fetchMe());
const roleNames = [...me.roles.roleIds]
.filter((id) => id !== guild.id)
.map((id) => guild.roles.get(id)?.name ?? id);
const permNames = [];
for (const [name, bit] of Object.entries(PermissionFlags)) {
if (typeof bit === 'number' && me.permissions.has(bit)) permNames.push(name);
}
const embed = new EmbedBuilder()
.setTitle('guild.members.me')
.setDescription("Bot's GuildMember in this server")
.setColor(me.accentColor ?? me.user.avatarColor ?? BRAND_COLOR)
.setThumbnail(me.displayAvatarURL({ size: 256 }))
.addFields(
{ name: 'ID', value: `\`${me.id}\``, inline: true },
{ name: 'Username', value: me.user.username ?? '—', inline: true },
{ name: 'Display name', value: me.displayName ?? '—', inline: true },
{ name: 'Nickname', value: me.nick ?? '*(none)*', inline: true },
{ name: 'Joined', value: me.joinedAt.toISOString(), inline: true },
{
name: 'Roles',
value: roleNames.length ? roleNames.slice(0, 15).join(', ') : '*(none)*',
},
{
name: 'Permissions (sample)',
value: permNames.length ? permNames.slice(0, 12).join(', ') : '*(none)*',
},
)
.setFooter({ text: `Guild: ${guild.name}` })
.setTimestamp();
await message.reply({ embeds: [embed] });
return;
}
if (command === 'userinfo') {
const userId = args[0] ? parseUserMention(args[0]) : message.author.id;
if (!userId) {
await message.reply('Provide a mention or user ID. Example: `!userinfo @Someone`');
return;
}
let data;
try {
data = await client.users.fetchWithProfile(userId, {
guildId: message.guildId ?? undefined,
});
} catch {
await message.reply('Could not fetch that user.');
return;
}
const { user, userData, globalProfile, serverProfile, memberData } = data;
const globalProf = globalProfile?.userProfile;
const serverProf = serverProfile?.userProfile;
const bannerHash = serverProf?.banner || globalProf?.banner || userData.banner;
const bannerUrl = bannerHash ? cdnBannerURL(userData.id, bannerHash, { size: 512 }) : null;
const accent =
Number(
serverProf?.accentColor ??
serverProf?.bannerColor ??
globalProf?.accentColor ??
globalProf?.bannerColor ??
userData.avatar_color ??
memberData?.accent_color ??
BRAND_COLOR,
) || BRAND_COLOR;
const avatarUrl =
message.guildId && memberData?.avatar
? cdnMemberAvatarURL(message.guildId, userData.id, memberData.avatar, { size: 256 })
: user.displayAvatarURL({ size: 256 });
const displayName = userData.global_name ?? userData.username ?? 'User';
const fields = [
{ name: 'Username', value: userData.username ?? '—', inline: true },
{ name: 'Display name', value: displayName, inline: true },
{ name: 'ID', value: `\`${userData.id}\``, inline: true },
{ name: 'Bot', value: userData.bot ? 'Yes' : 'No', inline: true },
];
if (globalProfile) {
fields.push({ name: '\u200B', value: '**Global profile**', inline: false });
addProfileFields(fields, globalProf, globalProfile);
}
if (message.guildId && (serverProfile || memberData)) {
fields.push({ name: '\u200B', value: '**Server profile**', inline: false });
addProfileFields(fields, serverProf, serverProfile, 'Server ');
if (memberData?.nick) {
fields.push({
name: 'Nickname',
value: String(memberData.nick).slice(0, 32),
inline: true,
});
}
if (memberData?.joined_at) {
fields.push({
name: 'Joined',
value: `<t:${Math.floor(new Date(memberData.joined_at).getTime() / 1000)}:R>`,
inline: true,
});
}
}
const embed = new EmbedBuilder()
.setTitle(`${displayName}'s profile`)
.setAuthor({ name: displayName, iconURL: avatarUrl })
.setColor(accent)
.addFields(...fields)
.setFooter({ text: `Requested by ${message.author.username}` })
.setTimestamp();
if (bannerUrl) embed.setImage(bannerUrl);
await message.reply({ embeds: [embed] });
return;
}
if (command === 'serverinfo') {
const guildId = args[0] ?? message.guildId;
if (!guildId) {
await message.reply('Use this in a server or provide a guild ID.');
return;
}
let guild;
try {
guild = client.guilds.get(guildId) ?? (await client.guilds.fetch(guildId));
} catch {
await message.reply('Could not fetch that server.');
return;
}
if (!guild) {
await message.reply('Could not fetch that server.');
return;
}
const embed = new EmbedBuilder()
.setTitle(guild.name ?? 'Server')
.setColor(BRAND_COLOR)
.setThumbnail(guild.iconURL?.({ size: 256 }) ?? null)
.addFields(
{ name: 'ID', value: `\`${guild.id}\``, inline: true },
{ name: 'Owner ID', value: `\`${guild.ownerId ?? '—'}\``, inline: true },
{
name: 'Verification',
value: VERIFICATION_LEVELS[guild.verificationLevel] ?? String(guild.verificationLevel),
inline: true,
},
{
name: 'MFA',
value: MFA_LEVELS[guild.mfaLevel] ?? String(guild.mfaLevel),
inline: true,
},
{
name: 'Explicit filter',
value:
EXPLICIT_CONTENT_FILTERS[guild.explicitContentFilter] ??
String(guild.explicitContentFilter ?? 0),
inline: true,
},
{
name: 'Default notifications',
value: DEFAULT_NOTIFICATION_LEVELS[guild.defaultMessageNotifications] ?? '—',
inline: true,
},
{
name: 'Features',
value: guild.features?.length ? guild.features.join(', ') : '—',
},
)
.setFooter({ text: `Requested by ${message.author.username}` })
.setTimestamp();
await message.reply({ embeds: [embed] });
return;
}
if (command === 'roleinfo') {
const guild = await requireGuild(message, client);
if (!guild) return;
const resolved = resolveRoleArg(args[0]);
if (!resolved) {
await message.reply('Provide a role mention, ID, or name. Example: `!roleinfo Moderator`');
return;
}
const roleList = await guild.fetchRoles();
const role =
roleList.find((r) =>
resolved.type === 'id'
? r.id === resolved.value
: r.name?.toLowerCase() === resolved.value.toLowerCase(),
) ??
(resolved.type === 'id' ? guild.roles.get(resolved.value) : null) ??
null;
if (!role) {
await message.reply(
resolved.type === 'id' ? 'No role found with that ID.' : 'No role found with that name.',
);
return;
}
const embed = new EmbedBuilder()
.setTitle(role.name ?? 'Role')
.setColor(role.color && role.color !== 0 ? role.color : BRAND_COLOR)
.addFields(
{ name: 'ID', value: `\`${role.id}\``, inline: true },
{ name: 'Position', value: String(role.position ?? 0), inline: true },
{
name: 'Color',
value:
role.color && role.color !== 0
? `#${Number(role.color).toString(16).padStart(6, '0')}`
: 'Default',
inline: true,
},
{ name: 'Hoist', value: role.hoist ? 'Yes' : 'No', inline: true },
{ name: 'Mentionable', value: role.mentionable ? 'Yes' : 'No', inline: true },
{ name: 'Unicode emoji', value: role.unicodeEmoji ?? '—', inline: true },
{ name: 'Permissions', value: String(role.permissions ?? '—').slice(0, 1024) },
)
.setFooter({ text: `Requested by ${message.author.username}` })
.setTimestamp();
await message.reply({ embeds: [embed] });
}
} catch (err) {
console.error('Command error:', err);
await message.reply('Something went wrong.').catch(() => {});
}
});
client.on(Events.Error, (err) => console.error('Client error:', err));
try {
await client.login(token);
} catch (err) {
console.error('Login failed:', err);
process.exit(1);
}