Última actividad 1 month ago

nuno revisó este gist 1 month ago. Ir a la revisión

1 file changed, 171 insertions

upload-emojis.ts(archivo creado)

@@ -0,0 +1,171 @@
1 + /**
2 + * Bulk emoji upload script
3 + * Usage: npx ts-node scripts/upload-emojis.ts [emoji_dir]
4 + *
5 + * Distributes emojis across a pool of donor servers (round-robin by available capacity).
6 + * Each emoji is unique across all servers — no duplicates.
7 + * Automatically updates messages/emojis.json with the uploaded emoji IDs.
8 + */
9 +
10 + import { REST, Routes } from "discord.js";
11 + import fs from "fs";
12 + import path from "path";
13 +
14 + // Load .env manually since we're outside the bot
15 + const envPath = path.join(__dirname, "../.env");
16 + if (fs.existsSync(envPath)) {
17 + for (const line of fs.readFileSync(envPath, "utf8").split("\n")) {
18 + const [key, ...rest] = line.split("=");
19 + if (key && rest.length) process.env[key.trim()] = rest.join("=").trim();
20 + }
21 + }
22 +
23 + const TOKEN = process.env.DISCORD_TOKEN!;
24 +
25 + // Primary server (your main guild, not used for emoji storage)
26 + const GUILD_ID = process.env.GUILD_ID!;
27 +
28 + // Donor servers — comma-separated list of guild IDs in .env
29 + // e.g. EMOJI_DONOR_GUILDS=111111111111,222222222222,333333333333
30 + const DONOR_GUILD_IDS: string[] = (process.env.EMOJI_DONOR_GUILDS ?? "")
31 + .split(",")
32 + .map((id) => id.trim())
33 + .filter(Boolean);
34 +
35 + if (!TOKEN || !GUILD_ID) {
36 + console.error("❌ DISCORD_TOKEN and GUILD_ID must be set in .env");
37 + process.exit(1);
38 + }
39 +
40 + if (DONOR_GUILD_IDS.length === 0) {
41 + console.error("❌ EMOJI_DONOR_GUILDS must be set in .env (comma-separated guild IDs)");
42 + process.exit(1);
43 + }
44 +
45 + const emojiDir = process.argv[2] ?? path.join(__dirname, "../emoji-uploads");
46 + const emojisPath = path.join(__dirname, "../messages/emojis.json");
47 +
48 + if (!fs.existsSync(emojiDir)) {
49 + console.error(`❌ Emoji directory not found: ${emojiDir}`);
50 + process.exit(1);
51 + }
52 +
53 + const rest = new REST({ version: "10" }).setToken(TOKEN);
54 +
55 + // Discord's base emoji limit per guild (static + animated counted separately,
56 + // but we treat total slots conservatively as 50 for safety unless you know your tiers)
57 + const EMOJI_LIMIT_PER_GUILD = 50;
58 +
59 + interface GuildEmojiSlot {
60 + guildId: string;
61 + existing: Map<string, string>; // name → id
62 + capacity: number; // remaining slots
63 + }
64 +
65 + async function fetchGuildSlots(guildIds: string[]): Promise<GuildEmojiSlot[]> {
66 + const slots: GuildEmojiSlot[] = [];
67 +
68 + for (const guildId of guildIds) {
69 + const existing = await rest.get(Routes.guildEmojis(guildId)) as any[];
70 + const existingMap = new Map(existing.map((e: any) => [e.name, e.id]));
71 + const capacity = EMOJI_LIMIT_PER_GUILD - existing.length;
72 +
73 + console.log(`🏠 Guild ${guildId}: ${existing.length} emojis, ${capacity} slots free`);
74 + slots.push({ guildId, existing: existingMap, capacity });
75 + }
76 +
77 + return slots;
78 + }
79 +
80 + async function uploadEmojis(): Promise<void> {
81 + const files = fs.readdirSync(emojiDir).filter((f) =>
82 + [".png", ".jpg", ".gif", ".webp"].includes(path.extname(f).toLowerCase())
83 + );
84 +
85 + if (files.length === 0) {
86 + console.error("❌ No image files found in the emoji directory.");
87 + process.exit(1);
88 + }
89 +
90 + // Load existing emojis.json
91 + let emojiMap: Record<string, string> = {};
92 + try {
93 + emojiMap = JSON.parse(fs.readFileSync(emojisPath, "utf8"));
94 + } catch {
95 + console.warn("⚠️ Could not load emojis.json — will create fresh mapping.");
96 + }
97 +
98 + console.log(`\n📁 Found ${files.length} file(s) in ${emojiDir}`);
99 + console.log(`🔍 Scanning ${DONOR_GUILD_IDS.length} donor server(s)...\n`);
100 +
101 + const guildSlots = await fetchGuildSlots(DONOR_GUILD_IDS);
102 +
103 + // Build a global map of all already-uploaded emojis across all donor guilds.
104 + // This is the deduplication layer — if an emoji exists in any guild, skip it.
105 + const globalExisting = new Map<string, string>(); // name → formatted emoji string
106 + for (const slot of guildSlots) {
107 + for (const [name, id] of slot.existing) {
108 + globalExisting.set(name, `<:${name}:${id}>`);
109 + }
110 + }
111 +
112 + console.log(`\n📊 ${globalExisting.size} emoji(s) already exist across all donor servers\n`);
113 +
114 + let uploaded = 0;
115 + let skipped = 0;
116 + let failed = 0;
117 +
118 + // Pick the next guild with available capacity (round-robin style)
119 + function nextAvailableSlot(): GuildEmojiSlot | null {
120 + return guildSlots.find((s) => s.capacity > 0) ?? null;
121 + }
122 +
123 + for (const file of files) {
124 + const emojiName = path.basename(file, path.extname(file));
125 + const filePath = path.join(emojiDir, file);
126 + const ext = path.extname(file).toLowerCase();
127 + const mimeType = ext === ".gif" ? "image/gif" : ext === ".webp" ? "image/webp" : "image/png";
128 +
129 + // Already exists somewhere in the pool — just ensure it's in the map
130 + if (globalExisting.has(emojiName)) {
131 + emojiMap[emojiName] = globalExisting.get(emojiName)!;
132 + console.log(`⏭️ Already exists: ${emojiName} → ${emojiMap[emojiName]}`);
133 + skipped++;
134 + continue;
135 + }
136 +
137 + const slot = nextAvailableSlot();
138 + if (!slot) {
139 + console.error(`❌ All donor servers are full! Could not upload: ${emojiName}`);
140 + console.error(` Add more donor servers to EMOJI_DONOR_GUILDS in .env`);
141 + failed++;
142 + continue;
143 + }
144 +
145 + try {
146 + const base64 = `data:${mimeType};base64,${fs.readFileSync(filePath).toString("base64")}`;
147 + const result = await rest.post(Routes.guildEmojis(slot.guildId), {
148 + body: { name: emojiName, image: base64 },
149 + }) as any;
150 +
151 + const formatted = `<:${emojiName}:${result.id}>`;
152 + emojiMap[emojiName] = formatted;
153 + slot.capacity--;
154 +
155 + console.log(`✅ Uploaded: ${emojiName} → ${formatted} (guild: ${slot.guildId})`);
156 + uploaded++;
157 +
158 + await new Promise((r) => setTimeout(r, 600));
159 + } catch (err: any) {
160 + console.error(`❌ Failed: ${emojiName} — ${err.message}`);
161 + failed++;
162 + }
163 + }
164 +
165 + fs.writeFileSync(emojisPath, JSON.stringify(emojiMap, null, 2));
166 +
167 + console.log(`\n📊 ${uploaded} uploaded · ${skipped} skipped · ${failed} failed`);
168 + console.log(`💾 messages/emojis.json updated`);
169 + }
170 +
171 + uploadEmojis().catch(console.error);
Siguiente Anterior