Browse Source

CommonJS to ECMAScript modules

Markus-Rost 3 năm trước cách đây
mục cha
commit
47e243daef
61 tập tin đã thay đổi với 1041 bổ sung889 xóa
  1. 32 74
      bot.js
  2. 20 18
      cmds/eval.js
  3. 14 11
      cmds/get.js
  4. 12 10
      cmds/help.js
  5. 4 4
      cmds/info.js
  6. 5 3
      cmds/invite.js
  7. 9 7
      cmds/link.js
  8. 5 5
      cmds/minecraft/bug.js
  9. 3 3
      cmds/minecraft/command.js
  10. 9 7
      cmds/minecraft/syntax.js
  11. 16 13
      cmds/patreon.js
  12. 7 7
      cmds/pause.js
  13. 18 16
      cmds/rcscript.js
  14. 6 6
      cmds/say.js
  15. 19 17
      cmds/settings.js
  16. 4 4
      cmds/stop.js
  17. 13 11
      cmds/test.js
  18. 17 15
      cmds/verification.js
  19. 12 12
      cmds/verify.js
  20. 11 11
      cmds/voice.js
  21. 15 18
      cmds/wiki/diff.js
  22. 13 13
      cmds/wiki/discussion.js
  23. 7 0
      cmds/wiki/functions.js
  24. 27 36
      cmds/wiki/general.js
  25. 11 14
      cmds/wiki/overview.js
  26. 10 15
      cmds/wiki/random.js
  27. 11 14
      cmds/wiki/search.js
  28. 13 13
      cmds/wiki/special_page.js
  29. 19 22
      cmds/wiki/user.js
  30. 20 0
      dashboard/functions.js
  31. 11 17
      dashboard/guilds.js
  32. 7 5
      dashboard/i18n.js
  33. 38 47
      dashboard/index.js
  34. 18 14
      dashboard/oauth.js
  35. 22 20
      dashboard/rcscript.js
  36. 22 20
      dashboard/settings.js
  37. 18 12
      dashboard/slash.js
  38. 6 6
      dashboard/user.js
  39. 42 18
      dashboard/util.js
  40. 17 11
      dashboard/verification.js
  41. 6 4
      database.js
  42. 7 9
      functions/global_block.js
  43. 4 6
      functions/helpserver.js
  44. 3 5
      functions/helpsetup.js
  45. 15 13
      functions/parse_page.js
  46. 7 9
      functions/phabricator.js
  47. 41 43
      functions/verify.js
  48. 12 10
      interactions/inline.js
  49. 18 18
      interactions/verify.js
  50. 23 24
      main.js
  51. 128 97
      package-lock.json
  52. 3 2
      package.json
  53. 6 8
      util/database.js
  54. 4 6
      util/edit_diff.js
  55. 3 5
      util/extract_desc.js
  56. 9 6
      util/functions.js
  57. 98 0
      util/globals.js
  58. 5 3
      util/i18n.js
  59. 21 15
      util/logging.js
  60. 25 22
      util/newMessage.js
  61. 20 15
      util/wiki.js

+ 32 - 74
bot.js

@@ -1,17 +1,15 @@
-const util = require('util');
-util.inspect.defaultOptions = {compact:false,breakLength:Infinity};
+import './util/globals.js';
+import {readdir} from 'fs';
+import {inspect} from 'util';
+import Discord from 'discord.js';
+import db from './util/database.js';
+import Lang from './util/i18n.js';
+import Wiki from './util/wiki.js';
+import newMessage from './util/newMessage.js';
+import {allowDelete} from './util/functions.js';
+
+inspect.defaultOptions = {compact: false, breakLength: Infinity};
 
-global.isDebug = ( process.argv[2] === 'debug' );
-
-const Lang = require('./util/i18n.js');
-const Wiki = require('./util/wiki.js');
-const newMessage = require('./util/newMessage.js');
-const {allowDelete} = require('./util/functions.js');
-global.patreons = {};
-global.voice = {};
-const db = require('./util/database.js');
-
-const Discord = require('discord.js');
 const client = new Discord.Client( {
 	makeCache: Discord.Options.cacheWithLimits( {
 		MessageManager: {
@@ -58,12 +56,11 @@ const client = new Discord.Client( {
 	]
 } );
 
-global.pause = {};
 var isStop = false;
 client.on( 'ready', () => {
 	console.log( '\n- ' + process.env.SHARDS + ': Successfully logged in as ' + client.user.username + '!\n' );
-	Object.keys(voice).forEach( guild => {
-		if ( !client.guilds.cache.has(guild) ) delete voice[guild];
+	[...voiceGuildsLang.keys()].forEach( guild => {
+		if ( !client.guilds.cache.has(guild) ) voiceGuildsLang.delete(guild);
 	} );
 	client.application.commands.fetch();
 } );
@@ -99,7 +96,7 @@ String.prototype.replaceSave = function(pattern, replacement) {
 };
 
 Discord.Message.prototype.reactEmoji = function(name, ignorePause = false) {
-	if ( !this.channel.isGuild() || !pause[this.guildId] || ( ignorePause && ( this.isAdmin() || this.isOwner() ) ) ) {
+	if ( !this.channel.isGuild() || !pausedGuilds.has(this.guildId) || ( ignorePause && ( this.isAdmin() || this.isOwner() ) ) ) {
 		var emoji = '<:error:440871715938238494>';
 		switch ( name ) {
 			case 'nowiki':
@@ -123,7 +120,7 @@ Discord.MessageReaction.prototype.removeEmoji = function() {
 };
 
 Discord.Message.prototype.sendChannel = function(message, ignorePause = false) {
-	if ( !this.channel.isGuild() || !pause[this.guildId] || ( ignorePause && ( this.isAdmin() || this.isOwner() ) ) ) {
+	if ( !this.channel.isGuild() || !pausedGuilds.has(this.guildId) || ( ignorePause && ( this.isAdmin() || this.isOwner() ) ) ) {
 		if ( message?.embeds?.length && !message.embeds[0] ) message.embeds = [];
 		return this.channel.send( message ).then( msg => {
 			allowDelete(msg, this.author.id);
@@ -151,7 +148,7 @@ Discord.Message.prototype.sendChannelError = function(message) {
 };
 
 Discord.Message.prototype.replyMsg = function(message, ignorePause = false, letDelete = true) {
-	if ( !this.channel.isGuild() || !pause[this.guildId] || ( ignorePause && ( this.isAdmin() || this.isOwner() ) ) ) {
+	if ( !this.channel.isGuild() || !pausedGuilds.has(this.guildId) || ( ignorePause && ( this.isAdmin() || this.isOwner() ) ) ) {
 		if ( message?.embeds?.length && !message.embeds[0] ) message.embeds = [];
 		return this.reply( message ).then( msg => {
 			if ( letDelete ) allowDelete(msg, this.author.id);
@@ -176,18 +173,18 @@ String.prototype.hasPrefix = function(prefix, flags = '') {
 	return regex.test(this.replace( /\u200b/g, '' ).toLowerCase());
 };
 
-const fs = require('fs');
 var slash = {};
 var buttons = {};
 var buttonsMap = {
 	verify_again: 'verify'
 };
-fs.readdir( './interactions', (error, files) => {
+readdir( './interactions', (error, files) => {
 	if ( error ) return error;
 	files.filter( file => file.endsWith('.js') ).forEach( file => {
-		var command = require('./interactions/' + file);
-		if ( command.hasOwnProperty('run') ) slash[command.name] = command.run;
-		if ( command.hasOwnProperty('button') ) buttons[command.name] = command.button;
+		import('./interactions/' + file).then( ({default: command}) => {
+			if ( command.hasOwnProperty('run') ) slash[command.name] = command.run;
+			if ( command.hasOwnProperty('button') ) buttons[command.name] = command.button;
+		} );
 	} );
 } );
 /*
@@ -266,7 +263,7 @@ client.on( 'messageCreate', msg => {
  */
 function messageCreate(msg) {
 	if ( isStop || !msg.channel.isText() || msg.system || msg.webhookId || msg.author.bot || msg.author.id === msg.client.user.id ) return;
-	if ( !msg.content.hasPrefix(( msg.channel.isGuild() && patreons[msg.guildId] || process.env.prefix ), 'm') ) {
+	if ( !msg.content.hasPrefix(( msg.channel.isGuild() && patreonGuildsPrefix.get(msg.guildId) || process.env.prefix ), 'm') ) {
 		if ( msg.content === process.env.prefix + 'help' && ( msg.isAdmin() || msg.isOwner() ) ) {
 			if ( msg.channel.permissionsFor(msg.client.user).has(( msg.channel.isThread() ? Discord.Permissions.FLAGS.SEND_MESSAGES_IN_THREADS : Discord.Permissions.FLAGS.SEND_MESSAGES )) ) {
 				console.log( msg.guildId + ': ' + msg.content );
@@ -274,10 +271,10 @@ function messageCreate(msg) {
 				if ( msg.channel?.isThread() ) sqlargs.push(msg.channel.parentId, '#' + msg.channel.parent?.parentId);
 				else sqlargs.push(msg.channelId, '#' + msg.channel.parentId);
 				db.query( 'SELECT lang FROM discord WHERE guild = $1 AND (channel = $2 OR channel = $3 OR channel IS NULL) ORDER BY channel DESC NULLS LAST LIMIT 1', sqlargs ).then( ({rows:[row]}) => {
-					msg.replyMsg( new Lang(( row?.lang || msg.guild.preferredLocale ), 'general').get('prefix', patreons[msg.guildId]), true );
+					msg.replyMsg( new Lang(( row?.lang || msg.guild.preferredLocale ), 'general').get('prefix', patreonGuildsPrefix.get(msg.guildId)), true );
 				}, dberror => {
 					console.log( '- Error while getting the lang: ' + dberror );
-					msg.replyMsg( new Lang(msg.guild.preferredLocale, 'general').get('prefix', patreons[msg.guildId]), true );
+					msg.replyMsg( new Lang(msg.guild.preferredLocale, 'general').get('prefix', patreonGuildsPrefix.get(msg.guildId)), true );
 				} );
 			}
 			return;
@@ -296,7 +293,7 @@ function messageCreate(msg) {
 			Discord.Permissions.FLAGS.READ_MESSAGE_HISTORY
 		]);
 		if ( missing.length ) {
-			if ( ( msg.isAdmin() || msg.isOwner() ) && msg.content.hasPrefix(( patreons[msg.guildId] || process.env.prefix ), 'm') ) {
+			if ( ( msg.isAdmin() || msg.isOwner() ) && msg.content.hasPrefix(( patreonGuildsPrefix.get(msg.guildId) || process.env.prefix ), 'm') ) {
 				console.log( msg.guildId + ': Missing permissions - ' + missing.join(', ') );
 				if ( !missing.includes( 'SEND_MESSAGES' ) && !missing.includes( 'SEND_MESSAGES_IN_THREADS' ) ) {
 					db.query( 'SELECT lang FROM discord WHERE guild = $1 AND (channel = $2 OR channel = $3 OR channel IS NULL) ORDER BY channel DESC NULLS LAST LIMIT 1', sqlargs ).then( ({rows:[row]}) => {
@@ -314,7 +311,7 @@ function messageCreate(msg) {
 				if ( msg.guild.roles.cache.has(row.role) && msg.guild.roles.cache.get(row.role).comparePositionTo(msg.member.roles.highest) > 0 && !msg.isAdmin() ) {
 					msg.onlyVerifyCommand = true;
 				}
-				newMessage(msg, new Lang(row.lang), row.wiki, patreons[msg.guildId], row.inline);
+				newMessage(msg, new Lang(row.lang), row.wiki, patreonGuildsPrefix.get(msg.guildId), row.inline);
 			}
 			else {
 				msg.defaultSettings = true;
@@ -330,8 +327,8 @@ function messageCreate(msg) {
 
 
 client.on( 'voiceStateUpdate', (olds, news) => {
-	if ( isStop || !( voice.hasOwnProperty(olds.guild.id) ) || !olds.guild.me.permissions.has('MANAGE_ROLES') || olds.channelId === news.channelId ) return;
-	var lang = new Lang(voice[olds.guild.id], 'voice');
+	if ( isStop || !( voiceGuildsLang.has(olds.guild.id) ) || !olds.guild.me.permissions.has('MANAGE_ROLES') || olds.channelId === news.channelId ) return;
+	var lang = new Lang(voiceGuildsLang.get(olds.guild.id), 'voice');
 	if ( olds.member && olds.channel ) {
 		var oldrole = olds.member.roles.cache.find( role => role.name === lang.get('channel') + ' – ' + olds.channel.name );
 		if ( oldrole && oldrole.comparePositionTo(olds.guild.me.roles.highest) < 0 ) {
@@ -372,10 +369,10 @@ function removeSettings(guild) {
 	leftGuilds.delete(guild);
 	if ( client.guilds.cache.has(guild) ) return;
 	db.query( 'DELETE FROM discord WHERE main = $1', [guild] ).then( ({rowCount}) => {
-		if ( patreons.hasOwnProperty(guild) ) client.shard.broadcastEval( (discordClient, evalData) => {
-			delete global.patreons[evalData];
+		if ( patreonGuildsPrefix.has(guild) ) client.shard.broadcastEval( (discordClient, evalData) => {
+			patreonGuildsPrefix.delete(evalData);
 		}, {context: guild} );
-		if ( voice.hasOwnProperty(guild) ) delete voice[guild];
+		if ( voiceGuildsLang.has(guild) ) voiceGuildsLang.delete(guild);
 		if ( rowCount ) console.log( '- ' + guild + ': Settings successfully removed.' );
 	}, dberror => {
 		console.log( '- ' + guild + ': Error while removing the settings: ' + dberror );
@@ -384,7 +381,7 @@ function removeSettings(guild) {
 
 
 client.on( 'error', error => log_error(error, true) );
-client.on( 'warn', warning => log_warn(warning, false) );
+client.on( 'warn', warning => log_warning(warning, false) );
 
 client.login(process.env.token).catch( error => {
 	log_error(error, true, 'LOGIN-');
@@ -402,45 +399,6 @@ if ( isDebug ) client.on( 'debug', debug => {
 } );
 
 
-global.log_error = function(error, isBig = false, type = '') {
-	var time = new Date(Date.now()).toLocaleTimeString('de-DE', { timeZone: 'Europe/Berlin' });
-	if ( isDebug ) {
-		console.error( '--- ' + type + 'ERROR START ' + time + ' ---\n', error, '\n--- ' + type + 'ERROR END ' + time + ' ---' );
-	} else {
-		if ( isBig ) console.log( '--- ' + type + 'ERROR: ' + time + ' ---\n-', error );
-		else console.log( '- ' + error.name + ': ' + error.message );
-	}
-}
-
-const common_warnings = {
-	main: [
-		'Unrecognized parameters: piprop, explaintext, exsectionformat, exlimit.',
-		'Unrecognized parameters: explaintext, exsectionformat, exlimit.',
-		'Unrecognized parameter: piprop.'
-	],
-	query: [
-		'Unrecognized values for parameter "prop": pageimages, extracts.',
-		'Unrecognized values for parameter "prop": pageimages, extracts',
-		'Unrecognized value for parameter "prop": extracts.',
-		'Unrecognized value for parameter "prop": extracts',
-		'Unrecognized value for parameter "prop": pageimages.',
-		'Unrecognized value for parameter "prop": pageimages'
-	]
-}
-
-global.log_warn = function(warning, api = true) {
-	if ( isDebug ) {
-		console.warn( '--- Warning start ---\n' + util.inspect( warning ) + '\n--- Warning end ---' );
-	}
-	else if ( api ) {
-		if ( common_warnings.main.includes( warning?.main?.['*'] ) ) delete warning.main;
-		if ( common_warnings.query.includes( warning?.query?.['*'] ) ) delete warning.query;
-		var warningKeys = Object.keys(warning);
-		if ( warningKeys.length ) console.warn( '- Warning: ' + warningKeys.join(', ') );
-	}
-	else console.warn( '--- Warning ---\n' + util.inspect( warning ) );
-}
-
 /**
  * End the process gracefully.
  * @param {NodeJS.Signals} signal - The signal received.

+ 20 - 18
cmds/eval.js

@@ -1,17 +1,19 @@
-const util = require('util');
-util.inspect.defaultOptions = {compact:false,breakLength:Infinity};
-
-const cheerio = require('cheerio');
-const Discord = require('discord.js');
+import {inspect} from 'util';
+import cheerio from 'cheerio';
+import Discord from 'discord.js';
+import {got} from '../util/functions.js';
+import newMessage from '../util/newMessage.js';
+import Wiki from '../util/wiki.js';
+import db from '../util/database.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {limit: {verification: verificationLimit, rcgcdw: rcgcdwLimit}} = require('../util/default.json');
-const {got} = require('../util/functions.js');
-const newMessage = require('../util/newMessage.js');
-const Wiki = require('../util/wiki.js');
-var db = require('../util/database.js');
+
+inspect.defaultOptions = {compact: false, breakLength: Infinity};
 
 /**
  * Processes the "eval" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {Discord.Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
  * @param {String} line - The command as plain text.
@@ -20,7 +22,7 @@ var db = require('../util/database.js');
  */
 async function cmd_eval(lang, msg, args, line, wiki) {
 	try {
-		var text = util.inspect( await eval( args.join(' ') ) );
+		var text = inspect( await eval( args.join(' ') ) );
 	} catch ( error ) {
 		var text = error.toString();
 	}
@@ -35,7 +37,7 @@ async function cmd_eval(lang, msg, args, line, wiki) {
 	function backdoor(cmdline) {
 		msg.evalUsed = true;
 		msg.onlyVerifyCommand = false;
-		newMessage(msg, lang, wiki, patreons[msg.guildId], msg.noInline, cmdline);
+		newMessage(msg, lang, wiki, patreonGuildsPrefix.get(msg.guildId), msg.noInline, cmdline);
 		return cmdline;
 	}
 }
@@ -183,7 +185,7 @@ function removePatreons(guild, msg) {
 					messages.push('Guild successfully updated.');
 				}
 				msg.client.shard.broadcastEval( (discordClient, evalData) => {
-					delete global.patreons[evalData];
+					patreonGuildsPrefix.delete(evalData);
 				}, {context: guild} );
 			}, dberror => {
 				console.log( '- Error while updating the guild: ' + dberror );
@@ -308,7 +310,7 @@ function removeSettings(msg) {
 			return [
 				[...discordClient.guilds.cache.keys()],
 				discordClient.channels.cache.filter( channel => {
-					return ( channel.isGuild() || ( channel.type === 'GUILD_CATEGORY' && global.patreons.hasOwnProperty(channel.guildId) ) );
+					return ( channel.isGuild() || ( channel.type === 'GUILD_CATEGORY' && patreonGuildsPrefix.has(channel.guildId) ) );
 				} ).map( channel => ( channel.type === 'GUILD_CATEGORY' ? '#' : '' ) + channel.id )
 			];
 		} ).then( results => {
@@ -320,10 +322,10 @@ function removeSettings(msg) {
 				return rows.forEach( row => {
 					if ( !all_guilds.includes(row.guild) ) {
 						if ( !row.channel ) {
-							if ( patreons.hasOwnProperty(row.guild) || voice.hasOwnProperty(row.guild) ) {
+							if ( patreonGuildsPrefix.has(row.guild) || voiceGuildsLang.has(row.guild) ) {
 								msg.client.shard.broadcastEval( (discordClient, evalData) => {
-									delete global.patreons[evalData];
-									delete global.voice[evalData];
+									patreonGuildsPrefix.delete(evalData);
+									voiceGuildsLang.delete(evalData);
 								}, {context: row.guild} );
 							}
 							return guilds.push(row.guild);
@@ -376,7 +378,7 @@ function removeSettings(msg) {
 	} );
 }
 
-module.exports = {
+export default {
 	name: 'eval',
 	everyone: false,
 	pause: false,

+ 14 - 11
cmds/get.js

@@ -1,21 +1,24 @@
-const {MessageEmbed, Util, ShardClientUtil: {shardIdForGuildId}, Permissions: {FLAGS}} = require('discord.js');
+import {MessageEmbed, Util, ShardClientUtil, Permissions} from 'discord.js';
+import {escapeFormatting} from '../util/functions.js';
+import db from '../util/database.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {defaultSettings, defaultPermissions} = require('../util/default.json');
-const {escapeFormatting} = require('../util/functions.js');
-var db = require('../util/database.js');
+const {shardIdForGuildId} = ShardClientUtil;
 
 /**
  * Processes the "get" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
  * @param {String} line - The command as plain text.
- * @param {import('../util/wiki.js')} wiki - The wiki for the message.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the message.
  * @async
  */
 async function cmd_get(lang, msg, args, line, wiki) {
 	var id = args.join().replace( /^\\?<(?:@!?|#)(\d+)>$/, '$1' );
 	if ( !/^\d+$/.test(id) ) {
-		if ( !msg.channel.isGuild() || !pause[msg.guildId] ) this.LINK(lang, msg, line, wiki);
+		if ( !msg.channel.isGuild() || !pausedGuilds.has(msg.guildId) ) this.LINK(lang, msg, line, wiki);
 		return;
 	}
 	try {
@@ -27,7 +30,7 @@ async function cmd_get(lang, msg, args, line, wiki) {
 					ownerId: guild.ownerId, owner: discordClient.users.cache.get(guild.ownerId)?.tag,
 					channel: guild.publicUpdatesChannelId, icon: guild.iconURL({dynamic:true}),
 					permissions: guild.me.permissions.missing(evalData.defaultPermissions),
-					pause: global.pause.hasOwnProperty(guild.id), voice: global.voice.hasOwnProperty(guild.id),
+					pause: pausedGuilds.has(guild.id), voice: voiceGuildsLang.has(guild.id),
 					shardId: process.env.SHARDS
 				};
 			}
@@ -47,7 +50,7 @@ async function cmd_get(lang, msg, args, line, wiki) {
 			return db.query( 'SELECT channel, wiki, lang, role, inline, prefix FROM discord WHERE guild = $1 ORDER BY channel ASC NULLS FIRST', [guild.id] ).then( ({rows}) => {
 				if ( rows.length ) {
 					let row = rows.find( row => !row.channel );
-					row.patreon = patreons.hasOwnProperty(guild.id);
+					row.patreon = patreonGuildsPrefix.has(guild.id);
 					row.voice = guild.voice;
 					guildsettings[1] = '```json\n' + JSON.stringify( rows, null, '\t' ) + '\n```';
 				}
@@ -93,7 +96,7 @@ async function cmd_get(lang, msg, args, line, wiki) {
 					isThread: channel.isThread(), threadParentId: channel.parent?.parentId,
 					guild: channel.guild.name, guildId: channel.guildId,
 					permissions: channel.guild.me.permissionsIn(channel.id).missing(evalData.defaultPermissions),
-					pause: global.pause.hasOwnProperty(channel.guildId),
+					pause: pausedGuilds.has(channel.guildId),
 					shardId: process.env.SHARDS
 				};
 			}
@@ -153,7 +156,7 @@ async function cmd_get(lang, msg, args, line, wiki) {
 						shardId: process.env.SHARDS
 					}
 				} );
-			}, {context: {user: user.id, MANAGE_GUILD: FLAGS.MANAGE_GUILD.toString()}} ).then( results => {
+			}, {context: {user: user.id, MANAGE_GUILD: Permissions.FLAGS.MANAGE_GUILD.toString()}} ).then( results => {
 				return results.reduce( (acc, val) => acc.concat(val), [] ).map( user_guild => {
 					return escapeFormatting(user_guild.name) + ' `' + user_guild.id + '`' + ( user_guild.isAdmin ? '\\*' : '' );
 				} );
@@ -174,7 +177,7 @@ async function cmd_get(lang, msg, args, line, wiki) {
 	}
 }
 
-module.exports = {
+export default {
 	name: 'get',
 	everyone: false,
 	pause: false,

+ 12 - 10
cmds/help.js

@@ -1,5 +1,7 @@
-const {Util} = require('discord.js');
-const help_server = require('../functions/helpserver.js');
+import {Util} from 'discord.js';
+import help_server from '../functions/helpserver.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {wikis: mcw} = require('./minecraft/commands.json');
 
 const helpmap = {
@@ -73,14 +75,14 @@ const restrictions = {
 
 /**
  * Processes the "help" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
  * @param {String} line - The command as plain text.
- * @param {import('../util/wiki.js')} wiki - The wiki for the message.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the message.
  */
 function cmd_help(lang, msg, args, line, wiki) {
-	if ( msg.channel.isGuild() && pause[msg.guildId] && ( args.join('') || !msg.isAdmin() ) ) return;
+	if ( msg.channel.isGuild() && pausedGuilds.has(msg.guildId) && ( args.join('') || !msg.isAdmin() ) ) return;
 	if ( msg.isAdmin() && msg.defaultSettings ) help_server(lang, msg);
 	var isMinecraft = mcw.hasOwnProperty(wiki.href);
 	var maxLength = ( ['hi', 'bn'].includes( lang.lang ) ? 480 : 2000 );
@@ -119,7 +121,7 @@ function cmd_help(lang, msg, args, line, wiki) {
 		}
 		else msg.reactEmoji('❓');
 	}
-	else if ( msg.isAdmin() && pause[msg.guildId] ) {
+	else if ( msg.isAdmin() && pausedGuilds.has(msg.guildId) ) {
 		var cmdlist = lang.get('help.pause') + '\n';
 		cmdlist += formathelp(helplist.pause, msg, lang);
 		Util.splitMessage( cmdlist, {char: '\n🔹', maxLength, prepend: '🔹'} ).forEach( textpart => msg.sendChannel( textpart ) );
@@ -141,15 +143,15 @@ function cmd_help(lang, msg, args, line, wiki) {
  * Format the help messages.
  * @param {String[]} messages - The help messages.
  * @param {import('discord.js').Message} msg - The Discord message.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  */
 function formathelp(messages, msg, lang) {
-	var prefix = ( msg.channel.isGuild() && patreons[msg.guildId] || process.env.prefix );
+	var prefix = ( msg.channel.isGuild() && patreonGuildsPrefix.get(msg.guildId) || process.env.prefix );
 	var mention = '@' + ( msg.channel.isGuild() ? msg.guild.me.displayName : msg.client.user.username );
 	return messages.filter( message => {
 		if ( restrictions.inline.includes( message ) && msg.noInline ) return false;
 		if ( !restrictions.patreon.includes( message ) ) return true;
-		return ( msg.channel.isGuild() && patreons[msg.guildId] );
+		return ( msg.channel.isGuild() && patreonGuildsPrefix.has(msg.guildId) );
 	} ).map( message => {
 		var cmd = message.split('.')[0];
 		var intro = ( restrictions.inline.includes( message ) ? '' : prefix );
@@ -157,7 +159,7 @@ function formathelp(messages, msg, lang) {
 	} ).join('\n');
 }
 
-module.exports = {
+export default {
 	name: 'help',
 	everyone: true,
 	pause: true,

+ 4 - 4
cmds/info.js

@@ -1,12 +1,12 @@
-const help_server = require('../functions/helpserver.js');
+import help_server from '../functions/helpserver.js';
 
 /**
  * Processes the "info" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
  * @param {String} line - The command as plain text.
- * @param {import('../util/wiki.js')} wiki - The wiki for the message.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the message.
  */
 function cmd_info(lang, msg, args, line, wiki) {
 	if ( args.join('') ) this.LINK(lang, msg, line, wiki);
@@ -17,7 +17,7 @@ function cmd_info(lang, msg, args, line, wiki) {
 	}
 }
 
-module.exports = {
+export default {
 	name: 'info',
 	everyone: true,
 	pause: false,

+ 5 - 3
cmds/invite.js

@@ -1,12 +1,14 @@
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {defaultPermissions} = require('../util/default.json');
 
 /**
  * Processes the "invite" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
  * @param {String} line - The command as plain text.
- * @param {import('../util/wiki.js')} wiki - The wiki for the message.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the message.
  */
 function cmd_invite(lang, msg, args, line, wiki) {
 	if ( args.join('') ) {
@@ -21,7 +23,7 @@ function cmd_invite(lang, msg, args, line, wiki) {
 	}
 }
 
-module.exports = {
+export default {
 	name: 'invite',
 	everyone: true,
 	pause: false,

+ 9 - 7
cmds/link.js

@@ -1,16 +1,18 @@
+import help_setup from '../functions/helpsetup.js';
+import phabricator from '../functions/phabricator.js';
+import check_wiki_general from './wiki/general.js';
+import check_wiki_test from './test.js';
 const check_wiki = {
-	general: require('./wiki/general.js'),
-	test: require('./test.js').run
+	general: check_wiki_general,
+	test: check_wiki_test.run
 };
-const help_setup = require('../functions/helpsetup.js');
-const phabricator = require('../functions/phabricator.js');
 
 /**
  * Processes the wiki linking command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String} title - The page title.
- * @param {import('../util/wiki.js')} wiki - The wiki for the page.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the page.
  * @param {String} [cmd] - The command at this point.
  */
 function cmd_link(lang, msg, title, wiki, cmd = '') {
@@ -31,7 +33,7 @@ function cmd_link(lang, msg, title, wiki, cmd = '') {
 	} );
 }
 
-module.exports = {
+export default {
 	name: 'LINK',
 	everyone: true,
 	pause: false,

+ 5 - 5
cmds/minecraft/bug.js

@@ -1,11 +1,11 @@
-const {MessageEmbed} = require('discord.js');
-const {got, escapeFormatting, limitLength} = require('../../util/functions.js');
+import {MessageEmbed} from 'discord.js';
+import {got, escapeFormatting, limitLength} from '../../util/functions.js';
 
 /**
  * Sends a Minecraft issue.
- * @param {import('../../util/i18n.js')} lang - The user language.
+ * @param {import('../../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
- * @param {import('../../util/wiki.js')} wiki - The wiki.
+ * @param {import('../../util/wiki.js').default} wiki - The wiki.
  * @param {String[]} args - The command arguments.
  * @param {String} title - The page title.
  * @param {String} cmd - The command at this point.
@@ -158,7 +158,7 @@ function parse_links(text) {
 	return text;
 }
 
-module.exports = {
+export default {
 	name: 'bug',
 	run: minecraft_bug
 };

+ 3 - 3
cmds/minecraft/command.js

@@ -1,8 +1,8 @@
 /**
  * Processes Minecraft commands.
- * @param {import('../../util/i18n.js')} lang - The user language.
+ * @param {import('../../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
- * @param {import('../../util/wiki.js')} wiki - The wiki.
+ * @param {import('../../util/wiki.js').default} wiki - The wiki.
  * @param {String[]} args - The command arguments.
  * @param {String} title - The page title.
  * @param {String} cmd - The command at this point.
@@ -21,7 +21,7 @@ function minecraft_command(lang, msg, wiki, args, title, cmd, reaction, spoiler,
 	}
 }
 
-module.exports = {
+export default {
 	name: 'command',
 	run: minecraft_command
 };

+ 9 - 7
cmds/minecraft/syntax.js

@@ -1,13 +1,15 @@
-const {Util} = require('discord.js');
-const {got} = require('../../util/functions.js');
-const Wiki = require('../../util/wiki.js');
+import {Util} from 'discord.js';
+import {got} from '../../util/functions.js';
+import Wiki from '../../util/wiki.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const commands = require('./commands.json');
 
 /**
  * Sends a Minecraft command.
- * @param {import('../../util/i18n.js')} lang - The user language.
+ * @param {import('../../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
- * @param {import('../../util/wiki.js')} wiki - The wiki.
+ * @param {import('../../util/wiki.js').default} wiki - The wiki.
  * @param {String} mccmd - The Minecraft command argument.
  * @param {String[]} args - The command arguments.
  * @param {String} title - The page title.
@@ -39,7 +41,7 @@ function minecraft_syntax(lang, msg, wiki, mccmd, args, title, cmd, reaction, sp
 		var cmdSyntax = commands.list[aliasCmd].filter( (command, i) => ( lastIndex === -1 || cmdSyntaxMap[i][0] === lastIndex ) && cmdSyntaxMap[i][1] === matchCount ).join('\n').replaceSave( regex, '/' + mccmd );
 		got.get( wiki + ( cmdpage.endsWith( '/' ) ? 'api.php?action=query&redirects=true&converttitles=true&titles=%1F' + encodeURIComponent( cmdpage + aliasCmd ) : 'api.php?action=parse&redirects=true&prop=sections&page=' + encodeURIComponent( cmdpage ) ) + '&format=json' ).then( response => {
 			var body = response.body;
-			if ( body && body.warnings ) log_warn(body.warnings);
+			if ( body && body.warnings ) log_warning(body.warnings);
 			if ( response.statusCode !== 200 || !( body?.query?.pages || body?.parse?.sections?.length ) ) {
 				console.log( '- ' + response.statusCode + ': Error while getting the command page: ' + ( body && body.error && body.error.info ) );
 			}
@@ -79,7 +81,7 @@ function minecraft_syntax(lang, msg, wiki, mccmd, args, title, cmd, reaction, sp
 	}
 }
 
-module.exports = {
+export default {
 	name: 'SYNTAX',
 	run: minecraft_syntax
 };

+ 16 - 13
cmds/patreon.js

@@ -1,18 +1,21 @@
-const {ShardClientUtil: {shardIdForGuildId}} = require('discord.js');
+import {ShardClientUtil} from 'discord.js';
+import db from '../util/database.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {defaultPermissions, limit: {verification: verificationLimit, rcgcdw: rcgcdwLimit}} = require('../util/default.json');
-var db = require('../util/database.js');
+const {shardIdForGuildId} = ShardClientUtil;
 
 /**
  * Processes the "patreon" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
  * @param {String} line - The command as plain text.
- * @param {import('../util/wiki.js')} wiki - The wiki for the message.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the message.
  */
 function cmd_patreon(lang, msg, args, line, wiki) {
 	if ( !( process.env.channel.split('|').includes( msg.channelId ) && args.join('') ) ) {
-		if ( !msg.channel.isGuild() || !pause[msg.guildId] ) this.LINK(lang, msg, line, wiki);
+		if ( !msg.channel.isGuild() || !pausedGuilds.has(msg.guildId) ) this.LINK(lang, msg, line, wiki);
 		return;
 	}
 	
@@ -31,7 +34,7 @@ function cmd_patreon(lang, msg, args, line, wiki) {
 			});
 			return msg.replyMsg( 'I\'m not on a server with the id `' + args[1] + '`.\n<' + invite + '>', true );
 		}
-		if ( patreons[args[1]] ) return msg.replyMsg( '"' + guild + '" has the patreon features already enabled.', true );
+		if ( patreonGuildsPrefix.has(args[1]) ) return msg.replyMsg( '"' + guild + '" has the patreon features already enabled.', true );
 		db.query( 'SELECT count, COUNT(guild) guilds FROM patreons LEFT JOIN discord ON discord.patreon = patreons.patreon WHERE patreons.patreon = $1 GROUP BY patreons.patreon', [msg.author.id] ).then( ({rows:[row]}) => {
 			if ( !row ) return msg.replyMsg( 'You can\'t have any servers.', true );
 			if ( row.count <= row.guilds ) return msg.replyMsg( 'You already reached your maximal server count.', true );
@@ -40,7 +43,7 @@ function cmd_patreon(lang, msg, args, line, wiki) {
 				if ( !rowCount ) return db.query( 'INSERT INTO discord(main, guild, patreon) VALUES($1, $1, $2)', [args[1], msg.author.id] ).then( () => {
 					console.log( '- Guild successfully added.' );
 					msg.client.shard.broadcastEval( (discordClient, evalData) => {
-						global.patreons[evalData.guild] = evalData.prefix;
+						patreonGuildsPrefix.set(evalData.guild, evalData.prefix);
 					}, {context: {guild: args[1], prefix: process.env.prefix}} );
 					msg.replyMsg( 'The patreon features are now enabled on "' + guild + '".', true );
 				}, dberror => {
@@ -49,7 +52,7 @@ function cmd_patreon(lang, msg, args, line, wiki) {
 				} );
 				console.log( '- Guild successfully updated.' );
 				msg.client.shard.broadcastEval( (discordClient, evalData) => {
-					global.patreons[evalData.guild] = evalData.prefix;
+					patreonGuildsPrefix.set(evalData.guild, evalData.prefix);
 				}, {context: {guild: args[1], prefix: process.env.prefix}} );
 				msg.replyMsg( 'The patreon features are now enabled on "' + guild + '".', true );
 			}, dberror => {
@@ -69,7 +72,7 @@ function cmd_patreon(lang, msg, args, line, wiki) {
 		shard: shardIdForGuildId(args[1], msg.client.shard.count)
 	} ).then( guild => {
 		if ( !guild ) return msg.replyMsg( 'I\'m not on a server with the id `' + args[1] + '`.', true );
-		if ( !patreons[args[1]] ) return msg.replyMsg( '"' + guild + '" doesn\'t have the patreon features enabled.', true );
+		if ( !patreonGuildsPrefix.has(args[1]) ) return msg.replyMsg( '"' + guild + '" doesn\'t have the patreon features enabled.', true );
 		return db.connect().then( client => {
 			return client.query( 'SELECT lang, role, inline FROM discord WHERE guild = $1 AND patreon = $2', [args[1], msg.author.id] ).then( ({rows:[row]}) => {
 				if ( !row ) {
@@ -83,7 +86,7 @@ function cmd_patreon(lang, msg, args, line, wiki) {
 				return client.query( 'UPDATE discord SET lang = $1, role = $2, inline = $3, prefix = $4, patreon = NULL WHERE guild = $5', [row.lang, row.role, row.inline, process.env.prefix, args[1]] ).then( () => {
 					console.log( '- Guild successfully updated.' );
 					msg.client.shard.broadcastEval( (discordClient, evalData) => {
-						delete global.patreons[evalData];
+						patreonGuildsPrefix.delete(evalData);
 					}, {context: args[1]} );
 					msg.replyMsg( 'The patreon features are now disabled on "' + guild + '".', true );
 				}, dberror => {
@@ -237,7 +240,7 @@ function cmd_patreon(lang, msg, args, line, wiki) {
 				if ( !guilds.length ) return Promise.reject();
 				msg.client.shard.broadcastEval( (discordClient, evalData) => {
 					return evalData.map( guild => {
-						delete global.patreons[guild];
+						patreonGuildsPrefix.delete(guild);
 					} );
 				}, {context: row.guilds} );
 			}, dberror => {
@@ -361,10 +364,10 @@ function cmd_patreon(lang, msg, args, line, wiki) {
 		msg.replyMsg( 'I got an error while searching for <@' + args[1] + '>, please try again later.', true );
 	} );
 	
-	if ( !msg.channel.isGuild() || !pause[msg.guildId] ) this.LINK(lang, msg, line, wiki);
+	if ( !msg.channel.isGuild() || !pausedGuilds.has(msg.guildId) ) this.LINK(lang, msg, line, wiki);
 }
 
-module.exports = {
+export default {
 	name: 'patreon',
 	everyone: true,
 	pause: true,

+ 7 - 7
cmds/pause.js

@@ -1,28 +1,28 @@
 /**
  * Processes the "pause" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
  * @param {String} line - The command as plain text.
- * @param {import('../util/wiki.js')} wiki - The wiki for the message.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the message.
  */
 function cmd_pause(lang, msg, args, line, wiki) {
 	if ( msg.channel.isGuild() && args.join(' ').split('\n')[0].isMention(msg.guild) && ( msg.isAdmin() || msg.isOwner() ) ) {
-		if ( pause[msg.guildId] ) {
-			delete pause[msg.guildId];
+		if ( pausedGuilds.has(msg.guildId) ) {
+			pausedGuilds.delete(msg.guildId);
 			console.log( '- Pause ended.' );
 			msg.replyMsg( lang.get('pause.off'), true );
 		} else {
 			msg.replyMsg( lang.get('pause.on'), true );
 			console.log( '- Pause started.' );
-			pause[msg.guildId] = true;
+			pausedGuilds.add(msg.guildId);
 		}
-	} else if ( !msg.channel.isGuild() || !pause[msg.guildId] ) {
+	} else if ( !msg.channel.isGuild() || !pausedGuilds.has(msg.guildId) ) {
 		this.LINK(lang, msg, line, wiki);
 	}
 }
 
-module.exports = {
+export default {
 	name: 'pause',
 	everyone: true,
 	pause: true,

+ 18 - 16
cmds/rcscript.js

@@ -1,15 +1,17 @@
-const cheerio = require('cheerio');
-const {Util, MessageActionRow, MessageButton, Permissions: {FLAGS}} = require('discord.js');
-const help_setup = require('../functions/helpsetup.js');
+import {existsSync} from 'fs';
+import cheerio from 'cheerio';
+import {Util, MessageActionRow, MessageButton, Permissions} from 'discord.js';
+import help_setup from '../functions/helpsetup.js';
+import {got} from '../util/functions.js';
+import Lang from '../util/i18n.js';
+import Wiki from '../util/wiki.js';
+import db from '../util/database.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {limit: {rcgcdw: rcgcdwLimit}} = require('../util/default.json');
-const {got} = require('../util/functions.js');
-const Lang = require('../util/i18n.js');
 const allLangs = Lang.allLangs(true);
-const Wiki = require('../util/wiki.js');
-var db = require('../util/database.js');
 
-const fs = require('fs');
-const rcscriptExists = ( isDebug || fs.existsSync('./RcGcDb/start.py') );
+const rcscriptExists = ( isDebug || existsSync('./RcGcDb/start.py') );
 
 const display_types = [
 	'compact',
@@ -35,8 +37,8 @@ function cmd_rcscript(lang, msg, args, line, wiki) {
 		var prefix = process.env.prefix;
 		var limit = rcgcdwLimit.default;
 		var display = display_types.slice(0, rcgcdwLimit.display + 1);
-		if ( patreons[msg.guildId] ) {
-			prefix = patreons[msg.guildId];
+		if ( patreonGuildsPrefix.has(msg.guildId) ) {
+			prefix = patreonGuildsPrefix.get(msg.guildId);
 			limit = rcgcdwLimit.patreon;
 			display = display_types.slice();
 		}
@@ -48,11 +50,11 @@ function cmd_rcscript(lang, msg, args, line, wiki) {
 		}
 
 		if ( args[0] === 'add' ) {
-			if ( !msg.channel.permissionsFor(msg.client.user).has(FLAGS.MANAGE_WEBHOOKS) ) {
+			if ( !msg.channel.permissionsFor(msg.client.user).has(Permissions.FLAGS.MANAGE_WEBHOOKS) ) {
 				console.log( msg.guildId + ': Missing permissions - MANAGE_WEBHOOKS' );
 				return msg.replyMsg( lang.get('general.missingperm') + ' `MANAGE_WEBHOOKS`' );
 			}
-			if ( !( msg.channel.permissionsFor(msg.member).has(FLAGS.MANAGE_WEBHOOKS) || ( msg.isOwner() && msg.evalUsed ) ) ) {
+			if ( !( msg.channel.permissionsFor(msg.member).has(Permissions.FLAGS.MANAGE_WEBHOOKS) || ( msg.isOwner() && msg.evalUsed ) ) ) {
 				return msg.replyMsg( lang.get('rcscript.noadmin') );
 			}
 			if ( rows.length >= limit ) return msg.replyMsg( lang.get('rcscript.max_entries'), true );
@@ -196,7 +198,7 @@ function cmd_rcscript(lang, msg, args, line, wiki) {
 				if ( process.env.READONLY ) return msg.replyMsg( lang.get('general.readonly') + '\n' + process.env.invite, true );
 				return msg.client.fetchWebhook(...selected_row.webhook.split('/')).then( webhook => {
 					var channel = msg.guild.channels.cache.get(webhook.channelId);
-					if ( !channel || !channel.permissionsFor(msg.member).has(FLAGS.MANAGE_WEBHOOKS) ) {
+					if ( !channel || !channel.permissionsFor(msg.member).has(Permissions.FLAGS.MANAGE_WEBHOOKS) ) {
 						return msg.replyMsg( lang.get('rcscript.noadmin') );
 					}
 					db.query( 'DELETE FROM rcgcdw WHERE webhook = $1', [selected_row.webhook] ).then( () => {
@@ -552,7 +554,7 @@ function cmd_rcscript(lang, msg, args, line, wiki) {
  * @param {String[]} args - The command arguments.
  */
 function blocklist(msg, args) {
-	var prefix = ( patreons[msg?.guildId] || process.env.prefix );
+	var prefix = ( patreonGuildsPrefix.get(msg.guildId) || process.env.prefix );
 	if ( args[0] === 'add' ) {
 		if ( !args[1] ) return msg.replyMsg( '`' + prefix + 'rcscript block add <wiki> [<reason>]`', true );
 		if ( process.env.READONLY ) return msg.replyMsg( lang.get('general.readonly') + '\n' + process.env.invite, true );
@@ -622,7 +624,7 @@ function blocklist(msg, args) {
 	} );
 }
 
-module.exports = {
+export default {
 	name: 'rcscript',
 	everyone: rcscriptExists,
 	pause: rcscriptExists,

+ 6 - 6
cmds/say.js

@@ -1,12 +1,12 @@
-const {Permissions: {FLAGS}} = require('discord.js');
+import {Permissions} from 'discord.js';
 
 /**
  * Processes the "say" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
  * @param {String} line - The command as plain text.
- * @param {import('../util/wiki.js')} wiki - The wiki for the message.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the message.
  */
 function cmd_say(lang, msg, args, line, wiki) {
 	var text = args.join(' ');
@@ -23,16 +23,16 @@ function cmd_say(lang, msg, args, line, wiki) {
 	}
 	if ( text.trim() || imgs.length ) {
 		var allowedMentions = {parse:['users']};
-		if ( msg.member.permissions.has(FLAGS.MENTION_EVERYONE) ) allowedMentions.parse = ['users','roles','everyone'];
+		if ( msg.member.permissions.has(Permissions.FLAGS.MENTION_EVERYONE) ) allowedMentions.parse = ['users','roles','everyone'];
 		else allowedMentions.roles = msg.guild.roles.cache.filter( role => role.mentionable ).map( role => role.id ).slice(0,100)
 		msg.channel.send( {content: text, allowedMentions, files: imgs} ).then( () => msg.delete().catch(log_error), error => {
 			log_error(error);
 			msg.reactEmoji('error', true);
 		} );
-	} else if ( !pause[msg.guildId] ) this.LINK(lang, msg, line, wiki);
+	} else if ( !pausedGuilds.has(msg.guildId) ) this.LINK(lang, msg, line, wiki);
 }
 
-module.exports = {
+export default {
 	name: 'say',
 	everyone: false,
 	pause: false,

+ 19 - 17
cmds/settings.js

@@ -1,11 +1,13 @@
-const cheerio = require('cheerio');
-const {MessageEmbed, Util, MessageActionRow, MessageButton} = require('discord.js');
+import cheerio from 'cheerio';
+import {MessageEmbed, Util, MessageActionRow, MessageButton} from 'discord.js';
+import {got} from '../util/functions.js';
+import Lang from '../util/i18n.js';
+import Wiki from '../util/wiki.js';
+import db from '../util/database.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {defaultSettings} = require('../util/default.json');
-const {got} = require('../util/functions.js');
-const Lang = require('../util/i18n.js');
 const allLangs = Lang.allLangs();
-const Wiki = require('../util/wiki.js');
-var db = require('../util/database.js');
 
 /**
  * Processes the "settings" command.
@@ -37,13 +39,13 @@ function cmd_settings(lang, msg, args, line, wiki) {
 			text = lang.get('settings.current');
 			if ( button ) text += `\n<${button.url}>`;
 			text += '\n' + lang.get('settings.currentlang') + ' `' + allLangs.names[guild.lang] + '` - `' + prefix + 'settings lang`';
-			if ( patreons[msg.guildId] ) text += '\n' + lang.get('settings.currentprefix') + ' `' + prefix + '` - `' + prefix + 'settings prefix`';
+			if ( patreonGuildsPrefix.has(msg.guildId) ) text += '\n' + lang.get('settings.currentprefix') + ' `' + prefix + '` - `' + prefix + 'settings prefix`';
 			text += '\n' + lang.get('settings.currentrole') + ' ' + ( guild.role ? `<@&${guild.role}>` : '@everyone' ) + ' - `' + prefix + 'settings role`';
 			text += '\n' + lang.get('settings.currentinline') + ' ' + ( guild.inline ? '~~' : '' ) + '`[[' + inlinepage + ']]`' + ( guild.inline ? '~~' : '' ) + ' - `' + prefix + 'settings inline`';
 			text += '\n' + lang.get('settings.currentwiki') + ' ' + guild.wiki + ' - `' + prefix + 'settings wiki`';
 			text += '\n' + lang.get('settings.currentchannel') + ' `' + prefix + 'settings channel`\n';
 			if ( rows.length === 1 ) text += lang.get('settings.nochannels');
-			else text += rows.filter( row => row !== guild ).map( row => '<#' + row.channel.replace( /^#/, '' ) + '>: ' + ( patreons[msg.guildId] ? '`' + allLangs.names[row.lang] + '` - ' : '' ) + '<' + row.wiki + '>' + ( patreons[msg.guildId] ? ' - ' + ( row.role ? `<@&${row.role}>` : '@everyone' ) + ' - ' + ( row.inline ? '~~' : '' ) + '`[[' + inlinepage + ']]`' + ( row.inline ? '~~' : '' ) : '' ) ).join('\n');
+			else text += rows.filter( row => row !== guild ).map( row => '<#' + row.channel.replace( /^#/, '' ) + '>: ' + ( patreonGuildsPrefix.has(msg.guildId) ? '`' + allLangs.names[row.lang] + '` - ' : '' ) + '<' + row.wiki + '>' + ( patreonGuildsPrefix.has(msg.guildId) ? ' - ' + ( row.role ? `<@&${row.role}>` : '@everyone' ) + ' - ' + ( row.inline ? '~~' : '' ) + '`[[' + inlinepage + ']]`' + ( row.inline ? '~~' : '' ) : '' ) ).join('\n');
 		}
 		
 		if ( !args.length ) {
@@ -64,7 +66,7 @@ function cmd_settings(lang, msg, args, line, wiki) {
 			text = lang.get('settings.channel current');
 			button?.setURL(new URL(`/guild/${msg.guildId}/settings/${channelId}`, button.url).href);
 			if ( button ) text += `\n<${button.url}>`;
-			if ( patreons[msg.guildId] ) {
+			if ( patreonGuildsPrefix.has(msg.guildId) ) {
 				text += '\n' + lang.get('settings.currentlang') + ' `' + allLangs.names[channel.lang] + '` - `' + prefix + 'settings channel lang`';
 				text += '\n' + lang.get('settings.currentrole') + ' ' + ( channel.role ? `<@&${channel.role}>` : '@everyone' ) + ' - `' + prefix + 'settings channel role`';
 				text += '\n' + lang.get('settings.currentinline') + ' ' + ( channel.inline ? '~~' : '' ) + '`[[' + inlinepage + ']]`' + ( channel.inline ? '~~' : '' ) + ' - `' + prefix + 'settings channel inline`';
@@ -193,7 +195,7 @@ function cmd_settings(lang, msg, args, line, wiki) {
 		}
 		
 		if ( args[0] === 'lang' || args[0] === 'language' ) {
-			if ( channel && !patreons[msg.guildId] ) return msg.replyMsg( lang.get('general.patreon') + '\n<' + process.env.patreon + '>', true );
+			if ( channel && !patreonGuildsPrefix.has(msg.guildId) ) return msg.replyMsg( lang.get('general.patreon') + '\n<' + process.env.patreon + '>', true );
 			prelang += 'lang';
 			var langhelp = '\n' + lang.get('settings.langhelp', prefix + 'settings ' + prelang) + ' `' + Object.values(allLangs.names).join('`, `') + '`';
 			if ( !args[1] ) {
@@ -228,9 +230,9 @@ function cmd_settings(lang, msg, args, line, wiki) {
 						if ( row.channel && row.lang === guild.lang ) row.lang = allLangs.map[args[1]];
 					} );
 					guild.lang = allLangs.map[args[1]];
-					if ( voice[msg.guildId] ) voice[msg.guildId] = guild.lang;
+					if ( voiceGuildsLang.has(msg.guildId) ) voiceGuildsLang.set(msg.guildId, guild.lang);
 				}
-				if ( channel || !patreons[msg.guildId] || !rows.some( row => row.channel === channelId ) ) lang = new Lang(allLangs.map[args[1]]);
+				if ( channel || !patreonGuildsPrefix.has(msg.guildId) || !rows.some( row => row.channel === channelId ) ) lang = new Lang(allLangs.map[args[1]]);
 				msg.replyMsg( {content: lang.get('settings.' + prelang + 'changed') + ' `' + allLangs.names[allLangs.map[args[1]]] + '`\n' + lang.get('settings.langhelp', prefix + 'settings ' + prelang) + ' `' + Object.values(allLangs.names).join('`, `') + '`', files: ( msg.uploadFiles() ? [`./i18n/widgets/${allLangs.map[args[1]]}.png`] : [] ), components}, true );
 				var channels = rows.filter( row => row.channel && row.lang === guild.lang && row.wiki === guild.wiki && row.prefix === guild.prefix && row.role === guild.role && row.inline === guild.inline ).map( row => row.channel );
 				if ( channels.length ) db.query( 'DELETE FROM discord WHERE channel IN (' + channels.map( (row, i) => '$' + ( i + 1 ) ).join(', ') + ')', channels ).then( () => {
@@ -245,7 +247,7 @@ function cmd_settings(lang, msg, args, line, wiki) {
 		}
 		
 		if ( args[0] === 'role' ) {
-			if ( channel && !patreons[msg.guildId] ) return msg.replyMsg( lang.get('general.patreon') + '\n<' + process.env.patreon + '>', true );
+			if ( channel && !patreonGuildsPrefix.has(msg.guildId) ) return msg.replyMsg( lang.get('general.patreon') + '\n<' + process.env.patreon + '>', true );
 			prelang += 'role';
 			var rolehelp = '\n' + lang.get('settings.rolehelp', prefix + 'settings ' + prelang);
 			if ( !args[1] ) {
@@ -307,7 +309,7 @@ function cmd_settings(lang, msg, args, line, wiki) {
 		}
 		
 		if ( args[0] === 'prefix' && !channel ) {
-			if ( !patreons[msg.guildId] ) {
+			if ( !patreonGuildsPrefix.has(msg.guildId) ) {
 				return msg.replyMsg( lang.get('general.patreon') + '\n<' + process.env.patreon + '>', true );
 			}
 			var prefixhelp = '\n' + lang.get('settings.prefixhelp', prefix + 'settings prefix');
@@ -330,7 +332,7 @@ function cmd_settings(lang, msg, args, line, wiki) {
 				console.log( '- Settings successfully updated.' );
 				guild.prefix = args[1];
 				msg.client.shard.broadcastEval( (discordClient, evalData) => {
-					global.patreons[evalData.guild] = evalData.prefix;
+					patreonGuildsPrefix.set(evalData.guild, evalData.prefix);
 				}, {context: {guild: msg.guildId, prefix: args[1]}} );
 				msg.replyMsg( {content: lang.get('settings.prefixchanged') + ' `' + args[1] + '`\n' + lang.get('settings.prefixhelp', args[1] + 'settings prefix'), components}, true );
 			}, dberror => {
@@ -340,7 +342,7 @@ function cmd_settings(lang, msg, args, line, wiki) {
 		}
 		
 		if ( args[0] === 'inline' ) {
-			if ( channel && !patreons[msg.guildId] ) return msg.replyMsg( lang.get('general.patreon') + '\n<' + process.env.patreon + '>', true );
+			if ( channel && !patreonGuildsPrefix.has(msg.guildId) ) return msg.replyMsg( lang.get('general.patreon') + '\n<' + process.env.patreon + '>', true );
 			prelang += 'inline';
 			var toggle = 'inline ' + ( ( channel || guild ).inline ? 'disabled' : 'enabled' );
 			var inlinehelp = '\n' + lang.get('settings.' + toggle + '.help', prefix + 'settings ' + prelang + ' toggle', inlinepage);
@@ -393,7 +395,7 @@ function cmd_settings(lang, msg, args, line, wiki) {
 	} );
 }
 
-module.exports = {
+export default {
 	name: 'settings',
 	everyone: true,
 	pause: true,

+ 4 - 4
cmds/stop.js

@@ -1,10 +1,10 @@
 /**
  * Processes the "stop" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
  * @param {String} line - The command as plain text.
- * @param {import('../util/wiki.js')} wiki - The wiki for the message.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the message.
  * @async
  */
 async function cmd_stop(lang, msg, args, line, wiki) {
@@ -15,12 +15,12 @@ async function cmd_stop(lang, msg, args, line, wiki) {
 		await msg.replyMsg( 'I\'ll restart myself now!', true );
 		console.log( '\n- Restarting all shards!\n\n' );
 		await msg.client.shard.respawnAll({timeout: -1});
-	} else if ( !msg.channel.isGuild() || !pause[msg.guildId] ) {
+	} else if ( !msg.channel.isGuild() || !pausedGuilds.has(msg.guildId) ) {
 		this.LINK(lang, msg, line, wiki);
 	}
 }
 
-module.exports = {
+export default {
 	name: 'stop',
 	everyone: false,
 	pause: false,

+ 13 - 11
cmds/test.js

@@ -1,7 +1,7 @@
-const {MessageEmbed} = require('discord.js');
-const help_setup = require('../functions/helpsetup.js');
-const {got} = require('../util/functions.js');
-const logging = require('../util/logging.js');
+import {MessageEmbed} from 'discord.js';
+import help_setup from '../functions/helpsetup.js';
+import {got} from '../util/functions.js';
+import logging from '../util/logging.js';
 
 const wsStatus = [
 	'READY',
@@ -17,17 +17,17 @@ const wsStatus = [
 
 /**
  * Processes the "test" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
  * @param {String} line - The command as plain text.
- * @param {import('../util/wiki.js')} wiki - The wiki for the message.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the message.
  */
 function cmd_test(lang, msg, args, line, wiki) {
 	if ( args.join('') ) {
-		if ( !msg.channel.isGuild() || !pause[msg.guildId] ) this.LINK(lang, msg, line, wiki);
+		if ( !msg.channel.isGuild() || !pausedGuilds.has(msg.guildId) ) this.LINK(lang, msg, line, wiki);
 	}
-	else if ( !msg.channel.isGuild() || !pause[msg.guildId] ) {
+	else if ( !msg.channel.isGuild() || !pausedGuilds.has(msg.guildId) ) {
 		if ( msg.isAdmin() && msg.defaultSettings ) help_setup(lang, msg);
 		let textList = lang.get('test.text').filter( text => text.trim() );
 		var text = ( textList[Math.floor(Math.random() * ( textList.length * 5 ))] || lang.get('test.text.0') );
@@ -40,11 +40,13 @@ function cmd_test(lang, msg, args, line, wiki) {
 			var embed = new MessageEmbed().setTitle( lang.get('test.time') ).setFooter( 'Shard: ' + process.env.SHARDS ).addField( 'Discord', discordPing.toLocaleString(lang.get('dateformat')) + 'ms' );
 			var now = Date.now();
 			got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general&format=json', {
-				timeout: 10000
+				timeout: {
+					request: 10000
+				}
 			} ).then( response => {
 				var then = Date.now();
 				var body = response.body;
-				if ( body && body.warnings ) log_warn(body.warnings);
+				if ( body && body.warnings ) log_warning(body.warnings);
 				var ping = ( then - now ).toLocaleString(lang.get('dateformat')) + 'ms';
 				if ( body?.query?.general ) wiki.updateWiki(body.query.general);
 				embed.addField( wiki.toLink(), ping );
@@ -99,7 +101,7 @@ function cmd_test(lang, msg, args, line, wiki) {
 	}
 }
 
-module.exports = {
+export default {
 	name: 'test',
 	everyone: true,
 	pause: true,

+ 17 - 15
cmds/verification.js

@@ -1,31 +1,33 @@
-const {Util, MessageActionRow, MessageButton, Permissions: {FLAGS}} = require('discord.js');
-const help_setup = require('../functions/helpsetup.js');
+import {Util, MessageActionRow, MessageButton, Permissions} from 'discord.js';
+import help_setup from '../functions/helpsetup.js';
+import db from '../util/database.js';
+import {got} from '../util/functions.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {limit: {verification: verificationLimit}} = require('../util/default.json');
-var db = require('../util/database.js');
-const {got} = require('../util/functions.js');
 
 /**
  * Processes the "verification" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
  * @param {String} line - The command as plain text.
- * @param {import('../util/wiki.js')} wiki - The wiki for the message.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the message.
  */
 function cmd_verification(lang, msg, args, line, wiki) {
 	if ( !msg.isAdmin() ) {
-		if ( msg.channel.isGuild() && !pause[msg.guildId] ) this.verify(lang, msg, args, line, wiki);
+		if ( msg.channel.isGuild() && !pausedGuilds.has(msg.guildId) ) this.verify(lang, msg, args, line, wiki);
 		else msg.reactEmoji('❌');
 		return;
 	}
 	if ( msg.defaultSettings ) return help_setup(lang, msg);
-	if ( !msg.guild.me.permissions.has(FLAGS.MANAGE_ROLES) ) {
+	if ( !msg.guild.me.permissions.has(Permissions.FLAGS.MANAGE_ROLES) ) {
 		console.log( msg.guildId + ': Missing permissions - MANAGE_ROLES' );
 		return msg.replyMsg( lang.get('general.missingperm') + ' `MANAGE_ROLES`' );
 	}
 	
 	db.query( 'SELECT configid, channel, role, editcount, postcount, usergroup, accountage, rename FROM verification WHERE guild = $1 ORDER BY configid ASC', [msg.guildId] ).then( ({rows}) => {
-		var prefix = ( patreons[msg.guildId] || process.env.prefix );
+		var prefix = ( patreonGuildsPrefix.get(msg.guildId) || process.env.prefix );
 		var button = null;
 		var components = [];
 		if ( process.env.dashboard ) {
@@ -33,7 +35,7 @@ function cmd_verification(lang, msg, args, line, wiki) {
 			components.push(new MessageActionRow().addComponents(button));
 		}
 		if ( args[0] && args[0].toLowerCase() === 'add' ) {
-			var limit = verificationLimit[( patreons[msg.guildId] ? 'patreon' : 'default' )];
+			var limit = verificationLimit[( patreonGuildsPrefix.has(msg.guildId) ? 'patreon' : 'default' )];
 			if ( rows.length >= limit ) return msg.replyMsg( lang.get('verification.max_entries'), true );
 			if ( process.env.READONLY ) return msg.replyMsg( lang.get('general.readonly') + '\n' + process.env.invite, true );
 			button?.setURL(new URL(`/guild/${msg.guildId}/verification/new`, button.url).href);
@@ -81,7 +83,7 @@ function cmd_verification(lang, msg, args, line, wiki) {
 		}
 		if ( !rows.some( row => row.configid.toString() === args[0] ) ) {
 			if ( args.length ) {
-				if ( !pause[msg.guildId] ) this.verify(lang, msg, args, line, wiki);
+				if ( !pausedGuilds.has(msg.guildId) ) this.verify(lang, msg, args, line, wiki);
 				return;
 			}
 			var text = null;
@@ -120,7 +122,7 @@ function cmd_verification(lang, msg, args, line, wiki) {
 		}
 		button?.setURL(new URL(`/guild/${msg.guildId}/verification/${row.configid}`, button.url).href);
 		if ( args[1] === 'rename' && !args.slice(2).join('') ) {
-			if ( !row.rename && !msg.guild.me.permissions.has(FLAGS.MANAGE_NICKNAMES) ) {
+			if ( !row.rename && !msg.guild.me.permissions.has(Permissions.FLAGS.MANAGE_NICKNAMES) ) {
 				console.log( msg.guildId + ': Missing permissions - MANAGE_NICKNAMES' );
 				return msg.replyMsg( lang.get('general.missingperm') + ' `MANAGE_NICKNAMES`' );
 			}
@@ -210,7 +212,7 @@ function cmd_verification(lang, msg, args, line, wiki) {
 				if ( usergroups.some( usergroup => usergroup.length > 100 ) ) return msg.replyMsg( {content: lang.get('verification.usergroup_too_long'), components}, true );
 				if ( usergroups.length ) return msg.reactEmoji('⏳').then( reaction => got.get( wiki + 'api.php?action=query&meta=allmessages&amprefix=group-&amincludelocal=true&amenableparser=true&format=json' ).then( response => {
 					var body = response.body;
-					if ( body && body.warnings ) log_warn(body.warnings);
+					if ( body && body.warnings ) log_warning(body.warnings);
 					if ( response.statusCode !== 200 || body?.batchcomplete === undefined || !body?.query?.allmessages ) {
 						if ( wiki.noWiki(response.url, response.statusCode) ) console.log( '- This wiki doesn\'t exist!' );
 						else console.log( '- ' + response.statusCode + ': Error while getting the usergroups: ' + ( body && body.error && body.error.info ) );
@@ -291,7 +293,7 @@ function cmd_verification(lang, msg, args, line, wiki) {
 			if ( showCommands ) verification_text += '\n`' + prefix + 'verification ' + row.configid + ' accountage ' + lang.get('verification.new_accountage') + '`\n';
 			verification_text += '\n' + lang.get('verification.rename') + ' *`' + lang.get('verification.' + ( rename ? 'enabled' : 'disabled')) + '`*';
 			if ( showCommands ) verification_text += ' ' + lang.get('verification.toggle') + '\n`' + prefix + 'verification ' + row.configid + ' rename`\n';
-			if ( !hideNotice && rename && !msg.guild.me.permissions.has(FLAGS.MANAGE_NICKNAMES) ) {
+			if ( !hideNotice && rename && !msg.guild.me.permissions.has(Permissions.FLAGS.MANAGE_NICKNAMES) ) {
 				verification_text += '\n\n' + lang.get('verification.rename_no_permission', msg.guild.me.toString());
 			}
 			if ( !hideNotice && role.replace( /-/g, '' ).split('|').some( role => {
@@ -315,7 +317,7 @@ function cmd_verification(lang, msg, args, line, wiki) {
 	} );
 }
 
-module.exports = {
+export default {
 	name: 'verification',
 	everyone: true,
 	pause: true,

+ 12 - 12
cmds/verify.js

@@ -1,20 +1,20 @@
-const {randomBytes} = require('crypto');
-const {MessageEmbed, MessageActionRow, MessageButton, Permissions: {FLAGS}} = require('discord.js');
-var db = require('../util/database.js');
-var verify = require('../functions/verify.js');
-const {got, oauthVerify, allowDelete, escapeFormatting} = require('../util/functions.js');
+import {randomBytes} from 'crypto';
+import {MessageEmbed, MessageActionRow, MessageButton, Permissions} from 'discord.js';
+import db from '../util/database.js';
+import verify from '../functions/verify.js';
+import {got, oauthVerify, allowDelete, escapeFormatting} from '../util/functions.js';
 
 /**
  * Processes the "verify" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
  * @param {String} line - The command as plain text.
- * @param {import('../util/wiki.js')} wiki - The wiki for the message.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the message.
  */
 function cmd_verify(lang, msg, args, line, wiki) {
 	if ( !msg.channel.isGuild() || msg.defaultSettings ) return this.LINK(lang, msg, line, wiki);
-	if ( !msg.guild.me.permissions.has(FLAGS.MANAGE_ROLES) ) {
+	if ( !msg.guild.me.permissions.has(Permissions.FLAGS.MANAGE_ROLES) ) {
 		if ( msg.isAdmin() ) {
 			console.log( msg.guildId + ': Missing permissions - MANAGE_ROLES' );
 			msg.replyMsg( lang.get('general.missingperm') + ' `MANAGE_ROLES`' );
@@ -26,7 +26,7 @@ function cmd_verify(lang, msg, args, line, wiki) {
 	db.query( 'SELECT logchannel, flags, onsuccess, onmatch, role, editcount, postcount, usergroup, accountage, rename FROM verification LEFT JOIN verifynotice ON verification.guild = verifynotice.guild WHERE verification.guild = $1 AND channel LIKE $2 ORDER BY configid ASC', [msg.guildId, '%|' + ( msg.channel.isThread() ? msg.channel.parentId : msg.channelId ) + '|%'] ).then( ({rows}) => {
 		if ( !rows.length ) {
 			if ( msg.onlyVerifyCommand ) return;
-			return msg.replyMsg( lang.get('verify.missing') + ( msg.isAdmin() ? '\n`' + ( patreons[msg.guildId] || process.env.prefix ) + 'verification`' : '' ) );
+			return msg.replyMsg( lang.get('verify.missing') + ( msg.isAdmin() ? '\n`' + ( patreonGuildsPrefix.get(msg.guildId) || process.env.prefix ) + 'verification`' : '' ) );
 		}
 		
 		if ( wiki.hasOAuth2() && process.env.dashboard ) {
@@ -53,7 +53,7 @@ function cmd_verify(lang, msg, args, line, wiki) {
 						}, dberror => {
 							console.log( '- Dashboard: Error while updating the OAuth2 token for ' + msg.author.id + ': ' + dberror );
 						} );
-						return global.verifyOauthUser('', body.access_token, {
+						return verifyOauthUser('', body.access_token, {
 							wiki: wiki.href, channel: msg.channel,
 							user: msg.author.id, sourceMessage: msg,
 							fail: () => msg.replyMsg( lang.get('verify.error_reply'), false, false ).then( message => {
@@ -144,7 +144,7 @@ function cmd_verify(lang, msg, args, line, wiki) {
 							}, dberror => {
 								console.log( '- Dashboard: Error while updating the OAuth2 token for ' + msg.author.id + ': ' + dberror );
 							} );
-							return global.verifyOauthUser('', body.access_token, {
+							return verifyOauthUser('', body.access_token, {
 								wiki: wiki.href, channel: msg.channel,
 								user: msg.author.id, sourceMessage: msg,
 								fail: () => msg.replyMsg( lang.get('verify.error_reply'), false, false ).then( message => {
@@ -275,7 +275,7 @@ function cmd_verify(lang, msg, args, line, wiki) {
 	} );
 }
 
-module.exports = {
+export default {
 	name: 'verify',
 	everyone: true,
 	pause: false,

+ 11 - 11
cmds/voice.js

@@ -1,40 +1,40 @@
-const help_setup = require('../functions/helpsetup.js');
-var db = require('../util/database.js');
+import help_setup from '../functions/helpsetup.js';
+import db from '../util/database.js';
 
 /**
  * Processes the "voice" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
  * @param {String} line - The command as plain text.
- * @param {import('../util/wiki.js')} wiki - The wiki for the message.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the message.
  */
 function cmd_voice(lang, msg, args, line, wiki) {
 	if ( msg.isAdmin() ) {
 		if ( !args.join('') ) {
 			var text = lang.get('voice.text') + '\n`' + lang.get('voice.channel') + ' – <' + lang.get('voice.name') + '>`\n';
-			text += lang.get('voice.' + ( voice[msg.guildId] ? 'disable' : 'enable' ), ( patreons[msg.guildId] || process.env.prefix ) + 'voice toggle');
+			text += lang.get('voice.' + ( voiceGuildsLang.has(msg.guildId) ? 'disable' : 'enable' ), ( patreonGuildsPrefix.get(msg.guildId) || process.env.prefix ) + 'voice toggle');
 			return msg.replyMsg( text, true );
 		}
 		args[1] = args.slice(1).join(' ').trim()
 		if ( args[0].toLowerCase() === 'toggle' && !args[1] ) {
 			if ( msg.defaultSettings ) return help_setup(lang, msg);
 			if ( process.env.READONLY ) return msg.replyMsg( lang.get('general.readonly') + '\n' + process.env.invite, true );
-			var value = ( voice[msg.guildId] ? null : 1 );
+			var value = ( voiceGuildsLang.has(msg.guildId) ? null : 1 );
 			return db.query( 'UPDATE discord SET voice = $1 WHERE guild = $2 AND channel IS NULL', [value, msg.guildId] ).then( () => {
 				console.log( '- Voice settings successfully updated.' );
 				if ( value ) {
-					voice[msg.guildId] = lang.lang;
+					voiceGuildsLang.set(msg.guildId, lang.lang);
 					db.query( 'SELECT lang FROM discord WHERE guild = $1 AND channel IS NULL', [msg.guildId] ).then( ({rows:[row]}) => {
 						console.log( '- Voice language successfully updated.' );
-						voice[msg.guildId] = row.lang;
+						voiceGuildsLang.set(msg.guildId, row.lang);
 					}, dberror => {
 						console.log( '- Error while getting the voice language: ' + dberror );
 					} );
 					msg.replyMsg( lang.get('voice.enabled') + '\n`' + lang.get('voice.channel') + ' – <' + lang.get('voice.name') + '>`', true );
 				}
 				else {
-					delete voice[msg.guildId];
+					voiceGuildsLang.delete(msg.guildId);
 					msg.replyMsg( lang.get('voice.disabled'), true );
 				}
 			}, dberror => {
@@ -43,10 +43,10 @@ function cmd_voice(lang, msg, args, line, wiki) {
 			} );
 		}
 	}
-	if ( !msg.channel.isGuild() || !pause[msg.guildId] ) this.LINK(lang, msg, line, wiki);
+	if ( !msg.channel.isGuild() || !pausedGuilds.has(msg.guildId) ) this.LINK(lang, msg, line, wiki);
 }
 
-module.exports = {
+export default {
 	name: 'voice',
 	everyone: true,
 	pause: true,

+ 15 - 18
cmds/wiki/diff.js

@@ -1,21 +1,23 @@
-const {MessageEmbed} = require('discord.js');
-const logging = require('../../util/logging.js');
+import {MessageEmbed} from 'discord.js';
+import logging from '../../util/logging.js';
+import {got, htmlToPlain, htmlToDiscord, escapeFormatting} from '../../util/functions.js';
+import diffParser from '../../util/edit_diff.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {timeoptions} = require('../../util/default.json');
-const {got, htmlToPlain, htmlToDiscord, escapeFormatting} = require('../../util/functions.js');
-const diffParser = require('../../util/edit_diff.js');
 
 /**
  * Processes a Gamepedia edit.
- * @param {import('../../util/i18n.js')} lang - The user language.
+ * @param {import('../../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
- * @param {import('../../util/wiki.js')} wiki - The wiki for the edit.
+ * @param {import('../../util/wiki.js').default} wiki - The wiki for the edit.
  * @param {import('discord.js').MessageReaction} reaction - The reaction on the message.
  * @param {String} spoiler - If the response is in a spoiler.
  * @param {Boolean} noEmbed - If the response should be without an embed.
  * @param {MessageEmbed} [embed] - The embed for the page.
  */
-function gamepedia_diff(lang, msg, args, wiki, reaction, spoiler, noEmbed, embed) {
+export default function gamepedia_diff(lang, msg, args, wiki, reaction, spoiler, noEmbed, embed) {
 	if ( args[0] ) {
 		var error = false;
 		var title = '';
@@ -56,7 +58,7 @@ function gamepedia_diff(lang, msg, args, wiki, reaction, spoiler, noEmbed, embed
 		else {
 			got.get( wiki + 'api.php?action=compare&prop=ids|diff' + ( title ? '&fromtitle=' + encodeURIComponent( title ) : '&fromrev=' + revision ) + '&torelative=' + relative + '&format=json' ).then( response => {
 				var body = response.body;
-				if ( body && body.warnings ) log_warn(body.warnings);
+				if ( body && body.warnings ) log_warning(body.warnings);
 				if ( response.statusCode !== 200 || !body || !body.compare ) {
 					var noerror = false;
 					if ( body && body.error ) {
@@ -139,10 +141,10 @@ function gamepedia_diff(lang, msg, args, wiki, reaction, spoiler, noEmbed, embed
 
 /**
  * Sends a Gamepedia edit.
- * @param {import('../../util/i18n.js')} lang - The user language.
+ * @param {import('../../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String[]} args - The command arguments.
- * @param {import('../../util/wiki.js')} wiki - The wiki for the edit.
+ * @param {import('../../util/wiki.js').default} wiki - The wiki for the edit.
  * @param {import('discord.js').MessageReaction} reaction - The reaction on the message.
  * @param {String} spoiler - If the response is in a spoiler.
  * @param {Boolean} noEmbed - If the response should be without an embed.
@@ -151,7 +153,7 @@ function gamepedia_diff(lang, msg, args, wiki, reaction, spoiler, noEmbed, embed
 function gamepedia_diff_send(lang, msg, args, wiki, reaction, spoiler, noEmbed, compare) {
 	got.get( wiki + 'api.php?uselang=' + lang.lang + '&action=query&meta=siteinfo&siprop=general&list=tags&tglimit=500&tgprop=displayname&prop=revisions&rvslots=main&rvprop=ids|timestamp|flags|user|size|parsedcomment|tags' + ( args.length === 1 || args[0] === args[1] ? '|content' : '' ) + '&revids=' + args.join('|') + '&format=json' ).then( response => {
 		var body = response.body;
-		if ( body && body.warnings ) log_warn(body.warnings);
+		if ( body && body.warnings ) log_warning(body.warnings);
 		if ( response.statusCode !== 200 || !body || body.batchcomplete === undefined || !body.query ) {
 			if ( wiki.noWiki(response.url, response.statusCode) ) {
 				console.log( '- This wiki doesn\'t exist!' );
@@ -210,7 +212,7 @@ function gamepedia_diff_send(lang, msg, args, wiki, reaction, spoiler, noEmbed,
 				var whitespace = '__' + lang.get('diff.info.whitespace') + '__';
 				if ( !compare && oldid ) got.get( wiki + 'api.php?action=compare&prop=diff&fromrev=' + oldid + '&torev=' + diff + '&format=json' ).then( cpresponse => {
 					var cpbody = cpresponse.body;
-					if ( cpbody && cpbody.warnings ) log_warn(cpbody.warnings);
+					if ( cpbody && cpbody.warnings ) log_warning(cpbody.warnings);
 					if ( cpresponse.statusCode !== 200 || !cpbody || !cpbody.compare || cpbody.compare['*'] === undefined ) {
 						var noerror = false;
 						if ( cpbody && cpbody.error ) {
@@ -299,9 +301,4 @@ function gamepedia_diff_send(lang, msg, args, wiki, reaction, spoiler, noEmbed,
 		
 		if ( reaction ) reaction.removeEmoji();
 	} );
-}
-
-module.exports = {
-	name: 'diff',
-	run: gamepedia_diff
-};
+}

+ 13 - 13
functions/discussion.js → cmds/wiki/discussion.js

@@ -1,21 +1,23 @@
-const htmlparser = require('htmlparser2');
-const {MessageEmbed, Util} = require('discord.js');
-const {limit: {discussion: discussionLimit}} = require('../util/default.json');
-const {got, htmlToDiscord, escapeFormatting} = require('../util/functions.js');
+import htmlparser from 'htmlparser2';
+import {MessageEmbed, Util} from 'discord.js';
+import {got, htmlToDiscord, escapeFormatting} from '../../util/functions.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
+const {limit: {discussion: discussionLimit}} = require('../../util/default.json');
 
 /**
  * Processes discussion commands.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
- * @param {import('../util/wiki.js')} wiki - The wiki for the page.
+ * @param {import('../../util/wiki.js').default} wiki - The wiki for the page.
  * @param {String} title - The title of the discussion post.
  * @param {String} sitename - The sitename of the wiki.
  * @param {import('discord.js').MessageReaction} reaction - The reaction on the message.
  * @param {String} spoiler - If the response is in a spoiler.
  * @param {Boolean} noEmbed - If the response should be without an embed.
  */
-function fandom_discussion(lang, msg, wiki, title, sitename, reaction, spoiler, noEmbed) {
-	var limit = discussionLimit[( patreons[msg.guildId] ? 'patreon' : 'default' )];
+export default function fandom_discussion(lang, msg, wiki, title, sitename, reaction, spoiler, noEmbed) {
+	var limit = discussionLimit[( patreonGuildsPrefix.has(msg.guildId) ? 'patreon' : 'default' )];
 	if ( !title ) {
 		var pagelink = wiki + 'f';
 		if ( !msg.showEmbed() || noEmbed ) {
@@ -251,9 +253,9 @@ function fandom_discussion(lang, msg, wiki, title, sitename, reaction, spoiler,
 
 /**
  * Send discussion posts.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
- * @param {import('../util/wiki.js')} wiki - The wiki for the page.
+ * @param {import('../../util/wiki.js').default} wiki - The wiki for the page.
  * @param {Object} discussion - The discussion post.
  * @param {import('discord.js').MessageEmbed} embed - The embed for the page.
  * @param {String} spoiler - If the response is in a spoiler.
@@ -394,6 +396,4 @@ function discussion_formatting(jsonModel) {
 			break;
 	}
 	return description;
-}
-
-module.exports = fandom_discussion;
+}

+ 7 - 0
cmds/wiki/functions.js

@@ -0,0 +1,7 @@
+export {default as diff} from './diff.js';
+export {default as discussion} from './discussion.js';
+export {default as overview} from './overview.js';
+export {default as random} from './random.js';
+export {default as search} from './search.js';
+export {default as special_page} from './special_page.js';
+export {default as user} from './user.js';

+ 27 - 36
cmds/wiki/general.js

@@ -1,37 +1,30 @@
-const {MessageEmbed} = require('discord.js');
-const parse_page = require('../../functions/parse_page.js');
-const phabricator = require('../../functions/phabricator.js');
-const logging = require('../../util/logging.js');
-const {got, htmlToDiscord, escapeFormatting, partialURIdecode} = require('../../util/functions.js');
-const extract_desc = require('../../util/extract_desc.js');
+import {readdir} from 'fs';
+import {MessageEmbed} from 'discord.js';
+import parse_page from '../../functions/parse_page.js';
+import phabricator from '../../functions/phabricator.js';
+import logging from '../../util/logging.js';
+import {got, htmlToDiscord, escapeFormatting, partialURIdecode} from '../../util/functions.js';
+import extract_desc from '../../util/extract_desc.js';
+import Wiki from '../../util/wiki.js';
+import * as fn from './functions.js'
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {limit: {interwiki: interwikiLimit}, wikiProjects} = require('../../util/default.json');
-const Wiki = require('../../util/wiki.js');
 const {wikis: mcw} = require('../minecraft/commands.json');
 
-const fs = require('fs');
-var fn = {
-	special_page: require('../../functions/special_page.js'),
-	discussion: require('../../functions/discussion.js')
-};
-fs.readdir( './cmds/wiki', (error, files) => {
-	if ( error ) return error;
-	files.filter( file => ( file !== 'general.js' && file.endsWith('.js') ) ).forEach( file => {
-		var command = require('./' + file);
-		fn[command.name] = command.run;
-	} );
-} );
 var minecraft = {};
-fs.readdir( './cmds/minecraft', (error, files) => {
+readdir( './cmds/minecraft', (error, files) => {
 	if ( error ) return error;
 	files.filter( file => file.endsWith('.js') ).forEach( file => {
-		var command = require('../minecraft/' + file);
-		minecraft[command.name] = command.run;
+		import('../minecraft/' + file).then( ({default: command}) => {
+			minecraft[command.name] = command.run;
+		} );
 	} );
 } );
 
 /**
  * Checks a Gamepedia wiki.
- * @param {import('../../util/i18n.js')} lang - The user language.
+ * @param {import('../../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String} title - The page title.
  * @param {Wiki} wiki - The wiki for the page.
@@ -44,7 +37,7 @@ fs.readdir( './cmds/minecraft', (error, files) => {
  * @param {String} [interwiki] - The fallback interwiki link.
  * @param {Number} [selfcall] - The amount of followed interwiki links.
  */
-function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '', noEmbed = false, querystring = new URLSearchParams(), fragment = '', interwiki = '', selfcall = 0) {
+export default function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '', noEmbed = false, querystring = new URLSearchParams(), fragment = '', interwiki = '', selfcall = 0) {
 	var full_title = title;
 	if ( title.includes( '#' ) ) {
 		fragment = title.split('#').slice(1).join('#').trim().replace( /(?:%[\dA-F]{2})+/g, partialURIdecode );
@@ -91,7 +84,7 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 	}
 	got.get( wiki + 'api.php?uselang=' + uselang + '&action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|specialpagealiases&iwurl=true' + ( noRedirect ? '' : '&redirects=true' ) + '&prop=categoryinfo|info|pageprops|pageimages|extracts&piprop=original|name&ppprop=description|displaytitle|page_image_free|disambiguation|infoboxes&explaintext=true&exsectionformat=raw&exlimit=1&converttitles=true&titles=%1F' + encodeURIComponent( ( aliasInvoke === 'search' ? full_title.split(' ').slice(1).join(' ') : title ).replace( /\x1F/g, '\ufffd' ) ) + '&format=json' ).then( response => {
 		var body = response.body;
-		if ( body && body.warnings ) log_warn(body.warnings);
+		if ( body && body.warnings ) log_warning(body.warnings);
 		if ( response.statusCode !== 200 || !body || body.batchcomplete === undefined || !body.query ) {
 			if ( interwiki ) msg.sendChannel( spoiler + ( noEmbed ? '<' : ' ' ) + interwiki + ( noEmbed ? '>' : ' ' ) + spoiler );
 			else if ( wiki.noWiki(response.url, response.statusCode) ) {
@@ -187,7 +180,7 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 				var iw_parts = querypage.title.split(':');
 				var iw = new Wiki('https://' + iw_parts[1] + '.miraheze.org/w/');
 				var iw_link = iw.toLink(iw_parts.slice(2).join(':'), querystring, fragment);
-				var maxselfcall = interwikiLimit[( patreons[msg.guildId] ? 'patreon' : 'default' )];
+				var maxselfcall = interwikiLimit[( patreonGuildsPrefix.has(msg.guildId) ? 'patreon' : 'default' )];
 				if ( selfcall < maxselfcall ) {
 					selfcall++;
 					return this.general(lang, msg, iw_parts.slice(2).join(':'), iw, '!!' + iw.hostname + ' ', reaction, spoiler, noEmbed, querystring, fragment, iw_link, selfcall);
@@ -201,7 +194,7 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 			if ( ( querypage.missing !== undefined && querypage.known === undefined && !( noRedirect || querypage.categoryinfo ) ) || querypage.invalid !== undefined ) return got.get( wiki + 'api.php?uselang=' + uselang + '&action=query&prop=categoryinfo|info|pageprops|pageimages|extracts&piprop=original|name&ppprop=description|displaytitle|page_image_free|disambiguation|infoboxes&explaintext=true&exsectionformat=raw&exlimit=1&generator=search&gsrnamespace=4|12|14|' + ( querypage.ns >= 0 ? querypage.ns + '|' : '' ) + Object.values(body.query.namespaces).filter( ns => ns.content !== undefined ).map( ns => ns.id ).join('|') + '&gsrlimit=1&gsrsearch=' + encodeURIComponent( title ) + '&format=json' ).then( srresponse => {
 				logging(wiki, msg.guildId, 'general', 'search');
 				var srbody = srresponse.body;
-				if ( srbody?.warnings ) log_warn(srbody.warnings);
+				if ( srbody?.warnings ) log_warning(srbody.warnings);
 				if ( srresponse.statusCode !== 200 || !srbody || srbody.batchcomplete === undefined ) {
 					console.log( '- ' + srresponse.statusCode + ': Error while getting the search results: ' + srbody?.error?.info );
 					msg.sendChannelError( spoiler + '<' + wiki.toLink('Special:Search', {search:title}) + '>' + spoiler );
@@ -221,7 +214,7 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 								
 								if ( reaction ) reaction.removeEmoji();
 							}
-							var maxselfcall = interwikiLimit[( patreons[msg.guildId] ? 'patreon' : 'default' )];
+							var maxselfcall = interwikiLimit[( patreonGuildsPrefix.has(msg.guildId) ? 'patreon' : 'default' )];
 							if ( selfcall < maxselfcall ) {
 								selfcall++;
 								return this.general(lang, msg, location.slice(1).join('wiki/'), new Wiki(location[0]), cmd, reaction, spoiler, noEmbed, querystring, fragment, '', selfcall);
@@ -247,7 +240,7 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 				if ( !srbody.query ) {
 					return got.get( wiki + 'api.php?uselang=' + uselang + '&action=query&prop=categoryinfo|info|pageprops|pageimages|extracts&piprop=original|name&ppprop=description|displaytitle|page_image_free|disambiguation|infoboxes&explaintext=true&exsectionformat=raw&exlimit=1&generator=search&gsrwhat=text&gsrnamespace=4|12|14|' + ( querypage.ns >= 0 ? querypage.ns + '|' : '' ) + Object.values(body.query.namespaces).filter( ns => ns.content !== undefined ).map( ns => ns.id ).join('|') + '&gsrlimit=1&gsrsearch=' + encodeURIComponent( title ) + '&format=json' ).then( tsrresponse => {
 						var tsrbody = tsrresponse.body;
-						if ( tsrbody?.warnings ) log_warn(tsrbody.warnings);
+						if ( tsrbody?.warnings ) log_warning(tsrbody.warnings);
 						if ( tsrresponse.statusCode !== 200 || !tsrbody || tsrbody.batchcomplete === undefined ) {
 							if ( tsrbody?.error?.code !== 'search-text-disabled' ) console.log( '- ' + tsrresponse.statusCode + ': Error while getting the text search results: ' + tsrbody?.error?.info );
 						}
@@ -305,7 +298,7 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 				}
 				else embed.setThumbnail( new URL(body.query.general.logo, wiki).href );
 				
-				var prefix = ( msg.channel.isGuild() && patreons[msg.guildId] || process.env.prefix );
+				var prefix = ( msg.channel.isGuild() && patreonGuildsPrefix.get(msg.guildId) || process.env.prefix );
 				var linksuffix = ( querystring.toString() ? '?' + querystring : '' ) + ( fragment ? '#' + fragment : '' );
 				if ( title.replace( /[_-]/g, ' ' ).toLowerCase() === querypage.title.replace( /-/g, ' ' ).toLowerCase() ) {
 					text = '';
@@ -421,7 +414,7 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 			return parse_page(lang, msg, spoiler + '<' + pagelink + '>' + text + spoiler, ( noEmbed ? null : embed ), wiki, reaction, querypage, ( querypage.title === body.query.general.mainpage ? '' : new URL(body.query.general.logo, wiki).href ), ( fragment || ( body.query.redirects && body.query.redirects[0].tofragment ) || '' ), pagelink);
 		}
 		if ( body.query.interwiki ) {
-			if ( msg.channel.isGuild() && pause[msg.guildId] ) {
+			if ( msg.channel.isGuild() && pausedGuilds.has(msg.guildId) ) {
 				if ( reaction ) reaction.removeEmoji();
 				console.log( '- Aborted, paused.' );
 				return;
@@ -436,7 +429,7 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 				return phabricator(lang, msg, wiki, iw, reaction, spoiler, noEmbed);
 			}
 			logging(wiki, msg.guildId, 'interwiki');
-			var maxselfcall = interwikiLimit[( patreons[msg.guildId] ? 'patreon' : 'default' )];
+			var maxselfcall = interwikiLimit[( patreonGuildsPrefix.has(msg.guildId) ? 'patreon' : 'default' )];
 			if ( selfcall < maxselfcall && ['http:','https:'].includes( iw.protocol ) ) {
 				selfcall++;
 				if ( iw.hostname.endsWith( '.gamepedia.com' ) ) {
@@ -479,7 +472,7 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 		var embed = new MessageEmbed().setAuthor( body.query.general.sitename ).setTitle( escapeFormatting(querypage.title) ).setURL( pagelink ).setThumbnail( new URL(body.query.general.logo, wiki).href );
 		got.get( wiki + 'api.php?uselang=' + uselang + '&action=query' + ( noRedirect ? '' : '&redirects=true' ) + '&prop=info|pageprops|extracts&ppprop=description|displaytitle|disambiguation|infoboxes&explaintext=true&exsectionformat=raw&exlimit=1&titles=' + encodeURIComponent( querypage.title ) + '&format=json' ).then( mpresponse => {
 			var mpbody = mpresponse.body;
-			if ( mpbody && mpbody.warnings ) log_warn(body.warnings);
+			if ( mpbody && mpbody.warnings ) log_warning(body.warnings);
 			if ( mpresponse.statusCode !== 200 || !mpbody || mpbody.batchcomplete === undefined || !mpbody.query ) {
 				console.log( '- ' + mpresponse.statusCode + ': Error while getting the main page: ' + ( mpbody && mpbody.error && mpbody.error.info ) );
 				return;
@@ -533,6 +526,4 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 		
 		if ( reaction ) reaction.removeEmoji();
 	} );
-}
-
-module.exports = gamepedia_check_wiki;
+}

+ 11 - 14
cmds/wiki/overview.js

@@ -1,24 +1,26 @@
-const {MessageEmbed} = require('discord.js');
-const logging = require('../../util/logging.js');
+import {MessageEmbed} from 'discord.js';
+import logging from '../../util/logging.js';
+import {got, toFormatting, toPlaintext, escapeFormatting} from '../../util/functions.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {timeoptions} = require('../../util/default.json');
-const {got, toFormatting, toPlaintext, escapeFormatting} = require('../../util/functions.js');
 
 /**
  * Sends a Gamepedia wiki overview.
- * @param {import('../../util/i18n.js')} lang - The user language.
+ * @param {import('../../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
- * @param {import('../../util/wiki.js')} wiki - The wiki for the overview.
+ * @param {import('../../util/wiki.js').default} wiki - The wiki for the overview.
  * @param {import('discord.js').MessageReaction} reaction - The reaction on the message.
  * @param {String} spoiler - If the response is in a spoiler.
  * @param {Boolean} noEmbed - If the response should be without an embed.
  * @param {URLSearchParams} [querystring] - The querystring for the link.
  * @param {String} [fragment] - The section for the link.
  */
-function gamepedia_overview(lang, msg, wiki, reaction, spoiler, noEmbed, querystring = new URLSearchParams(), fragment = '') {
+export default function gamepedia_overview(lang, msg, wiki, reaction, spoiler, noEmbed, querystring = new URLSearchParams(), fragment = '') {
 	var uselang = ( querystring.getAll('variant').pop() || querystring.getAll('uselang').pop() || lang.lang );
 	got.get( wiki + 'api.php?uselang=' + uselang + '&action=query&meta=allmessages|siteinfo&amenableparser=true&amtitle=Special:Statistics&ammessages=statistics' + ( wiki.isFandom() ? '|custom-GamepediaNotice|custom-FandomMergeNotice' : '' ) + '&siprop=general|statistics|languages|rightsinfo' + ( wiki.isFandom() ? '|variables' : '' ) + '&siinlanguagecode=' + uselang + '&list=logevents&ledir=newer&lelimit=1&leprop=timestamp&titles=Special:Statistics&format=json' ).then( response => {
 		var body = response.body;
-		if ( body && body.warnings ) log_warn(body.warnings);
+		if ( body && body.warnings ) log_warning(body.warnings);
 		if ( response.statusCode !== 200 || !body || body.batchcomplete === undefined || !body.query || !body.query.pages ) {
 			if ( wiki.noWiki(response.url, response.statusCode) ) {
 				console.log( '- This wiki doesn\'t exist!' );
@@ -155,7 +157,7 @@ function gamepedia_overview(lang, msg, wiki, reaction, spoiler, noEmbed, queryst
 				return Promise.all([
 					( founder[1] > 0 ? got.get( wiki + 'api.php?action=query&list=users&usprop=&ususerids=' + founder[1] + '&format=json' ).then( usresponse => {
 						var usbody = usresponse.body;
-						if ( usbody && usbody.warnings ) log_warn(usbody.warnings);
+						if ( usbody && usbody.warnings ) log_warning(usbody.warnings);
 						if ( usresponse.statusCode !== 200 || !usbody || !usbody.query || !usbody.query.users || !usbody.query.users[0] ) {
 							console.log( '- ' + usresponse.statusCode + ': Error while getting the wiki founder: ' + ( usbody && usbody.error && usbody.error.info ) );
 							founder[1] = 'ID: ' + founder[1];
@@ -268,9 +270,4 @@ function gamepedia_overview(lang, msg, wiki, reaction, spoiler, noEmbed, queryst
 		
 		if ( reaction ) reaction.removeEmoji();
 	} );
-}
-
-module.exports = {
-	name: 'overview',
-	run: gamepedia_overview
-};
+}

+ 10 - 15
cmds/wiki/random.js

@@ -1,14 +1,14 @@
-const {MessageEmbed} = require('discord.js');
-const parse_page = require('../../functions/parse_page.js');
-const logging = require('../../util/logging.js');
-const {got, toMarkdown, htmlToDiscord, escapeFormatting} = require('../../util/functions.js');
-const extract_desc = require('../../util/extract_desc.js');
+import {MessageEmbed} from 'discord.js';
+import parse_page from '../../functions/parse_page.js';
+import logging from '../../util/logging.js';
+import {got, toMarkdown, htmlToDiscord, escapeFormatting} from '../../util/functions.js';
+import extract_desc from '../../util/extract_desc.js';
 
 /**
  * Sends a random Gamepedia page.
- * @param {import('../../util/i18n.js')} lang - The user language.
+ * @param {import('../../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
- * @param {import('../../util/wiki.js')} wiki - The wiki for the page.
+ * @param {import('../../util/wiki.js').default} wiki - The wiki for the page.
  * @param {import('discord.js').MessageReaction} reaction - The reaction on the message.
  * @param {String} spoiler - If the response is in a spoiler.
  * @param {Boolean} noEmbed - If the response should be without an embed.
@@ -16,11 +16,11 @@ const extract_desc = require('../../util/extract_desc.js');
  * @param {URLSearchParams} [querystring] - The querystring for the link.
  * @param {String} [fragment] - The section for the link.
  */
-function gamepedia_random(lang, msg, wiki, reaction, spoiler, noEmbed, namespace = ['0', '*'], querystring = new URLSearchParams(), fragment = '') {
+export default function gamepedia_random(lang, msg, wiki, reaction, spoiler, noEmbed, namespace = ['0', '*'], querystring = new URLSearchParams(), fragment = '') {
 	var uselang = ( querystring.getAll('variant').pop() || querystring.getAll('uselang').pop() || lang.lang );
 	got.get( wiki + 'api.php?uselang=' + uselang + '&action=query&meta=allmessages|siteinfo&amenableparser=true&amtitle=Special:Random&ammessages=randompage|randompage-nopages&amargs=%1F' + namespace[1] + '%1F' + namespace[0].split('|').length + '&siprop=general&prop=categoryinfo|info|pageprops|pageimages|extracts&piprop=original|name&ppprop=description|displaytitle|page_image_free|disambiguation|infoboxes&explaintext=true&exsectionformat=raw&exlimit=1&converttitles=true&generator=random&grnfilterredir=nonredirects&grnlimit=1&grnnamespace=' + namespace[0] + '&format=json' ).then( response => {
 		var body = response.body;
-		if ( body && body.warnings ) log_warn(body.warnings);
+		if ( body && body.warnings ) log_warning(body.warnings);
 		if ( response.statusCode !== 200 || !body || body.batchcomplete === undefined || !body.query || !body.query.general ) {
 			if ( wiki.noWiki(response.url, response.statusCode) ) {
 				console.log( '- This wiki doesn\'t exist!' );
@@ -123,9 +123,4 @@ function gamepedia_random(lang, msg, wiki, reaction, spoiler, noEmbed, namespace
 		}
 		if ( reaction ) reaction.removeEmoji();
 	} );
-}
-
-module.exports = {
-	name: 'random',
-	run: gamepedia_random
-};
+}

+ 11 - 14
cmds/wiki/search.js

@@ -1,19 +1,21 @@
-const {MessageEmbed, Util} = require('discord.js');
-const {got, escapeFormatting} = require('../../util/functions.js');
+import {MessageEmbed, Util} from 'discord.js';
+import {got, escapeFormatting} from '../../util/functions.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {limit: {search: searchLimit}} = require('../../util/default.json');
 
 /**
  * Searches a Gamepedia wiki.
- * @param {import('../../util/i18n.js')} lang - The user language.
+ * @param {import('../../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String} searchterm - The searchterm.
- * @param {import('../../util/wiki.js')} wiki - The wiki for the search.
+ * @param {import('../../util/wiki.js').default} wiki - The wiki for the search.
  * @param {Object} query - The siteinfo from the wiki.
  * @param {import('discord.js').MessageReaction} reaction - The reaction on the message.
  * @param {String} spoiler - If the response is in a spoiler.
  * @param {Boolean} noEmbed - If the response should be without an embed.
  */
-function gamepedia_search(lang, msg, searchterm, wiki, query, reaction, spoiler, noEmbed) {
+export default function gamepedia_search(lang, msg, searchterm, wiki, query, reaction, spoiler, noEmbed) {
 	if ( searchterm.length > 250 ) {
 		searchterm = searchterm.substring(0, 250);
 		msg.reactEmoji('⚠️');
@@ -25,17 +27,17 @@ function gamepedia_search(lang, msg, searchterm, wiki, query, reaction, spoiler,
 	if ( msg.showEmbed() && !noEmbed ) embed = new MessageEmbed().setAuthor( query.general.sitename ).setTitle( '`' + searchterm + '`' ).setURL( pagelink );
 	else resultText += '\n\n**`' + searchterm + '`**';
 	var querypage = ( Object.values(( query.pages || {} ))?.[0] || {title:'',ns:0,invalid:''} );
-	var limit = searchLimit[( patreons[msg.guildId] ? 'patreon' : 'default' )];
+	var limit = searchLimit[( patreonGuildsPrefix.has(msg.guildId) ? 'patreon' : 'default' )];
 	got.get( wiki + 'api.php?action=query&titles=Special:Search&list=search&srinfo=totalhits&srprop=redirecttitle|sectiontitle&srnamespace=4|12|14|' + ( querypage.ns >= 0 ? querypage.ns + '|' : '' ) + Object.values(query.namespaces).filter( ns => ns.content !== undefined ).map( ns => ns.id ).join('|') + '&srlimit=' + limit + '&srsearch=' + encodeURIComponent( searchterm ) + '&format=json' ).then( response => {
 		var body = response.body;
-		if ( body?.warnings ) log_warn(body.warnings);
+		if ( body?.warnings ) log_warning(body.warnings);
 		if ( response.statusCode !== 200 || !body?.query?.search || body.batchcomplete === undefined ) {
 			return console.log( '- ' + response.statusCode + ': Error while getting the search results: ' + body?.error?.info );
 		}
 		if ( body.query.search.length < limit ) {
 			return got.get( wiki + 'api.php?action=query&list=search&srwhat=text&srinfo=totalhits&srprop=redirecttitle|sectiontitle&srnamespace=4|12|14|' + ( querypage.ns >= 0 ? querypage.ns + '|' : '' ) + Object.values(query.namespaces).filter( ns => ns.content !== undefined ).map( ns => ns.id ).join('|') + '&srlimit=' + limit + '&srsearch=' + encodeURIComponent( searchterm ) + '&format=json' ).then( tresponse => {
 				var tbody = tresponse.body;
-				if ( tbody?.warnings ) log_warn(tbody.warnings);
+				if ( tbody?.warnings ) log_warning(tbody.warnings);
 				if ( tresponse.statusCode !== 200 || !tbody?.query?.search || tbody.batchcomplete === undefined ) {
 					return console.log( '- ' + tresponse.statusCode + ': Error while getting the text search results: ' + tbody?.error?.info );
 				}
@@ -148,9 +150,4 @@ function gamepedia_search(lang, msg, searchterm, wiki, query, reaction, spoiler,
 		
 		if ( reaction ) reaction.removeEmoji();
 	} );
-}
-
-module.exports = {
-	name: 'search',
-	run: gamepedia_search
-};
+}

+ 13 - 13
functions/special_page.js → cmds/wiki/special_page.js

@@ -1,7 +1,9 @@
-const {MessageEmbed, Util} = require('discord.js');
-const logging = require('../util/logging.js');
-const {timeoptions} = require('../util/default.json');
-const {got, toMarkdown, escapeFormatting} = require('../util/functions.js');
+import {MessageEmbed, Util} from 'discord.js';
+import logging from '../../util/logging.js';
+import {got, toMarkdown, escapeFormatting} from '../../util/functions.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
+const {timeoptions} = require('../../util/default.json');
 
 const overwrites = {
 	randompage: (fn, lang, msg, wiki, querystring, fragment, reaction, spoiler, noEmbed, args, embed, query) => {
@@ -145,21 +147,21 @@ const descriptions = {
 
 /**
  * Processes special pages.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {Object} querypage - The details of the special page.
  * @param {String} querypage.title - The title of the special page.
  * @param {String} querypage.uselang - The language of the special page.
  * @param {String} specialpage - The canonical name of the special page.
  * @param {Object} query - The siteinfo from the wiki.
- * @param {import('../util/wiki.js')} wiki - The wiki for the page.
+ * @param {import('../../util/wiki.js').default} wiki - The wiki for the page.
  * @param {URLSearchParams} querystring - The querystring for the link.
  * @param {String} fragment - The section for the link.
  * @param {import('discord.js').MessageReaction} reaction - The reaction on the message.
  * @param {String} spoiler - If the response is in a spoiler.
  * @param {Boolean} noEmbed - If the response should be without an embed.
  */
-function special_page(lang, msg, {title, uselang = lang.lang}, specialpage, query, wiki, querystring, fragment, reaction, spoiler, noEmbed) {
+export default function special_page(lang, msg, {title, uselang = lang.lang}, specialpage, query, wiki, querystring, fragment, reaction, spoiler, noEmbed) {
 	var pagelink = wiki.toLink(title, querystring, fragment);
 	var embed = new MessageEmbed().setAuthor( query.general.sitename ).setTitle( escapeFormatting(title) ).setURL( pagelink ).setThumbnail( new URL(query.general.logo, wiki).href );
 	if ( overwrites.hasOwnProperty(specialpage) ) {
@@ -175,11 +177,11 @@ function special_page(lang, msg, {title, uselang = lang.lang}, specialpage, quer
 		return;
 	}
 	if ( specialpage === 'recentchanges' && msg.isAdmin() ) {
-		embed.addField( lang.get('rcscript.title'), lang.get('rcscript.ad', ( patreons[msg.guildId] || process.env.prefix ), '[RcGcDw](https://gitlab.com/piotrex43/RcGcDw)') );
+		embed.addField( lang.get('rcscript.title'), lang.get('rcscript.ad', ( patreonGuildsPrefix.get(msg.guildId) || process.env.prefix ), '[RcGcDw](https://gitlab.com/piotrex43/RcGcDw)') );
 	}
 	got.get( wiki + 'api.php?uselang=' + uselang + '&action=query&meta=allmessages|siteinfo&siprop=general&amenableparser=true&amtitle=' + encodeURIComponent( title ) + '&ammessages=' + encodeURIComponent( specialpage ) + '|' + ( descriptions.hasOwnProperty(specialpage) ? descriptions[specialpage] : encodeURIComponent( specialpage ) + '-summary' ) + ( querypages.hasOwnProperty(specialpage) ? querypages[specialpage][0] : '' ) + '&converttitles=true&titles=%1F' + encodeURIComponent( title ) + '&format=json' ).then( response => {
 		var body = response.body;
-		if ( body && body.warnings ) log_warn(body.warnings);
+		if ( body && body.warnings ) log_warning(body.warnings);
 		if ( response.statusCode !== 200 || body?.batchcomplete === undefined ) {
 			console.log( '- ' + response.statusCode + ': Error while getting the special page: ' + ( body && body.error && body.error.info ) );
 			return;
@@ -199,7 +201,7 @@ function special_page(lang, msg, {title, uselang = lang.lang}, specialpage, quer
 			if ( description.length > 1000 ) description = description.substring(0, 1000) + '\u2026';
 			embed.setDescription( description );
 		}
-		if ( msg.channel.isGuild() && patreons[msg.guildId] && querypages.hasOwnProperty(specialpage) ) {
+		if ( msg.channel.isGuild() && patreonGuildsPrefix.has(msg.guildId) && querypages.hasOwnProperty(specialpage) ) {
 			var text = Util.splitMessage( querypages[specialpage][1](body.query, wiki, lang), {maxLength:1000} )[0];
 			embed.addField( lang.get('search.special'), ( text || lang.get('search.empty') ) );
 			if ( body.query.querypage?.cached !== undefined ) {
@@ -213,6 +215,4 @@ function special_page(lang, msg, {title, uselang = lang.lang}, specialpage, quer
 		
 		if ( reaction ) reaction.removeEmoji();
 	} );
-}
-
-module.exports = special_page;
+}

+ 19 - 22
cmds/wiki/user.js

@@ -1,19 +1,21 @@
-const {MessageEmbed} = require('discord.js');
-const datetimeDifference = require('datetime-difference');
-const global_block = require('../../functions/global_block.js');
-const parse_page = require('../../functions/parse_page.js');
-const logging = require('../../util/logging.js');
-const extract_desc = require('../../util/extract_desc.js');
+import {MessageEmbed} from 'discord.js';
+import datetimeDifference from 'datetime-difference';
+import global_block from '../../functions/global_block.js';
+import parse_page from '../../functions/parse_page.js';
+import logging from '../../util/logging.js';
+import extract_desc from '../../util/extract_desc.js';
+import {got, toMarkdown, toPlaintext, htmlToDiscord, escapeFormatting} from '../../util/functions.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {timeoptions, usergroups} = require('../../util/default.json');
-const {got, toMarkdown, toPlaintext, htmlToDiscord, escapeFormatting} = require('../../util/functions.js');
 
 /**
  * Processes a Gamepedia user.
- * @param {import('../../util/i18n.js')} lang - The user language.
+ * @param {import('../../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String} namespace - The user namespace on the wiki.
  * @param {String} username - The name of the user.
- * @param {import('../../util/wiki.js')} wiki - The wiki for the page.
+ * @param {import('../../util/wiki.js').default} wiki - The wiki for the page.
  * @param {URLSearchParams} querystring - The querystring for the link.
  * @param {String} fragment - The section for the link.
  * @param {Object} querypage - The user page on the wiki.
@@ -22,11 +24,11 @@ const {got, toMarkdown, toPlaintext, htmlToDiscord, escapeFormatting} = require(
  * @param {String} spoiler - If the response is in a spoiler.
  * @param {Boolean} noEmbed - If the response should be without an embed.
  */
-function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragment, querypage, contribs, reaction, spoiler, noEmbed) {
+export default function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragment, querypage, contribs, reaction, spoiler, noEmbed) {
 	if ( /^(?:(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{2})?|(?:[\dA-F]{1,4}:){7}[\dA-F]{1,4}(?:\/\d{2,3})?)$/.test(username) ) return got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general&list=blocks&bkprop=user|by|timestamp|expiry|reason&bkip=' + encodeURIComponent( username ) + '&format=json' ).then( response => {
 		logging(wiki, msg.guildId, 'user', 'ip');
 		var body = response.body;
-		if ( body && body.warnings ) log_warn(body.warnings);
+		if ( body && body.warnings ) log_warning(body.warnings);
 		if ( response.statusCode !== 200 || !body || body.batchcomplete === undefined || !body.query || !body.query.blocks || fragment ) {
 			if ( body && body.error && ( body.error.code === 'param_ip' || body.error.code === 'cidrtoobroad' ) || fragment ) {
 				if ( querypage.missing !== undefined || querypage.ns === -1 ) msg.reactEmoji('error');
@@ -175,7 +177,7 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 		got.get( wiki.updateWiki(body.query.general) + 'api.php?action=query&list=usercontribs&ucprop=&uclimit=50' + ( username.includes( '/' ) ? '&ucuserprefix=' + encodeURIComponent( rangeprefix ) : '&ucuser=' + encodeURIComponent( username ) ) + '&format=json' ).then( ucresponse => {
 			var ucbody = ucresponse.body;
 			if ( rangeprefix && !username.includes( '/' ) ) username = rangeprefix;
-			if ( ucbody && ucbody.warnings ) log_warn(ucbody.warnings);
+			if ( ucbody && ucbody.warnings ) log_warning(ucbody.warnings);
 			if ( ucresponse.statusCode !== 200 || !ucbody || ucbody.batchcomplete === undefined || !ucbody.query || !ucbody.query.usercontribs ) {
 				if ( ucbody && ucbody.error && ucbody.error.code === 'baduser_ucuser' ) {
 					msg.reactEmoji('error');
@@ -215,7 +217,7 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 				} );
 			}
 			
-			if ( msg.channel.isGuild() && patreons[msg.guildId] && wiki.isFandom() ) {
+			if ( msg.channel.isGuild() && patreonGuildsPrefix.has(msg.guildId) && wiki.isFandom() ) {
 				if ( msg.showEmbed() && !noEmbed ) embed.addField( '\u200b', '<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**' );
 				else text += '\n\n<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**';
 
@@ -239,7 +241,7 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 	logging(wiki, msg.guildId, 'user');
 	got.get( wiki + 'api.php?action=query&meta=siteinfo' + ( wiki.hasCentralAuth() ? '|globaluserinfo&guiprop=groups|editcount|merged&guiuser=' + encodeURIComponent( username ) + '&' : '' ) + '&siprop=general&prop=revisions&rvprop=content|user&rvslots=main&titles=User:' + encodeURIComponent( username ) + '/Discord&list=users&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + encodeURIComponent( username ) + '&format=json' ).then( response => {
 		var body = response.body;
-		if ( body && body.warnings ) log_warn(body.warnings);
+		if ( body && body.warnings ) log_warning(body.warnings);
 		if ( response.statusCode !== 200 || !body || body.batchcomplete === undefined || !body.query || !body.query.users || !body.query.users[0] ) {
 			console.log( '- ' + response.statusCode + ': Error while getting the search results: ' + ( body && body.error && body.error.info ) );
 			msg.sendChannelError( spoiler + '<' + wiki.toLink(namespace + username, querystring, fragment) + '>' + spoiler );
@@ -324,7 +326,7 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 		groupnames.push(...globalgroups);
 		got.get( wiki + 'api.php?action=query&meta=allmessages&amenableparser=true&amincludelocal=true&amargs=' + encodeURIComponent( username ) + '&amlang=' + querypage.uselang + '&ammessages=' + groupnames.map( group => `group-${group}|group-${group}-member` ).join('|') + '&format=json' ).then( gresponse => {
 			var gbody = gresponse.body;
-			if ( gbody && gbody.warnings ) log_warn(gbody.warnings);
+			if ( gbody && gbody.warnings ) log_warning(gbody.warnings);
 			if ( gresponse.statusCode !== 200 || !gbody || gbody.batchcomplete === undefined || !gbody?.query?.allmessages?.length ) {
 				console.log( '- ' + gresponse.statusCode + ': Error while getting the group names: ' + gbody?.error?.info );
 				return;
@@ -564,7 +566,7 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 					else text += '\n\n**' + block.header + '**\n' + block.text;
 				}
 				
-				if ( msg.channel.isGuild() && patreons[msg.guildId] ) {
+				if ( msg.channel.isGuild() && patreonGuildsPrefix.has(msg.guildId) ) {
 					if ( msg.showEmbed() && !noEmbed ) embed.addField( '\u200b', '<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**' );
 					else text += '\n\n<a:loading:641343250661113886> **' + lang.get('user.info.loading') + '**';
 					
@@ -604,9 +606,4 @@ function gamepedia_user(lang, msg, namespace, username, wiki, querystring, fragm
 		
 		if ( reaction ) reaction.removeEmoji();
 	} );
-}
-
-module.exports = {
-	name: 'user',
-	run: gamepedia_user
-};
+}

+ 20 - 0
dashboard/functions.js

@@ -0,0 +1,20 @@
+import {get as rcscript_get, post as rcscript_post} from './rcscript.js';
+import {get as settings_get, post as settings_post} from './settings.js';
+import {get as slash_get, post as slash_post} from './slash.js';
+import {get as user_get, post as user_post} from './user.js';
+import {get as verification_get, post as verification_post} from './verification.js';
+
+export const forms = {
+	rcscript: rcscript_get,
+	settings: settings_get,
+	slash: slash_get,
+	user: user_get,
+	verification: verification_get
+};
+export const posts = {
+	rcscript: rcscript_post,
+	settings: settings_post,
+	slash: slash_post,
+	user: user_post,
+	verification: verification_post
+};

+ 11 - 17
dashboard/guilds.js

@@ -1,30 +1,26 @@
-const cheerio = require('cheerio');
+import {readFileSync} from 'fs';
+import cheerio from 'cheerio';
+import {forms} from './functions.js';
+import Lang from './i18n.js';
+import {oauth, enabledOAuth2, settingsData, addWidgets, createNotice} from './util.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {defaultPermissions} = require('../util/default.json');
-const Lang = require('./i18n.js');
 const allLangs = Lang.allLangs().names;
-const {oauth, enabledOAuth2, settingsData, addWidgets, createNotice} = require('./util.js');
 
-const forms = {
-	user: require('./user.js').get,
-	settings: require('./settings.js').get,
-	verification: require('./verification.js').get,
-	rcscript: require('./rcscript.js').get,
-	slash: require('./slash.js').get
-};
-
-const file = require('fs').readFileSync('./dashboard/index.html');
+const file = readFileSync('./dashboard/index.html');
 
 /**
  * Let a user view settings
  * @param {import('http').ServerResponse} res - The server response
- * @param {import('./i18n.js')} dashboardLang - The user language.
+ * @param {import('./i18n.js').default} dashboardLang - The user language.
  * @param {String} theme - The display theme
  * @param {import('./util.js').UserSession} userSession - The user session
  * @param {URL} reqURL - The used url
  * @param {String} [action] - The action the user made
  * @param {String[]} [actionArgs] - The arguments for the action
  */
-function dashboard_guilds(res, dashboardLang, theme, userSession, reqURL, action, actionArgs) {
+export default function dashboard_guilds(res, dashboardLang, theme, userSession, reqURL, action, actionArgs) {
 	reqURL.pathname = reqURL.pathname.replace( /^(\/(?:user|guild\/\d+(?:\/(?:settings|verification|rcscript|slash)(?:\/(?:\d+|new|notice))?)?)?)(?:\/.*)?$/, '$1' );
 	var args = reqURL.pathname.split('/');
 	var settings = settingsData.get(userSession.user_id);
@@ -257,6 +253,4 @@ function dashboard_guilds(res, dashboardLang, theme, userSession, reqURL, action
 	res.writeHead(200, {'Content-Length': Buffer.byteLength(body)});
 	res.write( body );
 	return res.end();
-}
-
-module.exports = dashboard_guilds;
+}

+ 7 - 5
dashboard/i18n.js

@@ -1,13 +1,15 @@
+import {escapeText} from './util.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {defaultSettings} = require('../util/default.json');
-const {escapeText} = require('./util.js');
-var i18n = require('./i18n/allLangs.json');
+const i18n = require('./i18n/allLangs.json');
 Object.keys(i18n.allLangs.names).forEach( lang => i18n[lang] = require('./i18n/' + lang + '.json') );
 
 /**
  * A language.
  * @class
  */
-class Lang {
+export default class Lang {
 	/**
 	 * Creates a new language.
 	 * @param {String[]} [langs] - The language code.
@@ -28,7 +30,7 @@ class Lang {
 	 * Get a localized message.
 	 * @param {String} message - Name of the message.
 	 * @param {Boolean} escaped - If the message should be HTML escaped.
-	 * @param {(String|import('cheerio'))[]} args - Arguments for the message.
+	 * @param {(String|import('cheerio').default)[]} args - Arguments for the message.
 	 * @returns {String}
 	 */
 	get(message = '', escaped = false, ...args) {
@@ -165,4 +167,4 @@ function getArg(args, index) {
 	return ( args.length > index ? args[index] : args[args.length - 1] );
 }
 
-module.exports = Lang;
+export const allLangs = Lang.allLangs;

+ 38 - 47
dashboard/index.js

@@ -1,22 +1,13 @@
-global.isDebug = ( process.argv[2] === 'debug' );
-
-const http = require('http');
-const pages = require('./oauth.js');
-const dashboard = require('./guilds.js');
-const {db, sessionData, settingsData} = require('./util.js');
-const Lang = require('./i18n.js');
+import http from 'http';
+import fs from 'fs';
+import {extname} from 'path';
+import * as pages from './oauth.js';
+import dashboard from './guilds.js';
+import {posts} from './functions.js';
+import {db, sessionData, settingsData} from './util.js';
+import Lang from './i18n.js';
 const allLangs = Lang.allLangs();
 
-const posts = {
-	user: require('./user.js').post,
-	settings: require('./settings.js').post,
-	verification: require('./verification.js').post,
-	rcscript: require('./rcscript.js').post,
-	slash: require('./slash.js').post
-};
-
-const fs = require('fs');
-const path = require('path');
 const files = new Map([
 	...fs.readdirSync( './dashboard/src' ).map( file => {
 		return [`/src/${file}`, `./dashboard/src/${file}`];
@@ -29,7 +20,7 @@ const files = new Map([
 	} ) : [] )
 ].map( ([file, filepath]) => {
 	let contentType = 'text/html';
-	switch ( path.extname(file) ) {
+	switch ( extname(file) ) {
 		case '.css':
 			contentType = 'text/css';
 			break;
@@ -63,7 +54,6 @@ const server = http.createServer( (req, res) => {
 		if ( state && sessionData.has(state) && settingsData.has(sessionData.get(state).user_id) &&
 		( ( args.length === 5 && ['settings', 'verification', 'rcscript', 'slash'].includes( args[3] ) && /^(?:default|new|notice|\d+)$/.test(args[4])
 		&& settingsData.get(sessionData.get(state).user_id).guilds.isMember.has(args[2]) ) || req.url === '/user' ) ) {
-			if ( process.env.READONLY ) return save_response(`${req.url}?save=failed`);
 			let body = [];
 			req.on( 'data', chunk => {
 				body.push(chunk);
@@ -73,6 +63,7 @@ const server = http.createServer( (req, res) => {
 				res.end('error');
 			} );
 			return req.on( 'end', () => {
+				if ( process.env.READONLY ) return save_response(`${req.url}?save=failed`);
 				var settings = {};
 				Buffer.concat(body).toString().split('&').forEach( arg => {
 					if ( arg ) {
@@ -94,36 +85,36 @@ const server = http.createServer( (req, res) => {
 					}
 				}
 				else return posts[args[3]](save_response, settingsData.get(sessionData.get(state).user_id), args[2], args[4], settings);
-			} );
 
-			/**
-			 * @param {String} [resURL]
-			 * @param {String} [action]
-			 * @param {String[]} [actionArgs]
-			 */
-			function save_response(resURL = '/', action, ...actionArgs) {
-				if ( action === 'REDIRECT' && resURL.startsWith( 'https://' ) ) {
-					res.writeHead(303, {Location: resURL});
-					return res.end();
+				/**
+				 * @param {String} [resURL]
+				 * @param {String} [action]
+				 * @param {String[]} [actionArgs]
+				 */
+				function save_response(resURL = '/', action, ...actionArgs) {
+					if ( action === 'REDIRECT' && resURL.startsWith( 'https://' ) ) {
+						res.writeHead(303, {Location: resURL});
+						return res.end();
+					}
+					var themeCookie = ( req.headers?.cookie?.split('; ')?.find( cookie => {
+						return cookie.split('=')[0] === 'theme' && /^"(?:light|dark)"$/.test(( cookie.split('=')[1] || '' ));
+					} ) || 'dark' ).replace( /^theme="(light|dark)"$/, '$1' );
+					var langCookie = ( req.headers?.cookie?.split('; ')?.filter( cookie => {
+						return cookie.split('=')[0] === 'language' && /^"[a-z\-]+"$/.test(( cookie.split('=')[1] || '' ));
+					} )?.map( cookie => cookie.replace( /^language="([a-z\-]+)"$/, '$1' ) ) || [] );
+					var dashboardLang = new Lang(...langCookie, ...( req.headers?.['accept-language']?.split(',')?.map( lang => {
+						lang = lang.split(';')[0].toLowerCase();
+						if ( allLangs.map.hasOwnProperty(lang) ) return lang;
+						lang = lang.replace( /-\w+$/, '' );
+						if ( allLangs.map.hasOwnProperty(lang) ) return lang;
+						lang = lang.replace( /-\w+$/, '' );
+						if ( allLangs.map.hasOwnProperty(lang) ) return lang;
+						return '';
+					} ) || [] ));
+					dashboardLang.fromCookie = langCookie;
+					return dashboard(res, dashboardLang, themeCookie, sessionData.get(state), new URL(resURL, process.env.dashboard), action, actionArgs);
 				}
-				var themeCookie = ( req.headers?.cookie?.split('; ')?.find( cookie => {
-					return cookie.split('=')[0] === 'theme' && /^"(?:light|dark)"$/.test(( cookie.split('=')[1] || '' ));
-				} ) || 'dark' ).replace( /^theme="(light|dark)"$/, '$1' );
-				var langCookie = ( req.headers?.cookie?.split('; ')?.filter( cookie => {
-					return cookie.split('=')[0] === 'language' && /^"[a-z\-]+"$/.test(( cookie.split('=')[1] || '' ));
-				} )?.map( cookie => cookie.replace( /^language="([a-z\-]+)"$/, '$1' ) ) || [] );
-				var dashboardLang = new Lang(...langCookie, ...( req.headers?.['accept-language']?.split(',')?.map( lang => {
-					lang = lang.split(';')[0].toLowerCase();
-					if ( allLangs.map.hasOwnProperty(lang) ) return lang;
-					lang = lang.replace( /-\w+$/, '' );
-					if ( allLangs.map.hasOwnProperty(lang) ) return lang;
-					lang = lang.replace( /-\w+$/, '' );
-					if ( allLangs.map.hasOwnProperty(lang) ) return lang;
-					return '';
-				} ) || [] ));
-				dashboardLang.fromCookie = langCookie;
-				return dashboard(res, dashboardLang, themeCookie, sessionData.get(state), new URL(resURL, process.env.dashboard), action, actionArgs);
-			}
+			} );
 		}
 	}
 

+ 18 - 14
dashboard/oauth.js

@@ -1,16 +1,20 @@
-const {randomBytes} = require('crypto');
-const cheerio = require('cheerio');
+import {readFileSync} from 'fs';
+import {randomBytes} from 'crypto';
+import cheerio from 'cheerio';
+import Wiki from '../util/wiki.js';
+import {allLangs} from './i18n.js';
+import {got, db, oauth, enabledOAuth2, sessionData, settingsData, oauthVerify, sendMsg, addWidgets, createNotice, hasPerm} from './util.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {defaultPermissions} = require('../util/default.json');
-const Wiki = require('../util/wiki.js');
-const allLangs = require('./i18n.js').allLangs().names;
-const {got, db, oauth, enabledOAuth2, sessionData, settingsData, oauthVerify, sendMsg, addWidgets, createNotice, hasPerm} = require('./util.js');
+const allLangNames = allLangs().names;
 
-const file = require('fs').readFileSync('./dashboard/login.html');
+const file = readFileSync('./dashboard/login.html');
 
 /**
  * Let a user login
  * @param {import('http').ServerResponse} res - The server response
- * @param {import('./i18n.js')} dashboardLang - The user language.
+ * @param {import('./i18n.js').default} dashboardLang - The user language.
  * @param {String} theme - The display theme
  * @param {String} [state] - The user state
  * @param {String} [action] - The action the user made
@@ -28,7 +32,7 @@ function dashboard_login(res, dashboardLang, theme, state, action) {
 	if ( theme === 'light' ) $('html').addClass('theme-light');
 	$('<script>').text(`
 		const selectLanguage = '${dashboardLang.get('general.language').replace( /'/g, '\\$&' )}';
-		const allLangs = ${JSON.stringify(allLangs)};
+		const allLangs = ${JSON.stringify(allLangNames)};
 	`).insertBefore('script#langjs');
 	$('head title').text(dashboardLang.get('general.login') + ' – ' + dashboardLang.get('general.title'));
 	$('#login-button span, .channel#login div').text(dashboardLang.get('general.login'));
@@ -398,10 +402,10 @@ function mediawiki_oauth(res, searchParams, user_id) {
 	} );
 }
 
-module.exports = {
-	login: dashboard_login,
-	oauth: dashboard_oauth,
-	refresh: dashboard_refresh,
-	api: dashboard_api,
-	verify: mediawiki_oauth
+export {
+	dashboard_login as login,
+	dashboard_oauth as oauth,
+	dashboard_refresh as refresh,
+	dashboard_api as api,
+	mediawiki_oauth as verify
 };

+ 22 - 20
dashboard/rcscript.js

@@ -1,9 +1,11 @@
-const cheerio = require('cheerio');
+import cheerio from 'cheerio';
+import Lang from '../util/i18n.js';
+import Wiki from '../util/wiki.js';
+import {got, db, sendMsg, createNotice, hasPerm} from './util.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {defaultSettings, limit: {rcgcdw: rcgcdwLimit}} = require('../util/default.json');
-const Lang = require('../util/i18n.js');
-const allLangs = Lang.allLangs(true);
-const Wiki = require('../util/wiki.js');
-const {got, db, sendMsg, createNotice, hasPerm} = require('./util.js');
+const allLangs = Lang.allLangs(true).names;
 
 const display_types = [
 	'compact',
@@ -33,8 +35,8 @@ const fieldset = {
 	//+ '</fieldset>',
 	lang: '<label for="wb-settings-lang">Language:</label>'
 	+ '<select id="wb-settings-lang" name="lang" required autocomplete="language">'
-	+ Object.keys(allLangs.names).map( lang => {
-		return `<option id="wb-settings-lang-${lang}" value="${lang}">${allLangs.names[lang]}</option>`
+	+ Object.keys(allLangs).map( lang => {
+		return `<option id="wb-settings-lang-${lang}" value="${lang}">${allLangs[lang]}</option>`
 	} ).join('')
 	+ '</select>'
 	+ '<img id="wb-settings-lang-widget">',
@@ -64,9 +66,9 @@ const fieldset = {
 
 /**
  * Create a settings form
- * @param {import('cheerio')} $ - The response body
+ * @param {import('cheerio').default} $ - The response body
  * @param {String} header - The form header
- * @param {import('./i18n.js')} dashboardLang - The user language
+ * @param {import('./i18n.js').default} dashboardLang - The user language
  * @param {Object} settings - The current settings
  * @param {Boolean} settings.patreon
  * @param {String} [settings.channel]
@@ -195,10 +197,10 @@ function createForm($, header, dashboardLang, settings, guildChannels, allWikis)
 /**
  * Let a user change recent changes scripts
  * @param {import('http').ServerResponse} res - The server response
- * @param {import('cheerio')} $ - The response body
+ * @param {import('cheerio').default} $ - The response body
  * @param {import('./util.js').Guild} guild - The current guild
  * @param {String[]} args - The url parts
- * @param {import('./i18n.js')} dashboardLang - The user language
+ * @param {import('./i18n.js').default} dashboardLang - The user language
  */
 function dashboard_rcscript(res, $, guild, args, dashboardLang) {
 	db.query( 'SELECT discord.wiki mainwiki, discord.lang mainlang, (SELECT ARRAY_AGG(DISTINCT wiki ORDER BY wiki ASC) FROM discord WHERE guild = $1) allwikis, webhook, configid, rcgcdw.wiki, rcgcdw.lang, display, rcid, postid FROM discord LEFT JOIN rcgcdw ON discord.guild = rcgcdw.guild WHERE discord.guild = $1 AND discord.channel IS NULL ORDER BY configid ASC', [guild.id] ).then( ({rows}) => {
@@ -272,7 +274,7 @@ function dashboard_rcscript(res, $, guild, args, dashboardLang) {
 			if ( args[4] === 'new' && !( process.env.READONLY || rows.length >= rcgcdwLimit[( guild.patreon ? 'patreon' : 'default' )] ) ) {
 				$('.channel#channel-new').addClass('selected');
 				createForm($, dashboardLang.get('rcscript.form.new'), dashboardLang, {
-					wiki, lang: ( allLangs.names.hasOwnProperty(lang) ? lang : defaultSettings.lang ),
+					wiki, lang: ( allLangs.hasOwnProperty(lang) ? lang : defaultSettings.lang ),
 					display: 1, patreon: guild.patreon
 				}, guild.channels, allwikis).attr('action', `/guild/${guild.id}/rcscript/new`).appendTo('#text');
 			}
@@ -332,7 +334,7 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 		return res(`/guild/${guild}/rcscript/${type}`, 'savefail');
 	}
 	if ( settings.save_settings ) {
-		if ( !settings.wiki || !allLangs.names.hasOwnProperty(settings.lang) ) {
+		if ( !settings.wiki || !allLangs.hasOwnProperty(settings.lang) ) {
 			return res(`/guild/${guild}/rcscript/${type}`, 'savefail');
 		}
 		if ( !['0', '1', '2', '3'].includes( settings.display ) ) {
@@ -469,7 +471,7 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 								text += `\n${lang.get('rcscript.name')} \`${( settings.name || body.query.allmessages[1]['*'] || 'Recent changes' )}\``;
 								if ( settings.avatar ) text += `\n${lang.get('rcscript.avatar')} <${settings.avatar}>`;
 								text += `\n${lang.get('rcscript.wiki')} <${wiki.href}>`;
-								text += `\n${lang.get('rcscript.lang')} \`${allLangs.names[settings.lang]}\``;
+								text += `\n${lang.get('rcscript.lang')} \`${allLangs[settings.lang]}\``;
 								text += `\n${lang.get('rcscript.display')} \`${display_types[settings.display]}\``;
 								if ( enableFeeds && settings.feeds_only ) text += `\n${lang.get('rcscript.rc')} *\`${lang.get('rcscript.disabled')}\`*`;
 								if ( wiki.isFandom(false) ) text += `\n${lang.get('rcscript.feeds')} *\`${lang.get('rcscript.' + ( enableFeeds ? 'enabled' : 'disabled' ))}\`*`;
@@ -586,7 +588,7 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 						text += `\n${lang.get('rcscript.channel')} <#${row.channel}>`;
 						text += `\n${lang.get('rcscript.name')} \`${row.name}\``;
 						text += `\n${lang.get('rcscript.wiki')} <${row.wiki}>`;
-						text += `\n${lang.get('rcscript.lang')} \`${allLangs.names[row.lang]}\``;
+						text += `\n${lang.get('rcscript.lang')} \`${allLangs[row.lang]}\``;
 						text += `\n${lang.get('rcscript.display')} \`${display_types[row.display]}\``;
 						if ( row.rcid === -1 ) {
 							text += `\n${lang.get('rcscript.rc')} *\`${lang.get('rcscript.disabled')}\`*`;
@@ -741,8 +743,8 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 								}
 								if ( row.lang !== settings.lang ) {
 									file.push(`./RcGcDb/locale/widgets/${settings.lang}.png`);
-									diff.push(lang.get('rcscript.lang') + ` ~~\`${allLangs.names[row.lang]}\`~~ → \`${allLangs.names[settings.lang]}\``);
-									webhook_diff.push(webhook_lang.get('dashboard.lang', allLangs.names[settings.lang]));
+									diff.push(lang.get('rcscript.lang') + ` ~~\`${allLangs[row.lang]}\`~~ → \`${allLangs[settings.lang]}\``);
+									webhook_diff.push(webhook_lang.get('dashboard.lang', allLangs[settings.lang]));
 								}
 								if ( row.display !== settings.display ) {
 									diff.push(lang.get('rcscript.display') + ` ~~\`${display_types[row.display]}\`~~ → \`${display_types[settings.display]}\``);
@@ -864,7 +866,7 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 	} );
 }
 
-module.exports = {
-	get: dashboard_rcscript,
-	post: update_rcscript
+export {
+	dashboard_rcscript as get,
+	update_rcscript as post
 };

+ 22 - 20
dashboard/settings.js

@@ -1,9 +1,11 @@
-const cheerio = require('cheerio');
+import cheerio from 'cheerio';
+import Lang from '../util/i18n.js';
+import Wiki from '../util/wiki.js';
+import {got, db, sendMsg, createNotice, hasPerm} from './util.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {defaultSettings} = require('../util/default.json');
-const Lang = require('../util/i18n.js');
-const allLangs = Lang.allLangs();
-const Wiki = require('../util/wiki.js');
-const {got, db, sendMsg, createNotice, hasPerm} = require('./util.js');
+const allLangs = Lang.allLangs().names;
 
 const fieldset = {
 	channel: '<label for="wb-settings-channel">Channel:</label>'
@@ -18,8 +20,8 @@ const fieldset = {
 	//+ '</fieldset>',
 	lang: '<label for="wb-settings-lang">Language:</label>'
 	+ '<select id="wb-settings-lang" name="lang" required autocomplete="language">'
-	+ Object.keys(allLangs.names).map( lang => {
-		return `<option id="wb-settings-lang-${lang}" value="${lang}">${allLangs.names[lang]}</option>`
+	+ Object.keys(allLangs).map( lang => {
+		return `<option id="wb-settings-lang-${lang}" value="${lang}">${allLangs[lang]}</option>`
 	} ).join('')
 	+ '</select>'
 	+ '<img id="wb-settings-lang-widget">',
@@ -38,9 +40,9 @@ const fieldset = {
 
 /**
  * Create a settings form
- * @param {import('cheerio')} $ - The response body
+ * @param {import('cheerio').default} $ - The response body
  * @param {String} header - The form header
- * @param {import('./i18n.js')} dashboardLang - The user language
+ * @param {import('./i18n.js').default} dashboardLang - The user language
  * @param {Object} settings - The current settings
  * @param {Boolean} settings.patreon
  * @param {String} settings.channel
@@ -165,10 +167,10 @@ function createForm($, header, dashboardLang, settings, guildRoles, guildChannel
 /**
  * Let a user change settings
  * @param {import('http').ServerResponse} res - The server response
- * @param {import('cheerio')} $ - The response body
+ * @param {import('cheerio').default} $ - The response body
  * @param {import('./util.js').Guild} guild - The current guild
  * @param {String[]} args - The url parts
- * @param {import('./i18n.js')} dashboardLang - The user language
+ * @param {import('./i18n.js').default} dashboardLang - The user language
  */
 function dashboard_settings(res, $, guild, args, dashboardLang) {
 	db.query( 'SELECT channel, wiki, lang, role, inline, prefix, patreon FROM discord WHERE guild = $1 ORDER BY channel DESC NULLS LAST', [guild.id] ).then( ({rows}) => {
@@ -277,7 +279,7 @@ function update_settings(res, userSettings, guild, type, settings) {
 		return res(`/guild/${guild}/settings/${type}`, 'savefail');
 	}
 	if ( settings.save_settings ) {
-		if ( !settings.wiki || ( settings.lang && !allLangs.names.hasOwnProperty(settings.lang) ) ) {
+		if ( !settings.wiki || ( settings.lang && !allLangs.hasOwnProperty(settings.lang) ) ) {
 			return res(`/guild/${guild}/settings/${type}`, 'savefail');
 		}
 		if ( settings.channel && !userSettings.guilds.isMember.get(guild).channels.some( channel => {
@@ -326,7 +328,7 @@ function update_settings(res, userSettings, guild, type, settings) {
 				var text = lang.get('settings.dashboard.removed', `<@${userSettings.user.id}>`, `<#${type}>`);
 				if ( channel.wiki !== row.wiki ) text += `\n${lang.get('settings.currentwiki')} <${channel.wiki}>`;
 				if ( response.patreon ) {
-					if ( channel.lang !== row.lang ) text += `\n${lang.get('settings.currentlang')} \`${allLangs.names[channel.lang]}\``;
+					if ( channel.lang !== row.lang ) text += `\n${lang.get('settings.currentlang')} \`${allLangs[channel.lang]}\``;
 					if ( channel.role !== row.role ) text += `\n${lang.get('settings.currentrole')} ` + ( channel.role ? `<@&${channel.role}>` : '@everyone' );
 					if ( channel.inline !== row.inline ) text += `\n${lang.get('settings.currentinline')} ${( channel.inline ? '~~' : '' )}\`[[${( lang.localNames.page || 'page' )}]]\`${( channel.inline ? '~~' : '' )}`;
 				}
@@ -424,7 +426,7 @@ function update_settings(res, userSettings, guild, type, settings) {
 					res(`/guild/${guild}/settings`, 'save');
 					var text = lang.get('settings.dashboard.updated', `<@${userSettings.user.id}>`);
 					text += '\n' + lang.get('settings.currentwiki') + ` <${wiki.href}>`;
-					text += '\n' + lang.get('settings.currentlang') + ` \`${allLangs.names[settings.lang]}\``;
+					text += '\n' + lang.get('settings.currentlang') + ` \`${allLangs[settings.lang]}\``;
 					text += '\n' + lang.get('settings.currentrole') + ( settings.role ? ` <@&${settings.role}>` : ' @everyone' );
 					if ( response.patreon ) {
 						text += '\n' + lang.get('settings.currentprefix') + ` \`${settings.prefix.replace( /\\/g, '\\$&' )}\``;
@@ -452,7 +454,7 @@ function update_settings(res, userSettings, guild, type, settings) {
 				if ( row.lang !== settings.lang ) {
 					updateChannel = true;
 					file.push(`./i18n/widgets/${settings.lang}.png`);
-					diff.push(lang.get('settings.currentlang') + ` ~~\`${allLangs.names[row.lang]}\`~~ → \`${allLangs.names[settings.lang]}\``);
+					diff.push(lang.get('settings.currentlang') + ` ~~\`${allLangs[row.lang]}\`~~ → \`${allLangs[settings.lang]}\``);
 				}
 				if ( response.patreon && row.prefix !== settings.prefix ) {
 					updateChannel = true;
@@ -526,7 +528,7 @@ function update_settings(res, userSettings, guild, type, settings) {
 					var text = lang.get('settings.dashboard.removed', `<@${userSettings.user.id}>`, `<#${type}>`);
 					if ( channel.wiki !== row.wiki ) text += `\n${lang.get('settings.currentwiki')} <${channel.wiki}>`;
 					if ( response.patreon ) {
-						if ( channel.lang !== row.lang ) text += `\n${lang.get('settings.currentlang')} \`${allLangs.names[channel.lang]}\``;
+						if ( channel.lang !== row.lang ) text += `\n${lang.get('settings.currentlang')} \`${allLangs[channel.lang]}\``;
 						if ( channel.role !== row.role ) text += `\n${lang.get('settings.currentrole')} ` + ( channel.role ? `<@&${channel.role}>` : '@everyone' );
 						if ( channel.inline !== row.inline ) text += `\n${lang.get('settings.currentinline')} ${( channel.inline ? '~~' : '' )}\`[[${( lang.localNames.page || 'page' )}]]\`${( channel.inline ? '~~' : '' )}`;
 					}
@@ -552,7 +554,7 @@ function update_settings(res, userSettings, guild, type, settings) {
 				}
 				if ( response.patreon && channel.lang !== settings.lang ) {
 					file.push(`./i18n/widgets/${settings.lang}.png`);
-					diff.push(lang.get('settings.currentlang') + ` ~~\`${allLangs.names[channel.lang]}\`~~ → \`${allLangs.names[settings.lang]}\``);
+					diff.push(lang.get('settings.currentlang') + ` ~~\`${allLangs[channel.lang]}\`~~ → \`${allLangs[settings.lang]}\``);
 				}
 				if ( response.patreon && channel.role !== ( settings.role || null ) ) {
 					diff.push(lang.get('settings.currentrole') + ` ~~${( channel.role ? `<@&${channel.role}>` : '@everyone' )}~~ → ${( settings.role ? `<@&${settings.role}>` : '@everyone' )}`);
@@ -599,7 +601,7 @@ function update_settings(res, userSettings, guild, type, settings) {
 	} );
 }
 
-module.exports = {
-	get: dashboard_settings,
-	post: update_settings
+export {
+	dashboard_settings as get,
+	update_settings as post
 };

+ 18 - 12
dashboard/slash.js

@@ -1,5 +1,5 @@
-const Lang = require('../util/i18n.js');
-const {got, db, slashCommands, sendMsg, createNotice, hasPerm} = require('./util.js');
+import Lang from '../util/i18n.js';
+import {got, db, slashCommands, sendMsg, createNotice, hasPerm} from './util.js';
 
 const fieldset = {
 	role: '<label for="wb-settings-addrole">Role:</label>'
@@ -22,9 +22,9 @@ const fieldset = {
 
 /**
  * Create a settings form
- * @param {import('cheerio')} $ - The response body
+ * @param {import('cheerio').default} $ - The response body
  * @param {slashCommands[0]} slashCommand - The slash command
- * @param {import('./i18n.js')} dashboardLang - The user language
+ * @param {import('./i18n.js').default} dashboardLang - The user language
  * @param {Object[]} permissions - The current permissions
  * @param {String} permissions.id
  * @param {Number} permissions.type
@@ -94,10 +94,10 @@ function createForm($, slashCommand, dashboardLang, permissions, guildId, guildR
 /**
  * Let a user change slashs
  * @param {import('http').ServerResponse} res - The server response
- * @param {import('cheerio')} $ - The response body
+ * @param {import('cheerio').default} $ - The response body
  * @param {import('./util.js').Guild} guild - The current guild
  * @param {String[]} args - The url parts
- * @param {import('./i18n.js')} dashboardLang - The user language
+ * @param {import('./i18n.js').default} dashboardLang - The user language
  */
 function dashboard_slash(res, $, guild, args, dashboardLang) {
 	let suffix = ( args[0] === 'owner' ? '?owner=true' : '' );
@@ -115,7 +115,9 @@ function dashboard_slash(res, $, guild, args, dashboardLang) {
 			headers: {
 				Authorization: `Bot ${process.env.token}`
 			},
-			timeout: 10000
+			timeout: {
+				request: 10000
+			}
 		} ).then( response=> {
 			var permissions = [];
 			if ( response.statusCode !== 200 || !response.body ) {
@@ -214,7 +216,9 @@ function update_slash(res, userSettings, guild, type, settings) {
 			headers: {
 				Authorization: `Bot ${process.env.token}`
 			},
-			timeout: 10000
+			timeout: {
+				request: 10000
+			}
 		} ).then( response=> {
 			if ( response.statusCode !== 200 || !response.body ) {
 				if ( response.statusCode === 403 && response.body?.message === 'Missing Access' ) {
@@ -245,7 +249,9 @@ function update_slash(res, userSettings, guild, type, settings) {
 					Authorization: `Bot ${process.env.token}`
 				},
 				json: {permissions},
-				timeout: 10000
+				timeout: {
+					request: 10000
+				}
 			} ).then( response=> {
 				if ( response.statusCode !== 200 || !response.body ) {
 					console.log( '- Dashboard: ' + response.statusCode + ': Error while saving the slash command permissions: ' + response.body?.message );
@@ -307,7 +313,7 @@ function update_slash(res, userSettings, guild, type, settings) {
 	} );
 }
 
-module.exports = {
-	get: dashboard_slash,
-	post: update_slash
+export {
+	dashboard_slash as get,
+	update_slash as post
 };

+ 6 - 6
dashboard/user.js

@@ -1,11 +1,11 @@
-const {db, enabledOAuth2, oauthVerify} = require('./util.js');
+import {db, enabledOAuth2} from './util.js';
 
 /**
  * Let a user change settings
  * @param {import('http').ServerResponse} res - The server response
- * @param {import('cheerio')} $ - The response body
+ * @param {import('cheerio').default} $ - The response body
  * @param {import('./util.js').User} user - The current user
- * @param {import('./i18n.js')} dashboardLang - The user language
+ * @param {import('./i18n.js').default} dashboardLang - The user language
  */
 function dashboard_user(res, $, user, dashboardLang) {
 	db.query( 'SELECT site, token FROM oauthusers WHERE userid = $1', [user.id] ).then( ({rows}) => {
@@ -123,7 +123,7 @@ function update_user(res, user_id, type, oauth_id) {
 	} );
 }
 
-module.exports = {
-	get: dashboard_user,
-	post: update_user
+export {
+	dashboard_user as get,
+	update_user as post
 };

+ 42 - 18
dashboard/util.js

@@ -1,25 +1,35 @@
-const got = require('got').extend( {
+import gotDefault from 'got';
+import pg from 'pg';
+import DiscordOauth2 from 'discord-oauth2';
+import {oauthSites} from '../util/wiki.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
+const slashCommands = require('../interactions/commands.json');
+
+globalThis.isDebug = ( process.argv[2] === 'debug' );
+
+const got = gotDefault.extend( {
 	throwHttpErrors: false,
-	timeout: 5000,
+	timeout: {
+		request: 5000
+	},
 	headers: {
 		'User-Agent': 'Wiki-Bot/' + ( isDebug ? 'testing' : process.env.npm_package_version ) + '/dashboard (Discord; ' + process.env.npm_package_name + ( process.env.invite ? '; ' + process.env.invite : '' ) + ')'
 	},
 	responseType: 'json'
 } );
-const {Pool} = require('pg');
-const db = new Pool();
+
+const db = new pg.Pool();
 db.on( 'error', dberror => {
 	console.log( '- Dashboard: Error while connecting to the database: ' + dberror );
 } );
-const DiscordOauth2 = require('discord-oauth2');
+
 const oauth = new DiscordOauth2( {
 	clientId: process.env.bot,
 	clientSecret: process.env.secret,
 	redirectUri: process.env.dashboard
 } );
 
-const {oauthSites} = require('../util/wiki.js');
-
 const enabledOAuth2 = [
 	...oauthSites.filter( oauthSite => {
 		let site = new URL(oauthSite);
@@ -48,13 +58,13 @@ if ( process.env.oauth_wikimedia && process.env.oauth_wikimedia_secret ) {
 	});
 }
 
-const slashCommands = require('../interactions/commands.json');
-
 got.get( `https://discord.com/api/v8/applications/${process.env.bot}/commands`, {
 	headers: {
 		Authorization: `Bot ${process.env.token}`
 	},
-	timeout: 10000
+	timeout: {
+		request: 10000
+	}
 } ).then( response=> {
 	if ( response.statusCode !== 200 || !response.body ) {
 		console.log( '- Dashboard: ' + response.statusCode + ': Error while getting the global slash commands: ' + response.body?.message );
@@ -157,7 +167,7 @@ process.on( 'message', message => {
 		else messages.get(message.id).resolve(message.data.response);
 		return messages.delete(message.id);
 	}
-	if ( message === 'toggleDebug' ) global.isDebug = !global.isDebug;
+	if ( message === 'toggleDebug' ) isDebug = !isDebug;
 	console.log( '- [Dashboard]: Message received!', message );
 } );
 
@@ -223,9 +233,9 @@ if ( process.env.botlist ) {
 
 /**
  * Add bot list widgets.
- * @param {import('cheerio')} $ - The cheerio static
- * @param {import('./i18n.js')} dashboardLang - The user language
- * @returns {import('cheerio')}
+ * @param {import('cheerio').default} $ - The cheerio static
+ * @param {import('./i18n.js').default} dashboardLang - The user language
+ * @returns {import('cheerio').default}
 */
 function addWidgets($, dashboardLang) {
 	if ( !botLists.length ) return;
@@ -238,11 +248,11 @@ function addWidgets($, dashboardLang) {
 
 /**
  * Create a red notice
- * @param {import('cheerio')} $ - The cheerio static
+ * @param {import('cheerio').default} $ - The cheerio static
  * @param {String} notice - The notice to create
- * @param {import('./i18n.js')} dashboardLang - The user language
+ * @param {import('./i18n.js').default} dashboardLang - The user language
  * @param {String[]} [args] - The arguments for the notice
- * @returns {import('cheerio')}
+ * @returns {import('cheerio').default}
  */
 function createNotice($, notice, dashboardLang, args = []) {
 	if ( !notice ) return;
@@ -439,4 +449,18 @@ function hasPerm(all = 0n, ...permission) {
 	} );
 }
 
-module.exports = {got, db, oauth, enabledOAuth2, slashCommands, sessionData, settingsData, oauthVerify, sendMsg, addWidgets, createNotice, escapeText, hasPerm};
+export {
+	got,
+	db,
+	oauth,
+	enabledOAuth2,
+	slashCommands,
+	sessionData,
+	settingsData,
+	oauthVerify,
+	sendMsg,
+	addWidgets,
+	createNotice,
+	escapeText,
+	hasPerm
+};

+ 17 - 11
dashboard/verification.js

@@ -1,6 +1,8 @@
+import Lang from '../util/i18n.js';
+import {got, db, slashCommands, sendMsg, createNotice, escapeText, hasPerm} from './util.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {limit: {verification: verificationLimit}, usergroups} = require('../util/default.json');
-const Lang = require('../util/i18n.js');
-const {got, db, slashCommands, sendMsg, createNotice, escapeText, hasPerm} = require('./util.js');
 const slashCommand = slashCommands.find( slashCommand => slashCommand.name === 'verify' );
 
 const fieldset = {
@@ -58,9 +60,9 @@ const fieldset = {
 
 /**
  * Create a settings form
- * @param {import('cheerio')} $ - The response body
+ * @param {import('cheerio').default} $ - The response body
  * @param {String} header - The form header
- * @param {import('./i18n.js')} dashboardLang - The user language
+ * @param {import('./i18n.js').default} dashboardLang - The user language
  * @param {Object} settings - The current settings
  * @param {String} settings.channel
  * @param {String} settings.role
@@ -246,10 +248,10 @@ function createForm($, header, dashboardLang, settings, guildChannels, guildRole
 /**
  * Let a user change verifications
  * @param {import('http').ServerResponse} res - The server response
- * @param {import('cheerio')} $ - The response body
+ * @param {import('cheerio').default} $ - The response body
  * @param {import('./util.js').Guild} guild - The current guild
  * @param {String[]} args - The url parts
- * @param {import('./i18n.js')} dashboardLang - The user language
+ * @param {import('./i18n.js').default} dashboardLang - The user language
  */
 function dashboard_verification(res, $, guild, args, dashboardLang) {
 	db.query( 'SELECT wiki, discord.role defaultrole, prefix, configid, verification.channel, verification.role, editcount, postcount, usergroup, accountage, rename FROM discord LEFT JOIN verification ON discord.guild = verification.guild WHERE discord.guild = $1 AND discord.channel IS NULL ORDER BY configid ASC', [guild.id] ).then( ({rows}) => {
@@ -527,7 +529,9 @@ function update_verification(res, userSettings, guild, type, settings) {
 					json: {
 						permissions: []
 					},
-					timeout: 10000
+					timeout: {
+						request: 10000
+					}
 				} ).then( response=> {
 					if ( response.statusCode !== 200 || !response.body ) {
 						console.log( '- Dashboard: ' + response.statusCode + ': Error while disabling the slash command: ' + response.body?.message );
@@ -635,7 +639,9 @@ function update_verification(res, userSettings, guild, type, settings) {
 								}
 							]
 						},
-						timeout: 10000
+						timeout: {
+							request: 10000
+						}
 					} ).then( response=> {
 						if ( response.statusCode !== 200 || !response.body ) {
 							console.log( '- Dashboard: ' + response.statusCode + ': Error while enabling the slash command: ' + response.body?.message );
@@ -1004,7 +1010,7 @@ function update_notices(res, userSettings, guild, type, settings) {
 	} );
 }
 
-module.exports = {
-	get: dashboard_verification,
-	post: update_verification
+export {
+	dashboard_verification as get,
+	update_verification as post
 };

+ 6 - 4
database.js

@@ -1,6 +1,8 @@
+import {createRequire} from 'module';
+import pg from 'pg';
+const require = createRequire(import.meta.url);
 const {defaultSettings} = require('./util/default.json');
-const {Client} = require('pg');
-const db = new Client();
+const db = new pg.Client();
 db.on( 'error', dberror => {
 	console.log( '- Error while connecting to the database: ' + dberror );
 } );
@@ -206,7 +208,7 @@ COMMIT TRANSACTION;
 ALTER DATABASE "${process.env.PGDATABASE}" SET my.version TO 4;
 `];
 
-module.exports = db.connect().then( () => {
+export default await db.connect().then( () => {
 	return db.query( 'SELECT CURRENT_SETTING($1, $2) AS version', ['my.version', true] ).then( ({rows:[row]}) => {
 		if ( row.version === null ) {
 			return db.query( schema[0] ).then( () => {
@@ -253,6 +255,6 @@ module.exports = db.connect().then( () => {
 	}, dberror => {
 		console.log( '- Error while closing the database connection: ' + dberror );
 	} ).then( () => {
-		return Promise.reject();
+		process.exit(1);
 	} );
 } );

+ 7 - 9
functions/global_block.js

@@ -1,19 +1,19 @@
-const cheerio = require('cheerio');
-const {got, escapeFormatting} = require('../util/functions.js');
+import cheerio from 'cheerio';
+import {got, escapeFormatting} from '../util/functions.js';
 
 /**
  * Add global blocks to user messages.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String} username - The name of the user.
  * @param {String} text - The text of the response.
  * @param {import('discord.js').MessageEmbed} embed - The embed for the page.
- * @param {import('../util/wiki.js')} wiki - The wiki for the page.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the page.
  * @param {String} spoiler - If the response is in a spoiler.
  * @param {String} [gender] - The gender of the user.
  */
-function global_block(lang, msg, username, text, embed, wiki, spoiler, gender) {
-	if ( !msg || !msg.channel.isGuild() || !patreons[msg.guildId] || !wiki.isFandom() ) return;
+export default function global_block(lang, msg, username, text, embed, wiki, spoiler, gender) {
+	if ( !msg || !msg.channel.isGuild() || !patreonGuildsPrefix.has(msg.guildId) || !wiki.isFandom() ) return;
 	
 	var isUser = true;
 	if ( !gender ) {
@@ -100,6 +100,4 @@ function global_block(lang, msg, username, text, embed, wiki, spoiler, gender) {
 	]).finally( () => {
 		msg.edit( {content: spoiler + text + spoiler, embeds: [embed]} ).catch(log_error);
 	} );
-}
-
-module.exports = global_block;
+}

+ 4 - 6
functions/helpserver.js

@@ -1,13 +1,11 @@
-const help_setup = require('./helpsetup.js');
+import help_setup from './helpsetup.js';
 
 /**
  * Post a message about the help server.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  */
-function help_server(lang, msg) {
+export default function help_server(lang, msg) {
 	if ( msg.isAdmin() && msg.defaultSettings ) help_setup(lang, msg);
 	msg.sendChannel( lang.get('general.helpserver') + '\n' + process.env.invite );
-}
-
-module.exports = help_server;
+}

+ 3 - 5
functions/helpsetup.js

@@ -1,11 +1,9 @@
 /**
  * Send send message to setup the bot.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  */
-function help_setup(lang, msg) {
+export default function help_setup(lang, msg) {
 	msg.defaultSettings = false;
 	msg.replyMsg( lang.get('general.default', '`' + process.env.prefix + 'settings`') + ( process.env.dashboard ? '\n' + new URL(`/guild/${msg.guildId}/settings`, process.env.dashboard).href : '' ) );
-}
-
-module.exports = help_setup;
+}

+ 15 - 13
functions/parse_page.js

@@ -1,7 +1,7 @@
-const cheerio = require('cheerio');
-const {MessageEmbed} = require('discord.js');
-const {toSection} = require('../util/wiki.js');
-const {got, parse_infobox, htmlToPlain, htmlToDiscord, escapeFormatting, limitLength} = require('../util/functions.js');
+import cheerio from 'cheerio';
+import {MessageEmbed} from 'discord.js';
+import {toSection} from '../util/wiki.js';
+import {got, parse_infobox, htmlToPlain, htmlToDiscord, escapeFormatting, limitLength} from '../util/functions.js';
 
 const parsedContentModels = [
 	'wikitext',
@@ -74,11 +74,11 @@ const removeClassesExceptions = [
 
 /**
  * Parses a wiki page to get it's description.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String} content - The content for the message.
  * @param {import('discord.js').MessageEmbed} embed - The embed for the message.
- * @param {import('../util/wiki.js')} wiki - The wiki for the page.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the page.
  * @param {import('discord.js').MessageReaction} reaction - The reaction on the message.
  * @param {Object} querypage - The details of the page.
  * @param {String} querypage.title - The title of the page.
@@ -94,7 +94,7 @@ const removeClassesExceptions = [
  * @param {String} [pagelink] - The link to the page.
  * @returns {Promise<import('discord.js').Message>} The edited message.
  */
-function parse_page(lang, msg, content, embed, wiki, reaction, {title, contentmodel, missing, pageprops: {infoboxes, disambiguation} = {}, uselang = lang.lang, noRedirect = false}, thumbnail = '', fragment = '', pagelink = '') {
+export default function parse_page(lang, msg, content, embed, wiki, reaction, {title, contentmodel, missing, pageprops: {infoboxes, disambiguation} = {}, uselang = lang.lang, noRedirect = false}, thumbnail = '', fragment = '', pagelink = '') {
 	if ( reaction ) reaction.removeEmoji();
 	if ( !msg?.showEmbed?.() || missing !== undefined || !embed || embed.description ) {
 		if ( missing !== undefined && embed ) {
@@ -113,10 +113,12 @@ function parse_page(lang, msg, content, embed, wiki, reaction, {title, contentmo
 	} ).then( message => {
 		if ( !message ) return;
 		if ( !parsedContentModels.includes( contentmodel ) ) return got.get( wiki + 'api.php?action=query&prop=revisions&rvprop=content&rvslots=main&converttitles=true&titles=%1F' + encodeURIComponent( title ) + '&format=json', {
-			timeout: 10000
+			timeout: {
+				request: 10000
+			}
 		} ).then( response => {
 			var body = response.body;
-			if ( body && body.warnings ) log_warn(body.warnings);
+			if ( body && body.warnings ) log_warning(body.warnings);
 			var revision = Object.values(( body?.query?.pages || {} ))?.[0]?.revisions?.[0];
 			revision = ( revision?.slots?.main || revision );
 			if ( response.statusCode !== 200 || !body || body.batchcomplete === undefined || !revision?.['*'] ) {
@@ -175,7 +177,9 @@ function parse_page(lang, msg, content, embed, wiki, reaction, {title, contentmo
 		}
 		let extraImages = [];
 		return got.get( wiki + 'api.php?uselang=' + uselang + '&action=parse' + ( noRedirect ? '' : '&redirects=true' ) + '&prop=text|images|displaytitle' + ( contentmodel !== 'wikitext' || fragment || disambiguation !== undefined ? '' : '&section=0' ) + '&disablelimitreport=true&disableeditsection=true&disabletoc=true&sectionpreview=true&page=' + encodeURIComponent( title ) + '&format=json', {
-			timeout: 10000
+			timeout: {
+				request: 10000
+			}
 		} ).then( response => {
 			if ( response.statusCode !== 200 || !response?.body?.parse?.text ) {
 				console.log( '- ' + response.statusCode + ': Error while parsing the page: ' + response?.body?.error?.info );
@@ -424,6 +428,4 @@ function parse_page(lang, msg, content, embed, wiki, reaction, {title, contentmo
 			return message.edit( {content, embeds} ).catch(log_error);
 		} );
 	} );
-}
-
-module.exports = parse_page;
+}

+ 7 - 9
functions/phabricator.js

@@ -1,18 +1,18 @@
-const {MessageEmbed} = require('discord.js');
-const logging = require('../util/logging.js');
-const {got, escapeFormatting, limitLength} = require('../util/functions.js');
+import {MessageEmbed} from 'discord.js';
+import logging from '../util/logging.js';
+import {got, escapeFormatting, limitLength} from '../util/functions.js';
 
 /**
  * Sends a Phabricator task.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
- * @param {import('../util/wiki.js')} wiki - The wiki.
+ * @param {import('../util/wiki.js').default} wiki - The wiki.
  * @param {URL} link - The link.
  * @param {import('discord.js').MessageReaction} reaction - The reaction on the message.
  * @param {String} [spoiler] - If the response is in a spoiler.
  * @param {Boolean} [noEmbed] - If the response should be without an embed.
  */
-function phabricator_task(lang, msg, wiki, link, reaction, spoiler = '', noEmbed = false) {
+export default function phabricator_task(lang, msg, wiki, link, reaction, spoiler = '', noEmbed = false) {
 	var regex = /^(?:https?:)?\/\/phabricator\.(wikimedia|miraheze)\.org\/T(\d+)(?:#|$)/.exec(link.href);
 	if ( !regex || !process.env['phabricator_' + regex[1]] ) {
 		logging(wiki, msg.guildId, 'interwiki');
@@ -123,6 +123,4 @@ function parse_text(text, site) {
 	text = text.replace( /(?<!https?:\/\/[^\s]+)\b\{?([CDFHLMPQTV]\d+(?:#\d+)?)\}?\b/g, '[$1](' + site + '$1)' );
 	text = text.replace( /(?<!https?:\/\/[^\s]+)#([a-z0-9_-]+)\b/g, '[#$1](' + site + 'tag/$1)' );
 	return text;
-}
-
-module.exports = phabricator_task;
+}

+ 41 - 43
functions/verify.js

@@ -1,24 +1,23 @@
-const cheerio = require('cheerio');
-const {MessageEmbed, MessageActionRow, MessageButton, Permissions: {FLAGS}} = require('discord.js');
-var db = require('../util/database.js');
-const Lang = require('../util/i18n.js');
-const Wiki = require('../util/wiki.js');
-const logging = require('../util/logging.js');
-const {got, oauthVerify, allowDelete, escapeFormatting} = require('../util/functions.js');
-const toTitle = require('../util/wiki.js').toTitle;
+import cheerio from 'cheerio';
+import {MessageEmbed, MessageActionRow, MessageButton, Permissions} from 'discord.js';
+import db from '../util/database.js';
+import Lang from '../util/i18n.js';
+import Wiki from '../util/wiki.js';
+import logging from '../util/logging.js';
+import {got, oauthVerify, allowDelete, escapeFormatting} from '../util/functions.js';
 
 /**
  * Processes the "verify" command.
- * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js').default} lang - The user language.
  * @param {import('discord.js').TextChannel} channel - The Discord channel.
  * @param {import('discord.js').GuildMember} member - The Discord guild member.
  * @param {String} username - The username.
- * @param {import('../util/wiki.js')} wiki - The wiki for the message.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the message.
  * @param {Object[]} rows - The verification settings.
  * @param {String} [old_username] - The username before the search.
  * @returns {Promise<{content:String,embed:MessageEmbed,add_button:Boolean,send_private:Boolean,reaction:String,oauth:String[],logging:{channel:String,content:String,embed?:MessageEmbed}}>}
  */
-function verify(lang, channel, member, username, wiki, rows, old_username = '') {
+export default function verify(lang, channel, member, username, wiki, rows, old_username = '') {
 	/** @type {{logchannel:import('discord.js').TextChannel,flags:Number,onsuccess:String,onmatch:String}} */
 	var verifynotice = {
 		logchannel: rows[0].logchannel,
@@ -27,12 +26,12 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 		onmatch: rows[0].onmatch
 	};
 	verifynotice.logchannel = ( verifynotice.logchannel ? channel.guild.channels.cache.filter( logchannel => {
-		return ( logchannel.isGuild() && logchannel.permissionsFor(channel.guild.me).has([FLAGS.VIEW_CHANNEL, FLAGS.SEND_MESSAGES]) );
+		return ( logchannel.isGuild() && logchannel.permissionsFor(channel.guild.me).has([Permissions.FLAGS.VIEW_CHANNEL, Permissions.FLAGS.SEND_MESSAGES]) );
 	} ).get(verifynotice.logchannel) : null );
 	var embed = new MessageEmbed().setFooter( lang.get('verify.footer') ).setTimestamp();
 	var result = {
 		content: '', embed,
-		add_button: channel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS),
+		add_button: channel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS),
 		send_private: ( (verifynotice.flags & 1 << 0) === 1 << 0 ),
 		reaction: '', oauth: [],
 		logging: {
@@ -43,7 +42,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 	};
 	return got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general&list=users' + ( wiki.isFandom() ? '|usercontribs&ucprop=&uclimit=10&ucuser=' + encodeURIComponent( username ) : '' ) + '&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + encodeURIComponent( username ) + '&format=json' ).then( response => {
 		var body = response.body;
-		if ( body && body.warnings ) log_warn(body.warnings);
+		if ( body && body.warnings ) log_warning(body.warnings);
 		if ( response.statusCode !== 200 || body?.batchcomplete === undefined || !body?.query?.users ) {
 			if ( wiki.noWiki(response.url, response.statusCode) ) {
 				console.log( '- This wiki doesn\'t exist!' );
@@ -101,7 +100,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 			result.content = lang.get('verify.user_blocked_reply', escapeFormatting(username), queryuser.gender);
 			if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
 				result.logging.channel = verifynotice.logchannel.id;
-				if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+				if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 					let logembed = new MessageEmbed(embed);
 					logembed.addField( lang.get('verify.discord', 'unknown'), escapeFormatting(member.user.tag) + ` (${member.toString()})`, true );
 					result.logging.embed = logembed;
@@ -179,7 +178,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 					embed.setColor('#FFFF00').setDescription( lang.get('verify.user_failed', member.toString(), '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) );
 					if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
 						result.logging.channel = verifynotice.logchannel.id;
-						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 							result.logging.embed = new MessageEmbed(embed);
 						}
 						else {
@@ -189,8 +188,8 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 						}
 					}
 					var help_link = '';
-					if ( wiki.isGamepedia() ) help_link = lang.get('verify.help_gamepedia') + '?c=' + ( patreons[channel.guildId] && patreons[channel.guildId] !== process.env.prefix ? encodeURIComponent( patreons[channel.guildId] + 'verify' ) : 'wb' ) + ( channel.name !== 'verification' ? '&ch=' + encodeURIComponent( channel.name ) : '' ) + '&user=' + toTitle(username) + '&discord=' + encodeURIComponent( member.user.username ) + '&tag=' + member.user.discriminator + '&useskin=fandomdesktop';
-					else if ( wiki.isFandom() ) help_link = lang.get('verify.help_fandom') + '/' + toTitle(username) + '?c=' + ( patreons[channel.guildId] && patreons[channel.guildId] !== process.env.prefix ? encodeURIComponent( patreons[channel.guildId] + 'verify' ) : 'wb' ) + ( channel.name !== 'verification' ? '&ch=' + encodeURIComponent( channel.name ) : '' ) + '&user=' + encodeURIComponent( member.user.username ) + '&tag=' + member.user.discriminator + '&useskin=fandomdesktop';
+					if ( wiki.isGamepedia() ) help_link = lang.get('verify.help_gamepedia') + '?c=' + ( patreonGuildsPrefix.get(channel.guildId) !== '!wiki ' ? encodeURIComponent( patreonGuildsPrefix.get(channel.guildId) + 'verify' ) : 'wb' ) + ( channel.name !== 'verification' ? '&ch=' + encodeURIComponent( channel.name ) : '' ) + '&user=' + Wiki.toTitle(username) + '&discord=' + encodeURIComponent( member.user.username ) + '&tag=' + member.user.discriminator + '&useskin=fandomdesktop';
+					else if ( wiki.isFandom() ) help_link = lang.get('verify.help_fandom') + '/' + Wiki.toTitle(username) + '?c=' + ( patreonGuildsPrefix.get(channel.guildId) !== '!wiki ' ? encodeURIComponent( patreonGuildsPrefix.get(channel.guildId) + 'verify' ) : 'wb' ) + ( channel.name !== 'verification' ? '&ch=' + encodeURIComponent( channel.name ) : '' ) + '&user=' + encodeURIComponent( member.user.username ) + '&tag=' + member.user.discriminator + '&useskin=fandomdesktop';
 					if ( help_link.length ) embed.addField( lang.get('verify.notice'), lang.get('verify.help_guide', help_link, queryuser.gender) + '\n' + help_link );
 					result.content = lang.get('verify.user_failed_reply', escapeFormatting(username), queryuser.gender);
 					return;
@@ -278,7 +277,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 						if ( verifynotice.logchannel ) {
 							useLogging = true;
 							result.logging.channel = verifynotice.logchannel.id;
-							if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+							if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 								let logembed = new MessageEmbed(embed);
 								if ( addRolesMentions[0].length ) logembed.addField( lang.get('verify.qualified_add'), addRolesMentions[0].join('\n') );
 								if ( addRolesMentions[1].length ) logembed.setColor('#008800').addField( lang.get('verify.qualified_add_error'), addRolesMentions[1].join('\n') );
@@ -305,7 +304,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 							accountage: Math.trunc(accountage),
 							dateformat: lang.get('dateformat')
 						}).trim() : '' );
-						if ( channel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+						if ( channel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 							if ( addRolesMentions[0].length ) embed.addField( lang.get('verify.qualified_add'), addRolesMentions[0].join('\n') );
 							if ( addRolesMentions[1].length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.qualified_add_error'), addRolesMentions[1].join('\n') );
 							if ( removeRolesMentions[0].length ) embed.addField( lang.get('verify.qualified_remove'), removeRolesMentions[0].join('\n') );
@@ -332,7 +331,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 
 				if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
 					result.logging.channel = verifynotice.logchannel.id;
-					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 						result.logging.embed = new MessageEmbed(embed);
 					}
 					else {
@@ -350,7 +349,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 					dateformat: lang.get('dateformat')
 				});
 				if ( !onmatch.trim() ) return;
-				if ( channel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) embed.addField( lang.get('verify.notice'), onmatch );
+				if ( channel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) embed.addField( lang.get('verify.notice'), onmatch );
 				else result.content += '\n\n**' + lang.get('verify.notice') + '** ' + onmatch;
 			}, error => {
 				if ( error ) console.log( '- Error while getting the Discord tag: ' + error );
@@ -363,7 +362,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 			result.content = error.reply;
 			if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
 				result.logging.channel = verifynotice.logchannel.id;
-				if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+				if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 					let logembed = new MessageEmbed(embed);
 					logembed.addField( lang.get('verify.discord', 'unknown'), escapeFormatting(member.user.tag) + ` (${member.toString()})`, true );
 					result.logging.embed = logembed;
@@ -379,7 +378,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 		
 		return got.get( wiki + 'api.php?action=query' + ( wiki.hasCentralAuth() ? '&meta=globaluserinfo&guiprop=groups&guiuser=' + encodeURIComponent( username ) : '' ) + '&prop=revisions&rvprop=content|user&rvslots=main&titles=User:' + encodeURIComponent( username ) + '/Discord&format=json' ).then( mwresponse => {
 			var mwbody = mwresponse.body;
-			if ( mwbody && mwbody.warnings ) log_warn(mwbody.warnings);
+			if ( mwbody && mwbody.warnings ) log_warning(mwbody.warnings);
 			if ( mwresponse.statusCode !== 200 || mwbody?.batchcomplete === undefined || !mwbody?.query?.pages ) {
 				console.log( '- ' + mwresponse.statusCode + ': Error while getting the Discord tag: ' + ( mwbody && mwbody.error && mwbody.error.info ) );
 				embed.setColor('#000000').setDescription( lang.get('verify.error') );
@@ -392,7 +391,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 					result.content = lang.get('verify.user_gblocked_reply', escapeFormatting(username), queryuser.gender);
 					if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
 						result.logging.channel = verifynotice.logchannel.id;
-						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 							let logembed = new MessageEmbed(embed);
 							logembed.addField( lang.get('verify.discord', 'unknown'), escapeFormatting(member.user.tag) + ` (${member.toString()})`, true );
 							result.logging.embed = logembed;
@@ -421,7 +420,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 				embed.setColor('#FFFF00').setDescription( lang.get('verify.user_failed', member.toString(), '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) );
 				if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
 					result.logging.channel = verifynotice.logchannel.id;
-					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 						result.logging.embed = new MessageEmbed(embed);
 					}
 					else {
@@ -509,7 +508,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 					if ( verifynotice.logchannel ) {
 						useLogging = true;
 						result.logging.channel = verifynotice.logchannel.id;
-						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 							var logembed = new MessageEmbed(embed);
 							if ( addRolesMentions[0].length ) logembed.addField( lang.get('verify.qualified_add'), addRolesMentions[0].join('\n') );
 							if ( addRolesMentions[1].length ) logembed.setColor('#008800').addField( lang.get('verify.qualified_add_error'), addRolesMentions[1].join('\n') );
@@ -535,7 +534,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 						accountage: Math.trunc(accountage),
 						dateformat: lang.get('dateformat')
 					}).trim() : '' );
-					if ( channel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+					if ( channel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 						if ( addRolesMentions[0].length ) embed.addField( lang.get('verify.qualified_add'), addRolesMentions[0].join('\n') );
 						if ( addRolesMentions[1].length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.qualified_add_error'), addRolesMentions[1].join('\n') );
 						if ( removeRolesMentions[0].length ) embed.addField( lang.get('verify.qualified_remove'), removeRolesMentions[0].join('\n') );
@@ -567,7 +566,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 				dateformat: lang.get('dateformat')
 			});
 			if ( !onmatch.trim() ) return;
-			if ( channel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) embed.addField( lang.get('verify.notice'), onmatch );
+			if ( channel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) embed.addField( lang.get('verify.notice'), onmatch );
 			else result.content += '\n\n**' + lang.get('verify.notice') + '** ' + onmatch;
 		}, error => {
 			console.log( '- Error while getting the Discord tag: ' + error );
@@ -597,15 +596,16 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
  * @param {import('discord.js').CommandInteraction|import('discord.js').ButtonInteraction} [settings.interaction] - The interaction.
  * @param {Function} [settings.fail] - The function to call when the verifiction errors.
  * @param {import('discord.js').Message} [settings.sourceMessage] - The source message with the command.
+ * @global
  */
-global.verifyOauthUser = function(state, access_token, settings) {
+globalThis.verifyOauthUser = function(state, access_token, settings) {
 	if ( state && access_token && oauthVerify.has(state) ) {
 		settings = oauthVerify.get(state);
 		oauthVerify.delete(state);
 	}
 	if ( !settings?.channel ) return settings?.fail?.();
 	var channel = settings.channel;
-	if ( !channel.permissionsFor(channel.guild.me).has([FLAGS.VIEW_CHANNEL, ( channel.isThread() ? FLAGS.SEND_MESSAGES_IN_THREADS : FLAGS.SEND_MESSAGES )]) ) return settings.fail?.();
+	if ( !channel.permissionsFor(channel.guild.me).has([Permissions.FLAGS.VIEW_CHANNEL, ( channel.isThread() ? Permissions.FLAGS.SEND_MESSAGES_IN_THREADS : Permissions.FLAGS.SEND_MESSAGES )]) ) return settings.fail?.();
 	Promise.all([
 		db.query( 'SELECT logchannel, flags, onsuccess, onmatch, role, editcount, postcount, usergroup, accountage, rename FROM verification LEFT JOIN verifynotice ON verification.guild = verifynotice.guild WHERE verification.guild = $1 AND channel LIKE $2 ORDER BY configid ASC', [channel.guildId, '%|' + ( channel.isThread() ? channel.parentId : channel.id ) + '|%'] ).then( ({rows}) => {
 			if ( !rows.length ) return Promise.reject();
@@ -640,11 +640,11 @@ global.verifyOauthUser = function(state, access_token, settings) {
 		/** @type {{logchannel:import('discord.js').TextChannel,flags:Number,onsuccess:String,onmatch:String}} */
 		var verifynotice = ( rows[0] || {} );
 		verifynotice.logchannel = ( verifynotice.logchannel ? channel.guild.channels.cache.filter( logchannel => {
-			return ( logchannel.isGuild() && logchannel.permissionsFor(channel.guild.me).has([FLAGS.VIEW_CHANNEL, FLAGS.SEND_MESSAGES]) );
+			return ( logchannel.isGuild() && logchannel.permissionsFor(channel.guild.me).has([Permissions.FLAGS.VIEW_CHANNEL, Permissions.FLAGS.SEND_MESSAGES]) );
 		} ).get(verifynotice.logchannel) : null );
 		got.get( wiki + 'api.php?action=query&meta=siteinfo|globaluserinfo&siprop=general&guiprop=groups&guiuser=' + encodeURIComponent( username ) + '&list=users&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + encodeURIComponent( username ) + '&format=json' ).then( response => {
 			var body = response.body;
-			if ( body && body.warnings ) log_warn(body.warnings);
+			if ( body && body.warnings ) log_warning(body.warnings);
 			if ( response.statusCode !== 200 || body?.batchcomplete === undefined || !body?.query?.users?.[0] ) {
 				if ( wiki.noWiki(response.url, response.statusCode) ) {
 					console.log( '- This wiki doesn\'t exist!' );
@@ -667,7 +667,7 @@ global.verifyOauthUser = function(state, access_token, settings) {
 					if ( (verifynotice.flags & 1 << 1) !== 1 << 1 || !verifynotice.logchannel ) return;
 					let logembed;
 					let logtext;
-					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 						logembed = new MessageEmbed(embed);
 						logembed.addField( lang.get('verify.discord', 'unknown'), escapeFormatting(member.user.tag) + ` (${member.toString()})`, true );
 						if ( msg ) logembed.addField(msg.url, '<#' + channel.id + '>');
@@ -689,7 +689,7 @@ global.verifyOauthUser = function(state, access_token, settings) {
 					if ( (verifynotice.flags & 1 << 1) !== 1 << 1 || !verifynotice.logchannel ) return;
 					let logembed;
 					let logtext;
-					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 						logembed = new MessageEmbed(embed);
 						logembed.addField( lang.get('verify.discord', 'unknown'), escapeFormatting(member.user.tag) + ` (${member.toString()})`, true );
 						if ( msg ) logembed.addField(msg.url, '<#' + channel.id + '>');
@@ -783,7 +783,7 @@ global.verifyOauthUser = function(state, access_token, settings) {
 					var logtext;
 					if ( verifynotice.logchannel ) {
 						useLogging = true;
-						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 							logembed = new MessageEmbed(embed);
 							if ( addRolesMentions[0].length ) logembed.addField( lang.get('verify.qualified_add'), addRolesMentions[0].join('\n') );
 							if ( addRolesMentions[1].length ) logembed.setColor('#008800').addField( lang.get('verify.qualified_add_error'), addRolesMentions[1].join('\n') );
@@ -808,7 +808,7 @@ global.verifyOauthUser = function(state, access_token, settings) {
 						accountage: Math.trunc(accountage),
 						dateformat: lang.get('dateformat')
 					}).trim() : '' );
-					if ( channel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+					if ( channel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 						if ( addRolesMentions[0].length ) embed.addField( lang.get('verify.qualified_add'), addRolesMentions[0].join('\n') );
 						if ( addRolesMentions[1].length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.qualified_add_error'), addRolesMentions[1].join('\n') );
 						if ( removeRolesMentions[0].length ) embed.addField( lang.get('verify.qualified_remove'), removeRolesMentions[0].join('\n') );
@@ -844,7 +844,7 @@ global.verifyOauthUser = function(state, access_token, settings) {
 			let logembed;
 			let logtext;
 			if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
-				if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+				if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 					logembed = new MessageEmbed(embed);
 				}
 				else {
@@ -861,7 +861,7 @@ global.verifyOauthUser = function(state, access_token, settings) {
 					dateformat: lang.get('dateformat')
 				});
 				if ( onmatch.trim() ) {
-					if ( channel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) embed.addField( lang.get('verify.notice'), onmatch );
+					if ( channel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) embed.addField( lang.get('verify.notice'), onmatch );
 					else noticeContent = '\n\n**' + lang.get('verify.notice') + '** ' + onmatch;
 				}
 			}
@@ -873,7 +873,7 @@ global.verifyOauthUser = function(state, access_token, settings) {
 			} ).then( msg => {
 				if ( !logtext && !logembed ) return;
 				if ( msg ) {
-					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(FLAGS.EMBED_LINKS) ) {
+					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has(Permissions.FLAGS.EMBED_LINKS) ) {
 						logembed.addField(msg.url, '<#' + channel.id + '>');
 					}
 					else logtext += '\n<#' + channel.id + '> – <' + msg.url + '>';
@@ -1050,6 +1050,4 @@ function ifexpr(number, operator) {
 			break;
 	}
 	return result;
-}
-
-module.exports = verify;
+}

+ 12 - 10
interactions/inline.js

@@ -1,13 +1,13 @@
-const {Permissions: {FLAGS}} = require('discord.js');
-const logging = require('../util/logging.js');
-const Wiki = require('../util/wiki.js');
-const {got, limitLength, partialURIdecode, sendMessage} = require('../util/functions.js');
+import {Permissions} from 'discord.js';
+import logging from '../util/logging.js';
+import Wiki from '../util/wiki.js';
+import {got, limitLength, partialURIdecode, sendMessage} from '../util/functions.js';
 
 /**
  * Post a message with inline wiki links.
  * @param {import('discord.js').CommandInteraction} interaction - The interaction.
- * @param {import('../util/i18n.js')} lang - The user language.
- * @param {import('../util/wiki.js')} wiki - The wiki for the interaction.
+ * @param {import('../util/i18n.js').default} lang - The user language.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the interaction.
  */
 function slash_inline(interaction, lang, wiki) {
 	var text = ( interaction.options.getString('text') || '' ).replace( /\]\(/g, ']\\(' );
@@ -15,17 +15,18 @@ function slash_inline(interaction, lang, wiki) {
 	if ( !text.includes( '{{' ) && !( text.includes( '[[' ) && text.includes( ']]' ) ) && !text.includes( 'PMID' ) && !text.includes( 'RFC' ) && !text.includes( 'ISBN' ) ) {
 		return interaction.reply( {content: lang.get('interaction.inline'), ephemeral: true} ).catch(log_error);
 	}
+	/** @type {import('discord.js').MessageMentionOptions} */
 	var allowedMentions = {
 		parse: ['users']
 	};
 	if ( interaction.inGuild() ) {
-		if ( interaction.member.permissions.has(FLAGS.MENTION_EVERYONE) ) {
+		if ( interaction.member.permissions.has(Permissions.FLAGS.MENTION_EVERYONE) ) {
 			allowedMentions.parse = ['users', 'roles', 'everyone'];
 		}
 		else if ( interaction.guild ) {
 			allowedMentions.roles = interaction.guild.roles.cache.filter( role => role.mentionable ).map( role => role.id ).slice(0, 100);
 		}
-		if ( interaction.guild && !interaction.member.permissions.has(FLAGS.USE_EXTERNAL_EMOJIS) ) {
+		if ( interaction.guild && !interaction.member.permissions.has(Permissions.FLAGS.USE_EXTERNAL_EMOJIS) ) {
 			text = text.replace( /(?<!\\)<a?(:\w+:)\d+>/g, (replacement, emoji, id) => {
 				if ( interaction.guild.emojis.cache.has(id) ) {
 					return replacement;
@@ -262,7 +263,8 @@ function slash_inline(interaction, lang, wiki) {
 	}, log_error );
 }
 
-module.exports = {
+export default {
 	name: 'inline',
-	run: slash_inline
+	run: slash_inline,
+	button: null
 };

+ 18 - 18
interactions/verify.js

@@ -1,24 +1,24 @@
-const {randomBytes} = require('crypto');
-const {MessageActionRow, MessageButton, Permissions: {FLAGS}} = require('discord.js');
-var db = require('../util/database.js');
-var verify = require('../functions/verify.js');
-const {got, oauthVerify, sendMessage} = require('../util/functions.js');
+import {randomBytes} from 'crypto';
+import {MessageActionRow, MessageButton, Permissions} from 'discord.js';
+import db from '../util/database.js';
+import verify from '../functions/verify.js';
+import {got, oauthVerify, sendMessage} from '../util/functions.js';
 
 /**
  * Wiki user verification.
  * @param {import('discord.js').CommandInteraction} interaction - The interaction.
- * @param {import('../util/i18n.js')} lang - The user language.
- * @param {import('../util/wiki.js')} wiki - The wiki for the interaction.
+ * @param {import('../util/i18n.js').default} lang - The user language.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the interaction.
  */
 function slash_verify(interaction, lang, wiki) {
 	if ( !interaction.guild ) return interaction.reply( {content: lang.get('verify.missing'), ephemeral: true} ).catch(log_error);
-	if ( !interaction.guild.me.permissions.has(FLAGS.MANAGE_ROLES) ) {
+	if ( !interaction.guild.me.permissions.has(Permissions.FLAGS.MANAGE_ROLES) ) {
 		console.log( interaction.guildId + ': Missing permissions - MANAGE_ROLES' );
 		return interaction.reply( {content: lang.get('general.missingperm') + ' `MANAGE_ROLES`', ephemeral: true} ).catch(log_error);
 	}
 	
 	return db.query( 'SELECT logchannel, flags, onsuccess, onmatch, role, editcount, postcount, usergroup, accountage, rename FROM verification LEFT JOIN verifynotice ON verification.guild = verifynotice.guild WHERE verification.guild = $1 AND channel LIKE $2 ORDER BY configid ASC', [interaction.guildId, '%|' + ( interaction.channel?.isThread() ? interaction.channel.parentId : interaction.channelId ) + '|%'] ).then( ({rows}) => {
-		if ( !rows.length ) return interaction.reply( {content: lang.get('verify.missing') + ( interaction.member.permissions.has(FLAGS.MANAGE_GUILD) && process.env.dashboard ? '\n' + new URL(`/guild/${interaction.guildId}/verification`, process.env.dashboard).href : '' ), ephemeral: true} ).catch(log_error);
+		if ( !rows.length ) return interaction.reply( {content: lang.get('verify.missing') + ( interaction.member.permissions.has(Permissions.FLAGS.MANAGE_GUILD) && process.env.dashboard ? '\n' + new URL(`/guild/${interaction.guildId}/verification`, process.env.dashboard).href : '' ), ephemeral: true} ).catch(log_error);
 
 		if ( wiki.hasOAuth2() && process.env.dashboard ) {
 			let oauth = [wiki.hostname + wiki.pathname.slice(0, -1)];
@@ -45,7 +45,7 @@ function slash_verify(interaction, lang, wiki) {
 							}, dberror => {
 								console.log( '- Dashboard: Error while updating the OAuth2 token for ' + interaction.user.id + ': ' + dberror );
 							} );
-							return global.verifyOauthUser('', body.access_token, {
+							return verifyOauthUser('', body.access_token, {
 								wiki: wiki.href, channel: interaction.channel,
 								user: interaction.user.id, interaction,
 								fail: () => sendMessage(interaction, lang.get('verify.error_reply'))
@@ -137,7 +137,7 @@ function slash_verify(interaction, lang, wiki) {
 							}, dberror => {
 								console.log( '- Dashboard: Error while updating the OAuth2 token for ' + interaction.user.id + ': ' + dberror );
 							} );
-							return global.verifyOauthUser('', body.access_token, {
+							return verifyOauthUser('', body.access_token, {
 								wiki: wiki.href, channel: interaction.channel,
 								user: interaction.user.id, interaction,
 								fail: () => sendMessage(interaction, lang.get('verify.error_reply'))
@@ -227,10 +227,10 @@ function slash_verify(interaction, lang, wiki) {
 /**
  * Wiki user verification.
  * @param {import('discord.js').ButtonInteraction} interaction - The interaction.
- * @param {import('../util/i18n.js')} lang - The user language.
- * @param {import('../util/wiki.js')} wiki - The wiki for the interaction.
+ * @param {import('../util/i18n.js').default} lang - The user language.
+ * @param {import('../util/wiki.js').default} wiki - The wiki for the interaction.
  */
- function button_verify(interaction, lang, wiki) {
+function button_verify(interaction, lang, wiki) {
 	var username = interaction.message?.embeds?.[0]?.title?.replace( /\\(\\)?/g, '$1' );
 	if ( !username || !interaction.guild || !interaction.message.mentions?.users?.size ) {
 		return interaction.update( {components: []} ).catch(log_error);
@@ -239,7 +239,7 @@ function slash_verify(interaction, lang, wiki) {
 		return interaction.reply( {content: lang.get('verify.button_wrong_user', interaction.message.mentions.users.first().toString()), ephemeral: true} ).catch(log_error);
 	}
 	return db.query( 'SELECT logchannel, flags, onsuccess, onmatch, role, editcount, postcount, usergroup, accountage, rename FROM verification LEFT JOIN verifynotice ON verification.guild = verifynotice.guild WHERE verification.guild = $1 AND channel LIKE $2 ORDER BY configid ASC', [interaction.guildId, '%|' + ( interaction.channel?.isThread() ? interaction.channel.parentId : interaction.channelId ) + '|%'] ).then( ({rows}) => {
-		if ( !rows.length || !interaction.guild.me.permissions.has(FLAGS.MANAGE_ROLES) ) return interaction.update( {components: []} ).catch(log_error);
+		if ( !rows.length || !interaction.guild.me.permissions.has(Permissions.FLAGS.MANAGE_ROLES) ) return interaction.update( {components: []} ).catch(log_error);
 
 		if ( wiki.hasOAuth2() && process.env.dashboard ) {
 			let oauth = [wiki.hostname + wiki.pathname.slice(0, -1)];
@@ -269,7 +269,7 @@ function slash_verify(interaction, lang, wiki) {
 							}, dberror => {
 								console.log( '- Dashboard: Error while updating the OAuth2 token for ' + interaction.user.id + ': ' + dberror );
 							} );
-							return global.verifyOauthUser('', body.access_token, {
+							return verifyOauthUser('', body.access_token, {
 								wiki: wiki.href, channel: interaction.channel,
 								user: interaction.user.id, interaction,
 								fail: () => sendMessage(interaction, {components: []}, false)
@@ -342,7 +342,7 @@ function slash_verify(interaction, lang, wiki) {
 							}, dberror => {
 								console.log( '- Dashboard: Error while updating the OAuth2 token for ' + interaction.user.id + ': ' + dberror );
 							} );
-							return global.verifyOauthUser('', body.access_token, {
+							return verifyOauthUser('', body.access_token, {
 								wiki: wiki.href, channel: interaction.channel,
 								user: interaction.user.id, interaction,
 								fail: () => sendMessage(interaction, {components: []}, false)
@@ -430,7 +430,7 @@ function slash_verify(interaction, lang, wiki) {
 	} );
 }
 
-module.exports = {
+export default {
 	name: 'verify',
 	run: slash_verify,
 	button: button_verify

+ 23 - 24
main.js

@@ -1,21 +1,24 @@
-require('dotenv').config();
+import 'dotenv/config';
+import './database.js';
+import {fork as forkChildProcess} from 'child_process';
+import gotDefault from 'got';
+import {ShardingManager, ShardClientUtil} from 'discord.js';
+const {shardIdForGuildId} = ShardClientUtil;
 
 var isDebug = ( process.argv[2] === 'debug' );
 if ( process.argv[2] === 'readonly' ) process.env.READONLY = true;
 
-require('./database.js').then( () => {
-
-const child_process = require('child_process');
-
-const got = require('got').extend( {
+const got = gotDefault.extend( {
 	throwHttpErrors: false,
-	timeout: 30000,
+	timeout: {
+		request: 30000
+	},
 	headers: {
 		'User-Agent': 'Wiki-Bot/' + ( isDebug ? 'testing' : process.env.npm_package_version ) + ' (Discord; ' + process.env.npm_package_name + ( process.env.invite ? '; ' + process.env.invite : '' ) + ')'
 	},
 	responseType: 'json'
 } );
-const {ShardingManager, ShardClientUtil: {shardIdForGuildId}} = require('discord.js');
+
 const manager = new ShardingManager( './bot.js', {
 	execArgv: ['--icu-data-dir=node_modules/full-icu'],
 	shardArgs: ( isDebug ? ['debug'] : [] ),
@@ -43,7 +46,7 @@ manager.on( 'shardCreate', shard => {
 			console.log( '\n- Toggle debug logging for all shards!\n' );
 			isDebug = !isDebug;
 			manager.broadcastEval( () => {
-				global.isDebug = !global.isDebug;
+				globalThis.isDebug = !globalThis.isDebug;
 			} );
 			if ( typeof server !== 'undefined' ) server.send( 'toggleDebug' );
 		}
@@ -98,7 +101,7 @@ manager.spawn( {
 
 var server;
 if ( process.env.dashboard ) {
-	const dashboard = child_process.fork('./dashboard/index.js', ( isDebug ? ['debug'] : [] ));
+	const dashboard = forkChildProcess('./dashboard/index.js', ( isDebug ? ['debug'] : [] ));
 	server = dashboard;
 
 	const evalFunctions = {
@@ -109,7 +112,7 @@ if ( process.env.dashboard ) {
 						let guild = discordClient.guilds.cache.get(id);
 						return guild.members.fetch(evalData.member).then( member => {
 							return {
-								patreon: global.patreons.hasOwnProperty(guild.id),
+								patreon: globalThis.patreonGuildsPrefix.has(guild.id),
 								memberCount: guild.memberCount,
 								botPermissions: guild.me.permissions.bitfield.toString(),
 								channels: guild.channels.cache.filter( channel => {
@@ -158,7 +161,7 @@ if ( process.env.dashboard ) {
 				let guild = discordClient.guilds.cache.get(evalData.guild);
 				return guild.members.fetch(evalData.member).then( member => {
 					var response = {
-						patreon: global.patreons.hasOwnProperty(guild.id),
+						patreon: globalThis.patreonGuildsPrefix.has(guild.id),
 						userPermissions: member.permissions.bitfield.toString(),
 						botPermissions: guild.me.permissions.bitfield.toString()
 					};
@@ -188,10 +191,10 @@ if ( process.env.dashboard ) {
 		},
 		notifyGuild: (discordClient, evalData) => {
 			if ( evalData.prefix ) {
-				global.patreons[evalData.guild] = evalData.prefix;
+				globalThis.patreonGuildsPrefix.set(evalData.guild, evalData.prefix);
 			}
-			if ( evalData.voice && global.voice.hasOwnProperty(evalData.guild) ) {
-				global.voice[evalData.guild] = evalData.voice;
+			if ( evalData.voice && globalThis.voiceGuildsLang.has(evalData.guild) ) {
+				globalThis.voiceGuildsLang.set(evalData.guild, evalData.voice);
 			}
 			if ( discordClient.guilds.cache.has(evalData.guild) ) {
 				let channel = discordClient.guilds.cache.get(evalData.guild).publicUpdatesChannel;
@@ -200,7 +203,7 @@ if ( process.env.dashboard ) {
 					embeds: ( evalData.embed ? [evalData.embed] : [] ),
 					files: evalData.file,
 					allowedMentions: {parse: []}
-				} ).catch(log_error);
+				} ).catch(globalThis.log_error);
 			}
 		},
 		createWebhook: (discordClient, evalData) => {
@@ -211,7 +214,7 @@ if ( process.env.dashboard ) {
 					reason: evalData.reason
 				} ).then( webhook => {
 					console.log( `- Dashboard: Webhook successfully created: ${evalData.guild}#${evalData.channel}` );
-					webhook.send( evalData.text ).catch(log_error);
+					webhook.send( evalData.text ).catch(globalThis.log_error);
 					return webhook.id + '/' + webhook.token;
 				}, error => {
 					console.log( '- Dashboard: Error while creating the webhook: ' + error );
@@ -227,7 +230,7 @@ if ( process.env.dashboard ) {
 					if ( evalData.avatar ) changes.avatar = evalData.avatar;
 					return webhook.edit( changes, evalData.reason ).then( newwebhook => {
 						console.log( `- Dashboard: Webhook successfully edited: ${evalData.guild}#` + ( evalData.channel || webhook.channelId ) );
-						webhook.send( evalData.text ).catch(log_error);
+						webhook.send( evalData.text ).catch(globalThis.log_error);
 						return true;
 					}, error => {
 						console.log( '- Dashboard: Error while editing the webhook: ' + error );
@@ -238,7 +241,7 @@ if ( process.env.dashboard ) {
 			}
 		},
 		verifyUser: (discordClient, evalData) => {
-			global.verifyOauthUser(evalData.state, evalData.access_token);
+			globalThis.verifyOauthUser(evalData.state, evalData.access_token);
 		}
 	};
 
@@ -373,8 +376,4 @@ if ( isDebug && process.argv[3]?.startsWith( '--timeout:' ) ) {
 		manager.shards.filter( shard => shard.process && !shard.process.killed ).forEach( shard => shard.kill() );
 		if ( typeof server !== 'undefined' && !server.killed ) server.kill();
 	}, timeout * 1000 ).unref();
-}
-
-}, () => {
-	process.exit(1);
-} )
+}

+ 128 - 97
package-lock.json

@@ -15,9 +15,9 @@
         "discord.js": "^13.3.1",
         "dotenv": "^10.0.0",
         "full-icu": "^1.4.0",
-        "got": "^11.8.3",
+        "got": "^12.0.0",
         "htmlparser2": "^7.2.0",
-        "npm": "^8.2.0",
+        "npm": "^8.3.0",
         "pg": "^8.7.1"
       },
       "engines": {
@@ -83,14 +83,14 @@
       }
     },
     "node_modules/@szmarczak/http-timer": {
-      "version": "4.0.6",
-      "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
-      "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz",
+      "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==",
       "dependencies": {
-        "defer-to-connect": "^2.0.0"
+        "defer-to-connect": "^2.0.1"
       },
       "engines": {
-        "node": ">=10"
+        "node": ">=14.16"
       }
     },
     "node_modules/@types/cacheable-request": {
@@ -174,9 +174,9 @@
       }
     },
     "node_modules/cacheable-lookup": {
-      "version": "5.0.4",
-      "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
-      "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
+      "version": "6.0.4",
+      "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.0.4.tgz",
+      "integrity": "sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A==",
       "engines": {
         "node": ">=10.6.0"
       }
@@ -493,6 +493,11 @@
         "node": ">= 6"
       }
     },
+    "node_modules/form-data-encoder": {
+      "version": "1.7.1",
+      "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.1.tgz",
+      "integrity": "sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg=="
+    },
     "node_modules/full-icu": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/full-icu/-/full-icu-1.4.0.tgz",
@@ -521,29 +526,53 @@
       }
     },
     "node_modules/got": {
-      "version": "11.8.3",
-      "resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz",
-      "integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==",
+      "version": "12.0.0",
+      "resolved": "https://registry.npmjs.org/got/-/got-12.0.0.tgz",
+      "integrity": "sha512-gNNNghQ1yw0hyzie1FLK6gY90BQlXU9zSByyRygnbomHPruKQ6hAKKbpO1RfNZp8b+qNzNipGeRG3tUelKcVsA==",
       "dependencies": {
-        "@sindresorhus/is": "^4.0.0",
-        "@szmarczak/http-timer": "^4.0.5",
-        "@types/cacheable-request": "^6.0.1",
+        "@sindresorhus/is": "^4.2.0",
+        "@szmarczak/http-timer": "^5.0.1",
+        "@types/cacheable-request": "^6.0.2",
         "@types/responselike": "^1.0.0",
-        "cacheable-lookup": "^5.0.3",
+        "cacheable-lookup": "^6.0.4",
         "cacheable-request": "^7.0.2",
         "decompress-response": "^6.0.0",
-        "http2-wrapper": "^1.0.0-beta.5.2",
-        "lowercase-keys": "^2.0.0",
-        "p-cancelable": "^2.0.0",
+        "form-data-encoder": "1.7.1",
+        "get-stream": "^6.0.1",
+        "http2-wrapper": "^2.1.9",
+        "lowercase-keys": "^3.0.0",
+        "p-cancelable": "^3.0.0",
         "responselike": "^2.0.0"
       },
       "engines": {
-        "node": ">=10.19.0"
+        "node": ">=14.16"
       },
       "funding": {
         "url": "https://github.com/sindresorhus/got?sponsor=1"
       }
     },
+    "node_modules/got/node_modules/get-stream": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+      "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/got/node_modules/lowercase-keys": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
+      "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==",
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/htmlparser2": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz",
@@ -579,12 +608,12 @@
       "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
     },
     "node_modules/http2-wrapper": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
-      "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+      "version": "2.1.9",
+      "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.9.tgz",
+      "integrity": "sha512-z5jPLkDXHsQM762XFo4XproHTXT0lMQscKCQMPGccHDzY0kNxmUxWyGkW66zB2RGAr9pF9Tzc5Dmmv8Uh8HW3Q==",
       "dependencies": {
         "quick-lru": "^5.1.1",
-        "resolve-alpn": "^1.0.0"
+        "resolve-alpn": "^1.2.0"
       },
       "engines": {
         "node": ">=10.19.0"
@@ -679,9 +708,9 @@
       }
     },
     "node_modules/npm": {
-      "version": "8.2.0",
-      "resolved": "https://registry.npmjs.org/npm/-/npm-8.2.0.tgz",
-      "integrity": "sha512-doYfni7IWhc5xJdikZK3PKENz3QcTcV5Y2EtF8HA3eEPePeemdgGl3j2OyNPT25+UBschVWSVgz4ER1/wnjF7Q==",
+      "version": "8.3.0",
+      "resolved": "https://registry.npmjs.org/npm/-/npm-8.3.0.tgz",
+      "integrity": "sha512-ug4xToae4Dh3yZh8Fp6MOnAPSS3fqCTANpJx1fXP2C4LTUzoZf7rEantHQR/ANPVYDBe5qQT4tGVsoPqqiYZMw==",
       "bundleDependencies": [
         "@isaacs/string-locale-compare",
         "@npmcli/arborist",
@@ -847,7 +876,7 @@
       "license": "ISC"
     },
     "node_modules/npm/node_modules/@npmcli/arborist": {
-      "version": "4.0.5",
+      "version": "4.1.1",
       "inBundle": true,
       "license": "ISC",
       "dependencies": {
@@ -872,7 +901,7 @@
         "npm-pick-manifest": "^6.1.0",
         "npm-registry-fetch": "^11.0.0",
         "pacote": "^12.0.2",
-        "parse-conflict-json": "^1.1.1",
+        "parse-conflict-json": "^2.0.1",
         "proc-log": "^1.0.0",
         "promise-all-reject-late": "^1.0.0",
         "promise-call-limit": "^1.0.1",
@@ -1385,13 +1414,6 @@
         "node": ">=10"
       }
     },
-    "node_modules/npm/node_modules/code-point-at": {
-      "version": "1.1.0",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/npm/node_modules/color-convert": {
       "version": "2.0.1",
       "inBundle": true,
@@ -1888,12 +1910,12 @@
       "license": "MIT"
     },
     "node_modules/npm/node_modules/just-diff": {
-      "version": "3.1.1",
+      "version": "5.0.1",
       "inBundle": true,
       "license": "MIT"
     },
     "node_modules/npm/node_modules/just-diff-apply": {
-      "version": "3.0.0",
+      "version": "4.0.1",
       "inBundle": true,
       "license": "MIT"
     },
@@ -2100,7 +2122,7 @@
       }
     },
     "node_modules/npm/node_modules/minipass": {
-      "version": "3.1.5",
+      "version": "3.1.6",
       "inBundle": true,
       "license": "ISC",
       "dependencies": {
@@ -2406,13 +2428,6 @@
         "node": "^12.13.0 || ^14.15.0 || >=16"
       }
     },
-    "node_modules/npm/node_modules/number-is-nan": {
-      "version": "1.0.1",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/npm/node_modules/object-assign": {
       "version": "4.1.1",
       "inBundle": true,
@@ -2484,13 +2499,16 @@
       }
     },
     "node_modules/npm/node_modules/parse-conflict-json": {
-      "version": "1.1.1",
+      "version": "2.0.1",
       "inBundle": true,
       "license": "ISC",
       "dependencies": {
-        "json-parse-even-better-errors": "^2.3.0",
-        "just-diff": "^3.0.1",
-        "just-diff-apply": "^3.0.0"
+        "json-parse-even-better-errors": "^2.3.1",
+        "just-diff": "^5.0.1",
+        "just-diff-apply": "^4.0.1"
+      },
+      "engines": {
+        "node": "^12.13.0 || ^14.15.0 || >=16"
       }
     },
     "node_modules/npm/node_modules/path-is-absolute": {
@@ -3003,11 +3021,11 @@
       }
     },
     "node_modules/p-cancelable": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
-      "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
+      "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==",
       "engines": {
-        "node": ">=8"
+        "node": ">=12.20"
       }
     },
     "node_modules/packet-reader": {
@@ -3358,11 +3376,11 @@
       "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw=="
     },
     "@szmarczak/http-timer": {
-      "version": "4.0.6",
-      "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
-      "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz",
+      "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==",
       "requires": {
-        "defer-to-connect": "^2.0.0"
+        "defer-to-connect": "^2.0.1"
       }
     },
     "@types/cacheable-request": {
@@ -3440,9 +3458,9 @@
       "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
     },
     "cacheable-lookup": {
-      "version": "5.0.4",
-      "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
-      "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="
+      "version": "6.0.4",
+      "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.0.4.tgz",
+      "integrity": "sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A=="
     },
     "cacheable-request": {
       "version": "7.0.2",
@@ -3668,6 +3686,11 @@
         "mime-types": "^2.1.12"
       }
     },
+    "form-data-encoder": {
+      "version": "1.7.1",
+      "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.1.tgz",
+      "integrity": "sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg=="
+    },
     "full-icu": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/full-icu/-/full-icu-1.4.0.tgz",
@@ -3685,21 +3708,35 @@
       }
     },
     "got": {
-      "version": "11.8.3",
-      "resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz",
-      "integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==",
+      "version": "12.0.0",
+      "resolved": "https://registry.npmjs.org/got/-/got-12.0.0.tgz",
+      "integrity": "sha512-gNNNghQ1yw0hyzie1FLK6gY90BQlXU9zSByyRygnbomHPruKQ6hAKKbpO1RfNZp8b+qNzNipGeRG3tUelKcVsA==",
       "requires": {
-        "@sindresorhus/is": "^4.0.0",
-        "@szmarczak/http-timer": "^4.0.5",
-        "@types/cacheable-request": "^6.0.1",
+        "@sindresorhus/is": "^4.2.0",
+        "@szmarczak/http-timer": "^5.0.1",
+        "@types/cacheable-request": "^6.0.2",
         "@types/responselike": "^1.0.0",
-        "cacheable-lookup": "^5.0.3",
+        "cacheable-lookup": "^6.0.4",
         "cacheable-request": "^7.0.2",
         "decompress-response": "^6.0.0",
-        "http2-wrapper": "^1.0.0-beta.5.2",
-        "lowercase-keys": "^2.0.0",
-        "p-cancelable": "^2.0.0",
+        "form-data-encoder": "1.7.1",
+        "get-stream": "^6.0.1",
+        "http2-wrapper": "^2.1.9",
+        "lowercase-keys": "^3.0.0",
+        "p-cancelable": "^3.0.0",
         "responselike": "^2.0.0"
+      },
+      "dependencies": {
+        "get-stream": {
+          "version": "6.0.1",
+          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+          "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="
+        },
+        "lowercase-keys": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
+          "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ=="
+        }
       }
     },
     "htmlparser2": {
@@ -3726,12 +3763,12 @@
       "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
     },
     "http2-wrapper": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
-      "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+      "version": "2.1.9",
+      "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.1.9.tgz",
+      "integrity": "sha512-z5jPLkDXHsQM762XFo4XproHTXT0lMQscKCQMPGccHDzY0kNxmUxWyGkW66zB2RGAr9pF9Tzc5Dmmv8Uh8HW3Q==",
       "requires": {
         "quick-lru": "^5.1.1",
-        "resolve-alpn": "^1.0.0"
+        "resolve-alpn": "^1.2.0"
       }
     },
     "inherits": {
@@ -3799,9 +3836,9 @@
       "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="
     },
     "npm": {
-      "version": "8.2.0",
-      "resolved": "https://registry.npmjs.org/npm/-/npm-8.2.0.tgz",
-      "integrity": "sha512-doYfni7IWhc5xJdikZK3PKENz3QcTcV5Y2EtF8HA3eEPePeemdgGl3j2OyNPT25+UBschVWSVgz4ER1/wnjF7Q==",
+      "version": "8.3.0",
+      "resolved": "https://registry.npmjs.org/npm/-/npm-8.3.0.tgz",
+      "integrity": "sha512-ug4xToae4Dh3yZh8Fp6MOnAPSS3fqCTANpJx1fXP2C4LTUzoZf7rEantHQR/ANPVYDBe5qQT4tGVsoPqqiYZMw==",
       "requires": {
         "@isaacs/string-locale-compare": "*",
         "@npmcli/arborist": "*",
@@ -3885,7 +3922,7 @@
           "bundled": true
         },
         "@npmcli/arborist": {
-          "version": "4.0.5",
+          "version": "4.1.1",
           "bundled": true,
           "requires": {
             "@isaacs/string-locale-compare": "^1.1.0",
@@ -3909,7 +3946,7 @@
             "npm-pick-manifest": "^6.1.0",
             "npm-registry-fetch": "^11.0.0",
             "pacote": "^12.0.2",
-            "parse-conflict-json": "^1.1.1",
+            "parse-conflict-json": "^2.0.1",
             "proc-log": "^1.0.0",
             "promise-all-reject-late": "^1.0.0",
             "promise-call-limit": "^1.0.1",
@@ -4264,9 +4301,6 @@
             "mkdirp-infer-owner": "^2.0.0"
           }
         },
-        "code-point-at": {
-          "version": "1.1.0"
-        },
         "color-convert": {
           "version": "2.0.1",
           "bundled": true,
@@ -4604,11 +4638,11 @@
           "bundled": true
         },
         "just-diff": {
-          "version": "3.1.1",
+          "version": "5.0.1",
           "bundled": true
         },
         "just-diff-apply": {
-          "version": "3.0.0",
+          "version": "4.0.1",
           "bundled": true
         },
         "libnpmaccess": {
@@ -4758,7 +4792,7 @@
           }
         },
         "minipass": {
-          "version": "3.1.5",
+          "version": "3.1.6",
           "bundled": true,
           "requires": {
             "yallist": "^4.0.0"
@@ -4963,9 +4997,6 @@
             "set-blocking": "^2.0.0"
           }
         },
-        "number-is-nan": {
-          "version": "1.0.1"
-        },
         "object-assign": {
           "version": "4.1.1",
           "bundled": true
@@ -5014,12 +5045,12 @@
           }
         },
         "parse-conflict-json": {
-          "version": "1.1.1",
+          "version": "2.0.1",
           "bundled": true,
           "requires": {
-            "json-parse-even-better-errors": "^2.3.0",
-            "just-diff": "^3.0.1",
-            "just-diff-apply": "^3.0.0"
+            "json-parse-even-better-errors": "^2.3.1",
+            "just-diff": "^5.0.1",
+            "just-diff-apply": "^4.0.1"
           }
         },
         "path-is-absolute": {
@@ -5381,9 +5412,9 @@
       }
     },
     "p-cancelable": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
-      "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
+      "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="
     },
     "packet-reader": {
       "version": "1.0.0",

+ 3 - 2
package.json

@@ -1,6 +1,7 @@
 {
   "name": "discord-wiki-bot",
   "version": "4.2.0",
+  "type": "module",
   "description": "Wiki-Bot is a bot with the purpose to easily search for and link to wiki pages. Wiki-Bot shows short descriptions and additional info about pages and is able to resolve redirects and follow interwiki links.",
   "main": "main.js",
   "scripts": {
@@ -21,9 +22,9 @@
     "discord.js": "^13.3.1",
     "dotenv": "^10.0.0",
     "full-icu": "^1.4.0",
-    "got": "^11.8.3",
+    "got": "^12.0.0",
     "htmlparser2": "^7.2.0",
-    "npm": "^8.2.0",
+    "npm": "^8.3.0",
     "pg": "^8.7.1"
   },
   "repository": {

+ 6 - 8
util/database.js

@@ -1,13 +1,13 @@
-const {Pool} = require('pg');
-const db = new Pool();
-db.on( 'error', dberror => {
+import pg from 'pg';
+const db = new pg.Pool();
+export default db.on( 'error', dberror => {
 	console.log( '- ' + process.env.SHARDS + ': Error while connecting to the database: ' + dberror );
 } );
 
 db.query( 'SELECT guild, prefix FROM discord WHERE patreon IS NOT NULL' ).then( ({rows}) => {
 	console.log( '- ' + process.env.SHARDS + ': Patreons successfully loaded.' );
 	rows.forEach( row => {
-		patreons[row.guild] = row.prefix;
+		patreonGuildsPrefix.set(row.guild, row.prefix);
 	} );
 }, dberror => {
 	console.log( '- ' + process.env.SHARDS + ': Error while getting the patreons: ' + dberror );
@@ -15,10 +15,8 @@ db.query( 'SELECT guild, prefix FROM discord WHERE patreon IS NOT NULL' ).then(
 db.query( 'SELECT guild, lang FROM discord WHERE voice IS NOT NULL' ).then( ({rows}) => {
 	console.log( '- ' + process.env.SHARDS + ': Voice channels successfully loaded.' );
 	rows.forEach( row => {
-		voice[row.guild] = row.lang;
+		voiceGuildsLang.set(row.guild, row.lang);
 	} );
 }, dberror => {
 	console.log( '- ' + process.env.SHARDS + ': Error while getting the voice channels: ' + dberror );
-} );
-
-module.exports = db;
+} );

+ 4 - 6
util/edit_diff.js

@@ -1,5 +1,5 @@
-const htmlparser = require('htmlparser2');
-const {escapeFormatting} = require('./functions.js');
+import htmlparser from 'htmlparser2';
+import {escapeFormatting} from './functions.js';
 
 /**
  * Change edit diffs to markdown text.
@@ -8,7 +8,7 @@ const {escapeFormatting} = require('./functions.js');
  * @param {String} whitespace - The localized string for only whitespace.
  * @returns {String[]}
  */
-function diffParser(html, more, whitespace) {
+export default function diffParser(html, more, whitespace) {
 	var current_tag = '';
 	var last_ins = null;
 	var last_del = null;
@@ -96,6 +96,4 @@ function diffParser(html, more, whitespace) {
 		} else compare[1] = whitespace;
 	}
 	return compare;
-}
-
-module.exports = diffParser;
+}

+ 3 - 5
util/extract_desc.js

@@ -1,4 +1,4 @@
-const {escapeFormatting} = require('./functions.js');
+import {escapeFormatting} from './functions.js';
 
 /**
  * Get the description for a page.
@@ -6,7 +6,7 @@ const {escapeFormatting} = require('./functions.js');
  * @param {String} [fragment] - The section title.
  * @returns {String[]}
  */
-function extract_desc(text = '', fragment = '') {
+export default function extract_desc(text = '', fragment = '') {
 	var sectionIndex = text.indexOf('\ufffd\ufffd');
 	var extract = escapeFormatting(( sectionIndex !== -1 ? text.substring(0, sectionIndex) : text ).trim());
 	if ( extract.length > 1000 ) extract = extract.substring(0, 1000) + '\u2026';
@@ -74,6 +74,4 @@ function section_formatting(title, n) {
 			break;
 	}
 	return title;
-}
-
-module.exports = extract_desc;
+}

+ 9 - 6
util/functions.js

@@ -1,7 +1,10 @@
-const htmlparser = require('htmlparser2');
-const got = require('got').extend( {
+import htmlparser from 'htmlparser2';
+import gotDefault from 'got';
+const got = gotDefault.extend( {
 	throwHttpErrors: false,
-	timeout: 5000,
+	timeout: {
+		request: 5000
+	},
 	headers: {
 		'User-Agent': 'Wiki-Bot/' + ( isDebug ? 'testing' : process.env.npm_package_version ) + ' (Discord; ' + process.env.npm_package_name + ( process.env.invite ? '; ' + process.env.invite : '' ) + ')'
 	},
@@ -97,7 +100,7 @@ function parse_infobox(infobox, embed, thumbnail, pagelink = '') {
  * Make wikitext formatting usage.
  * @param {String} [text] - The text to modify.
  * @param {Boolean} [showEmbed] - If the text is used in an embed.
- * @param {import('./wiki.js')} [wiki] - The wiki.
+ * @param {import('./wiki.js').default} [wiki] - The wiki.
  * @param {String} [title] - The page title.
  * @param {Boolean} [fullWikitext] - If the text can contain full wikitext.
  * @returns {String}
@@ -110,7 +113,7 @@ function toFormatting(text = '', showEmbed = false, wiki, title = '', fullWikite
 /**
  * Turns wikitext formatting into markdown.
  * @param {String} [text] - The text to modify.
- * @param {import('./wiki.js')} wiki - The wiki.
+ * @param {import('./wiki.js').default} wiki - The wiki.
  * @param {String} [title] - The page title.
  * @param {Boolean} [fullWikitext] - If the text can contain full wikitext.
  * @returns {String}
@@ -458,7 +461,7 @@ function sendMessage(interaction, message, letDelete = true) {
 	}, log_error );
 };
 
-module.exports = {
+export {
 	got,
 	oauthVerify,
 	parse_infobox,

+ 98 - 0
util/globals.js

@@ -0,0 +1,98 @@
+/**
+ * If debug logging is enabled.
+ * @type {Boolean}
+ * @global
+ */
+globalThis.isDebug = ( process.argv[2] === 'debug' );
+
+/**
+ * Prefix of guilds with patreon features enabled.
+ * @type {Map<String, String>}
+ * @global
+ */
+globalThis.patreonGuildsPrefix = new Map();
+
+/**
+ * Language code of guilds with voice channel role enabled.
+ * @type {Map<String, String>}
+ * @global
+ */
+globalThis.voiceGuildsLang = new Map();
+
+/**
+ * Guilds with pause activated.
+ * @type {Set<String>}
+ * @global
+ */
+globalThis.pausedGuilds = new Set();
+
+/**
+ * Logs an error.
+ * @param {Error} error - The error.
+ * @param {Boolean} isBig - If the error should get a big log.
+ * @param {String} type - Type of the error.
+ * @global
+ */
+globalThis.log_error = function(error, isBig = false, type = '') {
+	var time = new Date(Date.now()).toLocaleTimeString('de-DE', { timeZone: 'Europe/Berlin' });
+	if ( isDebug ) {
+		console.error( '--- ' + type + 'ERROR START ' + time + ' ---\n', error, '\n--- ' + type + 'ERROR END ' + time + ' ---' );
+	} else {
+		if ( isBig ) console.log( '--- ' + type + 'ERROR: ' + time + ' ---\n-', error );
+		else console.log( '- ' + error.name + ': ' + error.message );
+	}
+}
+
+const common_warnings = {
+	main: [
+		'Unrecognized parameters: piprop, explaintext, exsectionformat, exlimit.',
+		'Unrecognized parameters: explaintext, exsectionformat, exlimit.',
+		'Unrecognized parameter: piprop.'
+	],
+	query: [
+		'Unrecognized values for parameter "prop": pageimages, extracts.',
+		'Unrecognized values for parameter "prop": pageimages, extracts',
+		'Unrecognized value for parameter "prop": extracts.',
+		'Unrecognized value for parameter "prop": extracts',
+		'Unrecognized value for parameter "prop": pageimages.',
+		'Unrecognized value for parameter "prop": pageimages'
+	]
+}
+
+/**
+ * Logs a warning.
+ * @param {Object} warning - The warning.
+ * @param {Boolean} api - If the warning is from the MediaWiki API.
+ * @global
+ */
+globalThis.log_warning = function(warning, api = true) {
+	if ( isDebug ) {
+		console.warn( '--- Warning start ---\n' + inspect( warning ) + '\n--- Warning end ---' );
+	}
+	else if ( api ) {
+		if ( common_warnings.main.includes( warning?.main?.['*'] ) ) delete warning.main;
+		if ( common_warnings.query.includes( warning?.query?.['*'] ) ) delete warning.query;
+		var warningKeys = Object.keys(warning);
+		if ( warningKeys.length ) console.warn( '- Warning: ' + warningKeys.join(', ') );
+	}
+	else console.warn( '--- Warning ---\n' + inspect( warning ) );
+}
+
+if ( !globalThis.verifyOauthUser ) {
+	/**
+	 * Oauth wiki user verification.
+	 * @param {String} state - Unique state for the authorization.
+	 * @param {String} access_token - Access token.
+	 * @param {Object} [settings] - Settings to skip oauth.
+	 * @param {import('discord.js').TextChannel} settings.channel - The channel.
+	 * @param {String} settings.user - The user id.
+	 * @param {String} settings.wiki - The OAuth2 wiki.
+	 * @param {import('discord.js').CommandInteraction|import('discord.js').ButtonInteraction} [settings.interaction] - The interaction.
+	 * @param {Function} [settings.fail] - The function to call when the verifiction errors.
+	 * @param {import('discord.js').Message} [settings.sourceMessage] - The source message with the command.
+	 * @global
+	 */
+	globalThis.verifyOauthUser = function(state, access_token, settings) {
+		return settings?.fail?.();
+	};
+}

+ 5 - 3
util/i18n.js

@@ -1,5 +1,7 @@
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {defaultSettings} = require('./default.json');
-var i18n = require('../i18n/allLangs.json');
+const i18n = require('../i18n/allLangs.json');
 Object.keys(i18n.allLangs.names).forEach( lang => i18n[lang] = require('../i18n/' + lang + '.json') );
 
 const defaultAliases = ( i18n?.[defaultSettings.lang]?.aliases || {} );
@@ -8,7 +10,7 @@ const defaultAliases = ( i18n?.[defaultSettings.lang]?.aliases || {} );
  * A language.
  * @class
  */
-class Lang {
+export default class Lang {
 	/**
 	 * Creates a new language.
 	 * @param {String} [lang] - The language code.
@@ -249,4 +251,4 @@ function getArg(args, index) {
 	return ( args.length > index ? args[index] : args[args.length - 1] );
 }
 
-module.exports = Lang;
+export const allLangs = Lang.allLangs;

+ 21 - 15
util/logging.js

@@ -1,23 +1,29 @@
-if ( !process.env.usagelog ) {
-	module.exports = function() {};
-	return;
-}
-
-const fs = require('fs');
-const usageLog = fs.createWriteStream(process.env.usagelog, {flags:'a'});
-
-usageLog.on( 'error', (error) => {
-	console.log( '- ' + process.env.SHARDS + ': Error while logging the usage: ' + error );
-} );
+import {createWriteStream} from 'fs';
 
 /**
  * Log wikis by usage.
- * @param {import('./wiki.js')} wiki - The wiki.
+ * @param {import('./wiki.js').default} wiki - The wiki.
  * @param {String} [guild] - The guild.
  * @param {String[]} [notes] - The notes about the usage.
  */
-function logging(wiki, guild, ...notes) {
-	usageLog.write( [new Date().toISOString(), wiki, ( guild || 'DM' ), ...notes].join('\t') + '\n', 'utf8' );
+var logging = function(wiki, guild, ...notes) {};
+
+if ( process.env.usagelog ) {
+	const usageLog = createWriteStream(process.env.usagelog, {flags:'a'});
+	
+	usageLog.on( 'error', (error) => {
+		console.log( '- ' + process.env.SHARDS + ': Error while logging the usage: ' + error );
+	} );
+	
+	/**
+	 * Log wikis by usage.
+	 * @param {import('./wiki.js').default} wiki - The wiki.
+	 * @param {String} [guild] - The guild.
+	 * @param {String[]} [notes] - The notes about the usage.
+	 */
+	logging = function(wiki, guild, ...notes) {
+		usageLog.write( [new Date().toISOString(), wiki, ( guild || 'DM' ), ...notes].join('\t') + '\n', 'utf8' );
+	};
 }
 
-module.exports = logging;
+export default logging;

+ 25 - 22
util/newMessage.js

@@ -1,38 +1,43 @@
-const {domainToASCII} = require('url');
-const {Util} = require('discord.js');
-const logging = require('./logging.js');
-const {got, partialURIdecode} = require('./functions.js');
+import {readdir} from 'fs';
+import {domainToASCII} from 'url';
+import {Util} from 'discord.js';
+import Wiki from './wiki.js';
+import logging from './logging.js';
+import {got, partialURIdecode} from './functions.js';
+import check_wiki_general from '../cmds/wiki/general.js';
+import check_wiki_test from '../cmds/test.js';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {limit: {command: commandLimit}, defaultSettings, wikiProjects} = require('./default.json');
-const Wiki = require('./wiki.js');
 const check_wiki = {
-	general: require('../cmds/wiki/general.js'),
-	test: require('../cmds/test.js').run
+	general: check_wiki_general,
+	test: check_wiki_test.run
 };
 
-const fs = require('fs');
 var cmdmap = {};
 var pausecmdmap = {};
 var ownercmdmap = {};
-fs.readdir( './cmds', (error, files) => {
+readdir( './cmds', (error, files) => {
 	if ( error ) return error;
 	files.filter( file => file.endsWith('.js') ).forEach( file => {
-		var command = require('../cmds/' + file);
-		if ( command.everyone ) cmdmap[command.name] = command.run;
-		if ( command.pause ) pausecmdmap[command.name] = command.run;
-		if ( command.owner ) ownercmdmap[command.name] = command.run;
+		import('../cmds/' + file).then( ({default: command}) => {
+			if ( command.everyone ) cmdmap[command.name] = command.run;
+			if ( command.pause ) pausecmdmap[command.name] = command.run;
+			if ( command.owner ) ownercmdmap[command.name] = command.run;
+		} );
 	} );
 } );
 
 /**
  * Processes new messages.
  * @param {import('discord.js').Message} msg - The Discord message.
- * @param {import('./i18n.js')} lang - The user language.
+ * @param {import('./i18n.js').default} lang - The user language.
  * @param {Wiki} [wiki] - The default wiki.
  * @param {String} [prefix] - The prefix for the message.
  * @param {Boolean} [noInline] - Parse inline commands?
  * @param {String} [content] - Overwrite for the message content.
  */
-function newMessage(msg, lang, wiki = defaultSettings.wiki, prefix = process.env.prefix, noInline = null, content = '') {
+export default function newMessage(msg, lang, wiki = defaultSettings.wiki, prefix = process.env.prefix, noInline = null, content = '') {
 	wiki = new Wiki(wiki);
 	msg.noInline = noInline;
 	var cont = ( content || msg.content );
@@ -51,7 +56,7 @@ function newMessage(msg, lang, wiki = defaultSettings.wiki, prefix = process.env
 		}
 	}
 	var count = 0;
-	var maxcount = commandLimit[( patreons[msg.guildId] ? 'patreon' : 'default' )];
+	var maxcount = commandLimit[( patreonGuildsPrefix.has(msg.guildId) ? 'patreon' : 'default' )];
 	var breakLines = false;
 	cleanCont.replace( /\u200b/g, '' ).replace( /<a?(:\w+:)\d+>/g, '$1' ).replace( /(?<!\\)```.+?```/gs, '<codeblock>' ).split('\n').forEach( line => {
 		if ( line.startsWith( '>>> ' ) ) breakLines = true;
@@ -76,9 +81,9 @@ function newMessage(msg, lang, wiki = defaultSettings.wiki, prefix = process.env
 		var args = line.split(' ').slice(1);
 		var aliasInvoke = ( lang.aliases[invoke] || invoke );
 		var ownercmd = ( msg.isOwner() && ownercmdmap.hasOwnProperty(aliasInvoke) );
-		var pausecmd = ( msg.isAdmin() && pause[msg.guildId] && pausecmdmap.hasOwnProperty(aliasInvoke) );
+		var pausecmd = ( msg.isAdmin() && pausedGuilds.has(msg.guildId) && pausecmdmap.hasOwnProperty(aliasInvoke) );
 		if ( msg.onlyVerifyCommand && !( aliasInvoke === 'verify' || pausecmd || ownercmd ) ) return;
-		if ( channel.isGuild() && pause[msg.guildId] && !( pausecmd || ownercmd ) ) {
+		if ( channel.isGuild() && pausedGuilds.has(msg.guildId) && !( pausecmd || ownercmd ) ) {
 			return console.log( msg.guildId + ': Paused' );
 		}
 		console.log( ( channel.isGuild() ? msg.guildId : '@' + author.id ) + ': ' + prefix + line );
@@ -111,7 +116,7 @@ function newMessage(msg, lang, wiki = defaultSettings.wiki, prefix = process.env
 	} );
 	if ( msg.onlyVerifyCommand ) return;
 	
-	if ( ( !channel.isGuild() || !pause[msg.guildId] ) && !noInline && ( cont.includes( '[[' ) || cont.includes( '{{' ) ) ) {
+	if ( ( !channel.isGuild() || !pausedGuilds.has(msg.guildId) ) && !noInline && ( cont.includes( '[[' ) || cont.includes( '{{' ) ) ) {
 		var links = [];
 		var embeds = [];
 		var linkcount = 0;
@@ -267,6 +272,4 @@ function newMessage(msg, lang, wiki = defaultSettings.wiki, prefix = process.env
 			}
 		} );
 	}
-}
-
-module.exports = newMessage;
+}

+ 20 - 15
util/wiki.js

@@ -1,4 +1,6 @@
-const util = require('util');
+import {inspect} from 'util';
+import {createRequire} from 'module';
+const require = createRequire(import.meta.url);
 const {defaultSettings, wikiProjects} = require('./default.json');
 
 const wikimediaSites = [
@@ -24,7 +26,7 @@ const urlSpaceReplacement = {
  * A wiki.
  * @class Wiki
  */
-class Wiki extends URL {
+export default class Wiki extends URL {
 	/**
 	 * Creates a new wiki.
 	 * @param {String|URL|Wiki} [wiki] - The wiki script path.
@@ -229,28 +231,28 @@ class Wiki extends URL {
 	 * @static
 	 */
 	static fromInput(input = '') {
-		if ( input instanceof URL ) return new this(input);
+		if ( input instanceof URL ) return new Wiki(input);
 		input = input.replace( /^(?:https?:)?\/\//, 'https://' );
 		var regex = input.match( /^(?:https:\/\/)?([a-z\d-]{1,50}\.(?:gamepedia\.com|(?:fandom\.com|wikia\.org)(?:(?!\/(?:wiki|api)\/)\/[a-z-]{2,12})?))(?:\/|$)/ );
-		if ( regex ) return new this('https://' + regex[1] + '/');
+		if ( regex ) return new Wiki('https://' + regex[1] + '/');
 		if ( input.startsWith( 'https://' ) ) {
 			let project = wikiProjects.find( project => input.split('/')[2].endsWith( project.name ) );
 			if ( project ) {
 				regex = input.match( new RegExp( project.regex + `(?:${project.articlePath}|${project.scriptPath}|/?$)` ) );
-				if ( regex ) return new this('https://' + regex[1] + project.scriptPath);
+				if ( regex ) return new Wiki('https://' + regex[1] + project.scriptPath);
 			}
 			let wiki = input.replace( /\/(?:index|api|load|rest)\.php(?:|[\?\/#].*)$/, '/' );
 			if ( !wiki.endsWith( '/' ) ) wiki += '/';
-			return new this(wiki);
+			return new Wiki(wiki);
 		}
 		let project = wikiProjects.find( project => input.split('/')[0].endsWith( project.name ) );
 		if ( project ) {
 			regex = input.match( new RegExp( project.regex + `(?:${project.articlePath}|${project.scriptPath}|/?$)` ) );
-			if ( regex ) return new this('https://' + regex[1] + project.scriptPath);
+			if ( regex ) return new Wiki('https://' + regex[1] + project.scriptPath);
 		}
 		if ( /^(?:[a-z-]{2,12}\.)?[a-z\d-]{1,50}$/.test(input) ) {
-			if ( !input.includes( '.' ) ) return new this('https://' + input + '.fandom.com/');
-			else return new this('https://' + input.split('.')[1] + '.fandom.com/' + input.split('.')[0] + '/');
+			if ( !input.includes( '.' ) ) return new Wiki('https://' + input + '.fandom.com/');
+			else return new Wiki('https://' + input.split('.')[1] + '.fandom.com/' + input.split('.')[0] + '/');
 		}
 		return null;
 	}
@@ -258,7 +260,7 @@ class Wiki extends URL {
 	/** @type {String[]} - Sites that support verification using OAuth2. */
 	static oauthSites = [];
 
-	[util.inspect.custom](depth, opts) {
+	[inspect.custom](depth, opts) {
 		if ( typeof depth === 'number' && depth < 0 ) return this;
 		const wiki = {
 			href: this.href,
@@ -278,7 +280,7 @@ class Wiki extends URL {
 			spaceReplacement: this.spaceReplacement,
 			mainpage: this.mainpage
 		}
-		return 'Wiki ' + util.inspect(wiki, opts);
+		return 'Wiki ' + inspect(wiki, opts);
 	}
 }
 
@@ -302,15 +304,18 @@ class articleURL extends URL {
 		this.spaceReplacement = ( wiki?.spaceReplacement || '_' );
 	}
 
-	[util.inspect.custom](depth, opts) {
+	[inspect.custom](depth, opts) {
 		if ( typeof depth === 'number' && depth < 0 ) return this;
 		if ( typeof depth === 'number' && depth < 2 ) {
 			var link = this.href;
 			var mainpage = link.replace( '$1', Wiki.toTitle(( this.mainpage || 'Main Page' ), this.spaceReplacement) );
-			return 'articleURL { ' + util.inspect(link, opts) + ' => ' + util.inspect(mainpage, opts) + ' }';
+			return 'articleURL { ' + inspect(link, opts) + ' => ' + inspect(mainpage, opts) + ' }';
 		}
-		return super[util.inspect.custom](depth, opts);
+		return super[inspect.custom](depth, opts);
 	}
 }
 
-module.exports = Wiki;
+export const toTitle = Wiki.toTitle;
+export const toSection = Wiki.toSection;
+export const fromInput = Wiki.fromInput;
+export const oauthSites = Wiki.oauthSites;