Migrate to database for long term storage
This commit is contained in:
parent
0539c94e79
commit
1cde2f6915
45
README.md
45
README.md
@ -15,30 +15,33 @@ Add the bot to your server and make sure it has the proper permissions (`Send Me
|
|||||||
|
|
||||||
## Database Structure
|
## Database Structure
|
||||||
|
|
||||||
### Table: guildinfo
|
### Table: guild_info
|
||||||
```
|
```
|
||||||
+-----------------+-------------+------+-----+---------+----------------+
|
+------------------------+-------------+------+-----+---------+----------------+
|
||||||
| Field | Type | Null | Key | Default | Extra |
|
| Field | Type | Null | Key | Default | Extra |
|
||||||
+-----------------+-------------+------+-----+---------+----------------+
|
+------------------------+-------------+------+-----+---------+----------------+
|
||||||
| id | int(10) | NO | PRI | NULL | auto_increment |
|
| guild_id | varchar(50) | NO | PRI | NULL | auto_increment |
|
||||||
| guild_id | varchar(50) | NO | | NULL | |
|
| tree_message_id | varchar(50) | NO | | | |
|
||||||
| tree_message_id | varchar(50) | NO | | NULL | |
|
| tree_channel_id | varchar(50) | NO | | | |
|
||||||
| tree_channel_id | varchar(50) | NO | | NULL | |
|
| leaderboard_message_id | varchar(50) | NO | | | |
|
||||||
| rank_message_id | varchar(50) | NO | | NULL | |
|
| leaderboard_channel_id | varchar(50) | NO | | | |
|
||||||
| rank_channel_id | varchar(50) | NO | | NULL | |
|
| tree_height | varchar(10) | NO | | 0 | |
|
||||||
| tree_height | varchar(10) | NO | | NULL | |
|
+------------------------+-------------+------+-----+---------+----------------+
|
||||||
+-----------------+-------------+------+-----+---------+----------------+
|
|
||||||
```
|
```
|
||||||
### Table: treeinfo
|
### Table: leaderboard_info
|
||||||
```
|
```
|
||||||
+-----------+--------------+------+-----+---------+----------------+
|
+-------------+--------------+------+-----+---------+----------------+
|
||||||
| Field | Type | Null | Key | Default | Extra |
|
| Field | Type | Null | Key | Default | Extra |
|
||||||
+-----------+--------------+------+-----+---------+----------------+
|
+-------------+--------------+------+-----+---------+----------------+
|
||||||
| id | int(10) | NO | PRI | NULL | auto_increment |
|
| id | int(10) | NO | PRI | NULL | auto_increment |
|
||||||
| treename | varchar(100) | NO | | NULL | |
|
| guild_id | varchar(50) | NO | | | |
|
||||||
| treerank | int(10) | NO | | NULL | |
|
| tree_name | varchar(100) | NO | | | |
|
||||||
| timestamp | varchar(50) | NO | | NULL | |
|
| tree_rank | int(10) | NO | | | |
|
||||||
+-----------+--------------+------+-----+---------+----------------+
|
| tree_height | int(10) | NO | | 0 | |
|
||||||
|
| has_pin | tinyint(1) | NO | | 0 | |
|
||||||
|
| timestamp | varchar(50) | NO | | | |
|
||||||
|
+-------------+--------------+------+-----+---------+----------------+
|
||||||
```
|
```
|
||||||
|
|
||||||
## Changes to Implement
|
## Changes to Implement
|
||||||
|
229
modules/dbfn.js
Normal file
229
modules/dbfn.js
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
const dotenv = require('dotenv');
|
||||||
|
dotenv.config();
|
||||||
|
const debugMode = process.env.DEBUG || true;
|
||||||
|
const mysql = require('mysql');
|
||||||
|
const db = mysql.createConnection({
|
||||||
|
host : process.env.DBHOST,
|
||||||
|
user : process.env.DBUSER,
|
||||||
|
password : process.env.DBPASS,
|
||||||
|
database : process.env.DBNAME,
|
||||||
|
port : process.env.DBPORT
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Table Structures
|
||||||
|
guild_info
|
||||||
|
+------------------------+-------------+------+-----+---------+----------------+
|
||||||
|
| Field | Type | Null | Key | Default | Extra |
|
||||||
|
+------------------------+-------------+------+-----+---------+----------------+
|
||||||
|
| guild_id | varchar(50) | NO | PRI | NULL | auto_increment |
|
||||||
|
| tree_name | varchar(100)| NO | | | |
|
||||||
|
| tree_height | varchar(10) | NO | | 0 | |
|
||||||
|
| tree_message_id | varchar(50) | NO | | | |
|
||||||
|
| tree_channel_id | varchar(50) | NO | | | |
|
||||||
|
| leaderboard_message_id | varchar(50) | NO | | | |
|
||||||
|
| leaderboard_channel_id | varchar(50) | NO | | | |
|
||||||
|
+------------------------+-------------+------+-----+---------+----------------+
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
leaderboard
|
||||||
|
+-------------+--------------+------+-----+---------+----------------+
|
||||||
|
| Field | Type | Null | Key | Default | Extra |
|
||||||
|
+-------------+--------------+------+-----+---------+----------------+
|
||||||
|
| id | int(10) | NO | PRI | NULL | auto_increment |
|
||||||
|
| guild_id | varchar(50) | NO | | | |
|
||||||
|
| tree_name | varchar(100) | NO | | | |
|
||||||
|
| tree_rank | int(10) | NO | | | |
|
||||||
|
| tree_height | int(10) | NO | | 1 | |
|
||||||
|
| has_pin | tinyint(1) | NO | | 0 | |
|
||||||
|
| timestamp | varchar(50) | NO | | | |
|
||||||
|
+-------------+--------------+------+-----+---------+----------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
db.connect((err) => {
|
||||||
|
if (err) throw `Error connecting to the database: ${err.message}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
db.on('error', function(err) {
|
||||||
|
if(err.code === 'PROTOCOL_CONNECTION_LOST' || err.code == 'ECONNRESET') {
|
||||||
|
db.connect((err) => {
|
||||||
|
if (err) throw `Error connecting to the database: ${err.message}`;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createGuildTables(guildId) {
|
||||||
|
// Create the guild-information and rank-information tables to be used.
|
||||||
|
const createGuildInfoTableQuery = "CREATE TABLE IF NOT EXISTS guild_info(guild_id VARCHAR(50) NOT NULL, tree_name VARCHAR(100) NOT NULL DEFAULT 'Run /setup where your tree is.', tree_height INT(10) NOT NULL DEFAULT 0, tree_message_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your tree is.', tree_channel_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your tree is.', leaderboard_message_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your leaderboard is.', leaderboard_channel_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your leaderboard is.', CONSTRAINT guild_pk PRIMARY KEY (guild_id))";
|
||||||
|
const createLeaderboardTableQuery = "CREATE TABLE IF NOT EXISTS leaderboard(id INT(10) NOT NULL AUTO_INCREMENT,guild_id VARCHAR(50) NOT NULL,tree_name VARCHAR(100) NOT NULL,tree_rank INT(10) NOT NULL,tree_height INT(10) NOT NULL DEFAULT 1,has_pin TINYINT(1) NOT NULL DEFAULT 0,timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT id_pk PRIMARY KEY(id))";
|
||||||
|
// TODO run the queries, then add a call to this function at the beginning of main.js or functions.js
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.query(createGuildInfoTableQuery, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject("Error creating the guild_info table: " + err.message);
|
||||||
|
console.error("Offending query: " + createGuildInfoTableQuery);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
db.query(createLeaderboardTableQuery, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject("Error creating the leaderboard table: " + err.message);
|
||||||
|
console.error("Offending query: " + createLeaderboardTableQuery);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve({ "status": "Successfully checked both tables.", "data": null })
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getGuildInfo(guildId) {
|
||||||
|
// Get a server's tree information from the database
|
||||||
|
const selectGuildInfoQuery = `SELECT tree_name, tree_height, tree_message_id, tree_channel_id, leaderboard_message_id, leaderboard_channel_id FROM guild_info WHERE guild_id = ${db.escape(guildId)}`;
|
||||||
|
// 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) => {
|
||||||
|
db.query(selectGuildInfoQuery, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
reject("Error fetching guild information: " + err.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/*const guildInfo = { "guildId": "123",
|
||||||
|
"treeName": "name",
|
||||||
|
"treeHeight": 123,
|
||||||
|
"treeMessageId": "123",
|
||||||
|
"treeChannelId": "123",
|
||||||
|
"leaderboardMessageId": "123",
|
||||||
|
"leaderboardChannelId": "123"
|
||||||
|
};*/
|
||||||
|
if (res.length == 0) {
|
||||||
|
reject("There is no database entry for your guild yet. Try running /setup");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
row = res[0];
|
||||||
|
const guildInfo = { "guildId": row.guild_id,
|
||||||
|
"treeName": row.tree_name,
|
||||||
|
"treeHeight": row.tree_height,
|
||||||
|
"treeMessageId": row.tree_message_id,
|
||||||
|
"treeChannelId": row.tree_channel_id,
|
||||||
|
"leaderboardMessageId": row.leaderboard_message_id,
|
||||||
|
"leaderboardChannelId": row.leaderboard_channel_id
|
||||||
|
};
|
||||||
|
resolve({ "status": "Successfully fetched guild information", "data": guildInfo })
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setGuildInfo(guildInfo) {
|
||||||
|
// Returns a Promise, resolve({ "status": "", "data": null })
|
||||||
|
// guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"}
|
||||||
|
// Set a server's tree information in the database
|
||||||
|
const insertGuildInfoQuery = `INSERT INTO guild_info (guild_id, tree_name, tree_height, tree_message_id, tree_channel_id, leaderboard_message_id, leaderboard_channel_id) VALUES (${db.escape(guildInfo.guildId)}, ${db.escape(guildInfo.treeName)}, ${db.escape(guildInfo.treeHeight)},${db.escape(guildInfo.treeMessageId)}, ${db.escape(guildInfo.treeChannelId)}, ${db.escape(guildInfo.leaderboardMessageId)}, ${db.escape(guildInfo.leaderboardChannelId)}) ON DUPLICATE KEY UPDATE tree_name = ${db.escape(guildInfo.treeName)},tree_height = ${db.escape(guildInfo.treeHeight)},tree_message_id = ${db.escape(guildInfo.treeMessageId)},tree_channel_id = ${db.escape(guildInfo.treeChannelId)},leaderboard_message_id = ${db.escape(guildInfo.leaderboardMessageId)},leaderboard_channel_id = ${db.escape(guildInfo.leaderboardChannelId)}`;
|
||||||
|
// TODO run this query and return a promise, then resolve with { "status": , "data": null }
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.query(insertGuildInfoQuery, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
reject("Error setting the guild info: " + err.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve({ "status": "Successfully set the guild information", "data": null });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setTreeInfo(guildInfo) {
|
||||||
|
// Returns a Promise, resolve({ "status": "", "data": null })
|
||||||
|
// guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"}
|
||||||
|
// Set a server's tree information in the database)
|
||||||
|
const insertGuildInfoQuery = `INSERT INTO guild_info (guild_id, tree_name, tree_height, tree_message_id, tree_channel_id) VALUES (${db.escape(guildInfo.guildId)}, ${db.escape(guildInfo.treeName)}, ${db.escape(guildInfo.treeHeight)},${db.escape(guildInfo.treeMessageId)}, ${db.escape(guildInfo.treeChannelId)}) ON DUPLICATE KEY UPDATE tree_name = ${db.escape(guildInfo.treeName)},tree_height = ${db.escape(guildInfo.treeHeight)},tree_message_id = ${db.escape(guildInfo.treeMessageId)},tree_channel_id = ${db.escape(guildInfo.treeChannelId)}`;
|
||||||
|
// TODO run this query and return a promise, then resolve with { "status": , "data": null }
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.query(insertGuildInfoQuery, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
reject("Error setting the guild info: " + err.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve({ "status": "Successfully set the guild information", "data": null });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setLeaderboardInfo(guildInfo) {
|
||||||
|
// Returns a Promise, resolve({ "status": "", "data": null })
|
||||||
|
// guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"}
|
||||||
|
// Set a server's tree information in the database
|
||||||
|
const insertGuildInfoQuery = `INSERT INTO guild_info (guild_id, leaderboard_message_id, leaderboard_channel_id) VALUES (${db.escape(guildInfo.guildId)}, ${db.escape(guildInfo.leaderboardMessageId)}, ${db.escape(guildInfo.leaderboardChannelId)}) ON DUPLICATE KEY UPDATE leaderboard_message_id = ${db.escape(guildInfo.leaderboardMessageId)},leaderboard_channel_id = ${db.escape(guildInfo.leaderboardChannelId)}`;
|
||||||
|
// TODO run this query and return a promise, then resolve with { "status": , "data": null }
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.query(insertGuildInfoQuery, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
reject("Error setting the guild info: " + err.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve({ "status": "Successfully set the guild information", "data": null });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteGuildInfo(guildId) {
|
||||||
|
// Returns a Promise, resolve({ "status": "", "data": null })
|
||||||
|
// guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"}
|
||||||
|
// Set a server's tree information in the database
|
||||||
|
const deleteGuildInfoQuery = `DELETE FROM guild_info WHERE guild_id = ${db.escape(guildId)}`;
|
||||||
|
// TODO run this query and return a promise, then resolve with { "status": , "data": null }
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.query(deleteGuildInfoQuery, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
reject("Error deleting the guild info: " + err.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve({ "status": "Successfully deleted the guild information", "data": null });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getLeaderboard(guildId) {
|
||||||
|
// Returns a Promise, resolve({ "status": "", "data": leaderboard })
|
||||||
|
const selectLeaderboardQuery = `SELECT id, tree_name, tree_rank, tree_height, has_pin FROM leaderboard WHERE guild_id = ${db.escape(guildId)} ORDER BY id DESC LIMIT 10`;
|
||||||
|
// TODO run the query and return a promise then process the results. resolve with { "status": , "data": leaderboard }
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.query(selectLeaderboardQuery, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
reject("Error fetching the most recent leaderboard: " + err.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let leaderboard = [];
|
||||||
|
res.forEach(row => {
|
||||||
|
leaderboard.push({
|
||||||
|
"treeName": row.tree_name,
|
||||||
|
"treeRank": row.tree_rank,
|
||||||
|
"treeHeight": row.tree_height,
|
||||||
|
"hasPin": row.has_pin
|
||||||
|
});
|
||||||
|
});
|
||||||
|
resolve({ "status": "Successfully fetched leaderboard.", "data": leaderboard });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
uploadLeaderboard(leaderboard) {
|
||||||
|
// Returns a Promise, resolve({ "status": "", "data": res })
|
||||||
|
// leaderboard = { "guildId": 1234, "entries": [ { "treeHeight": 12, "treeRank": 34, "treeName": "name", "hasPin": false }, {...}, {...} ] }
|
||||||
|
const insertLeaderboardQuery = "INSERT INTO `leaderboard` (guild_id, tree_name, tree_rank, tree_height, has_pin) VALUES ?";
|
||||||
|
const leaderboardValues = [];
|
||||||
|
leaderboard.entries.forEach(ranking => {
|
||||||
|
leaderboardValues.push([leaderboard.guildId, ranking.treeName, ranking.treeRank, ranking.treeHeight, ranking.hasPin]);
|
||||||
|
});
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
db.query(insertLeaderboardQuery, [leaderboardValues], (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
reject("Error uploading the leaderboard: " + err.message);
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve({ "status": "Successfully uploaded the leaderboard", "data": res });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
db
|
||||||
|
};
|
@ -13,9 +13,15 @@ const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = Discord;
|
|||||||
|
|
||||||
// Various imports from other files
|
// Various imports from other files
|
||||||
const config = require('../data/config.json');
|
const config = require('../data/config.json');
|
||||||
let guildInfo = require('../data/guildInfo.json');
|
|
||||||
const strings = require('../data/strings.json');
|
const strings = require('../data/strings.json');
|
||||||
const slashCommandFiles = fs.readdirSync('./slash-commands/').filter(file => file.endsWith('.js'));
|
const slashCommandFiles = fs.readdirSync('./slash-commands/').filter(file => file.endsWith('.js'));
|
||||||
|
const dbfn = require('./dbfn.js');
|
||||||
|
|
||||||
|
dbfn.createGuildTables().then(res => {
|
||||||
|
console.log(res.status);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
|
||||||
const functions = {
|
const functions = {
|
||||||
// Functions for managing and creating Collections
|
// Functions for managing and creating Collections
|
||||||
@ -89,168 +95,208 @@ const functions = {
|
|||||||
rankings: {
|
rankings: {
|
||||||
parse(interaction) {
|
parse(interaction) {
|
||||||
return new Promise ((resolve, reject) => {
|
return new Promise ((resolve, reject) => {
|
||||||
if (guildInfo[interaction.guildId] == undefined) {
|
dbfn.getGuildInfo(interaction.guildId).then(res => {
|
||||||
reject(strings.error.noGuild);
|
const guildInfo = res.data;
|
||||||
return;
|
if (guildInfo.guildId == "") {
|
||||||
}
|
reject(strings.error.noGuild);
|
||||||
if (guildInfo[interaction.guildId].rankMessageId != undefined) {
|
return;
|
||||||
interaction.guild.channels.fetch(guildInfo[interaction.guildId].rankChannelId).then(c => {
|
}
|
||||||
c.messages.fetch(guildInfo[interaction.guildId].rankMessageId).then(rankMessage => {
|
if (guildInfo.leaderboardMessageId != undefined) {
|
||||||
if ((rankMessage.embeds.length == 0) || (rankMessage.embeds[0].data.title != 'Tallest Trees' )) {
|
interaction.guild.channels.fetch(guildInfo.leaderboardChannelId).then(c => {
|
||||||
reject("This doesn't appear to be a valid ``/top trees`` message.");
|
c.messages.fetch(guildInfo.leaderboardMessageId).then(leaderboardMessage => {
|
||||||
return;
|
if ((leaderboardMessage.embeds.length == 0) || (leaderboardMessage.embeds[0].data.title != 'Tallest Trees' )) {
|
||||||
}
|
reject("This doesn't appear to be a valid ``/top trees`` message.");
|
||||||
let lines = rankMessage.embeds[0].data.description.split('\n');
|
return;
|
||||||
let rankings = [];
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
let breakdown = lines[i].split(' - ');
|
|
||||||
if (breakdown[0].includes('🥇')) {
|
|
||||||
breakdown[0] = '``#1 ``'
|
|
||||||
} else if (breakdown[0].includes('🥈')) {
|
|
||||||
breakdown[0] = '``#2 ``'
|
|
||||||
} else if (breakdown[0].includes('🥉')) {
|
|
||||||
breakdown[0] = '``#3 ``'
|
|
||||||
}
|
}
|
||||||
|
let lines = leaderboardMessage.embeds[0].data.description.split('\n');
|
||||||
let trimmedRank = breakdown[0].slice(breakdown[0].indexOf('#') + 1, breakdown[0].lastIndexOf('``'));
|
let leaderboard = {
|
||||||
|
"guildId": interaction.guildId,
|
||||||
let trimmedName = breakdown[1].slice(breakdown[1].indexOf('``') + 2);
|
"entries": []
|
||||||
trimmedName = trimmedName.slice(0, trimmedName.indexOf('``'));
|
};
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
let trimmedHeight = parseFloat(breakdown[2].slice(0, breakdown[2].indexOf('ft'))).toFixed(1);
|
// Breakdown each line separating it on each -
|
||||||
let isMyTree = false;
|
let breakdown = lines[i].split(' - ');
|
||||||
let isMaybeMyTree = false;
|
|
||||||
if (breakdown[2].includes('📍')) isMyTree = true;
|
// Check if the first part, the ranking, has these emojis to detect first second and third place
|
||||||
if (breakdown[1].includes(guildInfo[interaction.guildId].treeName)) maybeMyTree = true;
|
if (breakdown[0].includes('🥇')) {
|
||||||
|
breakdown[0] = '``#1 ``'
|
||||||
rankings.push({
|
} else if (breakdown[0].includes('🥈')) {
|
||||||
rank: trimmedRank,
|
breakdown[0] = '``#2 ``'
|
||||||
name: trimmedName,
|
} else if (breakdown[0].includes('🥉')) {
|
||||||
height: trimmedHeight,
|
breakdown[0] = '``#3 ``'
|
||||||
myTree: isMyTree,
|
}
|
||||||
maybeMyTree: isMaybeMyTree
|
|
||||||
|
// Clean off the excess and get just the number from the rank, make sure it's an int not string
|
||||||
|
let trimmedRank = parseInt(breakdown[0].slice(breakdown[0].indexOf('#') + 1, breakdown[0].lastIndexOf('``')));
|
||||||
|
|
||||||
|
// Clean off the excess and get just the tree name
|
||||||
|
let trimmedName = breakdown[1].slice(breakdown[1].indexOf('``') + 2);
|
||||||
|
trimmedName = trimmedName.slice(0, trimmedName.indexOf('``'));
|
||||||
|
|
||||||
|
// Clean off the excess and get just the tree height, make sure it's a 1 decimal float
|
||||||
|
let trimmedHeight = parseFloat(breakdown[2].slice(0, breakdown[2].indexOf('ft'))).toFixed(1);
|
||||||
|
let isMyTree = false;
|
||||||
|
let isMaybeMyTree = false;
|
||||||
|
if (breakdown[2].includes('📍')) isMyTree = true;
|
||||||
|
if (breakdown[1].includes(guildInfo.treeName)) maybeMyTree = true;
|
||||||
|
|
||||||
|
// "entries": [ { "treeHeight": 12, "treeRank": 34, "treeName": "name" }, ] }
|
||||||
|
|
||||||
|
|
||||||
|
leaderboard.entries.push({
|
||||||
|
treeRank: trimmedRank,
|
||||||
|
treeName: trimmedName,
|
||||||
|
treeHeight: trimmedHeight,
|
||||||
|
hasPin: isMyTree
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dbfn.uploadLeaderboard(leaderboard).then(res => {
|
||||||
|
console.log(res.status);
|
||||||
|
resolve(res.status);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
guildInfo[interaction.guildId].rankings = rankings;
|
|
||||||
fs.writeFileSync('./data/guildInfo.json', JSON.stringify(guildInfo));
|
|
||||||
guildInfo = require('../data/guildInfo.json');
|
|
||||||
resolve(rankings);
|
|
||||||
});
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
reject("The leaderboardMessageId is undefined somehow");
|
||||||
reject("The rankMessageId is undefined somehow");
|
return;
|
||||||
return;
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
compare(interaction) {
|
compare(interaction) {
|
||||||
if (guildInfo[interaction.guildId] == undefined) {
|
return new Promise((resolve, reject) => {
|
||||||
return strings.error.noGuild;
|
dbfn.getGuildInfo(interaction.guildId).then(res => {
|
||||||
}
|
const guildInfo = res.data;
|
||||||
let treeHeight = parseFloat(guildInfo[interaction.guildId].treeHeight).toFixed(1);
|
guildInfo.guildId = interaction.guildId;
|
||||||
if ((guildInfo[interaction.guildId].rankings.length > 0) && (treeHeight > 0)) {
|
|
||||||
let replyString = 'Current Tree Height: ' + treeHeight + 'ft\n\n';
|
let treeHeight = parseFloat(guildInfo.treeHeight).toFixed(1);
|
||||||
guildInfo[interaction.guildId].rankings.forEach(treeRanking => {
|
dbfn.getLeaderboard(interaction.guildId).then(res => {
|
||||||
let difference = parseFloat(treeRanking.height).toFixed(1) - treeHeight;
|
const leaderboard = res.data;
|
||||||
let decimal = (treeRanking.height % 1).toFixed(1);
|
|
||||||
let growthIndicator = "";
|
let replyString = 'Current Tree Height: ' + treeHeight + 'ft\n\n';
|
||||||
if (decimal > 0) {
|
leaderboard.forEach(treeRanking => {
|
||||||
growthIndicator += "[+]";
|
let difference = parseFloat(treeRanking.treeHeight).toFixed(1) - treeHeight;
|
||||||
}
|
let decimal = (treeRanking.treeHeight % 1).toFixed(1);
|
||||||
const absDifference = parseFloat(Math.abs(difference)).toFixed(1);
|
let growthIndicator = "";
|
||||||
if (treeRanking.myTree) {
|
if (decimal > 0) {
|
||||||
replyString += "This is your tree. ";
|
growthIndicator += "[+]";
|
||||||
} else if (treeRanking.maybeMyTree) {
|
}
|
||||||
replyString += "This might be your tree. Same height, same name. ";
|
const absDifference = parseFloat(Math.abs(difference)).toFixed(1);
|
||||||
} else {
|
if (treeRanking.hasPin) {
|
||||||
if (difference > 0) {
|
replyString += "This is your tree. ";
|
||||||
replyString += `#${treeRanking.rank} - ${absDifference}ft${growthIndicator} shorter `;
|
} else if ((treeRanking.treeHeight == treeHeight) && (treeRanking.treeName == guildInfo.treeName)) {
|
||||||
} else if (difference < 0) {
|
replyString += "This might be your tree. Same height, same name. ";
|
||||||
replyString += `#${treeRanking.rank} - ${absDifference}ft${growthIndicator} taller `;
|
} else {
|
||||||
} else if (difference == 0) {
|
if (difference > 0) {
|
||||||
replyString += `#${treeRanking.rank} - Same Height${growthIndicator} `;
|
replyString += `#${treeRanking.treeRank} - ${absDifference}ft${growthIndicator} shorter `;
|
||||||
}
|
} else if (difference < 0) {
|
||||||
}
|
replyString += `#${treeRanking.treeRank} - ${absDifference}ft${growthIndicator} taller `;
|
||||||
replyString += `[${functions.getWaterTime(treeRanking.height)} mins]\n`;
|
} else if (difference == 0) {
|
||||||
|
replyString += `#${treeRanking.treeRank} - Same Height${growthIndicator} `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replyString += `[${functions.getWaterTime(treeRanking.treeHeight)} mins]\n`;
|
||||||
|
});
|
||||||
|
resolve('Here\'s how your tree compares: \n' + replyString);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
reject(err);
|
||||||
});
|
});
|
||||||
return 'Here\'s how your tree compares: \n' + replyString;
|
});
|
||||||
} else {
|
|
||||||
console.error('Not configured correctly\n' + 'Guild ID: ' + interaction.guildId + '\nGuild Info: ' + JSON.stringify(guildInfo[interaction.guildId]));
|
|
||||||
return 'Not configured correctly';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tree: {
|
tree: {
|
||||||
parse(interaction) {
|
parse(interaction) {
|
||||||
let input;
|
let input;
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (guildInfo[interaction.guildId] == undefined) {
|
dbfn.getGuildInfo(interaction.guildId).then(res => {
|
||||||
reject(`The guild entry hasn't been created yet. [${interaction.guildId || interaction.commandGuildId}]`);
|
const guildInfo = res.data;
|
||||||
|
guildInfo.guildId = interaction.guildId;
|
||||||
|
if (guildInfo == undefined) {
|
||||||
|
reject(`The guild entry hasn't been created yet. [${interaction.guildId || interaction.commandGuildId}]`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (guildInfo.treeMessageId != "Run /setup where your tree is.") {
|
||||||
|
interaction.guild.channels.fetch(guildInfo.treeChannelId).then(c => {
|
||||||
|
c.messages.fetch(guildInfo.treeMessageId).then(m => {
|
||||||
|
if ( (m.embeds.length == 0) || !(m.embeds[0].data.description.includes('Your tree is')) ) {
|
||||||
|
reject("This doesn't appear to be a valid ``/tree`` message.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
input = m.embeds[0].data.description;
|
||||||
|
let treeName = m.embeds[0].data.title;
|
||||||
|
let lines = input.split('\n');
|
||||||
|
guildInfo.treeHeight = parseFloat(lines[0].slice(lines[0].indexOf('is') + 3, lines[0].indexOf('ft'))).toFixed(1);
|
||||||
|
guildInfo.treeName = treeName;
|
||||||
|
dbfn.setTreeInfo(guildInfo).then(res => {
|
||||||
|
resolve("The reference tree message has been saved/updated.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.error('treeMessageId undefined');
|
||||||
|
reject("There was an unknown error while setting the tree message.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
reject(err);
|
||||||
|
console.error(err);
|
||||||
return;
|
return;
|
||||||
}
|
});
|
||||||
if (guildInfo[interaction.guildId].treeMessageId != "") {
|
|
||||||
interaction.guild.channels.fetch(guildInfo[interaction.guildId].treeChannelId).then(c => {
|
|
||||||
c.messages.fetch(guildInfo[interaction.guildId].treeMessageId).then(m => {
|
|
||||||
if ( (m.embeds.length == 0) || !(m.embeds[0].data.description.includes('Your tree is')) ) {
|
|
||||||
reject("This doesn't appear to be a valid ``/tree`` message.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
input = m.embeds[0].data.description;
|
|
||||||
let treeName = m.embeds[0].data.title;
|
|
||||||
let lines = input.split('\n');
|
|
||||||
guildInfo[interaction.guildId].treeHeight = parseFloat(lines[0].slice(lines[0].indexOf('is') + 3, lines[0].indexOf('ft'))).toFixed(1);
|
|
||||||
guildInfo[interaction.guildId].treeName = treeName;
|
|
||||||
fs.writeFileSync('./data/guildInfo.json', JSON.stringify(guildInfo));
|
|
||||||
guildInfo = require('../data/guildInfo.json');
|
|
||||||
resolve("The reference tree message has been saved/updated.");
|
|
||||||
});
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
console.error('treeMessageId undefined');
|
|
||||||
reject("There was an unknown error while setting the tree message.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refresh(interaction) {
|
refresh(interaction) {
|
||||||
functions.rankings.parse(interaction).then(r1 => {
|
functions.rankings.parse(interaction).then(r1 => {
|
||||||
functions.tree.parse(interaction).then(r2 => {
|
functions.tree.parse(interaction).then(r2 => {
|
||||||
const embed = functions.builders.comparisonEmbed(functions.rankings.compare(interaction), functions.builders.refreshAction())
|
functions.rankings.compare(interaction).then(res => {
|
||||||
interaction.update(embed);
|
const embed = functions.builders.comparisonEmbed(res, functions.builders.refreshAction())
|
||||||
|
interaction.update(embed);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
|
console.error(e);
|
||||||
interaction.reply(functions.builders.errorEmbed(e));
|
interaction.reply(functions.builders.errorEmbed(e));
|
||||||
});
|
});
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
|
console.error(e);
|
||||||
interaction.reply(functions.builders.errorEmbed(e));
|
interaction.reply(functions.builders.errorEmbed(e));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
reset(guildId) {
|
reset(guildId) {
|
||||||
delete guildInfo[guildId];
|
return new Promise((resolve, reject) => {
|
||||||
fs.writeFileSync('./data/guildInfo.json', JSON.stringify(guildInfo));
|
dbfn.deleteGuildInfo(guildId).then(res => {
|
||||||
guildInfo = require('../data/guildInfo.json');
|
resolve(res);
|
||||||
return;
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
getInfo(guildId) {
|
getInfo(guildId) {
|
||||||
const thisGuildInfo = guildInfo[guildId];
|
return new Promise((resolve, reject) => {
|
||||||
if (thisGuildInfo != undefined) {
|
dbfn.getGuildInfo(guildId).then(res => {
|
||||||
let thisGuildInfoString = "";
|
let guildInfo = res.data;
|
||||||
if (thisGuildInfo.treeMessageId != "") {
|
let guildInfoString = "";
|
||||||
thisGuildInfoString += `Tree Message: https://discord.com/channels/${guildId}/${thisGuildInfo.treeChannelId}/${thisGuildInfo.treeMessageId}\n`;
|
guildInfoString += `Tree Message: https://discord.com/channels/${guildId}/${guildInfo.treeChannelId}/${guildInfo.treeMessageId}\n`;
|
||||||
|
guildInfoString += `Rank Message: https://discord.com/channels/${guildId}/${guildInfo.leaderboardChannelId}/${guildInfo.leaderboardMessageId}\n`;
|
||||||
}
|
resolve(`Here is your servers setup info:\n${guildInfoString}`);
|
||||||
if (thisGuildInfo.rankMessageId != "") {
|
}).catch(err => {
|
||||||
thisGuildInfoString += `Rank Message: https://discord.com/channels/${guildId}/${thisGuildInfo.rankChannelId}/${thisGuildInfo.rankMessageId}\n`;
|
console.error(err);
|
||||||
}
|
reject(err);
|
||||||
if (thisGuildInfo.treeHeight != "") {
|
return;
|
||||||
thisGuildInfoString += `Tree Height: ${thisGuildInfo.treeHeight}\n`;
|
})
|
||||||
}
|
});
|
||||||
return `Here is your servers setup info:\n${thisGuildInfoString}`;
|
|
||||||
} else {
|
|
||||||
return "Your guild hasn't been set up yet.";
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
getWaterTime(size) {
|
getWaterTime(size) {
|
||||||
return Math.floor((Math.pow(size * 0.07 + 5, 1.1) / 60));
|
return Math.floor((Math.pow(size * 0.07 + 5, 1.1) / 60));
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"homepage": "https://github.com/voidf1sh/silvanus#readme",
|
"homepage": "https://github.com/voidf1sh/silvanus#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"discord.js": "^14.7.1",
|
"discord.js": "^14.7.1",
|
||||||
"dotenv": "^16.0.3"
|
"dotenv": "^16.0.3",
|
||||||
|
"mysql": "^2.18.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,18 @@ module.exports = {
|
|||||||
.setName('compare')
|
.setName('compare')
|
||||||
.setDescription('See how your tree compares to other trees!'),
|
.setDescription('See how your tree compares to other trees!'),
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
const embed = fn.builders.comparisonEmbed(fn.rankings.compare(interaction), fn.builders.refreshAction());
|
interaction.deferReply().then(() => {
|
||||||
interaction.reply(embed);
|
fn.rankings.compare(interaction).then(res => {
|
||||||
|
const embed = fn.builders.comparisonEmbed(res, fn.builders.refreshAction());
|
||||||
|
interaction.editReply(embed).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
interaction.editReply(fn.builders.errorEmbed(err)).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
},
|
},
|
||||||
};
|
};
|
@ -6,7 +6,19 @@ module.exports = {
|
|||||||
.setName('reset')
|
.setName('reset')
|
||||||
.setDescription('Reset all message assignments in your server'),
|
.setDescription('Reset all message assignments in your server'),
|
||||||
execute(interaction) {
|
execute(interaction) {
|
||||||
fn.reset(interaction.guildId);
|
interaction.deferReply({ ephemeral: true }).then(() => {
|
||||||
interaction.reply(fn.builders.embed("Assignments Reset"));
|
fn.reset(interaction.guildId).then(res => {
|
||||||
|
interaction.editReply(fn.builders.embed("Assignments Reset")).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
interaction.editReply("There was a problem deleting your guild information, contact @voidf1sh#0420 for help.").catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
@ -1,7 +1,7 @@
|
|||||||
const { SlashCommandBuilder } = require('discord.js');
|
const { SlashCommandBuilder } = require('discord.js');
|
||||||
const fn = require('../modules/functions.js');
|
const fn = require('../modules/functions.js');
|
||||||
const guildInfo = require('../data/guildInfo.json');
|
|
||||||
const strings = require('../data/strings.json');
|
const strings = require('../data/strings.json');
|
||||||
|
const dbfn = require('../modules/dbfn.js');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
@ -9,41 +9,57 @@ module.exports = {
|
|||||||
.setDescription('Attempt automatic configuration of the bot.'),
|
.setDescription('Attempt automatic configuration of the bot.'),
|
||||||
execute(interaction) {
|
execute(interaction) {
|
||||||
interaction.deferReply({ ephemeral: true }).then(function () {
|
interaction.deferReply({ ephemeral: true }).then(function () {
|
||||||
if (guildInfo[interaction.guildId] == undefined) {
|
/*const guildInfo = { "guildId": "123",
|
||||||
guildInfo[interaction.guildId] = {
|
"treeName": "name",
|
||||||
"treeMessageId": "",
|
"treeHeight": 123,
|
||||||
"treeChannelId": "",
|
"treeMessageId": "123",
|
||||||
"rankMessageId": "",
|
"treeChannelId": "123",
|
||||||
"rankChannelId": "",
|
"leaderboardMessageId": "123",
|
||||||
"treeName": "",
|
"leaderboardChannelId": "123"
|
||||||
"treeHeight": 0,
|
};*/
|
||||||
"rankings": []
|
const guildInfo = { "guildId": interaction.guildId,
|
||||||
};
|
"treeName": "name",
|
||||||
}
|
"treeHeight": 123,
|
||||||
|
"treeMessageId": "123",
|
||||||
|
"treeChannelId": "123",
|
||||||
|
"leaderboardMessageId": "123",
|
||||||
|
"leaderboardChannelId": "123"
|
||||||
|
};
|
||||||
interaction.channel.messages.fetch({ limit: 20 }).then(function (msgs) {
|
interaction.channel.messages.fetch({ limit: 20 }).then(function (msgs) {
|
||||||
let treeFound = false;
|
let treeFound = false;
|
||||||
let rankFound = false;
|
let leaderboardFound = false;
|
||||||
msgs.reverse().forEach(msg => {
|
msgs.reverse().forEach(msg => {
|
||||||
if (msg.embeds.length > 0) {
|
if (msg.embeds.length > 0) {
|
||||||
if (msg.embeds[0].data.description.includes("Your tree is")) {
|
if (msg.embeds[0].data.description.includes("Your tree is")) {
|
||||||
treeFound = true;
|
treeFound = true;
|
||||||
guildInfo[interaction.guildId].treeChannelId = msg.channelId;
|
guildInfo.treeName = msg.embeds[0].title;
|
||||||
guildInfo[interaction.guildId].treeMessageId = msg.id;
|
guildInfo.treeChannelId = msg.channelId;
|
||||||
fn.tree.parse(msg);
|
guildInfo.treeMessageId = msg.id;
|
||||||
} else if (msg.embeds[0].data.title == "Tallest Trees") {
|
} else if (msg.embeds[0].data.title == "Tallest Trees") {
|
||||||
rankFound = true;
|
leaderboardFound = true;
|
||||||
guildInfo[interaction.guildId].rankChannelId = msg.channelId;
|
guildInfo.leaderboardChannelId = msg.channelId;
|
||||||
guildInfo[interaction.guildId].rankMessageId = msg.id;
|
guildInfo.leaderboardMessageId = msg.id;
|
||||||
fn.rankings.parse(msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (treeFound && !(rankFound)) {
|
if (treeFound && !(leaderboardFound)) {
|
||||||
interaction.editReply(fn.builders.embed(strings.status.treeNoLeaderboard));
|
dbfn.setTreeInfo(guildInfo).then(res => {
|
||||||
} else if (!(treeFound) && rankFound) {
|
interaction.editReply(fn.builders.embed(strings.status.treeNoLeaderboard));
|
||||||
interaction.editReply(fn.builders.embed(strings.status.leaderboardNoTree));
|
}).catch(err => {
|
||||||
} else if (treeFound && rankFound) {
|
console.error(err);
|
||||||
interaction.editReply(fn.builders.embed(strings.status.treeAndLeaderboard));
|
});
|
||||||
|
} else if (!(treeFound) && leaderboardFound) {
|
||||||
|
dbfn.setLeaderboardInfo(guildInfo).then(res => {
|
||||||
|
interaction.editReply(fn.builders.embed(strings.status.leaderboardNoTree));
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
} else if (treeFound && leaderboardFound) {
|
||||||
|
dbfn.setGuildInfo(guildInfo).then(res => {
|
||||||
|
interaction.editReply(fn.builders.embed(strings.status.treeAndLeaderboard));
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,16 @@ module.exports = {
|
|||||||
.setName('setupinfo')
|
.setName('setupinfo')
|
||||||
.setDescription('View information about how the bot is set up in your server'),
|
.setDescription('View information about how the bot is set up in your server'),
|
||||||
execute(interaction) {
|
execute(interaction) {
|
||||||
const embed = fn.builders.embed(fn.getInfo(interaction.guildId));
|
interaction.deferReply({ephemeral: true}).then(() => {
|
||||||
interaction.reply(embed);
|
fn.getInfo(interaction.guildId).then(res => {
|
||||||
|
const embed = fn.builders.embed(res);
|
||||||
|
interaction.editReply(embed);
|
||||||
|
}).catch(err => {
|
||||||
|
interaction.editReply(err);
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
Loading…
Reference in New Issue
Block a user