Compare commits

...

19 Commits

Author SHA1 Message Date
68aeb9a467 Remove disallowed intents
Some checks failed
Silvanus Production Dockerization / build (push) Failing after 11s
2026-10-09 22:53:06 -04:00
486ed80d31 Goodbye<3
Some checks failed
Silvanus Production Dockerization / build (push) Failing after 2m13s
2026-10-09 22:43:36 -04:00
f9bb94a4a1 Maybe working 2024-09-22 18:13:15 -04:00
1575d0047c ? 2024-09-22 18:00:52 -04:00
d173338d09 Fix runs on 2024-09-22 17:57:58 -04:00
aedbbec6a2 Updating CI/CD 2024-09-22 17:56:32 -04:00
fa41a5d61e Add badges 2024-09-22 15:54:14 -04:00
ac6eca4827 Merge pull request 'v1.2.9-dev' (#8) from v1.2.9-dev into main
Reviewed-on: #8
2024-04-06 13:16:15 +00:00
88cc4eda3c Remove reminder embed, only send text message
Some checks failed
Silvanus Production Dockerization / build (pull_request) Has been cancelled
2024-04-06 09:01:57 -04:00
7291216dcb Versioning 2024-04-06 09:01:43 -04:00
36f44f4b41 Merge pull request 'v1.2.8 Heartbeat Monitoring' (#6) from v1.2.8 into main
Reviewed-on: #6
2023-09-02 08:37:11 -07:00
825ec725d1 Timer planning
Some checks reported warnings
Silvanus Production Dockerization / build (pull_request) Has been cancelled
2023-09-02 09:42:10 -04:00
fb43d09d13 Fix debug mode detection
Some checks reported warnings
Silvanus Production Dockerization / build (pull_request) Has been cancelled
2023-08-27 20:27:11 -04:00
0463695323 Improved logging
All checks were successful
Silvanus Production Dockerization / build (pull_request) Successful in 12s
2023-08-27 19:34:15 -04:00
314b793042 Versioning
All checks were successful
Silvanus Production Dockerization / build (pull_request) Successful in 53s
2023-08-10 22:18:28 -04:00
eca3207d4e Remove excess console.log 2023-08-10 22:16:23 -04:00
e2658c41e5 Add heartbeat url for uptime monitoring 2023-08-10 22:13:08 -04:00
dd313faa60 Merge pull request 'Change dispatch to pull req' (#3) from testing into main
Reviewed-on: #3
2023-08-04 09:52:53 -07:00
4dda58f48e Merge pull request 'Slight verbiage change' (#2) from testing into main
Reviewed-on: #2
2023-07-31 12:58:33 -07:00
10 changed files with 174 additions and 58 deletions

View File

@ -0,0 +1,47 @@
name: Silvanus Production Dockerization
on:
push:
tags:
- '*'
env:
DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
DHUB_PWORD: ${{ secrets.DHUB_PWORD }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Pull latest from Git
run: |
pwd
whoami
mkdir -p /var/lib/act_runner/
cd /var/lib/act_runner/
if [ ! -d "silvanus" ]; then
git clone https://git.vfsh.dev/voidf1sh/silvanus
cd silvanus
else
cd silvanus
git fetch --tags --force origin
fi
git checkout ${{ gitea.ref}}
- name: Build the Docker image
run: |
cd /var/lib/act_runner/silvanus
docker build . --file Dockerfile --tag v0idf1sh/silvanus
- name: Log into Docker Hub
run: docker login -u $DHUB_UNAME -p $DHUB_PWORD
- name: Push image to Docker Hub
run: |
cd /var/lib/act_runner/silvanus
docker push v0idf1sh/silvanus
- name: Restart the container
run: |
cd /srv/docker/silvanus
docker-compose down
docker-compose up -d

View File

@ -1,35 +0,0 @@
name: Silvanus Production Dockerization
on:
pull_request:
branches:
- main
env:
DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
DHUB_PWORD: ${{ secrets.DHUB_PWORD }}
jobs:
build:
runs-on: self-hosted
steps:
- name: Pull latest from Git
run: |
pwd
whoami
cd /root/silvanus
git pull
git checkout $GITHUB_HEAD_REF
- name: Build the Docker image
run: |
cd /root/silvanus
docker build . --file Dockerfile --tag v0idf1sh/silvanus
- name: Log into Docker Hub
run: docker login -u $DHUB_UNAME -p $DHUB_PWORD
- name: Push image to Docker Hub
run: |
cd /root/silvanus
docker push v0idf1sh/silvanus

View File

@ -5,5 +5,4 @@ WORKDIR /usr/src/app
COPY package.json ./
RUN npm install
COPY . .
# CMD ["/bin/sh", "-c", "node main.js 2> /logs/silvanus.err 1> /logs/silvanus.log"]
CMD ["/bin/sh", "-c", "node main.js 2> /logs/error/$(date +%Y-%m-%d_%H-%M-%S).txt 1> /logs/status/$(date +%Y-%m-%d_%H-%M-%S).txt"]
CMD ["/bin/sh", "-c", "node main.js 2>&1 > /logs/$(date +%Y-%m-%d_%H-%M-%S).txt"]

View File

@ -1,3 +1,5 @@
![Current Uptime Status](https://status.vfsh.dev/api/badge/1/status)
![Uptime Percentage](https://status.vfsh.dev/api/badge/1/uptime)
# Silvanus
Silvanus is the ultimate Grow A Tree companion bot! Quickly compare your server's tree to others on the leaderboard with automatic calculation of tree height differences, active growth detection, watering time calculations, and more!

24
TODO.md
View File

@ -1,10 +1,3 @@
## In Progress
☑ Switch `/setup` to ask for the tree and leaderboard channels
* Switch `/compare` to check for newer trees and leaderboards when run **and** on every refresh
## Future Ideas
* Go through and comment the code
## Variable Structures
guildInfo = {
@ -21,7 +14,18 @@ guildInfo = {
reminderOptIn: 0,
}
## Expected Behaviors
## New Table Planning
Table: `silvanus`.timers
* Run `/compare` before `/setup`: `/compare` will search the current channel for tree and leaderboard messages, then create a comparison embed. If it can't find `/tree` or `/top trees` messages, it'll return an error saying as much.
* Run `/compare` after `/setup`: ``/compare` will search the current channel for tree and leaderboard messages, then create a comparison embed. If it can't find `/tree` or `/top trees` messages, it'll just use old data silently (odds are `/compare` is being run from another channel, that's fine)
id | INT | NOT NULL | AUTO INCREMENT | PRIMARY KEY
status | VARCHAR(10) | NOT NULL | DEFAULT "WAITING"
dc_timecode | INT | NOT NULL | Discord timecode
guild_id | INT UNSIGNED | NOT NULL | Discord guild ID for referencing `silvanus`.guildInfo
Table: `silvanus`.auto_role_status
id | INT | NOT NULL | AUTO INCREMENT | PRIMARY KEY
user_id | INT UNSIGNED | NOT NULL
guild_id | INT UNSIGNED | NOT NULL
status | VARCHAR(10) | NOT NULL | DEFAULT "REMOVED" | OPTION: {"REMOVED", "ADDED"}

86
main.js
View File

@ -5,6 +5,8 @@ const dotenv = require('dotenv');
dotenv.config();
const token = process.env.TOKEN;
const statusChannelId = process.env.STATUSCHANNELID;
const heartbeatUrl = process.env.HEARTBEAT_URL;
const sendHeartbeat = typeof heartbeatUrl === 'string';
// Discord.JS
const { Client, GatewayIntentBits, Partials, ActivityType } = require('discord.js');
@ -13,11 +15,9 @@ const client = new Client({
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.MessageContent
],
partials: [
Partials.Channel,
Partials.Message
],
});
@ -26,10 +26,80 @@ const fn = require('./modules/functions.js');
const strings = require('./data/strings.json');
const dbfn = require('./modules/dbfn.js');
const { GuildInfo } = require('./modules/CustomClasses.js');
const isDev = process.env.DEBUG;
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);
@ -43,6 +113,16 @@ client.once('ready', async () => {
if (isDev == 'false') {
statusChannel.send(`${new Date().toISOString()} -- \nStartup Sequence Complete <@481933290912350209>`);
}
// Heartbeat Timer
if (sendHeartbeat) {
setInterval(() => {
fn.sendHeartbeat(heartbeatUrl);
}, 30000);
if (isDev) console.log("Heartbeat interval set.");
} else {
if (isDev) console.log("No heartbeat URL set, will not send heartbeats for uptime monitoring.");
}
});
// slash-commands

View File

@ -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);

View File

@ -20,6 +20,7 @@ const slashCommandFiles = fs.readdirSync('./slash-commands/').filter(file => fil
const dotCommandFiles = fs.readdirSync('./dot-commands/').filter(file => file.endsWith('.js'));
const dbfn = require('./dbfn.js');
const { finished } = require('stream');
const https = require('https');
const functions = {
// Functions for managing and creating Collections
@ -1045,9 +1046,9 @@ const functions = {
},
async sendWaterReminder(guildInfo, message, channelId, guild) {
const reminderChannel = await guild.channels.fetch(channelId);
const reminderEmbed = functions.builders.waterReminderEmbed(message, guildInfo);
// const reminderEmbed = functions.builders.waterReminderEmbed(message, guildInfo);
if (isDev) console.log(`WR: ${guild.name}: ${guildInfo.treeName}`);
await reminderChannel.send(reminderEmbed).then(async m => {
await reminderChannel.send(message).then(async m => {
if (!m.deletable) return;
await this.sleep(500).then(async () => {
await m.delete().catch(e => console.error(e));
@ -1058,9 +1059,9 @@ const functions = {
},
async sendFruitReminder(guildInfo, message, channelId, guild) {
const reminderChannel = await guild.channels.fetch(channelId);
const reminderEmbed = functions.builders.fruitReminderEmbed(message, guildInfo);
// const reminderEmbed = functions.builders.fruitReminderEmbed(message, guildInfo);
if (isDev) console.log(`FR: ${guild.name}: ${guildInfo.treeName}`);
await reminderChannel.send(reminderEmbed).then(async m => {
await reminderChannel.send(message).then(async m => {
if (!m.deletable) return;
await this.sleep(500).then(async () => {
await m.delete().catch(e => console.error(e));
@ -1142,6 +1143,20 @@ const functions = {
}
const errorId = digits.join("");
return errorId;
},
async sendHeartbeat(url) {
https.get(url, async (response) => {
if (isDev) console.log("Sent Heartbeat Request: " + url);
let data = '';
response.on('data', (chunk) => data += chunk);
response.on('end', () => {
if (isDev) console.log("Received Heartbeat Response: " + data);
parsedData = JSON.parse(data);
if ( !(parsedData.ok) ) console.error("Heartbeat failed");
});
}).on("error", (error) => console.error(error));
}
};

View File

@ -1,6 +1,6 @@
{
"name": "silvanus",
"version": "1.2.7",
"version": "1.2.9",
"description": "Grow A Tree Companion Bot",
"main": "main.js",
"scripts": {