From 5b24cf324f53a512a7a684a184d4f369b6ec9ad7 Mon Sep 17 00:00:00 2001 From: Kira Date: Wed, 11 Mar 2026 23:27:36 -0400 Subject: [PATCH] actually get profile picture rotation discord.js was outdated and it was making the client token reset every time i set a pfp, so this was taking forever to figure out because i dont understand packages --- .dockerignore | 1 + .gitignore | 3 +- config.json.default | 2 + index.js | 3 + lib/avatarRotation.js | 96 +++++++++++++++++++++++++++++++ package-lock.json | 127 ++++++++++++++++++++++-------------------- package.json | 2 +- 7 files changed, 172 insertions(+), 62 deletions(-) create mode 100644 lib/avatarRotation.js diff --git a/.dockerignore b/.dockerignore index bdcae77..ae787d1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ docker-compose.yml node_modules .git +.gitea state.json config.json avatars diff --git a/.gitignore b/.gitignore index 68fb955..261d7b2 100644 --- a/.gitignore +++ b/.gitignore @@ -141,4 +141,5 @@ dist *.code-workspace config.json -state.json \ No newline at end of file +state.json +avatars/ \ No newline at end of file diff --git a/config.json.default b/config.json.default index 85cef1d..8952784 100644 --- a/config.json.default +++ b/config.json.default @@ -1,6 +1,8 @@ { "token": "bot token go here", "status": "looking for x.com links", + "avatarDir": "./avatars", + "avatarInterval": "24h", "parentsAndOrGuardians": [ "230659159450845195", "297983197990354944" diff --git a/index.js b/index.js index b08fae2..5ba4105 100644 --- a/index.js +++ b/index.js @@ -27,6 +27,8 @@ const client = new Client({ ] }); +const avatarRotation = require('./lib/avatarRotation'); + // build the button below the message that swaps the URL const { ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); const swapRow = new ActionRowBuilder() @@ -78,6 +80,7 @@ client.on(Events.InteractionCreate, async interaction => { client.once(Events.ClientReady, readyClient => { console.log(`Discord: Connected as ${readyClient.user.tag}`); client.user.setActivity(config.status, { type: ActivityType.Custom }); + avatarRotation.start(client, config); }); client.login(config.token); diff --git a/lib/avatarRotation.js b/lib/avatarRotation.js new file mode 100644 index 0000000..1ad0473 --- /dev/null +++ b/lib/avatarRotation.js @@ -0,0 +1,96 @@ +// this entire file is Opus 4.6 sorry + +const fs = require('fs'); +const path = require('path'); + +const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp']; +const DEFAULT_INTERVAL = '24h'; +const DEFAULT_DIR = './avatars'; + +/** Parse an interval string like "24h" or "30m" into milliseconds */ +function parseInterval(str) { + const match = String(str).match(/^(\d+)\s*(h|m)$/i); + if (!match) { + console.warn(`Avatars: Invalid interval "${str}", falling back to ${DEFAULT_INTERVAL}`); + return parseInterval(DEFAULT_INTERVAL); + } + const value = parseInt(match[1], 10); + const unit = match[2].toLowerCase(); + return unit === 'h' ? value * 60 * 60 * 1000 : value * 60 * 1000; +} + +/** Scan the directory and return an array of image file paths */ +function scanAvatars(dir) { + try { + return fs.readdirSync(dir) + .filter(f => IMAGE_EXTENSIONS.includes(path.extname(f).toLowerCase())) + .map(f => path.join(dir, f)); + } catch (err) { + console.warn(`Avatars: Could not read directory "${dir}": ${err.message}`); + return []; + } +} + +/** Pick a random avatar and set it on the client */ +async function setRandomAvatar(client, avatars) { + if (!avatars.length) { + console.warn('Avatars: No images found, skipping avatar change'); + return; + } + const pick = avatars[Math.floor(Math.random() * avatars.length)]; + try { + await client.user.setAvatar(pick); + console.log(`Avatars: Changed to ${path.basename(pick)}`); + } catch (err) { + console.error(`Avatars: Failed to set avatar: ${err.message}`); + } +} + +/** + * Start the avatar rotation system. + * @param {import('discord.js').Client} client - The Discord client (must be ready) + * @param {object} config - Bot config object + */ +function start(client, config = {}) { + const avatarDir = path.resolve(config.avatarDir || DEFAULT_DIR); + const intervalMs = parseInterval(config.avatarInterval || DEFAULT_INTERVAL); + + // ensure the directory exists + if (!fs.existsSync(avatarDir)) { + console.warn(`Avatars: Directory "${avatarDir}" does not exist, avatar rotation disabled`); + return; + } + + let avatars = scanAvatars(avatarDir); + console.log(`Avatars: Found ${avatars.length} image(s) in ${avatarDir}`); + console.log(`Avatars: Rotation interval set to ${config.avatarInterval || DEFAULT_INTERVAL}`); + + // set one immediately on boot + setRandomAvatar(client, avatars); + + // rotate on the configured interval + setInterval(() => { + avatars = scanAvatars(avatarDir); // re-scan in case watcher missed something + setRandomAvatar(client, avatars); + }, intervalMs); + + // live-watch the folder for changes + let debounceTimer = null; + try { + fs.watch(avatarDir, (eventType, filename) => { + if (debounceTimer) clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + const updated = scanAvatars(avatarDir); + if (updated.length !== avatars.length) { + console.log(`Avatars: Folder changed, now ${updated.length} image(s)`); + } + avatars = updated; + }, 500); // 500ms debounce + }); + console.log(`Avatars: Watching "${avatarDir}" for changes`); + } catch (err) { + console.warn(`Avatars: Could not watch directory: ${err.message}`); + } +} + +module.exports = { start }; diff --git a/package-lock.json b/package-lock.json index 0d803e2..474b03e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "discord.js": "^14.18.0", + "discord.js": "^14.25.1", "fs": "^0.0.1-security" }, "devDependencies": { @@ -18,15 +18,15 @@ } }, "node_modules/@discordjs/builders": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.10.1.tgz", - "integrity": "sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz", + "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==", "license": "Apache-2.0", "dependencies": { - "@discordjs/formatters": "^0.6.0", - "@discordjs/util": "^1.1.1", + "@discordjs/formatters": "^0.6.2", + "@discordjs/util": "^1.2.0", "@sapphire/shapeshift": "^4.0.0", - "discord-api-types": "^0.37.119", + "discord-api-types": "^0.38.33", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" @@ -48,12 +48,12 @@ } }, "node_modules/@discordjs/formatters": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.0.tgz", - "integrity": "sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", + "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", "license": "Apache-2.0", "dependencies": { - "discord-api-types": "^0.37.114" + "discord-api-types": "^0.38.33" }, "engines": { "node": ">=16.11.0" @@ -63,9 +63,9 @@ } }, "node_modules/@discordjs/rest": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.3.tgz", - "integrity": "sha512-+SO4RKvWsM+y8uFHgYQrcTl/3+cY02uQOH7/7bKbVZsTfrfpoE62o5p+mmV+s7FVhTX82/kQUGGbu4YlV60RtA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz", + "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==", "license": "Apache-2.0", "dependencies": { "@discordjs/collection": "^2.1.1", @@ -73,10 +73,10 @@ "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", - "discord-api-types": "^0.37.119", + "discord-api-types": "^0.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", - "undici": "6.21.1" + "undici": "6.21.3" }, "engines": { "node": ">=18" @@ -98,10 +98,13 @@ } }, "node_modules/@discordjs/util": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", - "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", + "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.33" + }, "engines": { "node": ">=18" }, @@ -110,18 +113,18 @@ } }, "node_modules/@discordjs/ws": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.1.tgz", - "integrity": "sha512-PBvenhZG56a6tMWF/f4P6f4GxZKJTBG95n7aiGSPTnodmz4N5g60t79rSIAq7ywMbv8A4jFtexMruH+oe51aQQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", + "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", "license": "Apache-2.0", "dependencies": { "@discordjs/collection": "^2.1.0", - "@discordjs/rest": "^2.4.3", + "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", - "discord-api-types": "^0.37.119", + "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" }, @@ -409,12 +412,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.14.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", - "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.4.0.tgz", + "integrity": "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw==", "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/ws": { @@ -427,9 +430,9 @@ } }, "node_modules/@vladfrangu/async_event_emitter": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", - "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==", + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", + "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", "license": "MIT", "engines": { "node": ">=v14.0.0", @@ -612,29 +615,33 @@ "license": "MIT" }, "node_modules/discord-api-types": { - "version": "0.37.119", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.119.tgz", - "integrity": "sha512-WasbGFXEB+VQWXlo6IpW3oUv73Yuau1Ig4AZF/m13tXcTKnMpc/mHjpztIlz4+BM9FG9BHQkEXiPto3bKduQUg==", - "license": "MIT" + "version": "0.38.42", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.42.tgz", + "integrity": "sha512-qs1kya7S84r5RR8m9kgttywGrmmoHaRifU1askAoi+wkoSefLpZP6aGXusjNw5b0jD3zOg3LTwUa3Tf2iHIceQ==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] }, "node_modules/discord.js": { - "version": "14.18.0", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.18.0.tgz", - "integrity": "sha512-SvU5kVUvwunQhN2/+0t55QW/1EHfB1lp0TtLZUSXVHDmyHTrdOj5LRKdR0zLcybaA15F+NtdWuWmGOX9lE+CAw==", + "version": "14.25.1", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz", + "integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==", "license": "Apache-2.0", "dependencies": { - "@discordjs/builders": "^1.10.1", + "@discordjs/builders": "^1.13.0", "@discordjs/collection": "1.5.3", - "@discordjs/formatters": "^0.6.0", - "@discordjs/rest": "^2.4.3", - "@discordjs/util": "^1.1.1", - "@discordjs/ws": "^1.2.1", + "@discordjs/formatters": "^0.6.2", + "@discordjs/rest": "^2.6.0", + "@discordjs/util": "^1.2.0", + "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", - "discord-api-types": "^0.37.119", + "discord-api-types": "^0.38.33", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", + "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", - "undici": "6.21.1" + "undici": "6.21.3" }, "engines": { "node": ">=18" @@ -1066,9 +1073,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash.merge": { @@ -1085,9 +1092,9 @@ "license": "MIT" }, "node_modules/magic-bytes.js": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", - "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz", + "integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==", "license": "MIT" }, "node_modules/minimatch": { @@ -1305,18 +1312,18 @@ } }, "node_modules/undici": { - "version": "6.21.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", - "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", "license": "MIT", "engines": { "node": ">=18.17" } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, "node_modules/uri-js": { @@ -1356,9 +1363,9 @@ } }, "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index f21a2d3..b988800 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "homepage": "https://github.com/CesiumCs/tornbot#readme", "description": "", "dependencies": { - "discord.js": "^14.18.0", + "discord.js": "^14.25.1", "fs": "^0.0.1-security" }, "devDependencies": {