Compare commits

..

No commits in common. "main" and "testing" have entirely different histories.

10 changed files with 58 additions and 174 deletions

View File

@ -1,47 +0,0 @@
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

35
.github/workflows/docker-image-prod.yml vendored Executable file
View File

@ -0,0 +1,35 @@
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,4 +5,5 @@ WORKDIR /usr/src/app
COPY package.json ./
RUN npm install
COPY . .
CMD ["/bin/sh", "-c", "node main.js 2>&1 > /logs/$(date +%Y-%m-%d_%H-%M-%S).txt"]
# 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"]

View File

@ -1,5 +1,3 @@
![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,3 +1,10 @@
## 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 = {
@ -14,18 +21,7 @@ guildInfo = {
reminderOptIn: 0,
}
## New Table Planning
Table: `silvanus`.timers
## Expected Behaviors
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"}
* 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)

86
main.js
View File

@ -5,8 +5,6 @@ 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');
@ -15,9 +13,11 @@ const client = new Client({
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.MessageContent
],
partials: [
Partials.Channel,
Partials.Message
],
});
@ -26,80 +26,10 @@ 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 === "true";
const isDev = process.env.DEBUG;
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);
@ -113,16 +43,6 @@ 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,17 +79,13 @@ 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,7 +20,6 @@ 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
@ -1046,9 +1045,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(message).then(async m => {
await reminderChannel.send(reminderEmbed).then(async m => {
if (!m.deletable) return;
await this.sleep(500).then(async () => {
await m.delete().catch(e => console.error(e));
@ -1059,9 +1058,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(message).then(async m => {
await reminderChannel.send(reminderEmbed).then(async m => {
if (!m.deletable) return;
await this.sleep(500).then(async () => {
await m.delete().catch(e => console.error(e));
@ -1143,20 +1142,6 @@ 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.9",
"version": "1.2.7",
"description": "Grow A Tree Companion Bot",
"main": "main.js",
"scripts": {