From a03d982a0ffef7fb03f58664cad20ec853d7937a Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Thu, 4 Jun 2020 09:00:13 -0500 Subject: [PATCH 01/53] RSS/ICS Fix --- src/index.ts | 2 +- src/routes/rss.ts | 112 +++++++++++++++++++++++++--------------------- 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/src/index.ts b/src/index.ts index 60928f1..5273382 100644 --- a/src/index.ts +++ b/src/index.ts @@ -112,7 +112,7 @@ if (process.env.MAINTENANCE.toLowerCase() == "true") { * Routes */ app.use(apiRoutes({ client: client })); - app.use(rssRoutes({ client: client })); + app.use(rssRoutes()); app.use("/", (req: any, res, next) => { res.render("home"); }); diff --git a/src/routes/rss.ts b/src/routes/rss.ts index 5857eec..67d7929 100644 --- a/src/routes/rss.ts +++ b/src/routes/rss.ts @@ -1,4 +1,4 @@ -import { Client } from "discord.js"; +import ShardManager from "../processes/shard-manager"; const ics = require("ics"); import express from "express"; import moment from "moment"; @@ -6,59 +6,61 @@ import moment from "moment"; import config from "../models/config"; import { Game, RSVP } from "../models/game"; -export default (options: any) => { +export default () => { const router = express.Router(); - const client: Client = options.client; router.use(config.urls.rss.path, async (req, res, next) => { - res.sendStatus(404); - return; - const uid = req.params.uid; - const guilds = []; + try { + const uid = req.params.uid; + const guilds = []; - let tag = ""; + let tag = ""; - client.guilds.cache.forEach((guild) => { - guild.members.cache.forEach((member) => { - if (member.id === uid) { - tag = member.user.tag; - guilds.push({ - id: guild.id, - name: guild.name, - }); - } + const shardGuilds = await ShardManager.shardGuilds({ + memberIds: [uid], }); - }); - - const gameOptions: any = { - s: { - $in: guilds.reduce((i, g) => { - i.push(g.id); - return i; - }, []), - }, - timestamp: { - $gt: new Date().getTime(), - }, - dm: { - $ne: tag, - }, - }; - const games: any[] = await Game.fetchAllBy(gameOptions); + shardGuilds.forEach((guild) => { + guild.members.forEach((member) => { + if (member.id === uid) { + tag = member.user.tag; + guilds.push({ + id: guild.id, + name: guild.name, + }); + } + }); + }); - res.type("application/xml"); - res.status(200); - res.send(` + const gameOptions: any = { + s: { + $in: guilds.reduce((i, g) => { + i.push(g.id); + return i; + }, []), + }, + timestamp: { + $gt: new Date().getTime(), + }, + dm: { + $ne: tag, + }, + }; + + const games: any[] = await Game.fetchAllBy(gameOptions); + + res.type("application/xml"); + res.status(200); + res.send(` RPG Schedule - https://rpg-schedule.herokuapp.com + https://www.rpg-schedule.com Game scheduling bot for Discord - https://rpg-schedule.herokuapp.com/images/logo2.png + https://www.rpg-schedule.com/images/logo2.png RPG Schedule - https://rpg-schedule.herokuapp.com + https://www.rpg-schedule.com ${games .map((game) => { @@ -73,17 +75,17 @@ export default (options: any) => { from: moment(date).utcOffset(parseInt(game.timezone)).fromNow(), }; - game.slot = game.reserved.split(/\r?\n/).findIndex((t) => t.trim().replace("@", "") === tag) + 1; + game.slot = game.reserved.findIndex((t) => t.id === uid || t.tag.trim().replace("@", "") === tag) + 1; game.signedup = game.slot > 0 && game.slot <= parseInt(game.players); game.waitlisted = game.slot > parseInt(game.players); return ` ${game.adventure} - https://rpg-schedule.herokuapp.com/games/upcoming - https://rpg-schedule.herokuapp.com/games/view?g=${game._id.toString().slice(-12)} + https://www.rpg-schedule.com/games/upcoming + https://www.rpg-schedule.com/games/${game._id.toString().slice(-12)} - Discord Server: ${(guilds.find((g) => g.id === game.s) || {}).name}

GM: ${game.dm}

Where: ${game.where.replace(/\&/g, "&")}

When: ${ + Discord Server: ${(guilds.find((g) => g.id === game.s) || {}).name}

GM: ${game.dm.tag}

Where: ${game.where.replace(/\&/g, "&")}

When: ${ game.moment.date }

${game.description.trim().replace(/\&/g, "&").replace(/\r?\n/g, "
")}

]]>
@@ -93,6 +95,10 @@ export default (options: any) => {
`); + } catch (err) { + console.log(err); + res.sendStatus(500); + } }); router.use(config.urls.ics.path, async (req, res, next) => { @@ -102,8 +108,12 @@ export default (options: any) => { let tag = ""; - client.guilds.cache.forEach((guild) => { - guild.members.cache.forEach((member) => { + const shardGuilds = await ShardManager.shardGuilds({ + memberIds: [uid], + }); + + shardGuilds.forEach((guild) => { + guild.members.forEach((member) => { if (member.id === uid) { tag = member.user.tag; guilds.push({ @@ -136,8 +146,7 @@ export default (options: any) => { var { error, value } = ics.createEvents( games .filter((game) => { - game.updateReservedList(); - return signedup ? (game.dm).tag === tag || (game.reserved).find((r) => r.tag === tag) : true; + return signedup ? game.dm.id === uid || game.dm.tag === tag || game.reserved.find((r) => r.id === uid || r.tag === tag) : true; }) .map((game) => { const d = new Date(game.timestamp); @@ -149,8 +158,8 @@ export default (options: any) => { description: game.description, categories: ["RPG Schedule", game.discordGuild.name], location: game.where, - organizer: { name: (game.dm).tag, email: `${((game.dm).tag || "").replace(/[^a-z0-9_.]/gi, "")}@rpg-schedule.com` }, - attendees: (game.reserved) + organizer: { name: game.dm.tag, email: `${(game.dm.tag || "").replace(/[^a-z0-9_.]/gi, "")}@rpg-schedule.com` }, + attendees: game.reserved .filter((r) => r.tag.trim().length > 0) .map((r, i) => ({ name: r, @@ -161,8 +170,7 @@ export default (options: any) => { }; }) ); - } - catch(err) { + } catch (err) { console.log(err.message); } From 187c4d48374d1e0239ab126b04a6bc75c472f2f0 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Thu, 4 Jun 2020 09:00:41 -0500 Subject: [PATCH 02/53] Versioning --- src/routes/api.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/routes/api.ts b/src/routes/api.ts index e9e560b..48d7306 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -15,6 +15,8 @@ import { User } from "../models/user"; import config from "../models/config"; import aux from "../appaux"; +const apiVersion = process.env.VERSION; + interface APIRouteOptions { client: ShardingManager; } @@ -246,6 +248,7 @@ export default (options: APIRouteOptions) => { token: req.session.api.access.access_token, account: result.account, user: userSettings.data, + version: apiVersion }); }) .catch((err) => { From bb9f08c22fd0e422075ef0ef38ddb8f2c5269b4d Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Thu, 4 Jun 2020 09:01:00 -0500 Subject: [PATCH 03/53] Data Updates --- src/processes/discord.ts | 122 +++++++++++++++++++++++---------- src/processes/shard-manager.ts | 11 +++ 2 files changed, 95 insertions(+), 38 deletions(-) diff --git a/src/processes/discord.ts b/src/processes/discord.ts index aab91e8..34779fd 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -39,25 +39,11 @@ client.on("ready", async () => { if (!isReady) { isReady = true; - const guilds = client.guilds.cache.array(); - client.shard.send({ - type: "shard", - name: "guilds", - data: guilds.map((guild) => { - return { - id: guild.id, - name: guild.name, - icon: guild.icon, - shardID: guild.shardID, - ownerID: guild.ownerID, - members: guild.members.cache.array(), - users: guild.members.cache.array().map((m) => m.user), - memberRoles: guild.members.cache.map((m) => m.roles.cache), - channels: guild.channels.cache.array(), - roles: guild.roles.cache.array(), - }; - }), - }); + // Send updated server information to the API + sendGuildsToAPI(true); + setInterval(() => { + sendGuildsToAPI(true); + }, 30 * 60 * 1000); if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (!connected) connected = await db.database.connect(); @@ -752,31 +738,47 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { }); client.on("guildMemberAdd", async (member) => { - client.shard.send({ - type: "shard", - name: "guildMemberAdd", - data: member, - user: member.user - }); + if (member.user.id === client.user.id) { + sendGuildsToAPI(); + } else { + client.shard.send({ + type: "shard", + name: "guildMemberAdd", + data: member, + user: member.user, + }); + } }); client.on("guildMemberUpdate", async (oldR, member) => { - client.shard.send({ - type: "shard", - name: "guildMemberUpdate", - data: member, - user: member.user, - roles: member.roles.cache.array(), - }); + if (member.user.id === client.user.id) { + sendGuildsToAPI(); + } else { + client.shard.send({ + type: "shard", + name: "guildMemberUpdate", + data: member, + user: member.user, + roles: member.roles.cache.array(), + }); + } }); client.on("guildMemberRemove", async (member) => { - client.shard.send({ - type: "shard", - name: "guildMemberRemove", - data: member, - user: member.user, - }); + if (member.user.id === client.user.id) { + client.shard.send({ + type: "shard", + name: "guildDelete", + data: member.guild.id, + }); + } else { + client.shard.send({ + type: "shard", + name: "guildMemberRemove", + data: member, + user: member.user, + }); + } }); client.on("userUpdate", async (oldUser: User, newUser: User) => { @@ -835,6 +837,22 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } }); + client.on("guildUpdate", async (oldG, newG) => { + client.shard.send({ + type: "shard", + name: "guildUpdate", + data: newG, + }); + }); + + client.on("guildDelete", async (guild) => { + client.shard.send({ + type: "shard", + name: "guildDelete", + data: guild.id, + }); + }); + /** * Discord.JS - messageDelete * Delete the game from the database when the announcement message is deleted @@ -1389,3 +1407,31 @@ const postReminders = async () => { }); } }; + +const apiGuildIds: any = {}; + +const sendGuildsToAPI = (all: boolean = false) => { + const guilds = client.guilds.cache.array(); + client.shard.send({ + type: "shard", + name: "guilds", + data: guilds + .filter((guild) => all || !apiGuildIds[guild.shardID] || !apiGuildIds[guild.shardID].includes(guild.id)) + .map((guild) => { + if (!apiGuildIds[guild.shardID]) apiGuildIds[guild.shardID] = []; + if (!apiGuildIds[guild.shardID].includes(guild.id)) apiGuildIds[guild.shardID].push(guild.id); + return { + id: guild.id, + name: guild.name, + icon: guild.icon, + shardID: guild.shardID, + ownerID: guild.ownerID, + members: guild.members.cache.array(), + users: guild.members.cache.array().map((m) => m.user), + memberRoles: guild.members.cache.map((m) => m.roles.cache), + channels: guild.channels.cache.array(), + roles: guild.roles.cache.array(), + }; + }), + }); +}; diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index 3da3c45..43e44c5 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -133,6 +133,17 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { }); return g; }); + } else if (message.name === "guildUpdate") { + guildData = guildData.map((g) => { + if (g.id !== message.data.id) return g; + g.name = message.data.name; + return g; + }); + } else if (message.name === "guildDelete") { + const index = guildData.findIndex(g => g.id === message.data); + if (index >= 0) { + guildData.splice(index, 1); + } } } } From 0b69e3f67fd7c00b4e573142105420957c56f346 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Thu, 4 Jun 2020 14:16:32 -0500 Subject: [PATCH 04/53] Rescheduling Reactions Bug Fix --- src/models/game.ts | 122 ++++++++++++++++++++++----------------- src/processes/discord.ts | 72 +++++++++++++---------- 2 files changed, 111 insertions(+), 83 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index bd46c8a..17bf1fd 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -107,6 +107,10 @@ interface GameSaveData { modified: boolean; } +interface GameSaveOptions { + force?: boolean; +} + export enum GameReminder { NO_REMINDER = "0", MINUTES_15 = "15", @@ -213,14 +217,15 @@ export class Game implements GameModel { set discordGuild(guild: ShardGuild) { this._guild = guild; - if (guild) this._guild.channels.forEach((c) => { - if (!this._channel && c.type === "text") { - this._channel = c; - } - if (c.id === this.c && c.type === "text") { - this._channel = c; - } - }); + if (guild) + this._guild.channels.forEach((c) => { + if (!this._channel && c.type === "text") { + this._channel = c; + } + if (c.id === this.c && c.type === "text") { + this._channel = c; + } + }); } private _channel: ShardChannel; @@ -272,7 +277,7 @@ export class Game implements GameModel { }; } - async save(force: boolean = false) { + async save(options?: GameSaveOptions) { if (!connection()) { aux.log("No database connection"); return null; @@ -328,7 +333,7 @@ export class Game implements GameModel { var gmTag = dmmember.user.toString(); if (guildConfig.embeds === false) dm = gmTag; else dm = dmmember.nickname || dm; - } else if (!game._id && !force) { + } else if (!game._id && !options.force) { game.dm = game.author; dm = authorParts[0]; dmmember = { @@ -493,24 +498,7 @@ export class Game implements GameModel { } } - if (message) { - try { - if (game.method === GameMethod.AUTOMATED) await ShardManager.shardMessageReact(guild.id, channel.id, message.id, guildConfig.emojiAdd); - } catch (err) { - if (!aux.isEmoji(guildConfig.emojiAdd)) { - guildConfig.emojiAdd = "➕"; - if (game.method === GameMethod.AUTOMATED) await ShardManager.shardMessageReact(guild.id, channel.id, message.id, guildConfig.emojiAdd); - } - } - try { - if (game.method === GameMethod.AUTOMATED && guildConfig.dropOut) ShardManager.shardMessageReact(guild.id, channel.id, message.id, guildConfig.emojiRemove); - } catch (err) { - if (!aux.isEmoji(guildConfig.emojiRemove)) { - guildConfig.emojiRemove = "➖"; - if (game.method === GameMethod.AUTOMATED && guildConfig.dropOut) ShardManager.shardMessageReact(guild.id, channel.id, message.id, guildConfig.emojiRemove); - } - } - } + this.addReactions(message, guildConfig); prev._id = prev._id.toString(); game._id = game._id.toString(); @@ -540,7 +528,6 @@ export class Game implements GameModel { game.updatedTimestamp = new Date().getTime(); const inserted = await dbCollection.insertOne(game); let message: Message; - let gcUpdated = false; try { if (guildConfig.embeds) { @@ -550,33 +537,11 @@ export class Game implements GameModel { message = await channel.send(msg, embed); - try { - if (game.method === GameMethod.AUTOMATED) await ShardManager.shardMessageReact(guild.id, channel.id, message.id, guildConfig.emojiAdd); - } catch (err) { - if (!aux.isEmoji(guildConfig.emojiAdd)) { - gcUpdated = true; - guildConfig.emojiAdd = "➕"; - if (game.method === GameMethod.AUTOMATED) await ShardManager.shardMessageReact(guild.id, channel.id, message.id, guildConfig.emojiAdd); - } - } - try { - if (game.method === GameMethod.AUTOMATED && guildConfig.dropOut) await ShardManager.shardMessageReact(guild.id, channel.id, message.id, guildConfig.emojiRemove); - } catch (err) { - if (!aux.isEmoji(guildConfig.emojiRemove)) { - gcUpdated = true; - guildConfig.emojiRemove = "➖"; - if (game.method === GameMethod.AUTOMATED && guildConfig.dropOut) await ShardManager.shardMessageReact(guild.id, channel.id, message.id, guildConfig.emojiRemove); - } - } + this.addReactions(message, guildConfig); } catch (err) { aux.log("InsertGameError:", game.s, err); } - if (gcUpdated) { - guildConfig.save(guildConfig.data); - guildConfig.updateReactions(this.client); - } - let updated; if (message) { updated = await dbCollection.updateOne({ _id: new ObjectId(inserted.insertedId) }, { $set: { messageId: message.id } }); @@ -616,7 +581,7 @@ export class Game implements GameModel { } catch (err) { aux.log("GameSaveError", game._id, err); - if (game._id && force) { + if (game._id && options.force) { const dbCollection = connection().collection(collection); await dbCollection.updateOne({ _id: new ObjectId(game._id) }, { $set: game }); } @@ -698,6 +663,57 @@ export class Game implements GameModel { }); } + async addReactions(message: Message, guildConfig: GuildConfig) { + try { + let gcChanged = false; + const game = cloneDeep(this.data); + const guild = this.discordGuild; + const channel = this.discordChannel; + + if (message) { + try { + if (game.method === GameMethod.AUTOMATED) { + if (this.client) message.react(guildConfig.emojiAdd); + else await ShardManager.shardMessageReact(guild.id, channel.id, message.id, guildConfig.emojiAdd); + } + } catch (err) { + if (!aux.isEmoji(guildConfig.emojiAdd)) { + guildConfig.emojiAdd = "➕"; + gcChanged = true; + if (game.method === GameMethod.AUTOMATED) { + if (this.client) message.react(guildConfig.emojiAdd); + else await ShardManager.shardMessageReact(guild.id, channel.id, message.id, guildConfig.emojiAdd); + } + } + } + if (guildConfig.dropOut) { + try { + if (game.method === GameMethod.AUTOMATED) { + if (this.client) message.react(guildConfig.emojiRemove); + else await ShardManager.shardMessageReact(guild.id, channel.id, message.id, guildConfig.emojiRemove); + } + } catch (err) { + if (!aux.isEmoji(guildConfig.emojiRemove)) { + guildConfig.emojiRemove = "➖"; + gcChanged = true; + if (game.method === GameMethod.AUTOMATED) { + if (this.client) message.react(guildConfig.emojiRemove); + else await ShardManager.shardMessageReact(guild.id, channel.id, message.id, guildConfig.emojiRemove); + } + } + } + } + } + + if (gcChanged) { + guildConfig.save(guildConfig.data); + guildConfig.updateReactions(this.client); + } + } catch (err) { + aux.log(err); + } + } + public getWeekdays() { const days = this.weekdays; const validDays = []; diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 34779fd..0e3f2ea 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -39,19 +39,20 @@ client.on("ready", async () => { if (!isReady) { isReady = true; + if (!connected) connected = await db.database.connect(); + if (connected) { + aux.log("Database connected!"); + } else return; + // Send updated server information to the API sendGuildsToAPI(true); setInterval(() => { - sendGuildsToAPI(true); - }, 30 * 60 * 1000); + sendGuildsToAPI(); + }, 10 * 60 * 1000); if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { - if (!connected) connected = await db.database.connect(); - if (connected) { - aux.log("Database connected!"); - } else return; - refreshMessages(); + // Once per hour, prune games from the database that are more than 48 hours old pruneOldGames(); setInterval(() => { @@ -1331,7 +1332,7 @@ const postReminders = async () => { try { game.reminded = true; - const result = await game.save(true); + const result = await game.save({ force: true }); if (!result.modified) return; } catch (err) { aux.log("RemindedSaveError", game._id, err); @@ -1409,29 +1410,40 @@ const postReminders = async () => { }; const apiGuildIds: any = {}; +let nextShard = 0; const sendGuildsToAPI = (all: boolean = false) => { const guilds = client.guilds.cache.array(); - client.shard.send({ - type: "shard", - name: "guilds", - data: guilds - .filter((guild) => all || !apiGuildIds[guild.shardID] || !apiGuildIds[guild.shardID].includes(guild.id)) - .map((guild) => { - if (!apiGuildIds[guild.shardID]) apiGuildIds[guild.shardID] = []; - if (!apiGuildIds[guild.shardID].includes(guild.id)) apiGuildIds[guild.shardID].push(guild.id); - return { - id: guild.id, - name: guild.name, - icon: guild.icon, - shardID: guild.shardID, - ownerID: guild.ownerID, - members: guild.members.cache.array(), - users: guild.members.cache.array().map((m) => m.user), - memberRoles: guild.members.cache.map((m) => m.roles.cache), - channels: guild.channels.cache.array(), - roles: guild.roles.cache.array(), - }; - }), - }); + const fGuild = guilds[0]; + if (all || fGuild) { + if (all || fGuild.shardID === nextShard) { + aux.log("Refreshing data for shard", all ? "all shards" : nextShard); + client.shard.send({ + type: "shard", + name: "guilds", + data: guilds + .filter((guild) => all || !apiGuildIds[guild.shardID] || !apiGuildIds[guild.shardID].includes(guild.id)) + .map((guild) => { + if (!apiGuildIds[guild.shardID]) apiGuildIds[guild.shardID] = []; + if (!apiGuildIds[guild.shardID].includes(guild.id)) apiGuildIds[guild.shardID].push(guild.id); + return { + id: guild.id, + name: guild.name, + icon: guild.icon, + shardID: guild.shardID, + ownerID: guild.ownerID, + members: guild.members.cache.array(), + users: guild.members.cache.array().map((m) => m.user), + memberRoles: guild.members.cache.map((m) => m.roles.cache), + channels: guild.channels.cache.array(), + roles: guild.roles.cache.array(), + }; + }), + }); + } + if (!all) { + nextShard++; + if (nextShard === client.shard.count) nextShard = 0; + } + } }; From 0a713bdd1254a44960d6f74dc00b60fc2e939f34 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Thu, 4 Jun 2020 16:42:15 -0500 Subject: [PATCH 05/53] Don't sort weekdays based on locale --- src/models/game.ts | 110 ++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index 17bf1fd..7126351 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -560,7 +560,7 @@ export class Game implements GameModel { } } } else { - aux.log(`GameMessageNotPostedError:\n`, game.s, `${msg}\n`, embed); + aux.log(`GameMessageNotPostedError:\n`, 's', game.s, '_id', game._id); } if (this.client) @@ -714,12 +714,65 @@ export class Game implements GameModel { } } + static getNextDate(baseDate: moment.Moment, validDays: string[], frequency: Frequency, monthlyType: MonthlyType, xWeeks: number = 2) { + if (frequency == Frequency.NO_REPEAT) return null; + + let dateGenerator; + let nextDate = baseDate; + + try { + switch (frequency) { + case Frequency.DAILY: + nextDate = moment(baseDate).add(1, "days"); + break; + case Frequency.WEEKLY: // weekly + if (validDays === undefined || validDays.length === 0) break; + dateGenerator = moment(baseDate).recur().every(validDays).daysOfWeek(); + nextDate = dateGenerator.next(1)[0]; + break; + case Frequency.BIWEEKLY: // biweekly + if (validDays === undefined || validDays.length === 0) break; + // this is a compound interval... + dateGenerator = moment(baseDate).recur().every(validDays).daysOfWeek(); + nextDate = dateGenerator.next(1)[0]; + while (nextDate.week() - moment(baseDate).week() < xWeeks) { + // if the next date is in the same week, diff = 0. if it is just next week, diff = 1, so keep going forward. + dateGenerator = moment(nextDate).recur().every(validDays).daysOfWeek(); + nextDate = dateGenerator.next(1)[0]; + } + break; + case Frequency.MONTHLY: + if (monthlyType == MonthlyType.WEEKDAY) { + const weekOfMonth = moment(baseDate).monthWeekByDay(); + const validDay = moment(baseDate).day(); + dateGenerator = moment(baseDate).recur().every(validDay).daysOfWeek().every(weekOfMonth).weeksOfMonthByDay(); + nextDate = dateGenerator.next(1)[0]; + + if (weekOfMonth == 4 && moment(nextDate).month() != moment(baseDate).month() + 1) { + dateGenerator = moment(baseDate).recur().every(validDay).daysOfWeek().every(3).weeksOfMonthByDay(); + nextDate = dateGenerator.next(1)[0]; + } + } else { + nextDate = moment(baseDate).add(1, "month"); + } + break; + default: + throw new Error(`invalid frequency ${frequency} specified`); + } + } catch (err) { + aux.log(err.message || err); + return null; + } + + return moment(nextDate).format("YYYY-MM-DD"); + } + public getWeekdays() { const days = this.weekdays; const validDays = []; for (let i = 0; i < days.length; i++) { if (days[i] == true) { - validDays.push(moment.weekdays(true, i)); + validDays.push(moment.weekdays(false, i)); } } return validDays; @@ -948,59 +1001,6 @@ export class Game implements GameModel { return `${game.date.replace(/-/g, "")}T${game.time.replace(/:/g, "")}00${game.timezone >= 0 ? "+" : "-"}${aux.parseTimeZoneISO(game.timezone)}`; } - static getNextDate(baseDate: moment.Moment, validDays: string[], frequency: Frequency, monthlyType: MonthlyType, xWeeks: number = 2) { - if (frequency == Frequency.NO_REPEAT) return null; - - let dateGenerator; - let nextDate = baseDate; - - try { - switch (frequency) { - case Frequency.DAILY: - nextDate = moment(baseDate).add(1, "days"); - break; - case Frequency.WEEKLY: // weekly - if (validDays === undefined || validDays.length === 0) break; - dateGenerator = moment(baseDate).recur().every(validDays).daysOfWeek(); - nextDate = dateGenerator.next(1)[0]; - break; - case Frequency.BIWEEKLY: // biweekly - if (validDays === undefined || validDays.length === 0) break; - // this is a compound interval... - dateGenerator = moment(baseDate).recur().every(validDays).daysOfWeek(); - nextDate = dateGenerator.next(1)[0]; - while (nextDate.week() - moment(baseDate).week() < xWeeks) { - // if the next date is in the same week, diff = 0. if it is just next week, diff = 1, so keep going forward. - dateGenerator = moment(nextDate).recur().every(validDays).daysOfWeek(); - nextDate = dateGenerator.next(1)[0]; - } - break; - case Frequency.MONTHLY: - if (monthlyType == MonthlyType.WEEKDAY) { - const weekOfMonth = moment(baseDate).monthWeekByDay(); - const validDay = moment(baseDate).day(); - dateGenerator = moment(baseDate).recur().every(validDay).daysOfWeek().every(weekOfMonth).weeksOfMonthByDay(); - nextDate = dateGenerator.next(1)[0]; - - if (weekOfMonth == 4 && moment(nextDate).month() != moment(baseDate).month() + 1) { - dateGenerator = moment(baseDate).recur().every(validDay).daysOfWeek().every(3).weeksOfMonthByDay(); - nextDate = dateGenerator.next(1)[0]; - } - } else { - nextDate = moment(baseDate).add(1, "month"); - } - break; - default: - throw new Error(`invalid frequency ${frequency} specified`); - } - } catch (err) { - aux.log(err.message || err); - return null; - } - - return moment(nextDate).format("YYYY-MM-DD"); - } - async updateReservedList() { let guildMembers: ShardMember[]; let updated = false; From cdce89c64a0f20c297e0d966c240040939f9f151 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Thu, 4 Jun 2020 16:54:11 -0500 Subject: [PATCH 06/53] Role command fix --- src/processes/discord.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 0e3f2ea..49254e7 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -7,6 +7,7 @@ import config from "../models/config"; import aux from "../appaux"; import db from "../db"; import { ShardMember } from "./shard-manager"; +import cloneDeep from "lodash/cloneDeep"; const app: any = { locals: {} }; @@ -580,8 +581,13 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { const role = guild.roles.cache.array().find((r) => r.id === roleId); if (role) roleName = role.name; } - const save: GuildConfigModel = {}; - save.role = roleName == "" ? null : roleName; + const save: GuildConfigModel = { + role: roleName == "" ? null : roleName, + gameTemplates: cloneDeep(guildConfig.gameTemplates).map((gt) => { + if (gt.name === "Default") gt.role = roleName == "" ? null : roleName; + return gt; + }), + }; guildConfig .save(save) .then((result) => { From 8e13de99b4bac30f47edc8449c9aa377da48417e Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Sat, 6 Jun 2020 14:00:59 -0500 Subject: [PATCH 07/53] Development --- src/index.ts | 2 +- src/models/game.ts | 5 +- src/processes/discord.ts | 159 ++++++++++++++++++--------------- src/processes/shard-manager.ts | 4 +- src/routes/api.ts | 92 +++++++++++-------- 5 files changed, 148 insertions(+), 114 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5273382..bf13acf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,7 +25,7 @@ app.use(cookieParser()); app.use(async (req, res, next) => { res.set("Access-Control-Allow-Origin", process.env.HOST); res.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); - res.set("Access-Control-Allow-Headers", "authorization, accept, content-type, origin, x-requested-with, host, origin, referer"); + res.set("Access-Control-Allow-Headers", "authorization, accept, content-type, origin, x-requested-with, host, origin, referer, locale"); next(); }); diff --git a/src/models/game.ts b/src/models/game.ts index 7126351..0e87419 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -560,7 +560,7 @@ export class Game implements GameModel { } } } else { - aux.log(`GameMessageNotPostedError:\n`, 's', game.s, '_id', game._id); + aux.log(`GameMessageNotPostedError:\n`, "s", game.s, "_id", game._id); } if (this.client) @@ -1031,8 +1031,7 @@ export class Game implements GameModel { const reserved = list.split(/\r?\n/); reserved.forEach((r) => { const rsvp: RSVP = { tag: r.trim() }; - const member = guildMembers.find((m) => m.user.tag === r.trim()); - // console.log("url", !!member, JSON.stringify(r.trim()), JSON.stringify(guildMembers.map(m => m.user.tag))); + const member = guildMembers.find((m) => m.user.tag === r.trim().replace("@", "") || m.user.username === r.trim().replace("@", "")); if (member) { rsvp.id = member.user.id; } diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 49254e7..9192d22 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -46,7 +46,7 @@ client.on("ready", async () => { } else return; // Send updated server information to the API - sendGuildsToAPI(true); + sendGuildsToAPI(); setInterval(() => { sendGuildsToAPI(); }, 10 * 60 * 1000); @@ -745,54 +745,58 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { }); client.on("guildMemberAdd", async (member) => { - if (member.user.id === client.user.id) { - sendGuildsToAPI(); - } else { - client.shard.send({ - type: "shard", - name: "guildMemberAdd", - data: member, - user: member.user, - }); - } + client.shard.send({ + type: "shard", + name: "guildMemberAdd", + data: member, + user: { + id: member.user.id, + username: member.user.username, + tag: member.user.tag, + discriminator: member.user.discriminator, + avatar: member.user.avatar, + }, + roles: member.roles.cache.array().map((r) => ({ + id: r.id, + name: r.name, + permissions: r.permissions, + })), + }); }); client.on("guildMemberUpdate", async (oldR, member) => { - if (member.user.id === client.user.id) { - sendGuildsToAPI(); - } else { - client.shard.send({ - type: "shard", - name: "guildMemberUpdate", - data: member, - user: member.user, - roles: member.roles.cache.array(), - }); - } + client.shard.send({ + type: "shard", + name: "guildMemberUpdate", + data: member, + roles: member.roles.cache.array().map((r) => ({ + id: r.id, + name: r.name, + permissions: r.permissions, + })), + }); }); client.on("guildMemberRemove", async (member) => { - if (member.user.id === client.user.id) { - client.shard.send({ - type: "shard", - name: "guildDelete", - data: member.guild.id, - }); - } else { - client.shard.send({ - type: "shard", - name: "guildMemberRemove", - data: member, - user: member.user, - }); - } + client.shard.send({ + type: "shard", + name: "guildMemberRemove", + data: member, + user: member.user, + }); }); client.on("userUpdate", async (oldUser: User, newUser: User) => { client.shard.send({ type: "shard", name: "userUpdate", - data: newUser, + data: { + id: newUser.id, + username: newUser.username, + tag: newUser.tag, + discriminator: newUser.discriminator, + avatar: newUser.avatar, + }, }); if (oldUser.tag != newUser.tag) { @@ -853,6 +857,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { }); client.on("guildDelete", async (guild) => { + aux.log(`Bot has left ${guild.name}!`); client.shard.send({ type: "shard", name: "guildDelete", @@ -860,6 +865,15 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { }); }); + client.on("guildCreate", async (guild) => { + aux.log(`Bot has joined ${guild.name}!`); + client.shard.send({ + type: "shard", + name: "guilds", + data: [guild].map(guildMap), + }); + }); + /** * Discord.JS - messageDelete * Delete the game from the database when the announcement message is deleted @@ -1416,40 +1430,45 @@ const postReminders = async () => { }; const apiGuildIds: any = {}; -let nextShard = 0; -const sendGuildsToAPI = (all: boolean = false) => { +const sendGuildsToAPI = () => { const guilds = client.guilds.cache.array(); - const fGuild = guilds[0]; - if (all || fGuild) { - if (all || fGuild.shardID === nextShard) { - aux.log("Refreshing data for shard", all ? "all shards" : nextShard); - client.shard.send({ - type: "shard", - name: "guilds", - data: guilds - .filter((guild) => all || !apiGuildIds[guild.shardID] || !apiGuildIds[guild.shardID].includes(guild.id)) - .map((guild) => { - if (!apiGuildIds[guild.shardID]) apiGuildIds[guild.shardID] = []; - if (!apiGuildIds[guild.shardID].includes(guild.id)) apiGuildIds[guild.shardID].push(guild.id); - return { - id: guild.id, - name: guild.name, - icon: guild.icon, - shardID: guild.shardID, - ownerID: guild.ownerID, - members: guild.members.cache.array(), - users: guild.members.cache.array().map((m) => m.user), - memberRoles: guild.members.cache.map((m) => m.roles.cache), - channels: guild.channels.cache.array(), - roles: guild.roles.cache.array(), - }; - }), - }); - } - if (!all) { - nextShard++; - if (nextShard === client.shard.count) nextShard = 0; - } + const newGuilds = guilds.filter((guild) => !apiGuildIds[guild.shardID] || !apiGuildIds[guild.shardID].includes(guild.id)); + if (newGuilds.length > 0) { + aux.log("Refreshing data for", newGuilds.length, "guilds"); + client.shard.send({ + type: "shard", + name: "guilds", + data: guilds.filter((guild) => !apiGuildIds[guild.shardID] || !apiGuildIds[guild.shardID].includes(guild.id)).map(guildMap), + }); } }; + +const guildMap = (guild: Guild) => { + if (!apiGuildIds[guild.shardID]) apiGuildIds[guild.shardID] = []; + if (!apiGuildIds[guild.shardID].includes(guild.id)) apiGuildIds[guild.shardID].push(guild.id); + return { + id: guild.id, + name: guild.name, + icon: guild.icon, + shardID: guild.shardID, + ownerID: guild.ownerID, + members: guild.members.cache.array(), + users: guild.members.cache.map((m) => ({ + id: m.user.id, + username: m.user.username, + tag: m.user.tag, + discriminator: m.user.discriminator, + avatar: m.user.avatar, + })), + memberRoles: guild.members.cache.map((m) => + m.roles.cache.map((r) => ({ + id: r.id, + name: r.name, + permissions: r.permissions, + })) + ), + channels: guild.channels.cache.array(), + roles: guild.roles.cache.array(), + }; +}; diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index 43e44c5..1ae2c9e 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -99,6 +99,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { if (g.id !== message.data.guildID) return g; g.members.push(message.data); g.users.push(message.user); + g.memberRoles.push(message.roles); return g; }); } else if (message.name === "guildMemberUpdate") { @@ -107,7 +108,6 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { g.members = g.members.map((c, i) => { if (c.userID === message.data.userID) { g.memberRoles[i] = message.roles; - g.users[i] = message.user; c = message.data; } return c; @@ -120,6 +120,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { const index = g.members.findIndex((c) => c.userID === message.data.userID); if (index < 0) return g; g.members.splice(index, 1); + g.memberRoles.splice(index, 1); g.users.splice(index, 1); return g; }); @@ -335,7 +336,6 @@ const shardGuilds = async (filters: ShardFilters = {}) => { icon: guild.icon, shardID: guild.shardID, members: guild.members.map((member, memberIndex) => { - // console.log(guild.id, memberIndex, guild.users[memberIndex].id) const user = guild.users.find((u) => u.id === member.userID) || {}; return { id: member.userID, diff --git a/src/routes/api.ts b/src/routes/api.ts index 48d7306..0ff46d8 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -42,7 +42,7 @@ export default (options: APIRouteOptions) => { res.locals.url = req.originalUrl; res.locals.env = process.env; - moment.locale(req.app.locals.lang.code); + moment.locale("en"); next(); } catch (err) { @@ -182,7 +182,7 @@ export default (options: APIRouteOptions) => { router.use("/auth-api", async (req, res, next) => { const langs = req.app.locals.langs; - const selectedLang = req.cookies.lang && langs.map((l) => l.code).includes(req.cookies.lang) ? req.cookies.lang : "en"; + const selectedLang = req.headers.locale && langs.map((l) => l.code).includes(req.headers.locale) ? req.headers.locale : "en"; req.app.locals.ip = req.ip || req.headers["x-forwarded-for"] || req.connection.remoteAddress; req.app.locals.lang = merge(cloneDeep(langs.find((lang: any) => lang.code === "en")), cloneDeep(langs.find((lang: any) => lang.code === selectedLang))); @@ -191,7 +191,7 @@ export default (options: APIRouteOptions) => { res.locals.url = req.originalUrl; res.locals.env = process.env; - moment.locale(req.app.locals.lang.code); + moment.locale("en"); const bearer = (req.headers.authorization || "").replace("Bearer ", "").trim(); @@ -248,7 +248,7 @@ export default (options: APIRouteOptions) => { token: req.session.api.access.access_token, account: result.account, user: userSettings.data, - version: apiVersion + version: apiVersion, }); }) .catch((err) => { @@ -256,6 +256,7 @@ export default (options: APIRouteOptions) => { status: "error", token: req.session.api.access.access_token, message: `UserAuthError`, + reauthenticate: true, code: 8, }); }); @@ -263,8 +264,9 @@ export default (options: APIRouteOptions) => { res.json({ status: "error", token: req.session.api.access.access_token, - code: 9, message: `UserAuthError`, + reauthenticate: true, + code: 9, }); } }); @@ -407,7 +409,7 @@ export default (options: APIRouteOptions) => { fetchAccount(req.session.api.access, { client: client, ip: req.app.locals.ip, - guilds: true + guilds: true, }) .then(async (result: any) => { try { @@ -564,7 +566,7 @@ export default (options: APIRouteOptions) => { fetchAccount(req.session.api.access, { client: client, ip: req.app.locals.ip, - guilds: true + guilds: true, }) .then(async (result: any) => { try { @@ -793,26 +795,43 @@ export default (options: APIRouteOptions) => { } }); - router.get("/api/delete-game", async (req, res, next) => { - try { - if (req.query.g) { - const game = await Game.fetch(req.query.g); - if (!game) throw new Error("Game not found"); - game.delete({ sendWS: false }).then((response) => { + router.delete("/auth-api/game", async (req, res, next) => { + fetchAccount(req.session.api.access, { + client: client, + ip: req.app.locals.ip, + }) + .then(async (result: any) => { + try { + if (req.query.g) { + const game = await Game.fetch(req.query.g); + if (!game) throw new Error("Game not found"); + game.delete({ sendWS: false }).then((response) => { + res.json({ + status: "success", + token: req.session.api.access.access_token, + gameId: req.query.g, + }); + }); + } else { + throw new Error("Game not found"); + } + } catch (err) { res.json({ - status: "success", + status: "error", + token: req.session.api.access.access_token, + message: `DeleteGameError`, + code: 30, }); + } + }) + .catch((err) => { + res.json({ + status: "error", + token: req.session.api.access.access_token, + message: `UserAuthError`, + code: 31, }); - } else { - throw new Error("Game not found"); - } - } catch (err) { - res.json({ - status: "error", - message: err.message || err, }); - res.render("error", { message: "routes/game.ts:2:
" + err }); - } }); router.post("/auth-api/rsvp", async (req, res, next) => { @@ -1074,7 +1093,7 @@ const fetchAccount = (token: any, options: AccountOptions) => { config: new GuildConfig({ guild: guild.id }), games: [], }; - // console.log(guild.channels.filter(c => c instanceof TextChannel)) + guild.members.forEach((member) => { if (member.id === id) { guildInfo.member = member; @@ -1120,7 +1139,7 @@ const fetchAccount = (token: any, options: AccountOptions) => { } if (member) { - guild.userRoles = guild.roles.filter((r) => member.roles.find((mr) => mr.id === r.id)).map((r) => r.name); + guild.userRoles = member.roles.map((r) => r.name); guild.isAdmin = !!( member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || member.roles.find((r) => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) @@ -1253,28 +1272,25 @@ const fetchAccount = (token: any, options: AccountOptions) => { }); } - account.guilds = account.guilds.map((guild) => { - guild.games.sort((a, b) => { - return a.timestamp < b.timestamp ? -1 : 1; - }); - return guild; - }); - - if (options.page === GamesPages.Upcoming || options.page === GamesPages.MyGames) { - account.guilds.sort((a, b) => { + account.guilds = account.guilds + .map((guild) => { + guild.games.sort((a, b) => { + return a.timestamp < b.timestamp ? -1 : 1; + }); + return guild; + }) + .sort((a, b) => { if (a.games.length === 0 && b.games.length === 0) return a.name < b.name ? -1 : 1; + if (a.games.length > 0 && b.games.length > 0) return a.name < b.name ? -1 : 1; if (a.games.length === 0) return 1; if (b.games.length === 0) return -1; - - return a.games[0].timestamp < b.games[0].timestamp ? -1 : 1; }); - } } return resolve({ status: "success", account: account, - sGuilds: sGuilds + sGuilds: sGuilds, }); } throw new Error(`OAuth: ${error}`); From f56fcff79b7c3718ca09a510908737c644bea455 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Mon, 8 Jun 2020 08:37:56 -0500 Subject: [PATCH 08/53] development --- src/models/game.ts | 15 +- src/processes/discord.ts | 461 +++++++++++++++++---------------- src/processes/shard-manager.ts | 2 + src/routes/api.ts | 11 +- 4 files changed, 254 insertions(+), 235 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index 0e87419..5a25ffe 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -277,7 +277,7 @@ export class Game implements GameModel { }; } - async save(options?: GameSaveOptions) { + async save(options: GameSaveOptions = {}) { if (!connection()) { aux.log("No database connection"); return null; @@ -360,15 +360,20 @@ export class Game implements GameModel { let reserved: string[] = []; let waitlist: string[] = []; let rMentions: string[] = []; - game.reserved.forEach((rsvp, i) => { + game.reserved.map((rsvp) => { if (rsvp.tag.trim().length === 0 && !rsvp.id) return; - let member = guildMembers.find((mem) => mem.user.tag.trim() === rsvp.tag.trim() || mem.user.id === rsvp.id); + let member = guildMembers.find( + (mem) => mem.user.tag.trim() === rsvp.tag.trim().replace("@", "") || mem.user.id == rsvp.tag.trim().replace(/[<@>]/g, "") || mem.user.id === rsvp.id + ); let name = rsvp.tag.trim().replace(/\#\d{4}/, ""); if (member) { if (guildConfig.embeds === false || guildConfig.embedMentions) name = member.user.toString(); else name = member.nickname || member.user.username; - rsvp.tag = member.user.tag; + rsvp = { + id: member.user.id, + tag: member.user.tag, + }; } if (reserved.length < parseInt(game.players)) { @@ -377,6 +382,8 @@ export class Game implements GameModel { } else { waitlist.push(reserved.length + waitlist.length + 1 + ". " + name); } + + return rsvp; }); const eventTimes = aux.parseEventTimes(game, { diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 9192d22..82367c2 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -1,4 +1,4 @@ -import { TextChannel, Client, Message, User, GuildChannel, MessageEmbed, Permissions, Guild, Channel } from "discord.js"; +import { TextChannel, Client, Message, User, GuildChannel, MessageEmbed, Permissions, Guild } from "discord.js"; import { DeleteWriteOpResultObject, FilterQuery, ObjectId, UpdateWriteOpResult } from "mongodb"; import { GuildConfig, GuildConfigModel } from "../models/guild-config"; @@ -40,16 +40,13 @@ client.on("ready", async () => { if (!isReady) { isReady = true; - if (!connected) connected = await db.database.connect(); - if (connected) { - aux.log("Database connected!"); - } else return; - - // Send updated server information to the API - sendGuildsToAPI(); - setInterval(() => { + if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { + // Send updated server information to the API sendGuildsToAPI(); - }, 10 * 60 * 1000); + setInterval(() => { + sendGuildsToAPI(); + }, 10 * 60 * 1000); + } if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { refreshMessages(); @@ -75,6 +72,219 @@ client.on("ready", async () => { } }); +if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { + client.on("channelCreate", async (newC: any) => { + if (newC.type !== "text") return; + const channel: TextChannel = newC; + client.shard.send({ + type: "shard", + name: "channelCreate", + data: channel, + }); + }); + + client.on("channelUpdate", async (oldC, newC: any) => { + if (newC.type !== "text") return; + const channel: TextChannel = newC; + client.shard.send({ + type: "shard", + name: "channelUpdate", + data: { + id: channel.id, + type: channel.type, + name: channel.name, + parentID: channel.parentID, + members: channel.members.map(m => m.user.id) + }, + }); + }); + + client.on("channelDelete", async (channel: GuildChannel) => { + client.shard.send({ + type: "shard", + name: "channelDelete", + data: channel.id, + }); + + const channelId = channel.id; + const guildId = channel.guild.id; + const guildConfig = await GuildConfig.fetch(guildId); + if (guildConfig.channels.find((c) => c.channelId === channelId)) { + guildConfig.channel.splice( + guildConfig.channel.findIndex((c) => c.channelId === channelId), + 1 + ); + await guildConfig.save(); + + const games = await Game.fetchAllBy( + { + s: guildId, + c: channelId, + }, + client + ); + + games.forEach(async (game) => { + await game.delete(); + }); + } + }); + + client.on("roleCreate", async (role) => { + client.shard.send({ + type: "shard", + name: "roleCreate", + data: role, + }); + }); + + client.on("roleUpdate", async (oldR, role) => { + client.shard.send({ + type: "shard", + name: "roleUpdate", + data: role, + }); + }); + + client.on("roleDelete", async (role) => { + client.shard.send({ + type: "shard", + name: "roleDelete", + data: role.id, + }); + }); + + client.on("guildMemberAdd", async (member) => { + client.shard.send({ + type: "shard", + name: "guildMemberAdd", + data: member, + user: { + id: member.user.id, + username: member.user.username, + tag: member.user.tag, + discriminator: member.user.discriminator, + avatar: member.user.avatar, + }, + roles: member.roles.cache.array().map((r) => ({ + id: r.id, + name: r.name, + permissions: r.permissions, + })), + }); + }); + + client.on("guildMemberUpdate", async (oldR, member) => { + client.shard.send({ + type: "shard", + name: "guildMemberUpdate", + data: member, + roles: member.roles.cache.array().map((r) => ({ + id: r.id, + name: r.name, + permissions: r.permissions, + })), + }); + }); + + client.on("guildMemberRemove", async (member) => { + client.shard.send({ + type: "shard", + name: "guildMemberRemove", + data: member, + user: member.user, + }); + }); + + client.on("userUpdate", async (oldUser: User, newUser: User) => { + client.shard.send({ + type: "shard", + name: "userUpdate", + data: { + id: newUser.id, + username: newUser.username, + tag: newUser.tag, + discriminator: newUser.discriminator, + avatar: newUser.avatar, + }, + }); + + if (process.env.DISCORD_LOGIC.toLowerCase() === "true" && oldUser.tag != newUser.tag) { + const games = await Game.fetchAllBy( + { + $or: [ + { "author.tag": oldUser.tag }, + { "author.id": oldUser.id }, + { "dm.tag": oldUser.tag }, + { "dm.id": oldUser.id }, + { dm: oldUser.tag }, + { + reserved: { + $elemMatch: { + tag: oldUser.tag, + }, + }, + }, + { + reserved: { + $elemMatch: { + id: oldUser.id, + }, + }, + }, + { + reserved: { + $regex: oldUser.tag.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"), + }, + }, + ], + }, + client + ); + games.forEach(async (game) => { + if (game.author.tag === oldUser.tag) game.author.tag = newUser.tag; + if (game.author.tag === oldUser.tag) game.author.id = newUser.id; + if (game.dm.tag === oldUser.tag) game.dm.tag = newUser.tag; + if (game.dm.tag === oldUser.tag) game.dm.id = newUser.id; + if (game.reserved.find((r) => r.tag === oldUser.tag || r.id === oldUser.id)) { + game.reserved = game.reserved.map((r) => { + if (r.tag === oldUser.tag) r.tag = newUser.tag; + if (r.id === oldUser.id) r.id = newUser.id; + return r; + }); + } + game.save(); + }); + } + }); + + client.on("guildUpdate", async (oldG, newG) => { + client.shard.send({ + type: "shard", + name: "guildUpdate", + data: newG, + }); + }); + + client.on("guildDelete", async (guild) => { + aux.log(`Bot has left ${guild.name}!`); + client.shard.send({ + type: "shard", + name: "guildDelete", + data: guild.id, + }); + }); + + client.on("guildCreate", async (guild) => { + aux.log(`Bot has joined ${guild.name}!`); + client.shard.send({ + type: "shard", + name: "guilds", + data: [guild].map(guildMap), + }); + }); +} + if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { /** * Discord.JS - message @@ -457,18 +667,18 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { }; if (colors[color]) { color = colors[color]; - } else if (!color.match(/[0-9a-f]{3}|[0-9a-f]{6}/i)) { + } else if (!color.match(/[0-9a-f]{6}|[0-9a-f]{3}/i)) { (message.channel).send(lang.config.desc.EMBED_COLOR_ERROR); return; } const save: GuildConfigModel = {}; - save.embedColor = "#" + color.match(/[0-9a-f]{3}|[0-9a-f]{6}/i)[0]; + save.embedColor = "#" + color.match(/[0-9a-f]{6}|[0-9a-f]{3}/i)[0]; guildConfig .save(save) .then((result) => { let embed = new MessageEmbed() - .setColor("#" + color.match(/[0-9a-f]{3}|[0-9a-f]{6}/i)[0]) - .setDescription(`${lang.config.EMBED_COLOR_SET} \`#${color.match(/[0-9a-f]{3}|[0-9a-f]{6}/i)[0]}\`.`); + .setColor("#" + color.match(/[0-9a-f]{6}|[0-9a-f]{3}/i)[0]) + .setDescription(`${lang.config.EMBED_COLOR_SET} \`#${color.match(/[0-9a-f]{6}|[0-9a-f]{3}/i)[0]}\`.`); (message.channel).send(embed); }) .catch((err) => { @@ -669,211 +879,6 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } catch (err) {} }); - client.on("channelCreate", async (newC: any) => { - if (newC.type !== "text") return; - const channel: TextChannel = newC; - client.shard.send({ - type: "shard", - name: "channelCreate", - data: channel, - }); - }); - - client.on("channelUpdate", async (oldC, newC: any) => { - if (newC.type !== "text") return; - const channel: TextChannel = newC; - client.shard.send({ - type: "shard", - name: "channelUpdate", - data: channel, - }); - }); - - client.on("channelDelete", async (channel: GuildChannel) => { - client.shard.send({ - type: "shard", - name: "channelDelete", - data: channel.id, - }); - - const channelId = channel.id; - const guildId = channel.guild.id; - const guildConfig = await GuildConfig.fetch(guildId); - if (guildConfig.channels.find((c) => c.channelId === channelId)) { - guildConfig.channel.splice( - guildConfig.channel.findIndex((c) => c.channelId === channelId), - 1 - ); - await guildConfig.save(); - - const games = await Game.fetchAllBy( - { - s: guildId, - c: channelId, - }, - client - ); - - games.forEach(async (game) => { - await game.delete(); - }); - } - }); - - client.on("roleCreate", async (role) => { - client.shard.send({ - type: "shard", - name: "roleCreate", - data: role, - }); - }); - - client.on("roleUpdate", async (oldR, role) => { - client.shard.send({ - type: "shard", - name: "roleUpdate", - data: role, - }); - }); - - client.on("roleDelete", async (role) => { - client.shard.send({ - type: "shard", - name: "roleDelete", - data: role.id, - }); - }); - - client.on("guildMemberAdd", async (member) => { - client.shard.send({ - type: "shard", - name: "guildMemberAdd", - data: member, - user: { - id: member.user.id, - username: member.user.username, - tag: member.user.tag, - discriminator: member.user.discriminator, - avatar: member.user.avatar, - }, - roles: member.roles.cache.array().map((r) => ({ - id: r.id, - name: r.name, - permissions: r.permissions, - })), - }); - }); - - client.on("guildMemberUpdate", async (oldR, member) => { - client.shard.send({ - type: "shard", - name: "guildMemberUpdate", - data: member, - roles: member.roles.cache.array().map((r) => ({ - id: r.id, - name: r.name, - permissions: r.permissions, - })), - }); - }); - - client.on("guildMemberRemove", async (member) => { - client.shard.send({ - type: "shard", - name: "guildMemberRemove", - data: member, - user: member.user, - }); - }); - - client.on("userUpdate", async (oldUser: User, newUser: User) => { - client.shard.send({ - type: "shard", - name: "userUpdate", - data: { - id: newUser.id, - username: newUser.username, - tag: newUser.tag, - discriminator: newUser.discriminator, - avatar: newUser.avatar, - }, - }); - - if (oldUser.tag != newUser.tag) { - const games = await Game.fetchAllBy( - { - $or: [ - { "author.tag": oldUser.tag }, - { "author.id": oldUser.id }, - { "dm.tag": oldUser.tag }, - { "dm.id": oldUser.id }, - { dm: oldUser.tag }, - { - reserved: { - $elemMatch: { - tag: oldUser.tag, - }, - }, - }, - { - reserved: { - $elemMatch: { - id: oldUser.id, - }, - }, - }, - { - reserved: { - $regex: oldUser.tag.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"), - }, - }, - ], - }, - client - ); - games.forEach(async (game) => { - if (game.author.tag === oldUser.tag) game.author.tag = newUser.tag; - if (game.author.tag === oldUser.tag) game.author.id = newUser.id; - if (game.dm.tag === oldUser.tag) game.dm.tag = newUser.tag; - if (game.dm.tag === oldUser.tag) game.dm.id = newUser.id; - if (game.reserved.find((r) => r.tag === oldUser.tag || r.id === oldUser.id)) { - game.reserved = game.reserved.map((r) => { - if (r.tag === oldUser.tag) r.tag = newUser.tag; - if (r.id === oldUser.id) r.id = newUser.id; - return r; - }); - } - game.save(); - }); - } - }); - - client.on("guildUpdate", async (oldG, newG) => { - client.shard.send({ - type: "shard", - name: "guildUpdate", - data: newG, - }); - }); - - client.on("guildDelete", async (guild) => { - aux.log(`Bot has left ${guild.name}!`); - client.shard.send({ - type: "shard", - name: "guildDelete", - data: guild.id, - }); - }); - - client.on("guildCreate", async (guild) => { - aux.log(`Bot has joined ${guild.name}!`); - client.shard.send({ - type: "shard", - name: "guilds", - data: [guild].map(guildMap), - }); - }); - /** * Discord.JS - messageDelete * Delete the game from the database when the announcement message is deleted @@ -960,9 +965,9 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } (async () => { - let connected = await db.database.connect(); + if (!connected) connected = await db.database.connect(); if (connected) { - aux.log("Database connected!"); + aux.log("Database connected in shard!"); // Login the Discord bot client.login(process.env.TOKEN); } @@ -1322,9 +1327,11 @@ const postReminders = async () => { var where = Game.parseDiscord(game.where, game.discordGuild); game.reserved.forEach((rsvp) => { if (rsvp.tag.length === 0) return; - let member = guildMembers.find((mem) => mem.user.tag === rsvp.tag.replace("@", "") || rsvp.id === mem.user.id); + let member = guildMembers.find( + (mem) => mem.user.tag.trim() === rsvp.tag.trim().replace("@", "") || mem.user.id == rsvp.tag.trim().replace(/[<@>]/g, "") || mem.user.id === rsvp.id + ); - let name = rsvp.tag.replace("@", ""); + let name = rsvp.tag; if (member) name = member.user.toString(); if (member) reservedUsers.push(member); @@ -1468,7 +1475,13 @@ const guildMap = (guild: Guild) => { permissions: r.permissions, })) ), - channels: guild.channels.cache.array(), + channels: guild.channels.cache.array().map(c => ({ + id: c.id, + type: c.type, + name: c.name, + parentID: c.parentID, + members: c.members.map(m => m.user.id) + })), roles: guild.roles.cache.array(), }; }; diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index 1ae2c9e..3c5351d 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -182,6 +182,8 @@ export interface ShardChannel { id: string; name: string; type: string; + parentID: string; + members: string[]; messages: { fetch: (messageId: string) => Promise; }; diff --git a/src/routes/api.ts b/src/routes/api.ts index 0ff46d8..415dcce 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1240,13 +1240,10 @@ const fetchAccount = (token: any, options: AccountOptions) => { const games: any[] = []; for (let i = 0; i < fGames.length; i++) { const game = fGames[i]; - // const dc = game.discordChannel; - // if (dc) { - // const perm = await dc.permissionsFor(id, Permissions.FLAGS.VIEW_CHANNEL); - // if (perm) { - games.push(game); - // } - // } + const dc = game.discordChannel; + if (dc && dc.members.includes(id)) { + games.push(game); + } } games.forEach(async (game) => { if (!game.discordGuild) return; From b2032a7160813a016a485bed44b5d9251825f6e3 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Mon, 8 Jun 2020 22:11:15 -0500 Subject: [PATCH 09/53] Signup Improvements --- src/models/game.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index 5a25ffe..979aa92 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -931,13 +931,13 @@ export class Game implements GameModel { return result; } - async dmCustomInstructions(tag: string) { + async dmCustomInstructions(user: User) { if (this.method === "automated" && this.customSignup.trim().length > 0 && this.discordGuild) { const guild = this.discordGuild; const guildMembers = await guild.members; const guildConfig = await GuildConfig.fetch(guild.id); const dmmember = guildMembers.find((m) => m.user.tag === this.dm.tag.trim() || m.user.id === this.dm.id); - const member = guildMembers.find((m) => m.user.tag === tag.trim()); + const member = guildMembers.find((m) => m.user.tag === user.tag.trim() || m.user.id === user.id); if (!this.template) this.template = (guildConfig.gameTemplates.find((gt) => gt.isDefault) || guildConfig.gameTemplates[0]).id; const gameTemplate = guildConfig.gameTemplates.find((gt) => gt.id === this.template); @@ -1066,8 +1066,10 @@ export class Game implements GameModel { if (!this.reserved.find((r) => r.id === user.id || r.tag == user.tag) && hourDiff < 0) { this.reserved.push({ id: user.id, tag: user.tag }); this.save(); - this.dmCustomInstructions(user.tag); + this.dmCustomInstructions(user); + return true; } + return false; } async dropOut(user: User, guildConfig: GuildConfig) { @@ -1075,7 +1077,9 @@ export class Game implements GameModel { if (this.reserved.find((r) => r.tag === user.tag || r.id === user.id) && guildConfig.dropOut && hourDiff < 0) { this.reserved = this.reserved.filter((r) => r.tag && r.tag !== user.tag && !(r.id && r.id === user.id)); this.save(); + return true; } + return false; } static parseDiscord(text: string, guild: ShardGuild, getMentions: boolean = false) { From a0961dbcfb8c142385cf1d75aed2208022ffb723 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Mon, 8 Jun 2020 22:11:39 -0500 Subject: [PATCH 10/53] Performance Improvements --- src/processes/discord.ts | 15 +++++++++++--- src/processes/shard-manager.ts | 26 ++++++++++++----------- src/routes/api.ts | 38 +++++++++++++++++----------------- 3 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 82367c2..034566d 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -37,6 +37,7 @@ client.on("debug", function (info) { */ client.on("ready", async () => { aux.log(`Logged in as ${client.user.username}!`); + if (!isReady) { isReady = true; @@ -69,6 +70,13 @@ client.on("ready", async () => { postReminders(); }, 1 * 60 * 1000); // 1 minute } + + // If client is the dev bot, leave any server that Sillvva is not part of. + // if (client.user.id === "532635202808315906") { + // client.guilds.cache.forEach((guild) => { + // if (!guild.members.cache.find((m) => m.user.id === "202640192178225152")) guild.leave(); + // }); + // } } }); @@ -94,7 +102,8 @@ if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { type: channel.type, name: channel.name, parentID: channel.parentID, - members: channel.members.map(m => m.user.id) + members: channel.members.map((m) => m.user.id), + everyone: channel.permissionsFor(channel.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), }, }); }); @@ -1475,12 +1484,12 @@ const guildMap = (guild: Guild) => { permissions: r.permissions, })) ), - channels: guild.channels.cache.array().map(c => ({ + channels: guild.channels.cache.array().map((c) => ({ id: c.id, type: c.type, name: c.name, parentID: c.parentID, - members: c.members.map(m => m.user.id) + members: c.members.map((m) => m.user.id), })), roles: guild.roles.cache.array(), }; diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index 3c5351d..4156ee4 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -1,4 +1,4 @@ -import { ShardingManager, Role, MessageEmbed, Message, Client, TextChannel } from "discord.js"; +import { ShardingManager, Role, MessageEmbed, Message, Client, TextChannel, Permissions } from "discord.js"; import { Express } from "express"; import { io } from "./socket"; @@ -141,7 +141,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { return g; }); } else if (message.name === "guildDelete") { - const index = guildData.findIndex(g => g.id === message.data); + const index = guildData.findIndex((g) => g.id === message.data); if (index >= 0) { guildData.splice(index, 1); } @@ -184,6 +184,7 @@ export interface ShardChannel { type: string; parentID: string; members: string[]; + everyone: boolean; messages: { fetch: (messageId: string) => Promise; }; @@ -205,11 +206,6 @@ const clientGuilds = async (client: Client, guildIds: string[] = []) => { try { const guilds = client.guilds.cache.array().filter((g) => (guildIds.length > 0 ? guildIds.includes(g.id) : true)); const shards = [guilds]; - const sGuildMembers = [guilds.map((g) => g.members.cache.array())]; - const sGuildUsers = [guilds.map((g) => g.members.cache.array().map((m) => m.user))]; - const sGuildChannels = [guilds.map((g) => g.channels.cache.array())]; - const sGuildRoles = [guilds.map((g) => g.roles.cache.array())]; - const sGuildMemberRoles = [guilds.map((g) => g.members.cache.array().map((m) => m.roles.cache.array()))]; const result = shards.reduce((iter, shard, shardIndex) => { const append = shard.map((guild, guildIndex) => { const sGuild: ShardGuild = { @@ -217,8 +213,8 @@ const clientGuilds = async (client: Client, guildIds: string[] = []) => { name: guild.name, icon: guild.icon, shardID: guild.shardID, - members: sGuildMembers[shardIndex][guildIndex].map((member, memberIndex) => { - const user = sGuildUsers[shardIndex][guildIndex][memberIndex]; + members: guild.members.cache.array().map((member, memberIndex) => { + const user = member.user; return { id: user.id, nickname: member.nickname, @@ -231,7 +227,7 @@ const clientGuilds = async (client: Client, guildIds: string[] = []) => { avatarUrl: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=128`, toString: () => `<@${user.id}>`, }, - roles: sGuildMemberRoles[shardIndex][guildIndex][memberIndex], + roles: member.roles.cache.array(), isOwner: user.id === guild.ownerID, hasPermission: function (permission: number) { if (this.isOwner) return true; @@ -254,11 +250,14 @@ const clientGuilds = async (client: Client, guildIds: string[] = []) => { }, }; }), - channels: sGuildChannels[shardIndex][guildIndex].map((channel, channelIndex) => { + channels: guild.channels.cache.array().map((channel, channelIndex) => { const sChannel: ShardChannel = { id: channel.id, name: channel.name, type: channel.type, + parentID: channel.parentID, + members: channel.members.map((m) => m.user.id), + everyone: channel.permissionsFor(guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), messages: { fetch: async function (messageId: string) { const sGuild = client.guilds.cache.get(guild.id); @@ -297,7 +296,7 @@ const clientGuilds = async (client: Client, guildIds: string[] = []) => { }; return sChannel; }), - roles: sGuildRoles[shardIndex][guildIndex], + roles: guild.roles.cache.array(), }; return sGuild; }); @@ -382,6 +381,9 @@ const shardGuilds = async (filters: ShardFilters = {}) => { id: channel.id, name: channel.name, type: channel.type, + parentID: channel.parentID, + members: channel.members, + everyone: channel.everyone, messages: { fetch: async function (messageId: string) { const call = ` diff --git a/src/routes/api.ts b/src/routes/api.ts index 415dcce..4f27985 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -845,19 +845,27 @@ export default (options: APIRouteOptions) => { ip: req.app.locals.ip, }) .then(async (result: any) => { + let rsvp = false; if (!game.reserved.find((r) => r.id === result.account.user.id || r.tag === result.account.user.tag)) { - await game.signUp(result.account.user); + rsvp = await game.signUp(result.account.user); } else { const guildConfig = await GuildConfig.fetch(game.s); - await game.dropOut(result.account.user, guildConfig); + rsvp = await game.dropOut(result.account.user, guildConfig); } - res.json({ - status: "success", - token: token, - gameId: game._id, - reserved: game.reserved, - }); + if (rsvp) + res.json({ + status: "success", + token: token, + gameId: game._id, + reserved: game.reserved, + }); + else + res.json({ + status: "success", + token: token, + past: true, + }); }) .catch((err) => { res.json({ @@ -1130,7 +1138,7 @@ const fetchAccount = (token: any, options: AccountOptions) => { if (gcChannels.length == 0 || !guild.channels.find((gc) => !!gcChannels.find((c) => gc.id === c.channelId))) { let firstChannel: ShardChannel; for (let i = 0; i < guild.channels.length; i++) { - const pf = await guild.channels[i].permissionsFor(guild.roles.find((r) => r.name === "@everyone").id, Permissions.FLAGS.VIEW_CHANNEL); + const pf = await guild.channels[i].everyone; if (pf) firstChannel = guild.channels[i]; } if (firstChannel && guild.channels.length > 0) { @@ -1150,12 +1158,10 @@ const fetchAccount = (token: any, options: AccountOptions) => { let channels: ShardChannel[] = []; for (let gci = 0; gci < gcChannels.length; gci++) { const gcc = guild.channels.filter((gc: ShardChannel) => gc.id == gcChannels[gci].channelId); - const gccf: ShardChannel[] = []; for (let gcci = 0; gcci < gcc.length; gcci++) { - const pm = await gcc[gcci].permissionsFor(id, Permissions.FLAGS.VIEW_CHANNEL); - if (pm) gccf.push(gcc[gcci]); + const pm = gcc[gcci].members.includes(id); + if (pm) channels.push(gcc[gcci]); } - gccf.forEach((gc) => channels.push(gc)); } channels = channels.filter((c) => member && (guild.isAdmin || !!guildConfig.shardMemberHasPermission(member, c.id))); @@ -1164,10 +1170,6 @@ const fetchAccount = (token: any, options: AccountOptions) => { account.guilds[gi] = guild; } - // if (options.page === GamesPages.Server && !options.search) { - // account.guilds = account.guilds.filter((g) => account.guilds.find((s) => s.id === g.id && (s.isAdmin || config.author == tag))); - // } - if (options.games) { const gameOptions: any = { s: { @@ -1247,8 +1249,6 @@ const fetchAccount = (token: any, options: AccountOptions) => { } games.forEach(async (game) => { if (!game.discordGuild) return; - await game.updateReservedList(); - const date = Game.ISOGameDate(game); const parsed = aux.parseEventTimes(game); const gi = account.guilds.findIndex((g) => g.id === game.s); From 472bb84ad7fe99b2933cdb7496629ee94a1a26d4 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Mon, 8 Jun 2020 22:14:59 -0500 Subject: [PATCH 11/53] Remove excess logs --- src/models/game.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index 979aa92..c457ef2 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -290,8 +290,8 @@ export class Game implements GameModel { const guild = this._guild; if (!guild) { - aux.log(`Server (${game.s}) not found`); - aux.log(JSON.stringify(this.data)); + aux.log(`Server (${game.s}) not found when saving game (${this._id})`); + // aux.log(JSON.stringify(this.data)); } const guildConfig = await GuildConfig.fetch(guild.id); From 7d17a537d4d70353f12acd01fa3596941633dd2e Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 9 Jun 2020 08:13:37 -0500 Subject: [PATCH 12/53] Don't prune rescheduled games --- src/processes/discord.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 034566d..a153c7e 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -1101,6 +1101,7 @@ const pruneOldGames = async (guild?: Guild) => { timestamp: { $lt: new Date().getTime() - 48 * 3600 * 1000, }, + frequency: "0", hideDate: { $in: [false, null], }, From 06d5864d707d49e5641e5ca79ea2e51dd3571361 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 16 Jun 2020 14:28:36 -0500 Subject: [PATCH 13/53] Performance Improvements and Bug Fixes --- lang/es.json | 2 +- src/models/game.ts | 81 ++++++++--- src/processes/discord.ts | 102 ++++++++------ src/processes/shard-manager.ts | 44 +++--- src/routes/api.ts | 247 +++++++++++++++++++++++++++++++-- src/routes/rss.ts | 2 +- 6 files changed, 378 insertions(+), 100 deletions(-) diff --git a/lang/es.json b/lang/es.json index 631459f..32279cb 100644 --- a/lang/es.json +++ b/lang/es.json @@ -197,7 +197,7 @@ "RESCHEDULE_MODE": "Options: `repost` (default, crea u nuevo anuncio), `update` (actualiza el post original)" } }, - "messages": { + "other": { "DM_WAITLIST": "Estas ahora mismo en el slot :NUM de reservados.", "DM_INSTRUCTIONS": "Mensaje para el :DM for :EVENT", "MAINTENANCE": "The site will be under maintenance :TIME for approximately :DURATION hour(s).", diff --git a/src/models/game.ts b/src/models/game.ts index c457ef2..55af75c 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -287,11 +287,17 @@ export class Game implements GameModel { try { let channel = this._channel; - const guild = this._guild; + let guild = this._guild; + + if (this.client && !guild) { + const sGuilds = await ShardManager.clientGuilds(this.client); + guild = sGuilds.find((g) => g.id === game.s); + } if (!guild) { aux.log(`Server (${game.s}) not found when saving game (${this._id})`); // aux.log(JSON.stringify(this.data)); + return; } const guildConfig = await GuildConfig.fetch(guild.id); @@ -339,10 +345,6 @@ export class Game implements GameModel { dmmember = { id: game.author.id, nickname: null, - send: (content, options) => {}, - hasPermission: (permission: number) => { - return false; - }, roles: [], user: { id: game.author.id, @@ -352,6 +354,10 @@ export class Game implements GameModel { avatar: "", avatarUrl: "", }, + send: (content, options) => {}, + hasPermission: (permission: number) => { + return false; + }, }; } @@ -546,7 +552,7 @@ export class Game implements GameModel { this.addReactions(message, guildConfig); } catch (err) { - aux.log("InsertGameError:", game.s, err); + aux.log("InsertGameError:", "game.s", game.s, "game._id", game._id, err.message); } let updated; @@ -619,7 +625,9 @@ export class Game implements GameModel { return null; } const query: mongodb.FilterQuery = aux.fromEntries([[key, value]]); - const game: GameModel = await connection().collection(collection).findOne(query); + const game: GameModel = await connection() + .collection(collection) + .findOne({ deleted: { $in: [null, false] }, ...query }); const guilds = client ? await ShardManager.clientGuilds(client, [game.s]) : await ShardManager.shardGuilds({ guildIds: [game.s] }); return game ? new Game(game, guilds, client) : null; } @@ -629,7 +637,10 @@ export class Game implements GameModel { aux.log("No database connection"); return []; } - const games: GameModel[] = await connection().collection(collection).find(query).toArray(); + const games: GameModel[] = await connection() + .collection(collection) + .find({ deleted: { $in: [null, false] }, ...query }) + .toArray(); const out: Game[] = []; for (let i = 0; i < games.length; i++) { const guilds = sGuilds ? sGuilds : client ? await ShardManager.clientGuilds(client, [games[i].s]) : await ShardManager.shardGuilds({ guildIds: [games[i].s] }); @@ -643,7 +654,11 @@ export class Game implements GameModel { aux.log("No database connection"); return []; } - const games: GameModel[] = await connection().collection(collection).find(query).limit(limit).toArray(); + const games: GameModel[] = await connection() + .collection(collection) + .find({ deleted: { $in: [null, false] }, ...query }) + .limit(limit) + .toArray(); const out: Game[] = []; for (let i = 0; i < games.length; i++) { const guilds = client ? await ShardManager.clientGuilds(client, [games[i].s]) : await ShardManager.shardGuilds({ guildIds: [games[i].s] }); @@ -652,12 +667,24 @@ export class Game implements GameModel { return out; } - static async deleteAllBy(query: mongodb.FilterQuery) { + static async softDeleteAllBy(query: mongodb.FilterQuery) { + if (!connection()) { + aux.log("No database connection"); + return null; + } + return await connection() + .collection(collection) + .updateMany({ deleted: { $in: [null, false] }, ...query }, { $set: { deleted: true } }); + } + + static async hardDeleteAllBy(query: mongodb.FilterQuery) { if (!connection()) { aux.log("No database connection"); return null; } - return await connection().collection(collection).deleteMany(query); + return await connection() + .collection(collection) + .deleteMany({ deleted: { $nin: [null, false] }, ...query }); } static async updateAllBy(query: mongodb.FilterQuery, update: any) { @@ -832,12 +859,14 @@ export class Game implements GameModel { delete data.reminderMessageId; const game = new Game(data, guilds, this.client); const newGame = await game.save(); - const del = await this.delete(); - if (del.deletedCount == 0) { - const del2 = await Game.softDelete(id); - if (del2.deletedCount == 0) { - this.rescheduled = true; - await this.save(); + if (newGame.message && newGame.modified) { + const del = await this.delete(); + if (del.modifiedCount == 0) { + const del2 = await Game.softDelete(id); + if (del2.modifiedCount == 0) { + this.rescheduled = true; + await this.save(); + } } } if (this.client) @@ -855,16 +884,22 @@ export class Game implements GameModel { } } + static async hardDelete(_id: string | number | mongodb.ObjectID) { + return await connection() + .collection(collection) + .deleteOne({ _id: new ObjectId(_id), deleted: { $nin: [null, false] } }); + } + static async softDelete(_id: string | number | mongodb.ObjectID) { return await connection() .collection(collection) - .deleteOne({ _id: new ObjectId(_id) }); + .updateOne({ _id: new ObjectId(_id) }, { $set: { deleted: true } }); } async delete(options: any = {}) { if (!connection()) { aux.log("No database connection"); - return { deletedCount: 0 }; + return { modifiedCount: 0 }; } try { @@ -889,7 +924,7 @@ export class Game implements GameModel { } } } catch (e) { - aux.log("Announcement: ", e.message); + // aux.log("Announcement:", e.message); } try { @@ -903,7 +938,7 @@ export class Game implements GameModel { } } } catch (e) { - aux.log("Reminder: ", e.message); + aux.log("Reminder:", e.message); } // try { @@ -915,7 +950,7 @@ export class Game implements GameModel { // } // } // } catch (e) { - // aux.log("DM: ", e.message); + // aux.log("DM:", e.message); // } } @@ -981,7 +1016,7 @@ export class Game implements GameModel { this.reserved.forEach((res, index) => { var member = guildMembers.find((mem) => mem.user.tag.trim() === res.tag.trim() || mem.user.id === res.id); - if (index + 1 === parseInt(this.players)) { + if (index + 1 === parseInt(this.players) && lang.other) { const embed = new MessageEmbed(); embed.setColor(gameTemplate && gameTemplate.embedColor ? gameTemplate.embedColor : guildConfig.embedColor); diff --git a/src/processes/discord.ts b/src/processes/discord.ts index a153c7e..33ea24e 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -1,4 +1,4 @@ -import { TextChannel, Client, Message, User, GuildChannel, MessageEmbed, Permissions, Guild } from "discord.js"; +import { TextChannel, Client, Message, User, GuildChannel, MessageEmbed, Permissions, Guild, CategoryChannel } from "discord.js"; import { DeleteWriteOpResultObject, FilterQuery, ObjectId, UpdateWriteOpResult } from "mongodb"; import { GuildConfig, GuildConfigModel } from "../models/guild-config"; @@ -70,30 +70,28 @@ client.on("ready", async () => { postReminders(); }, 1 * 60 * 1000); // 1 minute } - - // If client is the dev bot, leave any server that Sillvva is not part of. - // if (client.user.id === "532635202808315906") { - // client.guilds.cache.forEach((guild) => { - // if (!guild.members.cache.find((m) => m.user.id === "202640192178225152")) guild.leave(); - // }); - // } } }); if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { - client.on("channelCreate", async (newC: any) => { - if (newC.type !== "text") return; - const channel: TextChannel = newC; + client.on("channelCreate", async (channel: GuildChannel) => { + if (!channel.guild) return; client.shard.send({ type: "shard", name: "channelCreate", - data: channel, + data: { + id: channel.id, + type: channel.type, + name: channel.name, + guild: channel.guild.id, + parentID: channel.parentID, + members: channel.members.array().map((m) => m.user.id), + everyone: channel.permissionsFor(channel.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), + }, }); }); - client.on("channelUpdate", async (oldC, newC: any) => { - if (newC.type !== "text") return; - const channel: TextChannel = newC; + client.on("channelUpdate", async (oldC, channel: GuildChannel) => { client.shard.send({ type: "shard", name: "channelUpdate", @@ -101,6 +99,7 @@ if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { id: channel.id, type: channel.type, name: channel.name, + guild: channel.guild.id, parentID: channel.parentID, members: channel.members.map((m) => m.user.id), everyone: channel.permissionsFor(channel.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), @@ -905,9 +904,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { ); games.forEach((game) => { if (game && message.channel instanceof TextChannel) { - game.delete().then((result) => { - aux.log("Game deleted"); - }); + game.delete(); } }); }); @@ -1074,9 +1071,6 @@ const rescheduleOldGames = async (guildId?: string) => { }, client ); - if (newGames.length > 0) { - await game.delete(); - } } } } @@ -1094,7 +1088,8 @@ const rescheduleOldGames = async (guildId?: string) => { }; const pruneOldGames = async (guild?: Guild) => { - let result: DeleteWriteOpResultObject; + let pruned: UpdateWriteOpResult; + let deleted: DeleteWriteOpResultObject; try { aux.log(`Pruning old games for ${guild ? `${guild.name} server` : "all servers"}`); const query: FilterQuery = { @@ -1105,6 +1100,9 @@ const pruneOldGames = async (guild?: Guild) => { hideDate: { $in: [false, null], }, + deleted: { + $in: [false, null], + }, }; let guildIds = []; @@ -1136,6 +1134,7 @@ const pruneOldGames = async (guild?: Guild) => { const gameChannelMessages: { guild: string; channel: string; message: string }[] = []; const prunedIds = []; const deletedIds = []; + const prunedMessageIds = []; for (let i = 0; i < games.length; i++) { let game = games[i]; if (!game.discordGuild) continue; @@ -1149,6 +1148,7 @@ const pruneOldGames = async (guild?: Guild) => { if (game.messageId) { if (guildConfig.pruneIntDiscord < guildConfig.pruneIntEvents && new Date().getTime() - game.timestamp < guildConfig.pruneIntEvents * 24 * 3600 * 1000) { prunedIds.push(game._id); + prunedMessageIds.push(game.messageId); client.shard.send({ type: "socket", name: "game", @@ -1187,7 +1187,7 @@ const pruneOldGames = async (guild?: Guild) => { } } - const updateResult = await Game.updateAllBy( + pruned = await Game.updateAllBy( { ...query, pruned: { @@ -1206,15 +1206,23 @@ const pruneOldGames = async (guild?: Guild) => { }, } ); - if ((updateResult).modifiedCount > 0) aux.log(`${(updateResult).modifiedCount} old game(s) pruned from Discord only`); + if (pruned.modifiedCount > 0) aux.log(`${pruned.modifiedCount} old game(s) pruned from Discord only`); - result = await Game.deleteAllBy({ + pruned = await Game.softDeleteAllBy({ ...query, _id: { $in: deletedIds.map((pid) => new ObjectId(pid)), }, }); - if (result.deletedCount > 0) aux.log(`${result.deletedCount} old game(s) successfully pruned`); + if (pruned.modifiedCount > 0) aux.log(`${pruned.modifiedCount} old game(s) successfully pruned`); + + deleted = await Game.hardDeleteAllBy({ + deleted: true, + timestamp: { + $lt: new Date().getTime() - 14 * 24 * 3600 * 1000, + }, + }); + if (deleted.deletedCount > 0) aux.log(`${deleted.deletedCount} old game(s) successfully deleted`); let count = 0; @@ -1230,8 +1238,11 @@ const pruneOldGames = async (guild?: Guild) => { .array() .filter( (m) => - m.embeds.filter((e) => new Date().getTime() - e.timestamp >= gc.pruneIntDiscord * 24 * 3600 * 1000).length > 0 && + m.embeds.filter( + (e) => new Date().getTime() - m.createdTimestamp >= 14 * 24 * 3600 * 1000 && new Date().getTime() - e.timestamp >= gc.pruneIntDiscord * 24 * 3600 * 1000 + ).length > 0 && m.author.id === client.user.id && + prunedMessageIds.includes(m.id) && m.deletable && !m.deleted ); @@ -1258,7 +1269,7 @@ const pruneOldGames = async (guild?: Guild) => { } catch (err) { aux.log("GamePruningError:", err); } - return result; + return pruned; }; const postReminders = async () => { @@ -1325,7 +1336,7 @@ const postReminders = async () => { return true; }); totalGames += filteredGames.length; - if (page === pages) aux.log(`Posting reminders for ${totalGames} games`); + if (page === pages && totalGames > 0) aux.log(`Posting reminders for ${totalGames} games`); filteredGames.forEach(async (game) => { try { const reserved: string[] = []; @@ -1343,9 +1354,9 @@ const postReminders = async () => { let name = rsvp.tag; if (member) name = member.user.toString(); - if (member) reservedUsers.push(member); if (reserved.length < parseInt(game.players)) { + if (member) reservedUsers.push(member); reserved.push(name); } }); @@ -1358,15 +1369,6 @@ const postReminders = async () => { console.log(err); } - if (reserved.length == 0) return; - - let minPlayers = parseInt(game.minPlayers); - if (!isNaN(parseInt(game.minPlayers))) minPlayers = 0; - if (reserved.length < minPlayers) return; - - const message = await game.discordChannel.messages.fetch(game.messageId); - if (!message || (message && message.author.id !== process.env.CLIENT_ID)) return false; - try { game.reminded = true; const result = await game.save({ force: true }); @@ -1376,6 +1378,15 @@ const postReminders = async () => { return; } + if (reserved.length == 0) return; + + let minPlayers = parseInt(game.minPlayers); + if (!isNaN(parseInt(game.minPlayers))) minPlayers = 0; + if (reserved.length < minPlayers) return; + + const message = await game.discordChannel.messages.fetch(game.messageId); + if (!message || (message && message.author.id !== process.env.CLIENT_ID)) return false; + const guildConfig = await GuildConfig.fetch(game.discordGuild.id); const lang = langs.find((l) => l.code === guildConfig.lang) || langs.find((l) => l.code === "en"); const reminder = game.reminder; @@ -1448,15 +1459,14 @@ const postReminders = async () => { const apiGuildIds: any = {}; -const sendGuildsToAPI = () => { - const guilds = client.guilds.cache.array(); - const newGuilds = guilds.filter((guild) => !apiGuildIds[guild.shardID] || !apiGuildIds[guild.shardID].includes(guild.id)); - if (newGuilds.length > 0) { - aux.log("Refreshing data for", newGuilds.length, "guilds"); +const sendGuildsToAPI = (all: boolean = false) => { + const guilds = client.guilds.cache.array().filter((guild) => all || !apiGuildIds[guild.shardID] || !apiGuildIds[guild.shardID].includes(guild.id)); + if (guilds.length > 0) { + aux.log("Refreshing data for", guilds.length, "guilds"); client.shard.send({ type: "shard", name: "guilds", - data: guilds.filter((guild) => !apiGuildIds[guild.shardID] || !apiGuildIds[guild.shardID].includes(guild.id)).map(guildMap), + data: guilds.map(guildMap), }); } }; @@ -1489,8 +1499,10 @@ const guildMap = (guild: Guild) => { id: c.id, type: c.type, name: c.name, + guild: c.guild.id, parentID: c.parentID, members: c.members.map((m) => m.user.id), + everyone: c.permissionsFor(c.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), })), roles: guild.roles.cache.array(), }; diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index 4156ee4..aff90b8 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -1,6 +1,7 @@ import { ShardingManager, Role, MessageEmbed, Message, Client, TextChannel, Permissions } from "discord.js"; import { Express } from "express"; import { io } from "./socket"; +import aux from "../appaux"; type DiscordProcessesOptions = { app: Express; @@ -23,7 +24,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { manager.spawn("auto", 0, 30000); manager.on("shardCreate", (shard) => { - console.log(`Shard ${shard.id} launched`); + aux.log(`Shard ${shard.id} launched`); shard.on("message", (message) => { if (typeof message === "object") { @@ -33,6 +34,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { if (message.type === "shard") { if (message.name === "guilds") { const guilds: any[] = message.data; + // console.log(message.name, guilds.length) guilds.forEach((guild, i) => { const gdi = guildData.findIndex((g) => g.id === guild.id); if (gdi >= 0) { @@ -43,27 +45,32 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { }); } else if (message.name === "channelCreate") { guildData = guildData.map((g) => { - if (g.id !== message.data.guild) return g; - g.channels.push(message.data); - g.channels.sort((a, b) => { - return a.rawPosition > b.rawPosition ? 1 : -1; - }); + if (g.id === message.data.guild) { + g.channels.push(message.data); + g.channels.sort((a, b) => { + return a.name > b.name ? 1 : -1; + }); + } return g; }); } else if (message.name === "channelUpdate") { guildData = guildData.map((g) => { - if (g.id !== message.data.guild) return g; - g.channels = g.channels.map((c) => { - if (c.id === message.data.id) c = message.data; - return c; - }); + if (g.id === message.data.guild) { + g.channels = g.channels.map((c) => { + if (c.id === message.data.id) { + c = message.data; + } + return c; + }); + } return g; }); } else if (message.name === "channelDelete") { guildData = guildData.map((g) => { const index = g.channels.findIndex((c) => c.id === message.data); - if (index < 0) return g; - g.channels.splice(index, 1); + if (index >= 0) { + g.channels.splice(index, 1); + } return g; }); } else if (message.name === "roleCreate") { @@ -90,8 +97,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { } else if (message.name === "roleDelete") { guildData = guildData.map((g) => { const index = g.roles.findIndex((c) => c.id === message.data); - if (index < 0) return g; - g.roles.splice(index, 1); + if (index >= 0) g.roles.splice(index, 1); return g; }); } else if (message.name === "guildMemberAdd") { @@ -206,14 +212,14 @@ const clientGuilds = async (client: Client, guildIds: string[] = []) => { try { const guilds = client.guilds.cache.array().filter((g) => (guildIds.length > 0 ? guildIds.includes(g.id) : true)); const shards = [guilds]; - const result = shards.reduce((iter, shard, shardIndex) => { - const append = shard.map((guild, guildIndex) => { + const result = shards.reduce((iter, shard) => { + const append = shard.map((guild) => { const sGuild: ShardGuild = { id: guild.id, name: guild.name, icon: guild.icon, shardID: guild.shardID, - members: guild.members.cache.array().map((member, memberIndex) => { + members: guild.members.cache.array().map((member) => { const user = member.user; return { id: user.id, @@ -250,7 +256,7 @@ const clientGuilds = async (client: Client, guildIds: string[] = []) => { }, }; }), - channels: guild.channels.cache.array().map((channel, channelIndex) => { + channels: guild.channels.cache.array().map((channel) => { const sChannel: ShardChannel = { id: channel.id, name: channel.name, diff --git a/src/routes/api.ts b/src/routes/api.ts index 4f27985..a64a0c7 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -330,6 +330,27 @@ export default (options: APIRouteOptions) => { }) .then(async (result: any) => { const userSettings = await getUserSettings(result.account.user.id, req); + result.account.guilds = result.account.guilds.map((guild) => { + guild.roles = guild.roles.map((role) => { + delete role.hoist; + delete role.createdTimestamp; + delete role.deleted; + delete role.mentionable; + delete role.permissions; + return role; + }); + guild.announcementChannels = guild.announcementChannels.map((channel) => { + delete channel.members; + delete channel.messages; + return channel; + }); + guild.channels = guild.channels.map((channel) => { + delete channel.members; + delete channel.messages; + return channel; + }); + return guild; + }); res.json({ status: "success", token: req.session.api.access.access_token, @@ -1009,6 +1030,213 @@ export default (options: APIRouteOptions) => { }); }); + router.get("/test/account", async (req, res, next) => { + const { id, username, discriminator, guildId } = req.query; + const tag = `${username}#${discriminator}`; + + const options = { + guilds: true, + games: false, + search: "", + }; + + const account = { + user: { + ...req.query, + ...{ + tag: tag, + }, + }, + guilds: [], + }; + + let sGuilds: ShardGuild[] = []; + + if (options.guilds) { + const fTime = new Date().getTime(); + sGuilds = await ShardManager.shardGuilds( + id + ? { + memberIds: [id], + } + : { + guildIds: [guildId], + } + ); + // console.log(new Date().getTime() - fTime, req.query, tag, sGuilds.length); + + sGuilds.forEach((guild) => { + const guildInfo: AccountGuild = { + id: guild.id, + name: guild.name, + icon: guild.icon ? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png?size=128` : "/images/logo2.png", + permission: false, + isAdmin: false, + member: null, + roles: guild.roles, + userRoles: [], + channelCategories: guild.channels.filter((c) => c.type === "category"), + channels: guild.channels.filter((c) => c.type === "text"), + announcementChannels: [], + config: new GuildConfig({ guild: guild.id }), + games: [], + }; + + // console.log(guild.members); + + guild.members.forEach((member) => { + if ((id && member.user.id === id) || member.user.tag === tag || (member.user.username === username && member.user.discriminator === discriminator)) { + guildInfo.member = member; + if (!options.search) account.guilds.push(guildInfo); + } + }); + if (options.search) { + if (new RegExp(options.search, "gi").test(guild.name)) { + account.guilds.push(guildInfo); + } + } + }); + + account.guilds = account.guilds.filter((guild) => (!guild.config.hidden && !options.search) || config.author == tag); + + const gcQuery = { + guild: { + $in: account.guilds.reduce((i, g) => { + i.push(g.id); + return i; + }, []), + }, + }; + + const guildConfigs = await GuildConfig.fetchAllBy(gcQuery); + // console.log(new Date().getTime() - fTime); + + for (let gi = 0; gi < account.guilds.length; gi++) { + const guild: AccountGuild = account.guilds[gi]; + const guildConfig = guildConfigs.find((gc) => gc.guild === guild.id) || new GuildConfig({ guild: guild.id }); + const member = guild.member; + + let gcChannels: ChannelConfig[] = guildConfig.channels; + if (gcChannels.length == 0 || !guild.channels.find((gc) => !!gcChannels.find((c) => gc.id === c.channelId))) { + let firstChannel: ShardChannel; + for (let i = 0; i < guild.channels.length; i++) { + const pf = await guild.channels[i].everyone; + if (pf) firstChannel = guild.channels[i]; + } + if (firstChannel && guild.channels.length > 0) { + gcChannels.push({ channelId: firstChannel.id, gameTemplates: [guildConfig.defaultGameTemplate.id] }); + } + } + + let channels: ShardChannel[] = []; + + if (member) { + guild.userRoles = member.roles.map((r) => r.name); + guild.isAdmin = !!( + member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || member.roles.find((r) => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) + ); + guild.permission = guildConfig.shardMemberHasPermission(member) || guild.isAdmin; + gcChannels.forEach((c) => { + const gcc = guild.channels.find((gc) => gc.id === c.channelId); + if (gcc && (guild.permission || (gcc.members && gcc.members.includes(member.user.id)))) channels.push(gcc); + }); + channels = channels.filter((c) => c && member && (guild.isAdmin || !!guildConfig.shardMemberHasPermission(member, c.id))); + // console.log(guild.isAdmin, guild.permission); + } + + guild.announcementChannels = channels; + guild.config = guildConfig; + account.guilds[gi] = guild; + } + + if (options.games) { + const gameOptions: any = { + s: { + $in: account.guilds.reduce((i, g) => { + i.push(g.id); + return i; + }, []), + }, + }; + + const fGames: Game[] = await Game.fetchAllBy(gameOptions, null, sGuilds); + // console.log(new Date().getTime() - fTime); + const games: any[] = []; + for (let i = 0; i < fGames.length; i++) { + const game = fGames[i]; + const dc = game.discordChannel; + if (dc && (dc.members || []).includes(id)) { + games.push(game); + } + } + games.forEach(async (game) => { + if (!game.discordGuild) return; + const date = Game.ISOGameDate(game); + const parsed = aux.parseEventTimes(game); + const gi = account.guilds.findIndex((g) => g.id === game.s); + account.guilds[gi].games.push({ + ...game.data, + moment: { + ...parsed, + iso: date, + date: moment(date).utcOffset(parseInt(game.timezone)).format(config.formats.dateLong), + calendar: moment(date).utcOffset(parseInt(game.timezone)).calendar(), + from: moment(date).utcOffset(parseInt(game.timezone)).fromNow(), + }, + reserved: game.reserved.filter((r) => r.tag), + slot: game.reserved.findIndex((t) => t.tag.replace("@", "") === tag || t.id === id) + 1, + signedup: game.slot > 0 && game.slot <= parseInt(game.players), + waitlisted: game.slot > parseInt(game.players), + }); + }); + } + + account.guilds = account.guilds + .map((guild) => { + guild.games.sort((a, b) => { + return a.timestamp < b.timestamp ? -1 : 1; + }); + return guild; + }) + .sort((a, b) => { + if (a.games.length === 0 && b.games.length === 0) return a.name < b.name ? -1 : 1; + if (a.games.length > 0 && b.games.length > 0) return a.name < b.name ? -1 : 1; + if (a.games.length === 0) return 1; + if (b.games.length === 0) return -1; + }); + + account.guilds = account.guilds.map((guild) => { + guild.roles = guild.roles.map((role) => { + delete role.hoist; + delete role.createdTimestamp; + delete role.deleted; + delete role.mentionable; + delete role.permissions; + delete role.rawPosition; + return role; + }); + guild.channelCategories = guild.channelCategories.map((channel) => { + delete channel.members; + delete channel.messages; + return channel; + }); + guild.announcementChannels = guild.announcementChannels.map((channel) => { + delete channel.members; + delete channel.messages; + return channel; + }); + guild.channels = guild.channels.map((channel) => { + delete channel.members; + delete channel.messages; + return channel; + }); + return guild; + }); + } + + res.json(account); + }); + return router; }; @@ -1070,7 +1298,6 @@ const fetchAccount = (token: any, options: AccountOptions) => { ...{ tag: tag, avatarURL: `https://cdn.discordapp.com/avatars/${id}/${avatar}.png?size=128`, - settings: await User.fetch(id), }, }, guilds: [], @@ -1156,14 +1383,12 @@ const fetchAccount = (token: any, options: AccountOptions) => { } let channels: ShardChannel[] = []; - for (let gci = 0; gci < gcChannels.length; gci++) { - const gcc = guild.channels.filter((gc: ShardChannel) => gc.id == gcChannels[gci].channelId); - for (let gcci = 0; gcci < gcc.length; gcci++) { - const pm = gcc[gcci].members.includes(id); - if (pm) channels.push(gcc[gcci]); - } - } - channels = channels.filter((c) => member && (guild.isAdmin || !!guildConfig.shardMemberHasPermission(member, c.id))); + gcChannels.forEach((c) => { + const gcc = guild.channels.find((gc) => gc.id === c.channelId); + if (gcc && (guild.permission || (gcc.members && gcc.members.includes(id)))) channels.push(gcc); + }); + + channels = channels.filter((c) => c && member && (guild.isAdmin || !!guildConfig.shardMemberHasPermission(member, c.id))); guild.announcementChannels = channels; guild.config = guildConfig; @@ -1243,7 +1468,7 @@ const fetchAccount = (token: any, options: AccountOptions) => { for (let i = 0; i < fGames.length; i++) { const game = fGames[i]; const dc = game.discordChannel; - if (dc && dc.members.includes(id)) { + if (dc && (dc.members || []).includes(id)) { games.push(game); } } @@ -1341,7 +1566,7 @@ const refreshToken = (access: any) => { const token = JSON.parse(body); - aux.log(access.access_token, token.access_token); + if (process.env.SITE === "dev") aux.log(access.access_token, token.access_token); if (storedSession && storedSession.token != token.access_token) { await storedSession.delete(); diff --git a/src/routes/rss.ts b/src/routes/rss.ts index 67d7929..60428a6 100644 --- a/src/routes/rss.ts +++ b/src/routes/rss.ts @@ -164,7 +164,7 @@ export default () => { .map((r, i) => ({ name: r, rsvp: parseInt(game.players) - i > 0, - email: `${r.id.replace(/[^a-z0-9_.]/gi, "")}@rpg-schedule.com`, + email: `${(r.id || "").replace(/[^a-z0-9_.]/gi, "")}@rpg-schedule.com`, })), sequence: game.sequence, }; From 5c91880d57174f89bbb561cfa642e4d7df8a2ef6 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Wed, 17 Jun 2020 13:16:08 -0500 Subject: [PATCH 14/53] Bug Fixes --- src/models/game.ts | 17 ++++-- src/processes/shard-manager.ts | 106 ++++++++++++++++++++++++++++++++- src/routes/api.ts | 104 ++++++++++++++++---------------- 3 files changed, 167 insertions(+), 60 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index 55af75c..f254c82 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -294,10 +294,15 @@ export class Game implements GameModel { guild = sGuilds.find((g) => g.id === game.s); } + if (!guild && !this.client) { + guild = await new Promise(async (resolve) => { + const g = await ShardManager.refreshGuild(game.s); + resolve(g.find(g => g.id === game.s)); + }); + } + if (!guild) { - aux.log(`Server (${game.s}) not found when saving game (${this._id})`); - // aux.log(JSON.stringify(this.data)); - return; + throw new Error(`Server (${game.s}) not found when saving game (${this._id})`); } const guildConfig = await GuildConfig.fetch(guild.id); @@ -349,8 +354,8 @@ export class Game implements GameModel { user: { id: game.author.id, tag: game.author.tag, - username: authorParts[0].trim(), - discriminator: authorParts[1].trim(), + username: (authorParts[0] || "").trim(), + discriminator: (authorParts[1] || "").trim(), avatar: "", avatarUrl: "", }, @@ -843,6 +848,7 @@ export class Game implements GameModel { const guildConfig = await GuildConfig.fetch(this.s); if (guildConfig.rescheduleMode === RescheduleMode.UPDATE) { + this.reminded = null; await this.save(); } else if (guildConfig.rescheduleMode === RescheduleMode.REPOST) { let data = cloneDeep(this.data); @@ -854,6 +860,7 @@ export class Game implements GameModel { } const id = data._id; delete data._id; + delete data.reminded; delete data.pm; delete data.messageId; delete data.reminderMessageId; diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index aff90b8..60f8eae 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -34,7 +34,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { if (message.type === "shard") { if (message.name === "guilds") { const guilds: any[] = message.data; - // console.log(message.name, guilds.length) + aux.log('Adding guild data to API memory', guilds.length) guilds.forEach((guild, i) => { const gdi = guildData.findIndex((g) => g.id === guild.id); if (gdi >= 0) { @@ -206,6 +206,7 @@ export interface ShardGuild { channels: ShardChannel[]; roles: Role[]; shardID: number; + refresh: () => void; } const clientGuilds = async (client: Client, guildIds: string[] = []) => { @@ -303,6 +304,7 @@ const clientGuilds = async (client: Client, guildIds: string[] = []) => { return sChannel; }), roles: guild.roles.cache.array(), + refresh: () => {}, }; return sGuild; }); @@ -318,20 +320,23 @@ const clientGuilds = async (client: Client, guildIds: string[] = []) => { interface ShardFilters { guildIds?: string[]; memberIds?: string[]; + guild?: any; } const shardGuilds = async (filters: ShardFilters = {}) => { const guildIds = filters.guildIds || []; const memberIds = filters.memberIds || []; + const sGuild = filters.guild; try { - const shards = [ + let shards = [ guildData .filter((guild) => guildIds.length === 0 || guildIds.includes(guild.id)) .filter((guild) => { return guild.members.find((member) => memberIds.length === 0 || memberIds.includes(member.userID)); }), ]; + if (sGuild) shards = [ [ sGuild ] ]; const result = shards.reduce((iter, shard, shardIndex) => { return [ ...iter, @@ -472,6 +477,53 @@ const shardGuilds = async (filters: ShardFilters = {}) => { return sChannel; }), roles: guild.roles, + refresh: async function () { + const call = ` + (async () => { + const guild = this.guilds.cache.get(${JSON.stringify(guild.id)}); + if (!guild) return; + const sGuild = { + id: guild.id, + name: guild.name, + icon: guild.icon, + shardID: guild.shardID, + ownerID: guild.ownerID, + members: guild.members.cache.array(), + users: guild.members.cache.map((m) => ({ + id: m.user.id, + username: m.user.username, + tag: m.user.tag, + discriminator: m.user.discriminator, + avatar: m.user.avatar, + })), + memberRoles: guild.members.cache.map((m) => + m.roles.cache.map((r) => ({ + id: r.id, + name: r.name, + permissions: r.permissions, + })) + ), + channels: guild.channels.cache.array().map((c) => ({ + id: c.id, + type: c.type, + name: c.name, + guild: c.guild.id, + parentID: c.parentID, + members: c.members.map((m) => m.user.id), + everyone: c.permissionsFor(c.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), + })), + roles: guild.roles.cache.array(), + }; + await this.shard.send({ + type: "shard", + name: "guilds", + data: [sGuild] + }); + return sGuild; + })(); + `; + const result = await discordClient().broadcastEval(call); + }, }; return sGuild; }) @@ -485,6 +537,55 @@ const shardGuilds = async (filters: ShardFilters = {}) => { } }; +const refreshGuild = async function (guildId: string) { + const call = ` + (async () => { + const guild = this.guilds.cache.get(${JSON.stringify(guildId)}); + if (!guild) return; + const sGuild = { + id: guild.id, + name: guild.name, + icon: guild.icon, + shardID: guild.shardID, + ownerID: guild.ownerID, + members: guild.members.cache.array(), + users: guild.members.cache.map((m) => ({ + id: m.user.id, + username: m.user.username, + tag: m.user.tag, + discriminator: m.user.discriminator, + avatar: m.user.avatar, + })), + memberRoles: guild.members.cache.map((m) => + m.roles.cache.map((r) => ({ + id: r.id, + name: r.name, + permissions: r.permissions, + })) + ), + channels: guild.channels.cache.array().map((c) => ({ + id: c.id, + type: c.type, + name: c.name, + guild: c.guild.id, + parentID: c.parentID, + members: c.members.map((m) => m.user.id), + everyone: c.permissionsFor(c.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), + })), + roles: guild.roles.cache.array(), + }; + await this.shard.send({ + type: "shard", + name: "guilds", + data: [sGuild] + }); + return sGuild; + })(); + `; + const guildData = (await discordClient().broadcastEval(call)).find(s => s); + return shardGuilds({ guild: guildData }); +}; + const shardUser = async () => { const shards = await discordClient().broadcastEval("this.user"); return shards.find((u) => u); @@ -557,6 +658,7 @@ export default { shardMessageReact: shardMessageReact, shardMessageEdit: shardMessageEdit, clientMessageEdit: clientMessageEdit, + refreshGuild: refreshGuild }; export function discordClient() { diff --git a/src/routes/api.ts b/src/routes/api.ts index a64a0c7..e54234c 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1,4 +1,4 @@ -import discord, { Permissions, Role, ShardingManager } from "discord.js"; +import discord, { Permissions, Role, ShardingManager, DataResolver } from "discord.js"; import express from "express"; import moment from "moment"; import request from "request"; @@ -337,8 +337,14 @@ export default (options: APIRouteOptions) => { delete role.deleted; delete role.mentionable; delete role.permissions; + delete role.rawPosition; return role; }); + guild.channelCategories = guild.channelCategories.map((channel) => { + delete channel.members; + delete channel.messages; + return channel; + }); guild.announcementChannels = guild.announcementChannels.map((channel) => { delete channel.members; delete channel.messages; @@ -430,14 +436,14 @@ export default (options: APIRouteOptions) => { fetchAccount(req.session.api.access, { client: client, ip: req.app.locals.ip, - guilds: true, }) .then(async (result: any) => { + const sGuilds: ShardGuild[] = result.sGuilds; try { let game: Game; let server: string = req.query.s; if (req.query.g) { - game = await Game.fetch(req.query.g, null, result.sGuilds); + game = await Game.fetch(req.query.g, null, sGuilds); if (game) { server = game.s; } else { @@ -446,11 +452,15 @@ export default (options: APIRouteOptions) => { } if (server) { - let guild: ShardGuild; - if (req.query.g) guild = game.discordGuild; + let guild: ShardGuild = await new Promise(async (resolve) => { + const g = await ShardManager.refreshGuild(server); + resolve(g.find((g) => g.id === server)); + }); + + if (!guild && req.query.g) guild = game.discordGuild; if (!guild) { - guild = result.sGuilds.find((g) => g.id === server); + guild = sGuilds.find((g) => g.id === server); if (req.query.g) game.discordGuild = guild; } @@ -591,10 +601,12 @@ export default (options: APIRouteOptions) => { }) .then(async (result: any) => { try { + const sGuilds: ShardGuild[] = result.sGuilds; + let game: Game; let server: string = req.query.s; if (req.query.g && !(req.body && req.body.copy)) { - game = await Game.fetch(req.query.g, null, result.sGuilds); + game = await Game.fetch(req.query.g, null, sGuilds); if (game) { server = game.s; } else { @@ -612,12 +624,17 @@ export default (options: APIRouteOptions) => { server = req.body.s; } if (req.query.s) { - game = new Game(req.body, result.sGuilds); + game = new Game(req.body, sGuilds); } } if (server) { - let guild = game.discordGuild; + let guild: ShardGuild = await new Promise(async (resolve) => { + const g = await ShardManager.refreshGuild(server); + resolve(g.find((g) => g.id === server)); + }); + + if (!guild) guild = game.discordGuild; if (guild) { let password: string; @@ -1048,21 +1065,32 @@ export default (options: APIRouteOptions) => { }, }, guilds: [], + sGuilds: [], }; let sGuilds: ShardGuild[] = []; if (options.guilds) { const fTime = new Date().getTime(); - sGuilds = await ShardManager.shardGuilds( - id - ? { - memberIds: [id], - } - : { - guildIds: [guildId], - } - ); + if (guildId) { + let guild: ShardGuild = await new Promise(async (resolve) => { + const g = await ShardManager.refreshGuild(guildId); + resolve(g.find((g) => g.id === guildId)); + }); + sGuilds.push(guild); + } else { + sGuilds = await ShardManager.shardGuilds( + id + ? { + memberIds: [id], + } + : { + guildIds: [guildId], + } + ); + } + + account.sGuilds = sGuilds; // console.log(new Date().getTime() - fTime, req.query, tag, sGuilds.length); sGuilds.forEach((guild) => { @@ -1204,34 +1232,6 @@ export default (options: APIRouteOptions) => { if (a.games.length === 0) return 1; if (b.games.length === 0) return -1; }); - - account.guilds = account.guilds.map((guild) => { - guild.roles = guild.roles.map((role) => { - delete role.hoist; - delete role.createdTimestamp; - delete role.deleted; - delete role.mentionable; - delete role.permissions; - delete role.rawPosition; - return role; - }); - guild.channelCategories = guild.channelCategories.map((channel) => { - delete channel.members; - delete channel.messages; - return channel; - }); - guild.announcementChannels = guild.announcementChannels.map((channel) => { - delete channel.members; - delete channel.messages; - return channel; - }); - guild.channels = guild.channels.map((channel) => { - delete channel.members; - delete channel.messages; - return channel; - }); - return guild; - }); } res.json(account); @@ -1303,15 +1303,13 @@ const fetchAccount = (token: any, options: AccountOptions) => { guilds: [], }; - let sGuilds: ShardGuild[] = []; + // const fTime = new Date().getTime(); + let sGuilds: ShardGuild[] = await ShardManager.shardGuilds({ + memberIds: [id], + }); + // console.log(new Date().getTime() - fTime); if (options.guilds) { - // const fTime = new Date().getTime(); - sGuilds = await ShardManager.shardGuilds({ - memberIds: [id], - }); - // console.log(new Date().getTime() - fTime); - sGuilds.forEach((guild) => { const guildInfo: AccountGuild = { id: guild.id, From ed2c98464c107903ba7063c23e01c13f1af39751 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Mon, 22 Jun 2020 13:03:44 -0500 Subject: [PATCH 15/53] Debugging data refresh --- src/processes/discord.ts | 12 +++++++--- src/processes/shard-manager.ts | 44 ++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 33ea24e..cc9053d 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -43,9 +43,11 @@ client.on("ready", async () => { if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { // Send updated server information to the API - sendGuildsToAPI(); - setInterval(() => { - sendGuildsToAPI(); + sendGuildsToAPI(true); + let i = 0; + setInterval(async () => { + i++; + sendGuildsToAPI(i % client.shard.count === client.guilds.cache.array()[0].shardID); }, 10 * 60 * 1000); } @@ -847,6 +849,10 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { .catch((err) => { aux.log(err); }); + } else if (cmd === "refresh-data" && member.user.tag === config.author) { + client.shard.send({ + type: "refresh" + }); } else { const response = await (message.channel).send("Command not recognized"); if (response) { diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index 60f8eae..7cd7fab 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -153,6 +153,50 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { } } } + if (message.type === "refresh") { + manager.broadcastEval(` + const guilds = this.guilds.cache.array(); + console.log("Force refreshing data for ", guilds.length, "guilds"); + this.shard.send({ + type: "shard", + name: "guilds", + data: guilds.map((guild) => { + return { + id: guild.id, + name: guild.name, + icon: guild.icon, + shardID: guild.shardID, + ownerID: guild.ownerID, + members: guild.members.cache.array(), + users: guild.members.cache.map((m) => ({ + id: m.user.id, + username: m.user.username, + tag: m.user.tag, + discriminator: m.user.discriminator, + avatar: m.user.avatar, + })), + memberRoles: guild.members.cache.map((m) => + m.roles.cache.map((r) => ({ + id: r.id, + name: r.name, + permissions: r.permissions, + })) + ), + channels: guild.channels.cache.array().map((c) => ({ + id: c.id, + type: c.type, + name: c.name, + guild: c.guild.id, + parentID: c.parentID, + members: c.members.map((m) => m.user.id), + everyone: c.permissionsFor(c.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), + })), + roles: guild.roles.cache.array(), + }; + }), + }); + `); + } } }); From 3a2420b628b8ee0733d379521824d51cbac47569 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Wed, 8 Jul 2020 08:02:55 -0500 Subject: [PATCH 16/53] Development --- lang/de-ch.json | 7 +- lang/de.json | 7 +- lang/en.json | 9 +- lang/es.json | 9 +- lang/fr.json | 7 +- lang/it.json | 9 +- lang/ru.json | 7 +- src/models/game-signups.ts | 105 ++++++ src/models/game.ts | 418 +++++++++++++------- src/processes/discord.ts | 268 ++++++++----- src/processes/shard-manager.ts | 221 ++++++----- src/routes/api.ts | 307 ++++++++++----- src/routes/init.ts | 1 + src/routes/rss.ts | 6 +- views/about.ejs | 242 ------------ views/calendar.ejs | 354 ----------------- views/foot.ejs | 0 views/game.ejs | 671 --------------------------------- views/games.ejs | 254 ------------- views/home.ejs | 15 - views/menu.ejs | 134 ------- 21 files changed, 959 insertions(+), 2092 deletions(-) create mode 100644 src/models/game-signups.ts delete mode 100644 views/about.ejs delete mode 100644 views/calendar.ejs delete mode 100644 views/foot.ejs delete mode 100644 views/game.ejs delete mode 100644 views/games.ejs delete mode 100644 views/home.ejs delete mode 100644 views/menu.ejs diff --git a/lang/de-ch.json b/lang/de-ch.json index e8784cd..7dadf94 100644 --- a/lang/de-ch.json +++ b/lang/de-ch.json @@ -69,6 +69,7 @@ "COUNTDOWN": "Countdown", "MONTHLY_TYPE": "Monatlicher Typ", "HIDE_DATE": "Datum ausblenden", + "PAST_SIGNUPS": "Zulassen von Anmeldungen nach dem Datum / der Uhrzeit der Anmeldung", "SLOT": "Slot", "UNSAVED": "Du hast nicht gespeicherte Änderungen. Möchten Sie fortfahren?", "options": { @@ -203,6 +204,10 @@ "MAINTENANCE": "Die Website wird gewartet :TIME für ungefähr :DURATION stunde(n).", "UNDER_MAINTENANCE": "ist in Wartung", "YOURE_IN": "Du bist in! Sie haben sich einen Platz für :GAME in :SERVER gesichert.", - "BACK_SOON": "Es wird bald zurück sein!" + "BACK_SOON": "Es wird bald zurück sein!", + "RESCHEDULED": "Das Spiel wurde verschoben", + "DELETED": "Das Spiel wurde gelöscht", + "EDIT_PERMISSION": "Sie haben keine Berechtigung, dieses Spiel zu bearbeiten", + "ALREADY_STARTED": "Das Spiel hat bereits begonnen!" } } diff --git a/lang/de.json b/lang/de.json index 491329c..38f4567 100644 --- a/lang/de.json +++ b/lang/de.json @@ -69,6 +69,7 @@ "COUNTDOWN": "Countdown", "MONTHLY_TYPE": "Monatlicher Typ", "HIDE_DATE": "Datum ausblenden", + "PAST_SIGNUPS": "Zulassen von Anmeldungen nach dem Datum / der Uhrzeit der Anmeldung", "SLOT": "Slot", "UNSAVED": "Du hast nicht gespeicherte Änderungen. Möchten Sie fortfahren?", "options": { @@ -203,6 +204,10 @@ "MAINTENANCE": "Die Website wird gewartet :TIME für ungefähr :DURATION stunde(n).", "UNDER_MAINTENANCE": "ist in Wartung", "YOURE_IN": "Du bist in! Sie haben sich einen Platz für :GAME in :SERVER gesichert.", - "BACK_SOON": "Es wird bald zurück sein!" + "BACK_SOON": "Es wird bald zurück sein!", + "RESCHEDULED": "Das Spiel wurde verschoben", + "DELETED": "Das Spiel wurde gelöscht", + "EDIT_PERMISSION": "Sie haben keine Berechtigung, dieses Spiel zu bearbeiten", + "ALREADY_STARTED": "Das Spiel hat bereits begonnen!" } } diff --git a/lang/en.json b/lang/en.json index 8cd2073..ee4eeab 100644 --- a/lang/en.json +++ b/lang/en.json @@ -29,7 +29,7 @@ "game": { "SERVER": "Server", "CHANNEL": "Channel", - "GAME_NAME": "Event Name", + "GAME_NAME": "Game / Adventure Name", "RUN_TIME": "Run Time", "MIN_PLAYERS": "Min Players", "MAX_PLAYERS": "Max Players", @@ -69,6 +69,7 @@ "COUNTDOWN": "Countdown", "MONTHLY_TYPE": "Monthly Type", "HIDE_DATE": "Hide Date", + "PAST_SIGNUPS": "Allow signups past the signup date/time", "SLOT": "Slot", "UNSAVED": "You have unsaved changes. Do you wish to proceed?", "options": { @@ -203,6 +204,10 @@ "MAINTENANCE": "The site will be under maintenance :TIME for approximately :DURATION hour(s).", "UNDER_MAINTENANCE": "is under maintenance", "YOURE_IN": "You're in! You've secured a spot for **:GAME** in **:SERVER**.", - "BACK_SOON": "It will be back soon!" + "BACK_SOON": "It will be back soon!", + "RESCHEDULED": "The game has been rescheduled", + "DELETED": "The game has been deleted", + "EDIT_PERMISSION": "You do not have permission to edit this game", + "ALREADY_STARTED": "That game has already started!" } } \ No newline at end of file diff --git a/lang/es.json b/lang/es.json index 32279cb..9b61cb3 100644 --- a/lang/es.json +++ b/lang/es.json @@ -69,6 +69,7 @@ "COUNTDOWN": "Cuenta atras", "MONTHLY_TYPE": "Tipo del mes", "HIDE_DATE": "Ocultar Fecha", + "PAST_SIGNUPS": "Permitir registros después de la fecha / hora de registro", "SLOT": "Espacio", "UNSAVED": "Usted tiene cambios no guardados. ¿Desea continuar?", "options": { @@ -197,12 +198,16 @@ "RESCHEDULE_MODE": "Options: `repost` (default, crea u nuevo anuncio), `update` (actualiza el post original)" } }, - "other": { + "messages": { "DM_WAITLIST": "Estas ahora mismo en el slot :NUM de reservados.", "DM_INSTRUCTIONS": "Mensaje para el :DM for :EVENT", "MAINTENANCE": "The site will be under maintenance :TIME for approximately :DURATION hour(s).", "UNDER_MAINTENANCE": "is under maintenance", "YOURE_IN": "¡Estas Dentro! Has asegurado una plaza en el **:GAME** in **:SERVER**.", - "BACK_SOON": "¡Regresará pronto!" + "BACK_SOON": "¡Regresará pronto!", + "RESCHEDULED": "El juego ha sido reprogramado", + "DELETED": "El juego ha sido eliminado", + "EDIT_PERMISSION": "No tienes permiso para editar este juego", + "ALREADY_STARTED": "¡Ese juego ya ha comenzado!" } } diff --git a/lang/fr.json b/lang/fr.json index 306e829..3f9e1b1 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -69,6 +69,7 @@ "COUNTDOWN": "Countdown", "MONTHLY_TYPE": "Type mensuel", "HIDE_DATE": "Masquer la date", + "PAST_SIGNUPS": "Autoriser les inscriptions après la date / l'heure d'inscription", "SLOT": "Fente", "UNSAVED": "Vous avez des changements non enregistrés. Voulez-vous continuer?", "options": { @@ -203,6 +204,10 @@ "MAINTENANCE": "Le site sera en maintenance :TIME d'environ :DURATION heure(s).", "UNDER_MAINTENANCE": "est en maintenance", "YOURE_IN": "You're in! You've secured a spot for **:GAME** in **:SERVER**.", - "BACK_SOON": "Il sera bientôt de retour!" + "BACK_SOON": "Il sera bientôt de retour!", + "RESCHEDULED": "Le jeu a été reprogrammé", + "DELETED": "Le jeu a été supprimé", + "EDIT_PERMISSION": "Vous n'êtes pas autorisé à modifier ce jeu", + "ALREADY_STARTED": "Ce jeu a déjà commencé!" } } \ No newline at end of file diff --git a/lang/it.json b/lang/it.json index 0f7ce66..f3eb3c2 100644 --- a/lang/it.json +++ b/lang/it.json @@ -55,7 +55,7 @@ "RESERVED": "Prenotati", "WAITLISTED": "Riserve", "DESCRIPTION": "Descrizione", - "EDIT_LINK": "Puoi modificare il tuo gioco `:SERVER_NAME` -`:GAME_NAME` qui:", + "EDIT_LINK": "Puoi modificare il tuo gioco `: SERVER_NAME` -`: GAME_NAME` qui:", "MAX": "Massimo", "CHARACTERS": "Caratteri", "NO_PLAYERS": "Nessun giocatore", @@ -67,6 +67,7 @@ "COUNTDOWN": "Conto alla rovescia", "MONTHLY_TYPE": "Tipo mensile", "HIDE_DATE": "Nascondi data", + "PAST_SIGNUPS": "Consenti iscrizioni oltre la data / ora di iscrizione", "SLOT": "Fessura", "UNSAVED": "Hai modifiche non salvate. Vuoi procedere?", "options": { @@ -201,6 +202,10 @@ "MAINTENANCE": "Il sito sarà in manutenzione :TIME per circa :DURATION ora / e.", "UNDER_MAINTENANCE": "è in manutenzione", "YOURE_IN": "You're in! You've secured a spot for **:GAME** in **:SERVER**.", - "BACK_SOON": "Tornerà presto!" + "BACK_SOON": "Tornerà presto!", + "RESCHEDULED": "Il gioco è stato riprogrammato", + "DELETED": "Il gioco è stato cancellato", + "EDIT_PERMISSION": "Non sei autorizzato a modificare questo gioco", + "ALREADY_STARTED": "Quel gioco è già iniziato!" } } diff --git a/lang/ru.json b/lang/ru.json index 4cc4afd..8e2ac29 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -69,6 +69,7 @@ "COUNTDOWN": "Обратный отчет", "MONTHLY_TYPE": "Ежемесячный тип", "HIDE_DATE": "Скрыть дату", + "PAST_SIGNUPS": "Разрешить регистрацию после даты / времени регистрации", "SLOT": "слот", "UNSAVED": "У вас есть несохраненные изменения. Вы хотите продолжить?", "options": { @@ -203,6 +204,10 @@ "MAINTENANCE": "Сайт будет в обслуживании :TIME приблизительно :DURATION часов.", "UNDER_MAINTENANCE": "находится на обслуживании", "YOURE_IN": "You're in! You've secured a spot for **:GAME** in **:SERVER**.", - "BACK_SOON": "Скоро вернусь!" + "BACK_SOON": "Скоро вернусь!", + "RESCHEDULED": "Игра перенесена", + "DELETED": "Игра была удалена", + "EDIT_PERMISSION": "У вас нет разрешения на редактирование этой игры", + "ALREADY_STARTED": "Эта игра уже началась!" } } diff --git a/src/models/game-signups.ts b/src/models/game-signups.ts new file mode 100644 index 0000000..22c375d --- /dev/null +++ b/src/models/game-signups.ts @@ -0,0 +1,105 @@ +import db from "../db"; +import { ObjectID } from "mongodb"; +import { RSVP, Game } from "./game"; +import { Client, User } from "discord.js"; +import { ShardGuild } from "../processes/shard-manager"; + +const connection = db.connection; +const collection = "rsvps"; + +export interface GameRSVPModel { + gameId: string | number | ObjectID; + tag: string; + id: string; + timestamp: number; +} + +interface GameRSVPDataModel extends GameRSVPModel { + _id?: string | number | ObjectID; +} + +export class GameRSVP implements GameRSVPDataModel { + _id: string | number | ObjectID; + gameId: string | number | ObjectID; + tag: string; + id: string; + timestamp: number; + + constructor(rsvp: GameRSVPDataModel | RSVP) { + Object.entries(rsvp).forEach(([key, value]) => { + this[key] = value; + }); + } + + get data(): GameRSVPDataModel { + return { + _id: this._id, + gameId: this.gameId, + tag: this.tag, + id: this.id, + timestamp: this.timestamp + }; + } + + async save() { + if (!connection()) throw new Error("No database connection"); + const rsvp: GameRSVPDataModel = this.data; + delete rsvp._id; + const col = connection().collection(collection); + const result = await col.updateOne({ _id: this._id }, { $set: { ...rsvp } }, { upsert: true }); + return result; + } + + async delete() { + if (!connection()) throw new Error("No database connection"); + const result = await connection().collection(collection).deleteOne({ _id: new ObjectID(this._id) }); + return result; + } + + static async fetch(gameId: string | number | ObjectID): Promise { + if (!connection()) throw new Error("No database connection"); + const data = await connection().collection(collection).find({ gameId: { $in: [new ObjectID(gameId), gameId] } }).toArray(); + if (data) { + return data.map(grsvp => new GameRSVP(grsvp)); + } + else { + return []; + } + } + + static async fetchAllByUser(user: User): Promise { + if (!connection()) throw new Error("No database connection"); + const data = await connection().collection(collection).find({ $or: [ { id: user.id }, { tag: user.tag } ] }).toArray(); + if (data) { + return data.map(grsvp => new GameRSVP(grsvp)); + } + else { + return []; + } + } + + static async fetchRSVP(gameId: string | number | ObjectID, uid: string): Promise { + if (!connection()) throw new Error("No database connection"); + const query = { gameId: new ObjectID(gameId), $or: [ { id: uid }, { tag: uid } ] }; + const data = await connection().collection(collection).findOne(query); + if (data) return new GameRSVP(data); + else return null; + } + + static async deleteGame(gameId: string | number | ObjectID) { + if (!connection()) throw new Error("No database connection"); + return await connection().collection(collection).deleteMany({ gameId: { $in: [new ObjectID(gameId), gameId] } }); + } + + static async deleteUser(gameId: string | number | ObjectID, uid: string) { + if (!connection()) throw new Error("No database connection"); + const query = { gameId: new ObjectID(gameId), $or: [ { id: uid }, { tag: uid } ] }; + return await connection().collection(collection).deleteMany(query); + } + + static async deleteAllGames(gameIds: (string | number | ObjectID)[]) { + if (!connection()) throw new Error("No database connection"); + if (gameIds.length === 0) return { deletedCount: 0 }; + return await connection().collection(collection).deleteMany({ gameId: { $in: [ ...gameIds, ...gameIds.map(g => new ObjectID(g)) ] } }); + } +} diff --git a/src/models/game.ts b/src/models/game.ts index f254c82..c2a30e6 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -5,9 +5,10 @@ import "moment-recur-ts"; import db from "../db"; import aux from "../appaux"; -import ShardManager, { ShardGuild, ShardMember, ShardChannel } from "../processes/shard-manager"; +import ShardManager, { ShardGuild, ShardMember, ShardChannel, ShardUser } from "../processes/shard-manager"; import { io } from "../processes/socket"; import { GuildConfig } from "./guild-config"; +import { GameRSVP } from "./game-signups"; import config from "./config"; import cloneDeep from "lodash/cloneDeep"; @@ -55,8 +56,11 @@ export enum RescheduleMode { } export interface RSVP { + _id?: string | number | ObjectID; + gameId?: string | number | ObjectID; id?: string; tag: string; + timestamp?: number; } export interface GameModel { @@ -68,13 +72,14 @@ export interface GameModel { template: string | number | ObjectID; adventure: string; runtime: string; + duration: number; minPlayers: string; players: string; dm: RSVP; author: RSVP; + reserved: RSVP[]; where: string; description: string; - reserved: RSVP[]; method: GameMethod; customSignup: string; when: GameWhen; @@ -95,6 +100,7 @@ export interface GameModel { monthlyType: MonthlyType; clearReservedOnRepeat: boolean; rescheduled: boolean; + pastSignups: boolean; sequence: number; pruned?: boolean; createdTimestamp: number; @@ -132,13 +138,14 @@ export class Game implements GameModel { template: string | number | ObjectID; adventure: string; runtime: string; + duration: number; minPlayers: string; players: string; dm: RSVP; author: RSVP; + reserved: RSVP[]; where: string; description: string; - reserved: RSVP[]; method: GameMethod; customSignup: string; when: GameWhen; @@ -159,15 +166,19 @@ export class Game implements GameModel { monthlyType: MonthlyType = MonthlyType.WEEKDAY; clearReservedOnRepeat: boolean = false; rescheduled: boolean = false; + pastSignups: boolean = false; sequence: number = 1; pruned: boolean = false; createdTimestamp: number; updatedTimestamp: number; + slot: number = 0; client: Client; + guilds: ShardGuild[] = []; constructor(game: GameModel, guilds: ShardGuild[], client?: Client) { if (client) this.client = client; + if (guilds) this.guilds = guilds; let guildMembers: ShardMember[] = []; const gameEntries = Object.entries(game || {}); @@ -195,8 +206,6 @@ export class Game implements GameModel { }); } else if (key === "dm" && guildMembers) { this[key] = Game.updateDM(value, guildMembers); - } else if (key === "reserved" && guildMembers) { - this[key] = Game.updateReservedList(value, guildMembers); } else this[key] = value; } @@ -243,13 +252,14 @@ export class Game implements GameModel { template: this.template, adventure: this.adventure, runtime: this.runtime, + duration: this.duration, minPlayers: this.minPlayers, players: this.players, dm: this.dm, author: this.author, + reserved: this.reserved, where: this.where, description: this.description, - reserved: this.reserved, method: this.method, customSignup: this.customSignup, when: this.when, @@ -270,6 +280,7 @@ export class Game implements GameModel { monthlyType: this.monthlyType, clearReservedOnRepeat: this.clearReservedOnRepeat, rescheduled: this.rescheduled, + pastSignups: this.pastSignups, sequence: this.sequence, pruned: this.pruned, createdTimestamp: this.createdTimestamp, @@ -283,22 +294,22 @@ export class Game implements GameModel { return null; } - const game: GameModel = cloneDeep(this.data); + let game: GameModel = cloneDeep(this.data); try { let channel = this._channel; let guild = this._guild; - if (this.client && !guild) { - const sGuilds = await ShardManager.clientGuilds(this.client); - guild = sGuilds.find((g) => g.id === game.s); - } - - if (!guild && !this.client) { - guild = await new Promise(async (resolve) => { - const g = await ShardManager.refreshGuild(game.s); - resolve(g.find(g => g.id === game.s)); - }); + if (!guild) { + if (this.client) { + const sGuilds = await ShardManager.clientGuilds(this.client, [game.s]); + guild = sGuilds.find((g) => g.id === game.s); + } else { + guild = await new Promise(async (resolve) => { + const g = await ShardManager.refreshGuild(game.s); + resolve(g.find((g) => g.id === game.s)); + }); + } } if (!guild) { @@ -366,12 +377,26 @@ export class Game implements GameModel { }; } - game.reserved = game.reserved.filter((r) => r.tag); + const rsvps = await GameRSVP.fetch(game._id); + game.reserved = game._id ? rsvps.map((r) => r.data).sort((a, b) => (a.timestamp > b.timestamp ? 1 : -1)) : game.reserved.filter((r) => r.tag); + const checkDupes = game.reserved.filter((r, i) => game.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || rr.tag === r.tag) === i); + if (game.reserved.length > checkDupes.length) { + game.reserved = checkDupes; + rsvps.forEach((r, i) => { + if (rsvps.findIndex((rr) => (rr.id ? rr.id === r.id : false) || rr.tag === r.tag) < i) { + r.delete(); + } + }); + } let reserved: string[] = []; let waitlist: string[] = []; let rMentions: string[] = []; game.reserved.map((rsvp) => { + delete rsvp._id; + delete rsvp.timestamp; + delete rsvp.gameId; + if (rsvp.tag.trim().length === 0 && !rsvp.id) return; let member = guildMembers.find( (mem) => mem.user.tag.trim() === rsvp.tag.trim().replace("@", "") || mem.user.id == rsvp.tag.trim().replace(/[<@>]/g, "") || mem.user.id === rsvp.id @@ -397,6 +422,7 @@ export class Game implements GameModel { return rsvp; }); + game.duration = Game.runtimeToHours(this.runtime); const eventTimes = aux.parseEventTimes(game, { isField: true, }); @@ -495,12 +521,15 @@ export class Game implements GameModel { let message: Message; try { try { - if (game.messageId) message = await channel.messages.fetch(game.messageId); + if (game.messageId) message = await ShardManager.findMessage(this.client, guild.id, channel.id, game.messageId, dmmember, game.timestamp); // channel.messages.fetch(game.messageId); + // console.log(guild.id, channel.id, game.messageId, !!dmmember, game.timestamp, !!message); } catch (err) {} if (guildConfig.embeds) { - if (guildConfig.embedMentionsAbove) - msg = [Game.parseDiscord(game.description, guild, true), dmmember && dmmember.user.toString(), rMentions.join(" ")].filter((m) => m).join(" "); + if (guildConfig.embedMentionsAbove) { + const mentions = [Game.parseDiscord(game.description, guild, true), dmmember && dmmember.user.toString(), rMentions.join(" ")]; + msg = mentions.filter((m, i) => m && mentions.indexOf(m) === i).join(" "); + } } else embed = null; if (message) { @@ -508,13 +537,14 @@ export class Game implements GameModel { if (this.client) message = await ShardManager.clientMessageEdit(this.client, guild.id, channel.id, message.id, msg, embed); else message = await ShardManager.shardMessageEdit(guild.id, channel.id, message.id, msg, embed); } - } else if (channel) { - message = await channel.send(msg, embed); - if (message) { - await dbCollection.updateOne({ _id: new ObjectId(game._id) }, { $set: { messageId: message.id } }); - game.messageId = message.id; - } - } + } // else if (channel && !game.messageId && !(game).deleted && !game.pruned && game.timestamp >= new Date().getTime() + parseInt(game.reminder) * 60 * 1000) { + // message = await channel.send(msg, embed); + // if (message) { + // await dbCollection.updateOne({ _id: new ObjectId(game._id) }, { $set: { messageId: message.id } }); + // game.messageId = message.id; + // } + // } + else return; this.addReactions(message, guildConfig); @@ -548,9 +578,26 @@ export class Game implements GameModel { let message: Message; try { + if (inserted.insertedCount > 0) { + const updatedGame = new Game(game, this.guilds, this.client); + for (let i = 0; i < updatedGame.reserved.length; i++) { + const ru = updatedGame.reserved[i]; + let member = guildMembers.find( + (mem) => mem.user.tag.trim() === ru.tag.trim().replace("@", "") || mem.user.id == ru.tag.trim().replace(/[<@>]/g, "") || mem.user.id === ru.id + ); + const rsvp = new GameRSVP({ _id: new ObjectID(), gameId: inserted.insertedId, id: ru.id, tag: ru.tag, timestamp: new Date().getTime() }); + await rsvp.save(); + if (member) { + this.dmCustomInstructions(member.user); + } + } + } + if (guildConfig.embeds) { - if (guildConfig.embedMentionsAbove) - msg = [Game.parseDiscord(game.description, guild, true), dmmember && dmmember.user.toString(), rMentions.join(" ")].filter((m) => m).join(" "); + if (guildConfig.embedMentionsAbove) { + const mentions = [Game.parseDiscord(game.description, guild, true), dmmember && dmmember.user.toString(), rMentions.join(" ")]; + msg = mentions.filter((m, i) => m && mentions.indexOf(m) === i).join(" "); + } } else embed = null; message = await channel.send(msg, embed); @@ -558,6 +605,16 @@ export class Game implements GameModel { this.addReactions(message, guildConfig); } catch (err) { aux.log("InsertGameError:", "game.s", game.s, "game._id", game._id, err.message); + if (inserted.insertedCount > 0) { + await Game.hardDelete(inserted.insertedId); + inserted.insertedCount = 0; + } + + return { + _id: "", + message: null, + modified: false, + }; } let updated; @@ -578,7 +635,19 @@ export class Game implements GameModel { } } } else { - aux.log(`GameMessageNotPostedError:\n`, "s", game.s, "_id", game._id); + if (inserted.insertedCount > 0) { + aux.log(`GameMessageNotPostedError:\n`, "s", game.s, "_id", game._id); + await Game.hardDelete(inserted.insertedId); + } + if (dmmember) { + dmmember.send("The bot does not have sufficient permissions to post in the configured Discord channel"); + } + + return { + _id: "", + message: null, + modified: false, + }; } if (this.client) @@ -601,7 +670,13 @@ export class Game implements GameModel { if (game._id && options.force) { const dbCollection = connection().collection(collection); - await dbCollection.updateOne({ _id: new ObjectId(game._id) }, { $set: game }); + const result = await dbCollection.updateOne({ _id: new ObjectId(game._id) }, { $set: game }); + + return { + _id: "", + message: null, + modified: result.modifiedCount > 0, + }; } return { @@ -620,41 +695,58 @@ export class Game implements GameModel { const game = await connection() .collection(collection) .findOne({ _id: new ObjectId(gameId) }); + if (!game) return null; const guilds = sGuilds ? sGuilds : game.s ? (client ? await ShardManager.clientGuilds(client, [game.s]) : await ShardManager.shardGuilds({ guildIds: [game.s] })) : []; - return game ? new Game(game, guilds, client) : null; + const sGame = new Game(game, guilds, client); + if (sGame) { + await sGame.updateReservedList(); + return sGame; + } else return null; } static async fetchBy(key: string, value: any, client?: Client): Promise { - if (!connection()) { - aux.log("No database connection"); + try { + if (!connection()) { + aux.log("No database connection"); + return null; + } + const query: mongodb.FilterQuery = aux.fromEntries([[key, value]]); + const game: GameModel = await connection() + .collection(collection) + .findOne({ deleted: { $in: [null, false] }, ...query }); + if (!game) return null; + const guilds = client ? await ShardManager.clientGuilds(client, [game.s]) : await ShardManager.shardGuilds({ guildIds: [game.s] }); + const sGame = new Game(game, guilds, client); + if (sGame) { + await sGame.updateReservedList(); + return sGame; + } else return null; + } catch (err) { + aux.log("Game.fetchBy Error:", err); return null; } - const query: mongodb.FilterQuery = aux.fromEntries([[key, value]]); - const game: GameModel = await connection() - .collection(collection) - .findOne({ deleted: { $in: [null, false] }, ...query }); - const guilds = client ? await ShardManager.clientGuilds(client, [game.s]) : await ShardManager.shardGuilds({ guildIds: [game.s] }); - return game ? new Game(game, guilds, client) : null; } - static async fetchAllBy(query: mongodb.FilterQuery, client?: Client, sGuilds?: ShardGuild[]): Promise { + static async fetchAllBy(query: mongodb.FilterQuery, client?: Client, sGuilds?: ShardGuild[], includeDeleted: boolean = false): Promise { if (!connection()) { aux.log("No database connection"); return []; } const games: GameModel[] = await connection() .collection(collection) - .find({ deleted: { $in: [null, false] }, ...query }) + .find({ ...(includeDeleted ? null : { deleted: { $in: [null, false] } }), ...query }) .toArray(); const out: Game[] = []; for (let i = 0; i < games.length; i++) { const guilds = sGuilds ? sGuilds : client ? await ShardManager.clientGuilds(client, [games[i].s]) : await ShardManager.shardGuilds({ guildIds: [games[i].s] }); - out.push(new Game(games[i], guilds, client)); + const game = new Game(games[i], guilds, client); + await game.updateReservedList(); + out.push(game); } return out; } - static async fetchAllByLimit(query: mongodb.FilterQuery, limit: number, client?: Client): Promise { + static async fetchAllByLimit(query: mongodb.FilterQuery, limit: number, client?: Client, sGuilds?: ShardGuild[]): Promise { if (!connection()) { aux.log("No database connection"); return []; @@ -666,32 +758,14 @@ export class Game implements GameModel { .toArray(); const out: Game[] = []; for (let i = 0; i < games.length; i++) { - const guilds = client ? await ShardManager.clientGuilds(client, [games[i].s]) : await ShardManager.shardGuilds({ guildIds: [games[i].s] }); - out.push(new Game(games[i], guilds, client)); + const guilds = sGuilds ? sGuilds : client ? await ShardManager.clientGuilds(client, [games[i].s]) : await ShardManager.shardGuilds({ guildIds: [games[i].s] }); + let game = new Game(games[i], guilds, client); + await game.updateReservedList(); + out.push(game); } return out; } - static async softDeleteAllBy(query: mongodb.FilterQuery) { - if (!connection()) { - aux.log("No database connection"); - return null; - } - return await connection() - .collection(collection) - .updateMany({ deleted: { $in: [null, false] }, ...query }, { $set: { deleted: true } }); - } - - static async hardDeleteAllBy(query: mongodb.FilterQuery) { - if (!connection()) { - aux.log("No database connection"); - return null; - } - return await connection() - .collection(collection) - .deleteMany({ deleted: { $nin: [null, false] }, ...query }); - } - static async updateAllBy(query: mongodb.FilterQuery, update: any) { if (!connection()) { aux.log("No database connection"); @@ -750,6 +824,7 @@ export class Game implements GameModel { } } catch (err) { aux.log(err); + if (this.discordChannel) this.discordChannel.send("The bot does not have sufficient permissions to add reactions in this channel."); } } @@ -817,9 +892,18 @@ export class Game implements GameModel { return validDays; } + static runtimeToHours(runtime: string | number) { + let hours = 0, + x: RegExpExecArray; + if ((x = /[\d\.]/g.exec(runtime.toString().trim()))) { + if (x[0]) hours = parseInt(x[0]); + } + return hours; + } + public canReschedule() { const validDays = this.getWeekdays(); - const hours = isNaN(parseFloat(this.runtime.replace(/[^\d\.-]/g, "").trim())) ? 0 : Math.abs(parseFloat(this.runtime.replace(/[^\d\.-]/g, "").trim())); + const hours = this.duration !== null ? this.duration : Game.runtimeToHours(this.runtime); const gameEnded = this.timestamp + hours * 3600 * 1000 < new Date().getTime(); const nextDate = Game.getNextDate(moment(this.date), validDays, Number(this.frequency), this.monthlyType, this.xWeeks); const nextISO = `${nextDate.replace(/-/g, "")}T${this.time.replace(/:/g, "")}00${this.timezone >= 0 ? "+" : "-"}${aux.parseTimeZoneISO(this.timezone)}`; @@ -865,36 +949,85 @@ export class Game implements GameModel { delete data.messageId; delete data.reminderMessageId; const game = new Game(data, guilds, this.client); - const newGame = await game.save(); - if (newGame.message && newGame.modified) { - const del = await this.delete(); - if (del.modifiedCount == 0) { - const del2 = await Game.softDelete(id); - if (del2.modifiedCount == 0) { - this.rescheduled = true; - await this.save(); + try { + const newGame = await game.save(); + if (newGame.message && newGame.modified) { + const del = await this.delete(); + if (del.modifiedCount == 0) { + const del2 = await Game.softDelete(id); + if (del2.modifiedCount == 0) { + this.rescheduled = true; + await this.save(); + } } + if (this.client) + this.client.shard.send({ + type: "socket", + name: "game", + data: { action: "rescheduled", gameId: this._id, newGameId: newGame._id }, + }); + else io().emit("game", { action: "rescheduled", gameId: this._id, newGameId: newGame._id }); + return true; + } else { + await game.delete(); + return false; } + } catch (err) { + aux.log(err); + await game.delete(); + return false; } - if (this.client) - this.client.shard.send({ - type: "socket", - name: "game", - data: { action: "rescheduled", gameId: this._id, newGameId: newGame._id }, - }); - else io().emit("game", { action: "rescheduled", gameId: this._id, newGameId: newGame._id }); } - return true; } catch (err) { aux.log("GameRescheduleError:", err.message || err); return false; } } + static async; + + static async softDeleteAllBy(query: mongodb.FilterQuery) { + if (!connection()) { + aux.log("No database connection"); + return null; + } + return await connection() + .collection(collection) + .updateMany({ ...query }, { $set: { deleted: true } }); + } + + static async hardDeleteAllBy(query: mongodb.FilterQuery) { + if (!connection()) { + aux.log("No database connection"); + return { deletedCount: 0 }; + } + return await connection() + .collection(collection) + .deleteMany({ ...query }); + } + + static async deleteAllBy(query: mongodb.FilterQuery, client?: Client, sGuilds?: ShardGuild[]) { + if (!connection()) { + aux.log("No database connection"); + return { deletedCount: 0 }; + } + let games = await Game.fetchAllByLimit(query, 200, client, sGuilds); + let deletedCount = 0; + while (games.length > 0 && deletedCount < 2000) { + const gameIds = games.map((g) => g._id); + await GameRSVP.deleteAllGames(gameIds); + const result = await Game.hardDeleteAllBy({ _id: { $in: gameIds.map((gid) => new ObjectID(gid)) } }); + deletedCount += result.deletedCount; + games = await Game.fetchAllByLimit(query, 200, client); + } + return { deletedCount: deletedCount }; + } + static async hardDelete(_id: string | number | mongodb.ObjectID) { + await GameRSVP.deleteGame(_id); return await connection() .collection(collection) - .deleteOne({ _id: new ObjectId(_id), deleted: { $nin: [null, false] } }); + .deleteOne({ _id: new ObjectId(_id) }); } static async softDelete(_id: string | number | mongodb.ObjectID) { @@ -973,7 +1106,7 @@ export class Game implements GameModel { return result; } - async dmCustomInstructions(user: User) { + async dmCustomInstructions(user: User | ShardUser) { if (this.method === "automated" && this.customSignup.trim().length > 0 && this.discordGuild) { const guild = this.discordGuild; const guildMembers = await guild.members; @@ -1052,42 +1185,41 @@ export class Game implements GameModel { async updateReservedList() { let guildMembers: ShardMember[]; - let updated = false; try { - if (this.dm && typeof this.dm === "string") { - if (!guildMembers) guildMembers = this.discordGuild.members; - this.dm = Game.updateDM(this.dm, guildMembers); - updated = true; + const rsvps = await GameRSVP.fetch(this._id); + const t = new Date().getTime() - 100 * this.reserved.length; + if (!this.discordGuild) return; + if (!guildMembers) guildMembers = this.discordGuild.members; + const checkDupes = this.reserved.filter((r, i) => this.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || rr.tag === r.tag) === i); + if (this.reserved.length > checkDupes.length) { + this.reserved = checkDupes; } - } catch (err) { - aux.log(err.message); - } - try { - if (typeof this.reserved === "string") { - if (!guildMembers) guildMembers = await this.discordGuild.members; - this.reserved = Game.updateReservedList(this.reserved, guildMembers); - updated = true; + for (let i = 0; i < this.reserved.length; i++) { + try { + const res = cloneDeep(this.reserved[i]); + const member = guildMembers.find((m) => (this.reserved[i] && m.user.id === this.reserved[i].id) || m.user.tag === this.reserved[i].tag.trim()); + let rsvp = rsvps.find((r) => r._id === res._id || (r.id && r.id === res.id) || r.tag === res.tag); + if (!rsvp) rsvp = await GameRSVP.fetchRSVP(this._id, res.id || res.tag); + if (!rsvp) { + rsvp = new GameRSVP({ + _id: new ObjectID(res._id), + gameId: this._id, + id: member ? member.user.id : (rsvp && rsvp.id) || (res && res.id), + tag: member ? member.user.tag : res.tag, + timestamp: t + i * 100, + }); + await rsvp.save(); + } + if (rsvp) this.reserved[i] = rsvp.data; + } catch (err) { + aux.log("InsertRSVPError:", err); + } } + this.reserved = this.reserved.filter((r, i) => i === this.reserved.findIndex((ri) => ri.id === r.id || ri.tag === r.tag)); } catch (err) { - aux.log(err.message); + aux.log("UpdateReservedListError:", err); } - if (updated && this._id) this.save(); - } - - static updateReservedList(list: RSVP[] | string, guildMembers: ShardMember[]) { - if (Array.isArray(list)) return list; - let rsvps: RSVP[] = []; - const reserved = list.split(/\r?\n/); - reserved.forEach((r) => { - const rsvp: RSVP = { tag: r.trim() }; - const member = guildMembers.find((m) => m.user.tag === r.trim().replace("@", "") || m.user.username === r.trim().replace("@", "")); - if (member) { - rsvp.id = member.user.id; - } - rsvps.push(rsvp); - }); - rsvps = rsvps.filter((r) => r.tag); - return rsvps; + return this.data; } static updateDM(dm: RSVP | string, guildMembers: ShardMember[]) { @@ -1103,23 +1235,53 @@ export class Game implements GameModel { } } - async signUp(user: User) { + async signUp(user: User | ShardUser, t?: number) { const hourDiff = (new Date().getTime() - this.timestamp) / 1000 / 3600; - if (!this.reserved.find((r) => r.id === user.id || r.tag == user.tag) && hourDiff < 0) { - this.reserved.push({ id: user.id, tag: user.tag }); - this.save(); - this.dmCustomInstructions(user); - return true; + if (hourDiff < 0 || this.pastSignups || this.hideDate) { + let match = await GameRSVP.fetchRSVP(this._id, user.id); + if (match && !this.reserved.find((r) => r.id === match.id || r.tag === match.tag)) { + await GameRSVP.deleteUser(this._id, user.id); + match = null; + } + if (!match) { + const rsvp = new GameRSVP({ _id: new ObjectID(), gameId: this._id, id: user.id, tag: user.tag, timestamp: t || new Date().getTime() }); + await rsvp.save(); + await this.save(); + this.dmCustomInstructions(user); + return true; + } + return false; + } else { + if (!this.discordGuild) return; + const member = this.discordGuild.members.find((m) => m.user.tag === user.tag.trim() || m.user.id === user.id); + const guildConfig = await GuildConfig.fetch(this.s); + const lang = gmLanguages.find((l) => l.code === guildConfig.lang) || gmLanguages.find((l) => l.code === "en"); + if (member) member.send(lang.other.ALREADY_STARTED); } return false; } - async dropOut(user: User, guildConfig: GuildConfig) { + async dropOut(user: User | ShardUser, guildConfig: GuildConfig) { const hourDiff = (new Date().getTime() - this.timestamp) / 1000 / 3600; - if (this.reserved.find((r) => r.tag === user.tag || r.id === user.id) && guildConfig.dropOut && hourDiff < 0) { - this.reserved = this.reserved.filter((r) => r.tag && r.tag !== user.tag && !(r.id && r.id === user.id)); - this.save(); - return true; + if (guildConfig.dropOut) { + if (hourDiff < 0 || this.pastSignups || this.hideDate) { + const rsvps = await GameRSVP.fetch(this._id); + const frsvp = rsvps.filter((r) => r.id == user.id || r.tag == user.tag); + for (let i = 0; i < frsvp.length; i++) { + const rsvp = frsvp[i]; + await rsvp.delete(); + } + await this.save(); + await GameRSVP.deleteUser(this._id, user.id); + await GameRSVP.deleteUser(this._id, user.tag); + return true; + } else { + if (!this.discordGuild) return; + const member = this.discordGuild.members.find((m) => m.user.tag === user.tag.trim() || m.user.id === user.id); + const guildConfig = await GuildConfig.fetch(this.s); + const lang = gmLanguages.find((l) => l.code === guildConfig.lang) || gmLanguages.find((l) => l.code === "en"); + if (member) member.send(lang.other.ALREADY_STARTED); + } } return false; } diff --git a/src/processes/discord.ts b/src/processes/discord.ts index cc9053d..7c12110 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -1,5 +1,5 @@ import { TextChannel, Client, Message, User, GuildChannel, MessageEmbed, Permissions, Guild, CategoryChannel } from "discord.js"; -import { DeleteWriteOpResultObject, FilterQuery, ObjectId, UpdateWriteOpResult } from "mongodb"; +import { DeleteWriteOpResultObject, FilterQuery, ObjectId, UpdateWriteOpResult, ObjectID } from "mongodb"; import { GuildConfig, GuildConfigModel } from "../models/guild-config"; import { Game, GameMethod, gameReminderOptions } from "../models/game"; @@ -8,6 +8,7 @@ import aux from "../appaux"; import db from "../db"; import { ShardMember } from "./shard-manager"; import cloneDeep from "lodash/cloneDeep"; +import { GameRSVP } from "../models/game-signups"; const app: any = { locals: {} }; @@ -24,6 +25,7 @@ app.locals.langs = app.locals.supportedLanguages.langs let client = new Client(); let isReady = false; let connected = false; +let numSlices = client.shard.count * 2; client.on("debug", function (info) { if (info.indexOf("hit on route") >= 0) return; @@ -46,9 +48,10 @@ client.on("ready", async () => { sendGuildsToAPI(true); let i = 0; setInterval(async () => { + numSlices = client.shard.count * 2; + sendGuildsToAPI(true, i % numSlices); i++; - sendGuildsToAPI(i % client.shard.count === client.guilds.cache.array()[0].shardID); - }, 10 * 60 * 1000); + }, 20 * 60 * 1000); } if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { @@ -87,8 +90,16 @@ if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { name: channel.name, guild: channel.guild.id, parentID: channel.parentID, - members: channel.members.array().map((m) => m.user.id), + members: [], // channel.members.array().map((m) => m.user.id), everyone: channel.permissionsFor(channel.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), + botPermissions: [ + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.VIEW_CHANNEL) && "VIEW_CHANNEL", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.READ_MESSAGE_HISTORY) && "READ_MESSAGE_HISTORY", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.SEND_MESSAGES) && "SEND_MESSAGES", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.MANAGE_MESSAGES) && "MANAGE_MESSAGES", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.EMBED_LINKS) && "EMBED_LINKS", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", + ].filter((check) => check), }, }); }); @@ -103,8 +114,16 @@ if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { name: channel.name, guild: channel.guild.id, parentID: channel.parentID, - members: channel.members.map((m) => m.user.id), + members: [], // channel.members.map((m) => m.user.id), everyone: channel.permissionsFor(channel.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), + botPermissions: [ + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.VIEW_CHANNEL) && "VIEW_CHANNEL", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.READ_MESSAGE_HISTORY) && "READ_MESSAGE_HISTORY", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.SEND_MESSAGES) && "SEND_MESSAGES", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.MANAGE_MESSAGES) && "MANAGE_MESSAGES", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.EMBED_LINKS) && "EMBED_LINKS", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", + ].filter((check) => check), }, }); }); @@ -220,6 +239,14 @@ if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { }); if (process.env.DISCORD_LOGIC.toLowerCase() === "true" && oldUser.tag != newUser.tag) { + const rsvps = await GameRSVP.fetchAllByUser(oldUser); + for (let i = 0; i < rsvps.length; i++) { + const rsvp = rsvps[i]; + rsvp.id = newUser.id; + rsvp.tag = newUser.tag; + await rsvp.save(); + } + const games = await Game.fetchAllBy( { $or: [ @@ -338,6 +365,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (member) { isAdmin = !!( member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || + member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || member.roles.cache.find((r) => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) ); permission = guildConfig.memberHasPermission(member) || isAdmin; @@ -440,15 +468,36 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (member) member.send(embed); } else if (cmd === "add-channel" && isAdmin) { if (params[0]) { - const channel: string = params[0].replace(/\<\#|\>/g, ""); + let channel: string = params[0].replace(/\<\#|\>/g, ""); + if (channel.trim().length === params[0].trim().length) { + const c = message.guild.channels.cache.find((ch) => ch.name === channel.trim()); + if (c) channel = c.id; + } + if (channel.trim().length === params[0].trim().length) { + return (message.channel).send(`Channel not found!`); + } const channels = guildConfig.channels; - channels.push({ channelId: channel, gameTemplates: [guildConfig.defaultGameTemplate.id] }); + if (!channels.find((c) => c.channelId === channel)) channels.push({ channelId: channel, gameTemplates: [guildConfig.defaultGameTemplate.id] }); guildConfig .save({ channel: channels, }) .then((result) => { (message.channel).send(`${lang.config.CHANNEL_ADDED}`); + const addedChannel = message.guild.channels.cache.find((c) => c.id === channel); + if (addedChannel) { + const missingPermissions = [ + !addedChannel.permissionsFor(client.user.id).has(Permissions.FLAGS.VIEW_CHANNEL) && "VIEW_CHANNEL", + !addedChannel.permissionsFor(client.user.id).has(Permissions.FLAGS.READ_MESSAGE_HISTORY) && "READ_MESSAGE_HISTORY", + !addedChannel.permissionsFor(client.user.id).has(Permissions.FLAGS.SEND_MESSAGES) && "SEND_MESSAGES", + !addedChannel.permissionsFor(client.user.id).has(Permissions.FLAGS.MANAGE_MESSAGES) && "MANAGE_MESSAGES", + !addedChannel.permissionsFor(client.user.id).has(Permissions.FLAGS.EMBED_LINKS) && "EMBED_LINKS", + !addedChannel.permissionsFor(client.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", + ].filter((check) => check); + if (missingPermissions.length > 0) { + (message.channel).send(`The bot is missing the following permissions in ${addedChannel.toString()}: ${missingPermissions.join(", ")}`); + } + } }) .catch((err) => { aux.log(err); @@ -456,7 +505,14 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } } else if (cmd === "remove-channel" && isAdmin) { if (params[0]) { - const channel: string = params[0].replace(/\<\#|\>/g, ""); + let channel: string = params[0].replace(/\<\#|\>/g, ""); + if (channel.trim().length === params[0].trim().length) { + const c = message.guild.channels.cache.find((ch) => ch.name === channel.trim()); + if (c) channel = c.id; + } + if (channel.trim().length === params[0].trim().length) { + return (message.channel).send(`Channel not found!`); + } const channels = guildConfig.channels; if (channels.find((c) => c.channelId === channel)) { channels.splice( @@ -849,10 +905,20 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { .catch((err) => { aux.log(err); }); - } else if (cmd === "refresh-data" && member.user.tag === config.author) { - client.shard.send({ - type: "refresh" + } else if (cmd === "refresh" && member.user.tag === config.author) { + const guildId = params[0] ? (params[0] === "all" ? null : params[0]) : message.guild.id; + await client.shard.send({ + type: "refresh", + guildId: guildId, }); + if (params[0] === "all") message.channel.send(`Data refresh started for all servers`); + else { + const shards = await client.shard.broadcastEval(`this.guilds.cache.find(g => g.id === "${guildId}");`); + const guild = shards.find((s) => s); + if (guild) { + message.channel.send(`Data refresh started for the \`${guild.name}\` server`); + } + } } else { const response = await (message.channel).send("Command not recognized"); if (response) { @@ -877,20 +943,24 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { client.on("messageReactionAdd", async (reaction, user: User) => { try { const message = reaction.message; - const game = await Game.fetchBy("messageId", message.id, client); - if (game.method !== GameMethod.AUTOMATED) return; - if (game && user.id !== message.author.id) { - const guildConfig = await GuildConfig.fetch(game.s); - if (reaction.emoji.name === guildConfig.emojiAdd) { - reaction.users.remove(user); - game.signUp(user); - } - if (reaction.emoji.name === guildConfig.emojiRemove) { - reaction.users.remove(user); - game.dropOut(user, guildConfig); + const t = new Date().getTime(); + const guildConfig = await GuildConfig.fetch(reaction.message.guild.id); + if (user.id !== message.author.id && (reaction.emoji.name === guildConfig.emojiAdd || reaction.emoji.name === guildConfig.emojiRemove)) { + if (guildConfig.channel.length > 0 && guildConfig.channel.find((c) => c.channelId === reaction.message.channel.id)) reaction.users.remove(user); + const game = await Game.fetchBy("messageId", message.id, client); + if (game) { + if (game.method !== GameMethod.AUTOMATED) return; + if (reaction.emoji.name === guildConfig.emojiAdd) { + game.signUp(user, t); + } + if (reaction.emoji.name === guildConfig.emojiRemove) { + game.dropOut(user, guildConfig); + } } } - } catch (err) {} + } catch (err) { + aux.log("DiscordReactionError:", err); + } }); /** @@ -966,6 +1036,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { client.on("shardResume", (id) => { aux.log("Client: Shard Resumed", id); + sendGuildsToAPI(); }); client.on("invalidated", () => { @@ -982,6 +1053,29 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { aux.log("Database connected in shard!"); // Login the Discord bot client.login(process.env.TOKEN); + // const result = await Game.deleteAllBy({ s: "497043627432738817", adventure: "Mothership: A Carrion-Eater Company" }, client); + // console.log(result.deletedCount); + + // const games = await Game.fetchAllBy({}); + // let gameCounts = []; + // games.filter((g:any) => !g.deleted).forEach(g => { + // const index = gameCounts.findIndex(gc => gc.s === g.s && gc.adventure === g.adventure); + // if (index >= 0) { + // gameCounts[index] = { + // ...gameCounts[index], + // count: gameCounts[index].count+1 + // }; + // } + // else { + // gameCounts.push({ + // s: g.s, + // adventure: g.adventure, + // frequency: g.frequency, + // count: 1 + // }); + // } + // }); + // console.log(gameCounts.filter(gc => gc.count > 100)); } })(); @@ -1009,26 +1103,21 @@ const rescheduleOldGames = async (guildId?: string) => { timestamp: { $lt: new Date().getTime(), }, - $and: [ - { - frequency: { - $ne: "0", - }, - }, - { - frequency: { - $ne: null, - }, - }, - ], - $or: [ - { - rescheduled: false, - }, - { - rescheduled: null, - }, - ], + frequency: { + $nin: ["0", null], + }, + hideDate: { + $in: [false, null], + }, + rescheduled: { + $in: [false, null], + }, + pruned: { + $in: [false, null], + }, + deleted: { + $in: [false, null], + }, }; let guildIds = []; @@ -1095,7 +1184,6 @@ const rescheduleOldGames = async (guildId?: string) => { const pruneOldGames = async (guild?: Guild) => { let pruned: UpdateWriteOpResult; - let deleted: DeleteWriteOpResultObject; try { aux.log(`Pruning old games for ${guild ? `${guild.name} server` : "all servers"}`); const query: FilterQuery = { @@ -1150,11 +1238,11 @@ const pruneOldGames = async (guild?: Guild) => { const guildConfig = guildConfigs.find((gc) => gc.guild === game.s) || new GuildConfig(); if (!guildConfig) continue; - if (guildConfig.pruning && !game.pruned && game.discordChannel && new Date().getTime() - game.timestamp >= guildConfig.pruneIntDiscord * 24 * 3600 * 1000) { + if (!game.pruned && game.discordChannel && new Date().getTime() - game.timestamp >= guildConfig.pruneIntDiscord * 24 * 3600 * 1000) { if (game.messageId) { if (guildConfig.pruneIntDiscord < guildConfig.pruneIntEvents && new Date().getTime() - game.timestamp < guildConfig.pruneIntEvents * 24 * 3600 * 1000) { prunedIds.push(game._id); - prunedMessageIds.push(game.messageId); + if (guildConfig.pruning) prunedMessageIds.push(game.messageId); client.shard.send({ type: "socket", name: "game", @@ -1168,18 +1256,11 @@ const pruneOldGames = async (guild?: Guild) => { data: { action: "deleted", gameId: game._id }, }); } - gameChannelMessages.push({ guild: game.s, channel: game.c, message: game.messageId }); + if (guildConfig.pruning) gameChannelMessages.push({ guild: game.s, channel: game.c, message: game.messageId }); } if (game.reminderMessageId) { - gameChannelMessages.push({ guild: game.s, channel: game.c, message: game.messageId }); + if (guildConfig.pruning) gameChannelMessages.push({ guild: game.s, channel: game.c, message: game.messageId }); } - // if (game.pm) { - // const m = game.discordGuild.members.find(m => m.user.tag === game.dm.tag || m.user.id === game.dm.id); - // if (m && m.user.dmChannel) { - // const msg = m.user.dmChannel.messages.resolve(game.pm); - // if (msg) await msg.delete(); - // } - // } } else if (new Date().getTime() - game.timestamp >= guildConfig.pruneIntEvents * 24 * 3600 * 1000) { deletedIds.push(game._id); client.shard.send({ @@ -1206,9 +1287,9 @@ const pruneOldGames = async (guild?: Guild) => { { $set: { pruned: true, - messageId: null, - reminderMessageId: null, - pm: null, + // messageId: null, + // reminderMessageId: null, + // pm: null, }, } ); @@ -1222,13 +1303,15 @@ const pruneOldGames = async (guild?: Guild) => { }); if (pruned.modifiedCount > 0) aux.log(`${pruned.modifiedCount} old game(s) successfully pruned`); - deleted = await Game.hardDeleteAllBy({ - deleted: true, - timestamp: { - $lt: new Date().getTime() - 14 * 24 * 3600 * 1000, + const hardDeleted = await Game.deleteAllBy( + { + timestamp: { + $lt: new Date().getTime() - 14 * 24 * 3600 * 1000, + }, }, - }); - if (deleted.deletedCount > 0) aux.log(`${deleted.deletedCount} old game(s) successfully deleted`); + client + ); + if (hardDeleted.deletedCount > 0) aux.log(`${hardDeleted.deletedCount} old game(s) successfully deleted`); let count = 0; @@ -1280,12 +1363,12 @@ const pruneOldGames = async (guild?: Guild) => { const postReminders = async () => { const cTime = new Date().getTime(); - const reminderOptions = gameReminderOptions; - const remExprs = reminderOptions.map((r) => { + const remExprs = gameReminderOptions.map((r) => { const rt = parseInt(r); return { reminder: r, timestamp: { + $gte: cTime + (rt - 2) * 60 * 1000, $lte: cTime + rt * 60 * 1000, }, }; @@ -1296,16 +1379,19 @@ const postReminders = async () => { timestamp: { $gt: cTime, }, - $and: [ - { - $or: remExprs, - }, - { - reminded: { - $in: [false, null], - }, - }, - ], + hideDate: { + $in: [false, null], + }, + reminded: { + $in: [false, null], + }, + deleted: { + $in: [false, null], + }, + pruned: { + $in: [false, null], + }, + $or: remExprs, }; const supportedLanguages = require("../../lang/langs.json"); @@ -1335,7 +1421,7 @@ const postReminders = async () => { const filteredGames = games.filter((game) => { if (!client.guilds.cache.array().find((g) => g.id === game.s)) return false; if (game.timestamp - parseInt(game.reminder) * 60 * 1000 > new Date().getTime()) return false; - if ((new Date().getTime() - (game.timestamp - parseInt(game.reminder) * 60 * 1000)) / 1000 > 5 * 60) return false; + if ((new Date().getTime() - (game.timestamp - parseInt(game.reminder) * 60 * 1000)) / 1000 > 2 * 60) return false; if (!game.discordGuild) return false; if (!game.discordChannel) return false; if (game.reminded) return false; @@ -1347,6 +1433,8 @@ const postReminders = async () => { try { const reserved: string[] = []; const reservedUsers: ShardMember[] = []; + let dmMember: ShardMember; + let dm: string; try { const guildMembers = await game.discordGuild.members; @@ -1368,8 +1456,8 @@ const postReminders = async () => { }); const member = guildMembers.find((mem) => mem.user.tag === game.dm.tag.trim().replace("@", "") || mem.user.id === game.dm.id); - var dm = game.dm.tag.trim().replace("@", ""); - var dmMember = member; + dm = game.dm.tag.trim().replace("@", ""); + dmMember = member; if (member) dm = member.user.toString(); } catch (err) { console.log(err); @@ -1414,13 +1502,13 @@ const postReminders = async () => { ); dmEmbed.addField(lang.game.WHEN, siLabel, true); if (game.discordGuild) dmEmbed.addField(lang.game.SERVER, game.discordGuild.name, true); - dmEmbed.addField(lang.game.GM, dmMember ? (dmMember.nickname ? dmMember.nickname : dmMember.user && dmMember.user.username) : game.dm, true); + dmEmbed.addField(lang.game.GM, dmMember ? dmMember.user.toString() : game.dm.tag, true); dmEmbed.addField(lang.game.WHERE, where); for (const member of reservedUsers) { try { if (member) member.send(dmEmbed); - if (dmMember && dmMember.user && member && member.user && dmMember.user.username == member.user.username) dmMember = null; + if (dmMember && dmMember.user && member && member.user && dmMember.user.id == member.user.id) dmMember = null; } catch (err) { aux.log("PrivateMemberReminderError:", member && member.user && member.user.tag, err); } @@ -1465,10 +1553,12 @@ const postReminders = async () => { const apiGuildIds: any = {}; -const sendGuildsToAPI = (all: boolean = false) => { - const guilds = client.guilds.cache.array().filter((guild) => all || !apiGuildIds[guild.shardID] || !apiGuildIds[guild.shardID].includes(guild.id)); +const sendGuildsToAPI = (all: boolean = false, slice: number = null) => { + let guilds = client.guilds.cache.array().filter((guild) => all || !apiGuildIds[guild.shardID] || !apiGuildIds[guild.shardID].includes(guild.id)); + const sliceLimit = Math.ceil(guilds.length / numSlices); + if (slice !== null) guilds = guilds.slice(slice * sliceLimit, slice * sliceLimit + sliceLimit); if (guilds.length > 0) { - aux.log("Refreshing data for", guilds.length, "guilds"); + aux.log("Refreshing data for", guilds.length, "guilds", `(Shard: ${guilds[0].shardID})`, slice !== null ? `(Slice: ${slice + 1})` : null); client.shard.send({ type: "shard", name: "guilds", @@ -1507,8 +1597,16 @@ const guildMap = (guild: Guild) => { name: c.name, guild: c.guild.id, parentID: c.parentID, - members: c.members.map((m) => m.user.id), + members: [], // c.members.map((m) => m.user.id), everyone: c.permissionsFor(c.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), + botPermissions: [ + c.permissionsFor(client.user.id).has(Permissions.FLAGS.VIEW_CHANNEL) && "VIEW_CHANNEL", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.READ_MESSAGE_HISTORY) && "READ_MESSAGE_HISTORY", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.SEND_MESSAGES) && "SEND_MESSAGES", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.MANAGE_MESSAGES) && "MANAGE_MESSAGES", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.EMBED_LINKS) && "EMBED_LINKS", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", + ].filter((check) => check), })), roles: guild.roles.cache.array(), }; diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index 7cd7fab..fec40c9 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -2,6 +2,8 @@ import { ShardingManager, Role, MessageEmbed, Message, Client, TextChannel, Perm import { Express } from "express"; import { io } from "./socket"; import aux from "../appaux"; +import { RSVP, Game } from "../models/game"; +import { find } from "lodash"; type DiscordProcessesOptions = { app: Express; @@ -34,7 +36,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { if (message.type === "shard") { if (message.name === "guilds") { const guilds: any[] = message.data; - aux.log('Adding guild data to API memory', guilds.length) + aux.log('Adding data to API memory for', guilds.length, 'guild(s)') guilds.forEach((guild, i) => { const gdi = guildData.findIndex((g) => g.id === guild.id); if (gdi >= 0) { @@ -154,48 +156,61 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { } } if (message.type === "refresh") { - manager.broadcastEval(` - const guilds = this.guilds.cache.array(); - console.log("Force refreshing data for ", guilds.length, "guilds"); - this.shard.send({ - type: "shard", - name: "guilds", - data: guilds.map((guild) => { - return { - id: guild.id, - name: guild.name, - icon: guild.icon, - shardID: guild.shardID, - ownerID: guild.ownerID, - members: guild.members.cache.array(), - users: guild.members.cache.map((m) => ({ - id: m.user.id, - username: m.user.username, - tag: m.user.tag, - discriminator: m.user.discriminator, - avatar: m.user.avatar, - })), - memberRoles: guild.members.cache.map((m) => - m.roles.cache.map((r) => ({ - id: r.id, - name: r.name, - permissions: r.permissions, - })) - ), - channels: guild.channels.cache.array().map((c) => ({ - id: c.id, - type: c.type, - name: c.name, - guild: c.guild.id, - parentID: c.parentID, - members: c.members.map((m) => m.user.id), - everyone: c.permissionsFor(c.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), - })), - roles: guild.roles.cache.array(), - }; - }), - }); - `); + if (message.guildId) { + refreshGuild(message.guildId); + } + else { + manager.broadcastEval(` + const guilds = this.guilds.cache.array(); + console.log("Force refreshing data for ", guilds.length, "guilds"); + this.shard.send({ + type: "shard", + name: "guilds", + data: guilds.map((guild) => { + return { + id: guild.id, + name: guild.name, + icon: guild.icon, + shardID: guild.shardID, + ownerID: guild.ownerID, + members: guild.members.cache.array(), + users: guild.members.cache.map((m) => ({ + id: m.user.id, + username: m.user.username, + tag: m.user.tag, + discriminator: m.user.discriminator, + avatar: m.user.avatar, + })), + memberRoles: guild.members.cache.map((m) => + m.roles.cache.map((r) => ({ + id: r.id, + name: r.name, + permissions: r.permissions, + })) + ), + channels: guild.channels.cache.array().map((c) => ({ + id: c.id, + type: c.type, + name: c.name, + guild: c.guild.id, + parentID: c.parentID, + members: [], // c.members.map((m) => m.user.id), + everyone: c.permissionsFor(c.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), + botPermissions: [ + c.permissionsFor(client.user.id).has(Permissions.FLAGS.VIEW_CHANNEL) && "VIEW_CHANNEL", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.READ_MESSAGE_HISTORY) && "READ_MESSAGE_HISTORY", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.SEND_MESSAGES) && "SEND_MESSAGES", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.MANAGE_MESSAGES) && "MANAGE_MESSAGES", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.EMBED_LINKS) && "EMBED_LINKS", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", + ].filter((check) => check) + })), + roles: guild.roles.cache.array(), + }; + }), + }); + `); + } } } }); @@ -240,6 +255,7 @@ export interface ShardChannel { }; send: (content?: any, options?: any) => any; permissionsFor: (id: string, permission: number) => Promise; + botPermissions: string[]; } export interface ShardGuild { @@ -307,7 +323,7 @@ const clientGuilds = async (client: Client, guildIds: string[] = []) => { name: channel.name, type: channel.type, parentID: channel.parentID, - members: channel.members.map((m) => m.user.id), + members: [], // channel.members.map((m) => m.user.id), everyone: channel.permissionsFor(guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), messages: { fetch: async function (messageId: string) { @@ -344,6 +360,14 @@ const clientGuilds = async (client: Client, guildIds: string[] = []) => { } return false; }, + botPermissions: [ + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.VIEW_CHANNEL) && "VIEW_CHANNEL", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.READ_MESSAGE_HISTORY) && "READ_MESSAGE_HISTORY", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.SEND_MESSAGES) && "SEND_MESSAGES", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.MANAGE_MESSAGES) && "MANAGE_MESSAGES", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.EMBED_LINKS) && "EMBED_LINKS", + channel.permissionsFor(client.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", + ].filter((check) => check) }; return sChannel; }), @@ -437,7 +461,7 @@ const shardGuilds = async (filters: ShardFilters = {}) => { name: channel.name, type: channel.type, parentID: channel.parentID, - members: channel.members, + members: [], // channel.members, everyone: channel.everyone, messages: { fetch: async function (messageId: string) { @@ -517,56 +541,13 @@ const shardGuilds = async (filters: ShardFilters = {}) => { }, false); return result; }, + botPermissions: channel.botPermissions }; return sChannel; }), roles: guild.roles, refresh: async function () { - const call = ` - (async () => { - const guild = this.guilds.cache.get(${JSON.stringify(guild.id)}); - if (!guild) return; - const sGuild = { - id: guild.id, - name: guild.name, - icon: guild.icon, - shardID: guild.shardID, - ownerID: guild.ownerID, - members: guild.members.cache.array(), - users: guild.members.cache.map((m) => ({ - id: m.user.id, - username: m.user.username, - tag: m.user.tag, - discriminator: m.user.discriminator, - avatar: m.user.avatar, - })), - memberRoles: guild.members.cache.map((m) => - m.roles.cache.map((r) => ({ - id: r.id, - name: r.name, - permissions: r.permissions, - })) - ), - channels: guild.channels.cache.array().map((c) => ({ - id: c.id, - type: c.type, - name: c.name, - guild: c.guild.id, - parentID: c.parentID, - members: c.members.map((m) => m.user.id), - everyone: c.permissionsFor(c.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), - })), - roles: guild.roles.cache.array(), - }; - await this.shard.send({ - type: "shard", - name: "guilds", - data: [sGuild] - }); - return sGuild; - })(); - `; - const result = await discordClient().broadcastEval(call); + await refreshGuild(guild.id); }, }; return sGuild; @@ -582,6 +563,7 @@ const shardGuilds = async (filters: ShardFilters = {}) => { }; const refreshGuild = async function (guildId: string) { + const call = ` (async () => { const guild = this.guilds.cache.get(${JSON.stringify(guildId)}); @@ -613,8 +595,16 @@ const refreshGuild = async function (guildId: string) { name: c.name, guild: c.guild.id, parentID: c.parentID, - members: c.members.map((m) => m.user.id), + members: [], // c.members.map((m) => m.user.id), everyone: c.permissionsFor(c.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), + botPermissions: [ + c.permissionsFor(client.user.id).has(Permissions.FLAGS.VIEW_CHANNEL) && "VIEW_CHANNEL", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.READ_MESSAGE_HISTORY) && "READ_MESSAGE_HISTORY", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.SEND_MESSAGES) && "SEND_MESSAGES", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.MANAGE_MESSAGES) && "MANAGE_MESSAGES", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.EMBED_LINKS) && "EMBED_LINKS", + c.permissionsFor(client.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", + ].filter((check) => check) })), roles: guild.roles.cache.array(), }; @@ -694,6 +684,52 @@ const clientMessageEdit = async (client: Client, guildId: string, channelId: str return null; }; +const findMessage = async (client: Client, guildId: string, channelId: string, messageId: string, author: ShardMember, timestamp: number) => { + try { + if (client) { + const guild = client.guilds.cache.get(guildId); + if (guild) { + const channel = guild.channels.cache.get(channelId); + if (channel) { + let message = channel.messages.cache.find(m => m.id === messageId || !!m.embeds.find(e => e.timestamp === timestamp && (e.author.name === author.user.tag || e.author.name === author.user.username || e.author.name === author.nickname))); + if (message) return message; + message = await channel.messages.fetch(messageId); + if (message) return message; + const messages = await channel.messages.fetch({ limit: 100 }); + message = messages.find(m => m.id === messageId || !!m.embeds.find(e => e.timestamp === timestamp && (e.author.name === author.user.tag || e.author.name === author.user.username || e.author.name === author.nickname))); + if (message) return message; + } + } + } + else { + const qString = ` + (async () => { + const guild = this.guilds.cache.get(${JSON.stringify(guildId)}); + if (guild) { + const channel = guild.channels.cache.get(${JSON.stringify(channelId)}); + if (channel) { + let message = channel.messages.cache.find(m => m.id === "${messageId}" || !!m.embeds.find(e => e.timestamp === ${timestamp} && (e.author.name === "${author.user.tag}" || e.author.name === "${author.user.username}" || e.author.name === "${author.nickname}"))); + if (message) return message; + message = await channel.messages.fetch("${messageId}"); + if (message) return message; + const messages = await channel.messages.fetch({ limit: 100 }); + message = messages.find(m => m.id === "${messageId}" || !!m.embeds.find(e => e.timestamp === ${timestamp} && (e.author.name === "${author.user.tag}" || e.author.name === "${author.user.username}" || e.author.name === "${author.nickname}"))); + if (message) return message; + } + } + return null; + })(); + `; + return (await discordClient().broadcastEval(qString)).find((m) => m); + } + return null; + } + catch(err) { + aux.log(err); + return null; + } +}; + export default { processes: managerConnect, shardGuilds: shardGuilds, @@ -702,7 +738,8 @@ export default { shardMessageReact: shardMessageReact, shardMessageEdit: shardMessageEdit, clientMessageEdit: clientMessageEdit, - refreshGuild: refreshGuild + refreshGuild: refreshGuild, + findMessage: findMessage }; export function discordClient() { diff --git a/src/routes/api.ts b/src/routes/api.ts index e54234c..d1d4e4d 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1,4 +1,4 @@ -import discord, { Permissions, Role, ShardingManager, DataResolver } from "discord.js"; +import discord, { Permissions, Role, ShardingManager } from "discord.js"; import express from "express"; import moment from "moment"; import request from "request"; @@ -7,13 +7,14 @@ import cloneDeep from "lodash/cloneDeep"; import ShardManager, { ShardGuild, ShardMember, ShardChannel } from "../processes/shard-manager"; import { io } from "../processes/socket"; -import { Game, GameMethod, RescheduleMode, GameWhen, MonthlyType } from "../models/game"; +import { Game, GameMethod, RescheduleMode, GameWhen, MonthlyType, GameModel } from "../models/game"; import { SiteSettings } from "../models/site-settings"; import { GuildConfig, ChannelConfig } from "../models/guild-config"; import { Session } from "../models/session"; import { User } from "../models/user"; import config from "../models/config"; import aux from "../appaux"; +import { GameRSVP } from "../models/game-signups"; const apiVersion = process.env.VERSION; @@ -341,17 +342,17 @@ export default (options: APIRouteOptions) => { return role; }); guild.channelCategories = guild.channelCategories.map((channel) => { - delete channel.members; + // delete channel.members; delete channel.messages; return channel; }); guild.announcementChannels = guild.announcementChannels.map((channel) => { - delete channel.members; + // delete channel.members; delete channel.messages; return channel; }); guild.channels = guild.channels.map((channel) => { - delete channel.members; + // delete channel.members; delete channel.messages; return channel; }); @@ -362,6 +363,7 @@ export default (options: APIRouteOptions) => { token: req.session.api.access.access_token, account: result.account, user: userSettings.data, + version: apiVersion, }); }) .catch((err) => { @@ -396,12 +398,32 @@ export default (options: APIRouteOptions) => { const guild = result.account.guilds.find((g) => g.id == req.body.id); if (!guild) throw new Error("Guild not found"); if (!guild.isAdmin) throw new Error("You don't have permission to do that"); + req.body.channel = req.body.channel.filter(c => !isNaN(c.channelId)); for (const property in guildConfig) { if (property === "_id") continue; if (typeof req.body[property] != "undefined") guildConfig[property] = req.body[property]; } const saveResult = await guildConfig.save(); + const sGuilds = await ShardManager.shardGuilds({ + guildIds: [req.body.id], + }); + + if (sGuilds.length > 0) { + const sGuild = sGuilds[0]; + const member = sGuild.members.find((m) => m.id === result.account.user.id); + guildConfig.channels.forEach((c) => { + const sChannel = sGuild.channels.find((sc) => sc.id === c.channelId); + if (sChannel) { + const requiredPermissions = ["VIEW_CHANNEL", "READ_MESSAGE_HISTORY", "SEND_MESSAGES", "MANAGE_MESSAGES", "EMBED_LINKS", "ADD_REACTIONS"]; + const missingPermissions = requiredPermissions.filter((rp) => !sChannel.botPermissions.includes(rp)); + if (missingPermissions.length > 0) { + member.send(`The bot is missing the following permissions in <#${sChannel.id}>: ${missingPermissions.join(", ")}`); + } + } + }); + } + const langs = req.app.locals.langs; const selectedLang = guildConfig.lang; const lang = merge(cloneDeep(langs.find((lang: any) => lang.code === "en")), cloneDeep(langs.find((lang: any) => lang.code === selectedLang))); @@ -436,6 +458,7 @@ export default (options: APIRouteOptions) => { fetchAccount(req.session.api.access, { client: client, ip: req.app.locals.ip, + guilds: true, }) .then(async (result: any) => { const sGuilds: ShardGuild[] = result.sGuilds; @@ -452,12 +475,13 @@ export default (options: APIRouteOptions) => { } if (server) { - let guild: ShardGuild = await new Promise(async (resolve) => { - const g = await ShardManager.refreshGuild(server); - resolve(g.find((g) => g.id === server)); - }); - + let guild = sGuilds.find((g) => g.id === server); if (!guild && req.query.g) guild = game.discordGuild; + if (!guild) + guild = await new Promise(async (resolve) => { + const g = await ShardManager.refreshGuild(server); + resolve(g.find((g) => g.id === server)); + }); if (!guild) { guild = sGuilds.find((g) => g.id === server); @@ -629,12 +653,13 @@ export default (options: APIRouteOptions) => { } if (server) { - let guild: ShardGuild = await new Promise(async (resolve) => { - const g = await ShardManager.refreshGuild(server); - resolve(g.find((g) => g.id === server)); - }); - - if (!guild) guild = game.discordGuild; + let guild = sGuilds.find((g) => g.id === server); + if (!guild && req.query.g) guild = game.discordGuild; + if (!guild) + guild = await new Promise(async (resolve) => { + const g = await ShardManager.refreshGuild(server); + resolve(g.find((g) => g.id === server)); + }); if (guild) { let password: string; @@ -646,19 +671,21 @@ export default (options: APIRouteOptions) => { const isAdmin = member && (member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || + member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || member.roles.find((r) => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim())); const gcChannel = guildConfig.channel.find((gcc) => gcc.channelId === game.c) || { channelId: game.c, gameTemplates: [guildConfig.defaultGameTemplate.id], }; - if (!game.template) + if (!game.template) { game.template = ( guildConfig.gameTemplates.find((gt) => gt.id === gcChannel.gameTemplates[0]) || guildConfig.gameTemplates.find((gt) => gt.isDefault) || guildConfig.gameTemplates[0] ).id; + } const gameTemplate = guildConfig.gameTemplates.find((gt) => gt.id === game.template); - if (guildConfig && member && member.user.tag !== config.author) { + if (guildConfig && member && member.user.tag !== config.author && gameTemplate) { password = guildConfig.password; // A role is required to post on the server if (gameTemplate.role && !isAdmin) { @@ -690,6 +717,12 @@ export default (options: APIRouteOptions) => { throw new Error("Discord channel not found. Make sure your server has a text channel."); } + if (req.query.g) { + await GameRSVP.deleteGame(req.query.g); + game.reserved = req.body.reserved; + await game.updateReservedList(); + } + let data: any = { title: req.query.g ? req.app.locals.lang.buttons.EDIT_GAME : req.app.locals.lang.buttons.NEW_GAME, guild: guild.name, @@ -768,11 +801,14 @@ export default (options: APIRouteOptions) => { updatedGame .save() - .then((response) => { + .then(async (response) => { + updatedGame._id = response.modified ? response._id : null; + let uRes: GameModel; + if (!req.query.g) uRes = await updatedGame.updateReservedList(); res.json({ status: response.modified ? "success" : "error", token: req.session.api.access.access_token, - game: data, + game: { ...data, ...uRes }, _id: response.modified ? response._id : null, }); }) @@ -786,7 +822,7 @@ export default (options: APIRouteOptions) => { status: "error", token: req.session.api.access.access_token, game: data, - message: err.message, + message: err, code: 17, }); }); @@ -837,11 +873,12 @@ export default (options: APIRouteOptions) => { fetchAccount(req.session.api.access, { client: client, ip: req.app.locals.ip, + guilds: true, }) .then(async (result: any) => { try { if (req.query.g) { - const game = await Game.fetch(req.query.g); + const game = await Game.fetch(req.query.g, null, result.sGuilds); if (!game) throw new Error("Game not found"); game.delete({ sendWS: false }).then((response) => { res.json({ @@ -872,20 +909,69 @@ export default (options: APIRouteOptions) => { }); }); + router.post("/api/rsvp", async (req, res, next) => { + try { + const bearer = (req.headers.authorization || "").replace("Bearer ", "").trim(); + if (bearer.length < 10) throw new Error("Not authenticated"); + + const sGuilds = await ShardManager.shardGuilds({ + guildIds: [req.body.guild], + memberIds: [req.body.id], + }); + + const game = await Game.fetch(req.body.game, null, sGuilds); + if (game) { + const member = game.discordGuild.members.find((m) => m.id === req.body.id); + let rsvp = false; + if (!game.reserved.find((r) => r.id === member.user.id || r.tag === member.user.tag)) { + rsvp = await game.signUp(member.user); + } else { + const guildConfig = await GuildConfig.fetch(game.s); + rsvp = await game.dropOut(member.user, guildConfig); + } + + if (rsvp) + res.json({ + status: "success", + gameId: game._id, + reserved: game.reserved, + }); + else + res.json({ + status: "success", + past: true, + }); + } else { + res.json({ + status: "error", + message: "Game not found", + }); + } + } catch (err) { + console.log(err); + res.json({ + status: "error", + message: err, + }); + } + }); + router.post("/auth-api/rsvp", async (req, res, next) => { const token = req.session.api && req.session.api.access.access_token; + const t = new Date().getTime(); try { if (req.body.g) { - const game = await Game.fetch(req.body.g); - if (game) { - fetchAccount(req.session.api.access, { - client: client, - ip: req.app.locals.ip, - }) - .then(async (result: any) => { + fetchAccount(req.session.api.access, { + client: client, + ip: req.app.locals.ip, + guilds: true, + }) + .then(async (result: any) => { + const game = await Game.fetch(req.body.g, null, result.sGuilds); + if (game) { let rsvp = false; if (!game.reserved.find((r) => r.id === result.account.user.id || r.tag === result.account.user.tag)) { - rsvp = await game.signUp(result.account.user); + rsvp = await game.signUp(result.account.user, t); } else { const guildConfig = await GuildConfig.fetch(game.s); rsvp = await game.dropOut(result.account.user, guildConfig); @@ -904,21 +990,21 @@ export default (options: APIRouteOptions) => { token: token, past: true, }); - }) - .catch((err) => { - res.json({ - status: "error", - token: token, - message: `UserAuthError (1)`, - redirect: "/", - code: 22, - }); + } else { + throw new Error("Game not found (2)"); + } + }) + .catch((err) => { + res.json({ + status: "error", + token: token, + message: `UserAuthError (1)`, + redirect: "/", + code: 22, }); - } else { - throw new Error("Game not found (1)"); - } + }); } else { - throw new Error("Game not found (2)"); + throw new Error("Game not found (1)"); } } catch (err) { res.json({ @@ -1110,8 +1196,6 @@ export default (options: APIRouteOptions) => { games: [], }; - // console.log(guild.members); - guild.members.forEach((member) => { if ((id && member.user.id === id) || member.user.tag === tag || (member.user.username === username && member.user.discriminator === discriminator)) { guildInfo.member = member; @@ -1161,15 +1245,17 @@ export default (options: APIRouteOptions) => { if (member) { guild.userRoles = member.roles.map((r) => r.name); guild.isAdmin = !!( - member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || member.roles.find((r) => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) + member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || + member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || + member.roles.find((r) => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) ); guild.permission = guildConfig.shardMemberHasPermission(member) || guild.isAdmin; gcChannels.forEach((c) => { const gcc = guild.channels.find((gc) => gc.id === c.channelId); - if (gcc && (guild.permission || (gcc.members && gcc.members.includes(member.user.id)))) channels.push(gcc); + if (gcc && guild.permission /*|| (gcc.members && gcc.members.includes(member.user.id))*/) channels.push(gcc); }); channels = channels.filter((c) => c && member && (guild.isAdmin || !!guildConfig.shardMemberHasPermission(member, c.id))); - // console.log(guild.isAdmin, guild.permission); + // console.log(guild.name, guild.isAdmin, guild.permission, channels.length); } guild.announcementChannels = channels; @@ -1189,15 +1275,15 @@ export default (options: APIRouteOptions) => { const fGames: Game[] = await Game.fetchAllBy(gameOptions, null, sGuilds); // console.log(new Date().getTime() - fTime); - const games: any[] = []; - for (let i = 0; i < fGames.length; i++) { - const game = fGames[i]; - const dc = game.discordChannel; - if (dc && (dc.members || []).includes(id)) { - games.push(game); - } - } - games.forEach(async (game) => { + // const games: any[] = []; + // for (let i = 0; i < fGames.length; i++) { + // const game = fGames[i]; + // const dc = game.discordChannel; + // if (dc && (dc.members || []).includes(id)) { + // games.push(game); + // } + // } + fGames.forEach(async (game) => { if (!game.discordGuild) return; const date = Game.ISOGameDate(game); const parsed = aux.parseEventTimes(game); @@ -1207,9 +1293,15 @@ export default (options: APIRouteOptions) => { moment: { ...parsed, iso: date, - date: moment(date).utcOffset(parseInt(game.timezone)).format(config.formats.dateLong), - calendar: moment(date).utcOffset(parseInt(game.timezone)).calendar(), - from: moment(date).utcOffset(parseInt(game.timezone)).fromNow(), + date: moment(date) + .utcOffset(parseInt(`${game.timezone}`)) + .format(config.formats.dateLong), + calendar: moment(date) + .utcOffset(parseInt(`${game.timezone}`)) + .calendar(), + from: moment(date) + .utcOffset(parseInt(`${game.timezone}`)) + .fromNow(), }, reserved: game.reserved.filter((r) => r.tag), slot: game.reserved.findIndex((t) => t.tag.replace("@", "") === tag || t.id === id) + 1, @@ -1275,6 +1367,7 @@ interface AccountGuild { const fetchAccount = (token: any, options: AccountOptions) => { const client = options.client; + // const gTime = new Date().getTime(); return new Promise((resolve, reject) => { const requestData = { @@ -1303,42 +1396,43 @@ const fetchAccount = (token: any, options: AccountOptions) => { guilds: [], }; - // const fTime = new Date().getTime(); - let sGuilds: ShardGuild[] = await ShardManager.shardGuilds({ + let sGuilds: ShardGuild[] = []; + sGuilds = await ShardManager.shardGuilds({ memberIds: [id], }); - // console.log(new Date().getTime() - fTime); if (options.guilds) { - sGuilds.forEach((guild) => { - const guildInfo: AccountGuild = { - id: guild.id, - name: guild.name, - icon: guild.icon ? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png?size=128` : "/images/logo2.png", - permission: false, - isAdmin: false, - member: null, - roles: guild.roles, - userRoles: [], - channelCategories: guild.channels.filter((c) => c.type === "category"), - channels: guild.channels.filter((c) => c.type === "text"), - announcementChannels: [], - config: new GuildConfig({ guild: guild.id }), - games: [], - }; + sGuilds + .filter((g) => g) + .forEach((guild) => { + const guildInfo: AccountGuild = { + id: guild.id, + name: guild.name, + icon: guild.icon ? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png?size=128` : "/images/logo2.png", + permission: false, + isAdmin: false, + member: null, + config: new GuildConfig({ guild: guild.id }), + announcementChannels: [], + channels: guild.channels.filter((c) => c.type === "text"), + channelCategories: guild.channels.filter((c) => c.type === "category"), + games: [], + roles: guild.roles, + userRoles: [], + }; - guild.members.forEach((member) => { - if (member.id === id) { - guildInfo.member = member; - if (!options.search) account.guilds.push(guildInfo); + guild.members.forEach((member) => { + if (member.id === id) { + guildInfo.member = member; + if (!options.search) account.guilds.push(guildInfo); + } + }); + if (options.search) { + if (new RegExp(options.search, "gi").test(guild.name)) { + account.guilds.push(guildInfo); + } } }); - if (options.search) { - if (new RegExp(options.search, "gi").test(guild.name)) { - account.guilds.push(guildInfo); - } - } - }); account.guilds = account.guilds.filter((guild) => (!guild.config.hidden && !options.search) || config.author == tag); @@ -1352,7 +1446,6 @@ const fetchAccount = (token: any, options: AccountOptions) => { }; const guildConfigs = await GuildConfig.fetchAllBy(gcQuery); - // console.log(new Date().getTime() - fTime); for (let gi = 0; gi < account.guilds.length; gi++) { const guild: AccountGuild = account.guilds[gi]; @@ -1375,6 +1468,7 @@ const fetchAccount = (token: any, options: AccountOptions) => { guild.userRoles = member.roles.map((r) => r.name); guild.isAdmin = !!( member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || + member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || member.roles.find((r) => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) ); guild.permission = guildConfig.shardMemberHasPermission(member) || guild.isAdmin; @@ -1383,7 +1477,7 @@ const fetchAccount = (token: any, options: AccountOptions) => { let channels: ShardChannel[] = []; gcChannels.forEach((c) => { const gcc = guild.channels.find((gc) => gc.id === c.channelId); - if (gcc && (guild.permission || (gcc.members && gcc.members.includes(id)))) channels.push(gcc); + if (gcc && guild.permission /*|| (gcc.members && gcc.members.includes(id))*/) channels.push(gcc); }); channels = channels.filter((c) => c && member && (guild.isAdmin || !!guildConfig.shardMemberHasPermission(member, c.id))); @@ -1461,16 +1555,15 @@ const fetchAccount = (token: any, options: AccountOptions) => { } const fGames: Game[] = await Game.fetchAllBy(gameOptions, null, sGuilds); - // console.log(new Date().getTime() - fTime); - const games: any[] = []; - for (let i = 0; i < fGames.length; i++) { - const game = fGames[i]; - const dc = game.discordChannel; - if (dc && (dc.members || []).includes(id)) { - games.push(game); - } - } - games.forEach(async (game) => { + // const games: any[] = []; + // for (let i = 0; i < fGames.length; i++) { + // const game = fGames[i]; + // const dc = game.discordChannel; + // if (dc && (dc.members || []).includes(id)) { + // games.push(game); + // } + // } + fGames.forEach(async (game) => { if (!game.discordGuild) return; const date = Game.ISOGameDate(game); const parsed = aux.parseEventTimes(game); @@ -1480,9 +1573,15 @@ const fetchAccount = (token: any, options: AccountOptions) => { moment: { ...parsed, iso: date, - date: moment(date).utcOffset(parseInt(game.timezone)).format(config.formats.dateLong), - calendar: moment(date).utcOffset(parseInt(game.timezone)).calendar(), - from: moment(date).utcOffset(parseInt(game.timezone)).fromNow(), + date: moment(date) + .utcOffset(parseInt(`${game.timezone}`)) + .format(config.formats.dateLong), + calendar: moment(date) + .utcOffset(parseInt(`${game.timezone}`)) + .calendar(), + from: moment(date) + .utcOffset(parseInt(`${game.timezone}`)) + .fromNow(), }, reserved: game.reserved.filter((r) => r.tag), slot: game.reserved.findIndex((t) => t.tag.replace("@", "") === tag || t.id === id) + 1, diff --git a/src/routes/init.ts b/src/routes/init.ts index ca51632..8935f0a 100644 --- a/src/routes/init.ts +++ b/src/routes/init.ts @@ -198,6 +198,7 @@ export default (options: any) => { guild.permission = guildConfig.role ? member.roles.cache.find(r => r.name.toLowerCase().trim() === (guildConfig.role || "").toLowerCase().trim()) : true; guild.isAdmin = member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || + member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || member.roles.cache.find(r => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()); guild.config = guildConfig; return guild; diff --git a/src/routes/rss.ts b/src/routes/rss.ts index 60428a6..1491ca1 100644 --- a/src/routes/rss.ts +++ b/src/routes/rss.ts @@ -4,7 +4,7 @@ import express from "express"; import moment from "moment"; import config from "../models/config"; -import { Game, RSVP } from "../models/game"; +import { Game } from "../models/game"; export default () => { const router = express.Router(); @@ -47,7 +47,7 @@ export default () => { }, }; - const games: any[] = await Game.fetchAllBy(gameOptions); + const games: any[] = await Game.fetchAllBy(gameOptions, null, shardGuilds); res.type("application/xml"); res.status(200); @@ -140,7 +140,7 @@ export default () => { // gameOptions.$or = [{ dm: tag }, { reserved: { $regex: tag } }]; // } - const games = await Game.fetchAllBy(gameOptions); + const games = await Game.fetchAllBy(gameOptions, null, shardGuilds); try { var { error, value } = ics.createEvents( diff --git a/views/about.ejs b/views/about.ejs deleted file mode 100644 index ebfc327..0000000 --- a/views/about.ejs +++ /dev/null @@ -1,242 +0,0 @@ - - - - <%= title %> - <%= config.title %> - - <%- include('head', {account: null}); %> - - - <%- include('menu'); %> -
-
-

Links

-
- Want to contribute? Github - | Patreon - | PayPal -
-
- Need assistance? Join my Discord server. -
-
Change Log
-
-
-

Important Notes

-

Note: Games are automatically pruned 48 - 72 hours after the scheduled time. Or you can delete them manually.

-
-
-

Command List

-

All bot commands must be sent in the server being configured. The bot will not respond to direct messages.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Command                                                     - Default                  Description
General Commands
!scheduleDisplays the help menu
!schedule helpDisplays the help menu
General Configuration
!schedule configurationGet the bot configuration
!schedule role role nameAll RolesAssign a role as a prerequisite for posting games
!schedule manager-role role nameServer AdminsAssign a role to allow managing all server games. By default any server user with the Manage Server permission has this capability. This option allows you to provide this capability to users without that permission.
!schedule password passworddisabledConfigure a password for posting games
!schedule passwordRemove the password
!schedule lang enen (English)Set the bot's language.
Bot Configuration
!schedule embeds on/offonUse discord embeds for announcements
!schedule embed-color color#2196f3Set a discord embed color. Can be a color name like red or a hexadecimal color like #2196f3
!schedule embed-user-tags on/offoff - Include user tags in announcement embeds (Can occasionally glitch) -
!schedule emoji-sign-up ➕Set the emoji used for automated sign up
!schedule emoji-drop-out ➖Set the emoji used for automated sign up
!schedule toggle-drop-outoffEnable/disable the ability for players to drop out
!schedule prefix-char ?!Set the prefix character for sending bot commands
Game Configuration
!schedule add-channel #channel-namefirst text channelAdd a channel where games are posted (recommended)
!schedule remove-channel #channel-nameRemove a channel where games are posted
!schedule pruning on/offoff - Automatically delete old game announcements. As noted above, games over 48 hours past their scheduled date are automatically pruned from the database. However, by - default the announcements are not. -
!schedule private-remindersoffToggle whether the game reminders are sent to private messages.
!schedule reschedule-moderepost - Available modes:
-
    -
  • repost - Creates a new announcement post
  • -
  • update - Updates the original announcement post
  • -
-
Usage
!schedule linkRetrieve the link for posting games
-
-
-
-

Rescheduling

-

The rescheduled announcement will be posted X hours after the date/time of the current announcement, where - X is the duration (or runtime) of the game (+/- 1 hour). If no duration is entered, it defaults to 0 hours.

-
Weekly
-

You enter: 3/25 (Wed) 9 am; Reschedule: Weekly, Friday; Duration: 2 hours

-
    -
  • First announcement says 3/25 (Wed) 9 am, posted manually
  • -
  • Next announcement says 3/27 (Fri) 9 am, posted automatically at 3/25 11 am
  • -
  • Next announcement says 4/3 (Fri) 9 am, posted automatically at 3/27 11 am
  • -
-

The first announcement is always on the date you entered. Any following announcements will be based on the day(s) of the week selected.

-
-
-

About the bot

-

- The bot is deployed from Github to Amazon Web Services as a Node.js and discord.js application with MongoDB for data storage. -

-
-
-

Credits

-
- My Website -
-
Patrons/Donors
-
    -
  • JmanX
  • - <% pledges.forEach(pledge => { - %> -
  • - <%= pledge.patron.attributes.full_name %> - <% if (pledge.patron.attributes.vanity && pledge.patron.url && !pledge.patron.url.startsWith('https://www.patreon.com/user')) { %> - (<%= pledge.patron.attributes.vanity %>) - <% } %> -
  • - <% - }); %> -
-
Code/Language Contributors
-

- This list contains contributors who have added something to RPG Schedule. You can see the full list of contributors and their contributions - here. -

-
    -
  • wilywyrm (Recurring Games Added)
  • -
  • Manuoue (French Language Added)
  • -
  • ILDaviz (Italian Language Added)
  • -
  • Level8Broccoli (German Language Added)
  • -
  • Rudolf J (Russian Language Added)
  • -
-
-
- - diff --git a/views/calendar.ejs b/views/calendar.ejs deleted file mode 100644 index 9d0592a..0000000 --- a/views/calendar.ejs +++ /dev/null @@ -1,354 +0,0 @@ - - - - <%= lang.nav.CALENDAR %> - <%= config.title %> - - - - - - - <%- include('head', {account: account, env: env}); %> - - - - - - <%- include('menu'); %> -
- -
-
-

-
- -
-
- -
-
- -
-
-
-
-
- <% const weekdays = [0,1,2,3,4,5,6].map(d => { return moment().day(d).format('dddd') }); weekdays.forEach((d, i) => { %> -
- -
- <% }) %> -
-
-
-
-

-
-
-
-
- - <% include foot.ejs %> - - diff --git a/views/foot.ejs b/views/foot.ejs deleted file mode 100644 index e69de29..0000000 diff --git a/views/game.ejs b/views/game.ejs deleted file mode 100644 index 58d7bbb..0000000 --- a/views/game.ejs +++ /dev/null @@ -1,671 +0,0 @@ - - - - <%= title %> - <%= config.title %> - - <%- include('head', {account: null, env: env}); %> - - - - - - - - <% if (is.editgame) { %> - - <% } %> - - -<%- include('menu'); %> -
- -
- <% if (errors.other) { %> - - <% } %> -
-
-
- - - -
-
- - <% if (is.editgame) { %> - "> - - - <% } else { %> - - - <% } %> - <% if (!channels.find(ch => ch.id === c)) { %> -
- Channel not found on server -
- <% } %> -
-
-
-
- - -
-
- - - -
-
- - - <% if (errors.dm) { %> - Could not find GM tag in server users - <% } else { %> - <%= lang.game.labels.DISCORD_TAGS %> - <% } %> -
-
- -
- -
-
<%= lang.game.labels.HOURS %>
-
-
-
-
- - - <% if (errors.minPlayers) { %> - Invalid number of players - <% } %> -
-
- - - <% if (errors.maxPlayers) { %> - Invalid number of players - <% } %> -
-
- - -
- <% if (is.newgame) { %> -
- - -
-
- - -
- -
-
- <% } else { %> - - - <% } %> -
-
-
- - -
- <% if (is.editgame) { %> - - <% } %> -
-
- -
-
-
- - -
-
- -
-
-
UTC
-
- -
-
- -
-
-
- - -
-
- <% if (env.RESCHEDULING) { %> -
-
-
- - -
- <%= lang.game.NEXT_DATE %>: -
-
-
-
-
- -
-
- -
-
-
-
- - -
-
-
-
-
- - -
-
-
- -
- <% for(var i = 0; i < 7; i++) { %> - - <% } %> -
-
-
-
-
- <% } else { %> - - <% for(var i = 0; i < 7; i++) { %> - <% if (weekdays[i]) { %><% } %> - <% } %> - <% if (clearReservedOnRepeat) { %><% } %> - <% } %> -
-
-
- - -
- <% if(errors.reserved.length > 0) { %> -
- Could not find the following tags in server users: -
    - <% errors.reserved.forEach(r => { %> -
  • <%= r %>
  • - <% }); %> -
-
- <% } %> -
-
-
- - -
- - <%= lang.game.labels.DISCORD_FORMATTING %>. <%= lang.game.SIGNUP_INSTRUCTIONS_DESC %> - (<%= customSignup.length %> / <%= lang.game.MAX %> 1500 <%= lang.game.CHARACTERS %>) - -
-
-
-
-
- - - <%= lang.game.labels.DISCORD_FORMATTING %>. (<%= description.length %> / <%= lang.game.MAX %> 1500 <%= lang.game.CHARACTERS %>) -
-
-
-
-   -
- <% if (is.newgame) { %> -
-

- -

-
- <% } %> - <% if (is.editgame) { %> -
-

- -

-

- -

-

- <%= lang.buttons.NEW_GAME %> -

-
-

- -

-
- <% } %> -
-
- - - \ No newline at end of file diff --git a/views/games.ejs b/views/games.ejs deleted file mode 100644 index 2607a50..0000000 --- a/views/games.ejs +++ /dev/null @@ -1,254 +0,0 @@ - - - - - <% if (account.viewing.games) { %> - <%= lang.nav.UPCOMING_GAMES %> - <% } else if (account.viewing.dashboard) { %> - <%= lang.nav.MY_GAMES %> - <% } else if (account.viewing.server) { %> - <%= lang.nav.MANAGE_SERVER %> - <% } %> - - <%= config.title %> - - - <%- include('head', {account: account, env: env}); %> - - - - - - - - - -<%- include('menu'); %> -
- - <% account.guilds.filter(guild => !account.viewing.games || guild.games.length > 0).forEach(guild => { %> -
-

- - <%= guild.name %> - <% if ((account.viewing.dashboard || account.viewing.server) && (guild.permission || guild.isAdmin) && guild.announcementChannels.length > 0) { %> - <%= lang.buttons.NEW_GAME %> - <% } %> -

-
- <% if (guild.games.filter(game => account.viewing.server || account.viewing.dashboard || game.discordChannel.permissionsFor(account.user.id).has(account.permissions.VIEW_CHANNEL)).length === 0) { %> -
- <% if (account.viewing.dashboard) { %> - <%= lang.dashboard.EMPTY_MY_GAMES %> - <% } else if (account.viewing.games || account.viewing.server) { %> - <%= lang.dashboard.EMPTY_UPCOMING_GAMES %> - <% } %> -
- <% } %> - <% guild.games.filter(game => account.viewing.server || account.viewing.dashboard || game.discordChannel.permissionsFor(account.user.id).has(account.permissions.VIEW_CHANNEL)).forEach(game => { %> - <% if ((account.viewing.server && (guild.isAdmin || account.user.tag === config.author)) || (account.viewing.dashboard && account.user.tag === game.dm)) { %> -
-

<%= game.adventure %>

- <% if (account.viewing.server) { %> -
- <%= lang.game.GM %>: - <%= game.dm.replace(/#\d{4}/g, '') %> -
- <% } %> - <% if (game.hideDate) { %> -
- <%= lang.game.WHEN %>: - <%= lang.game.labels.TBD %> -
- <% } else { %> -
- <%= lang.game.WHEN %>: - <% if (game.timestamp > new Date().getTime()) {%> - <%= game.moment.calendar %> (<%= game.moment.from %>) - <% } else { %> - <%= game.moment.date %> (<%= game.moment.from %>) - <% } %> -
- - <% } %> -
<%= lang.game.RESERVED %>: <%= Math.min(parseInt(game.players), game.reserved.trim().length > 0 ? game.reserved.trim().split("\n").length : 0)%> / <%= game.players %>
-
<%= lang.game.WAITLISTED %>: <%= Math.max(parseInt(game.players), game.reserved.trim().length > 0 ? game.reserved.trim().split("\n").length : 0) - parseInt(game.players)%>
-
- <% } else if (account.viewing.games) { %> -
-

<%= game.adventure %>

-
- <%= lang.game.GM %>: - <%= game.dm.replace(/#\d{4}/g, '') %> -
- <% if (game.hideDate) { %> -
- <%= lang.game.WHEN %>: - <%= lang.game.labels.TBD %> -
- <% } else { %> -
- <%= lang.game.WHEN %>: - <% if (game.timestamp > new Date().getTime()) {%> - <%= game.moment.calendar %> (<%= game.moment.from %>) - <% } else { %> - <%= game.moment.date %> (<%= game.moment.from %>) - <% } %> -
- - <% } %> -
- <%= lang.game.WHERE %>: - <%= game.where %> -
-
- <%= lang.game.RESERVED %>: - <%= Math.min(parseInt(game.players), game.reserved.trim().length > 0 ? game.reserved.trim().split("\n").length : 0)%> / <%= game.players %> - <% if (game.signedup) { %> - (Slot #<%= game.slot %>) - <% } %> -
-
- <%= lang.game.WAITLISTED %>: - <%= Math.max(parseInt(game.players), game.reserved.trim().length > 0 ? game.reserved.trim().split("\n").length : 0) - parseInt(game.players)%> - <% if (game.waitlisted) { %> - (Slot #<%= game.slot %>) - <% } %> -
-
- ### <%= game.adventure %> - - **<%= lang.game.DATE %>:** <%= game.hideDate ? 'TBD' : '%DATE%' %> - - **<%= lang.game.RUN_TIME %>:** <%= game.runtime %> <%= lang.game.labels.HOURS %> - - **<%= lang.game.WHERE %>:** <%= game.where %> - - **<%= lang.game.DESCRIPTION %>:** - - <%= game.description %> -
- <% if (game.method === "automated") { %> -
- <% if (!game.signedup && !game.waitlisted) { %> - <%= lang.buttons.SIGN_UP %> - <% } else { %> - <%= lang.buttons.DROP_OUT %> - <% } %> -
- <% } else { %> -
- -
- <% } %> -
- <% } %> - <% }); %> -
- <% }); %> - <% if (account.guilds.filter(guild => !account.viewing.games || guild.games.length > 0).length === 0) { %> - <% if (account.viewing.games || account.viewing.server) { %> -
-

<%= lang.dashboard.EMPTY_UPCOMING_GAMES %>

- <%= lang.nav.MY_GAMES %> -
- <% } else if (account.viewing.dashboard) { %> -
-

<%= lang.dashboard.NO_GAMES %>

- <%= lang.nav.INVITE %> -
- <% } %> - <% } %> -
- - - \ No newline at end of file diff --git a/views/home.ejs b/views/home.ejs deleted file mode 100644 index dfe91bc..0000000 --- a/views/home.ejs +++ /dev/null @@ -1,15 +0,0 @@ - - - - <%= config.title %> - <%- include('head', {account: null, env: null}); %> - - - -
-

- -

-
- - diff --git a/views/menu.ejs b/views/menu.ejs deleted file mode 100644 index 638645d..0000000 --- a/views/menu.ejs +++ /dev/null @@ -1,134 +0,0 @@ - From bcfe988670f64db94276e9846d6671b0f64ec85a Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Mon, 13 Jul 2020 14:50:09 -0500 Subject: [PATCH 17/53] Guild RSS --- src/models/config.ts | 1 + src/routes/rss.ts | 156 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 127 insertions(+), 30 deletions(-) diff --git a/src/models/config.ts b/src/models/config.ts index 78847ce..a272e63 100644 --- a/src/models/config.ts +++ b/src/models/config.ts @@ -27,6 +27,7 @@ export default { login: { path: "/login" }, logout: { path: "/logout" }, rss: { path: "/rss/:uid" }, + guildRss: { path: "/rss/:uid/:guildId" }, ics: { path: "/ics/:uid.ics" }, timezone: { convert: { path: "/tz/:time/:tz" }, diff --git a/src/routes/rss.ts b/src/routes/rss.ts index 1491ca1..e486f6f 100644 --- a/src/routes/rss.ts +++ b/src/routes/rss.ts @@ -9,6 +9,99 @@ import { Game } from "../models/game"; export default () => { const router = express.Router(); + router.use(config.urls.guildRss.path, async (req, res, next) => { + try { + const { guildId, uid } = req.params; + const guilds = []; + + let tag = ""; + + const shardGuilds = await ShardManager.shardGuilds({ + guildIds: [guildId], + memberIds: [uid] + }); + + shardGuilds.forEach((guild) => { + if (guild.id !== guildId) return; + guild.members.forEach((member) => { + if (member.id !== uid) return; + tag = member.user.tag; + guilds.push({ + id: guild.id, + name: guild.name, + }); + }); + }); + + const gameOptions: any = { + s: { + $in: guilds.reduce((i, g) => { + i.push(g.id); + return i; + }, []), + }, + timestamp: { + $gt: new Date().getTime(), + }, + dm: { + $ne: tag, + }, + }; + + const games: any[] = await Game.fetchAllBy(gameOptions, null, shardGuilds); + + res.type("application/xml"); + res.status(200); + res.send(` + + + RPG Schedule + https://www.rpg-schedule.com + Game scheduling bot for Discord + + https://www.rpg-schedule.com/images/logo2.png + RPG Schedule + https://www.rpg-schedule.com + + ${games + .map((game) => { + if (!game.discordGuild) return ""; + + const date = Game.ISOGameDate(game); + game.moment = { + raw: `${game.date} ${game.time} UTC${game.timezone < 0 ? "-" : "+"}${Math.abs(game.timezone)}`, + iso: date, + date: moment(date).utcOffset(parseInt(game.timezone)).format(config.formats.dateLong), + calendar: moment(date).utcOffset(parseInt(game.timezone)).calendar(), + from: moment(date).utcOffset(parseInt(game.timezone)).fromNow(), + }; + + game.slot = game.reserved.findIndex((t) => t.id === uid || t.tag.trim().replace("@", "") === tag) + 1; + game.signedup = game.slot > 0 && game.slot <= parseInt(game.players); + game.waitlisted = game.slot > parseInt(game.players); + + return ` + + ${game.adventure} + https://www.rpg-schedule.com/games/upcoming + https://www.rpg-schedule.com/games/${game._id.toString().slice(-12)} + + Discord Server: ${(guilds.find((g) => g.id === game.s) || {}).name}

GM: ${game.dm.tag}

Where: ${game.where.replace(/\&/g, "&")}

When: ${ + game.moment.date + }

${game.description.trim().replace(/\&/g, "&").replace(/\r?\n/g, "
")}

]]> +
+
`; + }) + .join("\n")} + +
+
`); + } catch (err) { + console.log(err); + res.sendStatus(500); + } + }); + router.use(config.urls.rss.path, async (req, res, next) => { try { const uid = req.params.uid; @@ -136,40 +229,43 @@ export default () => { }, }; - // if (signedup) { - // gameOptions.$or = [{ dm: tag }, { reserved: { $regex: tag } }]; - // } - const games = await Game.fetchAllBy(gameOptions, null, shardGuilds); try { - var { error, value } = ics.createEvents( - games - .filter((game) => { + const events = games + .filter((game) => { + return signedup ? game.dm.id === uid || game.dm.tag === tag || game.reserved.find((r) => r.id === uid || r.tag === tag) : true; + }) + .map((game) => { + const d = new Date(game.timestamp); + return { + title: game.adventure, + start: [d.getFullYear(), d.getMonth() + 1, d.getDate(), d.getHours(), d.getMinutes()], + duration: { hours: parseFloat(game.runtime) }, + description: [game.description].filter((s) => s.trim()).join("\n\n"), + categories: ["RPG Schedule", game.discordGuild.name], + location: game.where, + organizer: { name: game.dm.tag, email: `noreply@rpg-schedule.com` }, + attendees: game.reserved + .filter((r) => r.tag.trim().length > 0) + .map((r, i) => ({ + name: r.tag, + rsvp: parseInt(game.players) - i > 0, + email: `noreply@rpg-schedule.com`, + })), + sequence: game.sequence, + }; + }); + if (req.query.json) { + res.setHeader("Content-Type", "application/json"); + res.json( + games.filter((game) => { return signedup ? game.dm.id === uid || game.dm.tag === tag || game.reserved.find((r) => r.id === uid || r.tag === tag) : true; - }) - .map((game) => { - const d = new Date(game.timestamp); - return { - uid: `${game._id}@rpg-schedule.com`, - title: game.adventure, - start: [d.getFullYear(), d.getMonth() + 1, d.getDate(), d.getHours(), d.getMinutes()], - duration: { hours: game.runtime }, - description: game.description, - categories: ["RPG Schedule", game.discordGuild.name], - location: game.where, - organizer: { name: game.dm.tag, email: `${(game.dm.tag || "").replace(/[^a-z0-9_.]/gi, "")}@rpg-schedule.com` }, - attendees: game.reserved - .filter((r) => r.tag.trim().length > 0) - .map((r, i) => ({ - name: r, - rsvp: parseInt(game.players) - i > 0, - email: `${(r.id || "").replace(/[^a-z0-9_.]/gi, "")}@rpg-schedule.com`, - })), - sequence: game.sequence, - }; - }) - ); + }).map(game => game.data) + ); + return; + } + var { error, value } = ics.createEvents(events); } catch (err) { console.log(err.message); } From 8b615055a5936d71cf317e762ec29f289d30506c Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 14 Jul 2020 09:54:19 -0500 Subject: [PATCH 18/53] Language Updates --- lang/de-ch.json | 3 ++- lang/de.json | 3 ++- lang/en.json | 3 ++- lang/es.json | 5 +++-- lang/fr.json | 3 ++- lang/it.json | 3 ++- lang/ru.json | 3 ++- 7 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lang/de-ch.json b/lang/de-ch.json index 7dadf94..36c4750 100644 --- a/lang/de-ch.json +++ b/lang/de-ch.json @@ -195,7 +195,8 @@ "PASSWORD_CLEAR": "Passwort entfernen", "TOGGLE_DROP_OUT": "Aktivieren/Deaktivieren, ob sich Spieler austragen dürfen.", "LANG": "Wähle die Sprache des Bots. Unterstützt:", - "RESCHEDULE_MODE": "Optionen: `repost` (standard, erstellt einen neuen Ankündigungsbeitrag), `update` (aktualisiert den originalen Ankündigungsbeitrag)" + "RESCHEDULE_MODE": "Optionen: `repost` (standard, erstellt einen neuen Ankündigungsbeitrag), `update` (aktualisiert den originalen Ankündigungsbeitrag)", + "BOT_PERMISSIONS": "Zeigt an, welche Berechtigungen dem Bot im desginierten Kanal fehlen" } }, "other": { diff --git a/lang/de.json b/lang/de.json index 38f4567..102f5ee 100644 --- a/lang/de.json +++ b/lang/de.json @@ -195,7 +195,8 @@ "PASSWORD_CLEAR": "Passwort entfernen", "TOGGLE_DROP_OUT": "Aktivieren/Deaktivieren, ob sich Spieler austragen dürfen.", "LANG": "Wähle die Sprache des Bots. Unterstützt:", - "RESCHEDULE_MODE": "Optionen: `repost` (standard, erstellt einen neuen Ankündigungsbeitrag), `update` (aktualisiert den originalen Ankündigungsbeitrag)" + "RESCHEDULE_MODE": "Optionen: `repost` (standard, erstellt einen neuen Ankündigungsbeitrag), `update` (aktualisiert den originalen Ankündigungsbeitrag)", + "BOT_PERMISSIONS": "Zeigt an, welche Berechtigungen die Bot in dem bezeichneten Platz Kanal fehlt." } }, "other": { diff --git a/lang/en.json b/lang/en.json index ee4eeab..6350b28 100644 --- a/lang/en.json +++ b/lang/en.json @@ -195,7 +195,8 @@ "PASSWORD_CLEAR": "Remove the password", "TOGGLE_DROP_OUT": "Enable/disable the ability for players to drop out", "LANG": "Set the bot's language. Supported:", - "RESCHEDULE_MODE": "Options: `repost` (default, creates new announcement post), `update` (updates original announcement post)" + "RESCHEDULE_MODE": "Options: `repost` (default, creates new announcement post), `update` (updates original announcement post)", + "BOT_PERMISSIONS": "Shows which permissions the bot is missing in the desginated channel" } }, "other": { diff --git a/lang/es.json b/lang/es.json index 9b61cb3..681d7e1 100644 --- a/lang/es.json +++ b/lang/es.json @@ -195,10 +195,11 @@ "PASSWORD_CLEAR": "Eliminar la contraseña", "TOGGLE_DROP_OUT": "Activa/Desactiva la opcion a los jugadores de desconectarse", "LANG": "Pon el lenguaje del bot. Supported:", - "RESCHEDULE_MODE": "Options: `repost` (default, crea u nuevo anuncio), `update` (actualiza el post original)" + "RESCHEDULE_MODE": "Options: `repost` (default, crea u nuevo anuncio), `update` (actualiza el post original)", + "BOT_PERMISSIONS": "Muestra qué permisos falta el bot en el canal designado" } }, - "messages": { + "other": { "DM_WAITLIST": "Estas ahora mismo en el slot :NUM de reservados.", "DM_INSTRUCTIONS": "Mensaje para el :DM for :EVENT", "MAINTENANCE": "The site will be under maintenance :TIME for approximately :DURATION hour(s).", diff --git a/lang/fr.json b/lang/fr.json index 3f9e1b1..5b01ea7 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -195,7 +195,8 @@ "PASSWORD_CLEAR": "Supprimer le mot de passe", "TOGGLE_DROP_OUT": "Activer / désactiver la possibilité pour les joueurs de se désinscrire", "LANG": "Définissez la langue du bot. Prise en charge:", - "RESCHEDULE_MODE": "" + "RESCHEDULE_MODE": "Options: `repost` (par défaut, crée un nouveau message d'annonce), `update` (met à jour le message d'annonce d'origine)", + "BOT_PERMISSIONS": "Montre quelles autorisations le bot manque dans le canal désigné" } }, "other": { diff --git a/lang/it.json b/lang/it.json index f3eb3c2..e7b1280 100644 --- a/lang/it.json +++ b/lang/it.json @@ -193,7 +193,8 @@ "PASSWORD_CLEAR": "Rimuovi la password", "TOGGLE_DROP_OUT": "Abilita/disabilita la possibilità per i giocatori di abbandonare", "LANG": "Imposta la lingua del bot. Supportate:", - "RESCHEDULE_MODE": "" + "RESCHEDULE_MODE": "Opzioni: `repost` (impostazione predefinita, crea un nuovo annuncio),` update` (aggiorna l'annuncio originale)", + "BOT_PERMISSIONS": "Mostra quali autorizzazioni manca il bot nel canale desginated" } }, "other": { diff --git a/lang/ru.json b/lang/ru.json index 8e2ac29..fc4ed21 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -195,7 +195,8 @@ "PASSWORD_CLEAR": "Удалить пароль", "TOGGLE_DROP_OUT": "Разрешить/Запереть возможность покидать игру", "LANG": "Установить язык бота. Поддерживается:", - "RESCHEDULE_MODE": "Параметры: repost (по умолчанию, создает новую запись объявления), update (обновляет исходное сообщение)" + "RESCHEDULE_MODE": "Параметры: `repost` (по умолчанию, создает новую запись объявления), `update` (обновляет исходное сообщение)", + "BOT_PERMISSIONS": "Показывает, какие разрешения бот отсутствует в назначенном канале" } }, "other": { From a4aba23bdaf44a3cecc0be96c8c926c1e08f84b0 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 14 Jul 2020 09:54:43 -0500 Subject: [PATCH 19/53] New command !schedule bot-permissions #channel-name Bug Fixes --- src/index.ts | 2 +- src/models/game-signups.ts | 5 ++- src/models/game.ts | 62 ++++++++++++++++++++++++++-------- src/processes/discord.ts | 50 ++++++++++++++++++++++----- src/processes/shard-manager.ts | 8 ++--- src/routes/api.ts | 16 +++++++-- views/home.ejs | 0 7 files changed, 108 insertions(+), 35 deletions(-) create mode 100644 views/home.ejs diff --git a/src/index.ts b/src/index.ts index bf13acf..ac77884 100644 --- a/src/index.ts +++ b/src/index.ts @@ -114,6 +114,6 @@ if (process.env.MAINTENANCE.toLowerCase() == "true") { app.use(apiRoutes({ client: client })); app.use(rssRoutes()); app.use("/", (req: any, res, next) => { - res.render("home"); + res.send(""); }); } diff --git a/src/models/game-signups.ts b/src/models/game-signups.ts index 22c375d..b4dbc13 100644 --- a/src/models/game-signups.ts +++ b/src/models/game-signups.ts @@ -1,8 +1,7 @@ import db from "../db"; import { ObjectID } from "mongodb"; -import { RSVP, Game } from "./game"; -import { Client, User } from "discord.js"; -import { ShardGuild } from "../processes/shard-manager"; +import { RSVP } from "./game"; +import { User } from "discord.js"; const connection = db.connection; const collection = "rsvps"; diff --git a/src/models/game.ts b/src/models/game.ts index c2a30e6..f2481f8 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -378,12 +378,21 @@ export class Game implements GameModel { } const rsvps = await GameRSVP.fetch(game._id); + game.reserved = game.reserved.map(r => { + r.tag = r.tag.trim().replace(/^@/g, ""); + return r; + }); game.reserved = game._id ? rsvps.map((r) => r.data).sort((a, b) => (a.timestamp > b.timestamp ? 1 : -1)) : game.reserved.filter((r) => r.tag); - const checkDupes = game.reserved.filter((r, i) => game.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || rr.tag === r.tag) === i); + const checkDupes = game.reserved.filter((r, i) => { + const test = !/#\d{4}$/.test(r.tag.trim()) || game.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || (rr.tag === r.tag && /#\d{4}/i.test(r.tag))) === i; + // console.log(r, test); + return test; + }); + // console.log(checkDupes, game.reserved.length, checkDupes.length); if (game.reserved.length > checkDupes.length) { game.reserved = checkDupes; rsvps.forEach((r, i) => { - if (rsvps.findIndex((rr) => (rr.id ? rr.id === r.id : false) || rr.tag === r.tag) < i) { + if (!/#\d{4}$/.test(r.tag.trim()) || game.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || (rr.tag === r.tag && /#\d{4}/i.test(r.tag))) < i) { r.delete(); } }); @@ -513,7 +522,7 @@ export class Game implements GameModel { if (game._id) { game.sequence++; const gameData = cloneDeep(game); - const prev = await Game.fetch(game._id, this.client, [this._guild]); + const prev = await Game.fetch(game._id, this.client, [this._guild], false); delete gameData._id; gameData.updatedTimestamp = new Date().getTime(); @@ -565,6 +574,7 @@ export class Game implements GameModel { aux.log("UpdateGameError:", err); if (updated) updated.modifiedCount = 0; } + const saved: GameSaveData = { _id: game._id, message: message, @@ -585,7 +595,7 @@ export class Game implements GameModel { let member = guildMembers.find( (mem) => mem.user.tag.trim() === ru.tag.trim().replace("@", "") || mem.user.id == ru.tag.trim().replace(/[<@>]/g, "") || mem.user.id === ru.id ); - const rsvp = new GameRSVP({ _id: new ObjectID(), gameId: inserted.insertedId, id: ru.id, tag: ru.tag, timestamp: new Date().getTime() }); + const rsvp = new GameRSVP({ _id: new ObjectID(), gameId: inserted.insertedId, id: ru.id, tag: ru.tag, timestamp: game.createdTimestamp+i }); await rsvp.save(); if (member) { this.dmCustomInstructions(member.user); @@ -639,6 +649,7 @@ export class Game implements GameModel { aux.log(`GameMessageNotPostedError:\n`, "s", game.s, "_id", game._id); await Game.hardDelete(inserted.insertedId); } + if (dmmember) { dmmember.send("The bot does not have sufficient permissions to post in the configured Discord channel"); } @@ -687,7 +698,7 @@ export class Game implements GameModel { } } - static async fetch(gameId: string | number | ObjectID, client?: Client, sGuilds?: ShardGuild[]): Promise { + static async fetch(gameId: string | number | ObjectID, client?: Client, sGuilds?: ShardGuild[], updateResList: boolean = true): Promise { if (!connection()) { aux.log("No database connection"); return null; @@ -699,7 +710,7 @@ export class Game implements GameModel { const guilds = sGuilds ? sGuilds : game.s ? (client ? await ShardManager.clientGuilds(client, [game.s]) : await ShardManager.shardGuilds({ guildIds: [game.s] })) : []; const sGame = new Game(game, guilds, client); if (sGame) { - await sGame.updateReservedList(); + if (updateResList) await sGame.updateReservedList(); return sGame; } else return null; } @@ -906,6 +917,7 @@ export class Game implements GameModel { const hours = this.duration !== null ? this.duration : Game.runtimeToHours(this.runtime); const gameEnded = this.timestamp + hours * 3600 * 1000 < new Date().getTime(); const nextDate = Game.getNextDate(moment(this.date), validDays, Number(this.frequency), this.monthlyType, this.xWeeks); + if (!nextDate) return false; const nextISO = `${nextDate.replace(/-/g, "")}T${this.time.replace(/:/g, "")}00${this.timezone >= 0 ? "+" : "-"}${aux.parseTimeZoneISO(this.timezone)}`; const nextGamePassed = new Date(nextISO).getTime() <= new Date().getTime(); return ( @@ -926,13 +938,15 @@ export class Game implements GameModel { aux.log(`Rescheduling ${this.s}: ${this.adventure} from ${this.date} (${this.time}) to ${nextDate} (${this.time})`); this.date = nextDate; - if (this.clearReservedOnRepeat) { - this.reserved = []; - } - const guildConfig = await GuildConfig.fetch(this.s); if (guildConfig.rescheduleMode === RescheduleMode.UPDATE) { + if (this.clearReservedOnRepeat) { + this.reserved = []; + await GameRSVP.deleteGame(this._id); + } this.reminded = null; + this.reminderMessageId = null; + this.pm = null; await this.save(); } else if (guildConfig.rescheduleMode === RescheduleMode.REPOST) { let data = cloneDeep(this.data); @@ -943,11 +957,15 @@ export class Game implements GameModel { guilds = await ShardManager.shardGuilds({ guildIds: [data.s] }); } const id = data._id; + if (this.clearReservedOnRepeat) { + data.reserved = []; + } delete data._id; delete data.reminded; delete data.pm; delete data.messageId; delete data.reminderMessageId; + delete data.sequence; const game = new Game(data, guilds, this.client); try { const newGame = await game.save(); @@ -1186,21 +1204,28 @@ export class Game implements GameModel { async updateReservedList() { let guildMembers: ShardMember[]; try { - const rsvps = await GameRSVP.fetch(this._id); const t = new Date().getTime() - 100 * this.reserved.length; if (!this.discordGuild) return; if (!guildMembers) guildMembers = this.discordGuild.members; - const checkDupes = this.reserved.filter((r, i) => this.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || rr.tag === r.tag) === i); + this.reserved = this.reserved.map(r => { + r.tag = r.tag.trim().replace(/^@/g, ""); + return r; + }); + const checkDupes = this.reserved.filter((r, i) => !/#\d{4}$/.test(r.tag.trim()) || this.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || (rr.tag === r.tag && /#\d{4}/i.test(r.tag))) === i); if (this.reserved.length > checkDupes.length) { this.reserved = checkDupes; } + const rsvps = await GameRSVP.fetch(this._id); for (let i = 0; i < this.reserved.length; i++) { try { const res = cloneDeep(this.reserved[i]); const member = guildMembers.find((m) => (this.reserved[i] && m.user.id === this.reserved[i].id) || m.user.tag === this.reserved[i].tag.trim()); + const countMatches = this.reserved.filter((rr, ri) => ri <= i && ((rr.id ? rr.id === res.id : false) || (rr.tag === res.tag && !/#\d{4}/i.test(res.tag)))); + const rsvpMatches = rsvps.filter(r => r._id === res._id || (r.id && r.id === res.id) || r.tag === res.tag); + // console.log(res.tag, countMatches.length, rsvpMatches.length); let rsvp = rsvps.find((r) => r._id === res._id || (r.id && r.id === res.id) || r.tag === res.tag); if (!rsvp) rsvp = await GameRSVP.fetchRSVP(this._id, res.id || res.tag); - if (!rsvp) { + if (!rsvp || (!/#\d{4}/i.test(res.tag) && countMatches.length > rsvpMatches.length)) { rsvp = new GameRSVP({ _id: new ObjectID(res._id), gameId: this._id, @@ -1209,13 +1234,20 @@ export class Game implements GameModel { timestamp: t + i * 100, }); await rsvp.save(); + rsvps.push(rsvp); + } + if (rsvp) { + this.reserved[i] = { + id: rsvp.id, + tag: rsvp.tag, + }; } - if (rsvp) this.reserved[i] = rsvp.data; } catch (err) { aux.log("InsertRSVPError:", err); } } - this.reserved = this.reserved.filter((r, i) => i === this.reserved.findIndex((ri) => ri.id === r.id || ri.tag === r.tag)); + this.reserved = this.reserved.filter((r, i) => !/#\d{4}$/.test(r.tag.trim()) || this.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || (rr.tag === r.tag && /#\d{4}/i.test(r.tag))) === i); + // console.log(this.reserved); } catch (err) { aux.log("UpdateReservedListError:", err); } diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 7c12110..372806a 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -6,7 +6,7 @@ import { Game, GameMethod, gameReminderOptions } from "../models/game"; import config from "../models/config"; import aux from "../appaux"; import db from "../db"; -import { ShardMember } from "./shard-manager"; +import shardManager, { ShardMember } from "./shard-manager"; import cloneDeep from "lodash/cloneDeep"; import { GameRSVP } from "../models/game-signups"; @@ -392,7 +392,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { `\n${lang.config.USAGE}\n` + (permission ? `\`${botcmd} link\` - ${lang.config.desc.LINK}` : ``) ); - (message.channel).send(embed); + if (embed.description.length > 0) (message.channel).send(embed); let embed2 = new MessageEmbed() .setTitle("RPG Schedule Help") .setColor(guildConfig.embedColor) @@ -408,10 +408,11 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { `\`${botcmd} emoji-sign-up ${guildConfig.emojiAdd}\` - ${lang.config.desc.EMOJI}\n` + `\`${botcmd} emoji-drop-out ${guildConfig.emojiRemove}\` - ${lang.config.desc.EMOJI}\n` + `\`${botcmd} drop-outs ${guildConfig.dropOut || guildConfig.dropOut == null ? "on" : "off"}\` - \`on/off\` - ${lang.config.desc.TOGGLE_DROP_OUT}\n` + - `\`${botcmd} prefix-char ${prefix}\` - ${lang.config.desc.PREFIX.replace(/\:CHAR/gi, prefix)}\n` + `\`${botcmd} prefix-char ${prefix}\` - ${lang.config.desc.PREFIX.replace(/\:CHAR/gi, prefix)}\n` + + `\`${botcmd} bot-permissions #channel-name\` - ${lang.config.desc.BOT_PERMISSIONS}\n` : `` ); - if (isAdmin) (message.channel).send(embed2); + if (embed2.description.length > 0) (message.channel).send(embed2); let embed3 = new MessageEmbed() .setTitle("RPG Schedule Help") .setColor(guildConfig.embedColor) @@ -426,7 +427,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { `\`${botcmd} reschedule-mode ${guildConfig.rescheduleMode}\` - ${lang.config.desc.RESCHEDULE_MODE}\n` : `` ); - if (isAdmin) (message.channel).send(embed3); + if (embed3.description.length > 0) (message.channel).send(embed3); } else if (cmd === "link" && permission) { (message.channel).send(process.env.HOST + config.urls.game.create.path + "?s=" + guildId); } else if (cmd === "configuration" && isAdmin) { @@ -824,6 +825,8 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { }); } else if (cmd === "reschedule" && isAdmin) { await rescheduleOldGames(message.guild.id); + } else if (cmd === "remind" && isAdmin) { + await postReminders(message.guild.id); } else if (cmd === "password" && isAdmin) { guildConfig .save({ @@ -919,6 +922,27 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { message.channel.send(`Data refresh started for the \`${guild.name}\` server`); } } + } else if (cmd === "bot-permissions" && (isAdmin || member.user.tag === config.author)) { + let channelId = message.channel.id; + if (params[0]) { + channelId = params[0].replace(/\<\#|\>/g, ""); + if (channelId.trim().length === params[0].trim().length) { + const c = message.guild.channels.cache.find((ch) => ch.name === channelId.trim()); + if (c) channelId = c.id; + } + if (channelId.trim().length === params[0].trim().length) { + return (message.channel).send(`Channel not found!`); + } + } + const sGuilds = await shardManager.clientGuilds(client, [message.guild.id]); + const sChannel = sGuilds[0].channels.find((c) => c.id === channelId); + const requiredPermissions = ["VIEW_CHANNEL", "READ_MESSAGE_HISTORY", "SEND_MESSAGES", "MANAGE_MESSAGES", "EMBED_LINKS", "ADD_REACTIONS"]; + const missingPermissions = requiredPermissions.filter((rp) => !sChannel.botPermissions.includes(rp)); + if (missingPermissions.length > 0) { + (message.channel).send(`The bot is missing the following permissions in <#${sChannel.id}>: ${missingPermissions.join(", ")}`); + } else { + (message.channel).send(`The bot has all the permissions it needs in <#${sChannel.id}>.`); + } } else { const response = await (message.channel).send("Command not recognized"); if (response) { @@ -942,11 +966,12 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { */ client.on("messageReactionAdd", async (reaction, user: User) => { try { + if (!reaction) return; const message = reaction.message; const t = new Date().getTime(); - const guildConfig = await GuildConfig.fetch(reaction.message.guild.id); + const guildConfig = await GuildConfig.fetch(message.guild.id); if (user.id !== message.author.id && (reaction.emoji.name === guildConfig.emojiAdd || reaction.emoji.name === guildConfig.emojiRemove)) { - if (guildConfig.channel.length > 0 && guildConfig.channel.find((c) => c.channelId === reaction.message.channel.id)) reaction.users.remove(user); + if (guildConfig.channel.length > 0 && guildConfig.channel.find((c) => c.channelId === message.channel.id)) reaction.users.remove(user); const game = await Game.fetchBy("messageId", message.id, client); if (game) { if (game.method !== GameMethod.AUTOMATED) return; @@ -1305,6 +1330,9 @@ const pruneOldGames = async (guild?: Guild) => { const hardDeleted = await Game.deleteAllBy( { + hideDate: { + $in: [false, null], + }, timestamp: { $lt: new Date().getTime() - 14 * 24 * 3600 * 1000, }, @@ -1361,7 +1389,7 @@ const pruneOldGames = async (guild?: Guild) => { return pruned; }; -const postReminders = async () => { +const postReminders = async (guildId?: string) => { const cTime = new Date().getTime(); const remExprs = gameReminderOptions.map((r) => { const rt = parseInt(r); @@ -1394,6 +1422,10 @@ const postReminders = async () => { $or: remExprs, }; + if (guildId) { + query.s = guildId; + } + const supportedLanguages = require("../../lang/langs.json"); const langs = supportedLanguages.langs .map((lang: String) => { @@ -1466,7 +1498,7 @@ const postReminders = async () => { try { game.reminded = true; const result = await game.save({ force: true }); - if (!result.modified) return; + if (!result || !result.modified) return; } catch (err) { aux.log("RemindedSaveError", game._id, err); return; diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index fec40c9..535c2b6 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -691,9 +691,9 @@ const findMessage = async (client: Client, guildId: string, channelId: string, m if (guild) { const channel = guild.channels.cache.get(channelId); if (channel) { - let message = channel.messages.cache.find(m => m.id === messageId || !!m.embeds.find(e => e.timestamp === timestamp && (e.author.name === author.user.tag || e.author.name === author.user.username || e.author.name === author.nickname))); + let message = await channel.messages.fetch(messageId); if (message) return message; - message = await channel.messages.fetch(messageId); + message = channel.messages.cache.find(m => m.id === messageId || !!m.embeds.find(e => e.timestamp === timestamp && (e.author.name === author.user.tag || e.author.name === author.user.username || e.author.name === author.nickname))); if (message) return message; const messages = await channel.messages.fetch({ limit: 100 }); message = messages.find(m => m.id === messageId || !!m.embeds.find(e => e.timestamp === timestamp && (e.author.name === author.user.tag || e.author.name === author.user.username || e.author.name === author.nickname))); @@ -708,9 +708,9 @@ const findMessage = async (client: Client, guildId: string, channelId: string, m if (guild) { const channel = guild.channels.cache.get(${JSON.stringify(channelId)}); if (channel) { - let message = channel.messages.cache.find(m => m.id === "${messageId}" || !!m.embeds.find(e => e.timestamp === ${timestamp} && (e.author.name === "${author.user.tag}" || e.author.name === "${author.user.username}" || e.author.name === "${author.nickname}"))); + let message = await channel.messages.fetch("${messageId}"); if (message) return message; - message = await channel.messages.fetch("${messageId}"); + message = channel.messages.cache.find(m => m.id === "${messageId}" || !!m.embeds.find(e => e.timestamp === ${timestamp} && (e.author.name === "${author.user.tag}" || e.author.name === "${author.user.username}" || e.author.name === "${author.nickname}"))); if (message) return message; const messages = await channel.messages.fetch({ limit: 100 }); message = messages.find(m => m.id === "${messageId}" || !!m.embeds.find(e => e.timestamp === ${timestamp} && (e.author.name === "${author.user.tag}" || e.author.name === "${author.user.username}" || e.author.name === "${author.nickname}"))); diff --git a/src/routes/api.ts b/src/routes/api.ts index d1d4e4d..5d8c4a7 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -642,8 +642,10 @@ export default (options: APIRouteOptions) => { // req.body.reserved = req.body.reserved.replace(/@/g, ""); if (req.body.copy) { - delete req.query.g; delete req.body._id; + delete req.query.g; + delete req.body.messageId; + delete req.body.createdTimestamp; req.query.s = req.body.s; server = req.body.s; } @@ -797,6 +799,12 @@ export default (options: APIRouteOptions) => { game.hideDate = req.body["hideDate"] ? true : false; game.clearReservedOnRepeat = req.body["clearReservedOnRepeat"] ? true : false; + if (req.body.copy) { + delete game.reminded; + delete (game).deleted; + delete game.rescheduled; + } + const updatedGame = new Game(game.data, [game.discordGuild]); updatedGame @@ -805,10 +813,12 @@ export default (options: APIRouteOptions) => { updatedGame._id = response.modified ? response._id : null; let uRes: GameModel; if (!req.query.g) uRes = await updatedGame.updateReservedList(); + uRes = { ...data, ...uRes }; + uRes.reserved = uRes.reserved.filter((r, i) => !/#\d{4}$/.test(r.tag.trim()) || uRes.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || (rr.tag === r.tag && /#\d{4}/i.test(r.tag))) === i); res.json({ status: response.modified ? "success" : "error", token: req.session.api.access.access_token, - game: { ...data, ...uRes }, + game: uRes, _id: response.modified ? response._id : null, }); }) @@ -822,7 +832,7 @@ export default (options: APIRouteOptions) => { status: "error", token: req.session.api.access.access_token, game: data, - message: err, + message: err.message, code: 17, }); }); diff --git a/views/home.ejs b/views/home.ejs new file mode 100644 index 0000000..e69de29 From f128f97cf5816c45f283cf29404ff9cdf82fabb3 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 14 Jul 2020 09:57:23 -0500 Subject: [PATCH 20/53] Change "Reserved" to "Sign Ups" --- lang/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/en.json b/lang/en.json index 6350b28..2949ab6 100644 --- a/lang/en.json +++ b/lang/en.json @@ -53,8 +53,8 @@ "REMINDER_FOR": "Reminder for", "STARTING_IN_MINUTES": "Starting in :MINUTES minutes", "STARTING_IN_HOURS": "Starting in :HOURS hours", - "RESERVED_SLOTS": "Reserved Slots (1 per line)", - "RESERVED": "Reserved", + "RESERVED_SLOTS": "Sign Up Slots (1 per line)", + "RESERVED": "Sign Ups", "WAITLISTED": "Waitlisted", "DESCRIPTION": "Description", "EDIT_LINK": "You can edit your `:SERVER_NAME` - `:GAME_NAME` game here:", From e75c42f507a0d649218f1d58041f04f229c4ac18 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 14 Jul 2020 16:09:22 -0500 Subject: [PATCH 21/53] Guild RSS --- src/models/config.ts | 3 +- src/models/game.ts | 15 ++++---- src/routes/rss.ts | 85 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 93 insertions(+), 10 deletions(-) diff --git a/src/models/config.ts b/src/models/config.ts index a272e63..1682c63 100644 --- a/src/models/config.ts +++ b/src/models/config.ts @@ -27,7 +27,8 @@ export default { login: { path: "/login" }, logout: { path: "/logout" }, rss: { path: "/rss/:uid" }, - guildRss: { path: "/rss/:uid/:guildId" }, + guildUserRss: { path: "/rss/:uid/:guildId" }, + guildRss: { path: "/guild-rss/:guildId" }, ics: { path: "/ics/:uid.ics" }, timezone: { convert: { path: "/tz/:time/:tz" }, diff --git a/src/models/game.ts b/src/models/game.ts index f2481f8..7be8c62 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -970,6 +970,14 @@ export class Game implements GameModel { try { const newGame = await game.save(); if (newGame.message && newGame.modified) { + if (this.client) + this.client.shard.send({ + type: "socket", + name: "game", + data: { action: "rescheduled", gameId: this._id, newGameId: newGame._id }, + }); + else io().emit("game", { action: "rescheduled", gameId: this._id, newGameId: newGame._id }); + const del = await this.delete(); if (del.modifiedCount == 0) { const del2 = await Game.softDelete(id); @@ -978,13 +986,6 @@ export class Game implements GameModel { await this.save(); } } - if (this.client) - this.client.shard.send({ - type: "socket", - name: "game", - data: { action: "rescheduled", gameId: this._id, newGameId: newGame._id }, - }); - else io().emit("game", { action: "rescheduled", gameId: this._id, newGameId: newGame._id }); return true; } else { await game.delete(); diff --git a/src/routes/rss.ts b/src/routes/rss.ts index e486f6f..2b31a74 100644 --- a/src/routes/rss.ts +++ b/src/routes/rss.ts @@ -10,6 +10,87 @@ export default () => { const router = express.Router(); router.use(config.urls.guildRss.path, async (req, res, next) => { + try { + const { guildId } = req.params; + const guilds = []; + + let tag = ""; + + const shardGuilds = await ShardManager.shardGuilds({ + guildIds: [guildId], + }); + + shardGuilds.forEach((guild) => { + if (guild.id !== guildId) return; + guilds.push({ + id: guild.id, + name: guild.name, + }); + }); + + const gameOptions: any = { + s: { + $in: guilds.reduce((i, g) => { + i.push(g.id); + return i; + }, []), + }, + timestamp: { + $gt: new Date().getTime(), + }, + }; + + const games: any[] = await Game.fetchAllBy(gameOptions, null, shardGuilds); + + res.type("application/xml"); + res.status(200); + res.send(` + + + RPG Schedule + https://www.rpg-schedule.com + Game scheduling bot for Discord + + https://www.rpg-schedule.com/images/logo2.png + RPG Schedule + https://www.rpg-schedule.com + + + ${games + .map((game) => { + if (!game.discordGuild) return ""; + + const date = Game.ISOGameDate(game); + game.moment = { + raw: `${game.date} ${game.time} UTC${game.timezone < 0 ? "-" : "+"}${Math.abs(game.timezone)}`, + iso: date, + date: moment(date).utcOffset(parseInt(game.timezone)).format(config.formats.dateLong), + calendar: moment(date).utcOffset(parseInt(game.timezone)).calendar(), + from: moment(date).utcOffset(parseInt(game.timezone)).fromNow(), + }; + + return ` + + ${game.adventure} + https://www.rpg-schedule.com/games/upcoming + https://www.rpg-schedule.com/games/${game._id.toString().slice(-12)} + + Discord Server: ${(guilds.find((g) => g.id === game.s) || {}).name}

GM: ${game.dm.tag}

Where: ${game.where.replace(/\&/g, "&")}

When: ${ + game.moment.date + }

${game.description.trim().replace(/\&/g, "&").replace(/\r?\n/g, "
")}

]]> +
+
`; + }) + .join("\n")} +
+
`); + } catch (err) { + console.log(err); + res.sendStatus(500); + } + }); + + router.use(config.urls.guildUserRss.path, async (req, res, next) => { try { const { guildId, uid } = req.params; const guilds = []; @@ -63,6 +144,7 @@ export default () => { RPG Schedule https://www.rpg-schedule.com + ${games .map((game) => { if (!game.discordGuild) return ""; @@ -93,7 +175,6 @@ export default () => { `; }) .join("\n")} - `); } catch (err) { @@ -155,6 +236,7 @@ export default () => { RPG Schedule https://www.rpg-schedule.com + ${games .map((game) => { if (!game.discordGuild) return ""; @@ -185,7 +267,6 @@ export default () => { `; }) .join("\n")} - `); } catch (err) { From cc4801c1af978c8246ed7c518790da62235d1f5b Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 14 Jul 2020 19:18:31 -0500 Subject: [PATCH 22/53] Pruning and Config Embeds --- src/processes/discord.ts | 87 ++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 372806a..fac9cc4 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -429,7 +429,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { ); if (embed3.description.length > 0) (message.channel).send(embed3); } else if (cmd === "link" && permission) { - (message.channel).send(process.env.HOST + config.urls.game.create.path + "?s=" + guildId); + (message.channel).send(responseEmbed(process.env.HOST + config.urls.game.create.path + "?s=" + guildId)); } else if (cmd === "configuration" && isAdmin) { const channel = guildConfig.channels.length > 0 @@ -475,7 +475,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (c) channel = c.id; } if (channel.trim().length === params[0].trim().length) { - return (message.channel).send(`Channel not found!`); + return (message.channel).send(responseEmbed(`Channel not found!`)); } const channels = guildConfig.channels; if (!channels.find((c) => c.channelId === channel)) channels.push({ channelId: channel, gameTemplates: [guildConfig.defaultGameTemplate.id] }); @@ -484,7 +484,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { channel: channels, }) .then((result) => { - (message.channel).send(`${lang.config.CHANNEL_ADDED}`); + (message.channel).send(responseEmbed(lang.config.CHANNEL_ADDED)); const addedChannel = message.guild.channels.cache.find((c) => c.id === channel); if (addedChannel) { const missingPermissions = [ @@ -496,7 +496,9 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { !addedChannel.permissionsFor(client.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", ].filter((check) => check); if (missingPermissions.length > 0) { - (message.channel).send(`The bot is missing the following permissions in ${addedChannel.toString()}: ${missingPermissions.join(", ")}`); + (message.channel).send( + responseEmbed(`The bot is missing the following permissions in ${addedChannel.toString()}: ${missingPermissions.join(", ")}`) + ); } } }) @@ -512,7 +514,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (c) channel = c.id; } if (channel.trim().length === params[0].trim().length) { - return (message.channel).send(`Channel not found!`); + return (message.channel).send(responseEmbed(`Channel not found!`)); } const channels = guildConfig.channels; if (channels.find((c) => c.channelId === channel)) { @@ -526,7 +528,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { channel: channels, }) .then((result) => { - (message.channel).send(`${lang.config.CHANNEL_REMOVED}`); + (message.channel).send(responseEmbed(lang.config.CHANNEL_REMOVED)); }) .catch((err) => { aux.log(err); @@ -539,7 +541,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { pruning: params[0] === "on", }) .then((result) => { - (message.channel).send(params[0] === "on" ? lang.config.PRUNING_ON : lang.config.PRUNING_OFF); + (message.channel).send(responseEmbed(params[0] === "on" ? lang.config.PRUNING_ON : lang.config.PRUNING_OFF)); }) .catch((err) => { aux.log(err); @@ -547,7 +549,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } } else if (cmd === "prune" && isAdmin) { await pruneOldGames(message.guild); - (message.channel).send(lang.config.PRUNE); + (message.channel).send(responseEmbed(lang.config.PRUNE)); } else if (cmd === "embeds" && isAdmin) { if (params[0]) { guildConfig @@ -555,7 +557,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { embeds: !(params[0] === "off"), }) .then((result) => { - (message.channel).send(!(params[0] === "off") ? lang.config.EMBEDS_ON : lang.config.EMBEDS_OFF); + (message.channel).send(responseEmbed(!(params[0] === "off") ? lang.config.EMBEDS_ON : lang.config.EMBEDS_OFF)); }) .catch((err) => { aux.log(err); @@ -568,7 +570,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { embedMentions: !(params[0] === "off"), }) .then((result) => { - (message.channel).send(!(params[0] === "off") ? lang.config.EMBED_USER_TAGS_ON : lang.config.EMBED_USER_TAGS_OFF); + (message.channel).send(responseEmbed(!(params[0] === "off") ? lang.config.EMBED_USER_TAGS_ON : lang.config.EMBED_USER_TAGS_OFF)); }) .catch((err) => { aux.log(err); @@ -581,7 +583,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { embedMentionsAbove: !(params[0] === "off"), }) .then((result) => { - (message.channel).send(!(params[0] === "off") ? lang.config.EMBED_USER_TAGS_ABOVE_ON : lang.config.EMBED_USER_TAGS_ABOVE_OFF); + (message.channel).send(responseEmbed(!(params[0] === "off") ? lang.config.EMBED_USER_TAGS_ABOVE_ON : lang.config.EMBED_USER_TAGS_ABOVE_OFF)); }) .catch((err) => { aux.log(err); @@ -735,7 +737,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (colors[color]) { color = colors[color]; } else if (!color.match(/[0-9a-f]{6}|[0-9a-f]{3}/i)) { - (message.channel).send(lang.config.desc.EMBED_COLOR_ERROR); + (message.channel).send(responseEmbed(lang.config.desc.EMBED_COLOR_ERROR)); return; } const save: GuildConfigModel = {}; @@ -754,7 +756,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } else if (cmd === "emoji-sign-up" && isAdmin) { const emoji = params.join(" "); if (!aux.isEmoji(emoji) || (emoji.length > 2 && emoji.match(/\:[^\:]+\:/))) { - (message.channel).send(lang.config.desc.EMOJI_ERROR.replace(/\:char/gi, emoji.replace(/\<|\>/g, ""))); + (message.channel).send(responseEmbed(lang.config.desc.EMOJI_ERROR.replace(/\:char/gi, emoji.replace(/\<|\>/g, "")))); return; } guildConfig @@ -762,7 +764,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { emojiAdd: emoji, }) .then((result) => { - (message.channel).send(lang.config.EMOJI_JOIN_SET); + (message.channel).send(responseEmbed(lang.config.EMOJI_JOIN_SET)); guildConfig.emojiAdd = emoji; guildConfig.updateReactions(client); }) @@ -772,7 +774,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } else if (cmd === "emoji-drop-out" && isAdmin) { const emoji = params.join(" "); if (!aux.isEmoji(emoji) || (emoji.length > 2 && emoji.match(/\:[^\:]+\:/))) { - (message.channel).send(lang.config.desc.EMOJI_ERROR.replace(/\:char/gi, emoji.replace(/\<|\>/g, ""))); + (message.channel).send(responseEmbed(lang.config.desc.EMOJI_ERROR.replace(/\:char/gi, emoji.replace(/\<|\>/g, "")))); return; } await guildConfig @@ -780,7 +782,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { emojiRemove: emoji, }) .then((result) => { - (message.channel).send(lang.config.EMOJI_LEAVE_SET); + (message.channel).send(responseEmbed(lang.config.EMOJI_LEAVE_SET)); guildConfig.emojiRemove = emoji; guildConfig.updateReactions(client); }) @@ -794,7 +796,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { escape: prefix.length ? prefix : "!", }) .then((result) => { - (message.channel).send(lang.config.PREFIX_CHAR.replace(/\:CMD/gi, `${prefix.length ? prefix : "!"}${config.command}`)); + (message.channel).send(responseEmbed(lang.config.PREFIX_CHAR.replace(/\:CMD/gi, `${prefix.length ? prefix : "!"}${config.command}`))); }) .catch((err) => { aux.log(err); @@ -805,7 +807,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { privateReminders: !guildConfig.privateReminders, }) .then((result) => { - (message.channel).send(!guildConfig.privateReminders ? lang.config.PRIVATE_REMINDERS_ON : lang.config.PRIVATE_REMINDERS_OFF); + (message.channel).send(responseEmbed(!guildConfig.privateReminders ? lang.config.PRIVATE_REMINDERS_ON : lang.config.PRIVATE_REMINDERS_OFF)); }) .catch((err) => { aux.log(err); @@ -818,7 +820,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { rescheduleMode: options.includes(mode) ? mode : "repost", }) .then((result) => { - (message.channel).send(lang.config.RESCHEDULE_MODE_UPDATED); + (message.channel).send(responseEmbed(lang.config.RESCHEDULE_MODE_UPDATED)); }) .catch((err) => { aux.log(err); @@ -833,7 +835,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { password: params.join(" "), }) .then((result) => { - (message.channel).send(lang.config.PASSWORD_SET); + (message.channel).send(responseEmbed(lang.config.PASSWORD_SET)); }) .catch((err) => { aux.log(err); @@ -844,7 +846,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { dropOut: guildConfig.dropOut === false, }) .then((result) => { - (message.channel).send(guildConfig.dropOut === false ? lang.config.DROP_OUTS_ENABLED : lang.config.DROP_OUTS_DISABLED); + (message.channel).send(responseEmbed(guildConfig.dropOut === false ? lang.config.DROP_OUTS_ENABLED : lang.config.DROP_OUTS_DISABLED)); }) .catch((err) => { aux.log(err); @@ -870,7 +872,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { guildConfig .save(save) .then((result) => { - (message.channel).send(roleName.length > 0 ? lang.config.ROLE_SET.replace(/\:role/gi, roleName) : lang.config.ROLE_CLEARED); + (message.channel).send(responseEmbed(roleName.length > 0 ? lang.config.ROLE_SET.replace(/\:role/gi, roleName) : lang.config.ROLE_CLEARED)); }) .catch((err) => { aux.log(err); @@ -888,7 +890,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { managerRole: roleName == "" ? null : roleName, }) .then((result) => { - (message.channel).send(roleName.length > 0 ? lang.config.ROLE_SET.replace(/\:role/gi, roleName) : lang.config.ROLE_CLEARED); + (message.channel).send(responseEmbed(roleName.length > 0 ? lang.config.ROLE_SET.replace(/\:role/gi, roleName) : lang.config.ROLE_CLEARED)); }) .catch((err) => { aux.log(err); @@ -903,7 +905,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { lang: newLang.code, }) .then((result) => { - (message.channel).send(newLang.config.LANG_SET.replace(/\:lang/gi, newLang.name)); + (message.channel).send(responseEmbed(newLang.config.LANG_SET.replace(/\:lang/gi, newLang.name))); }) .catch((err) => { aux.log(err); @@ -914,12 +916,12 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { type: "refresh", guildId: guildId, }); - if (params[0] === "all") message.channel.send(`Data refresh started for all servers`); + if (params[0] === "all") message.channel.send(responseEmbed(`Data refresh started for all servers`)); else { const shards = await client.shard.broadcastEval(`this.guilds.cache.find(g => g.id === "${guildId}");`); const guild = shards.find((s) => s); if (guild) { - message.channel.send(`Data refresh started for the \`${guild.name}\` server`); + message.channel.send(responseEmbed(`Data refresh started for the \`${guild.name}\` server`)); } } } else if (cmd === "bot-permissions" && (isAdmin || member.user.tag === config.author)) { @@ -931,7 +933,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (c) channelId = c.id; } if (channelId.trim().length === params[0].trim().length) { - return (message.channel).send(`Channel not found!`); + return (message.channel).send(responseEmbed(`Channel not found!`)); } } const sGuilds = await shardManager.clientGuilds(client, [message.guild.id]); @@ -939,9 +941,9 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { const requiredPermissions = ["VIEW_CHANNEL", "READ_MESSAGE_HISTORY", "SEND_MESSAGES", "MANAGE_MESSAGES", "EMBED_LINKS", "ADD_REACTIONS"]; const missingPermissions = requiredPermissions.filter((rp) => !sChannel.botPermissions.includes(rp)); if (missingPermissions.length > 0) { - (message.channel).send(`The bot is missing the following permissions in <#${sChannel.id}>: ${missingPermissions.join(", ")}`); + (message.channel).send(responseEmbed(`The bot is missing the following permissions in <#${sChannel.id}>: ${missingPermissions.join(", ")}`)); } else { - (message.channel).send(`The bot has all the permissions it needs in <#${sChannel.id}>.`); + (message.channel).send(responseEmbed(`The bot has all the permissions it needs in <#${sChannel.id}>.`)); } } else { const response = await (message.channel).send("Command not recognized"); @@ -1104,6 +1106,14 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } })(); +const responseEmbed = (message: string, title?: string) => { + const embed = new MessageEmbed(); + if (title) embed.setTitle(title); + embed.setDescription(message); + embed.setColor("#2196f3"); + return embed; +}; + const refreshMessages = async () => { let games = await Game.fetchAllBy( { s: { $in: client.guilds.cache.array().map((g) => g.id) }, messageId: null, when: "datetime", method: "automated", timestamp: { $gte: new Date().getTime() } }, @@ -1243,7 +1253,6 @@ const pruneOldGames = async (guild?: Guild) => { let games = await Game.fetchAllBy(query, client); const guildConfigs = await GuildConfig.fetchAllBy({ - pruning: true, guild: guild ? guild.id : { @@ -1265,9 +1274,9 @@ const pruneOldGames = async (guild?: Guild) => { if (!game.pruned && game.discordChannel && new Date().getTime() - game.timestamp >= guildConfig.pruneIntDiscord * 24 * 3600 * 1000) { if (game.messageId) { + if (guildConfig.pruning) prunedMessageIds.push(game.messageId); if (guildConfig.pruneIntDiscord < guildConfig.pruneIntEvents && new Date().getTime() - game.timestamp < guildConfig.pruneIntEvents * 24 * 3600 * 1000) { prunedIds.push(game._id); - if (guildConfig.pruning) prunedMessageIds.push(game.messageId); client.shard.send({ type: "socket", name: "game", @@ -1312,9 +1321,6 @@ const pruneOldGames = async (guild?: Guild) => { { $set: { pruned: true, - // messageId: null, - // reminderMessageId: null, - // pm: null, }, } ); @@ -1347,22 +1353,25 @@ const pruneOldGames = async (guild?: Guild) => { const gc = guildConfigs[gci]; const guild = client.guilds.cache.find((g) => g.id === gc.guild); const channels = guild.channels.cache.array().filter((c) => gc.channels.find((gcc) => gcc.channelId === c.id) && c instanceof TextChannel); + for (let ci = 0; ci < channels.length; ci++) { const c = channels[ci]; const messages = await c.messages.fetch({ limit: 100 }); if (messages.size === 0) continue; + const clientMessages = messages .array() .filter( (m) => - m.embeds.filter( - (e) => new Date().getTime() - m.createdTimestamp >= 14 * 24 * 3600 * 1000 && new Date().getTime() - e.timestamp >= gc.pruneIntDiscord * 24 * 3600 * 1000 - ).length > 0 && m.author.id === client.user.id && - prunedMessageIds.includes(m.id) && m.deletable && - !m.deleted + !m.deleted && + (m.embeds.filter( + (e) => new Date().getTime() - m.createdTimestamp >= 14 * 24 * 3600 * 1000 && new Date().getTime() - e.timestamp >= gc.pruneIntDiscord * 24 * 3600 * 1000 + ).length > 0 || + prunedMessageIds.includes(m.id)) ); + for (let i = 0; i < gameChannelMessages.length; i++) { const msg = gameChannelMessages[i]; if (guild.id === msg.guild && c.id === msg.channel && !clientMessages.find((cm) => cm.id === msg.message)) { From a22c385063cee71b74d15973ec8855b4fa97321f Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 14 Jul 2020 21:47:49 -0500 Subject: [PATCH 23/53] moment-timezone --- package-lock.json | 8 ++++++++ package.json | 1 + src/appaux.ts | 2 +- src/models/config.ts | 2 +- src/models/game.ts | 8 ++++++-- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 58d9fba..fc22e08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2550,6 +2550,14 @@ "resolved": "https://registry.npmjs.org/moment-recur-ts/-/moment-recur-ts-1.3.1.tgz", "integrity": "sha1-L+GwML2IPikiTwfg+AjXWYGHOh0=" }, + "moment-timezone": { + "version": "0.5.31", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", + "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", + "requires": { + "moment": ">= 2.9.0" + } + }, "mongodb": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.1.10.tgz", diff --git a/package.json b/package.json index beb1431..09985f1 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "lodash": "^4.17.15", "moment": "^2.24.0", "moment-recur-ts": "^1.3.1", + "moment-timezone": "^0.5.31", "mongodb": "^3.1.10", "nodemon": "^1.18.9", "object.fromentries": "^2.0.0", diff --git a/src/appaux.ts b/src/appaux.ts index ba1ef47..5841b6f 100644 --- a/src/appaux.ts +++ b/src/appaux.ts @@ -2,7 +2,7 @@ import cloneDeep from "lodash/cloneDeep"; import isEqual from "lodash/isEqual"; import fromPairs from "lodash/fromPairs"; import toPairs from "lodash/toPairs"; -import moment from "moment"; +import moment from "moment-timezone"; import "moment-recur-ts"; import axios from "axios"; import { GameModel } from "./models/game"; diff --git a/src/models/config.ts b/src/models/config.ts index 1682c63..9279342 100644 --- a/src/models/config.ts +++ b/src/models/config.ts @@ -36,7 +36,7 @@ export default { } }, formats: { - dateLong: "llll" + dateLong: "llll z" }, author: "Sillvva#2532", command: "schedule", diff --git a/src/models/game.ts b/src/models/game.ts index 7be8c62..de4d3f2 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -1,6 +1,6 @@ import mongodb, { ObjectID } from "mongodb"; import discord, { Message, MessageEmbed, User, Client } from "discord.js"; -import moment from "moment"; +import moment from "moment-timezone"; import "moment-recur-ts"; import db from "../db"; @@ -86,6 +86,7 @@ export interface GameModel { date: string; time: string; timezone: number; + tz: string; timestamp: number; hideDate: boolean; reminder: string; @@ -152,6 +153,7 @@ export class Game implements GameModel { date: string; time: string; timezone: number; + tz: string; timestamp: number; hideDate: boolean; reminder: string; @@ -266,6 +268,7 @@ export class Game implements GameModel { date: this.date, time: this.time, timezone: this.timezone, + tz: this.tz, timestamp: this.timestamp, hideDate: this.hideDate, reminder: this.reminder, @@ -455,7 +458,8 @@ export class Game implements GameModel { if (game.when === GameWhen.DATETIME) { const date = Game.ISOGameDate(game); const tz = Math.round(parseFloat(game.timezone.toString()) * 4) / 4; - when = moment(date).utcOffset(tz).format(config.formats.dateLong) + ` (${timezone})`; + if (game.tz) when = moment(date).tz(game.tz).format(config.formats.dateLong) + ` (${timezone})`; + else when = moment(date).utcOffset(tz).format(config.formats.dateLong) + ` (${timezone})`; gameDate = new Date(rawDate); } else if (game.when === GameWhen.NOW) { when = lang.game.options.NOW; From 06fb306373ae4ebafd62b981cf1d60fad5557362 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 14 Jul 2020 22:03:32 -0500 Subject: [PATCH 24/53] Date Formatting --- src/models/config.ts | 3 ++- src/models/game.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/models/config.ts b/src/models/config.ts index 9279342..2248d75 100644 --- a/src/models/config.ts +++ b/src/models/config.ts @@ -36,7 +36,8 @@ export default { } }, formats: { - dateLong: "llll z" + dateLong: "llll", + dateLongTZ: "llll z" }, author: "Sillvva#2532", command: "schedule", diff --git a/src/models/game.ts b/src/models/game.ts index de4d3f2..89f32c6 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -458,7 +458,7 @@ export class Game implements GameModel { if (game.when === GameWhen.DATETIME) { const date = Game.ISOGameDate(game); const tz = Math.round(parseFloat(game.timezone.toString()) * 4) / 4; - if (game.tz) when = moment(date).tz(game.tz).format(config.formats.dateLong) + ` (${timezone})`; + if (game.tz) when = moment(date).tz(game.tz).format(config.formats.dateLongTZ) + ` (${timezone})`; else when = moment(date).utcOffset(tz).format(config.formats.dateLong) + ` (${timezone})`; gameDate = new Date(rawDate); } else if (game.when === GameWhen.NOW) { From 7f7f250a982bece8e93bef72911f30259b882632 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Fri, 17 Jul 2020 11:22:32 -0500 Subject: [PATCH 25/53] Bug Fix --- src/models/game.ts | 4 ++-- src/routes/api.ts | 13 +------------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index 89f32c6..08f1151 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -910,8 +910,8 @@ export class Game implements GameModel { static runtimeToHours(runtime: string | number) { let hours = 0, x: RegExpExecArray; - if ((x = /[\d\.]/g.exec(runtime.toString().trim()))) { - if (x[0]) hours = parseInt(x[0]); + if ((x = /[\d\.]+/g.exec(runtime.toString().trim()))) { + if (x[0]) hours = parseFloat(x[0]); } return hours; } diff --git a/src/routes/api.ts b/src/routes/api.ts index 5d8c4a7..31f48b5 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1149,7 +1149,7 @@ export default (options: APIRouteOptions) => { const options = { guilds: true, - games: false, + games: req.query.games, search: "", }; @@ -1161,7 +1161,6 @@ export default (options: APIRouteOptions) => { }, }, guilds: [], - sGuilds: [], }; let sGuilds: ShardGuild[] = []; @@ -1185,8 +1184,6 @@ export default (options: APIRouteOptions) => { } ); } - - account.sGuilds = sGuilds; // console.log(new Date().getTime() - fTime, req.query, tag, sGuilds.length); sGuilds.forEach((guild) => { @@ -1285,14 +1282,6 @@ export default (options: APIRouteOptions) => { const fGames: Game[] = await Game.fetchAllBy(gameOptions, null, sGuilds); // console.log(new Date().getTime() - fTime); - // const games: any[] = []; - // for (let i = 0; i < fGames.length; i++) { - // const game = fGames[i]; - // const dc = game.discordChannel; - // if (dc && (dc.members || []).includes(id)) { - // games.push(game); - // } - // } fGames.forEach(async (game) => { if (!game.discordGuild) return; const date = Game.ISOGameDate(game); From 64e5cc22ba5dfd2a0536ce2bae09692f1fe4fe75 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Sat, 18 Jul 2020 17:34:44 -0500 Subject: [PATCH 26/53] Disable Waitlist Option --- src/models/game.ts | 49 +++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index 08f1151..9b68bf3 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -100,6 +100,7 @@ export interface GameModel { xWeeks: number; monthlyType: MonthlyType; clearReservedOnRepeat: boolean; + disableWaitlist: boolean; rescheduled: boolean; pastSignups: boolean; sequence: number; @@ -167,6 +168,7 @@ export class Game implements GameModel { xWeeks: number = 2; monthlyType: MonthlyType = MonthlyType.WEEKDAY; clearReservedOnRepeat: boolean = false; + disableWaitlist: boolean = false; rescheduled: boolean = false; pastSignups: boolean = false; sequence: number = 1; @@ -282,6 +284,7 @@ export class Game implements GameModel { xWeeks: this.xWeeks, monthlyType: this.monthlyType, clearReservedOnRepeat: this.clearReservedOnRepeat, + disableWaitlist: this.disableWaitlist, rescheduled: this.rescheduled, pastSignups: this.pastSignups, sequence: this.sequence, @@ -447,7 +450,7 @@ export class Game implements GameModel { let automatedInstructions = `\n(${guildConfig.emojiAdd} ${lang.buttons.SIGN_UP}${guildConfig.dropOut ? ` | ${guildConfig.emojiRemove} ${lang.buttons.DROP_OUT}` : ""})`; if (game.method === GameMethod.AUTOMATED) { if (reserved.length > 0) signups += `\n**${lang.game.RESERVED}:**\n${reserved.join("\n")}\n`; - if (waitlist.length > 0) signups += `\n**${lang.game.WAITLISTED}:**\n${waitlist.join("\n")}\n`; + if (waitlist.length > 0 && !game.disableWaitlist) signups += `\n**${lang.game.WAITLISTED}:**\n${waitlist.join("\n")}\n`; signups += automatedInstructions; } else if (game.method === GameMethod.CUSTOM) { signups += `\n${game.customSignup}`; @@ -510,7 +513,7 @@ export class Game implements GameModel { if (game.method === GameMethod.AUTOMATED || (game.method === GameMethod.CUSTOM && reserved.length > 0)) { embed.addField(`${lang.game.RESERVED} (${reserved.length}/${game.players})`, reserved.length > 0 ? reserved.join("\n") : lang.game.NO_PLAYERS, true); } - if (waitlist.length > 0) embed.addField(`${lang.game.WAITLISTED} (${waitlist.length})`, waitlist.join("\n"), true); + if (waitlist.length > 0 && !game.disableWaitlist) embed.addField(`${lang.game.WAITLISTED} (${waitlist.length})`, waitlist.join("\n"), true); if (!game.hideDate) embed.addField( "Links", @@ -1274,26 +1277,36 @@ export class Game implements GameModel { async signUp(user: User | ShardUser, t?: number) { const hourDiff = (new Date().getTime() - this.timestamp) / 1000 / 3600; - if (hourDiff < 0 || this.pastSignups || this.hideDate) { - let match = await GameRSVP.fetchRSVP(this._id, user.id); - if (match && !this.reserved.find((r) => r.id === match.id || r.tag === match.tag)) { - await GameRSVP.deleteUser(this._id, user.id); - match = null; - } - if (!match) { - const rsvp = new GameRSVP({ _id: new ObjectID(), gameId: this._id, id: user.id, tag: user.tag, timestamp: t || new Date().getTime() }); - await rsvp.save(); - await this.save(); - this.dmCustomInstructions(user); - return true; - } - return false; - } else { - if (!this.discordGuild) return; + + if (hourDiff >= 0 && !this.pastSignups && !this.hideDate) { + if (!this.discordGuild) return false; const member = this.discordGuild.members.find((m) => m.user.tag === user.tag.trim() || m.user.id === user.id); const guildConfig = await GuildConfig.fetch(this.s); const lang = gmLanguages.find((l) => l.code === guildConfig.lang) || gmLanguages.find((l) => l.code === "en"); if (member) member.send(lang.other.ALREADY_STARTED); + return false; + } + + if (this.disableWaitlist && this.reserved.length >= parseInt(this.players)) { + if (!this.discordGuild) return false; + const member = this.discordGuild.members.find((m) => m.user.tag === user.tag.trim() || m.user.id === user.id); + const guildConfig = await GuildConfig.fetch(this.s); + const lang = gmLanguages.find((l) => l.code === guildConfig.lang) || gmLanguages.find((l) => l.code === "en"); + if (member) member.send(lang.other.MAX_NO_WAITLIST); + return false; + } + + let match = await GameRSVP.fetchRSVP(this._id, user.id); + if (match && !this.reserved.find((r) => r.id === match.id || r.tag === match.tag)) { + await GameRSVP.deleteUser(this._id, user.id); + match = null; + } + if (!match) { + const rsvp = new GameRSVP({ _id: new ObjectID(), gameId: this._id, id: user.id, tag: user.tag, timestamp: t || new Date().getTime() }); + await rsvp.save(); + await this.save(); + this.dmCustomInstructions(user); + return true; } return false; } From 4c979a9107f08ab3ce1f96f362e54d7f505471e9 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Thu, 30 Jul 2020 09:58:35 -0500 Subject: [PATCH 27/53] Development --- lang/de-ch.json | 7 +- lang/de.json | 7 +- lang/en.json | 7 +- lang/es.json | 7 +- lang/fr.json | 7 +- lang/it.json | 7 +- lang/ru.json | 7 +- package-lock.json | 20 ++ package.json | 1 + src/appaux.ts | 22 +++ src/index.ts | 5 +- src/models/config.ts | 3 +- src/models/game.ts | 69 ++++--- src/models/user.ts | 10 +- src/processes/shard-manager.ts | 12 +- src/routes/api.ts | 344 +++++++++++++++++++++++++++++++-- 16 files changed, 473 insertions(+), 62 deletions(-) diff --git a/lang/de-ch.json b/lang/de-ch.json index 36c4750..26d9e16 100644 --- a/lang/de-ch.json +++ b/lang/de-ch.json @@ -72,6 +72,10 @@ "PAST_SIGNUPS": "Zulassen von Anmeldungen nach dem Datum / der Uhrzeit der Anmeldung", "SLOT": "Slot", "UNSAVED": "Du hast nicht gespeicherte Änderungen. Möchten Sie fortfahren?", + "GAME_OPTIONS": "Spieleinstellungen", + "DISABLE_WAITLIST": "Deaktivieren Warteliste", + "UPLOAD_GAME_IMAGE": "Hochladen von Spielbild", + "MAX_UPLOAD_SIZE": "Max Größe (5 MB)", "options": { "AUTOMATED_SIGNUP": "Automatisches Eintragen", "CUSTOM_SIGNUP_INSTRUCTIONS": "Benutzerdefinierter Befehl zum Eintragen", @@ -209,6 +213,7 @@ "RESCHEDULED": "Das Spiel wurde verschoben", "DELETED": "Das Spiel wurde gelöscht", "EDIT_PERMISSION": "Sie haben keine Berechtigung, dieses Spiel zu bearbeiten", - "ALREADY_STARTED": "Das Spiel hat bereits begonnen!" + "ALREADY_STARTED": "Das Spiel hat bereits begonnen!", + "MAX_NO_WAITLIST": "Das Spiel ist voll und keine zusätzliche Anmeldungen zu akzeptieren." } } diff --git a/lang/de.json b/lang/de.json index 102f5ee..3fb1305 100644 --- a/lang/de.json +++ b/lang/de.json @@ -72,6 +72,10 @@ "PAST_SIGNUPS": "Zulassen von Anmeldungen nach dem Datum / der Uhrzeit der Anmeldung", "SLOT": "Slot", "UNSAVED": "Du hast nicht gespeicherte Änderungen. Möchten Sie fortfahren?", + "GAME_OPTIONS": "Spieleinstellungen", + "DISABLE_WAITLIST": "Deaktivieren Warteliste", + "UPLOAD_GAME_IMAGE": "Hochladen von Spielbild", + "MAX_UPLOAD_SIZE": "Max Größe (5 MB)", "options": { "AUTOMATED_SIGNUP": "Automatisches Eintragen", "CUSTOM_SIGNUP_INSTRUCTIONS": "Benutzerdefinierter Befehl zum Eintragen", @@ -209,6 +213,7 @@ "RESCHEDULED": "Das Spiel wurde verschoben", "DELETED": "Das Spiel wurde gelöscht", "EDIT_PERMISSION": "Sie haben keine Berechtigung, dieses Spiel zu bearbeiten", - "ALREADY_STARTED": "Das Spiel hat bereits begonnen!" + "ALREADY_STARTED": "Das Spiel hat bereits begonnen!", + "MAX_NO_WAITLIST": "Das Spiel ist voll und keine zusätzliche Anmeldungen zu akzeptieren." } } diff --git a/lang/en.json b/lang/en.json index 2949ab6..53c2876 100644 --- a/lang/en.json +++ b/lang/en.json @@ -72,6 +72,10 @@ "PAST_SIGNUPS": "Allow signups past the signup date/time", "SLOT": "Slot", "UNSAVED": "You have unsaved changes. Do you wish to proceed?", + "GAME_OPTIONS": "Game Options", + "DISABLE_WAITLIST": "Disable Waitlist", + "UPLOAD_GAME_IMAGE": "Upload Game Image", + "MAX_UPLOAD_SIZE": "Max size (5 MB)", "options": { "AUTOMATED_SIGNUP": "Automated Signup", "CUSTOM_SIGNUP_INSTRUCTIONS": "Custom Signup Instructions", @@ -209,6 +213,7 @@ "RESCHEDULED": "The game has been rescheduled", "DELETED": "The game has been deleted", "EDIT_PERMISSION": "You do not have permission to edit this game", - "ALREADY_STARTED": "That game has already started!" + "ALREADY_STARTED": "That game has already started.", + "MAX_NO_WAITLIST": "That game is full and not accepting additional signups." } } \ No newline at end of file diff --git a/lang/es.json b/lang/es.json index 681d7e1..0ac8bbe 100644 --- a/lang/es.json +++ b/lang/es.json @@ -72,6 +72,10 @@ "PAST_SIGNUPS": "Permitir registros después de la fecha / hora de registro", "SLOT": "Espacio", "UNSAVED": "Usted tiene cambios no guardados. ¿Desea continuar?", + "GAME_OPTIONS": "Opciones de juego", + "DISABLE_WAITLIST": "Desactivar la Lista de Espera", + "UPLOAD_GAME_IMAGE": "Subir imagen juego", + "MAX_UPLOAD_SIZE": "Tamaño máximo (5 MB)", "options": { "AUTOMATED_SIGNUP": "Registrarse Automaticamente", "CUSTOM_SIGNUP_INSTRUCTIONS": "Instrucciones para unirse al evento", @@ -209,6 +213,7 @@ "RESCHEDULED": "El juego ha sido reprogramado", "DELETED": "El juego ha sido eliminado", "EDIT_PERMISSION": "No tienes permiso para editar este juego", - "ALREADY_STARTED": "¡Ese juego ya ha comenzado!" + "ALREADY_STARTED": "¡Ese juego ya ha comenzado!", + "MAX_NO_WAITLIST": "Ese juego está lleno y no aceptar suscripciones adicionales." } } diff --git a/lang/fr.json b/lang/fr.json index 5b01ea7..1075556 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -72,6 +72,10 @@ "PAST_SIGNUPS": "Autoriser les inscriptions après la date / l'heure d'inscription", "SLOT": "Fente", "UNSAVED": "Vous avez des changements non enregistrés. Voulez-vous continuer?", + "GAME_OPTIONS": "Options de jeu", + "DISABLE_WAITLIST": "Désactiver la liste d'attente", + "UPLOAD_GAME_IMAGE": "Télécharger Image du jeu", + "MAX_UPLOAD_SIZE": "Taille maximale (10 Mo)", "options": { "AUTOMATED_SIGNUP": "Inscription automatisée", "CUSTOM_SIGNUP_INSTRUCTIONS": "Instructions d'inscription personnalisées", @@ -209,6 +213,7 @@ "RESCHEDULED": "Le jeu a été reprogrammé", "DELETED": "Le jeu a été supprimé", "EDIT_PERMISSION": "Vous n'êtes pas autorisé à modifier ce jeu", - "ALREADY_STARTED": "Ce jeu a déjà commencé!" + "ALREADY_STARTED": "Ce jeu a déjà commencé!", + "MAX_NO_WAITLIST": "Ce jeu est plein et ne pas accepter des inscriptions supplémentaires." } } \ No newline at end of file diff --git a/lang/it.json b/lang/it.json index e7b1280..984bf88 100644 --- a/lang/it.json +++ b/lang/it.json @@ -70,6 +70,10 @@ "PAST_SIGNUPS": "Consenti iscrizioni oltre la data / ora di iscrizione", "SLOT": "Fessura", "UNSAVED": "Hai modifiche non salvate. Vuoi procedere?", + "GAME_OPTIONS": "Opzioni di gioco", + "DISABLE_WAITLIST": "Disabilitare lista d'attesa", + "UPLOAD_GAME_IMAGE": "Carica Gioco Immagine", + "MAX_UPLOAD_SIZE": "Dimensione massima (5 MB)", "options": { "AUTOMATED_SIGNUP": "Entrata automatica", "CUSTOM_SIGNUP_INSTRUCTIONS": "Istruzioni di iscrizione personalizzate", @@ -207,6 +211,7 @@ "RESCHEDULED": "Il gioco è stato riprogrammato", "DELETED": "Il gioco è stato cancellato", "EDIT_PERMISSION": "Non sei autorizzato a modificare questo gioco", - "ALREADY_STARTED": "Quel gioco è già iniziato!" + "ALREADY_STARTED": "Quel gioco è già iniziato!", + "MAX_NO_WAITLIST": "Questo gioco è pieno e non accettare iscrizioni supplementari." } } diff --git a/lang/ru.json b/lang/ru.json index fc4ed21..1934a8f 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -72,6 +72,10 @@ "PAST_SIGNUPS": "Разрешить регистрацию после даты / времени регистрации", "SLOT": "слот", "UNSAVED": "У вас есть несохраненные изменения. Вы хотите продолжить?", + "GAME_OPTIONS": "Варианты игры", + "DISABLE_WAITLIST": "Отключить листа ожидания", + "UPLOAD_GAME_IMAGE": "Загрузить игру Изображение", + "MAX_UPLOAD_SIZE": "Максимальный размер (5 MB)", "options": { "AUTOMATED_SIGNUP": "Автоматическая запись", "CUSTOM_SIGNUP_INSTRUCTIONS": "Свои условия записи", @@ -209,6 +213,7 @@ "RESCHEDULED": "Игра перенесена", "DELETED": "Игра была удалена", "EDIT_PERMISSION": "У вас нет разрешения на редактирование этой игры", - "ALREADY_STARTED": "Эта игра уже началась!" + "ALREADY_STARTED": "Эта игра уже началась!", + "MAX_NO_WAITLIST": "Эта игра полна и не принимать дополнительные подписки." } } diff --git a/package-lock.json b/package-lock.json index fc22e08..bfbb2ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -594,6 +594,11 @@ "supports-color": "^5.3.0" } }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", @@ -780,6 +785,11 @@ "which": "^1.2.9" } }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", @@ -2449,6 +2459,16 @@ "object-visit": "^1.0.0" } }, + "md5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", + "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "requires": { + "charenc": "~0.0.1", + "crypt": "~0.0.1", + "is-buffer": "~1.1.1" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", diff --git a/package.json b/package.json index 09985f1..37ffbdc 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "express-session": "^1.16.1", "ics": "^2.19.0", "lodash": "^4.17.15", + "md5": "^2.2.1", "moment": "^2.24.0", "moment-recur-ts": "^1.3.1", "moment-timezone": "^0.5.31", diff --git a/src/appaux.ts b/src/appaux.ts index 5841b6f..05ddd6a 100644 --- a/src/appaux.ts +++ b/src/appaux.ts @@ -5,6 +5,8 @@ import toPairs from "lodash/toPairs"; import moment from "moment-timezone"; import "moment-recur-ts"; import axios from "axios"; +import md5 from "md5"; +import config from "./models/config"; import { GameModel } from "./models/game"; interface Path { @@ -53,6 +55,24 @@ const patreonPledges = async () => { } }; +const getAPIKey = async (discordId: string) => { + const pledges = await patreonPledges(); + const pledge = + pledges.status === "success" + ? pledges.data.find((p) => p.reward.id === config.patreon.apiPledge && (p.patron.attributes.social_connections.discord || {}).user_id === discordId) + : null; + return pledge && md5(`${pledge.patron.id}${discordId}`); +}; + +const validateAPIKey = async (key: string) => { + const pledges = await patreonPledges(); + const pledge = + pledges.status === "success" + ? pledges.data.find((p) => config.patreon.apiPledge.split(",").includes(p.reward.id) && md5(`${p.patron.id}${(p.patron.attributes.social_connections.discord || {}).user_id}`) === key) + : null; + return pledge && (pledge.patron.attributes.social_connections.discord || {}).user_id; +}; + const log = (...content: any) => { let locale = moment.locale(); if (locale !== "en") moment.locale("en"); @@ -372,4 +392,6 @@ export default { log: log, patreonPledges: patreonPledges, colorFixer: colorFixer, + getAPIKey: getAPIKey, + validateAPIKey: validateAPIKey }; diff --git a/src/index.ts b/src/index.ts index ac77884..d6edea5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,12 +18,13 @@ import rssRoutes from "./routes/rss"; const app = express(); app.use(bodyParser.urlencoded({ extended: true })); -app.use(bodyParser.json()); +app.use(bodyParser.json({ limit: "10mb" })); app.use(express.static(path.join(__dirname, "..", "public"))); app.use(cookieParser()); app.use(async (req, res, next) => { - res.set("Access-Control-Allow-Origin", process.env.HOST); + // res.set("Access-Control-Allow-Origin", process.env.HOST); + res.set("Access-Control-Allow-Origin", "*"); res.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); res.set("Access-Control-Allow-Headers", "authorization, accept, content-type, origin, x-requested-with, host, origin, referer, locale"); next(); diff --git a/src/models/config.ts b/src/models/config.ts index 2248d75..878ba05 100644 --- a/src/models/config.ts +++ b/src/models/config.ts @@ -42,7 +42,8 @@ export default { author: "Sillvva#2532", command: "schedule", patreon: { - creditPledge: '4773971' + creditPledge: "4773971", + apiPledge: "5658220" }, defaults: { sessionStatus: { diff --git a/src/models/game.ts b/src/models/game.ts index 9b68bf3..176a685 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -384,7 +384,7 @@ export class Game implements GameModel { } const rsvps = await GameRSVP.fetch(game._id); - game.reserved = game.reserved.map(r => { + game.reserved = game.reserved.map((r) => { r.tag = r.tag.trim().replace(/^@/g, ""); return r; }); @@ -504,9 +504,9 @@ export class Game implements GameModel { if (description.length > 0) embed.setDescription(description); if (game.hideDate) embed.addField(lang.game.WHEN, lang.game.labels.TBD, true); else embed.addField(lang.game.WHEN, when, true); - if (game.runtime && game.runtime.trim().length > 0 && game.runtime.trim() != "0") embed.addField(lang.game.RUN_TIME, `${game.runtime} ${lang.game.labels.HOURS}`, true); + if (game.runtime && game.runtime.trim().length > 0 && game.runtime.trim() != "0") embed.addField(lang.game.RUN_TIME, `${game.runtime} ${lang.game.labels.HOURS}`, guildConfig.embedMentions || where.trim().length > 0); + if (guildConfig.embedMentions) embed.addField(lang.game.GM, gmTag, where.trim().length > 0); if (where.trim().length > 0) embed.addField(lang.game.WHERE, where); - if (guildConfig.embedMentions) embed.addField(lang.game.GM, gmTag); if (game.method === GameMethod.CUSTOM) { embed.addField(lang.game.CUSTOM_SIGNUP_INSTRUCTIONS, game.customSignup); } @@ -543,7 +543,7 @@ export class Game implements GameModel { if (guildConfig.embeds) { if (guildConfig.embedMentionsAbove) { - const mentions = [Game.parseDiscord(game.description, guild, true), dmmember && dmmember.user.toString(), rMentions.join(" ")]; + const mentions = [dmmember && dmmember.user.toString(), Game.parseDiscord(game.description, guild, true), rMentions.join(" ")].join(" ").trim().split(" "); msg = mentions.filter((m, i) => m && mentions.indexOf(m) === i).join(" "); } } else embed = null; @@ -581,7 +581,7 @@ export class Game implements GameModel { aux.log("UpdateGameError:", err); if (updated) updated.modifiedCount = 0; } - + const saved: GameSaveData = { _id: game._id, message: message, @@ -602,7 +602,7 @@ export class Game implements GameModel { let member = guildMembers.find( (mem) => mem.user.tag.trim() === ru.tag.trim().replace("@", "") || mem.user.id == ru.tag.trim().replace(/[<@>]/g, "") || mem.user.id === ru.id ); - const rsvp = new GameRSVP({ _id: new ObjectID(), gameId: inserted.insertedId, id: ru.id, tag: ru.tag, timestamp: game.createdTimestamp+i }); + const rsvp = new GameRSVP({ _id: new ObjectID(), gameId: inserted.insertedId, id: ru.id, tag: ru.tag, timestamp: game.createdTimestamp + i }); await rsvp.save(); if (member) { this.dmCustomInstructions(member.user); @@ -612,7 +612,7 @@ export class Game implements GameModel { if (guildConfig.embeds) { if (guildConfig.embedMentionsAbove) { - const mentions = [Game.parseDiscord(game.description, guild, true), dmmember && dmmember.user.toString(), rMentions.join(" ")]; + const mentions = [dmmember && dmmember.user.toString(), Game.parseDiscord(game.description, guild, true), rMentions.join(" ")].join(" ").trim().split(" "); msg = mentions.filter((m, i) => m && mentions.indexOf(m) === i).join(" "); } } else embed = null; @@ -656,7 +656,7 @@ export class Game implements GameModel { aux.log(`GameMessageNotPostedError:\n`, "s", game.s, "_id", game._id); await Game.hardDelete(inserted.insertedId); } - + if (dmmember) { dmmember.send("The bot does not have sufficient permissions to post in the configured Discord channel"); } @@ -942,10 +942,25 @@ export class Game implements GameModel { try { const validDays = this.getWeekdays(); const nextDate = Game.getNextDate(moment(this.date), validDays, Number(this.frequency), this.monthlyType, this.xWeeks); - aux.log(`Rescheduling ${this.s}: ${this.adventure} from ${this.date} (${this.time}) to ${nextDate} (${this.time})`); this.date = nextDate; const guildConfig = await GuildConfig.fetch(this.s); + + if (this.client && this.dm.id) { + const guild = this.client.guilds.cache.find(g => g.id === this.s); + if (guild) { + const member = guild.members.cache.find(m => m.user.id === this.dm.id); + if (!member || !guildConfig.memberHasPermission(member, this.c)) { + aux.log(`Removing game ${this._id} from ${this.s}. User no longer has permission to post games.`); + this.frequency = Frequency.NO_REPEAT; + await this.save(); + await this.delete(); + return false; + } + } + } + + aux.log(`Rescheduling ${this.s}: ${this.adventure} from ${this.date} (${this.time}) to ${nextDate} (${this.time})`); if (guildConfig.rescheduleMode === RescheduleMode.UPDATE) { if (this.clearReservedOnRepeat) { this.reserved = []; @@ -1010,8 +1025,6 @@ export class Game implements GameModel { } } - static async; - static async softDeleteAllBy(query: mongodb.FilterQuery) { if (!connection()) { aux.log("No database connection"); @@ -1019,7 +1032,7 @@ export class Game implements GameModel { } return await connection() .collection(collection) - .updateMany({ ...query }, { $set: { deleted: true } }); + .updateMany({ ...query }, { $set: { deleted: true, frequency: Frequency.NO_REPEAT } }); } static async hardDeleteAllBy(query: mongodb.FilterQuery) { @@ -1059,7 +1072,7 @@ export class Game implements GameModel { static async softDelete(_id: string | number | mongodb.ObjectID) { return await connection() .collection(collection) - .updateOne({ _id: new ObjectId(_id) }, { $set: { deleted: true } }); + .updateOne({ _id: new ObjectId(_id) }, { $set: { deleted: true, frequency: Frequency.NO_REPEAT } }); } async delete(options: any = {}) { @@ -1215,11 +1228,13 @@ export class Game implements GameModel { const t = new Date().getTime() - 100 * this.reserved.length; if (!this.discordGuild) return; if (!guildMembers) guildMembers = this.discordGuild.members; - this.reserved = this.reserved.map(r => { + this.reserved = this.reserved.map((r) => { r.tag = r.tag.trim().replace(/^@/g, ""); return r; }); - const checkDupes = this.reserved.filter((r, i) => !/#\d{4}$/.test(r.tag.trim()) || this.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || (rr.tag === r.tag && /#\d{4}/i.test(r.tag))) === i); + const checkDupes = this.reserved.filter( + (r, i) => !/#\d{4}$/.test(r.tag.trim()) || this.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || (rr.tag === r.tag && /#\d{4}/i.test(r.tag))) === i + ); if (this.reserved.length > checkDupes.length) { this.reserved = checkDupes; } @@ -1229,7 +1244,7 @@ export class Game implements GameModel { const res = cloneDeep(this.reserved[i]); const member = guildMembers.find((m) => (this.reserved[i] && m.user.id === this.reserved[i].id) || m.user.tag === this.reserved[i].tag.trim()); const countMatches = this.reserved.filter((rr, ri) => ri <= i && ((rr.id ? rr.id === res.id : false) || (rr.tag === res.tag && !/#\d{4}/i.test(res.tag)))); - const rsvpMatches = rsvps.filter(r => r._id === res._id || (r.id && r.id === res.id) || r.tag === res.tag); + const rsvpMatches = rsvps.filter((r) => r._id === res._id || (r.id && r.id === res.id) || r.tag === res.tag); // console.log(res.tag, countMatches.length, rsvpMatches.length); let rsvp = rsvps.find((r) => r._id === res._id || (r.id && r.id === res.id) || r.tag === res.tag); if (!rsvp) rsvp = await GameRSVP.fetchRSVP(this._id, res.id || res.tag); @@ -1254,7 +1269,9 @@ export class Game implements GameModel { aux.log("InsertRSVPError:", err); } } - this.reserved = this.reserved.filter((r, i) => !/#\d{4}$/.test(r.tag.trim()) || this.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || (rr.tag === r.tag && /#\d{4}/i.test(r.tag))) === i); + this.reserved = this.reserved.filter( + (r, i) => !/#\d{4}$/.test(r.tag.trim()) || this.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || (rr.tag === r.tag && /#\d{4}/i.test(r.tag))) === i + ); // console.log(this.reserved); } catch (err) { aux.log("UpdateReservedListError:", err); @@ -1339,19 +1356,19 @@ export class Game implements GameModel { static parseDiscord(text: string, guild: ShardGuild, getMentions: boolean = false) { const mentions: string[] = []; try { - guild.members.forEach((mem) => { - if (new RegExp(`\@${aux.backslash(mem.user.tag)}`, "gi").test(text)) mentions.push(mem.user.toString()); - text = text.replace(new RegExp(`\@${aux.backslash(mem.user.tag)}`, "gi"), mem.user.toString()); - }); - guild.channels.forEach((c) => { - text = text.replace(new RegExp(`\#${aux.backslash(c.name)}`, "gi"), `<#${c.id}>`); - }); guild.roles.forEach((role) => { // const canMention = guild.members.hasPermission(Permissions.FLAGS.toString()_EVERYONE); const canMention = true; if ((!role.mentionable && !canMention) || ["@everyone", "@here"].includes(role.name)) return; - if (new RegExp(`\@${aux.backslash(role.name)}`, "gi").test(text)) mentions.push(`<@&${role.id}>`); - text = text.replace(new RegExp(`\@${aux.backslash(role.name)}`, "gi"), `<@&${role.id}>`); + if (new RegExp(`?`, "gi").test(text)) mentions.push(`<@&${role.id}>`); + text = text.replace(new RegExp(`?`, "gi"), `<@&${role.id}>`); + }); + guild.members.forEach((mem) => { + if (new RegExp(`?`, "gi").test(text)) mentions.push(mem.user.toString()); + text = text.replace(new RegExp(`?`, "gi"), mem.user.toString()); + }); + guild.channels.forEach((c) => { + text = text.replace(new RegExp(`\#${aux.backslash(c.name)}`, "gi"), `<#${c.id}>`); }); } catch (err) { aux.log(err); diff --git a/src/models/user.ts b/src/models/user.ts index 41225d4..b89ac47 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -7,6 +7,7 @@ const collection = "users"; export interface UserModel { lang?: string; notification?: string; + lastAPIAccess?: number; } interface UserDataModel extends UserModel { @@ -19,6 +20,7 @@ export class User implements UserDataModel { id: string; lang: string = "en"; notification: string = ""; + lastAPIAccess: number = 0; constructor(session: UserDataModel) { Object.entries(session).forEach(([key, value]) => { @@ -31,7 +33,8 @@ export class User implements UserDataModel { _id: this._id, id: this.id, lang: this.lang, - notification: this.notification + notification: this.notification, + lastAPIAccess: this.lastAPIAccess }; } @@ -42,14 +45,15 @@ export class User implements UserDataModel { return await col.updateOne({ id: this.id }, { $set: { ...user } }, { upsert: true }); } - static async fetch(id: string): Promise { + static async fetch(id: string, save: boolean = true): Promise { if (!connection()) throw new Error("No database connection"); const data = await connection().collection(collection).findOne({ id: id }); if (data) return new User(data); - else { + else if (save) { const user = new User({ _id: new ObjectId(), id: id }); await user.save(); return user; } + else return null; } } diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index 535c2b6..1f64b60 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -598,12 +598,12 @@ const refreshGuild = async function (guildId: string) { members: [], // c.members.map((m) => m.user.id), everyone: c.permissionsFor(c.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), botPermissions: [ - c.permissionsFor(client.user.id).has(Permissions.FLAGS.VIEW_CHANNEL) && "VIEW_CHANNEL", - c.permissionsFor(client.user.id).has(Permissions.FLAGS.READ_MESSAGE_HISTORY) && "READ_MESSAGE_HISTORY", - c.permissionsFor(client.user.id).has(Permissions.FLAGS.SEND_MESSAGES) && "SEND_MESSAGES", - c.permissionsFor(client.user.id).has(Permissions.FLAGS.MANAGE_MESSAGES) && "MANAGE_MESSAGES", - c.permissionsFor(client.user.id).has(Permissions.FLAGS.EMBED_LINKS) && "EMBED_LINKS", - c.permissionsFor(client.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.VIEW_CHANNEL) && "VIEW_CHANNEL", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.READ_MESSAGE_HISTORY) && "READ_MESSAGE_HISTORY", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.SEND_MESSAGES) && "SEND_MESSAGES", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.MANAGE_MESSAGES) && "MANAGE_MESSAGES", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.EMBED_LINKS) && "EMBED_LINKS", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", ].filter((check) => check) })), roles: guild.roles.cache.array(), diff --git a/src/routes/api.ts b/src/routes/api.ts index 31f48b5..1ce0564 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -2,6 +2,7 @@ import discord, { Permissions, Role, ShardingManager } from "discord.js"; import express from "express"; import moment from "moment"; import request from "request"; +import axios from "axios"; import merge from "lodash/merge"; import cloneDeep from "lodash/cloneDeep"; @@ -244,10 +245,14 @@ export default (options: APIRouteOptions) => { }) .then(async (result: any) => { const userSettings = await getUserSettings(result.account.user.id, req); + const apiKey = await aux.getAPIKey(result.account.user.id); res.json({ status: "success", token: req.session.api.access.access_token, - account: result.account, + account: { + ...result.account, + apiKey: await aux.getAPIKey(result.account.user.id), + }, user: userSettings.data, version: apiVersion, }); @@ -361,7 +366,10 @@ export default (options: APIRouteOptions) => { res.json({ status: "success", token: req.session.api.access.access_token, - account: result.account, + account: { + ...result.account, + apiKey: await aux.getAPIKey(result.account.user.id), + }, user: userSettings.data, version: apiVersion, }); @@ -398,7 +406,7 @@ export default (options: APIRouteOptions) => { const guild = result.account.guilds.find((g) => g.id == req.body.id); if (!guild) throw new Error("Guild not found"); if (!guild.isAdmin) throw new Error("You don't have permission to do that"); - req.body.channel = req.body.channel.filter(c => !isNaN(c.channelId)); + req.body.channel = req.body.channel.filter((c) => !isNaN(c.channelId)); for (const property in guildConfig) { if (property === "_id") continue; if (typeof req.body[property] != "undefined") guildConfig[property] = req.body[property]; @@ -814,7 +822,10 @@ export default (options: APIRouteOptions) => { let uRes: GameModel; if (!req.query.g) uRes = await updatedGame.updateReservedList(); uRes = { ...data, ...uRes }; - uRes.reserved = uRes.reserved.filter((r, i) => !/#\d{4}$/.test(r.tag.trim()) || uRes.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || (rr.tag === r.tag && /#\d{4}/i.test(r.tag))) === i); + uRes.reserved = uRes.reserved.filter( + (r, i) => + !/#\d{4}$/.test(r.tag.trim()) || uRes.reserved.findIndex((rr) => (rr.id ? rr.id === r.id : false) || (rr.tag === r.tag && /#\d{4}/i.test(r.tag))) === i + ); res.json({ status: response.modified ? "success" : "error", token: req.session.api.access.access_token, @@ -1143,6 +1154,315 @@ export default (options: APIRouteOptions) => { }); }); + router.get("/api/get-key", async (req, res, next) => { + const pledges = await aux.patreonPledges(); + const pledge = + pledges.status === "success" + ? pledges.data.find((p) => p.reward.id === config.patreon.apiPledge && (p.patron.attributes.social_connections.discord || {}).user_id === req.query.id) + : null; + const key = await aux.getAPIKey(req.query.id); + res.json( + pledge + ? { + status: "success", + key: key, + } + : { + status: "error", + message: "Missing required Patreon pledge", + } + ); + }); + + router.post("/api/upload-to-imgur", async (req, res, next) => { + try { + const result = await axios.post( + "https://api.imgur.com/3/image", + { + image: req.body.image + }, + { + headers: { + Authorization: `Client-ID ${process.env.IMGUR_CLIENT_ID}`, + "Content-Type": "application/json", + }, + } + ); + res.json(result.data.data); + } catch (err) { + res.json({ + error: err.message, + }); + } + }); + + const patronAPILimit = 5; // Once per X minutes + + router.get("/patron-api/games", async (req, res, next) => { + const { key, guildId } = req.query; + let id = await aux.validateAPIKey(key); + // id = "202640192178225152"; + + if (!id) { + return res.json({ + status: "error", + message: "Invalid API key", + }); + } + + let userSettings = await getUserSettings(id, req, false); + if (userSettings) { + const tdiff = new Date().getTime() - userSettings.lastAPIAccess; + if (tdiff < patronAPILimit * 60 * 1000) { + const remTime = Math.round((5 * 60 * 1000 - tdiff) / 1000); + const remSeconds = remTime % 60; + const remMinutes = (remTime - remSeconds) / 60; + return res.json({ + status: "error", + message: `Too soon. Please wait ${remMinutes > 0 ? `${remMinutes} minutes` : ""}${remMinutes > 0 && remSeconds > 0 ? ", " : ""}${ + remSeconds > 0 ? `${remSeconds} seconds` : "" + }.`, + }); + } else { + userSettings.lastAPIAccess = new Date().getTime(); + await userSettings.save(); + } + } + + const options = { + guilds: true, + games: true, + search: "", + }; + + const account = { + user: { + ...req.query, + ...{ + tag: "", + }, + }, + guilds: [], + }; + + let sGuilds: ShardGuild[] = []; + + if (options.guilds) { + const fTime = new Date().getTime(); + if (guildId) { + let guild: ShardGuild = await new Promise(async (resolve) => { + const g = await ShardManager.refreshGuild(guildId); + resolve(g.find((g) => g.id === guildId)); + }); + const member = guild.members.find((member) => { + return id && member.user.id === id; + }); + if (member) sGuilds.push(guild); + else + return res.json({ + status: "error", + message: "API key's owner was not found in the specified server", + }); + } else { + return res.json({ + status: "error", + message: "Server id not specified", + }); + } + + if (sGuilds.length === 0) { + return res.json({ + status: "error", + message: "Server not found", + }); + } + // console.log(new Date().getTime() - fTime, req.query, tag, sGuilds.length); + + sGuilds.forEach((guild) => { + const guildInfo: AccountGuild = { + id: guild.id, + name: guild.name, + icon: guild.icon ? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png?size=128` : "/images/logo2.png", + permission: false, + isAdmin: false, + member: null, + roles: guild.roles, + userRoles: [], + channelCategories: guild.channels.filter((c) => c.type === "category"), + channels: guild.channels.filter((c) => c.type === "text"), + announcementChannels: [], + config: new GuildConfig({ guild: guild.id }), + games: [], + }; + + guild.members.forEach((member) => { + if (id && member.user.id === id) { + guildInfo.member = member; + account.user = member.user; + if (!options.search) account.guilds.push(guildInfo); + } + }); + if (options.search) { + if (new RegExp(options.search, "gi").test(guild.name)) { + account.guilds.push(guildInfo); + } + } + }); + + if (!userSettings) { + userSettings = await getUserSettings(account.user.id, req); + if (userSettings) { + const tdiff = new Date().getTime() - userSettings.lastAPIAccess; + if (tdiff < 5 * 60 * 1000) { + const remTime = Math.round((5 * 60 * 1000 - tdiff) / 1000); + const remSeconds = remTime % 60; + const remMinutes = (remTime - remSeconds) / 60; + return res.json({ + status: "error", + message: `Too soon. Please wait ${remMinutes > 0 ? `${remMinutes} minutes` : ""}${remMinutes > 0 && remSeconds > 0 && ", "}${ + remSeconds > 0 ? `${remSeconds} seconds` : "" + }.`, + }); + } else { + userSettings.lastAPIAccess = new Date().getTime(); + await userSettings.save(); + } + } else { + userSettings.lastAPIAccess = new Date().getTime(); + await userSettings.save(); + } + } + + const gcQuery = { + guild: { + $in: account.guilds.reduce((i, g) => { + i.push(g.id); + return i; + }, []), + }, + }; + + const guildConfigs = await GuildConfig.fetchAllBy(gcQuery); + // console.log(new Date().getTime() - fTime); + + for (let gi = 0; gi < account.guilds.length; gi++) { + const guild: AccountGuild = account.guilds[gi]; + const guildConfig = guildConfigs.find((gc) => gc.guild === guild.id) || new GuildConfig({ guild: guild.id }); + const member = guild.member; + + let gcChannels: ChannelConfig[] = guildConfig.channels; + if (gcChannels.length == 0 || !guild.channels.find((gc) => !!gcChannels.find((c) => gc.id === c.channelId))) { + let firstChannel: ShardChannel; + for (let i = 0; i < guild.channels.length; i++) { + const pf = await guild.channels[i].everyone; + if (pf) firstChannel = guild.channels[i]; + } + if (firstChannel && guild.channels.length > 0) { + gcChannels.push({ channelId: firstChannel.id, gameTemplates: [guildConfig.defaultGameTemplate.id] }); + } + } + + let channels: ShardChannel[] = []; + + if (member) { + guild.userRoles = member.roles.map((r) => r.name); + guild.isAdmin = !!( + member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || + member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || + member.roles.find((r) => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) + ); + guild.permission = guildConfig.shardMemberHasPermission(member) || guild.isAdmin; + gcChannels.forEach((c) => { + const gcc = guild.channels.find((gc) => gc.id === c.channelId); + if (gcc && guild.permission /*|| (gcc.members && gcc.members.includes(member.user.id))*/) channels.push(gcc); + }); + channels = channels.filter((c) => c && member && (guild.isAdmin || !!guildConfig.shardMemberHasPermission(member, c.id))); + // console.log(guild.name, guild.isAdmin, guild.permission, channels.length); + } + + guild.announcementChannels = channels; + guild.config = guildConfig; + account.guilds[gi] = guild; + } + + const accountGuild = account.guilds[0]; + + if (accountGuild && options.games) { + const gameOptions: any = { + s: accountGuild.id, + }; + + const fGames: Game[] = await Game.fetchAllBy(gameOptions, null, sGuilds); + + // console.log(new Date().getTime() - fTime); + fGames + .filter((game) => { + return accountGuild.isAdmin || game.dm.id === id || game.dm.tag === account.user.tag; + }) + .forEach(async (game) => { + if (!game.discordGuild) return; + const date = Game.ISOGameDate(game); + const parsed = aux.parseEventTimes(game); + const sGuild = sGuilds.find((g) => g.id === accountGuild.id); + let gameData = { + links: { + upcoming: `https://www.rpg-schedule.com/games/upcoming?s=${escape(`_id:${game._id}`)}`, + myGames: `https://www.rpg-schedule.com/games/my-games?s=${escape(`_id:${game._id}`)}`, + }, + ...game.data, + dm: (function (r) { + const member = sGuild.members.find((m) => (r.id && m.user.id === r.id) || m.user.tag === r.tag); + return (member && member.nickname) || (r.tag.indexOf("#") < 0 && r.tag) || "User not found"; + })(game.data.dm), + author: (function (r) { + const member = sGuild.members.find((m) => (r.id && m.user.id === r.id) || m.user.tag === r.tag); + return (member && member.nickname) || (r.tag.indexOf("#") < 0 && r.tag) || "User not found"; + })(game.data.author), + reserved: game.data.reserved + .filter((r) => r.tag) + .map((r) => { + const member = sGuild.members.find((m) => (r.id && m.user.id === r.id) || m.user.tag === r.tag); + return (member && member.nickname) || (r.tag.indexOf("#") < 0 && r.tag) || "User not found"; + }), + moment: { + ...parsed, + iso: date, + date: moment(date) + .utcOffset(parseInt(`${game.timezone}`)) + .format(config.formats.dateLong), + calendar: moment(date) + .utcOffset(parseInt(`${game.timezone}`)) + .calendar(), + from: moment(date) + .utcOffset(parseInt(`${game.timezone}`)) + .fromNow(), + }, + }; + delete gameData.messageId; + delete gameData.reminderMessageId; + delete gameData.pm; + delete gameData.reminded; + delete gameData.pruned; + accountGuild.games.push(gameData); + }); + } + + accountGuild.games.sort((a, b) => { + return a.timestamp < b.timestamp ? -1 : 1; + }); + + return res.json({ + status: "success", + games: accountGuild.games, + }); + } + + res.json({ + status: "error", + message: "An error unknown occurred!", + }); + }); + router.get("/test/account", async (req, res, next) => { const { id, username, discriminator, guildId } = req.query; const tag = `${username}#${discriminator}`; @@ -1206,6 +1526,7 @@ export default (options: APIRouteOptions) => { guild.members.forEach((member) => { if ((id && member.user.id === id) || member.user.tag === tag || (member.user.username === username && member.user.discriminator === discriminator)) { guildInfo.member = member; + account.user.tag = member.user.tag; if (!options.search) account.guilds.push(guildInfo); } }); @@ -1704,18 +2025,7 @@ const refreshToken = (access: any) => { }); }; -const getUserSettings = async (id: string, req: any) => { - const userSettings = await User.fetch(id); - let updated = false; - - // if (req.app.locals.lang) { - // if (userSettings.lang != req.app.locals.lang.code) { - // userSettings.lang = req.app.locals.lang.code; - // updated = true; - // } - // } - - if (updated) await userSettings.save(); - +const getUserSettings = async (id: string, req: any, save: boolean = true) => { + const userSettings = await User.fetch(id, save); return userSettings; }; From 4b274e1353a068c34a67adb28b5814cebf09a1bf Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 1 Sep 2020 13:56:57 -0500 Subject: [PATCH 28/53] Reserved List Length Bug --- src/models/game.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index 176a685..d682a44 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -511,9 +511,32 @@ export class Game implements GameModel { embed.addField(lang.game.CUSTOM_SIGNUP_INSTRUCTIONS, game.customSignup); } if (game.method === GameMethod.AUTOMATED || (game.method === GameMethod.CUSTOM && reserved.length > 0)) { - embed.addField(`${lang.game.RESERVED} (${reserved.length}/${game.players})`, reserved.length > 0 ? reserved.join("\n") : lang.game.NO_PLAYERS, true); + const reservedHeader = `${lang.game.RESERVED} (${reserved.length}/${game.players})`; + const waitlistHeader = `${lang.game.WAITLISTED} (${waitlist.length})`; + let reservedColumn = []; + if (reserved.length > 0) { + reserved.forEach(r => { + reservedColumn.push(r); + if (reservedColumn.join("\n").length > 900) { + embed.addField(reservedHeader, reservedColumn.join("\n"), true); + reservedColumn = []; + } + }); + if (waitlist.length > 0 && !game.disableWaitlist) { + reservedColumn = []; + waitlist.forEach(w => { + reservedColumn.push(w); + if (reservedColumn.join("\n").length > 900) { + embed.addField(waitlistHeader, reservedColumn.join("\n"), true); + reservedColumn = []; + } + }) + } + } + else { + embed.addField(reservedHeader, lang.game.NO_PLAYERS, true); + } } - if (waitlist.length > 0 && !game.disableWaitlist) embed.addField(`${lang.game.WAITLISTED} (${waitlist.length})`, waitlist.join("\n"), true); if (!game.hideDate) embed.addField( "Links", From 3d217908800618d39d36c207d14658bfcbd94372 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 1 Sep 2020 13:58:14 -0500 Subject: [PATCH 29/53] Pruning Bug Fix --- src/models/game.ts | 10 ++++++---- src/processes/discord.ts | 9 ++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index d682a44..3597cf8 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -560,7 +560,7 @@ export class Game implements GameModel { let message: Message; try { try { - if (game.messageId) message = await ShardManager.findMessage(this.client, guild.id, channel.id, game.messageId, dmmember, game.timestamp); // channel.messages.fetch(game.messageId); + if (game.messageId) message = await ShardManager.findMessage(this.client, guild.id, channel.id, game.messageId, dmmember, game.timestamp); // console.log(guild.id, channel.id, game.messageId, !!dmmember, game.timestamp, !!message); } catch (err) {} @@ -576,7 +576,8 @@ export class Game implements GameModel { if (this.client) message = await ShardManager.clientMessageEdit(this.client, guild.id, channel.id, message.id, msg, embed); else message = await ShardManager.shardMessageEdit(guild.id, channel.id, message.id, msg, embed); } - } // else if (channel && !game.messageId && !(game).deleted && !game.pruned && game.timestamp >= new Date().getTime() + parseInt(game.reminder) * 60 * 1000) { + } + // else if (channel && !game.messageId && !(game).deleted && !game.pruned && game.timestamp >= new Date().getTime() + parseInt(game.reminder) * 60 * 1000) { // message = await channel.send(msg, embed); // if (message) { // await dbCollection.updateOne({ _id: new ObjectId(game._id) }, { $set: { messageId: message.id } }); @@ -1073,14 +1074,15 @@ export class Game implements GameModel { aux.log("No database connection"); return { deletedCount: 0 }; } - let games = await Game.fetchAllByLimit(query, 200, client, sGuilds); + let deleteQuery = { ...query, ...{ deleted: { $in: [null, false, true] } } }; + let games = await Game.fetchAllByLimit(deleteQuery, 200, client, sGuilds); let deletedCount = 0; while (games.length > 0 && deletedCount < 2000) { const gameIds = games.map((g) => g._id); await GameRSVP.deleteAllGames(gameIds); const result = await Game.hardDeleteAllBy({ _id: { $in: gameIds.map((gid) => new ObjectID(gid)) } }); deletedCount += result.deletedCount; - games = await Game.fetchAllByLimit(query, 200, client); + games = await Game.fetchAllByLimit(deleteQuery, 200, client, sGuilds); } return { deletedCount: deletedCount }; } diff --git a/src/processes/discord.ts b/src/processes/discord.ts index fac9cc4..702a866 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -1336,12 +1336,19 @@ const pruneOldGames = async (guild?: Guild) => { const hardDeleted = await Game.deleteAllBy( { + $or: [ + { hideDate: { $in: [false, null], + } }, + { + deleted: true + } + ], timestamp: { $lt: new Date().getTime() - 14 * 24 * 3600 * 1000, - }, + } }, client ); From 944e3f7291262e2a10c305b3677068a51826998d Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 1 Sep 2020 13:58:23 -0500 Subject: [PATCH 30/53] Bug Fixes --- src/models/game.ts | 24 ++++++++++++++---------- src/models/guild-config.ts | 3 +++ src/processes/discord.ts | 27 +++++++-------------------- src/routes/api.ts | 6 +++++- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index 3597cf8..fa9d276 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -520,7 +520,7 @@ export class Game implements GameModel { if (reservedColumn.join("\n").length > 900) { embed.addField(reservedHeader, reservedColumn.join("\n"), true); reservedColumn = []; - } + } }); if (waitlist.length > 0 && !game.disableWaitlist) { reservedColumn = []; @@ -1318,26 +1318,30 @@ export class Game implements GameModel { } async signUp(user: User | ShardUser, t?: number) { - const hourDiff = (new Date().getTime() - this.timestamp) / 1000 / 3600; + if (!this.discordGuild) return false; + const guildConfig = await GuildConfig.fetch(this.s); + const member = this.discordGuild.members.find((m) => m.user.tag === user.tag.trim() || m.user.id === user.id); + const lang = gmLanguages.find((l) => l.code === guildConfig.lang) || gmLanguages.find((l) => l.code === "en"); + + const hourDiff = (new Date().getTime() - this.timestamp) / 1000 / 3600; if (hourDiff >= 0 && !this.pastSignups && !this.hideDate) { - if (!this.discordGuild) return false; - const member = this.discordGuild.members.find((m) => m.user.tag === user.tag.trim() || m.user.id === user.id); - const guildConfig = await GuildConfig.fetch(this.s); - const lang = gmLanguages.find((l) => l.code === guildConfig.lang) || gmLanguages.find((l) => l.code === "en"); if (member) member.send(lang.other.ALREADY_STARTED); return false; } if (this.disableWaitlist && this.reserved.length >= parseInt(this.players)) { - if (!this.discordGuild) return false; - const member = this.discordGuild.members.find((m) => m.user.tag === user.tag.trim() || m.user.id === user.id); - const guildConfig = await GuildConfig.fetch(this.s); - const lang = gmLanguages.find((l) => l.code === guildConfig.lang) || gmLanguages.find((l) => l.code === "en"); if (member) member.send(lang.other.MAX_NO_WAITLIST); return false; } + const channelConfig = guildConfig.channel.find(c => c.channelId === this.c); + const template = channelConfig ? guildConfig.gameTemplates.find(t => channelConfig.gameTemplates.find(ct => ct.toString() === t.id.toString())) : guildConfig.gameTemplates.find(t => t.isDefault); + if (template && template.playerRole && !member.roles.find(r => r.name === template.playerRole)) { + if (member) member.send("You don't have the role required to join that event."); + return false; + } + let match = await GameRSVP.fetchRSVP(this._id, user.id); if (match && !this.reserved.find((r) => r.id === match.id || r.tag === match.tag)) { await GameRSVP.deleteUser(this._id, user.id); diff --git a/src/models/guild-config.ts b/src/models/guild-config.ts index ab928a4..7c1c791 100644 --- a/src/models/guild-config.ts +++ b/src/models/guild-config.ts @@ -31,6 +31,7 @@ export interface GameTemplate { name: string; isDefault: boolean; role?: string; + playerRole?: string; embedColor?: string; gameDefaults?: GameDefaults; } @@ -182,6 +183,8 @@ export class GuildConfig implements GuildConfigDataModel { updates.gameTemplates = updates.gameTemplates.map((gt) => { if (!gt.id) gt = { id: new ObjectId().toHexString(), ...gt }; gt.embedColor = aux.colorFixer(gt.embedColor); + gt.role = gt.role.trim(); + gt.playerRole = gt.playerRole.trim(); return gt; }); updates.channel = updates.channel.map((channel) => { diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 702a866..17aa75f 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -139,10 +139,7 @@ if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { const guildId = channel.guild.id; const guildConfig = await GuildConfig.fetch(guildId); if (guildConfig.channels.find((c) => c.channelId === channelId)) { - guildConfig.channel.splice( - guildConfig.channel.findIndex((c) => c.channelId === channelId), - 1 - ); + guildConfig.channel = guildConfig.channel.filter((ch) => ch.channelId !== channelId); await guildConfig.save(); const games = await Game.fetchAllBy( @@ -516,7 +513,9 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (channel.trim().length === params[0].trim().length) { return (message.channel).send(responseEmbed(`Channel not found!`)); } - const channels = guildConfig.channels; + const channels = guildConfig.channels.filter((c) => { + return !!guild.channels.cache.get(c.channelId); + }); if (channels.find((c) => c.channelId === channel)) { channels.splice( channels.findIndex((c) => c.channelId === channel), @@ -1189,18 +1188,6 @@ const rescheduleOldGames = async (guildId?: string) => { try { await game.reschedule(); } catch (err) { - const newGames = await Game.fetchAllBy( - { - s: game.s, - c: game.c, - adventure: game.adventure, - date: { - $ne: game.date, - }, - time: game.time, - }, - client - ); } } } @@ -1338,10 +1325,10 @@ const pruneOldGames = async (guild?: Guild) => { { $or: [ { - hideDate: { - $in: [false, null], + hideDate: { + $in: [false, null], } - }, + }, { deleted: true } diff --git a/src/routes/api.ts b/src/routes/api.ts index 1ce0564..5c8171a 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -807,6 +807,10 @@ export default (options: APIRouteOptions) => { game.hideDate = req.body["hideDate"] ? true : false; game.clearReservedOnRepeat = req.body["clearReservedOnRepeat"] ? true : false; + const eventTimes = aux.parseEventTimes(game, { isField: true }); + const timestamp = new Date(game.when === GameWhen.DATETIME ? eventTimes.rawDate : null).getTime(); + game.reminded = new Date().getTime() + parseInt(game.reminder) * 60 * 1000 < timestamp ? false : game.reminded; + if (req.body.copy) { delete game.reminded; delete (game).deleted; @@ -1179,7 +1183,7 @@ export default (options: APIRouteOptions) => { const result = await axios.post( "https://api.imgur.com/3/image", { - image: req.body.image + image: req.body.image, }, { headers: { From ef14d248f582be1b782bb6cbd1d95001656aec63 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Fri, 4 Sep 2020 12:07:59 -0500 Subject: [PATCH 31/53] Player Role Language --- lang/de-ch.json | 2 ++ lang/de.json | 2 ++ lang/en.json | 2 ++ lang/es.json | 2 ++ lang/fr.json | 2 ++ lang/it.json | 2 ++ lang/ru.json | 2 ++ 7 files changed, 14 insertions(+) diff --git a/lang/de-ch.json b/lang/de-ch.json index 26d9e16..bb3f16a 100644 --- a/lang/de-ch.json +++ b/lang/de-ch.json @@ -162,6 +162,7 @@ "ROLE": "Rolle", "ROLE_SET": "Rolle wurde auf `:ROLE` gesetzt!", "ROLE_CLEARED": "Rolle gelöscht!", + "PLAYER_ROLE": "Spieler Rolle", "NO_ROLE": "Keine Rolle", "MANAGER_ROLE": "Manager Rolle", "LANGUAGE": "Sprache", @@ -194,6 +195,7 @@ "EMOJI_ERROR": "Emoji muss ein Unicode Emoji Zeichen sein. Du hast eingegeben `:CHAR`. Hier eine vollständige Liste von Emojis (verwende die Browser-Version): https://www.unicode.org/emoji/charts/full-emoji-list.html", "PRIVATE_REMINDERS": "Stellen Sie ein, ob die Spielerinnerungen an private Nachrichten gesendet werden. Aktuell `:PR`", "ROLE": "Weise eine Rolle zu, die als Voraussetzung gilt, um Spiele zu veröffentlichen.", + "PLAYER_ROLE": "eine Rolle als Voraussetzung für Vergeben für Spiele anmelden", "MANAGER_ROLE": "Weise eine Rolle zu, um die Verwaltung aller Spiele auf dem Server zu ermöglichen.", "PASSWORD_SET": "Konfiguriere ein Passwort für das Veröffentlichen von Spielen.", "PASSWORD_CLEAR": "Passwort entfernen", diff --git a/lang/de.json b/lang/de.json index 3fb1305..0987863 100644 --- a/lang/de.json +++ b/lang/de.json @@ -161,6 +161,7 @@ "PASSWORD_SET": "Passwort aktualisiert!", "ROLE": "Rolle", "ROLE_SET": "Rolle wurde auf `:ROLE` gesetzt!", + "PLAYER_ROLE": "Spieler Rolle", "NO_ROLE": "Keine Rolle", "ROLE_CLEARED": "Rolle gelöscht!", "MANAGER_ROLE": "Manager Rolle", @@ -194,6 +195,7 @@ "EMOJI_ERROR": "Emoji muss ein Unicode Emoji Zeichen sein. Du hast eingegeben `:CHAR`. Hier eine vollständige Liste von Emojis (verwende die Browser-Version): https://www.unicode.org/emoji/charts/full-emoji-list.html", "PRIVATE_REMINDERS": "Stellen Sie ein, ob die Spielerinnerungen an private Nachrichten gesendet werden. Aktuell `:PR`", "ROLE": "Weise eine Rolle zu, die als Voraussetzung gilt, um Spiele zu veröffentlichen.", + "PLAYER_ROLE": "eine Rolle als Voraussetzung für Vergeben für Spiele anmelden", "MANAGER_ROLE": "Weise eine Rolle zu, um die Verwaltung aller Spiele auf dem Server zu ermöglichen.", "PASSWORD_SET": "Konfiguriere ein Passwort für das Veröffentlichen von Spielen.", "PASSWORD_CLEAR": "Passwort entfernen", diff --git a/lang/en.json b/lang/en.json index 53c2876..331f252 100644 --- a/lang/en.json +++ b/lang/en.json @@ -162,6 +162,7 @@ "ROLE": "Role", "ROLE_SET": "Role set to `:ROLE`!", "ROLE_CLEARED": "Role cleared!", + "PLAYER_ROLE": "Player Role", "NO_ROLE": "No Role", "MANAGER_ROLE": "Manager Role", "LANGUAGE": "Language", @@ -194,6 +195,7 @@ "EMOJI_ERROR": "Emoji must be a unicode emoji character. You entered `:CHAR`. See https://www.unicode.org/emoji/charts/full-emoji-list.html for a full list of emoji. Use the browser version.", "PRIVATE_REMINDERS": "Toggle whether the game reminders are sent to private messages. Currently `:PR`", "ROLE": "Assign a role as a prerequisite for posting games", + "PLAYER_ROLE": "Assign a role as a prerequisite for signing up for games", "MANAGER_ROLE": "Assign a role to allow managing all server games", "PASSWORD_SET": "Configure a password for posting games", "PASSWORD_CLEAR": "Remove the password", diff --git a/lang/es.json b/lang/es.json index 0ac8bbe..6d357da 100644 --- a/lang/es.json +++ b/lang/es.json @@ -162,6 +162,7 @@ "ROLE": "Rol", "ROLE_SET": "¡Rol puesto a `:ROLE`!", "ROLE_CLEARED": "Rol limpio!", + "PLAYER_ROLE": "Rol jugador", "NO_ROLE": "Sin rol", "MANAGER_ROLE": "Administrar Rol", "LANGUAGE": "Lenguaje", @@ -194,6 +195,7 @@ "EMOJI_ERROR": "Emoji debe ser un unicode emoji caracter. Has entrado `:CHAR`. Mira https://www.unicode.org/emoji/charts/full-emoji-list.html para la lista completa de emojis. Utiliza la version de escritorio.", "PRIVATE_REMINDERS": "ELegir si quieres que los recordatorisose envien por mensaje privado. Actualmente `:PR`", "ROLE": "Asignar un rol como pre-requisito para postear juegos", + "PLAYER_ROLE": "Asignar un papel como un requisito previo para la firma de juegos", "MANAGER_ROLE": "Asignar un rol para administar todos los servidores del juego", "PASSWORD_SET": "Configurar una contraseña para postear juegos", "PASSWORD_CLEAR": "Eliminar la contraseña", diff --git a/lang/fr.json b/lang/fr.json index 1075556..9080b8c 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -162,6 +162,7 @@ "ROLE": "Rôle", "ROLE_SET": "Rôle attribué à `:ROLE`!", "ROLE_CLEARED": "Rôle effacé!", + "PLAYER_ROLE": "joueur Rôle", "NO_ROLE": "Aucun rôle", "MANAGER_ROLE": "Rôle de Directeur", "LANGUAGE": "La langue", @@ -194,6 +195,7 @@ "EMOJI_ERROR": "L'emoji doit être un caractère unicode connu comme emoji. Tu as entré `:CHAR`. Allez voir https://www.unicode.org/emoji/charts/full-emoji-list.html pour une liste complete des Emojis. Utilisez la version du navigateur.", "PRIVATE_REMINDERS": "Indiquez si les rappels de jeu sont envoyés aux messages privés. Actuellement `:PR`", "ROLE": "Attribuer un rôle comme condition préalable à la publication de partie", + "PLAYER_ROLE": "Attribuer un rôle en tant que condition préalable à la signature pour les jeux", "MANAGER_ROLE": "Attribuer un rôle pour permettre la gestion de tous les jeux sur serveur", "PASSWORD_SET": "Configurer un mot de passe pour les parties à créer", "PASSWORD_CLEAR": "Supprimer le mot de passe", diff --git a/lang/it.json b/lang/it.json index 984bf88..c477c9d 100644 --- a/lang/it.json +++ b/lang/it.json @@ -160,6 +160,7 @@ "ROLE": "Ruolo", "ROLE_SET": "Ruolo impostato su `:ROLE`!", "ROLE_CLEARED": "Ruolo liberato!", + "PLAYER_ROLE": "Ruolo Player", "NO_ROLE": "No Ruolo", "MANAGER_ROLE": "Gestisci ruoli", "LANGUAGE": "Linguaggio", @@ -192,6 +193,7 @@ "EMOJI_ERROR": "L'emoji deve essere un carattere emoji unicode. Hai inserito `:CHAR`. Vedi https://www.unicode.org/emoji/charts/full-emoji-list.html per una liosta completa di emoji. Usa la versione browser.", "PRIVATE_REMINDERS": "Seleziona se i promemoria del gioco vengono inviati via messaggi privati. Attuale `:PR`", "ROLE": "Assegna un ruolo come prerequisito per la pubblicazione di giochi", + "PLAYER_ROLE": "Assegnare un ruolo di un prerequisito per la firma per i giochi", "MANAGER_ROLE": "Assegna un ruolo per consentire la gestione di tutti i giochi del server", "PASSWORD_SET": "Configura una password per la pubblicazione di giochi", "PASSWORD_CLEAR": "Rimuovi la password", diff --git a/lang/ru.json b/lang/ru.json index 1934a8f..4dd79c9 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -162,6 +162,7 @@ "ROLE": "Роль", "ROLE_SET": "Роль установлена на :ROLE!", "ROLE_CLEARED": "Роль очищена!", + "PLAYER_ROLE": "Роль игрока", "NO_ROLE": "Нет Роль", "MANAGER_ROLE": "Роль модератора", "LANGUAGE": "Язык", @@ -194,6 +195,7 @@ "EMOJI_ERROR": "Emoji должен быть символ unicode emoji. Вы вписали :CHAR. Смотрите https://www.unicode.org/emoji/charts/full-emoji-list.html для полного списка emoji. Используйте браузерную версию.", "PRIVATE_REMINDERS": "Переключить, отправляются ли напоминания об игре в личные сообщения. Сейчас :PR", "ROLE": "Назначить роль с доступом к публикации объявлений", + "PLAYER_ROLE": "Назначить роль в качестве необходимого условия для подписания для игр", "MANAGER_ROLE": "Назначить роль с доступом ко всем играм сервера", "PASSWORD_SET": "Настроить пароль для публикации игр", "PASSWORD_CLEAR": "Удалить пароль", From e850cdcd9a986a26c3eb2a567f4144fd0c04c9e5 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 8 Sep 2020 11:27:30 -0500 Subject: [PATCH 32/53] Bug Fix for Missing Reserved List --- src/models/game.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/models/game.ts b/src/models/game.ts index fa9d276..06e503c 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -522,6 +522,7 @@ export class Game implements GameModel { reservedColumn = []; } }); + embed.addField(reservedHeader, reservedColumn.join("\n"), true); if (waitlist.length > 0 && !game.disableWaitlist) { reservedColumn = []; waitlist.forEach(w => { @@ -530,7 +531,8 @@ export class Game implements GameModel { embed.addField(waitlistHeader, reservedColumn.join("\n"), true); reservedColumn = []; } - }) + }); + embed.addField(waitlistHeader, reservedColumn.join("\n"), true); } } else { From 7a9eb449a9674d56ab08501e9533e8dc01fe8675 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 8 Sep 2020 11:27:42 -0500 Subject: [PATCH 33/53] Portugese --- .gitignore | 7 +- lang/pt-br.json | 221 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+), 6 deletions(-) create mode 100644 lang/pt-br.json diff --git a/.gitignore b/.gitignore index 5bfa484..ead5d43 100644 --- a/.gitignore +++ b/.gitignore @@ -68,13 +68,8 @@ typings/ .yarn-integrity # dotenv environment variables file -.env -.env.test - -.devenv +*.env dev - -.prodenv prod # TypeScript Compiled Files diff --git a/lang/pt-br.json b/lang/pt-br.json new file mode 100644 index 0000000..0d1fcad --- /dev/null +++ b/lang/pt-br.json @@ -0,0 +1,221 @@ +{ + "code": "pt-br", + "name": "Português-BR", + "buttons": { + "SUBMIT": "Enviar", + "SAVE": "Salvar", + "NEW_GAME": "Novo Jogo", + "EDIT_GAME": "Editar Jogo", + "DELETE_GAME": "Deletar Jogo", + "SAVE_COPY": "Salvar Cópia", + "SIGN_UP": "Inscrição", + "DROP_OUT": "Cancelar Inscrição", + "ADD_TO_GOOGLE_CALENDAR": "Adicionar ao Google Calendar" + }, + "nav": { + "GAMES": "Jogos", + "MY_GAMES": "Meus Jogos", + "ALL_GAMES": "Todos os Jogos", + "UPCOMING_GAMES": "Jogos Programados", + "MANAGE_SERVER": "Administrar Servidores", + "PAST_EVENTS": "Jogos Anteriores", + "CALENDAR": "Calendário", + "INVITE": "Convite", + "LOGIN": "Login", + "LOGOUT": "Logout", + "DONATE": "Doação", + "HELP": "Ajuda" + }, + "game": { + "SERVER": "Servidor", + "CHANNEL": "Canal", + "GAME_NAME": "Nome do Evento", + "RUN_TIME": "Tempo Estimado", + "MIN_PLAYERS": "Min Jogadores", + "MAX_PLAYERS": "Max Jogadores", + "GM_DISCORD_TAG": "Discord Tag do GM", + "GM_ERROR": "GM deve ser Discord tag válido", + "GM": "GM", + "WHERE": "Onde", + "SIGN_UP_METHOD": "Método de Inscrição", + "WHEN": "Quando", + "CUSTOM_SIGNUP_INSTRUCTIONS": "Instruções para Inscrição", + "SIGNUP_INSTRUCTIONS_DESC": "Instruções serão enviadas no privado para usuários que se inscreveram através do modo automático.", + "FREQUENCY": "Frequência", + "WEEKDAYS": "Dias da Semana", + "DATE": "Data", + "TIME": "Hora", + "TIME_ZONE": "Fuso Horário", + "TODAY": "Hoje", + "GAME_IMAGE": "Imagem do Jogo", + "GAME_IMAGE_DESC": "Como padrão, é o avatar do Discord do GM", + "REMINDER": "Lembrete", + "REMINDER_FOR": "Lembrete para", + "STARTING_IN_MINUTES": "Iniciando em :MINUTES minutos", + "STARTING_IN_HOURS": "Iniciando em :HOURS horas", + "RESERVED_SLOTS": "Slots Reservados (1 por linha)", + "RESERVED": "Reservado", + "WAITLISTED": "Lista de Espera", + "DESCRIPTION": "Descrição", + "EDIT_LINK": "Você pode editar seu `:SERVER_NAME` - `:GAME_NAME` jogo aqui:", + "MAX": "Max", + "CHARACTERS": "personagens", + "NO_PLAYERS": "Sem Jogadores", + "NEXT_DATE": "Próxima Data Agendada", + "WHEN_REPEAT": "Quando o Jogo Auto-Reagendar", + "CLEAR_RESERVED": "Limpar a lista de reserva", + "ADD_TO_CALENDAR": "Adicionar ao Calendário", + "CONVERT_TIME_ZONE": "Converter Fuso Horário", + "COUNTDOWN": "Contagem Regressiva", + "MONTHLY_TYPE": "Tipo Mensal", + "HIDE_DATE": "Ocultar Data", + "PAST_SIGNUPS": "Permitir inscrições últimos a data de inscrição / hora", + "SLOT": "Slot", + "UNSAVED": "Você tem modificações não salvas. Deseja prosseguir?", + "GAME_OPTIONS": "Opções de jogo", + "DISABLE_WAITLIST": "Desativar Lista de Espera", + "UPLOAD_GAME_IMAGE": "Imagem Carregar Jogo", + "MAX_UPLOAD_SIZE": "Tamanho máximo (5 MB)", + "options": { + "AUTOMATED_SIGNUP": "Inscrição Automática", + "CUSTOM_SIGNUP_INSTRUCTIONS": "Instruções Customizadas para Inscrição", + "DATE_TIME": "Data/Hora", + "NOW": "Agora (Hora da Postagem)", + "NO_REMINDER": "Sem Lembrete", + "MINUTES_15": "15 minutos", + "MINUTES_30": "30 minutos", + "MINUTES_60": "1 hora", + "HOURS_6": "6 horas", + "HOURS_12": "12 horas", + "HOURS_24": "24 horas", + "NO_REPEAT": "Não Se Repeta", + "DAILY": "Diariamente", + "WEEKLY": "Semanalmente", + "BIWEEKLY": "A Cada X Semanas", + "MONTHLY": "Mensalmente", + "DATE": "Data", + "WEEKDAY": "Dia da Semana" + }, + "labels": { + "HOURS": "horas", + "TBD": "TBD", + "DISCORD_TAGS": "Tags do Discord tags diferem letras maiúsculas e minúsculas", + "DISCORD_FORMATTING": "Formatação do Discord permitida", + "AUTOMATED_SIGNUP_DESC": "Usa botões de reação do Discord no anúncio do jogo para permitir que usuários se inscrevam ou cancelem a inscrição", + "REQUEST_SERVER_PASS": "Insira a senha informada pelo servidor", + "GAME_DELETED": "Este jogo foi removido", + "MONTHLY_DATE": "O Jogo ocorre na mesma data todo mês", + "MONTHLY_WEEKDAY": "O jogo ocorre :XTH :WEEKDAY cada mês" + } + }, + "dashboard": { + "EMPTY_MY_GAMES": "Você não está executando ou jogando nenhum jogo no momento", + "NO_GAMES": "Você não é um membro que o servidor permite postar jogos. Se gostaria de usar esse bot em seu próprio servidor, clique no botão Invite abaixo.", + "EMPTY_UPCOMING_GAMES": "Não há jogos programados no momento" + }, + "config": { + "COMMAND_LIST": "Lista de Comandos", + "CONFIGURATION": "Configuração", + "GENERAL_CONFIGURATION": "Geral", + "BOT_CONFIGURATION": "Bot", + "GAME_CONFIGURATION": "Jogo", + "CHANNEL_CONFIGURATION": "Canal", + "TEMPLATE_CONFIGURATION": "Modelo", + "GAME_DEFAULTS": "Padrões de Jogo", + "NEW_TEMPLATE": "Novo Modelo", + "DEFAULT": "Padrão", + "TEMPLATE_NAME": "Nome do Modelo", + "DEFAULT_SERVER": "Padrão: Mesmo que o Servidor", + "USAGE": "Uso", + "GUILD": "Servidor", + "CHANNELS": "Canais", + "CHANNEL_ADDED": "Canal adicionado! Certifique-se que o bot possui permissões no canal informado.", + "CHANNEL_REMOVED": "Canal removido!", + "PRUNING": "Limpeza Automática", + "PRUNING_ON": "Limpeza automática foi ligada!", + "PRUNING_OFF": "Limpeza automática foi desligada!", + "PRUNE": "Jogos anteriores foram removidos", + "PRUNE_INTERVAL_EVENTS": "Intervalo de Limpeza Automática de Eventos", + "PRUNE_INTERVAL_DISCORD": "Intervalo de Limpeza Automática do Discord", + "EMBEDS": "Embeds", + "EMBEDS_ON": "Embeds foram ligados!", + "EMBEDS_OFF": "Embeds foram desligados!", + "EMBED_USER_TAGS": "Embed de Tags de Usuário", + "EMBED_USER_TAGS_ON": "Embed de tags de usuário foi ligado!", + "EMBED_USER_TAGS_OFF": "Embed de tags de usuário foi desligado!", + "EMBED_USER_TAGS_ABOVE": "Embed para Mencionar Acima", + "EMBED_USER_TAGS_ABOVE_ON": "Embed para mencionar acima foi ligado!!", + "EMBED_USER_TAGS_ABOVE_OFF": "Embed para mencionar acima foi desligado!!", + "EMBED_COLOR": "Cor do Embed", + "EMBED_COLOR_SET": "Cor do embed foi configurado para", + "EMOJI_JOIN": "Emoji (Inscrição)", + "EMOJI_LEAVE": "Emoji (Cancelar Inscrição)", + "EMOJI_JOIN_SET": "Emoji para Inscrição foi atualizado!", + "EMOJI_LEAVE_SET": "Emoji para Cancelar Inscrição foi atualizado!", + "PREFIX": "Comandos do Bot", + "PREFIX_CHAR": "O comando de bot foi atualizado para :CMD", + "PRIVATE_REMINDERS": "Lembretes Privados", + "PRIVATE_REMINDERS_ON": "Lembretes privados foram ligados!", + "PRIVATE_REMINDERS_OFF": "Lembretes privados foram desligados!", + "PASSWORD": "Senha", + "PASSWORD_SET": "Senha atualizada!", + "ROLE": "Cargo", + "ROLE_SET": "Cargo configurado para `:ROLE`!", + "ROLE_CLEARED": "Cargo removido!", + "PLAYER_ROLE": "Papel do jogador", + "NO_ROLE": "Sem Cargo", + "MANAGER_ROLE": "Cargo do Administrador", + "LANGUAGE": "Idioma", + "LANG_SET": "Idioma configurado para `:LANG`!", + "NO_LANG": "Idioma não encontrado", + "DROP_OUTS": "Desistências", + "DROP_OUTS_ENABLED": "Desistências estão agora `ligadas` para novos jogos.", + "DROP_OUTS_DISABLED": "Desistências estão agora `desligadas` para novos jogos.", + "RESCHEDULE_MODE": "Modo de Reagendamento", + "RESCHEDULE_MODE_UPDATED": "Modo de reagendamento foi atualizado", + "desc": { + "SERVER_COMMAND": "Este comando somente irá funcionar em um servidor", + "HELP": "Mostrar janela de ajuda", + "LINK": "Recuperar link para postar jogos", + "CONFIGURATION": "Buscar a configuração do bot", + "CHANNEL_CONFIGURATION": "Ajustes configurados aqui irão sobrepor os ajustes do servidor no canal informado.", + "ADD_CHANNEL": "Adicionar um canal onde os jogos serão anunciados", + "REMOVE_CHANNEL": "Remover um canal onde os jogos serão anunciados", + "PRUNING": "Automaticamente remover anúncios anteriores", + "PRUNE": "Limpar automaticamente jogos anteriores", + "PRUNE_INTERVAL_EVENTS": "Eventos de jogos serão limpados automaticamente do banco de dados a cada X dias", + "PRUNE_INTERVAL_DISCORD": "Anúncios de jogos serão limpados automaticamente do Discord a cada X dias", + "EMBEDS": "Usar embeds do Discord para anúncios", + "EMBED_USER_TAGS": "Incuir tags de usuário em embeds de anúncio (Pode ocasionalmente falhar)", + "EMBED_USER_TAGS_ABOVE": "Adicionar embeds para mencionar acima", + "EMBED_COLOR": "Configurar uma cor de embed do Discord", + "EMBED_COLOR_ERROR": "Cor do embed deve ser em formato hexadecimal. Exemplo: `#2196f3` Veja https://www.color-hex.com/ para maiores informações.", + "EMOJI": "Configurar o emoji usado para a inscrição automática", + "PREFIX": "Prefixar caractere para os comandos do bot. Atual `:CHAR`", + "EMOJI_ERROR": "Emoji escolhido deve ser um caractere emoji unicode. Você inseriu `:CHAR`. Veja https://www.unicode.org/emoji/charts/full-emoji-list.html para uma lista completad de emojis. Use a versão em browser.", + "PRIVATE_REMINDERS": "Ligar para os lembretes de jogos serem enviados como mensagens privadas. Atual `:PR`", + "ROLE": "Escolha um cargo como pré-requisito para anunciar jogos", + "PLAYER_ROLE": "Atribuir um papel como um pré-requisito para se inscrever para jogos", + "MANAGER_ROLE": "Escolha um cargo para permitir a administração de todos os jogos do servidor", + "PASSWORD_SET": "Configure uma senha para agendar jogos", + "PASSWORD_CLEAR": "Remova a senha", + "TOGGLE_DROP_OUT": "Habilite/Desabilite a permissão de jogadores de cancelarem a inscrição", + "LANG": "Escolher o idioma do Bot. Disponíveis:", + "RESCHEDULE_MODE": "Opções: `reagendar` (padrão, cria um novo anúncio), `atualizar` (atualiza o anúncio original)", + "BOT_PERMISSIONS": "Mostra quais permissões o bot está faltando no canal desginated" + } + }, + "other": { + "DM_WAITLIST": "Você está atualmente no slot :NUM da lista de espera.", + "DM_INSTRUCTIONS": "Uma mensagem de :DM para :EVENT", + "MAINTENANCE": "O site estará em manutenção :TIME por aproximadamente :DURATION hora(s).", + "UNDER_MAINTENANCE": "está em manutenção", + "YOURE_IN": "Você está dentro! Foi garantida uma vaga para você no jogo **:GAME** em **:SERVER**.", + "BACK_SOON": "Retornará em breve!", + "RESCHEDULED": "O jogo foi remarcado", + "DELETED": "O jogo foi excluído", + "EDIT_PERMISSION": "Você não tem permissão para editar este jogo", + "ALREADY_STARTED": "Esse jogo já começou.", + "MAX_NO_WAITLIST": "Esse jogo está cheio e não aceitar inscrições adicionais." + } +} \ No newline at end of file From 5a0131d0df211fb505caa92301707fc8900d2a59 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 8 Sep 2020 11:31:12 -0500 Subject: [PATCH 34/53] Add portugese to list --- lang/langs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/langs.json b/lang/langs.json index f8a2d02..0efde0a 100644 --- a/lang/langs.json +++ b/lang/langs.json @@ -1,3 +1,3 @@ { - "langs": ["en","de","de-ch","fr","it","es","ru"] + "langs": ["en","de","de-ch","fr","it","es","pt-br","ru"] } \ No newline at end of file From f3fe504d7bd6136d3a6a60f15c92ce5600b7d9ff Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 8 Sep 2020 22:03:44 -0500 Subject: [PATCH 35/53] Language Update --- lang/de-ch.json | 3 ++- lang/de.json | 3 ++- lang/en.json | 3 ++- lang/es.json | 3 ++- lang/fr.json | 3 ++- lang/it.json | 3 ++- lang/pt-br.json | 3 ++- lang/ru.json | 3 ++- src/models/game.ts | 6 +++--- 9 files changed, 19 insertions(+), 11 deletions(-) diff --git a/lang/de-ch.json b/lang/de-ch.json index bb3f16a..3f3eb1c 100644 --- a/lang/de-ch.json +++ b/lang/de-ch.json @@ -216,6 +216,7 @@ "DELETED": "Das Spiel wurde gelöscht", "EDIT_PERMISSION": "Sie haben keine Berechtigung, dieses Spiel zu bearbeiten", "ALREADY_STARTED": "Das Spiel hat bereits begonnen!", - "MAX_NO_WAITLIST": "Das Spiel ist voll und keine zusätzliche Anmeldungen zu akzeptieren." + "MAX_NO_WAITLIST": "Das Spiel ist voll und keine zusätzliche Anmeldungen zu akzeptieren.", + "MISSING_PLAYER_ROLE": "Sie haben nicht die :ROLE Rolle erforderlich, dass die Veranstaltung zu verbinden." } } diff --git a/lang/de.json b/lang/de.json index 0987863..8c34793 100644 --- a/lang/de.json +++ b/lang/de.json @@ -216,6 +216,7 @@ "DELETED": "Das Spiel wurde gelöscht", "EDIT_PERMISSION": "Sie haben keine Berechtigung, dieses Spiel zu bearbeiten", "ALREADY_STARTED": "Das Spiel hat bereits begonnen!", - "MAX_NO_WAITLIST": "Das Spiel ist voll und keine zusätzliche Anmeldungen zu akzeptieren." + "MAX_NO_WAITLIST": "Das Spiel ist voll und keine zusätzliche Anmeldungen zu akzeptieren.", + "MISSING_PLAYER_ROLE": "Sie haben nicht die :ROLE Rolle erforderlich, dass die Veranstaltung zu verbinden." } } diff --git a/lang/en.json b/lang/en.json index 331f252..14a07e7 100644 --- a/lang/en.json +++ b/lang/en.json @@ -216,6 +216,7 @@ "DELETED": "The game has been deleted", "EDIT_PERMISSION": "You do not have permission to edit this game", "ALREADY_STARTED": "That game has already started.", - "MAX_NO_WAITLIST": "That game is full and not accepting additional signups." + "MAX_NO_WAITLIST": "That game is full and not accepting additional signups.", + "MISSING_PLAYER_ROLE": "You don't have the :ROLE role required to join that event." } } \ No newline at end of file diff --git a/lang/es.json b/lang/es.json index 6d357da..be53844 100644 --- a/lang/es.json +++ b/lang/es.json @@ -216,6 +216,7 @@ "DELETED": "El juego ha sido eliminado", "EDIT_PERMISSION": "No tienes permiso para editar este juego", "ALREADY_STARTED": "¡Ese juego ya ha comenzado!", - "MAX_NO_WAITLIST": "Ese juego está lleno y no aceptar suscripciones adicionales." + "MAX_NO_WAITLIST": "Ese juego está lleno y no aceptar suscripciones adicionales.", + "MISSING_PLAYER_ROLE": "Usted no tiene el papel :ROLE requerida para unirse a ese evento." } } diff --git a/lang/fr.json b/lang/fr.json index 9080b8c..b998684 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -216,6 +216,7 @@ "DELETED": "Le jeu a été supprimé", "EDIT_PERMISSION": "Vous n'êtes pas autorisé à modifier ce jeu", "ALREADY_STARTED": "Ce jeu a déjà commencé!", - "MAX_NO_WAITLIST": "Ce jeu est plein et ne pas accepter des inscriptions supplémentaires." + "MAX_NO_WAITLIST": "Ce jeu est plein et ne pas accepter des inscriptions supplémentaires.", + "MISSING_PLAYER_ROLE": "Vous n'avez pas le rôle :ROLE nécessaire pour se joindre à cet événement." } } \ No newline at end of file diff --git a/lang/it.json b/lang/it.json index c477c9d..676954f 100644 --- a/lang/it.json +++ b/lang/it.json @@ -214,6 +214,7 @@ "DELETED": "Il gioco è stato cancellato", "EDIT_PERMISSION": "Non sei autorizzato a modificare questo gioco", "ALREADY_STARTED": "Quel gioco è già iniziato!", - "MAX_NO_WAITLIST": "Questo gioco è pieno e non accettare iscrizioni supplementari." + "MAX_NO_WAITLIST": "Questo gioco è pieno e non accettare iscrizioni supplementari.", + "MISSING_PLAYER_ROLE": "Non è necessario il ruolo :ROLE necessaria a raggiungere il detto evento." } } diff --git a/lang/pt-br.json b/lang/pt-br.json index 0d1fcad..3f3fab7 100644 --- a/lang/pt-br.json +++ b/lang/pt-br.json @@ -216,6 +216,7 @@ "DELETED": "O jogo foi excluído", "EDIT_PERMISSION": "Você não tem permissão para editar este jogo", "ALREADY_STARTED": "Esse jogo já começou.", - "MAX_NO_WAITLIST": "Esse jogo está cheio e não aceitar inscrições adicionais." + "MAX_NO_WAITLIST": "Esse jogo está cheio e não aceitar inscrições adicionais.", + "MISSING_PLAYER_ROLE": "Você não tem o papel :ROLE obrigados a participar desse evento." } } \ No newline at end of file diff --git a/lang/ru.json b/lang/ru.json index 4dd79c9..b8b5d67 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -216,6 +216,7 @@ "DELETED": "Игра была удалена", "EDIT_PERMISSION": "У вас нет разрешения на редактирование этой игры", "ALREADY_STARTED": "Эта игра уже началась!", - "MAX_NO_WAITLIST": "Эта игра полна и не принимать дополнительные подписки." + "MAX_NO_WAITLIST": "Эта игра полна и не принимать дополнительные подписки.", + "MISSING_PLAYER_ROLE": "У вас нет роли :ROLE должны присоединиться к этому событию." } } diff --git a/src/models/game.ts b/src/models/game.ts index 06e503c..b32cf49 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -522,7 +522,7 @@ export class Game implements GameModel { reservedColumn = []; } }); - embed.addField(reservedHeader, reservedColumn.join("\n"), true); + if (reservedColumn.length > 0) embed.addField(reservedHeader, reservedColumn.join("\n"), true); if (waitlist.length > 0 && !game.disableWaitlist) { reservedColumn = []; waitlist.forEach(w => { @@ -532,7 +532,7 @@ export class Game implements GameModel { reservedColumn = []; } }); - embed.addField(waitlistHeader, reservedColumn.join("\n"), true); + if (reservedColumn.length > 0) embed.addField(waitlistHeader, reservedColumn.join("\n"), true); } } else { @@ -1340,7 +1340,7 @@ export class Game implements GameModel { const channelConfig = guildConfig.channel.find(c => c.channelId === this.c); const template = channelConfig ? guildConfig.gameTemplates.find(t => channelConfig.gameTemplates.find(ct => ct.toString() === t.id.toString())) : guildConfig.gameTemplates.find(t => t.isDefault); if (template && template.playerRole && !member.roles.find(r => r.name === template.playerRole)) { - if (member) member.send("You don't have the role required to join that event."); + if (member) member.send(lang.other.MISSING_PLAYER_ROLE.replace(/\:ROLE/g, `\`${template.playerRole}\``)); return false; } From f7837336c2baeeeb2baa2273a186bfd0311c2929 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 8 Sep 2020 22:04:00 -0500 Subject: [PATCH 36/53] Bug Fixes --- src/models/guild-config.ts | 5 +++-- src/routes/api.ts | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/models/guild-config.ts b/src/models/guild-config.ts index 7c1c791..4e0fb84 100644 --- a/src/models/guild-config.ts +++ b/src/models/guild-config.ts @@ -164,6 +164,7 @@ export class GuildConfig implements GuildConfigDataModel { isDefault: true, embedColor: aux.colorFixer(this.embedColor), role: this.role, + playerRole: null, gameDefaults: { minPlayers: 1, maxPlayers: 7, @@ -183,8 +184,8 @@ export class GuildConfig implements GuildConfigDataModel { updates.gameTemplates = updates.gameTemplates.map((gt) => { if (!gt.id) gt = { id: new ObjectId().toHexString(), ...gt }; gt.embedColor = aux.colorFixer(gt.embedColor); - gt.role = gt.role.trim(); - gt.playerRole = gt.playerRole.trim(); + gt.role = gt.role ? gt.role.trim() : null; + gt.playerRole = gt.playerRole ? gt.playerRole.trim() : null; return gt; }); updates.channel = updates.channel.map((channel) => { diff --git a/src/routes/api.ts b/src/routes/api.ts index 5c8171a..9c7d1d9 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1101,9 +1101,7 @@ export default (options: APIRouteOptions) => { } } await guildConfig.save(); - io().emit("site", { action: "guild-config", config: guildConfig.data }); - res.json({ status: "success", token: token, From dd8fdc28d1131f74e0ba0d00c1b547141fae5b6f Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Fri, 11 Sep 2020 07:44:57 -0500 Subject: [PATCH 37/53] Websocket Rooms --- src/models/game.ts | 23 ++++++++++++++++++----- src/processes/shard-manager.ts | 10 +++++++--- src/processes/socket.ts | 8 ++++++-- src/routes/api.ts | 6 +++++- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index b32cf49..7d93628 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -596,13 +596,17 @@ export class Game implements GameModel { if (message) this.dmNextWaitlist(prev.reserved, game.reserved); const updatedGame = aux.objectChanges(prev, game); - if (this.client) + if (this.client) { this.client.shard.send({ type: "socket", name: "game", + room: game.s, data: { action: "updated", gameId: game._id, game: updatedGame, guildId: game.s }, }); - else io().emit("game", { action: "updated", gameId: game._id, game: updatedGame, guildId: game.s }); + } + else { + io().to(game.s).emit("game", { action: "updated", gameId: game._id, game: updatedGame, guildId: game.s }); + } } catch (err) { aux.log("UpdateGameError:", err); if (updated) updated.modifiedCount = 0; @@ -698,9 +702,12 @@ export class Game implements GameModel { this.client.shard.send({ type: "socket", name: "game", + room: game.s, data: { action: "new", gameId: inserted.insertedId.toString(), guildId: game.s, authorId: game.author.id }, }); - else io().emit("game", { action: "new", gameId: inserted.insertedId.toString(), guildId: game.s, authorId: game.author.id }); + else { + io().to(game.s).emit("game", { action: "new", gameId: inserted.insertedId.toString(), guildId: game.s, authorId: game.author.id }); + } const saved: GameSaveData = { _id: inserted.insertedId.toString(), @@ -1022,9 +1029,12 @@ export class Game implements GameModel { this.client.shard.send({ type: "socket", name: "game", + room: game.s, data: { action: "rescheduled", gameId: this._id, newGameId: newGame._id }, }); - else io().emit("game", { action: "rescheduled", gameId: this._id, newGameId: newGame._id }); + else { + io().to(game.s).emit("game", { action: "rescheduled", gameId: this._id, newGameId: newGame._id }); + } const del = await this.delete(); if (del.modifiedCount == 0) { @@ -1165,9 +1175,12 @@ export class Game implements GameModel { this.client.shard.send({ type: "socket", name: "game", + room: game.s, data: { action: "deleted", gameId: game._id, guildId: game.s }, }); - else io().emit("game", { action: "deleted", gameId: game._id, guildId: game.s }); + else { + io().to(game.s).emit("game", { action: "deleted", gameId: game._id, guildId: game.s }); + } } return result; } diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index 1f64b60..bc8ba67 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -2,8 +2,6 @@ import { ShardingManager, Role, MessageEmbed, Message, Client, TextChannel, Perm import { Express } from "express"; import { io } from "./socket"; import aux from "../appaux"; -import { RSVP, Game } from "../models/game"; -import { find } from "lodash"; type DiscordProcessesOptions = { app: Express; @@ -31,7 +29,13 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { shard.on("message", (message) => { if (typeof message === "object") { if (message.type === "socket") { - io().emit(message.name, message.data); + if (message.room) io().to(message.room).emit(message.name, message.data); + else if (message.rooms && Array.isArray(message.rooms)) { + message.rooms.forEach(room => { + io().to(room).emit(message.name, message.data); + }); + } + else io().emit(message.name, message.data); } if (message.type === "shard") { if (message.name === "guilds") { diff --git a/src/processes/socket.ts b/src/processes/socket.ts index 63fd6fb..d02a754 100644 --- a/src/processes/socket.ts +++ b/src/processes/socket.ts @@ -7,7 +7,11 @@ export function socket(httpServer: http.Server) { _io = SocketIO.listen(httpServer); _io.on("connection", (socket) => { - // console.log("Client connected!"); + // console.log("Client connected!", socket.handshake.query); + if (socket.handshake.query && socket.handshake.query.rooms) { + socket.join(socket.handshake.query.rooms.split(',')); + socket.emit("connected", `Connected to rooms: ${socket.handshake.query.rooms.split(',').join(', ')}`); + } }); return _io; @@ -16,4 +20,4 @@ export function socket(httpServer: http.Server) { export function io() { if (!_io) throw new Error("Socket.io not initialized!"); return _io; -} +} \ No newline at end of file diff --git a/src/routes/api.ts b/src/routes/api.ts index 9c7d1d9..6ba18ac 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -478,7 +478,7 @@ export default (options: APIRouteOptions) => { if (game) { server = game.s; } else { - throw new Error("Game not found"); + throw new Error("Game not found (1)"); } } @@ -503,6 +503,10 @@ export default (options: APIRouteOptions) => { const guildRoles = guild.roles; const guildMembers = guild.members; + // if (!guildMembers.find(gm => gm.user.id === result.account.user.id)) { + // throw new Error("Game not found (2)"); + // } + const guildConfig = await GuildConfig.fetch(guild.id); let gcChannels: ChannelConfig[] = guildConfig.channels; From 437a480ef867f1b3621519c785aa8137f9073043 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Fri, 11 Sep 2020 18:28:13 -0500 Subject: [PATCH 38/53] Player Role Bug Fix --- src/models/game.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index 7d93628..fb7cd7e 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -1350,8 +1350,7 @@ export class Game implements GameModel { return false; } - const channelConfig = guildConfig.channel.find(c => c.channelId === this.c); - const template = channelConfig ? guildConfig.gameTemplates.find(t => channelConfig.gameTemplates.find(ct => ct.toString() === t.id.toString())) : guildConfig.gameTemplates.find(t => t.isDefault); + const template = guildConfig.gameTemplates.find(t => t.id.toString() === this.template) || guildConfig.gameTemplates.find(t => t.isDefault); if (template && template.playerRole && !member.roles.find(r => r.name === template.playerRole)) { if (member) member.send(lang.other.MISSING_PLAYER_ROLE.replace(/\:ROLE/g, `\`${template.playerRole}\``)); return false; From 4a396dc064161efbc8c521f19f5a1f981a5ba037 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Fri, 11 Sep 2020 18:28:27 -0500 Subject: [PATCH 39/53] Web Socket Improvements and Additions --- src/models/game.ts | 16 ++++----- src/processes/shard-manager.ts | 28 ++++++++++++--- src/processes/socket.ts | 1 - src/routes/api.ts | 64 ++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 13 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index fb7cd7e..996a9ef 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -600,12 +600,12 @@ export class Game implements GameModel { this.client.shard.send({ type: "socket", name: "game", - room: game.s, + room: `g-${game.s}`, data: { action: "updated", gameId: game._id, game: updatedGame, guildId: game.s }, }); } else { - io().to(game.s).emit("game", { action: "updated", gameId: game._id, game: updatedGame, guildId: game.s }); + io().to(`g-${game.s}`).emit("game", { action: "updated", gameId: game._id, game: updatedGame, guildId: game.s }); } } catch (err) { aux.log("UpdateGameError:", err); @@ -702,11 +702,11 @@ export class Game implements GameModel { this.client.shard.send({ type: "socket", name: "game", - room: game.s, + room: `g-${game.s}`, data: { action: "new", gameId: inserted.insertedId.toString(), guildId: game.s, authorId: game.author.id }, }); else { - io().to(game.s).emit("game", { action: "new", gameId: inserted.insertedId.toString(), guildId: game.s, authorId: game.author.id }); + io().to(`g-${game.s}`).emit("game", { action: "new", gameId: inserted.insertedId.toString(), guildId: game.s, authorId: game.author.id }); } const saved: GameSaveData = { @@ -1029,11 +1029,11 @@ export class Game implements GameModel { this.client.shard.send({ type: "socket", name: "game", - room: game.s, + room: `g-${game.s}`, data: { action: "rescheduled", gameId: this._id, newGameId: newGame._id }, }); else { - io().to(game.s).emit("game", { action: "rescheduled", gameId: this._id, newGameId: newGame._id }); + io().to(`g-${game.s}`).emit("game", { action: "rescheduled", gameId: this._id, newGameId: newGame._id }); } const del = await this.delete(); @@ -1175,11 +1175,11 @@ export class Game implements GameModel { this.client.shard.send({ type: "socket", name: "game", - room: game.s, + room: `g-${game.s}`, data: { action: "deleted", gameId: game._id, guildId: game.s }, }); else { - io().to(game.s).emit("game", { action: "deleted", gameId: game._id, guildId: game.s }); + io().to(`g-${game.s}`).emit("game", { action: "deleted", gameId: game._id, guildId: game.s }); } } return result; diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index bc8ba67..d13e8c1 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -26,7 +26,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { manager.on("shardCreate", (shard) => { aux.log(`Shard ${shard.id} launched`); - shard.on("message", (message) => { + shard.on("message", async (message) => { if (typeof message === "object") { if (message.type === "socket") { if (message.room) io().to(message.room).emit(message.name, message.data); @@ -49,6 +49,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { guildData.push(guild); } }); + if (guilds.length === 1) io().to(`g-${guilds[0].id}`).emit('refresh-guilds', guilds[0].id); } else if (message.name === "channelCreate") { guildData = guildData.map((g) => { if (g.id === message.data.guild) { @@ -59,6 +60,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { } return g; }); + io().to(`g-${message.data.guild}`).emit('refresh-guilds', message.data.guild); } else if (message.name === "channelUpdate") { guildData = guildData.map((g) => { if (g.id === message.data.guild) { @@ -71,14 +73,18 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { } return g; }); + io().to(`g-${message.data.guild}`).emit('refresh-guilds', message.data.guild); } else if (message.name === "channelDelete") { + let guildId; guildData = guildData.map((g) => { const index = g.channels.findIndex((c) => c.id === message.data); if (index >= 0) { g.channels.splice(index, 1); + guildId = g.id; } return g; }); + if (guildId) io().to(`g-${message.data.guild}`).emit('refresh-guilds', message.data.guild); } else if (message.name === "roleCreate") { guildData = guildData.map((g) => { if (g.id !== message.data.guild) return g; @@ -88,6 +94,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { }); return g; }); + io().to(`g-${message.data.guild}`).emit('refresh-guilds', message.data.guild); } else if (message.name === "roleUpdate") { guildData = guildData.map((g) => { if (g.id !== message.data.guild) return g; @@ -100,12 +107,18 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { }); return g; }); + io().to(`g-${message.data.guild}`).emit('refresh-guilds', message.data.guild); } else if (message.name === "roleDelete") { + let guildId; guildData = guildData.map((g) => { const index = g.roles.findIndex((c) => c.id === message.data); - if (index >= 0) g.roles.splice(index, 1); + if (index >= 0) { + g.roles.splice(index, 1); + guildId = g.id; + } return g; }); + if (guildId) io().to(`g-${message.data.guild}`).emit('refresh-guilds', message.data.guild); } else if (message.name === "guildMemberAdd") { guildData = guildData.map((g) => { if (g.id !== message.data.guildID) return g; @@ -114,6 +127,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { g.memberRoles.push(message.roles); return g; }); + io().to(`u-${message.user.id}`).emit('refresh-guilds', 'all'); } else if (message.name === "guildMemberUpdate") { guildData = guildData.map((g) => { if (g.id !== message.data.guildID) return g; @@ -126,6 +140,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { }); return g; }); + io().to(`u-${message.data.userID}`).emit('refresh-guilds', message.data.guildID); } else if (message.name === "guildMemberRemove") { guildData = guildData.map((g) => { if (g.id !== message.data.guildID) return g; @@ -136,6 +151,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { g.users.splice(index, 1); return g; }); + io().to(`u-${message.data.userID}`).emit('delete-guild', message.data.guildID); } else if (message.name === "userUpdate") { guildData = guildData.map((g) => { const index = g.users.findIndex((c) => c.id === message.data.id); @@ -146,25 +162,29 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { }); return g; }); + io().to(`u-${message.data.id}`).emit('update-user', message.data); } else if (message.name === "guildUpdate") { guildData = guildData.map((g) => { if (g.id !== message.data.id) return g; g.name = message.data.name; + g.icon = message.data.icon; return g; }); + io().to(`g-${message.data.id}`).emit('update-guild', { id: message.data.id, name: message.data.name, icon: message.data.iconURL }); } else if (message.name === "guildDelete") { const index = guildData.findIndex((g) => g.id === message.data); if (index >= 0) { guildData.splice(index, 1); } + io().to(`g-${message.data}`).emit('delete-guild', message.data); } } if (message.type === "refresh") { if (message.guildId) { - refreshGuild(message.guildId); + await refreshGuild(message.guildId); } else { - manager.broadcastEval(` + await manager.broadcastEval(` const guilds = this.guilds.cache.array(); console.log("Force refreshing data for ", guilds.length, "guilds"); this.shard.send({ diff --git a/src/processes/socket.ts b/src/processes/socket.ts index d02a754..c49d680 100644 --- a/src/processes/socket.ts +++ b/src/processes/socket.ts @@ -7,7 +7,6 @@ export function socket(httpServer: http.Server) { _io = SocketIO.listen(httpServer); _io.on("connection", (socket) => { - // console.log("Client connected!", socket.handshake.query); if (socket.handshake.query && socket.handshake.query.rooms) { socket.join(socket.handshake.query.rooms.split(',')); socket.emit("connected", `Connected to rooms: ${socket.handshake.query.rooms.split(',').join(', ')}`); diff --git a/src/routes/api.ts b/src/routes/api.ts index 6ba18ac..c4f696f 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -393,6 +393,70 @@ export default (options: APIRouteOptions) => { } }); + router.get("/auth-api/guild", async (req, res, next) => { + try { + fetchAccount(req.session.api.access, { + client: client, + guilds: true, + games: !!req.query.games, + page: req.query.page, + ip: req.app.locals.ip, + search: req.query.search, + }) + .then(async (result: any) => { + const guilds = result.account.guilds.filter(g => g.id == req.query.guildId).map((guild) => { + guild.roles = guild.roles.map((role) => { + delete role.hoist; + delete role.createdTimestamp; + delete role.deleted; + delete role.mentionable; + delete role.permissions; + delete role.rawPosition; + return role; + }); + guild.channelCategories = guild.channelCategories.map((channel) => { + // delete channel.members; + delete channel.messages; + return channel; + }); + guild.announcementChannels = guild.announcementChannels.map((channel) => { + // delete channel.members; + delete channel.messages; + return channel; + }); + guild.channels = guild.channels.map((channel) => { + // delete channel.members; + delete channel.messages; + return channel; + }); + return guild; + }); + res.json({ + status: "success", + token: req.session.api.access.access_token, + guild: guilds[0], + version: apiVersion, + }); + }) + .catch((err) => { + res.json({ + status: "error", + token: req.session.api.access.access_token, + message: `GuildsAPI: FetchAccountError: ${err}`, + reauthenticate: (typeof (err.message || err) === "string" ? err.message || err : "").indexOf("OAuth:") >= 0, + code: 12, + }); + }); + } catch (err) { + res.json({ + status: "error", + token: req.session.api.access.access_token, + message: `GuildsAPI: ${err}`, + code: 13, + }); + } + }); + router.post("/auth-api/guild-config", async (req, res, next) => { try { fetchAccount(req.session.api.access, { From 73ef34b9eefa6854b957945f9236360b4605d8c3 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Mon, 21 Sep 2020 18:54:45 -0500 Subject: [PATCH 40/53] Development --- lang/de-ch.json | 5 +- lang/de.json | 5 +- lang/en.json | 5 +- lang/es.json | 5 +- lang/fr.json | 7 +- lang/it.json | 7 +- lang/pt-br.json | 5 +- lang/ru.json | 5 +- src/models/game.ts | 65 ++++++++------ src/processes/discord.ts | 156 +++++++++++++++++++++++++++++---- src/processes/shard-manager.ts | 62 ++++++++++++- src/routes/api.ts | 23 ++--- 12 files changed, 286 insertions(+), 64 deletions(-) diff --git a/lang/de-ch.json b/lang/de-ch.json index 3f3eb1c..03e36e6 100644 --- a/lang/de-ch.json +++ b/lang/de-ch.json @@ -159,7 +159,7 @@ "PRIVATE_REMINDERS_OFF": "Private Erinnerungen wurden deaktiviert!", "PASSWORD": "Passwort", "PASSWORD_SET": "Passwort aktualisiert!", - "ROLE": "Rolle", + "ROLE": "SL Rolle", "ROLE_SET": "Rolle wurde auf `:ROLE` gesetzt!", "ROLE_CLEARED": "Rolle gelöscht!", "PLAYER_ROLE": "Spieler Rolle", @@ -173,10 +173,13 @@ "DROP_OUTS_DISABLED": "Abmeldungen sind nun `deaktiviert` für neue Spiele.", "RESCHEDULE_MODE": "Terminverschiebungsmodus", "RESCHEDULE_MODE_UPDATED": "Terminverschiebungsmodus wurde aktualisiert", + "EVENTS_TITLE": "Die nächsten Spiele :SERVER innerhalb der nächsten :TIMEFRAME", + "EVENTS_NONE": "Keine Spiele innerhalb der vorgegebenen Zeit.", "desc": { "SERVER_COMMAND": "Dieser Befehl wird nur auf dem Server funktionieren.", "HELP": "Zeige dieses Hilfefenster an.", "LINK": "Erhalte den Link für das veröffentliche Spiel.", + "EVENTS": "Holen Sie sich eine private Nachricht mit einer Liste der kommenden Veranstaltungen. Für die `timeframe`, können Sie `# hours` angeben, `# days` oder `# weeks`.", "CONFIGURATION": "Zeige die Bot Konfiguration.", "CHANNEL_CONFIGURATION": "Die hier konfigurierten Einstellungen überschreiben die entsprechenden Servereinstellungen für den angegebenen Kanal.", "ADD_CHANNEL": "Füge einen Kanal hinzu, in welchem Spiele veröffentlicht werden können.", diff --git a/lang/de.json b/lang/de.json index 8c34793..0ded925 100644 --- a/lang/de.json +++ b/lang/de.json @@ -159,7 +159,7 @@ "PRIVATE_REMINDERS_OFF": "Private Erinnerungen wurden deaktiviert!", "PASSWORD": "Passwort", "PASSWORD_SET": "Passwort aktualisiert!", - "ROLE": "Rolle", + "ROLE": "SL Rolle", "ROLE_SET": "Rolle wurde auf `:ROLE` gesetzt!", "PLAYER_ROLE": "Spieler Rolle", "NO_ROLE": "Keine Rolle", @@ -173,10 +173,13 @@ "DROP_OUTS_DISABLED": "Austragungen sind nun `deaktiviert` für neue Spiele.", "RESCHEDULE_MODE": "Terminverschiebungsmodus", "RESCHEDULE_MODE_UPDATED": "Terminverschiebungsmodus wurde aktualisiert", + "EVENTS_TITLE": "Die nächsten Spiele :SERVER innerhalb der nächsten :TIMEFRAME", + "EVENTS_NONE": "Keine Spiele innerhalb der vorgegebenen Zeit.", "desc": { "SERVER_COMMAND": "Dieser Befehl wird nur auf dem Server funktionieren.", "HELP": "Zeige dieses Hilfefenster an.", "LINK": "Erhalte den Link für das veröffentliche Spiel.", + "EVENTS": "Holen Sie sich eine private Nachricht mit einer Liste der kommenden Veranstaltungen. Für die `timeframe`, können Sie `# hours` angeben, `# days` oder `# weeks`.", "CONFIGURATION": "Zeige die Bot Konfiguration.", "CHANNEL_CONFIGURATION": "Die hier konfigurierten Einstellungen überschreiben die entsprechenden Servereinstellungen für den angegebenen Kanal.", "ADD_CHANNEL": "Füge einen Kanal hinzu, in welchem Spiele veröffentlicht werden können.", diff --git a/lang/en.json b/lang/en.json index 14a07e7..98d47a7 100644 --- a/lang/en.json +++ b/lang/en.json @@ -159,7 +159,7 @@ "PRIVATE_REMINDERS_OFF": "Private reminders were turned off!", "PASSWORD": "Password", "PASSWORD_SET": "Password updated!", - "ROLE": "Role", + "ROLE": "GM Role", "ROLE_SET": "Role set to `:ROLE`!", "ROLE_CLEARED": "Role cleared!", "PLAYER_ROLE": "Player Role", @@ -173,10 +173,13 @@ "DROP_OUTS_DISABLED": "Drop outs are now `disabled` for new games.", "RESCHEDULE_MODE": "Reschedule Mode", "RESCHEDULE_MODE_UPDATED": "Reschedule mode has been updated", + "EVENTS_TITLE": "Upcoming games on :SERVER within the next :TIMEFRAME", + "EVENTS_NONE": "No games within the specified time frame.", "desc": { "SERVER_COMMAND": "This command will only work in a server", "HELP": "Display this help window", "LINK": "Retrieve link for posting games", + "EVENTS": "Get a private message with a list of upcoming events. For the `timeframe`, you can specify `# hours`, `# days`, or `# weeks`.", "CONFIGURATION": "Get the bot configuration", "CHANNEL_CONFIGURATION": "Settings configured here will override the corresponding server settings for the given channel.", "ADD_CHANNEL": "Add a channel where games are posted", diff --git a/lang/es.json b/lang/es.json index be53844..285fb92 100644 --- a/lang/es.json +++ b/lang/es.json @@ -159,7 +159,7 @@ "PRIVATE_REMINDERS_OFF": "¡Recordatorios privados se han desactivado!", "PASSWORD": "Contraseña", "PASSWORD_SET": "¡Contraseña Actualizada!", - "ROLE": "Rol", + "ROLE": "Rol GM", "ROLE_SET": "¡Rol puesto a `:ROLE`!", "ROLE_CLEARED": "Rol limpio!", "PLAYER_ROLE": "Rol jugador", @@ -173,10 +173,13 @@ "DROP_OUTS_DISABLED": "Abandonos estan ahora `disabled` para los juegos.", "RESCHEDULE_MODE": "Modo de cambiar fecha en el calendario", "RESCHEDULE_MODE_UPDATED": "Modo de cambiar fecha en el clanedario se ha actualizado", + "EVENTS_TITLE": "Próximos juegos en :SERVER dentro de la siguiente :TIMEFRAME", + "EVENTS_NONE": "No hay juegos dentro del marco de tiempo especificado.", "desc": { "SERVER_COMMAND": "Este comando solo funcionara en el server", "HELP": "Abrira una ventana con ayuda", "LINK": "Enlace para publicar los juegos", + "EVENTS": "Recibe un mensaje privado con una lista de los próximos eventos. Para el `timeframe`, puede especificar `# hours`, `# days`, o `# weeks`.", "CONFIGURATION": "Obtener las configuraciones del bot", "CHANNEL_CONFIGURATION": "La configuración configurada aquí anulará la configuración del servidor correspondiente para el canal dado.", "ADD_CHANNEL": "Añadir un canal donde se postean los juegos", diff --git a/lang/fr.json b/lang/fr.json index b998684..466c75a 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -159,10 +159,10 @@ "PRIVATE_REMINDERS_OFF": "Les rappels privés ont été désactivés!", "PASSWORD": "Mot de passe", "PASSWORD_SET": "Mot de passe mis à jour!", - "ROLE": "Rôle", + "ROLE": "MJ Rôle", "ROLE_SET": "Rôle attribué à `:ROLE`!", "ROLE_CLEARED": "Rôle effacé!", - "PLAYER_ROLE": "joueur Rôle", + "PLAYER_ROLE": "Joueur Rôle", "NO_ROLE": "Aucun rôle", "MANAGER_ROLE": "Rôle de Directeur", "LANGUAGE": "La langue", @@ -173,10 +173,13 @@ "DROP_OUTS_DISABLED": "Les désinscriptions sont maintenant `interdites` pour les nouvelles parties.", "RESCHEDULE_MODE": "Mode de reprogrammation", "RESCHEDULE_MODE_UPDATED": "Le mode reprogrammation a été mis à jour", + "EVENTS_TITLE": "Jeux à venir sur :SERVER dans le prochain :TIMEFRAME", + "EVENTS_NONE": "Pas de jeux dans le laps de temps spécifié.", "desc": { "SERVER_COMMAND": "Cette commande ne fonctionnera que sur un serveur", "HELP": "Afficher cette fenêtre d'aide", "LINK": "Récupérer le lien pour poster des parties", + "EVENTS": "Obtenez un message privé avec une liste des événements à venir. Pour la `timeframe`, vous pouvez spécifier `# hours`, `# days`, ou `# weeks`.", "CONFIGURATION": "Obtenir la configuration du bot", "CHANNEL_CONFIGURATION": "Les paramètres configurés ici remplaceront les paramètres de serveur correspondants pour le canal donné.", "ADD_CHANNEL": "Ajouter un channel où les parties seront publiées", diff --git a/lang/it.json b/lang/it.json index 676954f..fb2db32 100644 --- a/lang/it.json +++ b/lang/it.json @@ -157,10 +157,10 @@ "PRIVATE_REMINDERS_OFF": "I promemoria privati sono stati disattivati!", "PASSWORD": "Password", "PASSWORD_SET": "Password aggioranta!", - "ROLE": "Ruolo", + "ROLE": "Ruolo GM", "ROLE_SET": "Ruolo impostato su `:ROLE`!", "ROLE_CLEARED": "Ruolo liberato!", - "PLAYER_ROLE": "Ruolo Player", + "PLAYER_ROLE": "Ruolo Giocatore", "NO_ROLE": "No Ruolo", "MANAGER_ROLE": "Gestisci ruoli", "LANGUAGE": "Linguaggio", @@ -171,10 +171,13 @@ "DROP_OUTS_DISABLED": "Gli abbandoni sono ora`disabilitati` per i nuovi giochi.", "RESCHEDULE_MODE": "Modalità di ripianificazione", "RESCHEDULE_MODE_UPDATED": "La modalità di riprogrammazione è stata aggiornata", + "EVENTS_TITLE": "Giochi Prossimi su :SERVER entro il prossimo :TIMEFRAME", + "EVENTS_NONE": "Non ci sono giochi all'interno della cornice di tempo specificato.", "desc": { "SERVER_COMMAND": "Questo comando funzionerà solo in un server", "HELP": "Mostra questa finestra di aiuto", "LINK": "Ottieni link per postare giochi", + "EVENTS": "Ottenere un messaggio privato con un elenco dei prossimi eventi. Per la `timeframe`, è possibile specificare `# hours`, `# days`, o `# weeks`.", "CONFIGURATION": "Ottieni la configurazione del bot", "CHANNEL_CONFIGURATION": "Le impostazioni configurate qui sostituiranno le impostazioni del server corrispondenti per il canale specificato.", "ADD_CHANNEL": "Aggiungi un canale dove sono postati i giochi", diff --git a/lang/pt-br.json b/lang/pt-br.json index 3f3fab7..2a9d9e6 100644 --- a/lang/pt-br.json +++ b/lang/pt-br.json @@ -88,7 +88,7 @@ "HOURS_6": "6 horas", "HOURS_12": "12 horas", "HOURS_24": "24 horas", - "NO_REPEAT": "Não Se Repeta", + "NO_REPEAT": "Não Se Repete", "DAILY": "Diariamente", "WEEKLY": "Semanalmente", "BIWEEKLY": "A Cada X Semanas", @@ -173,10 +173,13 @@ "DROP_OUTS_DISABLED": "Desistências estão agora `desligadas` para novos jogos.", "RESCHEDULE_MODE": "Modo de Reagendamento", "RESCHEDULE_MODE_UPDATED": "Modo de reagendamento foi atualizado", + "EVENTS_TITLE": "Próximos jogos sobre :SERVER no próximo :TIMEFRAME", + "EVENTS_NONE": "Nenhum jogo dentro do prazo especificado.", "desc": { "SERVER_COMMAND": "Este comando somente irá funcionar em um servidor", "HELP": "Mostrar janela de ajuda", "LINK": "Recuperar link para postar jogos", + "EVENTS": "Obter uma mensagem privada com uma lista de próximos eventos. Para o `timeframe`, você pode especificar `# hours`, `# days`, ou `# weeks`.", "CONFIGURATION": "Buscar a configuração do bot", "CHANNEL_CONFIGURATION": "Ajustes configurados aqui irão sobrepor os ajustes do servidor no canal informado.", "ADD_CHANNEL": "Adicionar um canal onde os jogos serão anunciados", diff --git a/lang/ru.json b/lang/ru.json index b8b5d67..2ce7d91 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -159,7 +159,7 @@ "PRIVATE_REMINDERS_OFF": "Частные напоминания выключены!", "PASSWORD": "Пароль", "PASSWORD_SET": "Пароль обновлен!", - "ROLE": "Роль", + "ROLE": "Роль GM", "ROLE_SET": "Роль установлена на :ROLE!", "ROLE_CLEARED": "Роль очищена!", "PLAYER_ROLE": "Роль игрока", @@ -173,10 +173,13 @@ "DROP_OUTS_DISABLED": "Возможность покинуть игру для новых игр отключена.", "RESCHEDULE_MODE": "Режим обновления объявления", "RESCHEDULE_MODE_UPDATED": "Режим обновления объявлений обновлён", + "EVENTS_TITLE": "Предстоящие игры на :SERVER в следующем :TIMEFRAME", + "EVENTS_NONE": "Нет игр в течение указанного периода времени.", "desc": { "SERVER_COMMAND": "Эта комнада работает только на сервере", "HELP": "Отобразить это окно помощи", "LINK": "Запросить ссылку для публикации игр", + "EVENTS": "Получить личное сообщение со списком предстоящих событий. Для `timeframe`, вы можете указать `# hours`, `# days`, или `# weeks`.", "CONFIGURATION": "Запросить конфигурацию бота", "CHANNEL_CONFIGURATION": "Настроенные здесь настройки переопределяют соответствующие настройки сервера для данного канала.", "ADD_CHANNEL": "Указать канал для публикации объявлений", diff --git a/src/models/game.ts b/src/models/game.ts index 996a9ef..ca467f7 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -1,5 +1,5 @@ import mongodb, { ObjectID } from "mongodb"; -import discord, { Message, MessageEmbed, User, Client } from "discord.js"; +import discord, { Message, MessageEmbed, User, Client, GuildMember } from "discord.js"; import moment from "moment-timezone"; import "moment-recur-ts"; @@ -95,6 +95,7 @@ export interface GameModel { reminderMessageId: string; pm: string; gameImage: string; + thumbnail: string; frequency: Frequency; weekdays: boolean[]; xWeeks: number; @@ -117,6 +118,7 @@ interface GameSaveData { interface GameSaveOptions { force?: boolean; + user?: any; } export enum GameReminder { @@ -163,6 +165,7 @@ export class Game implements GameModel { reminderMessageId: string; pm: string; gameImage: string; + thumbnail: string; frequency: Frequency; weekdays: boolean[] = [false, false, false, false, false, false, false]; xWeeks: number = 2; @@ -279,6 +282,7 @@ export class Game implements GameModel { reminderMessageId: this.reminderMessageId, pm: this.pm, gameImage: this.gameImage, + thumbnail: this.thumbnail, frequency: this.frequency, weekdays: this.weekdays, xWeeks: this.xWeeks, @@ -339,6 +343,8 @@ export class Game implements GameModel { moment.locale(lang.code); + if (options.user && !game.dm.id && game.dm.tag === options.user.tag) game.dm.id = options.user.id; + const authorParts = game.author.tag.replace("@", "").split("#"); const dmParts = game.dm.tag.replace("@", "").split("#"); let dm = dmParts[0]; @@ -546,7 +552,8 @@ export class Game implements GameModel { true ); if (game.method === GameMethod.AUTOMATED) embed.setFooter(automatedInstructions); - if (game && game.gameImage && game.gameImage.trim().length > 0 && urlRegex.test(game.gameImage.trim())) embed.setImage(game.gameImage.trim().substr(0, 2048)); + if (game.thumbnail && game.thumbnail.trim().length > 0 && urlRegex.test(game.thumbnail.trim())) embed.setThumbnail(game.thumbnail.trim().substr(0, 2048)); + if (game.gameImage && game.gameImage.trim().length > 0 && urlRegex.test(game.gameImage.trim())) embed.setImage(game.gameImage.trim().substr(0, 2048)); if (!this.hideDate) embed.setTimestamp(gameDate); } @@ -595,17 +602,21 @@ export class Game implements GameModel { if (message) this.dmNextWaitlist(prev.reserved, game.reserved); - const updatedGame = aux.objectChanges(prev, game); - if (this.client) { - this.client.shard.send({ - type: "socket", - name: "game", - room: `g-${game.s}`, - data: { action: "updated", gameId: game._id, game: updatedGame, guildId: game.s }, - }); - } - else { - io().to(`g-${game.s}`).emit("game", { action: "updated", gameId: game._id, game: updatedGame, guildId: game.s }); + const updatedGame: any = aux.objectChanges(prev, game); + delete updatedGame.sequence; + delete updatedGame.updatedTimestamp; + if (Object.keys(updatedGame).length > 0) { + if (this.client) { + this.client.shard.send({ + type: "socket", + name: "game", + room: `g-${game.s}`, + data: { action: "updated", gameId: game._id, game: updatedGame, guildId: game.s }, + }); + } + else { + io().to(`g-${game.s}`).emit("game", { action: "updated", gameId: game._id, game: updatedGame, guildId: game.s }); + } } } catch (err) { aux.log("UpdateGameError:", err); @@ -1282,10 +1293,13 @@ export class Game implements GameModel { for (let i = 0; i < this.reserved.length; i++) { try { const res = cloneDeep(this.reserved[i]); - const member = guildMembers.find((m) => (this.reserved[i] && m.user.id === this.reserved[i].id) || m.user.tag === this.reserved[i].tag.trim()); + const member = guildMembers.find((m) => this.reserved[i] && m.user && (m.user.id === this.reserved[i].id || m.user.tag === this.reserved[i].tag.trim())); const countMatches = this.reserved.filter((rr, ri) => ri <= i && ((rr.id ? rr.id === res.id : false) || (rr.tag === res.tag && !/#\d{4}/i.test(res.tag)))); const rsvpMatches = rsvps.filter((r) => r._id === res._id || (r.id && r.id === res.id) || r.tag === res.tag); // console.log(res.tag, countMatches.length, rsvpMatches.length); + + if (!member) continue; + let rsvp = rsvps.find((r) => r._id === res._id || (r.id && r.id === res.id) || r.tag === res.tag); if (!rsvp) rsvp = await GameRSVP.fetchRSVP(this._id, res.id || res.tag); if (!rsvp || (!/#\d{4}/i.test(res.tag) && countMatches.length > rsvpMatches.length)) { @@ -1332,8 +1346,8 @@ export class Game implements GameModel { } } - async signUp(user: User | ShardUser, t?: number) { - if (!this.discordGuild) return false; + async signUp(user: User | ShardUser, t?: number): Promise<{ result: boolean, message?: string }> { + if (!this.discordGuild) return { result: false, message: "Server not found!" }; const guildConfig = await GuildConfig.fetch(this.s); const member = this.discordGuild.members.find((m) => m.user.tag === user.tag.trim() || m.user.id === user.id); @@ -1342,18 +1356,18 @@ export class Game implements GameModel { const hourDiff = (new Date().getTime() - this.timestamp) / 1000 / 3600; if (hourDiff >= 0 && !this.pastSignups && !this.hideDate) { if (member) member.send(lang.other.ALREADY_STARTED); - return false; + return { result: false, message: lang.other.ALREADY_STARTED }; } if (this.disableWaitlist && this.reserved.length >= parseInt(this.players)) { if (member) member.send(lang.other.MAX_NO_WAITLIST); - return false; + return { result: false, message: lang.other.MAX_NO_WAITLIST }; } const template = guildConfig.gameTemplates.find(t => t.id.toString() === this.template) || guildConfig.gameTemplates.find(t => t.isDefault); if (template && template.playerRole && !member.roles.find(r => r.name === template.playerRole)) { if (member) member.send(lang.other.MISSING_PLAYER_ROLE.replace(/\:ROLE/g, `\`${template.playerRole}\``)); - return false; + return { result: false, message: lang.other.MISSING_PLAYER_ROLE.replace(/\:ROLE/g, `\`${template.playerRole}\``) }; } let match = await GameRSVP.fetchRSVP(this._id, user.id); @@ -1366,12 +1380,12 @@ export class Game implements GameModel { await rsvp.save(); await this.save(); this.dmCustomInstructions(user); - return true; + return { result: true }; } - return false; + return { result: false, message: "An error occurred!" }; } - async dropOut(user: User | ShardUser, guildConfig: GuildConfig) { + async dropOut(user: User | ShardUser, guildConfig: GuildConfig): Promise<{ result: boolean, message?: string }> { const hourDiff = (new Date().getTime() - this.timestamp) / 1000 / 3600; if (guildConfig.dropOut) { if (hourDiff < 0 || this.pastSignups || this.hideDate) { @@ -1384,16 +1398,17 @@ export class Game implements GameModel { await this.save(); await GameRSVP.deleteUser(this._id, user.id); await GameRSVP.deleteUser(this._id, user.tag); - return true; + return { result: true }; } else { - if (!this.discordGuild) return; + if (!this.discordGuild) return { result: false, message: "Server not found!" }; const member = this.discordGuild.members.find((m) => m.user.tag === user.tag.trim() || m.user.id === user.id); const guildConfig = await GuildConfig.fetch(this.s); const lang = gmLanguages.find((l) => l.code === guildConfig.lang) || gmLanguages.find((l) => l.code === "en"); if (member) member.send(lang.other.ALREADY_STARTED); + return { result: false, message: lang.other.ALREADY_STARTED } } } - return false; + return { result: false, message: "Dropouts are not allowed" }; } static parseDiscord(text: string, guild: ShardGuild, getMentions: boolean = false) { diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 17aa75f..4429433 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -1,14 +1,18 @@ -import { TextChannel, Client, Message, User, GuildChannel, MessageEmbed, Permissions, Guild, CategoryChannel } from "discord.js"; -import { DeleteWriteOpResultObject, FilterQuery, ObjectId, UpdateWriteOpResult, ObjectID } from "mongodb"; +import { TextChannel, Client, Message, User, GuildChannel, MessageEmbed, Permissions, Guild, DMChannel } from "discord.js"; +import { DeleteWriteOpResultObject, FilterQuery, ObjectId, UpdateWriteOpResult } from "mongodb"; import { GuildConfig, GuildConfigModel } from "../models/guild-config"; import { Game, GameMethod, gameReminderOptions } from "../models/game"; +import { User as RPGSUser } from "../models/user"; import config from "../models/config"; import aux from "../appaux"; import db from "../db"; -import shardManager, { ShardMember } from "./shard-manager"; +import shardManager, { ShardMember, ShardGuild } from "./shard-manager"; import cloneDeep from "lodash/cloneDeep"; +import flatten from "lodash/flatten"; +import moment from "moment"; import { GameRSVP } from "../models/game-signups"; +import { multiply } from "lodash"; const app: any = { locals: {} }; @@ -325,12 +329,11 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { */ client.on("message", async (message: Message) => { try { - if (message.channel instanceof TextChannel) { let isCommand = false; for (let i = 1; i <= 3; i++) { isCommand = isCommand || message.content.startsWith(config.command, i); } - + if (message.channel instanceof TextChannel) { if (isCommand) { const guild = message.channel.guild; const guildId = guild.id; @@ -378,7 +381,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { `\`${botcmd}\` - ${lang.config.desc.HELP}\n` + `\`${botcmd} help\` - ${lang.config.desc.HELP}\n` + (isAdmin - ? `\n${lang.config.GENERAL_CONFIGURATION}\n` + + ? `\n__**${lang.config.GENERAL_CONFIGURATION}**__\n` + `\`${botcmd} configuration\` - ${lang.config.desc.CONFIGURATION}\n` + `\`${botcmd} role role name\` - ${lang.config.desc.ROLE}\n` + `\`${botcmd} manager-role role name\` - ${lang.config.desc.MANAGER_ROLE}\n` + @@ -386,16 +389,17 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { `\`${botcmd} password\` - ${lang.config.desc.PASSWORD_CLEAR}\n` + `\`${botcmd} lang ${guildConfig.lang}\` - ${lang.config.desc.LANG} ${languages.map((l) => `\`${l.code}\` (${l.name})`).join(", ")}\n` : ``) + - `\n${lang.config.USAGE}\n` + + `\n__**${lang.config.USAGE}**__\n` + + `\`${botcmd} events timeframe\` - ${lang.config.desc.EVENTS}\n` + (permission ? `\`${botcmd} link\` - ${lang.config.desc.LINK}` : ``) ); - if (embed.description.length > 0) (message.channel).send(embed); + if (embed.description.length > 0) message.author.send(embed); let embed2 = new MessageEmbed() .setTitle("RPG Schedule Help") .setColor(guildConfig.embedColor) .setDescription( isAdmin - ? `\n${lang.config.BOT_CONFIGURATION}\n` + + ? `\n__**${lang.config.BOT_CONFIGURATION}**__\n` + `\`${botcmd} embeds ${guildConfig.embeds || guildConfig.embeds == null ? "on" : "off"}\` - \`on/off\` - ${lang.config.desc.EMBEDS}\n` + `\`${botcmd} embed-color ${guildConfig.embedColor}\` - ${lang.config.desc.EMBED_COLOR}\n` + `\`${botcmd} embed-user-tags ${guildConfig.embedMentions || guildConfig.embedMentions == null ? "on" : "off"}\` - \`on/off\` - ${ @@ -409,13 +413,13 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { `\`${botcmd} bot-permissions #channel-name\` - ${lang.config.desc.BOT_PERMISSIONS}\n` : `` ); - if (embed2.description.length > 0) (message.channel).send(embed2); + if (embed2.description.length > 0) message.author.send(embed2); let embed3 = new MessageEmbed() .setTitle("RPG Schedule Help") .setColor(guildConfig.embedColor) .setDescription( isAdmin - ? `\n${lang.config.GAME_CONFIGURATION}\n` + + ? `\n__**${lang.config.GAME_CONFIGURATION}**__\n` + `\`${botcmd} add-channel #channel-name\` - ${lang.config.desc.ADD_CHANNEL}\n` + `\`${botcmd} remove-channel #channel-name\` - ${lang.config.desc.REMOVE_CHANNEL}\n` + `\`${botcmd} pruning ${guildConfig.pruning ? "on" : "off"}\` - \`on/off\` - ${lang.config.desc.PRUNING}\n` + @@ -424,7 +428,8 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { `\`${botcmd} reschedule-mode ${guildConfig.rescheduleMode}\` - ${lang.config.desc.RESCHEDULE_MODE}\n` : `` ); - if (embed3.description.length > 0) (message.channel).send(embed3); + if (embed3.description.length > 0) message.author.send(embed3); + message.delete(); } else if (cmd === "link" && permission) { (message.channel).send(responseEmbed(process.env.HOST + config.urls.game.create.path + "?s=" + guildId)); } else if (cmd === "configuration" && isAdmin) { @@ -923,6 +928,10 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { message.channel.send(responseEmbed(`Data refresh started for the \`${guild.name}\` server`)); } } + } else if (cmd === "reboot" && member.user.tag === config.author) { + await client.shard.send({ + type: "reboot" + }); } else if (cmd === "bot-permissions" && (isAdmin || member.user.tag === config.author)) { let channelId = message.channel.id; if (params[0]) { @@ -944,6 +953,11 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } else { (message.channel).send(responseEmbed(`The bot has all the permissions it needs in <#${sChannel.id}>.`)); } + } else if (cmd === "events") { + const response = await cmdFetchEvents(message.guild, params.join(" "), lang); + if (response) message.author.send(response); + else message.author.send(lang.config.EVENTS_NONE); + message.delete(); } else { const response = await (message.channel).send("Command not recognized"); if (response) { @@ -957,6 +971,48 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } } } + if (message.channel instanceof DMChannel) { + if (isCommand) { + const userSettings = await RPGSUser.fetch(message.author.id); + + const prefix = "!"; + const botcmd = `${prefix}${config.command}`; + if (!message.content.startsWith(botcmd)) return; + + const parts = message.content + .trim() + .split(" ") + .filter((part) => part.length > 0); + const cmd = parts.slice(1, 2)[0]; + const params = parts.slice(2); + + const languages = app.locals.langs; + const lang = languages.find((l) => l.code === userSettings.lang) || languages.find((l) => l.code === "en"); + + try { + if (cmd === "events") { + const guilds = flatten(await shardManager.clientShardGuilds(client, { memberIds: [message.author.id] })); + let count = 0; + for (let i = 0; i < guilds.length; i++) { + const guild = guilds[i]; + const response = await cmdFetchEvents(guild, params.join(" "), lang, guilds); + if (response) count++; + if (response) message.author.send(response); + } + if (!count) message.author.send(lang.config.EVENTS_NONE); + } else { + const response = await (message.channel).send("Command not recognized"); + if (response) { + setTimeout(() => { + response.delete(); + }, 3000); + } + } + } catch (err) { + aux.log("BotCommandError:", err); + } + } + } } catch (err) { aux.log(err); } @@ -1187,8 +1243,7 @@ const rescheduleOldGames = async (guildId?: string) => { count++; try { await game.reschedule(); - } catch (err) { - } + } catch (err) {} } } } @@ -1327,15 +1382,15 @@ const pruneOldGames = async (guild?: Guild) => { { hideDate: { $in: [false, null], - } + }, }, { - deleted: true - } + deleted: true, + }, ], timestamp: { $lt: new Date().getTime() - 14 * 24 * 3600 * 1000, - } + }, }, client ); @@ -1646,3 +1701,68 @@ const guildMap = (guild: Guild) => { roles: guild.roles.cache.array(), }; }; + +const cmdFetchEvents = async (guild: ShardGuild | Guild, time: string, lang: any, sGuilds: ShardGuild[] = null) => { + try { + const start = new Date(); + const end = new Date(start); + let timeframe = ""; + + let param = time; + if (!param) param = "24hr"; + if (param.match(/\d{1,3} ?h(ou)?rs?/)) { + const hours = param.replace(/ ?h(ou)?rs?/, ""); + end.setHours(start.getHours() + parseInt(hours)); + timeframe = `${hours} hours`; + } + if (param.match(/\d{1,3} ?days?/)) { + const days = param.replace(/ ?days?/, ""); + end.setDate(start.getDate() + parseInt(days)); + timeframe = `${days} days`; + } + if (param.match(/\d{1,3} ?weeks?/)) { + const weeks = param.replace(/ ?weeks?/, ""); + end.setDate(start.getDate() + 7 * parseInt(weeks)); + timeframe = `${weeks} weeks`; + } + + const games = await Game.fetchAllBy( + { + s: guild.id, + $and: [{ timestamp: { $gt: start.getTime() } }, { timestamp: { $lte: end.getTime() } }], + }, + sGuilds ? null : client, + sGuilds ? sGuilds : null + ); + + games.sort((a, b) => { + return a.timestamp > b.timestamp ? 1 : -1; + }); + + let response: MessageEmbed; + + if (games.length > 0) { + moment.locale(lang.code); + const embed = new MessageEmbed(); + embed.setTitle(lang.config.EVENTS_TITLE.replace(/\:SERVER/g, guild.name).replace(/\:TIMEFRAME/g, timeframe)); + games.forEach((game, i) => { + const date = Game.ISOGameDate(game); + const timezone = "UTC" + (game.timezone >= 0 ? "+" : "") + game.timezone; + const calendarDate = moment(date).utcOffset(game.timezone).calendar(); + // console.log(date, '||', moment(date).calendar(), '||', moment(date).utcOffset(0).calendar(), '||', moment(date).utcOffset(game.timezone).calendar()); + const gameDate = calendarDate.indexOf(" at ") >= 0 ? calendarDate : moment(date).format(config.formats[game.tz ? "dateLongTZ" : "dateLong"]); + embed.addField(lang.game.GAME_NAME, `[${game.adventure}](https://discordapp.com/channels/${game.discordGuild.id}/${game.discordChannel.id}/${game.messageId})`, true); + embed.addField(lang.game.WHEN, `${gameDate} (${timezone})`, true); + embed.addField(lang.game.RESERVED, `${game.reserved.length} / ${game.players}`, true); + }); + + response = embed; + } + + moment.locale("en"); + + return response; + } catch (err) { + return `${err.name}: ${err.message}`; + } +}; diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index d13e8c1..a631148 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -179,6 +179,9 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { io().to(`g-${message.data}`).emit('delete-guild', message.data); } } + if (message.type === "reboot") { + manager.respawnAll(0, 0, 30000); + } if (message.type === "refresh") { if (message.guildId) { await refreshGuild(message.guildId); @@ -587,7 +590,6 @@ const shardGuilds = async (filters: ShardFilters = {}) => { }; const refreshGuild = async function (guildId: string) { - const call = ` (async () => { const guild = this.guilds.cache.get(${JSON.stringify(guildId)}); @@ -644,6 +646,63 @@ const refreshGuild = async function (guildId: string) { return shardGuilds({ guild: guildData }); }; +const clientShardGuilds = function (client: Client, filters: ShardFilters = {}) { + const guildIds = filters.guildIds || []; + const memberIds = filters.memberIds || []; + const call = ` + const guilds = this.guilds.cache.array() + .filter((guild) => ${JSON.stringify(guildIds)}.length === 0 || ${JSON.stringify(guildIds)}.includes(guild.id)) + .filter((guild) => { + return guild.members.cache.find((member) => ${JSON.stringify(memberIds)}.length === 0 || ${JSON.stringify(memberIds)}.includes(member.user.id)); + }); + guilds.map(guild => { + const members = guild.members.cache.array().filter((member) => ${JSON.stringify(memberIds)}.length === 0 || ${JSON.stringify(memberIds)}.includes(member.user.id)); + const sGuild = { + id: guild.id, + name: guild.name, + icon: guild.icon, + shardID: guild.shardID, + ownerID: guild.ownerID, + members: members, + users: members.map((m) => ({ + id: m.user.id, + username: m.user.username, + tag: m.user.tag, + discriminator: m.user.discriminator, + avatar: m.user.avatar, + })), + memberRoles: members.map((m) => + m.roles.cache.map((r) => ({ + id: r.id, + name: r.name, + permissions: r.permissions, + })) + ), + channels: guild.channels.cache.array().map((c) => ({ + id: c.id, + type: c.type, + name: c.name, + guild: c.guild.id, + parentID: c.parentID, + members: [], + everyone: c.permissionsFor(c.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), + botPermissions: [ + c.permissionsFor(this.user.id).has(Permissions.FLAGS.VIEW_CHANNEL) && "VIEW_CHANNEL", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.READ_MESSAGE_HISTORY) && "READ_MESSAGE_HISTORY", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.SEND_MESSAGES) && "SEND_MESSAGES", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.MANAGE_MESSAGES) && "MANAGE_MESSAGES", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.EMBED_LINKS) && "EMBED_LINKS", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", + ].filter((check) => check) + })), + roles: guild.roles.cache.array(), + }; + return sGuild; + }); + `; + return client.shard.broadcastEval(call); +}; + const shardUser = async () => { const shards = await discordClient().broadcastEval("this.user"); return shards.find((u) => u); @@ -758,6 +817,7 @@ export default { processes: managerConnect, shardGuilds: shardGuilds, clientGuilds: clientGuilds, + clientShardGuilds: clientShardGuilds, shardUser: shardUser, shardMessageReact: shardMessageReact, shardMessageEdit: shardMessageEdit, diff --git a/src/routes/api.ts b/src/routes/api.ts index c4f696f..a04bcd8 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -16,6 +16,7 @@ import { User } from "../models/user"; import config from "../models/config"; import aux from "../appaux"; import { GameRSVP } from "../models/game-signups"; +import { result } from "lodash"; const apiVersion = process.env.VERSION; @@ -776,7 +777,7 @@ export default (options: APIRouteOptions) => { } } - if (!isAdmin && game.dm.id !== result.account.user.id) { + if (!isAdmin && game.dm.id !== result.account.user.id && game.dm.tag !== result.account.user.tag) { throw new Error("You are not the GM of this game."); } @@ -888,7 +889,9 @@ export default (options: APIRouteOptions) => { const updatedGame = new Game(game.data, [game.discordGuild]); updatedGame - .save() + .save({ + user: result.account.user + }) .then(async (response) => { updatedGame._id = response.modified ? response._id : null; let uRes: GameModel; @@ -1015,7 +1018,7 @@ export default (options: APIRouteOptions) => { const game = await Game.fetch(req.body.game, null, sGuilds); if (game) { const member = game.discordGuild.members.find((m) => m.id === req.body.id); - let rsvp = false; + let rsvp: { result: boolean, message?: string }; if (!game.reserved.find((r) => r.id === member.user.id || r.tag === member.user.tag)) { rsvp = await game.signUp(member.user); } else { @@ -1023,7 +1026,7 @@ export default (options: APIRouteOptions) => { rsvp = await game.dropOut(member.user, guildConfig); } - if (rsvp) + if (rsvp.result) res.json({ status: "success", gameId: game._id, @@ -1031,8 +1034,8 @@ export default (options: APIRouteOptions) => { }); else res.json({ - status: "success", - past: true, + status: "error", + message: rsvp.message, }); } else { res.json({ @@ -1062,7 +1065,7 @@ export default (options: APIRouteOptions) => { .then(async (result: any) => { const game = await Game.fetch(req.body.g, null, result.sGuilds); if (game) { - let rsvp = false; + let rsvp: { result: boolean, message?: string }; if (!game.reserved.find((r) => r.id === result.account.user.id || r.tag === result.account.user.tag)) { rsvp = await game.signUp(result.account.user, t); } else { @@ -1070,7 +1073,7 @@ export default (options: APIRouteOptions) => { rsvp = await game.dropOut(result.account.user, guildConfig); } - if (rsvp) + if (rsvp.result) res.json({ status: "success", token: token, @@ -1079,9 +1082,9 @@ export default (options: APIRouteOptions) => { }); else res.json({ - status: "success", + status: "error", token: token, - past: true, + message: rsvp.message, }); } else { throw new Error("Game not found (2)"); From 8c17d7344648602de909ed49ddcec9a66e13e137 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Mon, 21 Sep 2020 19:36:46 -0500 Subject: [PATCH 41/53] When role name is changed, update server config --- src/processes/discord.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 4429433..7c58c41 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -174,6 +174,15 @@ if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { name: "roleUpdate", data: role, }); + + const guildConfig = await GuildConfig.fetch(oldR.guild.id); + if (guildConfig.role == oldR.name) guildConfig.role = role.name; + guildConfig.gameTemplates = guildConfig.gameTemplates.map(gt => { + if (gt.role == oldR.name) gt.role = role.name; + if (gt.playerRole == oldR.name) gt.playerRole = role.name; + return gt; + }); + await guildConfig.save(); }); client.on("roleDelete", async (role) => { @@ -329,10 +338,10 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { */ client.on("message", async (message: Message) => { try { - let isCommand = false; - for (let i = 1; i <= 3; i++) { - isCommand = isCommand || message.content.startsWith(config.command, i); - } + let isCommand = false; + for (let i = 1; i <= 3; i++) { + isCommand = isCommand || message.content.startsWith(config.command, i); + } if (message.channel instanceof TextChannel) { if (isCommand) { const guild = message.channel.guild; @@ -1008,7 +1017,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { }, 3000); } } - } catch (err) { + } catch (err) { aux.log("BotCommandError:", err); } } From 85cfc857a5f246691ea247a85505b62aa7a0214e Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Mon, 21 Sep 2020 19:37:41 -0500 Subject: [PATCH 42/53] Support for announcement channels --- src/models/game.ts | 4 +- src/models/guild-config.ts | 4 +- src/processes/discord.ts | 75 +++++++++++++++++----------------- src/processes/shard-manager.ts | 10 ++--- src/routes/api.ts | 6 +-- 5 files changed, 50 insertions(+), 49 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index ca467f7..5723149 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -207,7 +207,7 @@ export class Game implements GameModel { if (!this._channel && c.type === "text") { this._channel = c; } - if (c.id === value && c.type === "text") { + if (c.id === value && (c.type === "text" || c.type === "news")) { this._channel = c; } }); @@ -238,7 +238,7 @@ export class Game implements GameModel { if (!this._channel && c.type === "text") { this._channel = c; } - if (c.id === this.c && c.type === "text") { + if (c.id === this.c && (c.type === "text" || c.type === "news")) { this._channel = c; } }); diff --git a/src/models/guild-config.ts b/src/models/guild-config.ts index 4e0fb84..1e04948 100644 --- a/src/models/guild-config.ts +++ b/src/models/guild-config.ts @@ -2,7 +2,7 @@ import db from "../db"; import { ObjectID, ObjectId, FilterQuery } from "mongodb"; import { Game, GameReminder } from "./game"; import aux from "../appaux"; -import { GuildMember, Client, TextChannel } from "discord.js"; +import { GuildMember, Client, TextChannel, NewsChannel } from "discord.js"; import { ShardMember } from "../processes/shard-manager"; const supportedLanguages = require("../../lang/langs.json"); @@ -301,7 +301,7 @@ export class GuildConfig implements GuildConfigDataModel { const game = games[i]; const guild = client.guilds.cache.find((g) => g.id === game.discordGuild.id); if (guild) { - const channel = guild.channels.cache.find((c) => c instanceof TextChannel && c.id === game.discordChannel.id); + const channel = guild.channels.cache.find((c) => (c instanceof TextChannel || c instanceof NewsChannel) && c.id === game.discordChannel.id); if (channel) { const message = await channel.messages.fetch(game.messageId); if (message) { diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 7c58c41..049ae30 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -1,4 +1,4 @@ -import { TextChannel, Client, Message, User, GuildChannel, MessageEmbed, Permissions, Guild, DMChannel } from "discord.js"; +import { TextChannel, Client, Message, User, GuildChannel, MessageEmbed, Permissions, Guild, DMChannel, NewsChannel } from "discord.js"; import { DeleteWriteOpResultObject, FilterQuery, ObjectId, UpdateWriteOpResult } from "mongodb"; import { GuildConfig, GuildConfigModel } from "../models/guild-config"; @@ -12,7 +12,6 @@ import cloneDeep from "lodash/cloneDeep"; import flatten from "lodash/flatten"; import moment from "moment"; import { GameRSVP } from "../models/game-signups"; -import { multiply } from "lodash"; const app: any = { locals: {} }; @@ -440,7 +439,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (embed3.description.length > 0) message.author.send(embed3); message.delete(); } else if (cmd === "link" && permission) { - (message.channel).send(responseEmbed(process.env.HOST + config.urls.game.create.path + "?s=" + guildId)); + message.channel.send(responseEmbed(process.env.HOST + config.urls.game.create.path + "?s=" + guildId)); } else if (cmd === "configuration" && isAdmin) { const channel = guildConfig.channels.length > 0 @@ -486,7 +485,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (c) channel = c.id; } if (channel.trim().length === params[0].trim().length) { - return (message.channel).send(responseEmbed(`Channel not found!`)); + return message.channel.send(responseEmbed(`Channel not found!`)); } const channels = guildConfig.channels; if (!channels.find((c) => c.channelId === channel)) channels.push({ channelId: channel, gameTemplates: [guildConfig.defaultGameTemplate.id] }); @@ -495,7 +494,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { channel: channels, }) .then((result) => { - (message.channel).send(responseEmbed(lang.config.CHANNEL_ADDED)); + message.channel.send(responseEmbed(lang.config.CHANNEL_ADDED)); const addedChannel = message.guild.channels.cache.find((c) => c.id === channel); if (addedChannel) { const missingPermissions = [ @@ -507,7 +506,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { !addedChannel.permissionsFor(client.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", ].filter((check) => check); if (missingPermissions.length > 0) { - (message.channel).send( + message.channel.send( responseEmbed(`The bot is missing the following permissions in ${addedChannel.toString()}: ${missingPermissions.join(", ")}`) ); } @@ -525,7 +524,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (c) channel = c.id; } if (channel.trim().length === params[0].trim().length) { - return (message.channel).send(responseEmbed(`Channel not found!`)); + return message.channel.send(responseEmbed(`Channel not found!`)); } const channels = guildConfig.channels.filter((c) => { return !!guild.channels.cache.get(c.channelId); @@ -541,7 +540,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { channel: channels, }) .then((result) => { - (message.channel).send(responseEmbed(lang.config.CHANNEL_REMOVED)); + message.channel.send(responseEmbed(lang.config.CHANNEL_REMOVED)); }) .catch((err) => { aux.log(err); @@ -554,7 +553,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { pruning: params[0] === "on", }) .then((result) => { - (message.channel).send(responseEmbed(params[0] === "on" ? lang.config.PRUNING_ON : lang.config.PRUNING_OFF)); + message.channel.send(responseEmbed(params[0] === "on" ? lang.config.PRUNING_ON : lang.config.PRUNING_OFF)); }) .catch((err) => { aux.log(err); @@ -562,7 +561,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } } else if (cmd === "prune" && isAdmin) { await pruneOldGames(message.guild); - (message.channel).send(responseEmbed(lang.config.PRUNE)); + message.channel.send(responseEmbed(lang.config.PRUNE)); } else if (cmd === "embeds" && isAdmin) { if (params[0]) { guildConfig @@ -570,7 +569,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { embeds: !(params[0] === "off"), }) .then((result) => { - (message.channel).send(responseEmbed(!(params[0] === "off") ? lang.config.EMBEDS_ON : lang.config.EMBEDS_OFF)); + message.channel.send(responseEmbed(!(params[0] === "off") ? lang.config.EMBEDS_ON : lang.config.EMBEDS_OFF)); }) .catch((err) => { aux.log(err); @@ -583,7 +582,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { embedMentions: !(params[0] === "off"), }) .then((result) => { - (message.channel).send(responseEmbed(!(params[0] === "off") ? lang.config.EMBED_USER_TAGS_ON : lang.config.EMBED_USER_TAGS_OFF)); + message.channel.send(responseEmbed(!(params[0] === "off") ? lang.config.EMBED_USER_TAGS_ON : lang.config.EMBED_USER_TAGS_OFF)); }) .catch((err) => { aux.log(err); @@ -596,7 +595,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { embedMentionsAbove: !(params[0] === "off"), }) .then((result) => { - (message.channel).send(responseEmbed(!(params[0] === "off") ? lang.config.EMBED_USER_TAGS_ABOVE_ON : lang.config.EMBED_USER_TAGS_ABOVE_OFF)); + message.channel.send(responseEmbed(!(params[0] === "off") ? lang.config.EMBED_USER_TAGS_ABOVE_ON : lang.config.EMBED_USER_TAGS_ABOVE_OFF)); }) .catch((err) => { aux.log(err); @@ -750,7 +749,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (colors[color]) { color = colors[color]; } else if (!color.match(/[0-9a-f]{6}|[0-9a-f]{3}/i)) { - (message.channel).send(responseEmbed(lang.config.desc.EMBED_COLOR_ERROR)); + message.channel.send(responseEmbed(lang.config.desc.EMBED_COLOR_ERROR)); return; } const save: GuildConfigModel = {}; @@ -761,7 +760,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { let embed = new MessageEmbed() .setColor("#" + color.match(/[0-9a-f]{6}|[0-9a-f]{3}/i)[0]) .setDescription(`${lang.config.EMBED_COLOR_SET} \`#${color.match(/[0-9a-f]{6}|[0-9a-f]{3}/i)[0]}\`.`); - (message.channel).send(embed); + message.channel.send(embed); }) .catch((err) => { aux.log(err); @@ -769,7 +768,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } else if (cmd === "emoji-sign-up" && isAdmin) { const emoji = params.join(" "); if (!aux.isEmoji(emoji) || (emoji.length > 2 && emoji.match(/\:[^\:]+\:/))) { - (message.channel).send(responseEmbed(lang.config.desc.EMOJI_ERROR.replace(/\:char/gi, emoji.replace(/\<|\>/g, "")))); + message.channel.send(responseEmbed(lang.config.desc.EMOJI_ERROR.replace(/\:char/gi, emoji.replace(/\<|\>/g, "")))); return; } guildConfig @@ -777,7 +776,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { emojiAdd: emoji, }) .then((result) => { - (message.channel).send(responseEmbed(lang.config.EMOJI_JOIN_SET)); + message.channel.send(responseEmbed(lang.config.EMOJI_JOIN_SET)); guildConfig.emojiAdd = emoji; guildConfig.updateReactions(client); }) @@ -787,7 +786,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } else if (cmd === "emoji-drop-out" && isAdmin) { const emoji = params.join(" "); if (!aux.isEmoji(emoji) || (emoji.length > 2 && emoji.match(/\:[^\:]+\:/))) { - (message.channel).send(responseEmbed(lang.config.desc.EMOJI_ERROR.replace(/\:char/gi, emoji.replace(/\<|\>/g, "")))); + message.channel.send(responseEmbed(lang.config.desc.EMOJI_ERROR.replace(/\:char/gi, emoji.replace(/\<|\>/g, "")))); return; } await guildConfig @@ -795,7 +794,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { emojiRemove: emoji, }) .then((result) => { - (message.channel).send(responseEmbed(lang.config.EMOJI_LEAVE_SET)); + message.channel.send(responseEmbed(lang.config.EMOJI_LEAVE_SET)); guildConfig.emojiRemove = emoji; guildConfig.updateReactions(client); }) @@ -809,7 +808,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { escape: prefix.length ? prefix : "!", }) .then((result) => { - (message.channel).send(responseEmbed(lang.config.PREFIX_CHAR.replace(/\:CMD/gi, `${prefix.length ? prefix : "!"}${config.command}`))); + message.channel.send(responseEmbed(lang.config.PREFIX_CHAR.replace(/\:CMD/gi, `${prefix.length ? prefix : "!"}${config.command}`))); }) .catch((err) => { aux.log(err); @@ -820,7 +819,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { privateReminders: !guildConfig.privateReminders, }) .then((result) => { - (message.channel).send(responseEmbed(!guildConfig.privateReminders ? lang.config.PRIVATE_REMINDERS_ON : lang.config.PRIVATE_REMINDERS_OFF)); + message.channel.send(responseEmbed(!guildConfig.privateReminders ? lang.config.PRIVATE_REMINDERS_ON : lang.config.PRIVATE_REMINDERS_OFF)); }) .catch((err) => { aux.log(err); @@ -833,7 +832,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { rescheduleMode: options.includes(mode) ? mode : "repost", }) .then((result) => { - (message.channel).send(responseEmbed(lang.config.RESCHEDULE_MODE_UPDATED)); + message.channel.send(responseEmbed(lang.config.RESCHEDULE_MODE_UPDATED)); }) .catch((err) => { aux.log(err); @@ -848,7 +847,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { password: params.join(" "), }) .then((result) => { - (message.channel).send(responseEmbed(lang.config.PASSWORD_SET)); + message.channel.send(responseEmbed(lang.config.PASSWORD_SET)); }) .catch((err) => { aux.log(err); @@ -859,7 +858,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { dropOut: guildConfig.dropOut === false, }) .then((result) => { - (message.channel).send(responseEmbed(guildConfig.dropOut === false ? lang.config.DROP_OUTS_ENABLED : lang.config.DROP_OUTS_DISABLED)); + message.channel.send(responseEmbed(guildConfig.dropOut === false ? lang.config.DROP_OUTS_ENABLED : lang.config.DROP_OUTS_DISABLED)); }) .catch((err) => { aux.log(err); @@ -885,7 +884,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { guildConfig .save(save) .then((result) => { - (message.channel).send(responseEmbed(roleName.length > 0 ? lang.config.ROLE_SET.replace(/\:role/gi, roleName) : lang.config.ROLE_CLEARED)); + message.channel.send(responseEmbed(roleName.length > 0 ? lang.config.ROLE_SET.replace(/\:role/gi, roleName) : lang.config.ROLE_CLEARED)); }) .catch((err) => { aux.log(err); @@ -903,7 +902,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { managerRole: roleName == "" ? null : roleName, }) .then((result) => { - (message.channel).send(responseEmbed(roleName.length > 0 ? lang.config.ROLE_SET.replace(/\:role/gi, roleName) : lang.config.ROLE_CLEARED)); + message.channel.send(responseEmbed(roleName.length > 0 ? lang.config.ROLE_SET.replace(/\:role/gi, roleName) : lang.config.ROLE_CLEARED)); }) .catch((err) => { aux.log(err); @@ -911,14 +910,14 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } else if (cmd === "lang" && isAdmin) { const newLang = languages.find((l) => l.code === params[0].trim()); if (!newLang) { - return (message.channel).send(lang.config.NO_LANG); + return message.channel.send(lang.config.NO_LANG); } guildConfig .save({ lang: newLang.code, }) .then((result) => { - (message.channel).send(responseEmbed(newLang.config.LANG_SET.replace(/\:lang/gi, newLang.name))); + message.channel.send(responseEmbed(newLang.config.LANG_SET.replace(/\:lang/gi, newLang.name))); }) .catch((err) => { aux.log(err); @@ -950,7 +949,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (c) channelId = c.id; } if (channelId.trim().length === params[0].trim().length) { - return (message.channel).send(responseEmbed(`Channel not found!`)); + return message.channel.send(responseEmbed(`Channel not found!`)); } } const sGuilds = await shardManager.clientGuilds(client, [message.guild.id]); @@ -958,9 +957,9 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { const requiredPermissions = ["VIEW_CHANNEL", "READ_MESSAGE_HISTORY", "SEND_MESSAGES", "MANAGE_MESSAGES", "EMBED_LINKS", "ADD_REACTIONS"]; const missingPermissions = requiredPermissions.filter((rp) => !sChannel.botPermissions.includes(rp)); if (missingPermissions.length > 0) { - (message.channel).send(responseEmbed(`The bot is missing the following permissions in <#${sChannel.id}>: ${missingPermissions.join(", ")}`)); + message.channel.send(responseEmbed(`The bot is missing the following permissions in <#${sChannel.id}>: ${missingPermissions.join(", ")}`)); } else { - (message.channel).send(responseEmbed(`The bot has all the permissions it needs in <#${sChannel.id}>.`)); + message.channel.send(responseEmbed(`The bot has all the permissions it needs in <#${sChannel.id}>.`)); } } else if (cmd === "events") { const response = await cmdFetchEvents(message.guild, params.join(" "), lang); @@ -968,7 +967,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { else message.author.send(lang.config.EVENTS_NONE); message.delete(); } else { - const response = await (message.channel).send("Command not recognized"); + const response = await message.channel.send("Command not recognized"); if (response) { setTimeout(() => { response.delete(); @@ -1060,19 +1059,21 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { */ client.on("messageDelete", async (message) => { if (message.author.id !== client.user.id) return; + if (!(message.channel instanceof TextChannel || message.channel instanceof NewsChannel)) return; const games = await Game.fetchAllBy( { messageId: message.id, pruned: { $in: [false, null], }, + deleted: { + $in: [false, null], + } }, client ); games.forEach((game) => { - if (game && message.channel instanceof TextChannel) { - game.delete(); - } + if (game) game.delete(); }); }); @@ -1089,7 +1090,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { const { d: data } = event; const user = client.users.cache.get(data.user_id); - const channel = client.channels.cache.get(data.channel_id) || (await user.createDM()); + const channel = client.channels.cache.get(data.channel_id) || (await user.createDM()); if (!channel || channel.messages.cache.has(data.message_id)) return; @@ -1410,7 +1411,7 @@ const pruneOldGames = async (guild?: Guild) => { for (let gci = 0; gci < guildConfigs.length; gci++) { const gc = guildConfigs[gci]; const guild = client.guilds.cache.find((g) => g.id === gc.guild); - const channels = guild.channels.cache.array().filter((c) => gc.channels.find((gcc) => gcc.channelId === c.id) && c instanceof TextChannel); + const channels = <(TextChannel | NewsChannel)[]>guild.channels.cache.array().filter((c) => gc.channels.find((gcc) => gcc.channelId === c.id) && (c instanceof TextChannel || c instanceof NewsChannel)); for (let ci = 0; ci < channels.length; ci++) { const c = channels[ci]; diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index a631148..6cfa187 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -1,4 +1,4 @@ -import { ShardingManager, Role, MessageEmbed, Message, Client, TextChannel, Permissions } from "discord.js"; +import { ShardingManager, Role, MessageEmbed, Message, Client, TextChannel, Permissions, NewsChannel } from "discord.js"; import { Express } from "express"; import { io } from "./socket"; import aux from "../appaux"; @@ -358,7 +358,7 @@ const clientGuilds = async (client: Client, guildIds: string[] = []) => { if (sGuild) { const sChannel = sGuild.channels.cache.get(channel.id); if (sChannel) { - return await (sChannel).messages.fetch(messageId); + return await (sChannel).messages.fetch(messageId); } } return null; @@ -370,7 +370,7 @@ const clientGuilds = async (client: Client, guildIds: string[] = []) => { if (sGuild) { const sChannel = sGuild.channels.cache.get(channel.id); if (sChannel) { - return [await (sChannel).send(content, options)]; + return [await (sChannel).send(content, options)]; } } return [null]; @@ -758,7 +758,7 @@ const clientMessageEdit = async (client: Client, guildId: string, channelId: str if (guild) { const channel = guild.channels.cache.get(channelId); if (channel) { - const message = await (channel).messages.fetch(messageId); + const message = await (channel).messages.fetch(messageId); if (message) { return message.edit(content, options); } @@ -772,7 +772,7 @@ const findMessage = async (client: Client, guildId: string, channelId: string, m if (client) { const guild = client.guilds.cache.get(guildId); if (guild) { - const channel = guild.channels.cache.get(channelId); + const channel = guild.channels.cache.get(channelId); if (channel) { let message = await channel.messages.fetch(messageId); if (message) return message; diff --git a/src/routes/api.ts b/src/routes/api.ts index a04bcd8..2477351 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -1362,7 +1362,7 @@ export default (options: APIRouteOptions) => { roles: guild.roles, userRoles: [], channelCategories: guild.channels.filter((c) => c.type === "category"), - channels: guild.channels.filter((c) => c.type === "text"), + channels: guild.channels.filter((c) => c.type === "text" || c.type === "news"), announcementChannels: [], config: new GuildConfig({ guild: guild.id }), games: [], @@ -1590,7 +1590,7 @@ export default (options: APIRouteOptions) => { roles: guild.roles, userRoles: [], channelCategories: guild.channels.filter((c) => c.type === "category"), - channels: guild.channels.filter((c) => c.type === "text"), + channels: guild.channels.filter((c) => c.type === "text" || c.type === "news"), announcementChannels: [], config: new GuildConfig({ guild: guild.id }), games: [], @@ -1807,7 +1807,7 @@ const fetchAccount = (token: any, options: AccountOptions) => { member: null, config: new GuildConfig({ guild: guild.id }), announcementChannels: [], - channels: guild.channels.filter((c) => c.type === "text"), + channels: guild.channels.filter((c) => c.type === "text" || c.type === "news"), channelCategories: guild.channels.filter((c) => c.type === "category"), games: [], roles: guild.roles, From e0298ed0301786977a112c116fe0dfc7cf14f1b1 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Mon, 21 Sep 2020 19:57:23 -0500 Subject: [PATCH 43/53] `!schedule events` bug fix for too many games --- src/processes/discord.ts | 41 ++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 049ae30..81821bd 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -963,7 +963,16 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } } else if (cmd === "events") { const response = await cmdFetchEvents(message.guild, params.join(" "), lang); - if (response) message.author.send(response); + if (response) { + if (Array.isArray(response)) { + response.forEach(r => { + message.author.send(r); + }); + } + else { + message.author.send(response) + } + } else message.author.send(lang.config.EVENTS_NONE); message.delete(); } else { @@ -1005,7 +1014,16 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { const guild = guilds[i]; const response = await cmdFetchEvents(guild, params.join(" "), lang, guilds); if (response) count++; - if (response) message.author.send(response); + if (response) { + if (Array.isArray(response)) { + response.forEach(r => { + message.author.send(r); + }); + } + else { + message.author.send(response) + } + } } if (!count) message.author.send(lang.config.EVENTS_NONE); } else { @@ -1749,12 +1767,13 @@ const cmdFetchEvents = async (guild: ShardGuild | Guild, time: string, lang: any return a.timestamp > b.timestamp ? 1 : -1; }); - let response: MessageEmbed; + let response: MessageEmbed[] = []; if (games.length > 0) { moment.locale(lang.code); - const embed = new MessageEmbed(); - embed.setTitle(lang.config.EVENTS_TITLE.replace(/\:SERVER/g, guild.name).replace(/\:TIMEFRAME/g, timeframe)); + let embed = new MessageEmbed(); + const title = lang.config.EVENTS_TITLE.replace(/\:SERVER/g, guild.name).replace(/\:TIMEFRAME/g, timeframe); + embed.setTitle(title); games.forEach((game, i) => { const date = Game.ISOGameDate(game); const timezone = "UTC" + (game.timezone >= 0 ? "+" : "") + game.timezone; @@ -1764,9 +1783,19 @@ const cmdFetchEvents = async (guild: ShardGuild | Guild, time: string, lang: any embed.addField(lang.game.GAME_NAME, `[${game.adventure}](https://discordapp.com/channels/${game.discordGuild.id}/${game.discordChannel.id}/${game.messageId})`, true); embed.addField(lang.game.WHEN, `${gameDate} (${timezone})`, true); embed.addField(lang.game.RESERVED, `${game.reserved.length} / ${game.players}`, true); + + if ((i + 1) % 6 === 0) { + if (games.length > 6) embed.setFooter(`Page ${response.length + 1}`); + response.push(embed); + embed = new MessageEmbed(); + // embed.setTitle(title); + } }); - response = embed; + if ((games.length + 1) % 6 !== 0) { + if (games.length > 6) embed.setFooter(`Page ${response.length + 1}`); + response.push(embed); + } } moment.locale("en"); From 9dac2f755171a71808144ed2888900ee61fc2f9d Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Mon, 21 Sep 2020 23:19:01 -0500 Subject: [PATCH 44/53] Thumbnail Bug Fix --- src/models/game.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index 5723149..9a1d58b 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -505,8 +505,8 @@ export class Game implements GameModel { embed = new discord.MessageEmbed(); embed.setColor(gameTemplate && gameTemplate.embedColor ? gameTemplate.embedColor : guildConfig.embedColor); embed.setTitle(game.adventure); - embed.setAuthor(dm, dmmember && dmmember.user.avatarUrl && urlRegex.test(dmmember.user.avatarUrl) ? dmmember.user.avatarUrl.substr(0, 2048) : null); - if (dmmember && dmmember.user.avatarUrl && urlRegex.test(dmmember.user.avatarUrl)) embed.setThumbnail(dmmember.user.avatarUrl.substr(0, 2048)); + embed.setAuthor(dm, dmmember && dmmember.user.avatarUrl && dmmember.user.avatarUrl.match(urlRegex) ? dmmember.user.avatarUrl.substr(0, 2048) : null); + if (dmmember && dmmember.user.avatarUrl && dmmember.user.avatarUrl.match(urlRegex)) embed.setThumbnail(dmmember.user.avatarUrl.substr(0, 2048)); if (description.length > 0) embed.setDescription(description); if (game.hideDate) embed.addField(lang.game.WHEN, lang.game.labels.TBD, true); else embed.addField(lang.game.WHEN, when, true); @@ -552,8 +552,8 @@ export class Game implements GameModel { true ); if (game.method === GameMethod.AUTOMATED) embed.setFooter(automatedInstructions); - if (game.thumbnail && game.thumbnail.trim().length > 0 && urlRegex.test(game.thumbnail.trim())) embed.setThumbnail(game.thumbnail.trim().substr(0, 2048)); - if (game.gameImage && game.gameImage.trim().length > 0 && urlRegex.test(game.gameImage.trim())) embed.setImage(game.gameImage.trim().substr(0, 2048)); + if (game.gameImage && game.gameImage.trim().length > 0 && game.gameImage.trim().match(urlRegex)) embed.setImage(game.gameImage.trim().substr(0, 2048)); + if (game.thumbnail && game.thumbnail.trim().length > 0 && game.thumbnail.trim().match(urlRegex)) embed.setThumbnail(game.thumbnail.trim().substr(0, 2048)); if (!this.hideDate) embed.setTimestamp(gameDate); } From 88d53922f1a60eb11a62e317f7e50e392663c819 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 22 Sep 2020 09:25:46 -0500 Subject: [PATCH 45/53] Store role ids --- src/appaux.ts | 5 +++ src/models/game.ts | 7 ++-- src/models/guild-config.ts | 24 +++++++------ src/processes/discord.ts | 46 ++++++++++++++++--------- src/routes/api.ts | 70 ++++---------------------------------- src/routes/init.ts | 22 ++++++------ 6 files changed, 71 insertions(+), 103 deletions(-) diff --git a/src/appaux.ts b/src/appaux.ts index 05ddd6a..cf47afb 100644 --- a/src/appaux.ts +++ b/src/appaux.ts @@ -235,6 +235,10 @@ const isEmoji = (emoji: string) => { ); }; +const isObject = (value: any) => { + return value && typeof value === 'object' && value.constructor === Object; +}; + var colors = { aliceblue: "#f0f8ff", antiquewhite: "#faebd7", @@ -389,6 +393,7 @@ export default { backslash: backslash, timer: timer, isEmoji: isEmoji, + isObject: isObject, log: log, patreonPledges: patreonPledges, colorFixer: colorFixer, diff --git a/src/models/game.ts b/src/models/game.ts index 9a1d58b..85aa514 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -11,6 +11,7 @@ import { GuildConfig } from "./guild-config"; import { GameRSVP } from "./game-signups"; import config from "./config"; import cloneDeep from "lodash/cloneDeep"; +import { isObject } from "lodash"; const connection = db.connection; const ObjectId = mongodb.ObjectId; @@ -1365,9 +1366,9 @@ export class Game implements GameModel { } const template = guildConfig.gameTemplates.find(t => t.id.toString() === this.template) || guildConfig.gameTemplates.find(t => t.isDefault); - if (template && template.playerRole && !member.roles.find(r => r.name === template.playerRole)) { - if (member) member.send(lang.other.MISSING_PLAYER_ROLE.replace(/\:ROLE/g, `\`${template.playerRole}\``)); - return { result: false, message: lang.other.MISSING_PLAYER_ROLE.replace(/\:ROLE/g, `\`${template.playerRole}\``) }; + if (template && template.playerRole && !member.roles.find(r => isObject(template.playerRole) ? r.id === template.playerRole.id : r.name === template.playerRole)) { + if (member) member.send(lang.other.MISSING_PLAYER_ROLE.replace(/\:ROLE/g, `\`${isObject(template.playerRole) ? template.playerRole.name : template.playerRole}\``)); + return { result: false, message: lang.other.MISSING_PLAYER_ROLE.replace(/\:ROLE/g, `\`${isObject(template.playerRole) ? template.playerRole.name : template.playerRole}\``) }; } let match = await GameRSVP.fetchRSVP(this._id, user.id); diff --git a/src/models/guild-config.ts b/src/models/guild-config.ts index 1e04948..bc69248 100644 --- a/src/models/guild-config.ts +++ b/src/models/guild-config.ts @@ -4,6 +4,7 @@ import { Game, GameReminder } from "./game"; import aux from "../appaux"; import { GuildMember, Client, TextChannel, NewsChannel } from "discord.js"; import { ShardMember } from "../processes/shard-manager"; +import { isObject } from "lodash"; const supportedLanguages = require("../../lang/langs.json"); const langs = supportedLanguages.langs @@ -30,12 +31,17 @@ export interface GameTemplate { id?: MongoDBId; name: string; isDefault: boolean; - role?: string; - playerRole?: string; + role?: string | ConfigRole; + playerRole?: string | ConfigRole; embedColor?: string; gameDefaults?: GameDefaults; } +export interface ConfigRole { + id: string; + name: string; +} + export interface ChannelConfig { channelId: string; gameTemplates: MongoDBId[]; @@ -54,13 +60,13 @@ export interface GuildConfigModel { emojiAdd?: string; emojiRemove?: string; password?: string; - role?: string; + role?: string | ConfigRole; hidden?: boolean; dropOut?: boolean; lang?: string; privateReminders?: boolean; rescheduleMode?: string; - managerRole?: string; + managerRole?: string | ConfigRole; escape?: string; gameTemplates?: GameTemplate[]; } @@ -84,13 +90,13 @@ export class GuildConfig implements GuildConfigDataModel { emojiAdd: string = "➕"; emojiRemove: string = "➖"; password: string = ""; - role: string = null; + role: string | ConfigRole = null; hidden: boolean = false; dropOut: boolean = true; lang: string = "en"; privateReminders: boolean = false; rescheduleMode: string = "repost"; - managerRole: string = null; + managerRole: string | ConfigRole = null; escape?: "!"; gameTemplates?: GameTemplate[] = []; saveDefaultTemplate = false; @@ -184,8 +190,6 @@ export class GuildConfig implements GuildConfigDataModel { updates.gameTemplates = updates.gameTemplates.map((gt) => { if (!gt.id) gt = { id: new ObjectId().toHexString(), ...gt }; gt.embedColor = aux.colorFixer(gt.embedColor); - gt.role = gt.role ? gt.role.trim() : null; - gt.playerRole = gt.playerRole ? gt.playerRole.trim() : null; return gt; }); updates.channel = updates.channel.map((channel) => { @@ -270,7 +274,7 @@ export class GuildConfig implements GuildConfigDataModel { shardMemberHasPermission(member: ShardMember, channelId?: string) { return !!this.gameTemplates.find((gt) => { const matchedChannel = this.channel.find((c) => c.gameTemplates.find((cgt) => cgt === gt.id) && (!channelId || c.channelId === channelId)); - const userHasRole = !gt.role || !!member.roles.find((r) => gt.role.toLowerCase().trim() === r.name.toLowerCase().trim()); + const userHasRole = !gt.role || !!member.roles.find((r) => isObject(gt.role) ? gt.role.id === r.id : gt.role.toLowerCase().trim() === r.name.toLowerCase().trim()); return matchedChannel && userHasRole; }); } @@ -279,7 +283,7 @@ export class GuildConfig implements GuildConfigDataModel { return !!this.gameTemplates.find((gt) => { return ( this.channel.find((c) => c.gameTemplates.find((cgt) => cgt === gt.id) && (!channelId || c.channelId === channelId)) && - (!gt.role || !!member.roles.cache.array().find((r) => gt.role.toLowerCase().trim() === r.name.toLowerCase().trim())) + (!gt.role || !!member.roles.cache.array().find((r) => isObject(gt.role) ? gt.role.id === r.id : gt.role.toLowerCase().trim() === r.name.toLowerCase().trim())) ); }); } diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 81821bd..0c38c53 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -1,7 +1,7 @@ import { TextChannel, Client, Message, User, GuildChannel, MessageEmbed, Permissions, Guild, DMChannel, NewsChannel } from "discord.js"; import { DeleteWriteOpResultObject, FilterQuery, ObjectId, UpdateWriteOpResult } from "mongodb"; -import { GuildConfig, GuildConfigModel } from "../models/guild-config"; +import { GuildConfig, GuildConfigModel, ConfigRole } from "../models/guild-config"; import { Game, GameMethod, gameReminderOptions } from "../models/game"; import { User as RPGSUser } from "../models/user"; import config from "../models/config"; @@ -12,6 +12,7 @@ import cloneDeep from "lodash/cloneDeep"; import flatten from "lodash/flatten"; import moment from "moment"; import { GameRSVP } from "../models/game-signups"; +import { isObject } from "lodash"; const app: any = { locals: {} }; @@ -175,10 +176,11 @@ if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { }); const guildConfig = await GuildConfig.fetch(oldR.guild.id); - if (guildConfig.role == oldR.name) guildConfig.role = role.name; + if (isObject(guildConfig.role) ? guildConfig.role.id == oldR.id : guildConfig.role == oldR.name) guildConfig.role = { id: role.id, name: role.name }; + if (isObject(guildConfig.managerRole) ? guildConfig.managerRole.id == oldR.id : guildConfig.managerRole == oldR.name) guildConfig.managerRole = { id: role.id, name: role.name }; guildConfig.gameTemplates = guildConfig.gameTemplates.map(gt => { - if (gt.role == oldR.name) gt.role = role.name; - if (gt.playerRole == oldR.name) gt.playerRole = role.name; + if (isObject(gt.role) ? gt.role.id === oldR.id : gt.role == oldR.name) gt.role = { id: role.id, name: role.name }; + if (isObject(gt.playerRole) ? gt.playerRole.id === oldR.id : gt.playerRole == oldR.name) gt.playerRole = { id: role.id, name: role.name }; return gt; }); await guildConfig.save(); @@ -374,7 +376,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { isAdmin = !!( member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || - member.roles.cache.find((r) => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) + member.roles.cache.find((r) => isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) ); permission = guildConfig.memberHasPermission(member) || isAdmin; } @@ -865,44 +867,56 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { }); } else if (cmd === "role" && isAdmin) { const mentioned = (params[0] || "").match(/(\d+)/); - let roleName = params.join(" "); - if (roleName.trim() === "All Roles") { - roleName = ""; + let roleObj: ConfigRole = { + id: null, + name: params.join(" ") + }; + if (roleObj.name.trim() === "All Roles") { + roleObj.name = ""; } if (mentioned) { const roleId = mentioned[0]; const role = guild.roles.cache.array().find((r) => r.id === roleId); - if (role) roleName = role.name; + if (role) { + roleObj.id = role.id; + roleObj.name = role.name; + } } const save: GuildConfigModel = { - role: roleName == "" ? null : roleName, + role: roleObj.id ? roleObj : null, gameTemplates: cloneDeep(guildConfig.gameTemplates).map((gt) => { - if (gt.name === "Default") gt.role = roleName == "" ? null : roleName; + if (gt.name === "Default") gt.role = roleObj.id ? roleObj : null; return gt; }), }; guildConfig .save(save) .then((result) => { - message.channel.send(responseEmbed(roleName.length > 0 ? lang.config.ROLE_SET.replace(/\:role/gi, roleName) : lang.config.ROLE_CLEARED)); + message.channel.send(responseEmbed(roleObj.name.length > 0 ? lang.config.ROLE_SET.replace(/\:role/gi, roleObj.name) : lang.config.ROLE_CLEARED)); }) .catch((err) => { aux.log(err); }); } else if (cmd === "manager-role" && isAdmin) { const mentioned = (params[0] || "").match(/(\d+)/); - let roleName = params.join(" "); + let roleObj: ConfigRole = { + id: null, + name: params.join(" ") + }; if (mentioned) { const roleId = mentioned[0]; const role = guild.roles.cache.array().find((r) => r.id === roleId); - if (role) roleName = role.name; + if (role) { + roleObj.id = role.id; + roleObj.name = role.name; + } } guildConfig .save({ - managerRole: roleName == "" ? null : roleName, + managerRole: roleObj.id ? roleObj : null, }) .then((result) => { - message.channel.send(responseEmbed(roleName.length > 0 ? lang.config.ROLE_SET.replace(/\:role/gi, roleName) : lang.config.ROLE_CLEARED)); + message.channel.send(responseEmbed(roleObj.name.length > 0 ? lang.config.ROLE_SET.replace(/\:role/gi, roleObj.name) : lang.config.ROLE_CLEARED)); }) .catch((err) => { aux.log(err); diff --git a/src/routes/api.ts b/src/routes/api.ts index 2477351..b638413 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -16,7 +16,7 @@ import { User } from "../models/user"; import config from "../models/config"; import aux from "../appaux"; import { GameRSVP } from "../models/game-signups"; -import { result } from "lodash"; +import { result, isObject } from "lodash"; const apiVersion = process.env.VERSION; @@ -477,7 +477,7 @@ export default (options: APIRouteOptions) => { if (typeof req.body[property] != "undefined") guildConfig[property] = req.body[property]; } const saveResult = await guildConfig.save(); - + const sGuilds = await ShardManager.shardGuilds({ guildIds: [req.body.id], }); @@ -751,7 +751,7 @@ export default (options: APIRouteOptions) => { member && (member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || - member.roles.find((r) => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim())); + member.roles.find((r) => isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim())); const gcChannel = guildConfig.channel.find((gcc) => gcc.channelId === game.c) || { channelId: game.c, gameTemplates: [guildConfig.defaultGameTemplate.id], @@ -770,7 +770,7 @@ export default (options: APIRouteOptions) => { if (gameTemplate.role && !isAdmin) { if (member) { // User does not have the require role - if (!member.roles.find((r) => r.name.toLowerCase().trim() === gameTemplate.role.toLowerCase().trim())) { + if (!member.roles.find((r) => isObject(gameTemplate.role) ? r.id === gameTemplate.role.id : r.name.toLowerCase().trim() === gameTemplate.role.toLowerCase().trim())) { throw new Error("You are either not logged in or are missing the role required for posting on this server."); } } @@ -1152,62 +1152,6 @@ export default (options: APIRouteOptions) => { } }); - router.post("/auth-api/guild-config", async (req, res, next) => { - const token = req.session.api && req.session.api.access.access_token; - try { - fetchAccount(req.session.api.access, { - client: client, - ip: req.app.locals.ip, - }) - .then(async (result: any) => { - if (Object.keys(req.body).length > 0) { - const guild: AccountGuild = result.account.guilds.find((g: AccountGuild) => g.id === req.query.s); - if (guild && guild.isAdmin) { - const guildConfig = await GuildConfig.fetch(req.query.s); - for (const item in guildConfig.data) { - if (req.body[item] !== null && typeof req.body[item] !== "undefined") { - if (typeof guildConfig[item] == "number") guildConfig[item] = parseFloat(req.body[item]); - else if (typeof guildConfig[item] == "boolean") guildConfig[item] = req.body[item] == "true"; - else guildConfig[item] = req.body[item]; - } - } - await guildConfig.save(); - io().emit("site", { action: "guild-config", config: guildConfig.data }); - res.json({ - status: "success", - token: token, - }); - } else { - res.json({ - status: "error", - token: token, - message: "You are either not part of that guild or do not have administrative privileges", - code: 25, - }); - } - } else { - throw new Error("No settings specified"); - } - }) - .catch((err) => { - res.json({ - status: "error", - token: token, - message: `UserAuthError`, - code: 26, - }); - }); - } catch (err) { - console.log(err); - res.json({ - status: "error", - token: token, - message: err.message || err, - code: 27, - }); - } - }); - router.get("/api/pledges", async (req, res, next) => { const pledges = await aux.patreonPledges(); res.json({ @@ -1442,7 +1386,7 @@ export default (options: APIRouteOptions) => { guild.isAdmin = !!( member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || - member.roles.find((r) => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) + member.roles.find((r) => isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) ); guild.permission = guildConfig.shardMemberHasPermission(member) || guild.isAdmin; gcChannels.forEach((c) => { @@ -1648,7 +1592,7 @@ export default (options: APIRouteOptions) => { guild.isAdmin = !!( member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || - member.roles.find((r) => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) + member.roles.find((r) => isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) ); guild.permission = guildConfig.shardMemberHasPermission(member) || guild.isAdmin; gcChannels.forEach((c) => { @@ -1862,7 +1806,7 @@ const fetchAccount = (token: any, options: AccountOptions) => { guild.isAdmin = !!( member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || - member.roles.find((r) => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) + member.roles.find((r) => isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) ); guild.permission = guildConfig.shardMemberHasPermission(member) || guild.isAdmin; } diff --git a/src/routes/init.ts b/src/routes/init.ts index 8935f0a..fcd9028 100644 --- a/src/routes/init.ts +++ b/src/routes/init.ts @@ -192,17 +192,17 @@ export default (options: any) => { } }); - req.account.guilds = req.account.guilds.map(guild => { - const guildConfig = guildConfigs.find(gc => gc.guild === guild.id) || new GuildConfig({ guild: guild.id }); - const member: GuildMember = guild.member; - guild.permission = guildConfig.role ? member.roles.cache.find(r => r.name.toLowerCase().trim() === (guildConfig.role || "").toLowerCase().trim()) : true; - guild.isAdmin = - member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || - member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || - member.roles.cache.find(r => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()); - guild.config = guildConfig; - return guild; - }); + // req.account.guilds = req.account.guilds.map(guild => { + // const guildConfig = guildConfigs.find(gc => gc.guild === guild.id) || new GuildConfig({ guild: guild.id }); + // const member: GuildMember = guild.member; + // guild.permission = guildConfig.role ? member.roles.cache.find(r => r.name.toLowerCase().trim() === (guildConfig.role || "").toLowerCase().trim()) : true; + // guild.isAdmin = + // member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || + // member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || + // member.roles.cache.find(r => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()); + // guild.config = guildConfig; + // return guild; + // }); // Manage Server Page if (req.account.viewing.server) { From f4afc2f3097e8cf2c07b71f1c589180b8c5c521e Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 22 Sep 2020 09:54:08 -0500 Subject: [PATCH 46/53] Ad hoc reserved list bug --- src/models/game.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index 85aa514..f0ae365 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -1299,9 +1299,8 @@ export class Game implements GameModel { const rsvpMatches = rsvps.filter((r) => r._id === res._id || (r.id && r.id === res.id) || r.tag === res.tag); // console.log(res.tag, countMatches.length, rsvpMatches.length); - if (!member) continue; - let rsvp = rsvps.find((r) => r._id === res._id || (r.id && r.id === res.id) || r.tag === res.tag); + // if (rsvp && rsvp.id && !member) continue; if (!rsvp) rsvp = await GameRSVP.fetchRSVP(this._id, res.id || res.tag); if (!rsvp || (!/#\d{4}/i.test(res.tag) && countMatches.length > rsvpMatches.length)) { rsvp = new GameRSVP({ From df82bde65635addde519950e353fc9148648c959 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Tue, 22 Sep 2020 17:56:17 -0500 Subject: [PATCH 47/53] Multiple player roles per template --- lang/de-ch.json | 8 +- lang/de.json | 8 +- lang/en.json | 8 +- lang/es.json | 12 +- lang/fr.json | 8 +- lang/it.json | 8 +- lang/pt-br.json | 8 +- lang/ru.json | 8 +- src/models/game.ts | 13 +- src/models/guild-config.ts | 4 +- src/processes/discord.ts | 5 +- src/routes/info.ts | 20 --- src/routes/init.ts | 326 ------------------------------------- src/routes/login.ts | 63 ------- 14 files changed, 75 insertions(+), 424 deletions(-) delete mode 100644 src/routes/info.ts delete mode 100644 src/routes/init.ts delete mode 100644 src/routes/login.ts diff --git a/lang/de-ch.json b/lang/de-ch.json index 03e36e6..2d2220b 100644 --- a/lang/de-ch.json +++ b/lang/de-ch.json @@ -48,6 +48,7 @@ "TIME_ZONE": "Zeitzone", "TODAY": "Heute", "GAME_IMAGE": "Spielbild", + "THUMBNAIL": "Miniaturansicht", "GAME_IMAGE_DESC": "Standardmässig wird der Avatar des SL verwendet.", "REMINDER": "Erinnerung", "REMINDER_FOR": "Erinnerung für", @@ -75,7 +76,11 @@ "GAME_OPTIONS": "Spieleinstellungen", "DISABLE_WAITLIST": "Deaktivieren Warteliste", "UPLOAD_GAME_IMAGE": "Hochladen von Spielbild", + "UPLOAD_THUMBNAIL": "Hochladen Minibild", "MAX_UPLOAD_SIZE": "Max Größe (5 MB)", + "DESCRIPTION_PANEL": "Beschreibung", + "RESCHEDULING_PANEL": "Neuterminierung", + "EXTRA_PANEL": "Zusätzliche Optionen", "options": { "AUTOMATED_SIGNUP": "Automatisches Eintragen", "CUSTOM_SIGNUP_INSTRUCTIONS": "Benutzerdefinierter Befehl zum Eintragen", @@ -220,6 +225,7 @@ "EDIT_PERMISSION": "Sie haben keine Berechtigung, dieses Spiel zu bearbeiten", "ALREADY_STARTED": "Das Spiel hat bereits begonnen!", "MAX_NO_WAITLIST": "Das Spiel ist voll und keine zusätzliche Anmeldungen zu akzeptieren.", - "MISSING_PLAYER_ROLE": "Sie haben nicht die :ROLE Rolle erforderlich, dass die Veranstaltung zu verbinden." + "MISSING_PLAYER_ROLE": "Sie haben nicht die :ROLE Rolle erforderlich, dass die Veranstaltung zu verbinden.", + "OR": "oder" } } diff --git a/lang/de.json b/lang/de.json index 0ded925..e9282b0 100644 --- a/lang/de.json +++ b/lang/de.json @@ -48,6 +48,7 @@ "TIME_ZONE": "Zeitzone", "TODAY": "Heute", "GAME_IMAGE": "Spielbild", + "THUMBNAIL": "Miniaturansicht", "GAME_IMAGE_DESC": "Standardmäßig wird der Avatar des SL verwendet.", "REMINDER": "Erinnerung", "REMINDER_FOR": "Erinnerung für", @@ -75,7 +76,11 @@ "GAME_OPTIONS": "Spieleinstellungen", "DISABLE_WAITLIST": "Deaktivieren Warteliste", "UPLOAD_GAME_IMAGE": "Hochladen von Spielbild", + "UPLOAD_THUMBNAIL": "Hochladen Minibild", "MAX_UPLOAD_SIZE": "Max Größe (5 MB)", + "DESCRIPTION_PANEL": "Beschreibung", + "RESCHEDULING_PANEL": "Neuterminierung", + "EXTRA_PANEL": "Zusätzliche Optionen", "options": { "AUTOMATED_SIGNUP": "Automatisches Eintragen", "CUSTOM_SIGNUP_INSTRUCTIONS": "Benutzerdefinierter Befehl zum Eintragen", @@ -220,6 +225,7 @@ "EDIT_PERMISSION": "Sie haben keine Berechtigung, dieses Spiel zu bearbeiten", "ALREADY_STARTED": "Das Spiel hat bereits begonnen!", "MAX_NO_WAITLIST": "Das Spiel ist voll und keine zusätzliche Anmeldungen zu akzeptieren.", - "MISSING_PLAYER_ROLE": "Sie haben nicht die :ROLE Rolle erforderlich, dass die Veranstaltung zu verbinden." + "MISSING_PLAYER_ROLE": "Sie haben nicht die :ROLE Rolle erforderlich, dass die Veranstaltung zu verbinden.", + "OR": "oder" } } diff --git a/lang/en.json b/lang/en.json index 98d47a7..5fecd94 100644 --- a/lang/en.json +++ b/lang/en.json @@ -48,6 +48,7 @@ "TIME_ZONE": "Time Zone", "TODAY": "Today", "GAME_IMAGE": "Game Image", + "THUMBNAIL": "Thumbnail", "GAME_IMAGE_DESC": "Will default to GM's Discord avatar", "REMINDER": "Reminder", "REMINDER_FOR": "Reminder for", @@ -75,7 +76,11 @@ "GAME_OPTIONS": "Game Options", "DISABLE_WAITLIST": "Disable Waitlist", "UPLOAD_GAME_IMAGE": "Upload Game Image", + "UPLOAD_THUMBNAIL": "Upload Thumbnail", "MAX_UPLOAD_SIZE": "Max size (5 MB)", + "DESCRIPTION_PANEL": "Description", + "RESCHEDULING_PANEL": "Rescheduling", + "EXTRA_PANEL": "Extra Options", "options": { "AUTOMATED_SIGNUP": "Automated Signup", "CUSTOM_SIGNUP_INSTRUCTIONS": "Custom Signup Instructions", @@ -220,6 +225,7 @@ "EDIT_PERMISSION": "You do not have permission to edit this game", "ALREADY_STARTED": "That game has already started.", "MAX_NO_WAITLIST": "That game is full and not accepting additional signups.", - "MISSING_PLAYER_ROLE": "You don't have the :ROLE role required to join that event." + "MISSING_PLAYER_ROLE": "You don't have the :ROLE role required to join that event.", + "OR": "or" } } \ No newline at end of file diff --git a/lang/es.json b/lang/es.json index 285fb92..8481975 100644 --- a/lang/es.json +++ b/lang/es.json @@ -48,6 +48,7 @@ "TIME_ZONE": "Zona Horaria", "TODAY": "Hoy", "GAME_IMAGE": "Imagen del Juego", + "THUMBNAIL": "Miniatura", "GAME_IMAGE_DESC": "Descripcion de la imagen del juego GM's Discord avatar", "REMINDER": "Recordatorio", "REMINDER_FOR": "Recordatorio para", @@ -56,10 +57,10 @@ "RESERVED_SLOTS": "Plazas reservadas (1 Jugador por linea)", "RESERVED": "Reservado", "WAITLISTED": "Lista Espera", - "DESCRIPTION": "Descripcion", + "DESCRIPTION": "Descripción", "EDIT_LINK": "Puedes editar tu `:SERVER_NAME` - `:GAME_NAME` aqui mismo:", "MAX": "Maximo", - "CHARACTERS": "caracteres", + "CHARACTERS": "Caracteres", "NO_PLAYERS": "Sin Jugadores", "NEXT_DATE": "Siguiente Fecha en el calendario", "WHEN_REPEAT": "Cuando se Repite este evento", @@ -75,7 +76,11 @@ "GAME_OPTIONS": "Opciones de juego", "DISABLE_WAITLIST": "Desactivar la Lista de Espera", "UPLOAD_GAME_IMAGE": "Subir imagen juego", + "UPLOAD_THUMBNAIL": "Subir Imagen en miniatura", "MAX_UPLOAD_SIZE": "Tamaño máximo (5 MB)", + "DESCRIPTION_PANEL": "Descripción", + "RESCHEDULING_PANEL": "Reprogramación", + "EXTRA_PANEL": "Opciones Adicionales", "options": { "AUTOMATED_SIGNUP": "Registrarse Automaticamente", "CUSTOM_SIGNUP_INSTRUCTIONS": "Instrucciones para unirse al evento", @@ -220,6 +225,7 @@ "EDIT_PERMISSION": "No tienes permiso para editar este juego", "ALREADY_STARTED": "¡Ese juego ya ha comenzado!", "MAX_NO_WAITLIST": "Ese juego está lleno y no aceptar suscripciones adicionales.", - "MISSING_PLAYER_ROLE": "Usted no tiene el papel :ROLE requerida para unirse a ese evento." + "MISSING_PLAYER_ROLE": "Usted no tiene el papel :ROLE requerida para unirse a ese evento.", + "OR": "o" } } diff --git a/lang/fr.json b/lang/fr.json index 466c75a..4dc53ec 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -48,6 +48,7 @@ "TIME_ZONE": "Fuseau horaire", "TODAY": "Aujourd'hui", "GAME_IMAGE": "Image du jeu", + "THUMBNAIL": "La vignette", "GAME_IMAGE_DESC": "Par défaut, l'avatar Discord de GM", "REMINDER": "Rappel", "REMINDER_FOR": "Rappel pour", @@ -75,7 +76,11 @@ "GAME_OPTIONS": "Options de jeu", "DISABLE_WAITLIST": "Désactiver la liste d'attente", "UPLOAD_GAME_IMAGE": "Télécharger Image du jeu", + "UPLOAD_THUMBNAIL": "Télécharger Vignette", "MAX_UPLOAD_SIZE": "Taille maximale (10 Mo)", + "DESCRIPTION_PANEL": "Description", + "RESCHEDULING_PANEL": "Rééchelonnement", + "EXTRA_PANEL": "Options Supplémentaires", "options": { "AUTOMATED_SIGNUP": "Inscription automatisée", "CUSTOM_SIGNUP_INSTRUCTIONS": "Instructions d'inscription personnalisées", @@ -220,6 +225,7 @@ "EDIT_PERMISSION": "Vous n'êtes pas autorisé à modifier ce jeu", "ALREADY_STARTED": "Ce jeu a déjà commencé!", "MAX_NO_WAITLIST": "Ce jeu est plein et ne pas accepter des inscriptions supplémentaires.", - "MISSING_PLAYER_ROLE": "Vous n'avez pas le rôle :ROLE nécessaire pour se joindre à cet événement." + "MISSING_PLAYER_ROLE": "Vous n'avez pas le rôle :ROLE nécessaire pour se joindre à cet événement.", + "OR": "ou" } } \ No newline at end of file diff --git a/lang/it.json b/lang/it.json index fb2db32..be169a1 100644 --- a/lang/it.json +++ b/lang/it.json @@ -46,6 +46,7 @@ "TIME_ZONE": "Time-zone", "TODAY": "Today", "GAME_IMAGE": "Immagine del gioco", + "THUMBNAIL": "Miniatura", "GAME_IMAGE_DESC": "L'impostazione predefinita è Avatar Discord di GM", "REMINDER": "Promemoria", "REMINDER_FOR": "Promemoria per", @@ -73,7 +74,11 @@ "GAME_OPTIONS": "Opzioni di gioco", "DISABLE_WAITLIST": "Disabilitare lista d'attesa", "UPLOAD_GAME_IMAGE": "Carica Gioco Immagine", + "UPLOAD_THUMBNAIL": "Carica Miniatura", "MAX_UPLOAD_SIZE": "Dimensione massima (5 MB)", + "DESCRIPTION_PANEL": "Descrizione", + "RESCHEDULING_PANEL": "Ripianificazione", + "EXTRA_PANEL": "Opzioni Extra", "options": { "AUTOMATED_SIGNUP": "Entrata automatica", "CUSTOM_SIGNUP_INSTRUCTIONS": "Istruzioni di iscrizione personalizzate", @@ -218,6 +223,7 @@ "EDIT_PERMISSION": "Non sei autorizzato a modificare questo gioco", "ALREADY_STARTED": "Quel gioco è già iniziato!", "MAX_NO_WAITLIST": "Questo gioco è pieno e non accettare iscrizioni supplementari.", - "MISSING_PLAYER_ROLE": "Non è necessario il ruolo :ROLE necessaria a raggiungere il detto evento." + "MISSING_PLAYER_ROLE": "Non è necessario il ruolo :ROLE necessaria a raggiungere il detto evento.", + "OR": "o" } } diff --git a/lang/pt-br.json b/lang/pt-br.json index 2a9d9e6..5ed3f6f 100644 --- a/lang/pt-br.json +++ b/lang/pt-br.json @@ -48,6 +48,7 @@ "TIME_ZONE": "Fuso Horário", "TODAY": "Hoje", "GAME_IMAGE": "Imagem do Jogo", + "THUMBNAIL": "Miniatura", "GAME_IMAGE_DESC": "Como padrão, é o avatar do Discord do GM", "REMINDER": "Lembrete", "REMINDER_FOR": "Lembrete para", @@ -75,7 +76,11 @@ "GAME_OPTIONS": "Opções de jogo", "DISABLE_WAITLIST": "Desativar Lista de Espera", "UPLOAD_GAME_IMAGE": "Imagem Carregar Jogo", + "UPLOAD_THUMBNAIL": "Imagem Carregar Miniatura", "MAX_UPLOAD_SIZE": "Tamanho máximo (5 MB)", + "DESCRIPTION_PANEL": "Descrição", + "RESCHEDULING_PANEL": "Reagendar", + "EXTRA_PANEL": "Opções Extras", "options": { "AUTOMATED_SIGNUP": "Inscrição Automática", "CUSTOM_SIGNUP_INSTRUCTIONS": "Instruções Customizadas para Inscrição", @@ -220,6 +225,7 @@ "EDIT_PERMISSION": "Você não tem permissão para editar este jogo", "ALREADY_STARTED": "Esse jogo já começou.", "MAX_NO_WAITLIST": "Esse jogo está cheio e não aceitar inscrições adicionais.", - "MISSING_PLAYER_ROLE": "Você não tem o papel :ROLE obrigados a participar desse evento." + "MISSING_PLAYER_ROLE": "Você não tem o papel :ROLE obrigados a participar desse evento.", + "OR": "ou" } } \ No newline at end of file diff --git a/lang/ru.json b/lang/ru.json index 2ce7d91..e08e24e 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -48,6 +48,7 @@ "TIME_ZONE": "Часовой пояс", "TODAY": "Сегодня", "GAME_IMAGE": "Изображение игры", + "THUMBNAIL": "Thumbnail", "GAME_IMAGE_DESC": "По умолчанию аватар GM'а в Discord", "REMINDER": "Напоминание", "REMINDER_FOR": "Напоминание для", @@ -75,7 +76,11 @@ "GAME_OPTIONS": "Варианты игры", "DISABLE_WAITLIST": "Отключить листа ожидания", "UPLOAD_GAME_IMAGE": "Загрузить игру Изображение", + "UPLOAD_THUMBNAIL": "Загрузить изображение Thumbnail", "MAX_UPLOAD_SIZE": "Максимальный размер (5 MB)", + "DESCRIPTION_PANEL": "Описание", + "RESCHEDULING_PANEL": "Перепланирование", + "EXTRA_PANEL": "Дополнительные опции", "options": { "AUTOMATED_SIGNUP": "Автоматическая запись", "CUSTOM_SIGNUP_INSTRUCTIONS": "Свои условия записи", @@ -220,6 +225,7 @@ "EDIT_PERMISSION": "У вас нет разрешения на редактирование этой игры", "ALREADY_STARTED": "Эта игра уже началась!", "MAX_NO_WAITLIST": "Эта игра полна и не принимать дополнительные подписки.", - "MISSING_PLAYER_ROLE": "У вас нет роли :ROLE должны присоединиться к этому событию." + "MISSING_PLAYER_ROLE": "У вас нет роли :ROLE должны присоединиться к этому событию.", + "OR": "или" } } diff --git a/src/models/game.ts b/src/models/game.ts index f0ae365..7873995 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -1365,9 +1365,16 @@ export class Game implements GameModel { } const template = guildConfig.gameTemplates.find(t => t.id.toString() === this.template) || guildConfig.gameTemplates.find(t => t.isDefault); - if (template && template.playerRole && !member.roles.find(r => isObject(template.playerRole) ? r.id === template.playerRole.id : r.name === template.playerRole)) { - if (member) member.send(lang.other.MISSING_PLAYER_ROLE.replace(/\:ROLE/g, `\`${isObject(template.playerRole) ? template.playerRole.name : template.playerRole}\``)); - return { result: false, message: lang.other.MISSING_PLAYER_ROLE.replace(/\:ROLE/g, `\`${isObject(template.playerRole) ? template.playerRole.name : template.playerRole}\``) }; + if (template && template.playerRole && template.playerRole.length > 0) { + if (!template.playerRole.find(pr => member.roles.find(r => r.id === pr.id || (!pr.id && r.name === pr.name)))) { + const separator = "`, `" + const pattern = new RegExp('(\\b' + separator + '\\b)(?!.*\\b\\1\\b)', 'i'); + let message = template.playerRole.map(pr => pr.name).join(separator); + if (message.indexOf(separator) === message.lastIndexOf(separator)) message = message.replace(separator, "` "+lang.other.OR+" `"); + else message = message.replace(pattern, "`, "+lang.other.OR+" `"); + if (member) member.send(lang.other.MISSING_PLAYER_ROLE.replace(/\:ROLE/g, `\`${message}\``)); + return { result: false, message: lang.other.MISSING_PLAYER_ROLE.replace(/\:ROLE/g, `\`${message}\``) }; + } } let match = await GameRSVP.fetchRSVP(this._id, user.id); diff --git a/src/models/guild-config.ts b/src/models/guild-config.ts index bc69248..b67965a 100644 --- a/src/models/guild-config.ts +++ b/src/models/guild-config.ts @@ -32,7 +32,7 @@ export interface GameTemplate { name: string; isDefault: boolean; role?: string | ConfigRole; - playerRole?: string | ConfigRole; + playerRole?: ConfigRole[]; embedColor?: string; gameDefaults?: GameDefaults; } @@ -140,6 +140,8 @@ export class GuildConfig implements GuildConfigDataModel { } else if (key === "gameTemplates") { this[key] = (value || []).map((gt) => { gt.embedColor = aux.colorFixer(gt.embedColor); + if (gt.playerRole && !Array.isArray(gt.playerRole)) gt.playerRole = [ isObject(gt.playerRole) ? gt.playerRole : { name: gt.playerRole } ]; + else if (!gt.playerRole) gt.playerRole = []; return gt; }); } else if (key === "embedColor") { diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 0c38c53..148f9e6 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -180,7 +180,10 @@ if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { if (isObject(guildConfig.managerRole) ? guildConfig.managerRole.id == oldR.id : guildConfig.managerRole == oldR.name) guildConfig.managerRole = { id: role.id, name: role.name }; guildConfig.gameTemplates = guildConfig.gameTemplates.map(gt => { if (isObject(gt.role) ? gt.role.id === oldR.id : gt.role == oldR.name) gt.role = { id: role.id, name: role.name }; - if (isObject(gt.playerRole) ? gt.playerRole.id === oldR.id : gt.playerRole == oldR.name) gt.playerRole = { id: role.id, name: role.name }; + gt.playerRole = gt.playerRole.map(pr => { + if (pr.id === oldR.id || (!pr.id && pr.name === oldR.name)) pr = { id: role.id, name: role.name }; + return pr; + }); return gt; }); await guildConfig.save(); diff --git a/src/routes/info.ts b/src/routes/info.ts deleted file mode 100644 index 8da75e2..0000000 --- a/src/routes/info.ts +++ /dev/null @@ -1,20 +0,0 @@ -import express, { request } from "express"; - -import config from "../models/config"; -import aux from "../appaux"; - -export default () => { - const router = express.Router(); - - router.use(config.urls.about.path, async (req: any, res: any, next) => { - const pledges = await aux.patreonPledges(); - const data: any = { - title: 'About RPG Schedule', - pledges: pledges.status === "success" ? pledges.data.filter(p => p.reward.id === config.patreon.creditPledge ) : [] - }; - - res.render("about", data); - }); - - return router; -}; diff --git a/src/routes/init.ts b/src/routes/init.ts deleted file mode 100644 index fcd9028..0000000 --- a/src/routes/init.ts +++ /dev/null @@ -1,326 +0,0 @@ -import { Client, Permissions, GuildMember, GuildChannel, Collection, RoleManager } from "discord.js"; -import express from "express"; -import request from "request"; -import moment from "moment"; -import merge from "lodash/merge"; -import cloneDeep from "lodash/cloneDeep"; - -import { Game } from "../models/game"; -import { GuildConfig } from "../models/guild-config"; -import config from "../models/config"; -import aux from "../appaux"; -import db from "../db"; - -const connection = db.connection; - -interface AccountGuild { - id: string; - name: string; - icon: string; - permission: boolean; - isAdmin: boolean; - member: GuildMember; - roles: RoleManager; - channels: GuildChannel[]; - announcementChannels: GuildChannel[]; - config: GuildConfig; - games: Game[]; -} - -export default (options: any) => { - const router = express.Router(); - const client: Client = options.client; - - router.use("/", async (req: any, res, next) => { - const langs = req.app.locals.langs; - const selectedLang = req.cookies.lang && langs.map((l) => l.code).includes(req.cookies.lang) ? req.cookies.lang : "en"; - - req.lang = merge(cloneDeep(langs.find((lang: any) => lang.code === "en")), cloneDeep(langs.find((lang: any) => lang.code === selectedLang))); - - res.locals.lang = req.lang; - res.locals.urlPath = req._parsedOriginalUrl.pathname; - res.locals.url = req.originalUrl; - res.locals.env = process.env; - - moment.locale(req.lang.code); - - req.account = { - viewing: { - home: res.locals.urlPath === config.urls.base.path, - games: res.locals.urlPath === config.urls.game.games.path, - dashboard: res.locals.urlPath === config.urls.game.dashboard.path, - server: res.locals.urlPath === config.urls.game.server.path, - calendar: res.locals.urlPath === config.urls.game.calendar.path, - game: res.locals.urlPath === config.urls.game.create.path, - about: res.locals.urlPath === config.urls.about.path, - }, - guilds: [], - user: null, - permissions: Permissions.FLAGS, - }; - - const parsedURLs = aux.parseConfigURLs(config.urls); - res.locals.account = req.account; - - try { - // console.log(req.session.id, req.session.status); - const storedSession = await connection().collection("sessions").findOne({ _id: req.session.id }); - if (storedSession) { - req.session.status = storedSession.session.status; - } - - if (req.session.status) { - const access = req.session.status.access; - if (access.token_type) { - // Refresh token - const requestData = { - url: "https://discordapp.com/api/v6/oauth2/token", - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - form: { - client_id: process.env.CLIENT_ID, - client_secret: process.env.CLIENT_SECRET, - grant_type: "refresh_token", - refresh_token: access.refresh_token, - redirect_uri: process.env.HOST + config.urls.login.path, - scope: "identify guilds", - }, - }; - - // if (!req.session.status.lastRefreshed || req.session.status.lastRefreshed + 300 < moment().unix()) { - request(requestData, - function(error, response, body) { - if (error || response.statusCode !== 200) { - if (response.statusCode == 400) res.redirect(config.urls.login.path); - else res.render("error", { message: `Discord OAuth: ${response.statusCode}
${error}` }); - return; - } - - const token = JSON.parse(body); - req.session.status = { - ...config.defaults.sessionStatus, - ...req.session.status, - ...{ - lastRefreshed: moment().unix() - } - }; - req.session.status.access = token; - delete req.session.redirect; - init(req, res, next, token); - } - ); - // } else { - // init(req, res, next, access); - // } - } else { - if (!parsedURLs.find((path) => path.session && res.locals.urlPath === path.path)) next(); - else res.redirect(config.urls.login.path + "?redirect=" + escape(req.originalUrl)); - } - } else { - if (!parsedURLs.find((path) => path.session && res.locals.urlPath === path.path)) next(); - else res.redirect(config.urls.login.path + "?redirect=" + escape(req.originalUrl)); - } - } catch (e) { - res.render("error", { message: "init.ts:2:
" + e.message }); - } - }); - - router.use(config.urls.changeLang.path, (req, res, next) => { - res.cookie("lang", req.params.newLang).redirect(req.query.returnTo); - }); - - const init = async (req: any, res: express.Response, next: express.NextFunction, token: any) => { - const parsedURLs = aux.parseConfigURLs(config.urls); - if (!parsedURLs.find((path) => path.session && res.locals.urlPath === path.path) && !(token && token.access_token)) { - next(); - return; - } - - const guildPermission = parsedURLs.find((path) => path.guildPermission && res.locals.urlPath === path.path) ? true : false; - const loadGames = parsedURLs.find((path) => path.loadGames && res.locals.urlPath === path.path) ? true : false; - - const requestData = { - url: "https://discordapp.com/api/users/@me", - method: "GET", - headers: { - authorization: `${token.token_type} ${token.access_token}` - } - }; - - request(requestData, async (error, response, body) => { - try { - console.log('init.ts', requestData, response.statusCode) - if (!error && response.statusCode === 200) { - const response = JSON.parse(body); - const { username, discriminator, id, avatar } = response; - const tag = `${username}#${discriminator}`; - // const guildConfigs = await GuildConfig.fetchAll(); - req.account.user = { - ...response, - ...{ - tag: tag, - avatarURL: `https://cdn.discordapp.com/avatars/${id}/${avatar}.png?size=128` - } - }; - - const guildIds = []; - - client.guilds.cache.forEach(guild => { - guild.members.cache.forEach(member => { - if (member.id === id) { - guildIds.push(guild.id); - req.account.guilds.push({ - id: guild.id, - name: guild.name, - icon: guild.iconURL, - permission: false, - member: member, - isAdmin: false, - channels: guild.channels, - config: new GuildConfig({ guild: guild.id }), - games: [] - }); - } - }); - }); - - const guildConfigs = await GuildConfig.fetchAllBy({ - guild: { - $in: guildIds - } - }); - - // req.account.guilds = req.account.guilds.map(guild => { - // const guildConfig = guildConfigs.find(gc => gc.guild === guild.id) || new GuildConfig({ guild: guild.id }); - // const member: GuildMember = guild.member; - // guild.permission = guildConfig.role ? member.roles.cache.find(r => r.name.toLowerCase().trim() === (guildConfig.role || "").toLowerCase().trim()) : true; - // guild.isAdmin = - // member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || - // member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || - // member.roles.cache.find(r => r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()); - // guild.config = guildConfig; - // return guild; - // }); - - // Manage Server Page - if (req.account.viewing.server) { - req.account.guilds = req.account.guilds.filter(g => req.account.guilds.find(s => s.id === g.id && (s.isAdmin || config.author == tag))); - } - - // Page requires permission to post games and guild is not hidden - if (guildPermission) { - req.account.guilds = req.account.guilds.filter(guild => !guild.config.hidden || config.author == tag); - } - - if (loadGames) { - const gameOptions: any = { - s: { - $in: req.account.guilds.reduce((i, g) => { - i.push(g.id); - return i; - }, []) - } - }; - - // My Games Page - if (req.account.viewing.dashboard) { - gameOptions.$or = [ - { - dm: tag - }, - { - reserved: { - $regex: tag.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") - } - } - ]; - } - - // Upcoming Games Page - if (req.account.viewing.games) { - gameOptions.timestamp = { - $gt: new Date().getTime() - }; - if (tag !== config.author) { - gameOptions.dm = { - $ne: tag - }; - } - } - - const games: any[] = await Game.fetchAllBy(gameOptions); - games.forEach(game => { - if (!game.discordGuild) return; - - const date = Game.ISOGameDate(game); - game.moment = { - raw: `${game.date} ${game.time} UTC${game.timezone < 0 ? "-" : "+"}${Math.abs(game.timezone)}`, - isoutc: `${new Date(`${game.date} ${game.time} UTC${game.timezone < 0 ? "-" : "+"}${Math.abs(game.timezone)}`) - .toISOString() - .replace(/[^0-9T]/gi, "") - .slice(0, 13)}00Z`, - iso: date, - date: moment(date) - .utcOffset(parseInt(game.timezone)) - .format(config.formats.dateLong), - calendar: moment(date) - .utcOffset(parseInt(game.timezone)) - .calendar(), - from: moment(date) - .utcOffset(parseInt(game.timezone)) - .fromNow() - }; - - game.slot = game.reserved.split(/\r?\n/).findIndex(t => t.trim().replace("@", "") === tag) + 1; - game.signedup = game.slot > 0 && game.slot <= parseInt(game.players); - game.waitlisted = game.slot > parseInt(game.players); - - const gi = req.account.guilds.findIndex(g => g.id === game.s); - req.account.guilds[gi].games.push(game); - }); - } - - if (req.account.viewing.games || req.account.viewing.dashboard) { - req.account.guilds = req.account.guilds.map(guild => { - guild.games.sort((a, b) => { - return a.timestamp < b.timestamp ? -1 : 1; - }); - return guild; - }); - - req.account.guilds.sort((a, b) => { - if (a.games.length === 0 && b.games.length === 0) return a.name < b.name ? -1 : 1; - if (a.games.length === 0) return 1; - if (b.games.length === 0) return -1; - - return a.games[0].timestamp < b.games[0].timestamp ? -1 : 1; - }); - } - - if (req.account.viewing.home) { - res.redirect(config.urls.game.dashboard.path); - return; - } - - res.locals.account = req.account; - - next(); - return; - } - throw new Error(error); - } catch (err) { - if (req.account.viewing.dashboard) { - res.locals.account = req.account; - res.render("error", { message: "init.ts:253:
" + err }); - } else { - res.locals.account = req.account; - next(); - } - } - }); - }; - - return router; -}; diff --git a/src/routes/login.ts b/src/routes/login.ts deleted file mode 100644 index 5e0100c..0000000 --- a/src/routes/login.ts +++ /dev/null @@ -1,63 +0,0 @@ -import express from "express"; -import request from "request"; - -import config from "../models/config"; -import moment from "moment"; - -export default () => { - const router = express.Router(); - - router.use(config.urls.login.path, (req, res, next) => { - if (req.query.code) { - const requestData = { - url: "https://discordapp.com/api/v6/oauth2/token", - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded" - }, - form: { - client_id: process.env.CLIENT_ID, - client_secret: process.env.CLIENT_SECRET, - grant_type: "authorization_code", - code: req.query.code, - redirect_uri: process.env.HOST + config.urls.login.path, - scope: "identify guilds" - } - }; - - request(requestData, function(error, response, body) { - if (error || response.statusCode !== 200) { - console.log(requestData, response.statusCode); - return res.render("error", { message: `Discord OAuth: ${response.statusCode}${error ? `
${error}` : '' }` }); - } - - const token = JSON.parse(body); - req.session.status = { - ...config.defaults.sessionStatus, - ...req.session.status, - ...{ - lastRefreshed: moment().unix() - } - }; - req.session.status.access = token; - res.redirect(req.session.redirect || config.urls.game.games.path); - delete req.session.redirect; - }); - } else if (req.query.error) { - res.redirect("/"); - } else { - delete req.session.redirect; - if (req.query.redirect) { - req.session.redirect = req.query.redirect; - } - res.redirect(process.env.AUTH_URL); - } - }); - - router.use(config.urls.logout.path, (req, res, next) => { - req.session.status = config.defaults.sessionStatus; - res.redirect("/"); - }); - - return router; -}; From a7df48354136fb591939b5cdb301a22df61306df Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Sat, 3 Oct 2020 18:20:48 -0500 Subject: [PATCH 48/53] Pruning Improvements --- src/processes/discord.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 148f9e6..96813b8 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -1475,8 +1475,12 @@ const pruneOldGames = async (guild?: Guild) => { } if (clientMessages.length === 0) continue; try { - const deleted = await c.bulkDelete(clientMessages); + const deleted = await c.bulkDelete(clientMessages, true); count += deleted.size; + clientMessages.filter(cm => !deleted.find(d => d.id === cm.id)).forEach(cm => { + cm.delete({ reason: "Automated pruning..." }); + count++; + }); } catch (err) { console.log("AutomatedPruningError:", err); } From 8de8aee72e0d1fb801ee3a78fb3aeb0aab2dfa7c Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Sun, 4 Oct 2020 08:29:32 -0500 Subject: [PATCH 49/53] Pruning improvements and code formatting --- src/processes/discord.ts | 99 ++++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 49 deletions(-) diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 96813b8..a033e59 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -177,10 +177,11 @@ if (process.env.DISCORD_API_LOGIC.toLowerCase() === "true") { const guildConfig = await GuildConfig.fetch(oldR.guild.id); if (isObject(guildConfig.role) ? guildConfig.role.id == oldR.id : guildConfig.role == oldR.name) guildConfig.role = { id: role.id, name: role.name }; - if (isObject(guildConfig.managerRole) ? guildConfig.managerRole.id == oldR.id : guildConfig.managerRole == oldR.name) guildConfig.managerRole = { id: role.id, name: role.name }; - guildConfig.gameTemplates = guildConfig.gameTemplates.map(gt => { + if (isObject(guildConfig.managerRole) ? guildConfig.managerRole.id == oldR.id : guildConfig.managerRole == oldR.name) + guildConfig.managerRole = { id: role.id, name: role.name }; + guildConfig.gameTemplates = guildConfig.gameTemplates.map((gt) => { if (isObject(gt.role) ? gt.role.id === oldR.id : gt.role == oldR.name) gt.role = { id: role.id, name: role.name }; - gt.playerRole = gt.playerRole.map(pr => { + gt.playerRole = gt.playerRole.map((pr) => { if (pr.id === oldR.id || (!pr.id && pr.name === oldR.name)) pr = { id: role.id, name: role.name }; return pr; }); @@ -379,7 +380,9 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { isAdmin = !!( member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || - member.roles.cache.find((r) => isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) + member.roles.cache.find((r) => + isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim() + ) ); permission = guildConfig.memberHasPermission(member) || isAdmin; } @@ -511,9 +514,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { !addedChannel.permissionsFor(client.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", ].filter((check) => check); if (missingPermissions.length > 0) { - message.channel.send( - responseEmbed(`The bot is missing the following permissions in ${addedChannel.toString()}: ${missingPermissions.join(", ")}`) - ); + message.channel.send(responseEmbed(`The bot is missing the following permissions in ${addedChannel.toString()}: ${missingPermissions.join(", ")}`)); } } }) @@ -872,7 +873,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { const mentioned = (params[0] || "").match(/(\d+)/); let roleObj: ConfigRole = { id: null, - name: params.join(" ") + name: params.join(" "), }; if (roleObj.name.trim() === "All Roles") { roleObj.name = ""; @@ -904,7 +905,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { const mentioned = (params[0] || "").match(/(\d+)/); let roleObj: ConfigRole = { id: null, - name: params.join(" ") + name: params.join(" "), }; if (mentioned) { const roleId = mentioned[0]; @@ -955,7 +956,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } } else if (cmd === "reboot" && member.user.tag === config.author) { await client.shard.send({ - type: "reboot" + type: "reboot", }); } else if (cmd === "bot-permissions" && (isAdmin || member.user.tag === config.author)) { let channelId = message.channel.id; @@ -982,15 +983,13 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { const response = await cmdFetchEvents(message.guild, params.join(" "), lang); if (response) { if (Array.isArray(response)) { - response.forEach(r => { + response.forEach((r) => { message.author.send(r); }); + } else { + message.author.send(response); } - else { - message.author.send(response) - } - } - else message.author.send(lang.config.EVENTS_NONE); + } else message.author.send(lang.config.EVENTS_NONE); message.delete(); } else { const response = await message.channel.send("Command not recognized"); @@ -1033,12 +1032,11 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { if (response) count++; if (response) { if (Array.isArray(response)) { - response.forEach(r => { + response.forEach((r) => { message.author.send(r); }); - } - else { - message.author.send(response) + } else { + message.author.send(response); } } } @@ -1103,7 +1101,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { }, deleted: { $in: [false, null], - } + }, }, client ); @@ -1306,11 +1304,12 @@ const rescheduleOldGames = async (guildId?: string) => { const pruneOldGames = async (guild?: Guild) => { let pruned: UpdateWriteOpResult; + const day = 24 * 3600 * 1000; try { aux.log(`Pruning old games for ${guild ? `${guild.name} server` : "all servers"}`); const query: FilterQuery = { timestamp: { - $lt: new Date().getTime() - 48 * 3600 * 1000, + $lt: new Date().getTime() - 2 * day, }, frequency: "0", hideDate: { @@ -1339,6 +1338,10 @@ const pruneOldGames = async (guild?: Guild) => { page++; let games = await Game.fetchAllBy(query, client); + games.sort((a, b) => { + return a.s > b.s ? 1 : -1; + }); + const guildConfigs = await GuildConfig.fetchAllBy({ guild: guild ? guild.id @@ -1346,10 +1349,10 @@ const pruneOldGames = async (guild?: Guild) => { $in: games.map((g) => g.s).filter((s, i, arr) => arr.indexOf(s) === i), }, }); - const gameChannelMessages: { guild: string; channel: string; message: string }[] = []; + const prunedIds = []; const deletedIds = []; - const prunedMessageIds = []; + for (let i = 0; i < games.length; i++) { let game = games[i]; if (!game.discordGuild) continue; @@ -1359,10 +1362,14 @@ const pruneOldGames = async (guild?: Guild) => { const guildConfig = guildConfigs.find((gc) => gc.guild === game.s) || new GuildConfig(); if (!guildConfig) continue; - if (!game.pruned && game.discordChannel && new Date().getTime() - game.timestamp >= guildConfig.pruneIntDiscord * 24 * 3600 * 1000) { + if (!game.pruned && game.discordChannel && new Date().getTime() - game.timestamp >= guildConfig.pruneIntDiscord * day) { if (game.messageId) { - if (guildConfig.pruning) prunedMessageIds.push(game.messageId); - if (guildConfig.pruneIntDiscord < guildConfig.pruneIntEvents && new Date().getTime() - game.timestamp < guildConfig.pruneIntEvents * 24 * 3600 * 1000) { + if (guildConfig.pruning) { + const message = await game.discordChannel.messages.fetch(game.messageId); + if (message.deletable) message.delete(); + } + + if (guildConfig.pruneIntDiscord < guildConfig.pruneIntEvents && new Date().getTime() - game.timestamp < guildConfig.pruneIntEvents * day) { prunedIds.push(game._id); client.shard.send({ type: "socket", @@ -1377,12 +1384,13 @@ const pruneOldGames = async (guild?: Guild) => { data: { action: "deleted", gameId: game._id }, }); } - if (guildConfig.pruning) gameChannelMessages.push({ guild: game.s, channel: game.c, message: game.messageId }); } + if (game.reminderMessageId) { - if (guildConfig.pruning) gameChannelMessages.push({ guild: game.s, channel: game.c, message: game.messageId }); + const message = await game.discordChannel.messages.fetch(game.reminderMessageId); + if (message.deletable) message.delete(); } - } else if (new Date().getTime() - game.timestamp >= guildConfig.pruneIntEvents * 24 * 3600 * 1000) { + } else if (new Date().getTime() - game.timestamp >= guildConfig.pruneIntEvents * day) { deletedIds.push(game._id); client.shard.send({ type: "socket", @@ -1397,7 +1405,6 @@ const pruneOldGames = async (guild?: Guild) => { pruned = await Game.updateAllBy( { - ...query, pruned: { $in: [false, null], }, @@ -1414,7 +1421,6 @@ const pruneOldGames = async (guild?: Guild) => { if (pruned.modifiedCount > 0) aux.log(`${pruned.modifiedCount} old game(s) pruned from Discord only`); pruned = await Game.softDeleteAllBy({ - ...query, _id: { $in: deletedIds.map((pid) => new ObjectId(pid)), }, @@ -1434,7 +1440,7 @@ const pruneOldGames = async (guild?: Guild) => { }, ], timestamp: { - $lt: new Date().getTime() - 14 * 24 * 3600 * 1000, + $lt: new Date().getTime() - 14 * day, }, }, client @@ -1446,7 +1452,9 @@ const pruneOldGames = async (guild?: Guild) => { for (let gci = 0; gci < guildConfigs.length; gci++) { const gc = guildConfigs[gci]; const guild = client.guilds.cache.find((g) => g.id === gc.guild); - const channels = <(TextChannel | NewsChannel)[]>guild.channels.cache.array().filter((c) => gc.channels.find((gcc) => gcc.channelId === c.id) && (c instanceof TextChannel || c instanceof NewsChannel)); + const channels = <(TextChannel | NewsChannel)[]>( + guild.channels.cache.array().filter((c) => gc.channels.find((gcc) => gcc.channelId === c.id) && (c instanceof TextChannel || c instanceof NewsChannel)) + ); for (let ci = 0; ci < channels.length; ci++) { const c = channels[ci]; @@ -1460,27 +1468,20 @@ const pruneOldGames = async (guild?: Guild) => { m.author.id === client.user.id && m.deletable && !m.deleted && - (m.embeds.filter( - (e) => new Date().getTime() - m.createdTimestamp >= 14 * 24 * 3600 * 1000 && new Date().getTime() - e.timestamp >= gc.pruneIntDiscord * 24 * 3600 * 1000 - ).length > 0 || - prunedMessageIds.includes(m.id)) + (new Date().getTime() - m.createdTimestamp >= 14 * day || new Date().getTime() - m.createdTimestamp >= gc.pruneIntDiscord * day) ); - for (let i = 0; i < gameChannelMessages.length; i++) { - const msg = gameChannelMessages[i]; - if (guild.id === msg.guild && c.id === msg.channel && !clientMessages.find((cm) => cm.id === msg.message)) { - const chm = await c.messages.resolve(msg.message); - if (chm) clientMessages.push(chm); - } - } if (clientMessages.length === 0) continue; + try { const deleted = await c.bulkDelete(clientMessages, true); count += deleted.size; - clientMessages.filter(cm => !deleted.find(d => d.id === cm.id)).forEach(cm => { - cm.delete({ reason: "Automated pruning..." }); - count++; - }); + clientMessages + .filter((cm) => !deleted.find((d) => d.id === cm.id)) + .forEach((cm) => { + cm.delete({ reason: "Automated pruning..." }); + count++; + }); } catch (err) { console.log("AutomatedPruningError:", err); } From f916d3d4b5b92a9f7861fca9da89e4a9d48b8db9 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Sun, 4 Oct 2020 18:51:38 -0500 Subject: [PATCH 50/53] Socket improvements --- src/processes/discord.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/processes/discord.ts b/src/processes/discord.ts index a033e59..469efcd 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -1374,14 +1374,14 @@ const pruneOldGames = async (guild?: Guild) => { client.shard.send({ type: "socket", name: "game", - data: { action: "pruned", gameId: game._id }, + data: { action: "pruned", gameId: game._id, room: `g-${game.s}` }, }); } else { deletedIds.push(game._id); client.shard.send({ type: "socket", name: "game", - data: { action: "deleted", gameId: game._id }, + data: { action: "deleted", gameId: game._id, room: `g-${game.s}` }, }); } } @@ -1395,7 +1395,7 @@ const pruneOldGames = async (guild?: Guild) => { client.shard.send({ type: "socket", name: "game", - data: { action: "deleted", gameId: game._id }, + data: { action: "deleted", gameId: game._id, room: `g-${game.s}` }, }); } } catch (err) { From 9065653a4d26678613031b87c085875b551890a9 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Thu, 22 Oct 2020 09:34:14 -0500 Subject: [PATCH 51/53] Development --- src/appaux.ts | 52 ++++++++++------ src/models/game.ts | 30 ++++++--- src/processes/discord.ts | 109 ++++++++++++++------------------- src/routes/api.ts | 128 +++++++++++++++++++++++++++------------ 4 files changed, 193 insertions(+), 126 deletions(-) diff --git a/src/appaux.ts b/src/appaux.ts index cf47afb..3e33802 100644 --- a/src/appaux.ts +++ b/src/appaux.ts @@ -22,27 +22,41 @@ const patreonPledges = async () => { const accessToken = process.env.PATREON_API_ACCESS_TOKEN; const campaignId = process.env.PATREON_CAMPAIGN_ID; try { - const url = `https://www.patreon.com/api/oauth2/api/campaigns/${campaignId}/pledges`; - const response = await axios.get(url, { - headers: { - authorization: `Bearer ${accessToken}`, - }, - }); - const data = response.data; - const rewards = data.included.filter((i) => i.type === "reward" && i.id > 0); - const pledges = data.data.filter((i) => i.type === "pledge" && i.id > 0); - const users = data.included.filter((i) => i.type === "user" && i.id > 0); const result = []; - pledges.forEach((pledge) => { - const rewardId = pledge.relationships.reward.data.id; - const reward = rewards.find((r) => r.id === rewardId); - const patronId = pledge.relationships.patron.data.id; - const user = users.find((u) => u.id === patronId); - result.push({ - patron: user, - reward: reward, + let url = `https://www.patreon.com/api/oauth2/api/campaigns/${campaignId}/pledges?page%5Bcount%5D=100`; + do { + const response = await axios.get(url, { + headers: { + authorization: `Bearer ${accessToken}`, + }, }); - }); + const data = response.data; + const rewards = data.included.filter((i) => i.type === "reward" && i.id > 0); + const pledges = data.data.filter((i) => i.type === "pledge" && i.id > 0); + const users = data.included.filter((i) => i.type === "user" && i.id > 0); + pledges.forEach((pledge) => { + if (!pledge.relationships.reward.data) return; + if (!pledge.relationships.patron.data) return; + try { + const rewardId = pledge.relationships.reward.data.id; + const reward = rewards.find((r) => r.id === rewardId); + const patronId = pledge.relationships.patron.data.id; + const user = users.find((u) => u.id === patronId); + result.push({ + patron: user, + reward: reward, + }); + } + catch(err) { + console.log(pledge.relationships.reward) + } + }); + + // Patreon API returns paginated results + // data.links.next is the URL for the next set of pledges + url = data.links.next; + } + while(url) return { status: "success", data: result, diff --git a/src/models/game.ts b/src/models/game.ts index 7873995..7dde24d 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -107,6 +107,7 @@ export interface GameModel { pastSignups: boolean; sequence: number; pruned?: boolean; + deleted?: boolean; createdTimestamp: number; updatedTimestamp: number; } @@ -120,6 +121,7 @@ interface GameSaveData { interface GameSaveOptions { force?: boolean; user?: any; + repost?: boolean; } export enum GameReminder { @@ -177,6 +179,7 @@ export class Game implements GameModel { pastSignups: boolean = false; sequence: number = 1; pruned: boolean = false; + deleted: boolean = false; createdTimestamp: number; updatedTimestamp: number; slot: number = 0; @@ -294,6 +297,7 @@ export class Game implements GameModel { pastSignups: this.pastSignups, sequence: this.sequence, pruned: this.pruned, + deleted: this.deleted, createdTimestamp: this.createdTimestamp, updatedTimestamp: this.updatedTimestamp, }; @@ -561,6 +565,11 @@ export class Game implements GameModel { const dbCollection = connection().collection(collection); if (game._id) { game.sequence++; + if (options.repost) { + game.deleted = false; + game.pruned = false; + } + const gameData = cloneDeep(game); const prev = await Game.fetch(game._id, this.client, [this._guild], false); delete gameData._id; @@ -581,19 +590,20 @@ export class Game implements GameModel { } } else embed = null; + if (channel && options.repost) { + message = await channel.send(msg, embed); + if (message) { + await dbCollection.updateOne({ _id: new ObjectId(game._id) }, { $set: { messageId: message.id } }); + game.messageId = message.id; + } + } + if (message) { if ((message.author ? message.author.id : (message).authorID) === process.env.CLIENT_ID) { if (this.client) message = await ShardManager.clientMessageEdit(this.client, guild.id, channel.id, message.id, msg, embed); else message = await ShardManager.shardMessageEdit(guild.id, channel.id, message.id, msg, embed); } } - // else if (channel && !game.messageId && !(game).deleted && !game.pruned && game.timestamp >= new Date().getTime() + parseInt(game.reminder) * 60 * 1000) { - // message = await channel.send(msg, embed); - // if (message) { - // await dbCollection.updateOne({ _id: new ObjectId(game._id) }, { $set: { messageId: message.id } }); - // game.messageId = message.id; - // } - // } else return; this.addReactions(message, guildConfig); @@ -1124,6 +1134,12 @@ export class Game implements GameModel { .updateOne({ _id: new ObjectId(_id) }, { $set: { deleted: true, frequency: Frequency.NO_REPEAT } }); } + async undelete() { + return await this.save({ + repost: true + }); + } + async delete(options: any = {}) { if (!connection()) { aux.log("No database connection"); diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 469efcd..356ff08 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -1178,29 +1178,6 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { aux.log("Database connected in shard!"); // Login the Discord bot client.login(process.env.TOKEN); - // const result = await Game.deleteAllBy({ s: "497043627432738817", adventure: "Mothership: A Carrion-Eater Company" }, client); - // console.log(result.deletedCount); - - // const games = await Game.fetchAllBy({}); - // let gameCounts = []; - // games.filter((g:any) => !g.deleted).forEach(g => { - // const index = gameCounts.findIndex(gc => gc.s === g.s && gc.adventure === g.adventure); - // if (index >= 0) { - // gameCounts[index] = { - // ...gameCounts[index], - // count: gameCounts[index].count+1 - // }; - // } - // else { - // gameCounts.push({ - // s: g.s, - // adventure: g.adventure, - // frequency: g.frequency, - // count: 1 - // }); - // } - // }); - // console.log(gameCounts.filter(gc => gc.count > 100)); } })(); @@ -1386,7 +1363,7 @@ const pruneOldGames = async (guild?: Guild) => { } } - if (game.reminderMessageId) { + if (game.reminderMessageId && guildConfig.pruning) { const message = await game.discordChannel.messages.fetch(game.reminderMessageId); if (message.deletable) message.delete(); } @@ -1403,6 +1380,8 @@ const pruneOldGames = async (guild?: Guild) => { } } + // console.log(deletedIds); return; + pruned = await Game.updateAllBy( { pruned: { @@ -1449,44 +1428,50 @@ const pruneOldGames = async (guild?: Guild) => { let count = 0; - for (let gci = 0; gci < guildConfigs.length; gci++) { - const gc = guildConfigs[gci]; - const guild = client.guilds.cache.find((g) => g.id === gc.guild); - const channels = <(TextChannel | NewsChannel)[]>( - guild.channels.cache.array().filter((c) => gc.channels.find((gcc) => gcc.channelId === c.id) && (c instanceof TextChannel || c instanceof NewsChannel)) - ); - - for (let ci = 0; ci < channels.length; ci++) { - const c = channels[ci]; - const messages = await c.messages.fetch({ limit: 100 }); - if (messages.size === 0) continue; - - const clientMessages = messages - .array() - .filter( - (m) => - m.author.id === client.user.id && - m.deletable && - !m.deleted && - (new Date().getTime() - m.createdTimestamp >= 14 * day || new Date().getTime() - m.createdTimestamp >= gc.pruneIntDiscord * day) - ); - - if (clientMessages.length === 0) continue; - - try { - const deleted = await c.bulkDelete(clientMessages, true); - count += deleted.size; - clientMessages - .filter((cm) => !deleted.find((d) => d.id === cm.id)) - .forEach((cm) => { - cm.delete({ reason: "Automated pruning..." }); - count++; - }); - } catch (err) { - console.log("AutomatedPruningError:", err); - } - } - } + // for (let gci = 0; gci < guildConfigs.length; gci++) { + // const gc = guildConfigs[gci]; + // if (!gc.pruning) continue; + + // const sGames = games.filter((g) => g.s === gc.guild && !g.hideDate && !!g.messageId); + + // const guild = client.guilds.cache.find((g) => g.id === gc.guild); + // const channels = <(TextChannel | NewsChannel)[]>( + // guild.channels.cache.array().filter((c) => gc.channels.find((gcc) => gcc.channelId === c.id) && (c instanceof TextChannel || c instanceof NewsChannel)) + // ); + + // for (let ci = 0; ci < channels.length; ci++) { + // const c = channels[ci]; + // const messages = await c.messages.fetch({ limit: 100 }); + // if (messages.size === 0) continue; + + // const clientMessages = messages + // .array() + // .filter( + // (m) => + // m.author.id === client.user.id && + // m.deletable && + // !m.deleted && + // !sGames.find((sg) => sg.messageId === m.id) && + // !sGames.find((sg) => sg.reminderMessageId === m.id) && + // new Date().getTime() - m.createdTimestamp >= 14 * day + // ); + + // if (clientMessages.length === 0) continue; + + // try { + // const deleted = await c.bulkDelete(clientMessages, true); + // count += deleted.size; + // clientMessages + // .filter((cm) => !deleted.find((d) => d.id === cm.id)) + // .forEach((cm) => { + // cm.delete({ reason: "Automated pruning..." }); + // count++; + // }); + // } catch (err) { + // console.log("AutomatedPruningError:", err); + // } + // } + // } if (count > 0) aux.log(`${count} old message(s) successfully pruned`); page++; diff --git a/src/routes/api.ts b/src/routes/api.ts index b638413..c4c5c60 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -405,33 +405,35 @@ export default (options: APIRouteOptions) => { search: req.query.search, }) .then(async (result: any) => { - const guilds = result.account.guilds.filter(g => g.id == req.query.guildId).map((guild) => { - guild.roles = guild.roles.map((role) => { - delete role.hoist; - delete role.createdTimestamp; - delete role.deleted; - delete role.mentionable; - delete role.permissions; - delete role.rawPosition; - return role; - }); - guild.channelCategories = guild.channelCategories.map((channel) => { - // delete channel.members; - delete channel.messages; - return channel; - }); - guild.announcementChannels = guild.announcementChannels.map((channel) => { - // delete channel.members; - delete channel.messages; - return channel; - }); - guild.channels = guild.channels.map((channel) => { - // delete channel.members; - delete channel.messages; - return channel; + const guilds = result.account.guilds + .filter((g) => g.id == req.query.guildId) + .map((guild) => { + guild.roles = guild.roles.map((role) => { + delete role.hoist; + delete role.createdTimestamp; + delete role.deleted; + delete role.mentionable; + delete role.permissions; + delete role.rawPosition; + return role; + }); + guild.channelCategories = guild.channelCategories.map((channel) => { + // delete channel.members; + delete channel.messages; + return channel; + }); + guild.announcementChannels = guild.announcementChannels.map((channel) => { + // delete channel.members; + delete channel.messages; + return channel; + }); + guild.channels = guild.channels.map((channel) => { + // delete channel.members; + delete channel.messages; + return channel; + }); + return guild; }); - return guild; - }); res.json({ status: "success", token: req.session.api.access.access_token, @@ -477,7 +479,7 @@ export default (options: APIRouteOptions) => { if (typeof req.body[property] != "undefined") guildConfig[property] = req.body[property]; } const saveResult = await guildConfig.save(); - + const sGuilds = await ShardManager.shardGuilds({ guildIds: [req.body.id], }); @@ -751,7 +753,9 @@ export default (options: APIRouteOptions) => { member && (member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || - member.roles.find((r) => isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim())); + member.roles.find((r) => + isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim() + )); const gcChannel = guildConfig.channel.find((gcc) => gcc.channelId === game.c) || { channelId: game.c, gameTemplates: [guildConfig.defaultGameTemplate.id], @@ -770,7 +774,11 @@ export default (options: APIRouteOptions) => { if (gameTemplate.role && !isAdmin) { if (member) { // User does not have the require role - if (!member.roles.find((r) => isObject(gameTemplate.role) ? r.id === gameTemplate.role.id : r.name.toLowerCase().trim() === gameTemplate.role.toLowerCase().trim())) { + if ( + !member.roles.find((r) => + isObject(gameTemplate.role) ? r.id === gameTemplate.role.id : r.name.toLowerCase().trim() === gameTemplate.role.toLowerCase().trim() + ) + ) { throw new Error("You are either not logged in or are missing the role required for posting on this server."); } } @@ -890,7 +898,7 @@ export default (options: APIRouteOptions) => { updatedGame .save({ - user: result.account.user + user: result.account.user, }) .then(async (response) => { updatedGame._id = response.modified ? response._id : null; @@ -1005,6 +1013,40 @@ export default (options: APIRouteOptions) => { }); }); + router.get("/auth-api/game-restore", async (req, res, next) => { + fetchAccount(req.session.api.access, { + client: client, + ip: req.app.locals.ip, + guilds: true, + }) + .then(async (result: any) => { + try { + if (req.query.g) { + const game = await Game.fetch(req.query.g, null, result.sGuilds); + if (!game) throw new Error("Game not found"); + await game.undelete(); + } else { + throw new Error("Game not found"); + } + } catch (err) { + res.json({ + status: "error", + token: req.session.api.access.access_token, + message: `UndeleteGameError`, + code: 32, + }); + } + }) + .catch((err) => { + res.json({ + status: "error", + token: req.session.api.access.access_token, + message: `UserAuthError`, + code: 33, + }); + }); + }); + router.post("/api/rsvp", async (req, res, next) => { try { const bearer = (req.headers.authorization || "").replace("Bearer ", "").trim(); @@ -1018,7 +1060,7 @@ export default (options: APIRouteOptions) => { const game = await Game.fetch(req.body.game, null, sGuilds); if (game) { const member = game.discordGuild.members.find((m) => m.id === req.body.id); - let rsvp: { result: boolean, message?: string }; + let rsvp: { result: boolean; message?: string }; if (!game.reserved.find((r) => r.id === member.user.id || r.tag === member.user.tag)) { rsvp = await game.signUp(member.user); } else { @@ -1065,7 +1107,7 @@ export default (options: APIRouteOptions) => { .then(async (result: any) => { const game = await Game.fetch(req.body.g, null, result.sGuilds); if (game) { - let rsvp: { result: boolean, message?: string }; + let rsvp: { result: boolean; message?: string }; if (!game.reserved.find((r) => r.id === result.account.user.id || r.tag === result.account.user.tag)) { rsvp = await game.signUp(result.account.user, t); } else { @@ -1386,7 +1428,9 @@ export default (options: APIRouteOptions) => { guild.isAdmin = !!( member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || - member.roles.find((r) => isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) + member.roles.find((r) => + isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim() + ) ); guild.permission = guildConfig.shardMemberHasPermission(member) || guild.isAdmin; gcChannels.forEach((c) => { @@ -1427,20 +1471,23 @@ export default (options: APIRouteOptions) => { myGames: `https://www.rpg-schedule.com/games/my-games?s=${escape(`_id:${game._id}`)}`, }, ...game.data, - dm: (function (r) { + dm: ((r) => { const member = sGuild.members.find((m) => (r.id && m.user.id === r.id) || m.user.tag === r.tag); return (member && member.nickname) || (r.tag.indexOf("#") < 0 && r.tag) || "User not found"; })(game.data.dm), - author: (function (r) { + dmData: game.data.dm, + author: ((r) => { const member = sGuild.members.find((m) => (r.id && m.user.id === r.id) || m.user.tag === r.tag); return (member && member.nickname) || (r.tag.indexOf("#") < 0 && r.tag) || "User not found"; })(game.data.author), + authorData: game.data.author, reserved: game.data.reserved .filter((r) => r.tag) .map((r) => { const member = sGuild.members.find((m) => (r.id && m.user.id === r.id) || m.user.tag === r.tag); return (member && member.nickname) || (r.tag.indexOf("#") < 0 && r.tag) || "User not found"; }), + reservedData: game.data.reserved.filter((r) => r.tag), moment: { ...parsed, iso: date, @@ -1495,6 +1542,7 @@ export default (options: APIRouteOptions) => { ...req.query, ...{ tag: tag, + apiKey: await aux.getAPIKey(id), }, }, guilds: [], @@ -1592,7 +1640,9 @@ export default (options: APIRouteOptions) => { guild.isAdmin = !!( member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || - member.roles.find((r) => isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) + member.roles.find((r) => + isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim() + ) ); guild.permission = guildConfig.shardMemberHasPermission(member) || guild.isAdmin; gcChannels.forEach((c) => { @@ -1806,7 +1856,9 @@ const fetchAccount = (token: any, options: AccountOptions) => { guild.isAdmin = !!( member.hasPermission(Permissions.FLAGS.MANAGE_GUILD) || member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) || - member.roles.find((r) => isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim()) + member.roles.find((r) => + isObject(guildConfig.managerRole) ? r.id === guildConfig.managerRole.id : r.name.toLowerCase().trim() === (guildConfig.managerRole || "").toLowerCase().trim() + ) ); guild.permission = guildConfig.shardMemberHasPermission(member) || guild.isAdmin; } @@ -1891,7 +1943,7 @@ const fetchAccount = (token: any, options: AccountOptions) => { }; } - const fGames: Game[] = await Game.fetchAllBy(gameOptions, null, sGuilds); + const fGames: Game[] = await Game.fetchAllBy(gameOptions, null, sGuilds, true); // const games: any[] = []; // for (let i = 0; i < fGames.length; i++) { // const game = fGames[i]; From 66320a10a479a2ae744b3d4e3d4f00ea927399d7 Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Sun, 8 Nov 2020 10:45:08 -0600 Subject: [PATCH 52/53] Development --- src/models/game.ts | 2 +- src/processes/discord.ts | 7 ++++--- src/processes/shard-manager.ts | 14 +++++++------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/models/game.ts b/src/models/game.ts index 7dde24d..dbb5208 100644 --- a/src/models/game.ts +++ b/src/models/game.ts @@ -698,7 +698,7 @@ export class Game implements GameModel { dmEmbed.addField(lang.game.SERVER, guild.name, true); dmEmbed.addField(lang.game.GAME_NAME, `[${game.adventure}](https://discordapp.com/channels/${this.discordGuild.id}/${this.discordChannel.id}/${message.id})`, true); const pm = await dmmember.send(dmEmbed); - await dbCollection.updateOne({ _id: new ObjectId(inserted.insertedId) }, { $set: { pm: pm.id } }); + if (pm) await dbCollection.updateOne({ _id: new ObjectId(inserted.insertedId) }, { $set: { pm: pm.id } }); } catch (err) { aux.log("EditLinkError:", err); } diff --git a/src/processes/discord.ts b/src/processes/discord.ts index 356ff08..b95bf87 100644 --- a/src/processes/discord.ts +++ b/src/processes/discord.ts @@ -26,7 +26,7 @@ app.locals.langs = app.locals.supportedLanguages.langs }) .sort((a: any, b: any) => (a.name > b.name ? 1 : -1)); -let client = new Client(); +let client = new Client({ fetchAllMembers: true }); let isReady = false; let connected = false; let numSlices = client.shard.count * 2; @@ -955,6 +955,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { } } } else if (cmd === "reboot" && member.user.tag === config.author) { + await message.channel.send(responseEmbed(`Rebooting the bot's shards`)); await client.shard.send({ type: "reboot", }); @@ -1064,7 +1065,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { */ client.on("messageReactionAdd", async (reaction, user: User) => { try { - if (!reaction) return; + if (!reaction || !user) return; const message = reaction.message; const t = new Date().getTime(); const guildConfig = await GuildConfig.fetch(message.guild.id); @@ -1151,7 +1152,7 @@ if (process.env.DISCORD_LOGIC.toLowerCase() === "true") { aux.log(err); }); - client.on("shardReady", (id) => { + client.on("shardReady", async (id) => { aux.log("Client: Shard Ready", id); }); diff --git a/src/processes/shard-manager.ts b/src/processes/shard-manager.ts index 6cfa187..7ba4173 100644 --- a/src/processes/shard-manager.ts +++ b/src/processes/shard-manager.ts @@ -189,7 +189,7 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { else { await manager.broadcastEval(` const guilds = this.guilds.cache.array(); - console.log("Force refreshing data for ", guilds.length, "guilds"); + console.log("Forcing data refresh for ", guilds.length, "guilds"); this.shard.send({ type: "shard", name: "guilds", @@ -224,12 +224,12 @@ const managerConnect = (options: DiscordProcessesOptions, readyCallback: () => { members: [], // c.members.map((m) => m.user.id), everyone: c.permissionsFor(c.guild.roles.cache.find((r) => r.name === "@everyone").id).has(Permissions.FLAGS.VIEW_CHANNEL), botPermissions: [ - c.permissionsFor(client.user.id).has(Permissions.FLAGS.VIEW_CHANNEL) && "VIEW_CHANNEL", - c.permissionsFor(client.user.id).has(Permissions.FLAGS.READ_MESSAGE_HISTORY) && "READ_MESSAGE_HISTORY", - c.permissionsFor(client.user.id).has(Permissions.FLAGS.SEND_MESSAGES) && "SEND_MESSAGES", - c.permissionsFor(client.user.id).has(Permissions.FLAGS.MANAGE_MESSAGES) && "MANAGE_MESSAGES", - c.permissionsFor(client.user.id).has(Permissions.FLAGS.EMBED_LINKS) && "EMBED_LINKS", - c.permissionsFor(client.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.VIEW_CHANNEL) && "VIEW_CHANNEL", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.READ_MESSAGE_HISTORY) && "READ_MESSAGE_HISTORY", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.SEND_MESSAGES) && "SEND_MESSAGES", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.MANAGE_MESSAGES) && "MANAGE_MESSAGES", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.EMBED_LINKS) && "EMBED_LINKS", + c.permissionsFor(this.user.id).has(Permissions.FLAGS.ADD_REACTIONS) && "ADD_REACTIONS", ].filter((check) => check) })), roles: guild.roles.cache.array(), From 8bdfa8dda93ebd44ad130f01f458fe01937c118e Mon Sep 17 00:00:00 2001 From: Matt DeKok Date: Mon, 16 Nov 2020 11:46:32 -0600 Subject: [PATCH 53/53] Bug Fix - Hide sensitive info --- src/routes/api.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routes/api.ts b/src/routes/api.ts index c4c5c60..3d199ea 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -835,7 +835,10 @@ export default (options: APIRouteOptions) => { weekdays: [false, false, false, false, false, false, false], xWeeks: 2, clearReservedOnRepeat: false, - env: process.env, + env: { + REMINDERS: process.env.REMINDERS, + RESCHEDULING: process.env.RESCHEDULING, + }, is: { newgame: !req.query.g ? true : false, editgame: req.query.g ? true : false,