From eab9665f76d239e34f497742836ba70f5e52abcb Mon Sep 17 00:00:00 2001 From: versx Date: Fri, 29 Jul 2022 21:52:55 -0700 Subject: [PATCH] Actually fix delete channels this time --- package-lock.json | 24 ++++++- package.json | 5 +- src/handlers/dm.ts | 8 +-- src/index.ts | 110 +++++++++++++++++++++------------ src/models/events.ts | 3 +- src/services/create-locales.ts | 5 +- src/services/url-watcher.ts | 7 +-- src/services/utils.ts | 27 ++++---- 8 files changed, 123 insertions(+), 66 deletions(-) diff --git a/package-lock.json b/package-lock.json index b3c898d..5b2013b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "eventwatcher", - "version": "2.0.5", + "version": "2.0.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "eventwatcher", - "version": "2.0.5", + "version": "2.0.6", "license": "ISC", "dependencies": { "axios": "^0.21.1", @@ -16,6 +16,7 @@ "mustache": "^4.1.0" }, "devDependencies": { + "@types/axios": "^0.14.0", "@types/i18n": "^0.12.0", "@types/mustache": "^4.1.1", "@types/node": "^14.0.1", @@ -126,6 +127,16 @@ "node": ">=6" } }, + "node_modules/@types/axios": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz", + "integrity": "sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==", + "deprecated": "This is a stub types definition for axios (https://github.com/mzabriskie/axios). axios provides its own type definitions, so you don't need @types/axios installed!", + "dev": true, + "dependencies": { + "axios": "*" + } + }, "node_modules/@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -2937,6 +2948,15 @@ "defer-to-connect": "^1.0.1" } }, + "@types/axios": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz", + "integrity": "sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==", + "dev": true, + "requires": { + "axios": "*" + } + }, "@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", diff --git a/package.json b/package.json index fd2de87..8071321 100644 --- a/package.json +++ b/package.json @@ -30,14 +30,15 @@ "mustache": "^4.1.0" }, "devDependencies": { + "@types/axios": "^0.14.0", "@types/i18n": "^0.12.0", "@types/mustache": "^4.1.1", "@types/node": "^14.0.1", "@typescript-eslint/eslint-plugin": "^3.0.0", "@typescript-eslint/parser": "^3.0.0", "eslint": "^7.0.0", - "typescript": "^3.9.7", - "nodemon": "^2.0.7" + "nodemon": "^2.0.7", + "typescript": "^3.9.7" }, "nodemonConfig": { "ignore": [ diff --git a/src/handlers/dm.ts b/src/handlers/dm.ts index bbd8a6a..b5c74b2 100644 --- a/src/handlers/dm.ts +++ b/src/handlers/dm.ts @@ -8,12 +8,12 @@ import { User } from 'discord.js'; * @param {*} data */ export const sendDm = async (member: User, data: any): Promise => { - if (!member || !data) { - console.warn('Member or data is null, cannot send direct message'); + if (!member) { + console.error('Failed to find Discord member', member); return; } - if (!member) { - console.error('Failed to find member', member); + if (!data) { + console.warn('DM data is null, cannot send direct message'); return; } const dm = await member.createDM(); diff --git a/src/index.ts b/src/index.ts index db1184b..d016161 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,13 @@ import { readdirSync } from 'fs'; import { resolve } from 'path'; -import { CategoryChannel, Client, Collection, Guild, GuildChannel, OverwriteResolvable, TextChannel } from 'discord.js'; +import { + CategoryChannel, + Client, + Guild, + GuildChannel, + TextChannel, +} from 'discord.js'; import { render } from 'mustache'; const config = require('../src/config.json'); @@ -11,17 +17,28 @@ import { sendDm } from './handlers/dm'; import { createActiveEventEmbed, createEmbedFromNewEvent } from './handlers/embeds'; import { PokemonEvents } from './models/events'; import { UrlWatcher } from './services/url-watcher'; -import { post, getWebhookData, } from './services/utils'; -import { Dictionary } from './types/dictionary'; +import { post, getWebhookData, WebhookData } from './services/utils'; import { ActiveEvent } from './types/events'; const client = new Client(); -//const urlToWatch = 'https://raw.githubusercontent.com/ccev/pogoinfo/info/events/active.json'; const urlToWatch = 'https://raw.githubusercontent.com/ccev/pogoinfo/v2/active/events.json'; const intervalM = 1 * 60 * 1000; const NotAvailable = 'N/A'; let started = false; +const EveryonePermissionOverwrites = { + CONNECT: false, + VIEW_CHANNEL: true, +}; + +const BotPermissionOverwrites = { + CONNECT: true, + VIEW_CHANNEL: true, + MANAGE_CHANNELS: true, + MANAGE_MESSAGES: true, + MANAGE_ROLES: true, +}; + // TODO: Show time when event expires that day // TODO: Remove all 'any' types @@ -72,75 +89,79 @@ const createVoiceChannels = async (guildInfo: any, activeEvents: any): Promise x.id === guild.id); - const permissions: OverwriteResolvable[] = [{ - id: everyoneRole?.id ?? guild.id, - allow: ['VIEW_CHANNEL'], - },{ - id: everyoneRole?.id ?? guild.id, - deny: ['CONNECT'], - },{ - id: client.user?.id ?? '0', - allow: [ - 'MANAGE_CHANNELS', - 'VIEW_CHANNEL', - ], - }]; + // Get event category channel from id const channelCategory = guild.channels.cache.get(guildInfo.eventsCategoryId); if (!channelCategory) { console.error(`Failed to get channel category by id ${guildInfo.eventsCategoryId} from guild ${guildInfo.id}`); return; } - if (everyoneRole != null) { - // Update permissions for event category channel - await channelCategory.updateOverwrite(everyoneRole, { CONNECT: false, VIEW_CHANNEL: true }); - } - //const now = new Date(); + // Update @everyone role permissions for event channel category + await channelCategory.updateOverwrite(guild.id, EveryonePermissionOverwrites); + + // Update bot permissions for event category channel + await channelCategory.updateOverwrite(client.user!.id, BotPermissionOverwrites); + // Loop all active events for (const event of activeEvents) { // Get channel name from event name and ends date const channelName = formatEventName(event); // Check if channel name matches event name, if not delete channel - const channel = await createVoiceChannel(guild, channelName, channelCategory, permissions); + const channel = await createVoiceChannel(guild, channelName, channelCategory); if (channel == null) { + // Failed to delete channel, continue on to the next continue; } + // Attempt to delete any expired event channels await deleteExpiredEvents(channelCategory, activeEvents); } }; const createVoiceChannel = async (guild: Guild, channelName: string, - channelCategory: GuildChannel, - permissions: Collection | OverwriteResolvable[] | undefined): Promise => { + channelCategory: GuildChannel): Promise => { + // Attempt to find channel from ChannelManager cache let channel = guild.channels.cache.find(x => x.name.toLowerCase() === channelName.toLowerCase()); - // Check if channel does not exist - if (!channel) { + // Check if channel already exists + if (channel) { + return channel; + } + // Channel does not exist, create it + try { // Create voice channel with permissions channel = await guild.channels.create(channelName, { type: 'voice', parent: channelCategory, - permissionOverwrites: permissions, }); + // Set channel permissions for @everyone + channel.updateOverwrite(guild.id, EveryonePermissionOverwrites); + // Set channel permissions for bot + channel.updateOverwrite(client.user?.id ?? '0', BotPermissionOverwrites); + console.info('Event voice channel', channel?.name, 'created'); + return channel; + } catch (e) { + console.error('createVoiceChannel:', e); } - return channel; + return undefined; }; const deleteChannel = async (guild: Guild, channelId: string): Promise => { + // Fetch channel by id from ChannelManager cache const channel = guild.channels.cache.get(channelId); if (!channel) { console.error(`Failed to find expired event channel ${channelId} to delete.`); return; } + // Only delete voice channels if (channel.isText()) { return; } try { - await channel.delete('Expired event'); + // Delete expired event channel + await channel.delete('Event has expired'); + console.log('Event voice channel', channel.name, 'deleted'); } catch (e) { console.error(`Failed to delete channel ${channel.id}: ${e}`); } @@ -166,9 +187,14 @@ const formatEventName = (event: ActiveEvent): string => { // Format event ends date const eventEndDate = event.end ? new Date(event.end) : NotAvailable; // Get channel name from event name and ends date + // Use mustache to template the channel's naming scheme const channelName = render(config.channelNameFormat, { - month: eventEndDate !== NotAvailable ? eventEndDate.getMonth() + 1 : NotAvailable, - day: eventEndDate !== NotAvailable ? eventEndDate.getDate() : '', + month: eventEndDate !== NotAvailable + ? eventEndDate.getMonth() + 1 + : NotAvailable, + day: eventEndDate !== NotAvailable + ? eventEndDate.getDate() + : '', name: event.name, }); return channelName; @@ -183,13 +209,18 @@ UrlWatcher(urlToWatch, intervalM, async (): Promise => { for (const webhook of config.webhooks) { // Delete previous event messages if set if (config.deletePreviousEvents) { - getWebhookData(webhook)?.then(whData => { - if (whData?.guild_id && whData?.channel_id) { - const guild = client.guilds.cache.get(whData.guild_id); + // Get information returned about webhook + getWebhookData(webhook)?.then((webhookData: WebhookData | null) => { + // Check if + if (webhookData?.guild_id && webhookData?.channel_id) { + const guild = client.guilds.cache.get(webhookData.guild_id); if (guild) { - const channel = guild.channels.cache.get(whData.channel_id); + const channel = guild.channels.cache.get(webhookData.channel_id); try { - (channel as TextChannel).bulkDelete(100); + // Ensure we only try to delete messages from text channels + if (channel?.type == 'text') { + (channel as TextChannel).bulkDelete(100); + } } catch (err) { console.error('Error:', err); } @@ -212,6 +243,7 @@ UrlWatcher(urlToWatch, intervalM, async (): Promise => { console.error(`Failed to get member by id ${userId}`); continue; } + // Send DM info about event to Discord user await sendDm(member, { embed: embed }); console.info(`New event direct message sent to ${member?.username} (${member?.id})`); } diff --git a/src/models/events.ts b/src/models/events.ts index 4a68a29..1d5708c 100644 --- a/src/models/events.ts +++ b/src/models/events.ts @@ -1,7 +1,6 @@ 'use strict'; -import { getPokemonName } from '../services/locale'; -import { get, stripIds, } from '../services/utils'; +import { get } from '../services/utils'; import { ActiveEvent, ActiveRaidsDictionary } from '../types/events'; const baseUrl = 'https://raw.githubusercontent.com/ccev/pogoinfo/v2/'; diff --git a/src/services/create-locales.ts b/src/services/create-locales.ts index a98a0c1..93dbc05 100644 --- a/src/services/create-locales.ts +++ b/src/services/create-locales.ts @@ -4,11 +4,12 @@ import { readFileSync, writeFileSync, existsSync } from 'fs'; import { resolve } from 'path'; import axios from 'axios'; +const BaseTranslationsUrl = 'https://raw.githubusercontent.com/WatWowMap/pogo-translations/master/'; const appLocalesFolder = resolve(__dirname, '../../static/locales'); async function createLocales() { const englishRef = readFileSync(resolve(appLocalesFolder, '_en.json'), { encoding: 'utf8', flag: 'r' }); - const remoteIndex: string[] = await axios.get('https://raw.githubusercontent.com/WatWowMap/pogo-translations/master/index.json') + const remoteIndex: string[] = await axios.get(BaseTranslationsUrl + 'index.json') .then((response) => response.data); await Promise.all(remoteIndex.map(async locale => { @@ -19,7 +20,7 @@ async function createLocales() { const trimmedRemoteFiles: { [key: string]: string } = {}; try { - const { data } = await axios.get(`https://raw.githubusercontent.com/WatWowMap/pogo-translations/master/static/locales/${baseName}.json`); + const { data } = await axios.get(`${BaseTranslationsUrl}static/locales/${baseName}.json`); Object.keys(data).forEach(key => { if (!key.startsWith('desc_') && !key.startsWith('pokemon_category_') && !key.startsWith('quest')) { diff --git a/src/services/url-watcher.ts b/src/services/url-watcher.ts index 8d9b46b..43f2930 100644 --- a/src/services/url-watcher.ts +++ b/src/services/url-watcher.ts @@ -1,8 +1,7 @@ 'use strict'; import { NoParamCallback } from 'fs'; - -const utils = require('../services/utils.js'); +import { get, deepEqual } from '../services/utils'; let previousData: any; @@ -11,12 +10,12 @@ export const UrlWatcher = (urlToWatch: string, interval: number, changedCallback * Start checking for url address content changes */ setInterval(async () => { - const data = await utils.get(urlToWatch); + const data = await get(urlToWatch); if (!previousData) { previousData = data; return; } - if (!utils.deepEqual(previousData, data)) { + if (!deepEqual(previousData, data)) { previousData = data; changedCallback(null); } diff --git a/src/services/utils.ts b/src/services/utils.ts index 0e5444d..f2ce3af 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -1,13 +1,24 @@ 'use strict'; -const axios = require('axios'); +const axios = require('axios').default; + +const UserAgent = 'PokemonGoEventWatcher'; /** * HTTP GET request to url * @param {*} url Web address url to make request to */ export const get = async (url: string): Promise => { - const req = await axios.get(url); + //const req = await axios.get(url); + const req = await axios({ + url: url, + method: 'GET', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'User-Agent': UserAgent, + }, + }); if (req.status !== 200) { console.error(`Failed to get data from ${url}:`, req.statusText); return null; @@ -28,6 +39,7 @@ export const post = async (url: string, data: any): Promise => { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', + 'User-Agent': UserAgent, }, }); //const req = await axios.post(url, data); @@ -48,14 +60,6 @@ export const cropText = (text: string, maxLength: number): string => { return (text.length > maxLength) ? text.substr(0, maxLength - 3) + '...' : text; }; -/** - * Strip PMSF icon format for raw ids - * @param {*} ids - */ -export const stripIds = (ids: string[]): number[] => { - return ids.map(x => parseInt(x.replace('_00', ''))); -}; - /** * Deep equals between two objects * @param {*} object1 @@ -102,7 +106,8 @@ export const getWebhookData = async (webhookUrl: string): Promise