diff --git a/Xiao.js b/Xiao.js index e2734f89..be942675 100644 --- a/Xiao.js +++ b/Xiao.js @@ -57,12 +57,6 @@ client.registry client.on('ready', async () => { client.logger.info(`[READY] Logged in as ${client.user.tag}! ID: ${client.user.id}`); - // Register all canvas fonts - await client.registerFontsIn(path.join(__dirname, 'assets', 'fonts')); - - // Set up existing timers - await client.timers.fetchAll(); - // Push client-related activities client.activities.push( { text: () => `${formatNumber(client.guilds.cache.size)} servers`, type: 'WATCHING' }, @@ -77,6 +71,36 @@ client.on('ready', async () => { client.user.setActivity(text, { type: activity.type }); }, 60000); + // Import command-leaderboard.json + try { + const results = client.importCommandLeaderboard(); + if (!results) client.logger.error('[LEADERBOARD] command-leaderboard.json is not formatted correctly.'); + } catch (err) { + client.logger.error(`[LEADERBOARD] Could not parse command-leaderboard.json:\n${err.stack}`); + } + + // Import command-last-run.json + try { + const results = client.importLastRun(); + if (!results) client.logger.error('[LASTRUN] command-last-run.json is not formatted correctly.'); + } catch (err) { + client.logger.error(`[LASTRUN] Could not parse command-last-run.json:\n${err.stack}`); + } + + // Export command-leaderboard.json and command-last-run.json every 30 minutes + client.setInterval(() => { + try { + client.exportCommandLeaderboard(); + } catch (err) { + client.logger.error(`[LEADERBOARD] Failed to export command-leaderboard.json:\n${err.stack}`); + } + try { + client.exportLastRun(); + } catch (err) { + client.logger.error(`[LASTRUN] Failed to export command-last-run.json:\n${err.stack}`); + } + }, 1.8e+6); + // Import blacklist try { const results = client.importBlacklist(); @@ -111,35 +135,18 @@ client.on('ready', async () => { } client.logger.info(`[BLACKLIST] Left ${guildsLeft} guilds owned by blacklisted users.`); - // Import command-leaderboard.json - try { - const results = client.importCommandLeaderboard(); - if (!results) client.logger.error('[LEADERBOARD] command-leaderboard.json is not formatted correctly.'); - } catch (err) { - client.logger.error(`[LEADERBOARD] Could not parse command-leaderboard.json:\n${err.stack}`); - } + // Set up existing timers + await client.timers.fetchAll(); - // Export command-last-run.json - try { - const results = client.importLastRun(); - if (!results) client.logger.error('[LASTRUN] command-last-run.json is not formatted correctly.'); - } catch (err) { - client.logger.error(`[LASTRUN] Could not parse command-last-run.json:\n${err.stack}`); - } + // Register all canvas fonts + await client.registerFontsIn(path.join(__dirname, 'assets', 'fonts')); - // Export command-leaderboard.json and command-last-run.json every 30 minutes - client.setInterval(() => { - try { - client.exportCommandLeaderboard(); - } catch (err) { - client.logger.error(`[LEADERBOARD] Failed to export command-leaderboard.json:\n${err.stack}`); - } - try { - client.exportLastRun(); - } catch (err) { - client.logger.error(`[LASTRUN] Failed to export command-last-run.json:\n${err.stack}`); - } - }, 1.8e+6); + // Fetch adult site list + try { + await this.client.fetchAdultSiteList(); + } catch (err) { + client.logger.error(`[ADULT SITES] Failed to fetch list\n${err.stack}`); + } }); client.on('message', async msg => { diff --git a/commands/other/portal-send.js b/commands/other/portal-send.js index 33d672c0..ca2e2acc 100644 --- a/commands/other/portal-send.js +++ b/commands/other/portal-send.js @@ -1,5 +1,5 @@ const Command = require('../../structures/Command'); -const { stripInvites } = require('../../util/Util'); +const { stripInvites, stripNSFWURLs } = require('../../util/Util'); const { stripIndents } = require('common-tags'); const { PORTAL_EMOJI_ID, PORTAL_EMOJI_NAME } = process.env; @@ -27,7 +27,6 @@ module.exports = class PortalSendCommand extends Command { } return true; }, - parse: val => val ? stripInvites(val) : '', isEmpty: (val, msg) => !msg.attachments.size && !val } ] @@ -47,8 +46,11 @@ module.exports = class PortalSendCommand extends Command { try { const displayName = msg.guild ? msg.guild.name : 'DM'; const attachments = msg.attachments.size ? msg.attachments.map(a => a.url).join('\n') : null; + const content = !msg.channel.nsfw && this.client.adultSiteList + ? stripNSFWURLs(stripInvites(message), this.client.adultSiteList) + : stripInvites(message); await channel.send(stripIndents` - **${this.portalEmoji} ${msg.author.tag} (${displayName}):** ${message} + **${this.portalEmoji} ${msg.author.tag} (${displayName}):** ${content} ${attachments || ''} `); if (channel.topic.includes('')) { diff --git a/commands/other/screenshot.js b/commands/other/screenshot.js index 29f09c6b..e332e1ed 100644 --- a/commands/other/screenshot.js +++ b/commands/other/screenshot.js @@ -1,6 +1,7 @@ const Command = require('../../structures/Command'); const request = require('node-superfetch'); const url = require('url'); +const validURL = require('valid-url'); module.exports = class ScreenshotCommand extends Command { constructor(client) { @@ -16,12 +17,6 @@ module.exports = class ScreenshotCommand extends Command { name: 'Thum.io', url: 'https://www.thum.io/', reason: 'API' - }, - { - name: 'Block List Project', - url: 'https://blocklist.site/', - reason: 'NSFW Site List', - reasonURL: 'https://raw.githubusercontent.com/blocklistproject/Lists/master/porn.txt' } ], args: [ @@ -29,19 +24,16 @@ module.exports = class ScreenshotCommand extends Command { key: 'site', prompt: 'What webpage do you want to take a screenshot of?', type: 'string', - parse: site => /^(https?:\/\/)/i.test(site) ? site : `http://${site}` + validate: site => validURL.isWebUri(site) } ] }); - - this.pornList = null; } async run(msg, { site }) { try { - if (!this.pornList) await this.fetchPornList(); const parsed = url.parse(site); - if (!msg.channel.nsfw && this.pornList.some(pornURL => parsed.host === pornURL)) { + if (!msg.channel.nsfw && this.client.adultSiteList.some(pornURL => parsed.host === pornURL)) { return msg.reply('This site is NSFW.'); } const { body } = await request.get(`https://image.thum.io/get/width/1920/crop/675/noanimate/${site}`); @@ -51,13 +43,4 @@ module.exports = class ScreenshotCommand extends Command { return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); } } - - async fetchPornList(force = false) { - if (!force && this.pornList) return this.pornList; - const { text } = await request.get('https://raw.githubusercontent.com/blocklistproject/Lists/master/porn.txt'); - this.pornList = text.split('\n') - .filter(site => site && !site.startsWith('#')) - .map(site => site.replace(/^(0.0.0.0 )/, '')); // eslint-disable-line no-control-regex - return this.pornList; - } }; diff --git a/package.json b/package.json index 0fa1bc27..bebcb254 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiao", - "version": "131.0.0", + "version": "131.0.1", "description": "Your personal server companion.", "main": "Xiao.js", "scripts": { diff --git a/structures/Client.js b/structures/Client.js index 486073b6..d6d29645 100644 --- a/structures/Client.js +++ b/structures/Client.js @@ -41,6 +41,7 @@ module.exports = class XiaoClient extends CommandoClient { this.phone = new PhoneManager(this); this.activities = activities; this.leaveMessages = leaveMsgs; + this.adultSiteList = null; } async registerFontsIn(filepath) { @@ -156,6 +157,15 @@ module.exports = class XiaoClient extends CommandoClient { return buf; } + async fetchAdultSiteList(force = false) { + if (!force && this.adultSiteList) return this.adultSiteList; + const { text } = await request.get('https://raw.githubusercontent.com/blocklistproject/Lists/master/porn.txt'); + this.adultSiteList = text.split('\n') + .filter(site => site && !site.startsWith('#')) + .map(site => site.replace(/^(0.0.0.0 )/, '')); // eslint-disable-line no-control-regex + return this.adultSiteList; + } + fetchReportChannel() { if (!REPORT_CHANNEL_ID) return null; return this.channels.fetch(REPORT_CHANNEL_ID); diff --git a/structures/phone/PhoneCall.js b/structures/phone/PhoneCall.js index 0d165cdf..67144f2a 100644 --- a/structures/phone/PhoneCall.js +++ b/structures/phone/PhoneCall.js @@ -1,7 +1,7 @@ const { stripIndents } = require('common-tags'); const moment = require('moment'); require('moment-duration-format'); -const { shorten, stripInvites, preventURLEmbeds, verify } = require('../../util/Util'); +const { shorten, stripInvites, preventURLEmbeds, stripNSFWURLs, verify } = require('../../util/Util'); module.exports = class PhoneCall { constructor(client, startUser, origin, recipient, adminCall) { @@ -146,17 +146,19 @@ module.exports = class PhoneCall { setTimeout(() => this.ratelimitMeters.set(msg.author.id, 0), 5000); } } - const attachments = hasImage ? msg.attachments.map(a => this.cleanContent(a.url)).join('\n') : null; + const attachments = hasImage ? msg.attachments.map(a => this.cleanContent(a.url, channel.nsfw)).join('\n') : null; if (!hasText && hasImage) return channel.send(`☎️ **${msg.author.tag}:**\n${attachments}`); if (!hasText && hasEmbed) return channel.send(`☎️ **${msg.author.tag}** sent an embed.`); const content = msg.content.length > 1000 ? `${shorten(msg.content, 500)} (Message too long)` : msg.content; - return channel.send(`☎️ **${msg.author.tag}:** ${this.cleanContent(content)}\n${attachments || ''}`.trim()); + return channel.send( + `☎️ **${msg.author.tag}:** ${this.cleanContent(content, channel.nsfw)}\n${attachments || ''}`.trim() + ); } sendVoicemail(channel, msg) { return channel.send(stripIndents` ☎️ New Voicemail from **${this.originDM ? `${this.startUser.tag}'s DMs` : this.origin.guild.name}:** - **${msg.author.tag}:** ${this.cleanContent(msg.content)} + **${msg.author.tag}:** ${this.cleanContent(msg.content, channel.nsfw)} `); } @@ -170,8 +172,9 @@ module.exports = class PhoneCall { return moment.duration(Date.now() - this.timeStarted).format('hh[h]mm[m]ss[s]'); } - cleanContent(str) { + cleanContent(str, nsfw) { str = stripInvites(str); + if (!nsfw && this.client.adultSiteList) str = stripNSFWURLs(str, this.client.adultSiteList); str = preventURLEmbeds(str); return str; } diff --git a/util/Util.js b/util/Util.js index 415ec2b6..7d3ede40 100644 --- a/util/Util.js +++ b/util/Util.js @@ -1,6 +1,7 @@ const crypto = require('crypto'); const { decode: decodeHTML } = require('html-entities'); const { stripIndents } = require('common-tags'); +const url = require('url'); const { SUCCESS_EMOJI_ID } = process.env; const yes = ['yes', 'y', 'ye', 'yeah', 'yup', 'yea', 'ya', 'hai', 'si', 'sí', 'oui', 'はい', 'correct']; const no = ['no', 'n', 'nah', 'nope', 'nop', 'iie', 'いいえ', 'non', 'fuck off']; @@ -204,6 +205,16 @@ module.exports = class Util { return str.replace(/(https?:\/\/\S+)/g, '<$1>'); } + static stripNSFWURLs(str, siteList, text = '[redacted nsfw url]') { + const uris = str.match(/(https?:\/\/\S+)/g); + for (const uri of uris) { + const parsed = url.parse(uri); + if (!siteList.some(pornURL => parsed.host === pornURL)) continue; + str = str.replace(uri, text); + } + return str; + } + static async reactIfAble(msg, user, emoji, fallbackEmoji) { const dm = !msg.guild; if (fallbackEmoji && (!dm && !msg.channel.permissionsFor(user).has('USE_EXTERNAL_EMOJIS'))) {