From 486ed80d31de649a42bc4dd74b3b60788e41f3e0 Mon Sep 17 00:00:00 2001 From: Skylar Grant Date: Fri, 9 Oct 2026 22:43:36 -0400 Subject: [PATCH] Goodbye<3 --- .gitea/workflows/docker-image-prod.yml | 10 ++-- main.js | 70 ++++++++++++++++++++++++++ modules/dbfn.js | 10 ++-- 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/docker-image-prod.yml b/.gitea/workflows/docker-image-prod.yml index e16a9b1..c63270f 100644 --- a/.gitea/workflows/docker-image-prod.yml +++ b/.gitea/workflows/docker-image-prod.yml @@ -1,10 +1,10 @@ name: Silvanus Production Dockerization on: - pull_request: - branches: - - main - + push: + tags: + - '*' + env: DHUB_UNAME: ${{ secrets.DHUB_UNAME }} DHUB_PWORD: ${{ secrets.DHUB_PWORD }} @@ -27,7 +27,7 @@ jobs: cd silvanus else cd silvanus - git pull + git fetch --tags --force origin fi git checkout ${{ gitea.ref}} - name: Build the Docker image diff --git a/main.js b/main.js index 6727767..ee9a3ef 100755 --- a/main.js +++ b/main.js @@ -31,7 +31,77 @@ const { GuildInfo } = require('./modules/CustomClasses.js'); const isDev = process.env.DEBUG === "true"; let statusChannel; +// Silvanus is being shut down: on launch, leave every guild and exit instead of starting normally. +const shuttingDown = true; + +const farewellMessage = [ + 'Silvanus is being permanently shut down. Many of its core features are now built into Grow A Tree, and Discord has restricted bots\' access to message content, which Silvanus needs in order to work.', + '', + 'It has been so much fun over the last few years. Thank you all for your support, and much love to you all!', + '', + 'So long and thanks for all the apples 🍎', + '-Silvanus and @vfsh' +].join('\n'); + +// Post the farewell in the guild's reminder channel, falling back to its tree channel. Returns the channel used, if any. +async function sendFarewell(guild) { + const guildInfo = client.guildInfos.get(guild.id); + if (!guildInfo) return; + const channelIds = [...new Set([guildInfo.reminderChannelId, guildInfo.treeChannelId].filter(id => id))]; + for (const channelId of channelIds) { + try { + const channel = await client.channels.fetch(channelId); + if (channel.guildId !== guild.id) continue; + await channel.send({ content: farewellMessage, allowedMentions: { parse: [] } }); + return channel; + } catch (e) { + console.error(`Failed to send farewell to channel ${channelId} in ${guild.name} (${guild.id}): ${e}`); + } + } +} + +async function leaveAllGuilds() { + // Skip the normal guildDelete handler, it depends on state that isn't built in shutdown mode + client.removeAllListeners('guildDelete'); + statusChannel = await client.channels.fetch(statusChannelId).catch(e => console.error('Unable to fetch status channel', e)); + const logStatus = async msg => { + console.log(msg); + if (statusChannel) await statusChannel.send(msg).catch(e => console.error(e)); + }; + // Load each guild's configured channels from the database so we know where to say goodbye + // If the database can't be read, abort without leaving anything so the shutdown can be retried + try { + await fn.collectionBuilders.guildInfos(client); + } catch (e) { + await logStatus(`Shutdown aborted, unable to load guild infos from the database: ${e}`); + client.destroy(); + process.exit(1); + } + // Don't leave the server the status channel lives in until the end, so messages can still be sent + const guilds = [...client.guilds.cache.values()] + .sort((a, b) => (a.id === statusChannel?.guildId) - (b.id === statusChannel?.guildId)); + await logStatus(`Shutting down: leaving ${guilds.length} guild(s)...`); + let farewellCount = 0; + for (const guild of guilds) { + // Once the bot leaves the dev server it can't post there anymore, so say goodbye first + if (guild.id === statusChannel?.guildId) await logStatus(`Leaving my final guild: ${guild.name} (${guild.id}). Goodbye, and thanks for all the trees. 🌳`); + const farewellChannel = await sendFarewell(guild); + if (farewellChannel) farewellCount++; + const farewellNote = farewellChannel ? `farewell sent in #${farewellChannel.name}` : 'no farewell sent'; + await guild.leave() + .then(() => { + const msg = `I've left a guild: ${guild.name} (${guild.id}) -- ${farewellNote}`; + return guild.id === statusChannel?.guildId ? console.log(msg) : logStatus(msg); + }) + .catch(e => logStatus(`Failed to leave guild: ${guild.name} (${guild.id}) -- ${e}`)); + } + console.log(`Finished leaving guilds, sent ${farewellCount} farewell(s). Goodbye!`); + client.destroy(); + process.exit(0); +} + client.once('ready', async () => { + if (shuttingDown) return leaveAllGuilds(); fn.collectionBuilders.slashCommands(client); fn.collectionBuilders.dotCommands(client); fn.collectionBuilders.setvalidCommands(client); diff --git a/modules/dbfn.js b/modules/dbfn.js index 911f2e2..b04f300 100755 --- a/modules/dbfn.js +++ b/modules/dbfn.js @@ -79,13 +79,17 @@ module.exports = { database: process.env.DBNAME, port: process.env.DBPORT }); - db.connect((err) => { - if (err) throw `Error connecting to the database: ${err.message}`; - }); // Get a server's tree information from the database const query = 'SELECT * FROM guild_info'; // TODO run this query and return a promise then structure the output into a GuildInfo object. resolve with { "status": , "data": guildInfo } return new Promise((resolve, reject) => { + // Reject instead of throwing so callers can handle a failed connection + db.connect((err) => { + if (err) { + reject(`Error connecting to the database: ${err.message}`); + db.destroy(); + } + }); db.query(query, (err, res) => { if (err) { console.error(err);