Compare commits
No commits in common. "main" and "testing" have entirely different histories.
@ -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
|
||||
0
.gitea/workflows/docker-image-dev.yml → .github/workflows/docker-image-dev.yml
vendored
Normal file → Executable file
0
.gitea/workflows/docker-image-dev.yml → .github/workflows/docker-image-dev.yml
vendored
Normal file → Executable file
35
.github/workflows/docker-image-prod.yml
vendored
Executable file
35
.github/workflows/docker-image-prod.yml
vendored
Executable 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
|
||||
@ -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"]
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||

|
||||

|
||||
# 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
24
TODO.md
@ -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
86
main.js
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "silvanus",
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.7",
|
||||
"description": "Grow A Tree Companion Bot",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user