first commit

This commit is contained in:
yhqe committed 2026-06-28 11:30:57 -05:00
commit d94c5c6cc0
134 files changed
+23720

No files matched your search

+249
View File
@@ -0,0 +1,249 @@
// =============================================
// DISCORD ROLE EMOJI ADDER + ROLE ICON SETTER
// =============================================
// !addicons — fetches skin icons and uploads as server emojis
// !setroleicons — applies uploaded emojis as role icons
// !removeoldemojis — removes all skin emojis so you can re-add fresh
//
// Run with: node "add-icons.js"
// =============================================
require('dotenv').config();
const { Client, GatewayIntentBits, PermissionsBitField } = require('discord.js');
const sharp = require('sharp');
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
// Direct cosmetic IDs — all verified to return valid icons from fortnite-api.com
// overrideUrl = use a specific variant image instead of the default icon
const roleIconMap = [
{ name: 'Fully Golden Peely', id: 'CID_701_Athena_Commando_M_BananaAgent', emoji: 'FullyGoldenPeely',
overrideUrl: 'https://fortnite-api.com/images/cosmetics/br/cid_701_athena_commando_m_bananaagent/variants/progressive/stage4.png' },
{ name: 'OG Aerial Assault Trooper', id: 'CID_017_Athena_Commando_M', emoji: 'AerialAssaultTrooper' },
{ name: 'OG Ghoul Trooper', id: 'CID_029_Athena_Commando_F_Halloween', emoji: 'GhoulTrooper',
overrideUrl: 'https://fortnite-api.com/images/cosmetics/br/cid_029_athena_commando_f_halloween/variants/material/mat3.png' },
{ name: 'OG Renegade Raider', id: 'CID_028_Athena_Commando_F', emoji: 'RenegadeRaider' },
{ name: 'OG Skull Trooper', id: 'CID_030_Athena_Commando_M_Halloween', emoji: 'SkullTrooper',
overrideUrl: 'https://fortnite-api.com/images/cosmetics/br/cid_030_athena_commando_m_halloween/variants/clothingcolor/mat1.png' },
{ name: 'Barricade', id: 'Character_RankedPhantom', emoji: 'Barricade' },
{ name: 'Double Helix', id: 'CID_183_Athena_Commando_M_ModernMilitaryRed', emoji: 'DoubleHelix' },
{ name: 'Honor Guard', id: 'CID_342_Athena_Commando_M_StreetRacerMetallic', emoji: 'HonorGuard' },
{ name: 'Wonder', id: 'CID_434_Athena_Commando_F_StealthHonor', emoji: 'Wonder' },
{ name: 'Surf Strider', id: 'CID_783_Athena_Commando_M_AquaJacket', emoji: 'SurfStrider' },
{ name: 'Stealth Reflex', id: 'CID_386_Athena_Commando_M_StreetOpsStealth', emoji: 'StealthReflex' },
{ name: 'Dark Vertex', id: 'CID_371_Athena_Commando_M_SpeedyMidnight', emoji: 'DarkVertex' },
{ name: 'Royale Bomber', id: 'CID_113_Athena_Commando_M_BlueAce', emoji: 'RoyaleBomber' },
{ name: 'Wildcat', id: 'CID_757_Athena_Commando_F_WildCat', emoji: 'Wildcat' },
{ name: 'Clyde', id: 'Character_UnbrokenTrash', emoji: 'Clyde' },
{ name: 'Eon', id: 'CID_174_Athena_Commando_F_CarbideWhite', emoji: 'Eon' },
{ name: 'Huntmaster Saber', id: 'Character_MasterKeyOrder', emoji: 'HuntmasterSaber' },
{ name: 'Dark Skully', id: 'CID_850_Athena_Commando_F_SkullBriteCube', emoji: 'DarkSkully' },
{ name: 'Galaxy', id: 'CID_175_Athena_Commando_M_Celestial', emoji: 'Galaxy' },
{ name: 'Ikonik', id: 'CID_313_Athena_Commando_M_KpopFashion', emoji: 'Ikonik' },
{ name: 'Black Knight', id: 'CID_035_Athena_Commando_M_Medieval', emoji: 'BlackKnight' },
{ name: 'Glow', id: 'CID_479_Athena_Commando_F_Davinci', emoji: 'Glow' },
{ name: 'Rogue Spider Knight', id: 'CID_516_Athena_Commando_M_BlackWidowRogue', emoji: 'RogueSpiderKnight' },
{ name: 'Thrilldiver', id: 'Character_SummerUnsuitable', emoji: 'Thrilldiver' },
{ name: 'Freediver', id: 'Character_CoolSuitable', emoji: 'Freediver' },
{ name: 'Neo Versa', id: 'CID_441_Athena_Commando_F_CyberScavengerBlue', emoji: 'NeoVersa' },
{ name: 'Cobalt Snowfoot', id: 'Character_SnowNinjaDark', emoji: 'CobaltSnowfoot' },
{ name: 'Florin', id: 'Character_AsterOrder', emoji: 'Florin' },
{ name: "What's Wrong, Toona Fish?", id: 'CID_A_201_Athena_Commando_M_TeriyakiFishToon', emoji: 'ToonaFish' },
{ name: 'Cameo Needs Loot', id: 'Emoji_S20_NeedLoot', emoji: 'Cameo',
overrideUrl: 'https://fortnite-api.com/images/cosmetics/br/emoji_s20_needloot/smallicon.png' },
];
// ── fetch with timeout so nothing ever hangs ──────────────────────────────────
async function fetchWithTimeout(url, ms = 12000) {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), ms);
try {
return await fetch(url, { signal: ctrl.signal });
} finally {
clearTimeout(timer);
}
}
// ── get icon URL from fortnite-api.com by direct ID ───────────────────────────
async function fetchIconUrl(cosmeticId) {
const url = `https://fortnite-api.com/v2/cosmetics/br/${cosmeticId}`;
const res = await fetchWithTimeout(url);
if (!res.ok) throw new Error(`API returned ${res.status} for ${cosmeticId}`);
const json = await res.json();
if (!json.data?.images?.icon) throw new Error(`No icon in response for ${cosmeticId}`);
return json.data.images.icon;
}
// ── download image and upscale to 256x256 for crisp Discord emoji ─────────────
async function downloadAndProcess(imageUrl) {
const res = await fetchWithTimeout(imageUrl, 15000);
if (!res.ok) throw new Error(`Image download failed: ${res.status}`);
const raw = Buffer.from(await res.arrayBuffer());
// Upscale to 256x256 with Lanczos resampling + sharpening
return await sharp(raw)
.resize(256, 256, { fit: 'cover', position: 'centre', kernel: 'lanczos3' })
.sharpen({ sigma: 0.8, m1: 1.0, m2: 2.0 })
.png({ compressionLevel: 6 })
.toBuffer();
}
// ── create emoji with automatic rate-limit retry ──────────────────────────────
async function createEmojiSafe(guild, buffer, name, reason) {
for (let attempt = 1; attempt <= 6; attempt++) {
try {
return await guild.emojis.create({ attachment: buffer, name, reason });
} catch (err) {
const isRateLimit = err.status === 429 || err.httpStatus === 429 ||
(err.message || '').toLowerCase().includes('rate limit');
if (isRateLimit) {
const wait = ((err.retryAfter || 5) * 1000) + 1000;
console.warn(` ⏳ Rate limited on "${name}", waiting ${wait}ms (attempt ${attempt}/6)...`);
await new Promise(r => setTimeout(r, wait));
} else {
throw err;
}
}
}
throw new Error(`Gave up creating emoji "${name}" after 6 attempts`);
}
// ─────────────────────────────────────────────────────────────────────────────
client.once('ready', () => {
console.log(`✅ Logged in as ${client.user.tag}`);
console.log('Commands: !addicons | !setroleicons | !removeoldemojis');
});
// ── !removeoldemojis ──────────────────────────────────────────────────────────
client.on('messageCreate', async (message) => {
if (message.author.bot || message.content !== '!removeoldemojis') return;
if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator))
return message.reply('Need Administrator permission.');
await message.reply('Removing old skin emojis...');
const names = new Set(roleIconMap.map(e => e.emoji.toLowerCase()));
let removed = 0;
// Fetch fresh emoji list
await message.guild.emojis.fetch();
for (const emoji of message.guild.emojis.cache.values()) {
if (!names.has(emoji.name.toLowerCase())) continue;
try {
await emoji.delete('Replacing with corrected icons');
console.log(` 🗑 Deleted: ${emoji.name}`);
removed++;
await new Promise(r => setTimeout(r, 400));
} catch (err) {
console.error(` ❌ Failed to delete ${emoji.name}: ${err.message}`);
}
}
message.channel.send(`Removed **${removed}** emojis. Now run \`!addicons\`.`);
});
// ── !addicons ─────────────────────────────────────────────────────────────────
client.on('messageCreate', async (message) => {
if (message.author.bot || message.content !== '!addicons') return;
if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator))
return message.reply('Need Administrator permission.');
await message.reply(`Starting — uploading ${roleIconMap.length} skin icons. This takes a few minutes, watch the terminal for progress.`);
// Always fetch a fresh copy of existing emojis
await message.guild.emojis.fetch();
const existingNames = new Set(
message.guild.emojis.cache.map(e => e.name.toLowerCase())
);
let added = 0, skipped = 0, failed = 0;
for (const entry of roleIconMap) {
if (existingNames.has(entry.emoji.toLowerCase())) {
console.log(` ⏭ Already exists: ${entry.emoji}`);
skipped++;
continue;
}
process.stdout.write(` 📥 ${entry.name} — fetching... `);
try {
const iconUrl = entry.overrideUrl || await fetchIconUrl(entry.id);
process.stdout.write(`downloading... `);
const buffer = await downloadAndProcess(iconUrl);
process.stdout.write(`uploading... `);
await createEmojiSafe(message.guild, buffer, entry.emoji, `Skin icon: ${entry.name}`);
console.log(`✅`);
added++;
} catch (err) {
console.log(`❌ ${err.message}`);
failed++;
}
// 2s gap between uploads to stay under Discord rate limits
await new Promise(r => setTimeout(r, 2000));
}
message.channel.send(
`Done! ✅ Added **${added}** emojis. ` +
`⏭ Skipped **${skipped}** (already existed). ` +
`❌ **${failed}** failed.`
);
});
// ── !setroleicons ─────────────────────────────────────────────────────────────
client.on('messageCreate', async (message) => {
if (message.author.bot || message.content !== '!setroleicons') return;
if (!message.member.permissions.has(PermissionsBitField.Flags.Administrator))
return message.reply('Need Administrator permission.');
await message.reply('Applying emojis to roles...');
// Refresh both caches
await message.guild.emojis.fetch();
await message.guild.roles.fetch();
let updated = 0, skipped = 0, failed = 0;
for (const entry of roleIconMap) {
const role = message.guild.roles.cache.find(
r => r.name.toLowerCase() === entry.name.toLowerCase()
);
if (!role) {
console.log(` ⏭ Role not found: ${entry.name}`);
skipped++;
continue;
}
const emoji = message.guild.emojis.cache.find(
e => e.name.toLowerCase() === entry.emoji.toLowerCase()
);
if (!emoji) {
console.log(` ⚠ Emoji not found for: ${entry.name} — run !addicons first`);
skipped++;
continue;
}
try {
await role.setIcon(emoji);
console.log(` ✅ ${entry.name} → :${entry.emoji}:`);
updated++;
} catch (err) {
console.error(` ❌ Failed "${entry.name}": ${err.message}`);
failed++;
}
await new Promise(r => setTimeout(r, 500));
}
message.channel.send(
`Done! ✅ Set icons on **${updated}** roles. ` +
`⏭ Skipped **${skipped}**. ❌ **${failed}** failed.`
);
});
client.login(process.env.TOKEN);