Procházet zdrojové kódy

Merge remote-tracking branch 'origin/master'

Owen Diffey před 4 roky
rodič
revize
d1e22eddce
68 změnil soubory, kde provedl 1255 přidání a 654 odebrání
  1. 2 1
      bot.js
  2. 31 18
      cmds/verification.js
  3. 1 1
      cmds/verify.js
  4. 1 1
      cmds/wiki/general.js
  5. 1 1
      cmds/wiki/search.js
  6. 0 5
      dashboard/i18n/bn.json
  7. 22 13
      dashboard/i18n/de.json
  8. 14 5
      dashboard/i18n/en.json
  9. 0 5
      dashboard/i18n/es.json
  10. 0 5
      dashboard/i18n/fr.json
  11. 6 7
      dashboard/i18n/hi.json
  12. 35 26
      dashboard/i18n/ja.json
  13. 0 5
      dashboard/i18n/ko.json
  14. 0 5
      dashboard/i18n/pl.json
  15. 4 5
      dashboard/i18n/pt-br.json
  16. 16 5
      dashboard/i18n/ru.json
  17. 0 5
      dashboard/i18n/tr.json
  18. 14 5
      dashboard/i18n/zh-hans.json
  19. 14 5
      dashboard/i18n/zh-hant.json
  20. 7 2
      dashboard/index.js
  21. 95 17
      dashboard/rcscript.js
  22. 3 3
      dashboard/slash.js
  23. 46 4
      dashboard/src/index.css
  24. 99 14
      dashboard/src/index.js
  25. 6 1
      dashboard/src/lang.js
  26. 4 4
      dashboard/util.js
  27. 123 70
      dashboard/verification.js
  28. 155 65
      functions/verify.js
  29. 0 4
      i18n/bn.json
  30. 65 57
      i18n/de.json
  31. 12 4
      i18n/en.json
  32. 0 4
      i18n/es.json
  33. 0 4
      i18n/fr.json
  34. 14 4
      i18n/hi.json
  35. 133 9
      i18n/ja.json
  36. 0 4
      i18n/ko.json
  37. 0 1
      i18n/nl.json
  38. 20 5
      i18n/pl.json
  39. 9 4
      i18n/pt-br.json
  40. 14 4
      i18n/ru.json
  41. 15 4
      i18n/sv.json
  42. 0 4
      i18n/tr.json
  43. binární
      i18n/widgets/bn.png
  44. binární
      i18n/widgets/de.png
  45. binární
      i18n/widgets/en.png
  46. binární
      i18n/widgets/es.png
  47. binární
      i18n/widgets/fr.png
  48. binární
      i18n/widgets/hi.png
  49. binární
      i18n/widgets/it.png
  50. binární
      i18n/widgets/ja.png
  51. binární
      i18n/widgets/ko.png
  52. binární
      i18n/widgets/nl.png
  53. binární
      i18n/widgets/pl.png
  54. binární
      i18n/widgets/pt-br.png
  55. binární
      i18n/widgets/ru.png
  56. binární
      i18n/widgets/sv.png
  57. binární
      i18n/widgets/th.png
  58. binární
      i18n/widgets/tr.png
  59. binární
      i18n/widgets/uk.png
  60. binární
      i18n/widgets/vi.png
  61. binární
      i18n/widgets/zh-hans.png
  62. binární
      i18n/widgets/zh-hant.png
  63. 12 4
      i18n/zh-hans.json
  64. 12 4
      i18n/zh-hant.json
  65. 35 33
      main.js
  66. 201 186
      package-lock.json
  67. 5 5
      package.json
  68. 9 7
      util/edit_diff.js

+ 2 - 1
bot.js

@@ -231,11 +231,12 @@ fs.readdir( './interactions', (error, files) => {
 client.ws.on( 'INTERACTION_CREATE', interaction => {
 client.ws.on( 'INTERACTION_CREATE', interaction => {
 	if ( interaction.version !== 1 ) return;
 	if ( interaction.version !== 1 ) return;
 	interaction.client = client;
 	interaction.client = client;
+	var channel = client.channels.cache.get(interaction.channel_id);
 	if ( interaction.guild_id ) {
 	if ( interaction.guild_id ) {
 		interaction.user = interaction.member.user;
 		interaction.user = interaction.member.user;
 		interaction.member.permissions = new Discord.Permissions(+interaction.member.permissions);
 		interaction.member.permissions = new Discord.Permissions(+interaction.member.permissions);
+		channel?.guild?.members.add(interaction.member);
 	}
 	}
-	var channel = client.channels.cache.get(interaction.channel_id);
 	if ( interaction.type === 2 ) return slash_command(interaction, channel);
 	if ( interaction.type === 2 ) return slash_command(interaction, channel);
 	if ( interaction.type === 3 ) return message_button(interaction, channel);
 	if ( interaction.type === 3 ) return message_button(interaction, channel);
 } );
 } );

+ 31 - 18
cmds/verification.js

@@ -53,15 +53,19 @@ function cmd_verification(lang, msg, args, line, wiki) {
 			if ( !roles.length ) return msg.replyMsg( lang.get('verification.no_role') + '\n`' + prefix + 'verification add ' + lang.get('verification.new_role') + '`', {components}, true );
 			if ( !roles.length ) return msg.replyMsg( lang.get('verification.no_role') + '\n`' + prefix + 'verification add ' + lang.get('verification.new_role') + '`', {components}, true );
 			if ( roles.length > 10 ) return msg.replyMsg( lang.get('verification.role_max'), {components}, true );
 			if ( roles.length > 10 ) return msg.replyMsg( lang.get('verification.role_max'), {components}, true );
 			roles = roles.map( role => {
 			roles = roles.map( role => {
-				var new_role = '';
-				if ( /^\d+$/.test(role) ) new_role = msg.guild.roles.cache.get(role);
-				if ( !new_role ) new_role = msg.guild.roles.cache.find( gc => gc.name === role.replace( /^@/, '' ) );
-				if ( !new_role ) new_role = msg.guild.roles.cache.find( gc => gc.name.toLowerCase() === role.toLowerCase().replace( /^@/, '' ) );
+				var new_role = ['', null];
+				if ( role.startsWith( '-' ) ) {
+					role = role.replace( '-', '' );
+					new_role[0] = '-';
+				}
+				if ( /^\d+$/.test(role) ) new_role[1] = msg.guild.roles.cache.get(role);
+				if ( !new_role[1] ) new_role[1] = msg.guild.roles.cache.find( gc => gc.name === role.replace( /^@/, '' ) );
+				if ( !new_role[1] ) new_role[1] = msg.guild.roles.cache.find( gc => gc.name.toLowerCase() === role.toLowerCase().replace( /^@/, '' ) );
 				return new_role;
 				return new_role;
 			} );
 			} );
-			if ( roles.some( role => !role ) ) return msg.replyMsg( lang.get('verification.role_missing'), {components}, true );
-			if ( roles.some( role => role.managed ) ) return msg.replyMsg( lang.get('verification.role_managed'), {components}, true );
-			roles = roles.map( role => role.id ).join('|');
+			if ( roles.some( role => !role[1] ) ) return msg.replyMsg( lang.get('verification.role_missing'), {components}, true );
+			if ( roles.some( role => role[1].managed || role[1].id === msg.guild.id ) ) return msg.replyMsg( lang.get('verification.role_managed'), {components}, true );
+			roles = roles.map( role => role[0] + role[1].id ).join('|');
 			var new_configid = 1;
 			var new_configid = 1;
 			for ( let i of rows.map( row => row.configid ) ) {
 			for ( let i of rows.map( row => row.configid ) ) {
 				if ( new_configid === i ) new_configid++;
 				if ( new_configid === i ) new_configid++;
@@ -174,15 +178,19 @@ function cmd_verification(lang, msg, args, line, wiki) {
 				var roles = args[2].replace( /\s*>?\s*[,|]\s*<?\s*/g, '|' ).split('|').filter( role => role.length );
 				var roles = args[2].replace( /\s*>?\s*[,|]\s*<?\s*/g, '|' ).split('|').filter( role => role.length );
 				if ( roles.length > 10 ) return msg.replyMsg( lang.get('verification.role_max'), {components}, true );
 				if ( roles.length > 10 ) return msg.replyMsg( lang.get('verification.role_max'), {components}, true );
 				roles = roles.map( role => {
 				roles = roles.map( role => {
-					var new_role = null;
-					if ( /^\d+$/.test(role) ) new_role = msg.guild.roles.cache.get(role);
-					if ( !new_role ) new_role = msg.guild.roles.cache.find( gc => gc.name === role.replace( /^@/, '' ) );
-					if ( !new_role ) new_role = msg.guild.roles.cache.find( gc => gc.name.toLowerCase() === role.toLowerCase().replace( /^@/, '' ) );
+					var new_role = ['', null];
+					if ( role.startsWith( '-' ) ) {
+						role = role.replace( '-', '' );
+						new_role[0] = '-';
+					}
+					if ( /^\d+$/.test(role) ) new_role[1] = msg.guild.roles.cache.get(role);
+					if ( !new_role[1] ) new_role[1] = msg.guild.roles.cache.find( gc => gc.name === role.replace( /^@/, '' ) );
+					if ( !new_role[1] ) new_role[1] = msg.guild.roles.cache.find( gc => gc.name.toLowerCase() === role.toLowerCase().replace( /^@/, '' ) );
 					return new_role;
 					return new_role;
 				} );
 				} );
-				if ( roles.some( role => !role ) ) return msg.replyMsg( lang.get('verification.role_missing'), {components}, true );
-				if ( roles.some( role => role.managed || role.id === msg.guild.id ) ) return msg.replyMsg( lang.get('verification.role_managed'), {components}, true );
-				roles = roles.map( role => role.id ).join('|');
+				if ( roles.some( role => !role[1] ) ) return msg.replyMsg( lang.get('verification.role_missing'), {components}, true );
+				if ( roles.some( role => role[1].managed || role[1].id === msg.guild.id ) ) return msg.replyMsg( lang.get('verification.role_managed'), {components}, true );
+				roles = roles.map( role => role[0] + role[1].id ).join('|');
 				if ( roles.length ) return db.query( 'UPDATE verification SET role = $1 WHERE guild = $2 AND configid = $3', [roles, msg.guild.id, row.configid] ).then( () => {
 				if ( roles.length ) return db.query( 'UPDATE verification SET role = $1 WHERE guild = $2 AND configid = $3', [roles, msg.guild.id, row.configid] ).then( () => {
 					console.log( '- Verification successfully updated.' );
 					console.log( '- Verification successfully updated.' );
 					row.role = roles;
 					row.role = roles;
@@ -268,17 +276,22 @@ function cmd_verification(lang, msg, args, line, wiki) {
 		function formatVerification(showCommands, hideNotice, {
 		function formatVerification(showCommands, hideNotice, {
 			configid,
 			configid,
 			channel = '|' + msg.channel.id + '|',
 			channel = '|' + msg.channel.id + '|',
-			role,
+			role = '',
 			editcount = 0,
 			editcount = 0,
 			postcount = 0,
 			postcount = 0,
 			usergroup = 'user',
 			usergroup = 'user',
 			accountage = 0,
 			accountage = 0,
 			rename = 0
 			rename = 0
 		} = row) {
 		} = row) {
+			var roles = [
+				role.split('|').filter( roleid => !roleid.startsWith( '-' ) ),
+				role.split('|').filter( roleid => roleid.startsWith( '-' ) ).map( roleid => roleid.replace( '-', '' ) )
+			];
 			var verification_text = '\n\n`' + prefix + 'verification ' + configid + '`';
 			var verification_text = '\n\n`' + prefix + 'verification ' + configid + '`';
 			verification_text += '\n' + lang.get('verification.channel') + ' <#' + channel.split('|').filter( channel => channel.length ).join('>, <#') + '>';
 			verification_text += '\n' + lang.get('verification.channel') + ' <#' + channel.split('|').filter( channel => channel.length ).join('>, <#') + '>';
 			if ( showCommands ) verification_text += '\n`' + prefix + 'verification ' + row.configid + ' channel ' + lang.get('verification.new_channel') + '`\n';
 			if ( showCommands ) verification_text += '\n`' + prefix + 'verification ' + row.configid + ' channel ' + lang.get('verification.new_channel') + '`\n';
-			verification_text += '\n' + lang.get('verification.role') + ' <@&' + role.split('|').join('>, <@&') + '>';
+			if ( roles[0].length ) verification_text += '\n' + lang.get('verification.role_add') + ' <@&' + roles[0].join('>, <@&') + '>';
+			if ( roles[1].length ) verification_text += '\n' + lang.get('verification.role_remove') + ' <@&' + roles[1].join('>, <@&') + '>';
 			if ( showCommands ) verification_text += '\n`' + prefix + 'verification ' + row.configid + ' role ' + lang.get('verification.new_role') + '`\n';
 			if ( showCommands ) verification_text += '\n`' + prefix + 'verification ' + row.configid + ' role ' + lang.get('verification.new_role') + '`\n';
 			if ( postcount === null ) verification_text += '\n' + lang.get('verification.posteditcount') + ' `' + editcount + '`';
 			if ( postcount === null ) verification_text += '\n' + lang.get('verification.posteditcount') + ' `' + editcount + '`';
 			else verification_text += '\n' + lang.get('verification.editcount') + ' `' + editcount + '`';
 			else verification_text += '\n' + lang.get('verification.editcount') + ' `' + editcount + '`';
@@ -297,11 +310,11 @@ function cmd_verification(lang, msg, args, line, wiki) {
 			if ( !hideNotice && rename && !msg.guild.me.permissions.has('MANAGE_NICKNAMES') ) {
 			if ( !hideNotice && rename && !msg.guild.me.permissions.has('MANAGE_NICKNAMES') ) {
 				verification_text += '\n\n' + lang.get('verification.rename_no_permission', msg.guild.me.toString());
 				verification_text += '\n\n' + lang.get('verification.rename_no_permission', msg.guild.me.toString());
 			}
 			}
-			if ( !hideNotice && role.split('|').some( role => {
+			if ( !hideNotice && role.replace( /-/g, '' ).split('|').some( role => {
 				return ( !msg.guild.roles.cache.has(role) || msg.guild.me.roles.highest.comparePositionTo(role) <= 0 );
 				return ( !msg.guild.roles.cache.has(role) || msg.guild.me.roles.highest.comparePositionTo(role) <= 0 );
 			} ) ) {
 			} ) ) {
 				verification_text += '\n';
 				verification_text += '\n';
-				role.split('|').forEach( role => {
+				role.replace( /-/g, '' ).split('|').forEach( role => {
 					if ( !msg.guild.roles.cache.has(role) ) {
 					if ( !msg.guild.roles.cache.has(role) ) {
 						verification_text += '\n' + lang.get('verification.role_deleted', '<@&' + role + '>');
 						verification_text += '\n' + lang.get('verification.role_deleted', '<@&' + role + '>');
 					}
 					}

+ 1 - 1
cmds/verify.js

@@ -155,7 +155,7 @@ function cmd_verify(lang, msg, args, line, wiki) {
 						dmEmbed.fields.forEach( field => {
 						dmEmbed.fields.forEach( field => {
 							field.value = field.value.replace( /<@&(\d+)>/g, (mention, id) => {
 							field.value = field.value.replace( /<@&(\d+)>/g, (mention, id) => {
 								if ( !msg.guild.roles.cache.has(id) ) return mention;
 								if ( !msg.guild.roles.cache.has(id) ) return mention;
-								return '@' + msg.guild.roles.cache.get(id)?.name;
+								return escapeFormatting('@' + msg.guild.roles.cache.get(id)?.name);
 							} );
 							} );
 						} );
 						} );
 						msg.member.send( msg.channel.toString() + '; ' + result.content, {embed: dmEmbed, components: []} ).then( message => {
 						msg.member.send( msg.channel.toString() + '; ' + result.content, {embed: dmEmbed, components: []} ).then( message => {

+ 1 - 1
cmds/wiki/general.js

@@ -196,7 +196,7 @@ function gamepedia_check_wiki(lang, msg, title, wiki, cmd, reaction, spoiler = '
 				if ( reaction ) reaction.removeEmoji();
 				if ( reaction ) reaction.removeEmoji();
 				return;
 				return;
 			}
 			}
-			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 => {
+			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&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( srresponse => {
 				logging(wiki, msg.guild?.id, 'general', 'search');
 				logging(wiki, msg.guild?.id, 'general', 'search');
 				var srbody = srresponse.body;
 				var srbody = srresponse.body;
 				if ( srbody && srbody.warnings ) log_warn(srbody.warnings);
 				if ( srbody && srbody.warnings ) log_warn(srbody.warnings);

+ 1 - 1
cmds/wiki/search.js

@@ -25,7 +25,7 @@ function gamepedia_search(lang, msg, searchterm, wiki, query, reaction, spoiler)
 	var querypage = ( Object.values(( query.pages || {} ))?.[0] || {title:'',ns:0,invalid:''} );
 	var querypage = ( Object.values(( query.pages || {} ))?.[0] || {title:'',ns:0,invalid:''} );
 	var description = [];
 	var description = [];
 	var limit = searchLimit[( patreons[msg.guild?.id] ? 'patreon' : 'default' )];
 	var limit = searchLimit[( patreons[msg.guild?.id] ? '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 => {
+	got.get( wiki + 'api.php?action=query&titles=Special:Search&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( response => {
 		var body = response.body;
 		var body = response.body;
 		if ( body && body.warnings ) log_warn(body.warnings);
 		if ( body && body.warnings ) log_warn(body.warnings);
 		if ( response.statusCode !== 200 || !body || !body.query || !body.query.search || body.batchcomplete === undefined ) {
 		if ( response.statusCode !== 200 || !body || !body.query || !body.query.search || body.batchcomplete === undefined ) {

+ 0 - 5
dashboard/i18n/bn.json

@@ -76,11 +76,6 @@
             "text": "হয় আপনি বা উইকি-বট এই ফাংশনের জন্য $1 এর অনুপস্থিতি হারিয়েছেন।",
             "text": "হয় আপনি বা উইকি-বট এই ফাংশনের জন্য $1 এর অনুপস্থিতি হারিয়েছেন।",
             "title": "অনুমতি অনুপস্থিত!"
             "title": "অনুমতি অনুপস্থিত!"
         },
         },
-        "movefail": {
-            "note": "ওয়েবহুক চ্যানেল পরিবর্তন করা যায়নি!",
-            "text": "সেটিংসটি কেবলমাত্র আংশিকভাবে আপডেট হয়েছে।",
-            "title": "সেটিংস আংশিকভাবে সংরক্ষণ করা হয়েছে!"
-        },
         "mwversion": {
         "mwversion": {
             "text": "কমপক্ষে MediaWiki 1.30 প্রয়োজন, $1 $2--এ পাওয়া যায়।",
             "text": "কমপক্ষে MediaWiki 1.30 প্রয়োজন, $1 $2--এ পাওয়া যায়।",
             "title": "পুরানো মিডিয়াউইকি সংস্করণ!"
             "title": "পুরানো মিডিয়াউইকি সংস্করণ!"

+ 22 - 13
dashboard/i18n/de.json

@@ -30,6 +30,10 @@
         "welcome": "<h2>Willkommen auf dem Wiki-Bot Dashboard.</h2>\n<p>Wiki-Bot ist ein Discord-Bot der dazu dient Discord-Server und MediaWiki-Wikis zu verbinden. Er hilft durch das Verlinken von Wiki-Artikeln, Verifizieren von Wiki-Benutzern, informiert über die neusten Änderungen auf dem Wiki und mehr. <a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[Mehr Information]</a></p>\n<p>Hier kannst du die Bot-Einstellungen für Server ändern auf denen du die Server verwalten Berechtigung besitzt. Dafür musst du dich mit deinem Discord-Account anmelden, was du über diesen Knopf tuen kannst:</p>"
         "welcome": "<h2>Willkommen auf dem Wiki-Bot Dashboard.</h2>\n<p>Wiki-Bot ist ein Discord-Bot der dazu dient Discord-Server und MediaWiki-Wikis zu verbinden. Er hilft durch das Verlinken von Wiki-Artikeln, Verifizieren von Wiki-Benutzern, informiert über die neusten Änderungen auf dem Wiki und mehr. <a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[Mehr Information]</a></p>\n<p>Hier kannst du die Bot-Einstellungen für Server ändern auf denen du die Server verwalten Berechtigung besitzt. Dafür musst du dich mit deinem Discord-Account anmelden, was du über diesen Knopf tuen kannst:</p>"
     },
     },
     "indexjs": {
     "indexjs": {
+        "avatar": {
+            "content_type": "Der Inhalt des angegebenen Links hat den Typ $1, aber es sind nur die folgenden Inhaltstypen erlaubt:",
+            "invalid_url": "Die URL konnte nicht zu einer gültigen Bilddatei aufgelöst werden."
+        },
         "invalid": {
         "invalid": {
             "note_http": "Die angegebene Website besitzt kein gültiges TLS/SSL-Zertifikat! Aus Sicherheitsgründen werden nur Wikis die HTTPS nutzen unterstützt.",
             "note_http": "Die angegebene Website besitzt kein gültiges TLS/SSL-Zertifikat! Aus Sicherheitsgründen werden nur Wikis die HTTPS nutzen unterstützt.",
             "note_private": "Das angegebene Wiki ist privat!",
             "note_private": "Das angegebene Wiki ist privat!",
@@ -38,7 +42,7 @@
             "title": "Ungültiges Wiki!"
             "title": "Ungültiges Wiki!"
         },
         },
         "outdated": {
         "outdated": {
-            "text": "Der Letzte Änderungen-Webhook benötigt mindestens MediaWiki 1.30!",
+            "text": "Der Letzte Änderungen-WebHook benötigt mindestens MediaWiki 1.30!",
             "title": "Veraltete MediaWiki-Version!"
             "title": "Veraltete MediaWiki-Version!"
         },
         },
         "prefix": {
         "prefix": {
@@ -76,11 +80,6 @@
             "text": "Entweder dir oder Wiki-Bot fehlt die $1-Berechtigung für diese Funktion.",
             "text": "Entweder dir oder Wiki-Bot fehlt die $1-Berechtigung für diese Funktion.",
             "title": "Fehlende Berechtigung!"
             "title": "Fehlende Berechtigung!"
         },
         },
-        "movefail": {
-            "note": "Der Webhook-Kanal konnte nicht geändert werden!",
-            "text": "Die Einstellungen konnten nur teilweise aktualisiert werden.",
-            "title": "Einstellungen teilweise gespeichert!"
-        },
         "mwversion": {
         "mwversion": {
             "text": "Es wird mindestens MediaWiki 1.30 benötigt, auf $2 wurde $1 gefunden.",
             "text": "Es wird mindestens MediaWiki 1.30 benötigt, auf $2 wurde $1 gefunden.",
             "title": "Veraltete MediaWiki version!"
             "title": "Veraltete MediaWiki version!"
@@ -134,33 +133,41 @@
             "text": "Bitte melde dich an, bevor du Einstellungen ändern kannst.",
             "text": "Bitte melde dich an, bevor du Einstellungen ändern kannst.",
             "title": "Nicht angemeldet!"
             "title": "Nicht angemeldet!"
         },
         },
+        "webhookfail": {
+            "note": "Der Discord-WebHook konnte nicht geändert werden!",
+            "text": "Die Einstellungen wurden nur teilweise gespeichert.",
+            "title": "Einstellungen teilweise gespeichert!"
+        },
         "wikiblocked": {
         "wikiblocked": {
             "note": "Grund:",
             "note": "Grund:",
-            "text": "$1 wurde gesperrt und kann nicht als Letzte Änderungen-Webhook hinzugefügt werden.",
+            "text": "$1 wurde gesperrt und kann nicht als Letzte Änderungen-WebHook hinzugefügt werden.",
             "title": "Wiki ist gesperrt!"
             "title": "Wiki ist gesperrt!"
         }
         }
     },
     },
     "rcscript": {
     "rcscript": {
-        "desc": "Dies sind die Letzte Änderungen-Webhooks für $1:",
-        "explanation": "<h2>Letzte Änderungen-Webhook</h2>\n<p>Wiki-Bot ist in der Lage einen Letzte Änderungen-Webhook basierend auf <a href=\"https://gitlab.com/piotrex43/RcGcDw\" target=\"_blank\">RcGcDw</a> auszuführen. Die letzten Änderungen können als kompakte Textnachrichten mit Inline-Links oder als Einbettungen mit Bearbeitungsmarkierungen und Kategorieänderungen angezeigt werden.</p>\n<p>Voraussetzungen um einen Letzte Änderungen-Webhook hinzuzufügen:</p>\n<ul>\n<li>Das Wiki benötigt mindestens <a href=\"https://www.mediawiki.org/wiki/MediaWiki_1.30\" target=\"_blank\">MediaWiki 1.30</a>.</li>\n<li>Die Systemnachricht <code class=\"user-select\">MediaWiki:Custom-RcGcDw</code> muss mit der Discord-Server-ID <code class=\"user-select\" id=\"server-id\"></code> übereinstimmen.</li>\n</ul>",
+        "desc": "Dies sind die Letzte Änderungen-WebHooks für $1:",
+        "explanation": "<h2>Letzte Änderungen-WebHook</h2>\n<p>Wiki-Bot ist in der Lage einen Letzte Änderungen-WebHook basierend auf <a href=\"https://gitlab.com/piotrex43/RcGcDw\" target=\"_blank\">RcGcDw</a> auszuführen. Die letzten Änderungen können als kompakte Textnachrichten mit Inline-Links oder als Einbettungen mit Bearbeitungsmarkierungen und Kategorieänderungen angezeigt werden.</p>\n<p>Voraussetzungen um einen Letzte Änderungen-WebHook hinzuzufügen:</p>\n<ul>\n<li>Das Wiki benötigt mindestens <a href=\"https://www.mediawiki.org/wiki/MediaWiki_1.30\" target=\"_blank\">MediaWiki 1.30</a>.</li>\n<li>Die Systemnachricht <code class=\"user-select\">MediaWiki:Custom-RcGcDw</code> muss mit der Discord-Server-ID <code class=\"user-select\" id=\"server-id\"></code> übereinstimmen.</li>\n</ul>",
         "form": {
         "form": {
+            "avatar": "WebHook-Avatar:",
+            "avatar_preview": "Vorschau",
             "channel": "Kanal:",
             "channel": "Kanal:",
-            "confirm": "Möchtest du wirklich den Letzte Änderungen-Webhook löschen?",
+            "confirm": "Möchtest du wirklich den Letzte Änderungen-WebHook löschen?",
             "display": "Anzeigemodus:",
             "display": "Anzeigemodus:",
             "display_compact": "Kompakte Textnachrichten mit Inline-Links.",
             "display_compact": "Kompakte Textnachrichten mit Inline-Links.",
             "display_diff": "Einbettungen mit Bildvorschau und Bearbeitungsunterschieden.",
             "display_diff": "Einbettungen mit Bildvorschau und Bearbeitungsunterschieden.",
             "display_embed": "Einbettungen mit Bearbeitungsmarkierungen und Kategorieänderungen.",
             "display_embed": "Einbettungen mit Bearbeitungsmarkierungen und Kategorieänderungen.",
             "display_image": "Einbettungen mit Bildvorschau.",
             "display_image": "Einbettungen mit Bildvorschau.",
-            "entry": "$1. Letzte Änderungen-Webhook",
+            "entry": "$1. Letzte Änderungen-WebHook",
             "feeds": "Feeds-basierte Änderungen:",
             "feeds": "Feeds-basierte Änderungen:",
             "feeds_only": "Nur Feeds-basierte Änderungen:",
             "feeds_only": "Nur Feeds-basierte Änderungen:",
             "lang": "Sprache:",
             "lang": "Sprache:",
-            "new": "Neuer Letzte Änderungen-Webhook",
+            "name": "WebHook-Name:",
+            "new": "Neuer Letzte Änderungen-WebHook",
             "select_channel": "-- Wähle einen Kanal --",
             "select_channel": "-- Wähle einen Kanal --",
             "wiki": "Wiki:",
             "wiki": "Wiki:",
             "wiki_check": "Wiki testen"
             "wiki_check": "Wiki testen"
         },
         },
-        "new": "Neuer Webhook"
+        "new": "Neuer WebHook"
     },
     },
     "selector": {
     "selector": {
         "desc": "Dies ist eine Liste aller Server dessen Einstellungen du ändern kannst, da du die [Server verwalten]($1)-Berechtigung hast. Bitte wähle einen Server:",
         "desc": "Dies ist eine Liste aller Server dessen Einstellungen du ändern kannst, da du die [Server verwalten]($1)-Berechtigung hast. Bitte wähle einen Server:",
@@ -230,6 +237,8 @@
             "postcount_or": "Benötige entweder das Bearbeitungs- oder das Diskussionsbeitragslimit.",
             "postcount_or": "Benötige entweder das Bearbeitungs- oder das Diskussionsbeitragslimit.",
             "rename": "Benutzer umbenennen:",
             "rename": "Benutzer umbenennen:",
             "role": "Rolle:",
             "role": "Rolle:",
+            "role_add": "Vergeben",
+            "role_remove": "Entfernen",
             "select_channel": "-- Wähle einen Kanal --",
             "select_channel": "-- Wähle einen Kanal --",
             "select_role": "-- Wähle eine Rolle --",
             "select_role": "-- Wähle eine Rolle --",
             "success": "Hinweis bei Erfolg:",
             "success": "Hinweis bei Erfolg:",

+ 14 - 5
dashboard/i18n/en.json

@@ -30,6 +30,10 @@
         "welcome": "<h2>Welcome to the Wiki-Bot Dashboard.</h2>\n<p>Wiki-Bot is a Discord bot made to bring Discord servers and MediaWiki wikis together. It helps with linking wiki pages, verifying wiki users, informing about latest changes on the wiki and more. <a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[More information]</a></p>\n<p>Here you can change different bot settings for servers you have Manage Server permission on. To begin, you will have to authenticate your Discord account which you can do with this button:</p>"
         "welcome": "<h2>Welcome to the Wiki-Bot Dashboard.</h2>\n<p>Wiki-Bot is a Discord bot made to bring Discord servers and MediaWiki wikis together. It helps with linking wiki pages, verifying wiki users, informing about latest changes on the wiki and more. <a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[More information]</a></p>\n<p>Here you can change different bot settings for servers you have Manage Server permission on. To begin, you will have to authenticate your Discord account which you can do with this button:</p>"
     },
     },
     "indexjs": {
     "indexjs": {
+        "avatar": {
+            "content_type": "The provided link has the content type $1, but only the following content types are allowed:",
+            "invalid_url": "The URL couldn't be resolved to a valid image file."
+        },
         "invalid": {
         "invalid": {
             "note_http": "The provided website doesn't have a valid TLS/SSL certificate! For security reasons only wikis using HTTPS are supported.",
             "note_http": "The provided website doesn't have a valid TLS/SSL certificate! For security reasons only wikis using HTTPS are supported.",
             "note_private": "The provided wiki is private!",
             "note_private": "The provided wiki is private!",
@@ -76,11 +80,6 @@
             "text": "Either you or Wiki-Bot are missing the $1 permission for this function.",
             "text": "Either you or Wiki-Bot are missing the $1 permission for this function.",
             "title": "Missing permission!"
             "title": "Missing permission!"
         },
         },
-        "movefail": {
-            "note": "The webhook channel could not be changed!",
-            "text": "The settings have only been partially updated.",
-            "title": "Settings partially saved!"
-        },
         "mwversion": {
         "mwversion": {
             "text": "Requires at least MediaWiki 1.30, found $1 on $2.",
             "text": "Requires at least MediaWiki 1.30, found $1 on $2.",
             "title": "Outdated MediaWiki version!"
             "title": "Outdated MediaWiki version!"
@@ -134,6 +133,11 @@
             "text": "Please login before you can change any settings.",
             "text": "Please login before you can change any settings.",
             "title": "Not logged in!"
             "title": "Not logged in!"
         },
         },
+        "webhookfail": {
+            "note": "The Discord webhook could not be changed!",
+            "text": "The settings have only been partially updated.",
+            "title": "Settings partially saved!"
+        },
         "wikiblocked": {
         "wikiblocked": {
             "note": "Reason:",
             "note": "Reason:",
             "text": "$1 has been blocked from being added as a recent changes webhook.",
             "text": "$1 has been blocked from being added as a recent changes webhook.",
@@ -144,6 +148,8 @@
         "desc": "These are the recent changes webhooks for $1:",
         "desc": "These are the recent changes webhooks for $1:",
         "explanation": "<h2>Recent Changes Webhook</h2>\n<p>Wiki-Bot is able to run a recent changes webhook based on <a href=\"https://gitlab.com/piotrex43/RcGcDw\" target=\"_blank\">RcGcDw</a>. The recent changes can be displayed in compact text messages with inline links or embed messages with edit tags and category changes.</p>\n<p>Requirements to add a recent changes webhook:</p>\n<ul>\n<li>The wiki needs to run on <a href=\"https://www.mediawiki.org/wiki/MediaWiki_1.30\" target=\"_blank\">MediaWiki 1.30</a> or higher.</li>\n<li>The system message <code class=\"user-select\">MediaWiki:Custom-RcGcDw</code> needs to be set to the Discord server id <code class=\"user-select\" id=\"server-id\"></code>.</li>\n</ul>",
         "explanation": "<h2>Recent Changes Webhook</h2>\n<p>Wiki-Bot is able to run a recent changes webhook based on <a href=\"https://gitlab.com/piotrex43/RcGcDw\" target=\"_blank\">RcGcDw</a>. The recent changes can be displayed in compact text messages with inline links or embed messages with edit tags and category changes.</p>\n<p>Requirements to add a recent changes webhook:</p>\n<ul>\n<li>The wiki needs to run on <a href=\"https://www.mediawiki.org/wiki/MediaWiki_1.30\" target=\"_blank\">MediaWiki 1.30</a> or higher.</li>\n<li>The system message <code class=\"user-select\">MediaWiki:Custom-RcGcDw</code> needs to be set to the Discord server id <code class=\"user-select\" id=\"server-id\"></code>.</li>\n</ul>",
         "form": {
         "form": {
+            "avatar": "Webhook avatar:",
+            "avatar_preview": "Preview",
             "channel": "Channel:",
             "channel": "Channel:",
             "confirm": "Do you really want to delete the recent changes webhook?",
             "confirm": "Do you really want to delete the recent changes webhook?",
             "display": "Display mode:",
             "display": "Display mode:",
@@ -155,6 +161,7 @@
             "feeds": "Feeds based changes:",
             "feeds": "Feeds based changes:",
             "feeds_only": "Only feeds based changes:",
             "feeds_only": "Only feeds based changes:",
             "lang": "Language:",
             "lang": "Language:",
+            "name": "Webhook name:",
             "new": "New Recent Changes Webhook",
             "new": "New Recent Changes Webhook",
             "select_channel": "-- Select a Channel --",
             "select_channel": "-- Select a Channel --",
             "wiki": "Wiki:",
             "wiki": "Wiki:",
@@ -230,6 +237,8 @@
             "postcount_or": "Require either edit or post count.",
             "postcount_or": "Require either edit or post count.",
             "rename": "Rename users:",
             "rename": "Rename users:",
             "role": "Role:",
             "role": "Role:",
+            "role_add": "Add",
+            "role_remove": "Remove",
             "select_channel": "-- Select a Channel --",
             "select_channel": "-- Select a Channel --",
             "select_role": "-- Select a Role --",
             "select_role": "-- Select a Role --",
             "success": "Success notice:",
             "success": "Success notice:",

+ 0 - 5
dashboard/i18n/es.json

@@ -76,11 +76,6 @@
             "text": "A ti o a Wiki-Bot les falta el permiso de $1 para realizar esta función.",
             "text": "A ti o a Wiki-Bot les falta el permiso de $1 para realizar esta función.",
             "title": "¡Falta el permiso!"
             "title": "¡Falta el permiso!"
         },
         },
-        "movefail": {
-            "note": "¡No se pudo cambiar el canal del webhook!",
-            "text": "La configuración se ha actualizado parcialmente.",
-            "title": "¡Configuración parcialmente guardada!"
-        },
         "mwversion": {
         "mwversion": {
             "text": "Requiere al menos MediaWiki 1.30, se encontró $1 en $2.",
             "text": "Requiere al menos MediaWiki 1.30, se encontró $1 en $2.",
             "title": "¡Versión de MediaWiki desactualizada!"
             "title": "¡Versión de MediaWiki desactualizada!"

+ 0 - 5
dashboard/i18n/fr.json

@@ -75,11 +75,6 @@
             "text": "Soit vous, soit le bot manque la permission $1 pour effectuer cette action.",
             "text": "Soit vous, soit le bot manque la permission $1 pour effectuer cette action.",
             "title": "Permission manquante !"
             "title": "Permission manquante !"
         },
         },
-        "movefail": {
-            "note": "Le salon de l'intégration n'a pas pu être changé !",
-            "text": "Les paramètres n'ont été que partiellement mis à jour.",
-            "title": "Paramètres partiellement sauvegardés !"
-        },
         "mwversion": {
         "mwversion": {
             "text": "Requiert au moins la version 1.30 de MediaWiki, trouvé $1 dans $2.",
             "text": "Requiert au moins la version 1.30 de MediaWiki, trouvé $1 dans $2.",
             "title": "Version obsolète de MediaWiki !"
             "title": "Version obsolète de MediaWiki !"

+ 6 - 7
dashboard/i18n/hi.json

@@ -31,7 +31,7 @@
     },
     },
     "indexjs": {
     "indexjs": {
         "invalid": {
         "invalid": {
-            "note_http": "यह वेबसाइट HTTPS का इस्तेमाल नहीं करता!",
+            "note_http": "इस वेबसाइट के पास स्वीकृत TLS/SSL प्रमाणपत्र नहीं है! सुरक्षा के कारण सिर्फ HTTPS का इस्तेमाल करने वाले विकियाँ ही समर्थित हैं।",
             "note_private": "यह विकि व्यक्तिगत है!",
             "note_private": "यह विकि व्यक्तिगत है!",
             "note_timeout": "इस लिंक को जवाब देने में कुछ ज़्यादा ही वक्त लगा!",
             "note_timeout": "इस लिंक को जवाब देने में कुछ ज़्यादा ही वक्त लगा!",
             "text": "URL किसी स्वीकृत मीडियाविकि साइट का नहीं है!",
             "text": "URL किसी स्वीकृत मीडियाविकि साइट का नहीं है!",
@@ -76,11 +76,6 @@
             "text": "या तो आप या विकी-बॉट के पास इस फंक्शन के लिए $1 अनुमति नहीं है।",
             "text": "या तो आप या विकी-बॉट के पास इस फंक्शन के लिए $1 अनुमति नहीं है।",
             "title": "अनुमति नहीं है!"
             "title": "अनुमति नहीं है!"
         },
         },
-        "movefail": {
-            "note": "वेबहुक चैनल को बदला न जा सका!",
-            "text": "सेटिंग्स को अधूरे तरीके से अपडेट किया गया है।",
-            "title": "सेटिंग्स को अधूरे तरीके से सेव किया गया!"
-        },
         "mwversion": {
         "mwversion": {
             "text": "कम से कम मीडियाविकि १.३० चाहिए, $2 पर $1 मिला।",
             "text": "कम से कम मीडियाविकि १.३० चाहिए, $2 पर $1 मिला।",
             "title": "आउटडेटिड मीडियाविकि संस्करण!"
             "title": "आउटडेटिड मीडियाविकि संस्करण!"
@@ -120,7 +115,7 @@
             "title": "सेटिंग्स को सेव कर दिया गया है!"
             "title": "सेटिंग्स को सेव कर दिया गया है!"
         },
         },
         "savefail": {
         "savefail": {
-            "note_http": "यह वेबसाइट HTTPS का इस्तेमाल नहीं करता!",
+            "note_http": "इस वेबसाइट के पास स्वीकृत TLS/SSL प्रमाणपत्र नहीं है! सुरक्षा के कारण सिर्फ HTTPS का इस्तेमाल करने वाले विकियाँ ही समर्थित हैं।",
             "note_private": "यह विकि व्यक्तिगत है!",
             "note_private": "यह विकि व्यक्तिगत है!",
             "note_timeout": "इस लिंक को जवाब देने में कुछ ज़्यादा ही वक्त लगा!",
             "note_timeout": "इस लिंक को जवाब देने में कुछ ज़्यादा ही वक्त लगा!",
             "text": "सेटिंग्स को सेव न किया जा सका, कृपया दोबारा कोशिश करें।",
             "text": "सेटिंग्स को सेव न किया जा सका, कृपया दोबारा कोशिश करें।",
@@ -215,6 +210,8 @@
             "confirm": "क्या आप सच में वेरिफिकेशन को डिलीट करना चाहते हैं?",
             "confirm": "क्या आप सच में वेरिफिकेशन को डिलीट करना चाहते हैं?",
             "editcount": "न्यूनतम सम्पादना की मात्रा:",
             "editcount": "न्यूनतम सम्पादना की मात्रा:",
             "entry": "वेरिफिकेशन #$1",
             "entry": "वेरिफिकेशन #$1",
+            "flag_logall": "असफल वेरिफिकेशन लॉग करें:",
+            "flag_private": "व्यक्तिगत कमांड के जवाब:",
             "logging": "लॉग करने के लिए चैनल:",
             "logging": "लॉग करने के लिए चैनल:",
             "match": "आवश्यकता में न आने के लिए सूचना:",
             "match": "आवश्यकता में न आने के लिए सूचना:",
             "match_placeholder": "डिस्कॉर्ड टैग के मिलते हुए भी आवश्यकता में न आने के लिए मार्कडाउन टेक्स्ट।",
             "match_placeholder": "डिस्कॉर्ड टैग के मिलते हुए भी आवश्यकता में न आने के लिए मार्कडाउन टेक्स्ट।",
@@ -228,6 +225,8 @@
             "postcount_or": "या तो सम्पादना या फिर पोस्ट की मात्रा का इस्तेमाल करें।",
             "postcount_or": "या तो सम्पादना या फिर पोस्ट की मात्रा का इस्तेमाल करें।",
             "rename": "सदस्यों को रीनेम करना है:",
             "rename": "सदस्यों को रीनेम करना है:",
             "role": "रोल:",
             "role": "रोल:",
+            "role_add": "जोड़ें",
+            "role_remove": "हटाएँ",
             "select_channel": "-- एक चैनल चुनिए --",
             "select_channel": "-- एक चैनल चुनिए --",
             "select_role": "-- एक रोल चुनिए --",
             "select_role": "-- एक रोल चुनिए --",
             "success": "सफलता की सूचना:",
             "success": "सफलता की सूचना:",

+ 35 - 26
dashboard/i18n/ja.json

@@ -8,7 +8,7 @@
     ],
     ],
     "general": {
     "general": {
         "botlist": {
         "botlist": {
-            "text": "Botリストに投票することにより、他のユーザーがWiki-Botを見つけやすくなります:",
+            "text": "Botリストに投票することにより、他の利用者がWiki-Botを見つけやすくなります:",
             "title": "Botリスト"
             "title": "Botリスト"
         },
         },
         "delete": "削除",
         "delete": "削除",
@@ -16,7 +16,7 @@
         "language": "言語の変更",
         "language": "言語の変更",
         "login": "ログイン",
         "login": "ログイン",
         "logout": "ログアウト",
         "logout": "ログアウト",
-        "rcscript": "最近の変更点",
+        "rcscript": "最近の更新",
         "refresh": "サーバーリストの更新",
         "refresh": "サーバーリストの更新",
         "save": "保存",
         "save": "保存",
         "selector": "サーバーを選択",
         "selector": "サーバーを選択",
@@ -27,9 +27,13 @@
         "theme-light": "ライトテーマを使う",
         "theme-light": "ライトテーマを使う",
         "title": "Wiki-Botの設定",
         "title": "Wiki-Botの設定",
         "verification": "認証",
         "verification": "認証",
-        "welcome": "<h2>Wiki-Bot ダッシュボードへようこそ。</h2>\n<p>Wiki-Botは、DiscordサーバーとMediaWikiで作られたWikiを連携させるために作られたDiscord Botです。Wikiページへのリンク、Wikiユーザーの確認、Wikiの最新の変更についての情報提供などを行います。<a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[詳しく見る]</a></p>\n<p>ここでは、あなたがDiscordで「サーバー管理」権限を持っているサーバーの様々なBotの設定を変更することができます。まず、Discordアカウントを認証する必要がありますが、このボタンで認証できます。</p>"
+        "welcome": "<h2>Wiki-Bot ダッシュボードへようこそ。</h2>\n<p>Wiki-Botは、DiscordサーバーとMediaWikiで作られたWikiを連携させるために作られたDiscord Botです。Wikiページへのリンク、Wiki利用者の確認、Wikiの最新の変更についての情報提供などを行います。<a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[詳しく見る]</a></p>\n<p>ここでは、あなたがDiscordで「サーバー管理」権限を持っているサーバーの様々なBotの設定を変更することができます。まず、Discordアカウントを認証する必要がありますが、このボタンで認証できます。</p>"
     },
     },
     "indexjs": {
     "indexjs": {
+        "avatar": {
+            "content_type": "提供されたリンクのコンテンツタイプは $1 ですが、以下のコンテンツタイプのみ許可されています:",
+            "invalid_url": "URLが有効な画像ファイルにアクセスできませんでした。"
+        },
         "invalid": {
         "invalid": {
             "note_http": "提供されたWebサイトに有効なTLS/SSL証明書がありません。セキュリティ上の理由から、HTTPSを使用したWikiのみがサポートされています。",
             "note_http": "提供されたWebサイトに有効なTLS/SSL証明書がありません。セキュリティ上の理由から、HTTPSを使用したWikiのみがサポートされています。",
             "note_private": "提供されたWikiはプライベートなものです!",
             "note_private": "提供されたWikiはプライベートなものです!",
@@ -38,7 +42,7 @@
             "title": "無効なWikiです!"
             "title": "無効なWikiです!"
         },
         },
         "outdated": {
         "outdated": {
-            "text": "最近の更のウェブフックには、MediaWiki 1.30以降が必要です!",
+            "text": "最近の更のウェブフックには、MediaWiki 1.30以降が必要です!",
             "title": "古いMediaWikiのバージョンです!"
             "title": "古いMediaWikiのバージョンです!"
         },
         },
         "prefix": {
         "prefix": {
@@ -61,8 +65,8 @@
             "title": "不明なエラーが発生!"
             "title": "不明なエラーが発生!"
         },
         },
         "invalidusergroup": {
         "invalidusergroup": {
-            "text": "ユーザーグループ名が長すぎるか、指定した数が多すぎました。",
-            "title": "無効なユーザーグループです!"
+            "text": "利用者グループ名が長すぎるか、指定した数が多すぎました。",
+            "title": "無効な利用者グループです!"
         },
         },
         "loginfail": {
         "loginfail": {
             "text": "ログイン中にエラーが発生しました、もう一度お試しください。",
             "text": "ログイン中にエラーが発生しました、もう一度お試しください。",
@@ -76,11 +80,6 @@
             "text": "あなたかWiki-Botのどちらかが、この $1 の権限を許可されていません。",
             "text": "あなたかWiki-Botのどちらかが、この $1 の権限を許可されていません。",
             "title": "許可がありません!"
             "title": "許可がありません!"
         },
         },
-        "movefail": {
-            "note": "ウェブフックチャンネルを変更できませんでした!",
-            "text": "一部の設定は更新されました。",
-            "title": "設定が一部保存されました!"
-        },
         "mwversion": {
         "mwversion": {
             "text": "少なくともMediaWiki 1.30が必要で、 $2 で $1 が見つかりました。",
             "text": "少なくともMediaWiki 1.30が必要で、 $2 で $1 が見つかりました。",
             "title": "古いMediaWikiのバージョンです!"
             "title": "古いMediaWikiのバージョンです!"
@@ -134,28 +133,36 @@
             "text": "設定を変更するにはログインが必要です。",
             "text": "設定を変更するにはログインが必要です。",
             "title": "ログインしていません!"
             "title": "ログインしていません!"
         },
         },
+        "webhookfail": {
+            "note": "DiscordのWebhookを変更できませんでした!",
+            "text": "設定は一部しか更新されていません。",
+            "title": "設定が一部保存されました!"
+        },
         "wikiblocked": {
         "wikiblocked": {
             "note": "理由:",
             "note": "理由:",
-            "text": "$1 が最近の変更のウェブフックとして追加されるのを阻止しました。",
+            "text": "$1 が最近の更のウェブフックとして追加されるのを阻止しました。",
             "title": "Wikiがブロックされています!"
             "title": "Wikiがブロックされています!"
         }
         }
     },
     },
     "rcscript": {
     "rcscript": {
-        "desc": "$1 の最近の変更点のウェブフックです。",
-        "explanation": "<h2>最近の変更点のウェブフック</h2>\n<p>Wiki-Botは、<a href=\"https://gitlab.com/piotrex43/RcGcDw\" target=\"_blank\">RcGcDw</a>をベースにした最近の変更のウェブフックを実行することができます。最近の変更点は、直リンク付きの小さなテキストメッセージや、編集されたタグやカテゴリーの変更ができる埋め込みメッセージを表示することができます。</p>\n<p>最近の変更のウェブフックを追加するための要件:</p>\n<ul>\n<li>Wikiは<a href=\"https://www.mediawiki.org/wiki/MediaWiki_1.30\" target=\"_blank\">MediaWiki 1.30</a>以上で動作している必要があります。</li>\n<li>システムメッセージの<code class=\"user-select\">MediaWiki:Custom-RcGcDw</code> にDiscordのサーバーID <code class=\"user-select\" id=\"server-id\"></code> を設定する必要があります。</li>\n</ul>",
+        "desc": "$1 の最近の更新のウェブフックです。",
+        "explanation": "<h2>最近の変更点のウェブフック</h2>\n<p>Wiki-Botは、<a href=\"https://gitlab.com/piotrex43/RcGcDw\" target=\"_blank\">RcGcDw</a>をベースにした最近の変更のウェブフックを実行することができます。最近の変更、直リンク付きの小さなテキストメッセージや、編集されたタグやカテゴリーの変更ができる埋め込みメッセージを表示することができます。</p>\n<p>最近の変更のウェブフックを追加するための要件:</p>\n<ul>\n<li>Wikiは<a href=\"https://www.mediawiki.org/wiki/MediaWiki_1.30\" target=\"_blank\">MediaWiki 1.30</a>以上で動作している必要があります。</li>\n<li>システムメッセージの<code class=\"user-select\">MediaWiki:Custom-RcGcDw</code> にDiscordのサーバーID <code class=\"user-select\" id=\"server-id\"></code> を設定する必要があります。</li>\n</ul>",
         "form": {
         "form": {
+            "avatar": "Webhookのアバター:",
+            "avatar_preview": "プレビュー",
             "channel": "チャンネル:",
             "channel": "チャンネル:",
-            "confirm": "本当に最近の変更のウェブフックを削除しますか?",
+            "confirm": "本当に最近の更のウェブフックを削除しますか?",
             "display": "表示形態:",
             "display": "表示形態:",
             "display_compact": "直リンク付きの小型のテキストメッセージ。",
             "display_compact": "直リンク付きの小型のテキストメッセージ。",
             "display_diff": "画像プレビューと編集の違いを表示した埋め込みメッセージ。",
             "display_diff": "画像プレビューと編集の違いを表示した埋め込みメッセージ。",
             "display_embed": "編集されたタグやカテゴリーの変更ができる埋め込みメッセージ。",
             "display_embed": "編集されたタグやカテゴリーの変更ができる埋め込みメッセージ。",
             "display_image": "画像プレビューを表示した埋め込みメッセージ。",
             "display_image": "画像プレビューを表示した埋め込みメッセージ。",
-            "entry": "最近の変更点のウェブフック #$1",
+            "entry": "最近の更新のウェブフック #$1",
             "feeds": "フィードベースの変更:",
             "feeds": "フィードベースの変更:",
             "feeds_only": "フィードベースの変更のみ:",
             "feeds_only": "フィードベースの変更のみ:",
             "lang": "言語:",
             "lang": "言語:",
-            "new": "新しい最近の変更点のウェブフック",
+            "name": "Webhook名:",
+            "new": "新しい最近の変更のウェブフック",
             "select_channel": "-- チャンネルを選択 --",
             "select_channel": "-- チャンネルを選択 --",
             "wiki": "Wiki:",
             "wiki": "Wiki:",
             "wiki_check": "Wikiを確認する"
             "wiki_check": "Wikiを確認する"
@@ -180,16 +187,16 @@
             "default": "サーバー全体の設定",
             "default": "サーバー全体の設定",
             "inline": "インラインコマンド:",
             "inline": "インラインコマンド:",
             "lang": "言語:",
             "lang": "言語:",
-            "new": "新しいチャンネルの上書き",
+            "new": "別のWikiの通知を追加",
             "overwrite": "$1 設定",
             "overwrite": "$1 設定",
             "prefix": "プレフィックス:",
             "prefix": "プレフィックス:",
             "prefix_space": "プレフィックスはスペースで終わります:",
             "prefix_space": "プレフィックスはスペースで終わります:",
             "role": "最小のロール:",
             "role": "最小のロール:",
             "select_channel": "-- チャンネルを選択 --",
             "select_channel": "-- チャンネルを選択 --",
-            "wiki": "既定のWiki:",
+            "wiki": "通知するWiki:",
             "wiki_check": "Wikiを確認"
             "wiki_check": "Wikiを確認"
         },
         },
-        "new": "新しいチャンネルの上書き"
+        "new": "別のWikiの通知を追加"
     },
     },
     "slash": {
     "slash": {
         "desc": "これらは、 $1 のスラッシュコマンドです:",
         "desc": "これらは、 $1 のスラッシュコマンドです:",
@@ -208,9 +215,9 @@
     },
     },
     "verification": {
     "verification": {
         "desc": "$1 の認証結果:",
         "desc": "$1 の認証結果:",
-        "explanation": "<h2>ユーザー認証</h2>\n<p><code class=\"prefix\">verify &lt;wiki username&gt;</code>コマンドを使用すると、ユーザーは自分のWikiプロフィールのDiscord欄を使用し、自分が特定のWikiユーザーであることを認証することができます。ユーザーが一致し、サーバー上でユーザー認証が設定されている場合、Wiki-Botは一致したすべての認証エントリの役割を与えます。</p>\n<p>各認証項目では、ユーザーがどのような場合に認証に一致するかについて、複数の制限を設けることができます。:</p>\n<ul>\n<li><code class=\"prefix\">verify</code>コマンドを使用するチャンネル。</li>\n<li>認証項目の照合時に取得する役割</li>\n<li>認証項目と一致するために必要なWikiの編集回数。</li>\n<li>認証項目と一致するために、Wiki上でメンバーとなる必要のあるユーザーグループ。</li>\n<li>認証項目と一致するために必要なアカウントを作成してからの日数。</li>\n<li>Discordユーザーのニックネームを、認証項目に一致したときのWikiユーザー名に設定するかどうか。</li>\n</ul>",
+        "explanation": "<h2>利用者認証</h2>\n<p><code class=\"prefix\">verify &lt;wiki username&gt;</code>コマンドを使用すると、利用者は自分のWikiプロフィールのDiscord欄を使用し、自分が特定のWiki利用者であることを認証することができます。利用者が一致し、サーバー上で利用者認証が設定されている場合、Wiki-Botは一致したすべての認証エントリの役割を与えます。</p>\n<p>各認証項目では、利用者がどのような場合に認証に一致するかについて、複数の制限を設けることができます。:</p>\n<ul>\n<li><code class=\"prefix\">verify</code>コマンドを使用するチャンネル。</li>\n<li>認証項目の照合時に取得する役割</li>\n<li>認証項目と一致するために必要なWikiの編集回数。</li>\n<li>認証項目と一致するために、Wiki上でメンバーとなる必要のある利用者グループ。</li>\n<li>認証項目と一致するために必要なアカウントを作成してからの日数。</li>\n<li>Discord利用者のニックネームを、認証項目に一致したときのWiki利用者名に設定するかどうか。</li>\n</ul>",
         "form": {
         "form": {
-            "accountage": "アカウントを作成してからの日数:",
+            "accountage": "会員登録後経過日数:",
             "channel": "チャンネル:",
             "channel": "チャンネル:",
             "confirm": "本当にこの認証を削除しますか?",
             "confirm": "本当にこの認証を削除しますか?",
             "editcount": "最低編集回数:",
             "editcount": "最低編集回数:",
@@ -228,16 +235,18 @@
             "postcount_both": "編集回数と投稿回数を合計した回数を必要要件にする。",
             "postcount_both": "編集回数と投稿回数を合計した回数を必要要件にする。",
             "postcount_fandom": "Fandom Wikiのみ:",
             "postcount_fandom": "Fandom Wikiのみ:",
             "postcount_or": "編集回数と投稿回数のどちらかを必要要件にする。",
             "postcount_or": "編集回数と投稿回数のどちらかを必要要件にする。",
-            "rename": "ユーザー名の変更:",
+            "rename": "利用者名の変更:",
             "role": "ロール:",
             "role": "ロール:",
+            "role_add": "追加",
+            "role_remove": "削除",
             "select_channel": "-- チャンネルを選択 --",
             "select_channel": "-- チャンネルを選択 --",
             "select_role": "-- ロールを選択 --",
             "select_role": "-- ロールを選択 --",
             "success": "成功の通知:",
             "success": "成功の通知:",
             "success_placeholder": "認証に成功した場合のMarkdownテキスト。",
             "success_placeholder": "認証に成功した場合のMarkdownテキスト。",
-            "usergroup": "Wikiのユーザーグループ:",
-            "usergroup_and": "全てのユーザーグループを要求します:"
+            "usergroup": "Wikiの利用者グループ:",
+            "usergroup_and": "全ての利用者グループを要求します:"
         },
         },
-        "help_notice": "<p>カスタム通知は、いくつかの簡単な関数や変数をサポートしています。</p>\n<ul>\n<li><code class=\"form-button user-select\">$editcount</code> – そのユーザーの現在の編集回数です。</li>\n<li><code class=\"form-button user-select\">$accountage</code> – そのユーザーの現在のアカウントを作成してからの日数です。</li>\n<li><code class=\"form-button user-select\">$postcount</code> – そのユーザーの現在の議論投稿数です (Fandom Wikiのみ)。</li>\n<li><code class=\"form-button user-select\" data-after=\" }}\" data-before=\"{{#expr: \">{{#expr: 1+1}}</code> – 式の結果を返します。\n<ul>\n<li>加算の <code class=\"form-button user-select\">+</code> と減算の <code class=\"form-button user-select\">-</code>のみ対応しています。</li>\n</ul></li>\n<li><code class=\"form-button user-select\" data-after=\" |  |  }}\" data-before=\"{{#ifexpr: \">{{#ifexpr: 1 &gt; 1 | <i>if true</i> | <i>if false</i> }}</code> – 式の結果に応じたテキストを返します。\n<ul>\n<li><code class=\"form-button user-select\">&lt;</code>、 <code class=\"form-button user-select\">&gt;</code>、 <code class=\"form-button user-select\">=</code>、 <code class=\"form-button user-select\">&lt;=</code>、 <code class=\"form-button user-select\">&gt;=</code>、 <code class=\"form-button user-select\">!=</code>、 <code class=\"form-button user-select\">&lt;&gt;</code> のほか、 <code class=\"form-button user-select\">and</code>、 <code class=\"form-button user-select\">or</code>に対応しています。</li>\n</ul></li>\n</ul>",
+        "help_notice": "<p>カスタム通知は、いくつかの簡単な関数や変数をサポートしています。</p>\n<ul>\n<li><code class=\"form-button user-select\">$editcount</code> – その利用者の現在の編集回数です。</li>\n<li><code class=\"form-button user-select\">$accountage</code> – その利用者の現在のアカウントを作成してからの日数です。</li>\n<li><code class=\"form-button user-select\">$postcount</code> – その利用者の現在のディスカッション投稿数です (Fandom Wikiのみ)。</li>\n<li><code class=\"form-button user-select\" data-after=\" }}\" data-before=\"{{#expr: \">{{#expr: 1+1}}</code> – 式の結果を返します。\n<ul>\n<li>加算の <code class=\"form-button user-select\">+</code> と減算の <code class=\"form-button user-select\">-</code>のみ対応しています。</li>\n</ul></li>\n<li><code class=\"form-button user-select\" data-after=\" |  |  }}\" data-before=\"{{#ifexpr: \">{{#ifexpr: 1 &gt; 1 | <i>if true</i> | <i>if false</i> }}</code> – 式の結果に応じたテキストを返します。\n<ul>\n<li><code class=\"form-button user-select\">&lt;</code>、 <code class=\"form-button user-select\">&gt;</code>、 <code class=\"form-button user-select\">=</code>、 <code class=\"form-button user-select\">&lt;=</code>、 <code class=\"form-button user-select\">&gt;=</code>、 <code class=\"form-button user-select\">!=</code>、 <code class=\"form-button user-select\">&lt;&gt;</code> のほか、 <code class=\"form-button user-select\">and</code>、 <code class=\"form-button user-select\">or</code>に対応しています。</li>\n</ul></li>\n</ul>",
         "new": "新しい認証",
         "new": "新しい認証",
         "notice": "認証の通知"
         "notice": "認証の通知"
     }
     }

+ 0 - 5
dashboard/i18n/ko.json

@@ -69,11 +69,6 @@
             "text": "사용자 또는 Wiki-Bot에 $1 권한이 없어서 이 기능을 설정할 수 없습니다.",
             "text": "사용자 또는 Wiki-Bot에 $1 권한이 없어서 이 기능을 설정할 수 없습니다.",
             "title": "권한 부족!"
             "title": "권한 부족!"
         },
         },
-        "movefail": {
-            "note": "웹훅 채널을 생성할 수 없었습니다!",
-            "text": "설정이 일부만 적용되었습니다.",
-            "title": "설정 일부 적용!"
-        },
         "mwversion": {
         "mwversion": {
             "text": "미디어위키 1.30 이상이 필요하지만, $2 위키에서 $1 버전을 발견했습니다.",
             "text": "미디어위키 1.30 이상이 필요하지만, $2 위키에서 $1 버전을 발견했습니다.",
             "title": "오래된 미디어위키 버전!"
             "title": "오래된 미디어위키 버전!"

+ 0 - 5
dashboard/i18n/pl.json

@@ -76,11 +76,6 @@
             "text": "Ty lub Wiki-Bot nie posiada zezwolenia $1 dla tej funkcji.",
             "text": "Ty lub Wiki-Bot nie posiada zezwolenia $1 dla tej funkcji.",
             "title": "Niewystarczające zezwolenia!"
             "title": "Niewystarczające zezwolenia!"
         },
         },
-        "movefail": {
-            "note": "Kanał webhooków nie mógł zostać zmieniony!",
-            "text": "Ustawienia zostały jedynie częściowo zapisane.",
-            "title": "Ustawienia częściowo zapisane!"
-        },
         "mwversion": {
         "mwversion": {
             "text": "Wymaga przynajmniej MediaWiki w wersji 1.30, znaleziono $1 na $2.",
             "text": "Wymaga przynajmniej MediaWiki w wersji 1.30, znaleziono $1 na $2.",
             "title": "Przestarzała wersja MediaWiki!"
             "title": "Przestarzała wersja MediaWiki!"

+ 4 - 5
dashboard/i18n/pt-br.json

@@ -76,11 +76,6 @@
             "text": "Ou você ou o Wiki-Bot não têm a permissão $1 para esta função.",
             "text": "Ou você ou o Wiki-Bot não têm a permissão $1 para esta função.",
             "title": "Faltando permissão!"
             "title": "Faltando permissão!"
         },
         },
-        "movefail": {
-            "note": "O canal do webhook não pode ser alterado!",
-            "text": "As configurações foram apenas parcialmente atualizadas.",
-            "title": "Configurações parcialmente salvas!"
-        },
         "mwversion": {
         "mwversion": {
             "text": "Requer pelo menos MediaWiki 1,30, encontrado $1 em $2.",
             "text": "Requer pelo menos MediaWiki 1,30, encontrado $1 em $2.",
             "title": "Versão desatualizada do MediaWiki!"
             "title": "Versão desatualizada do MediaWiki!"
@@ -215,6 +210,8 @@
             "confirm": "Tem certeza de que deseja excluir a verificação?",
             "confirm": "Tem certeza de que deseja excluir a verificação?",
             "editcount": "Contagem mínima de edições:",
             "editcount": "Contagem mínima de edições:",
             "entry": "Verificação #$1",
             "entry": "Verificação #$1",
+            "flag_logall": "Registrar verificações malsucedidas:",
+            "flag_private": "Respostas de comando privadas:",
             "logging": "Canal de registro:",
             "logging": "Canal de registro:",
             "match": "Aviso de requisitos ausentes:",
             "match": "Aviso de requisitos ausentes:",
             "match_placeholder": "Texto de marcação nas etiquetas do Discord correspondentes, mas não cumprindo os requisitos para os cargos.",
             "match_placeholder": "Texto de marcação nas etiquetas do Discord correspondentes, mas não cumprindo os requisitos para os cargos.",
@@ -228,6 +225,8 @@
             "postcount_or": "Requer edição ou contagem de postagens.",
             "postcount_or": "Requer edição ou contagem de postagens.",
             "rename": "Renomear usuários:",
             "rename": "Renomear usuários:",
             "role": "Cargo:",
             "role": "Cargo:",
+            "role_add": "Adicionar",
+            "role_remove": "Remover",
             "select_channel": "-- Selecionar canal --",
             "select_channel": "-- Selecionar canal --",
             "select_role": "-- Selecionar cargo --",
             "select_role": "-- Selecionar cargo --",
             "success": "Aviso de sucesso:",
             "success": "Aviso de sucesso:",

+ 16 - 5
dashboard/i18n/ru.json

@@ -30,6 +30,10 @@
         "welcome": "<h2>Добро пожаловать на панель управления Вики-Ботом.</h2>\n<p>Вики-Бот - это бот, созданный для связывания серверов Discord и вики-сайтов MediaWiki. Он может предоставлять ссылки на страницы, верифицировать пользователей, информировать о свежих правках и многое другое. <a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[Узнать больше]</a></p>\n<p>На этой панели управления можно изменять настройки для серверов на которых у вас есть разрешение Управлять Сервером. Чтобы начать, вам нужно авторизовать ваш аккаунт Discord, что вы можете сделать, нажав на эту кнопку:</p>"
         "welcome": "<h2>Добро пожаловать на панель управления Вики-Ботом.</h2>\n<p>Вики-Бот - это бот, созданный для связывания серверов Discord и вики-сайтов MediaWiki. Он может предоставлять ссылки на страницы, верифицировать пользователей, информировать о свежих правках и многое другое. <a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[Узнать больше]</a></p>\n<p>На этой панели управления можно изменять настройки для серверов на которых у вас есть разрешение Управлять Сервером. Чтобы начать, вам нужно авторизовать ваш аккаунт Discord, что вы можете сделать, нажав на эту кнопку:</p>"
     },
     },
     "indexjs": {
     "indexjs": {
+        "avatar": {
+            "content_type": "Файл, находящийся по данной ссылке, имеет тип содержимого $1, но поддерживаются только следующие типы содержимого:",
+            "invalid_url": "Этот URL не указывает на изображение."
+        },
         "invalid": {
         "invalid": {
             "note_http": "Этот сайт не имеет действительного сертификата TLS/SSL! В целях безопасности поддерживаются только вики, использующие HTTPS.",
             "note_http": "Этот сайт не имеет действительного сертификата TLS/SSL! В целях безопасности поддерживаются только вики, использующие HTTPS.",
             "note_private": "Эта вики приватная!",
             "note_private": "Эта вики приватная!",
@@ -76,11 +80,6 @@
             "text": "Либо у вас, либо у Вики-Бота отсутствует разрешение $1 для выполнения этой функции.",
             "text": "Либо у вас, либо у Вики-Бота отсутствует разрешение $1 для выполнения этой функции.",
             "title": "Отсутствует разрешение!"
             "title": "Отсутствует разрешение!"
         },
         },
-        "movefail": {
-            "note": "Канал вебхука не мог быть изменён!",
-            "text": "Настройки были изменены лишь частично.",
-            "title": "Настройки частично сохранены!"
-        },
         "mwversion": {
         "mwversion": {
             "text": "Необходима как минимум MediaWiki 1.30, но на $2 версия $1.",
             "text": "Необходима как минимум MediaWiki 1.30, но на $2 версия $1.",
             "title": "Устаревшая версия MediaWiki!"
             "title": "Устаревшая версия MediaWiki!"
@@ -134,6 +133,11 @@
             "text": "Перед тем, как бы сможете изменить какие-либо настройки, вам нужно войти.",
             "text": "Перед тем, как бы сможете изменить какие-либо настройки, вам нужно войти.",
             "title": "Вы не вошли!"
             "title": "Вы не вошли!"
         },
         },
+        "webhookfail": {
+            "note": "Этот вебхук не удалось изменить!",
+            "text": "Настройки были применены лишь частично.",
+            "title": "Настройки частично сохранены!"
+        },
         "wikiblocked": {
         "wikiblocked": {
             "note": "Причина:",
             "note": "Причина:",
             "text": "$1 была блокирована от добавления к вебхуку свежих правок.",
             "text": "$1 была блокирована от добавления к вебхуку свежих правок.",
@@ -144,6 +148,8 @@
         "desc": "Вебхуки свежих правок на $1:",
         "desc": "Вебхуки свежих правок на $1:",
         "explanation": "<h2>Вебхук Свежих Правок</h2>\n<p>Вики-Бот может создавать вебхук свежих правок, основанный на <a href=\"https://gitlab.com/piotrex43/RcGcDw\" target=\"_blank\">RcGcDw</a>. Свежие правки могут отображаться как компактные текстовые сообщения со встроенными ссылками или встроенные сообщения с метками правок и изменениями категорий.</p>\n<p>Требования для создания нового вебхука свежих правок:</p>\n<ul>\n<li>Вики должна работать на <a href=\"https://www.mediawiki.org/wiki/MediaWiki_1.30\" target=\"_blank\">MediaWiki 1.30</a> или выше.</li>\n<li>Системное сообщение <code class=\"user-select\">MediaWiki:Custom-RcGcDw</code> должно содержать ID сервера: <code class=\"user-select\" id=\"server-id\"></code>.</li>\n</ul>",
         "explanation": "<h2>Вебхук Свежих Правок</h2>\n<p>Вики-Бот может создавать вебхук свежих правок, основанный на <a href=\"https://gitlab.com/piotrex43/RcGcDw\" target=\"_blank\">RcGcDw</a>. Свежие правки могут отображаться как компактные текстовые сообщения со встроенными ссылками или встроенные сообщения с метками правок и изменениями категорий.</p>\n<p>Требования для создания нового вебхука свежих правок:</p>\n<ul>\n<li>Вики должна работать на <a href=\"https://www.mediawiki.org/wiki/MediaWiki_1.30\" target=\"_blank\">MediaWiki 1.30</a> или выше.</li>\n<li>Системное сообщение <code class=\"user-select\">MediaWiki:Custom-RcGcDw</code> должно содержать ID сервера: <code class=\"user-select\" id=\"server-id\"></code>.</li>\n</ul>",
         "form": {
         "form": {
+            "avatar": "Аватар вебхука:",
+            "avatar_preview": "Предпросмотр",
             "channel": "Канал:",
             "channel": "Канал:",
             "confirm": "Вы действительно хотите удалить вебхук свежих правок?",
             "confirm": "Вы действительно хотите удалить вебхук свежих правок?",
             "display": "Режим просмотра:",
             "display": "Режим просмотра:",
@@ -155,6 +161,7 @@
             "feeds": "Изменения в лентах:",
             "feeds": "Изменения в лентах:",
             "feeds_only": "Только изменения в лентах:",
             "feeds_only": "Только изменения в лентах:",
             "lang": "Язык:",
             "lang": "Язык:",
+            "name": "Имя вебхука:",
             "new": "Новый Вебхук Свежих Правок",
             "new": "Новый Вебхук Свежих Правок",
             "select_channel": "-- Выберите Канал --",
             "select_channel": "-- Выберите Канал --",
             "wiki": "Вики:",
             "wiki": "Вики:",
@@ -215,6 +222,8 @@
             "confirm": "Вы действительно хотите удалить верификацию?",
             "confirm": "Вы действительно хотите удалить верификацию?",
             "editcount": "Минимальное количество правок:",
             "editcount": "Минимальное количество правок:",
             "entry": "Верификация №$1",
             "entry": "Верификация №$1",
+            "flag_logall": "Записывать неуспешные попытки верификации в журнал:",
+            "flag_private": "Приватные ответы:",
             "logging": "Канал для журнала:",
             "logging": "Канал для журнала:",
             "match": "Уведомление о несоответствии требованиям:",
             "match": "Уведомление о несоответствии требованиям:",
             "match_placeholder": "Текст (с разметкой) при верном тэге Discord, но несоответствии требованиям.",
             "match_placeholder": "Текст (с разметкой) при верном тэге Discord, но несоответствии требованиям.",
@@ -228,6 +237,8 @@
             "postcount_or": "Проверять либо количество постов, либо количество правок.",
             "postcount_or": "Проверять либо количество постов, либо количество правок.",
             "rename": "Изменять никнейм участников:",
             "rename": "Изменять никнейм участников:",
             "role": "Роль:",
             "role": "Роль:",
+            "role_add": "Добавить",
+            "role_remove": "Убрать",
             "select_channel": "-- Выберите Канал --",
             "select_channel": "-- Выберите Канал --",
             "select_role": "-- Выберите Роль --",
             "select_role": "-- Выберите Роль --",
             "success": "Уведомление об успехе:",
             "success": "Уведомление об успехе:",

+ 0 - 5
dashboard/i18n/tr.json

@@ -69,11 +69,6 @@
             "text": "Ya sen ya da Wiki-Bot bu işlev için gereken $1 iznine sahip değil.",
             "text": "Ya sen ya da Wiki-Bot bu işlev için gereken $1 iznine sahip değil.",
             "title": "Eksik izin!"
             "title": "Eksik izin!"
         },
         },
-        "movefail": {
-            "note": "Bu webhook kanalı değiştirilemedi!",
-            "text": "Ayarlar sadece kısmen güncellendi.",
-            "title": "Ayarlar kısmen kaydedildi!"
-        },
         "mwversion": {
         "mwversion": {
             "text": "En az MediaWiki 1.30 gerekli. $2, $1 sürümüne sahip.",
             "text": "En az MediaWiki 1.30 gerekli. $2, $1 sürümüne sahip.",
             "title": "Eski MediaWiki sürümü!"
             "title": "Eski MediaWiki sürümü!"

+ 14 - 5
dashboard/i18n/zh-hans.json

@@ -30,6 +30,10 @@
         "welcome": "<h2>欢迎来到 Wiki-Bot 控制面板。</h2>\n<p>Wiki-Bot 是一个 Discord 机器人,旨在将 MediaWiki 驱动的 wiki 站点与 Discord 服务器整合在一起。它可以帮忙链接到 wiki 页面,验证 wiki 用户,通知 wiki 上最近的更改,等等。 <a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[更多信息]</a></p>\n<p>在此,你可以为每个你拥有“管理服务器”权限的服务器管理并使用不同的机器人设置。但首先,你需要通过这个按钮验证你的 Discord 账号:</p>"
         "welcome": "<h2>欢迎来到 Wiki-Bot 控制面板。</h2>\n<p>Wiki-Bot 是一个 Discord 机器人,旨在将 MediaWiki 驱动的 wiki 站点与 Discord 服务器整合在一起。它可以帮忙链接到 wiki 页面,验证 wiki 用户,通知 wiki 上最近的更改,等等。 <a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[更多信息]</a></p>\n<p>在此,你可以为每个你拥有“管理服务器”权限的服务器管理并使用不同的机器人设置。但首先,你需要通过这个按钮验证你的 Discord 账号:</p>"
     },
     },
     "indexjs": {
     "indexjs": {
+        "avatar": {
+            "content_type": "所提供的链接的内容类型(Content-Type)为$1,但仅允许以下内容类型:",
+            "invalid_url": "此 URL 无法解析到有效的图像文件。"
+        },
         "invalid": {
         "invalid": {
             "note_http": "所提供的网站没有合法的TLS/SSL证书!由于安全原因,仅支持使用HTTPS的wiki。",
             "note_http": "所提供的网站没有合法的TLS/SSL证书!由于安全原因,仅支持使用HTTPS的wiki。",
             "note_private": "所提供的 wiki 是非公开的!",
             "note_private": "所提供的 wiki 是非公开的!",
@@ -76,11 +80,6 @@
             "text": "您或 Wiki-Bot 缺少此功能所需的 $1 权限。",
             "text": "您或 Wiki-Bot 缺少此功能所需的 $1 权限。",
             "title": "缺少权限!"
             "title": "缺少权限!"
         },
         },
-        "movefail": {
-            "note": "无法更改 webhook 频道!",
-            "text": "仅成功更新部分设置。",
-            "title": "已保存部分设置!"
-        },
         "mwversion": {
         "mwversion": {
             "text": "至少需要 MediaWiki 1.30,但 $2 为 $1。",
             "text": "至少需要 MediaWiki 1.30,但 $2 为 $1。",
             "title": "过时的 MediaWiki 版本!"
             "title": "过时的 MediaWiki 版本!"
@@ -134,6 +133,11 @@
             "text": "在更改设置前,请先登录。",
             "text": "在更改设置前,请先登录。",
             "title": "未登录!"
             "title": "未登录!"
         },
         },
+        "webhookfail": {
+            "note": "无法更改Discord Webhook!",
+            "text": "仅更新了一部分设置。",
+            "title": "已保存部分设置!"
+        },
         "wikiblocked": {
         "wikiblocked": {
             "note": "原因:",
             "note": "原因:",
             "text": "$1 已被阻止添加为最近更改 webhook。",
             "text": "$1 已被阻止添加为最近更改 webhook。",
@@ -144,6 +148,8 @@
         "desc": "这些是 $1 的最近更改 webhook:",
         "desc": "这些是 $1 的最近更改 webhook:",
         "explanation": "<h2>最近更改 webhook</h2>\n<p>Wiki-Bot可以运行基于<a href=\"https://gitlab.com/piotrex43/RcGcDw\" target=\"_blank\">RcGcDw</a>的最近更改 webhook。最近更改可以以多种显示模式显示:有行内链接的紧凑型文字信息或有编辑标签和分类更改的嵌入式消息。</p>\n<p>添加最近更改 webhook 的基本要求:</p>\n<ul>\n<li>需要以<a href=\"https://www.mediawiki.org/wiki/MediaWiki_1.30\" target=\"_blank\">MediaWiki 1.30</a>或更高版本运行的 wiki。</li>\n<li>系统消息<code class=\"user-select\">MediaWiki:Custom-RcGcDw</code>需要设置为 Discord 服务器的服务器 ID <code class=\"user-select\" id=\"server-id\"></code>。</li>\n</ul>",
         "explanation": "<h2>最近更改 webhook</h2>\n<p>Wiki-Bot可以运行基于<a href=\"https://gitlab.com/piotrex43/RcGcDw\" target=\"_blank\">RcGcDw</a>的最近更改 webhook。最近更改可以以多种显示模式显示:有行内链接的紧凑型文字信息或有编辑标签和分类更改的嵌入式消息。</p>\n<p>添加最近更改 webhook 的基本要求:</p>\n<ul>\n<li>需要以<a href=\"https://www.mediawiki.org/wiki/MediaWiki_1.30\" target=\"_blank\">MediaWiki 1.30</a>或更高版本运行的 wiki。</li>\n<li>系统消息<code class=\"user-select\">MediaWiki:Custom-RcGcDw</code>需要设置为 Discord 服务器的服务器 ID <code class=\"user-select\" id=\"server-id\"></code>。</li>\n</ul>",
         "form": {
         "form": {
+            "avatar": "Webhook 头像:",
+            "avatar_preview": "预览",
             "channel": "频道:",
             "channel": "频道:",
             "confirm": "你真的想删除最近更改 webhook 吗?",
             "confirm": "你真的想删除最近更改 webhook 吗?",
             "display": "显示模式:",
             "display": "显示模式:",
@@ -155,6 +161,7 @@
             "feeds": "基于推送页的更改:",
             "feeds": "基于推送页的更改:",
             "feeds_only": "仅启用基于推送页的更改:",
             "feeds_only": "仅启用基于推送页的更改:",
             "lang": "语言:",
             "lang": "语言:",
+            "name": "Webhook 名称:",
             "new": "新的最近更改 webhook",
             "new": "新的最近更改 webhook",
             "select_channel": "-- 选择频道 --",
             "select_channel": "-- 选择频道 --",
             "wiki": "wiki:",
             "wiki": "wiki:",
@@ -230,6 +237,8 @@
             "postcount_or": "要求编辑数或帖子数。",
             "postcount_or": "要求编辑数或帖子数。",
             "rename": "重命名用户:",
             "rename": "重命名用户:",
             "role": "身份组:",
             "role": "身份组:",
+            "role_add": "添加",
+            "role_remove": "移除",
             "select_channel": "-- 选择频道 --",
             "select_channel": "-- 选择频道 --",
             "select_role": "-- 选择身份组 --",
             "select_role": "-- 选择身份组 --",
             "success": "成功提示:",
             "success": "成功提示:",

+ 14 - 5
dashboard/i18n/zh-hant.json

@@ -30,6 +30,10 @@
         "welcome": "<h2>歡迎使用Wiki-Bot控制台。</h2>\n<p>Wiki-Bot是一個Discord機器人,旨在將使用MediaWiki的wiki與Discord伺服器整合在一起。它可以幫忙連結到wiki頁面,驗證wiki使用者,傳送近期變更等。<a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[更多資訊]</a></p>\n<p>您可以在此為您擁有「管理伺服器」權限的伺服器變更機器人設定。在開始之前,您需要透過這個按鈕驗證您的Discord帳號:</p>"
         "welcome": "<h2>歡迎使用Wiki-Bot控制台。</h2>\n<p>Wiki-Bot是一個Discord機器人,旨在將使用MediaWiki的wiki與Discord伺服器整合在一起。它可以幫忙連結到wiki頁面,驗證wiki使用者,傳送近期變更等。<a href=\"https://wiki.wikibot.de/wiki/Wiki-Bot_Wiki\" target=\"_blank\">[更多資訊]</a></p>\n<p>您可以在此為您擁有「管理伺服器」權限的伺服器變更機器人設定。在開始之前,您需要透過這個按鈕驗證您的Discord帳號:</p>"
     },
     },
     "indexjs": {
     "indexjs": {
+        "avatar": {
+            "content_type": "所提供的連結的內容類型(Content-Type)為$1,但僅允許以下內容類型:",
+            "invalid_url": "此URL無法解析至有效的圖檔。"
+        },
         "invalid": {
         "invalid": {
             "note_http": "您提供的網站沒有有效的 TLS/SSL 憑證!由於安全原因,僅支援使用 HTTPS 的 wiki。",
             "note_http": "您提供的網站沒有有效的 TLS/SSL 憑證!由於安全原因,僅支援使用 HTTPS 的 wiki。",
             "note_private": "所提供的 wiki 是私人 wiki!",
             "note_private": "所提供的 wiki 是私人 wiki!",
@@ -76,11 +80,6 @@
             "text": "您或Wiki-Bot缺少此功能需要的 $1 權限。",
             "text": "您或Wiki-Bot缺少此功能需要的 $1 權限。",
             "title": "缺少權限!"
             "title": "缺少權限!"
         },
         },
-        "movefail": {
-            "note": "無法變更webhook頻道!",
-            "text": "僅成功更新部分設定。",
-            "title": "已儲存部分設定!"
-        },
         "mwversion": {
         "mwversion": {
             "text": "至少需要MediaWiki 1.30,但 $2 為 $1。",
             "text": "至少需要MediaWiki 1.30,但 $2 為 $1。",
             "title": "過時的MediaWiki版本!"
             "title": "過時的MediaWiki版本!"
@@ -134,6 +133,11 @@
             "text": "請登入以變更設定。",
             "text": "請登入以變更設定。",
             "title": "未登入!"
             "title": "未登入!"
         },
         },
+        "webhookfail": {
+            "note": "無法變更Discord Webhook!",
+            "text": "僅更新了一部分設定。",
+            "title": "已儲存部分設定!"
+        },
         "wikiblocked": {
         "wikiblocked": {
             "note": "原因:",
             "note": "原因:",
             "text": "$1 已被阻止加入近期變更webhook。",
             "text": "$1 已被阻止加入近期變更webhook。",
@@ -144,6 +148,8 @@
         "desc": "以下為 $1 的近期變更webhook:",
         "desc": "以下為 $1 的近期變更webhook:",
         "explanation": "<h2>近期變更webhook</h2>\n<p>Wiki-Bot能運行基於<a href=\"https://gitlab.com/piotrex43/RcGcDw\" target=\"_blank\">RcGcDw</a>的近期變更webhook。近期變更可以以多種顯示模式顯示:包含行內連結的精簡文字訊息或包含圖片預覽及編輯差異的嵌入式訊息。</p>\n<p>加入近期變更webhook的基本需求:</p>\n<ul>\n<li>需要以<a href=\"https://www.mediawiki.org/wiki/MediaWiki_1.30\" target=\"_blank\">MediaWiki 1.30</a>或更高版本運行的wiki。</li>\n<li>系統訊息<code class=\"user-select\">MediaWiki:Custom-RcGcDw</code>需要設為Discord伺服器的伺服器id <code class=\"user-select\" id=\"server-id\"></code>。</li>\n</ul>",
         "explanation": "<h2>近期變更webhook</h2>\n<p>Wiki-Bot能運行基於<a href=\"https://gitlab.com/piotrex43/RcGcDw\" target=\"_blank\">RcGcDw</a>的近期變更webhook。近期變更可以以多種顯示模式顯示:包含行內連結的精簡文字訊息或包含圖片預覽及編輯差異的嵌入式訊息。</p>\n<p>加入近期變更webhook的基本需求:</p>\n<ul>\n<li>需要以<a href=\"https://www.mediawiki.org/wiki/MediaWiki_1.30\" target=\"_blank\">MediaWiki 1.30</a>或更高版本運行的wiki。</li>\n<li>系統訊息<code class=\"user-select\">MediaWiki:Custom-RcGcDw</code>需要設為Discord伺服器的伺服器id <code class=\"user-select\" id=\"server-id\"></code>。</li>\n</ul>",
         "form": {
         "form": {
+            "avatar": "Webhook 頭像:",
+            "avatar_preview": "預覽",
             "channel": "頻道:",
             "channel": "頻道:",
             "confirm": "您確定要刪除近期變更webhook嗎?",
             "confirm": "您確定要刪除近期變更webhook嗎?",
             "display": "顯示模式:",
             "display": "顯示模式:",
@@ -155,6 +161,7 @@
             "feeds": "基於推送頁的變更:",
             "feeds": "基於推送頁的變更:",
             "feeds_only": "僅啟用基於推送頁的變更:",
             "feeds_only": "僅啟用基於推送頁的變更:",
             "lang": "語言:",
             "lang": "語言:",
+            "name": "Webhook 名稱:",
             "new": "新的近期變更webhook",
             "new": "新的近期變更webhook",
             "select_channel": "-- 選擇頻道 --",
             "select_channel": "-- 選擇頻道 --",
             "wiki": "wiki:",
             "wiki": "wiki:",
@@ -230,6 +237,8 @@
             "postcount_or": "需要編輯次數或貼文數。",
             "postcount_or": "需要編輯次數或貼文數。",
             "rename": "重新命名使用者:",
             "rename": "重新命名使用者:",
             "role": "身分組:",
             "role": "身分組:",
+            "role_add": "新增",
+            "role_remove": "移除",
             "select_channel": "-- 選擇頻道 --",
             "select_channel": "-- 選擇頻道 --",
             "select_role": "-- 選擇身分組 --",
             "select_role": "-- 選擇身分組 --",
             "success": "成功提示:",
             "success": "成功提示:",

+ 7 - 2
dashboard/index.js

@@ -115,6 +115,13 @@ const server = http.createServer( (req, res) => {
 		}
 		}
 	}
 	}
 
 
+	var reqURL = new URL(req.url, process.env.dashboard);
+
+	if ( req.method === 'HEAD' && files.has(reqURL.pathname) ) {
+		let file = files.get(reqURL.pathname);
+		res.writeHead(200, {'Content-Type': file.contentType});
+		return res.end();
+	}
 	if ( req.method !== 'GET' ) {
 	if ( req.method !== 'GET' ) {
 		let body = '<img width="400" src="https://http.cat/418"><br><strong>' + http.STATUS_CODES[418] + '</strong>';
 		let body = '<img width="400" src="https://http.cat/418"><br><strong>' + http.STATUS_CODES[418] + '</strong>';
 		res.writeHead(418, {
 		res.writeHead(418, {
@@ -125,8 +132,6 @@ const server = http.createServer( (req, res) => {
 		return res.end();
 		return res.end();
 	}
 	}
 
 
-	var reqURL = new URL(req.url, process.env.dashboard);
-
 	if ( reqURL.pathname === '/oauth/mw' ) {
 	if ( reqURL.pathname === '/oauth/mw' ) {
 		return pages.verify(res, reqURL.searchParams);
 		return pages.verify(res, reqURL.searchParams);
 	}
 	}

+ 95 - 17
dashboard/rcscript.js

@@ -11,10 +11,17 @@ const display_types = [
 	'image',
 	'image',
 	'diff'
 	'diff'
 ];
 ];
+const avatar_content_types = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
 
 
 const fieldset = {
 const fieldset = {
 	channel: '<label for="wb-settings-channel">Channel:</label>'
 	channel: '<label for="wb-settings-channel">Channel:</label>'
 	+ '<select id="wb-settings-channel" name="channel" required></select>',
 	+ '<select id="wb-settings-channel" name="channel" required></select>',
+	name: '<label for="wb-settings-name">Webhook name:</label>'
+	+ '<input type="text" id="wb-settings-name" name="name" minlength="2" maxlength="32" autocomplete="on">',
+	avatar: '<label for="wb-settings-avatar">Webhook avatar:</label>'
+	+ '<input type="url" id="wb-settings-avatar" name="avatar" list="wb-settings-avatar-list" autocomplete="url">'
+	+ '<datalist id="wb-settings-avatar-list"></datalist>'
+	+ '<button type="button" id="wb-settings-avatar-preview">Preview</button>',
 	wiki: '<label for="wb-settings-wiki">Wiki:</label>'
 	wiki: '<label for="wb-settings-wiki">Wiki:</label>'
 	+ '<input type="url" id="wb-settings-wiki" name="wiki" list="wb-settings-wiki-list" required autocomplete="url">'
 	+ '<input type="url" id="wb-settings-wiki" name="wiki" list="wb-settings-wiki-list" required autocomplete="url">'
 	+ '<datalist id="wb-settings-wiki-list"></datalist>'
 	+ '<datalist id="wb-settings-wiki-list"></datalist>'
@@ -34,16 +41,16 @@ const fieldset = {
 	display: '<span>Display mode:</span>'
 	display: '<span>Display mode:</span>'
 	+ '<div class="wb-settings-display">'
 	+ '<div class="wb-settings-display">'
 	+ '<input type="radio" id="wb-settings-display-0" name="display" value="0" required>'
 	+ '<input type="radio" id="wb-settings-display-0" name="display" value="0" required>'
-	+ '<label for="wb-settings-display-0">Compact text messages with inline links.</label>'
+	+ '<label for="wb-settings-display-0" class="radio-label">Compact text messages with inline links.</label>'
 	+ '</div><div class="wb-settings-display">'
 	+ '</div><div class="wb-settings-display">'
 	+ '<input type="radio" id="wb-settings-display-1" name="display" value="1" required>'
 	+ '<input type="radio" id="wb-settings-display-1" name="display" value="1" required>'
-	+ '<label for="wb-settings-display-1">Embed messages with edit tags and category changes.</label>'
+	+ '<label for="wb-settings-display-1" class="radio-label">Embed messages with edit tags and category changes.</label>'
 	+ '</div><div class="wb-settings-display">'
 	+ '</div><div class="wb-settings-display">'
 	+ '<input type="radio" id="wb-settings-display-2" name="display" value="2" required>'
 	+ '<input type="radio" id="wb-settings-display-2" name="display" value="2" required>'
-	+ '<label for="wb-settings-display-2">Embed messages with image previews.</label>'
+	+ '<label for="wb-settings-display-2" class="radio-label">Embed messages with image previews.</label>'
 	+ '</div><div class="wb-settings-display">'
 	+ '</div><div class="wb-settings-display">'
 	+ '<input type="radio" id="wb-settings-display-3" name="display" value="3" required>'
 	+ '<input type="radio" id="wb-settings-display-3" name="display" value="3" required>'
-	+ '<label for="wb-settings-display-3">Embed messages with image previews and edit differences.</label>'
+	+ '<label for="wb-settings-display-3" class="radio-label">Embed messages with image previews and edit differences.</label>'
 	+ '</div>',
 	+ '</div>',
 	feeds: '<label for="wb-settings-feeds">Feeds based changes:</label>'
 	feeds: '<label for="wb-settings-feeds">Feeds based changes:</label>'
 	+ '<input type="checkbox" id="wb-settings-feeds" name="feeds">'
 	+ '<input type="checkbox" id="wb-settings-feeds" name="feeds">'
@@ -63,6 +70,8 @@ const fieldset = {
  * @param {Object} settings - The current settings
  * @param {Object} settings - The current settings
  * @param {Boolean} settings.patreon
  * @param {Boolean} settings.patreon
  * @param {String} [settings.channel]
  * @param {String} [settings.channel]
+ * @param {String} [settings.name]
+ * @param {String} [settings.avatar]
  * @param {String} settings.wiki
  * @param {String} settings.wiki
  * @param {String} settings.lang
  * @param {String} settings.lang
  * @param {Number} settings.display
  * @param {Number} settings.display
@@ -115,6 +124,20 @@ function createForm($, header, dashboardLang, settings, guildChannels, allWikis)
 		$(`<option id="wb-settings-channel-${settings.channel}">`).val(settings.channel).attr('selected', '').text(settings.channel)
 		$(`<option id="wb-settings-channel-${settings.channel}">`).val(settings.channel).attr('selected', '').text(settings.channel)
 	);
 	);
 	fields.push(channel);
 	fields.push(channel);
+	let webhook_name = $('<div>').append(fieldset.name);
+	webhook_name.find('label').text(dashboardLang.get('rcscript.form.name'));
+	webhook_name.find('#wb-settings-name').val(settings.name);
+	fields.push(webhook_name);
+	let avatar = $('<div>').append(fieldset.avatar);
+	avatar.find('label').text(dashboardLang.get('rcscript.form.avatar'));
+	avatar.find('#wb-settings-avatar-preview').text(dashboardLang.get('rcscript.form.avatar_preview'));
+	avatar.find('#wb-settings-avatar').val(( settings.avatar || '' ));
+	if ( settings.avatar ) avatar.find('#wb-settings-avatar').attr('size', settings.avatar.length + 10);
+	avatar.find('#wb-settings-avatar-list').append(
+		$(`<option>`).val(new URL('/src/icon.png', process.env.dashboard).href),
+		( settings.avatar ? $(`<option>`).val(settings.avatar) : null )
+	);
+	fields.push(avatar);
 	let wiki = $('<div>').append(fieldset.wiki);
 	let wiki = $('<div>').append(fieldset.wiki);
 	wiki.find('label').text(dashboardLang.get('rcscript.form.wiki'));
 	wiki.find('label').text(dashboardLang.get('rcscript.form.wiki'));
 	wiki.find('#wb-settings-wiki-check').text(dashboardLang.get('rcscript.form.wiki_check'));
 	wiki.find('#wb-settings-wiki-check').text(dashboardLang.get('rcscript.form.wiki_check'));
@@ -161,7 +184,7 @@ function createForm($, header, dashboardLang, settings, guildChannels, allWikis)
 	if ( readonly ) {
 	if ( readonly ) {
 		form.find('input').attr('readonly', '');
 		form.find('input').attr('readonly', '');
 		form.find('input[type="checkbox"], input[type="radio"]:not(:checked), option, optgroup').attr('disabled', '');
 		form.find('input[type="checkbox"], input[type="radio"]:not(:checked), option, optgroup').attr('disabled', '');
-		form.find('input[type="submit"], button.addmore').remove();
+		form.find('input[type="submit"]').remove();
 	}
 	}
 	return $('<form id="wb-settings" method="post" enctype="application/x-www-form-urlencoded">').append(
 	return $('<form id="wb-settings" method="post" enctype="application/x-www-form-urlencoded">').append(
 		$('<h2>').text(header),
 		$('<h2>').text(header),
@@ -199,11 +222,19 @@ function dashboard_rcscript(res, $, guild, args, dashboardLang) {
 				if ( !response.body?.channel_id ) {
 				if ( !response.body?.channel_id ) {
 					console.log( '- Dashboard: ' + response.statusCode + ': Error while getting the webhook: ' + response.body?.message );
 					console.log( '- Dashboard: ' + response.statusCode + ': Error while getting the webhook: ' + response.body?.message );
 					row.channel = 'UNKNOWN';
 					row.channel = 'UNKNOWN';
+					row.name = 'UNKNOWN';
+					row.avatar = '';
+				}
+				else {
+					row.channel = response.body.channel_id;
+					row.name = response.body.name;
+					row.avatar = ( response.body.avatar ? `https://cdn.discordapp.com/avatars/${response.body.id}/${response.body.avatar}` : '' );
 				}
 				}
-				else row.channel = response.body.channel_id;
 			}, error => {
 			}, error => {
 				console.log( '- Dashboard: Error while getting the webhook: ' + error );
 				console.log( '- Dashboard: Error while getting the webhook: ' + error );
 				row.channel = 'UNKNOWN';
 				row.channel = 'UNKNOWN';
+				row.name = 'UNKNOWN';
+				row.avatar = '';
 			} );
 			} );
 		} )).finally( () => {
 		} )).finally( () => {
 			let suffix = ( args[0] === 'owner' ? '?owner=true' : '' );
 			let suffix = ( args[0] === 'owner' ? '?owner=true' : '' );
@@ -268,6 +299,8 @@ function dashboard_rcscript(res, $, guild, args, dashboardLang) {
  * @param {String|Number} type - The setting to change
  * @param {String|Number} type - The setting to change
  * @param {Object} settings - The new settings
  * @param {Object} settings - The new settings
  * @param {String} settings.channel
  * @param {String} settings.channel
+ * @param {String} [settings.name]
+ * @param {String} [settings.avatar]
  * @param {String} settings.wiki
  * @param {String} settings.wiki
  * @param {String} settings.lang
  * @param {String} settings.lang
  * @param {Number} settings.display
  * @param {Number} settings.display
@@ -294,6 +327,9 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 		if ( type === 'new' && !userSettings.guilds.isMember.get(guild).channels.some( channel => {
 		if ( type === 'new' && !userSettings.guilds.isMember.get(guild).channels.some( channel => {
 			return ( channel.id === settings.channel && !channel.isCategory );
 			return ( channel.id === settings.channel && !channel.isCategory );
 		} ) ) return res(`/guild/${guild}/rcscript/new`, 'savefail');
 		} ) ) return res(`/guild/${guild}/rcscript/new`, 'savefail');
+		settings.name = ( settings.name || '' ).trim();
+		if ( settings.name.length < 2 ) settings.name = '';
+		if ( !settings.avatar || !/^https?:\/\//.test(settings.avatar) ) settings.avatar = '';
 	}
 	}
 	if ( settings.delete_settings && type === 'new' ) {
 	if ( settings.delete_settings && type === 'new' ) {
 		return res(`/guild/${guild}/rcscript/new`, 'savefail');
 		return res(`/guild/${guild}/rcscript/new`, 'savefail');
@@ -357,7 +393,16 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 				if ( body.query.allmessages[0]['*'] !== guild ) {
 				if ( body.query.allmessages[0]['*'] !== guild ) {
 					return res(`/guild/${guild}/rcscript/new`, 'sysmessage', guild, wiki.toLink('MediaWiki:Custom-RcGcDw', 'action=edit'));
 					return res(`/guild/${guild}/rcscript/new`, 'sysmessage', guild, wiki.toLink('MediaWiki:Custom-RcGcDw', 'action=edit'));
 				}
 				}
-				return db.query( 'SELECT reason FROM blocklist WHERE wiki = $1', [wiki.href] ).then( ({rows:[block]}) => {
+				return Promise.all([
+					db.query( 'SELECT reason FROM blocklist WHERE wiki = $1', [wiki.href] ),
+					( settings.avatar ? got.head( settings.avatar ).then( headresponse => {
+						if ( avatar_content_types.includes( headresponse.headers?.['content-type'] ) ) return;
+						settings.avatar = '';
+					}, error => {
+						console.log( '- Dashboard: Error while checking for the HEAD: ' + error );
+						settings.avatar = '';
+					} ) : null )
+				]).then( ([{rows:[block]}]) => {
 					if ( block ) {
 					if ( block ) {
 						console.log( `- Dashboard: ${wiki.href} is blocked: ${block.reason}` );
 						console.log( `- Dashboard: ${wiki.href} is blocked: ${block.reason}` );
 						return res(`/guild/${guild}/rcscript/new`, 'wikiblocked', body.query.general.sitename, block.reason);
 						return res(`/guild/${guild}/rcscript/new`, 'wikiblocked', body.query.general.sitename, block.reason);
@@ -390,7 +435,8 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 							type: 'createWebhook',
 							type: 'createWebhook',
 							guild: guild,
 							guild: guild,
 							channel: settings.channel,
 							channel: settings.channel,
-							name: ( body.query.allmessages[1]['*'] || 'Recent changes' ),
+							name: ( settings.name || body.query.allmessages[1]['*'] || 'Recent changes' ),
+							avatar: settings.avatar,
 							reason: lang.get('rcscript.audit_reason', wiki.href),
 							reason: lang.get('rcscript.audit_reason', wiki.href),
 							text: webhook_lang.get('created', body.query.general.sitename) + ( enableFeeds && settings.feeds_only ? '' : `\n<${wiki.toLink(body.query.pages['-1'].title)}>` ) + ( enableFeeds ? `\n<${wiki.href}f>` : '' )
 							text: webhook_lang.get('created', body.query.general.sitename) + ( enableFeeds && settings.feeds_only ? '' : `\n<${wiki.toLink(body.query.pages['-1'].title)}>` ) + ( enableFeeds ? `\n<${wiki.href}f>` : '' )
 						} ).then( webhook => {
 						} ).then( webhook => {
@@ -405,6 +451,8 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 								res(`/guild/${guild}/rcscript/${configid}`, 'save');
 								res(`/guild/${guild}/rcscript/${configid}`, 'save');
 								var text = lang.get('rcscript.dashboard.added', `<@${userSettings.user.id}>`, configid);
 								var text = lang.get('rcscript.dashboard.added', `<@${userSettings.user.id}>`, configid);
 								text += `\n${lang.get('rcscript.channel')} <#${settings.channel}>`;
 								text += `\n${lang.get('rcscript.channel')} <#${settings.channel}>`;
+								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.wiki')} <${wiki.href}>`;
 								text += `\n${lang.get('rcscript.lang')} \`${allLangs.names[settings.lang]}\``;
 								text += `\n${lang.get('rcscript.lang')} \`${allLangs.names[settings.lang]}\``;
 								text += `\n${lang.get('rcscript.display')} \`${display_types[settings.display]}\``;
 								text += `\n${lang.get('rcscript.display')} \`${display_types[settings.display]}\``;
@@ -458,6 +506,8 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 				return res(`/guild/${guild}/rcscript/${type}`, 'savefail');
 				return res(`/guild/${guild}/rcscript/${type}`, 'savefail');
 			}
 			}
 			row.channel = wresponse.body.channel_id;
 			row.channel = wresponse.body.channel_id;
+			row.name = wresponse.body.name;
+			row.avatar = ( wresponse.body.avatar ? `https://cdn.discordapp.com/avatars/${wresponse.body.id}/${wresponse.body.avatar}` : '' );
 			var newChannel = false;
 			var newChannel = false;
 			if ( settings.save_settings && row.channel !== settings.channel ) {
 			if ( settings.save_settings && row.channel !== settings.channel ) {
 				if ( !userSettings.guilds.isMember.get(guild).channels.some( channel => {
 				if ( !userSettings.guilds.isMember.get(guild).channels.some( channel => {
@@ -519,6 +569,7 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 						} );
 						} );
 						var text = lang.get('rcscript.dashboard.removed', `<@${userSettings.user.id}>`, type);
 						var text = lang.get('rcscript.dashboard.removed', `<@${userSettings.user.id}>`, type);
 						text += `\n${lang.get('rcscript.channel')} <#${row.channel}>`;
 						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.wiki')} <${row.wiki}>`;
 						text += `\n${lang.get('rcscript.lang')} \`${allLangs.names[row.lang]}\``;
 						text += `\n${lang.get('rcscript.lang')} \`${allLangs.names[row.lang]}\``;
 						text += `\n${lang.get('rcscript.display')} \`${display_types[row.display]}\``;
 						text += `\n${lang.get('rcscript.display')} \`${display_types[row.display]}\``;
@@ -545,6 +596,8 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 				}
 				}
 				var hasDiff = false;
 				var hasDiff = false;
 				if ( newChannel ) hasDiff = true;
 				if ( newChannel ) hasDiff = true;
+				if ( settings.name && row.name !== settings.name ) hasDiff = true;
+				if ( settings.avatar && row.avatar !== settings.avatar ) hasDiff = true;
 				if ( row.wiki !== settings.wiki ) hasDiff = true;
 				if ( row.wiki !== settings.wiki ) hasDiff = true;
 				if ( row.lang !== settings.lang ) hasDiff = true;
 				if ( row.lang !== settings.lang ) hasDiff = true;
 				if ( row.display !== settings.display ) hasDiff = true;
 				if ( row.display !== settings.display ) hasDiff = true;
@@ -584,7 +637,16 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 					if ( row.wiki !== wiki.href && body.query.allmessages[0]['*'] !== guild ) {
 					if ( row.wiki !== wiki.href && body.query.allmessages[0]['*'] !== guild ) {
 						return res(`/guild/${guild}/rcscript/${type}`, 'sysmessage', guild, wiki.toLink('MediaWiki:Custom-RcGcDw', 'action=edit'));
 						return res(`/guild/${guild}/rcscript/${type}`, 'sysmessage', guild, wiki.toLink('MediaWiki:Custom-RcGcDw', 'action=edit'));
 					}
 					}
-					return db.query( 'SELECT reason FROM blocklist WHERE wiki = $1', [wiki.href] ).then( ({rows:[block]}) => {
+					return Promise.all([
+						db.query( 'SELECT reason FROM blocklist WHERE wiki = $1', [wiki.href] ),
+						( settings.avatar && row.avatar !== settings.avatar ? got.head( settings.avatar ).then( headresponse => {
+							if ( avatar_content_types.includes( headresponse.headers?.['content-type'] ) ) return;
+							settings.avatar = '';
+						}, error => {
+							console.log( '- Dashboard: Error while checking for the HEAD: ' + error );
+							settings.avatar = '';
+						} ) : null )
+					]).then( ([{rows:[block]}]) => {
 						if ( block ) {
 						if ( block ) {
 							console.log( `- Dashboard: ${wiki.href} is blocked: ${block.reason}` );
 							console.log( `- Dashboard: ${wiki.href} is blocked: ${block.reason}` );
 							return res(`/guild/${guild}/rcscript/${type}`, 'wikiblocked', body.query.general.sitename, block.reason);
 							return res(`/guild/${guild}/rcscript/${type}`, 'wikiblocked', body.query.general.sitename, block.reason);
@@ -637,6 +699,7 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 							}
 							}
 							db.query( sql + ' WHERE webhook = $1', sqlargs ).then( () => {
 							db.query( sql + ' WHERE webhook = $1', sqlargs ).then( () => {
 								console.log( `- Dashboard: RcGcDw successfully updated: ${guild}#${type}` );
 								console.log( `- Dashboard: RcGcDw successfully updated: ${guild}#${type}` );
+								var webhook_changes = {};
 								var lang = new Lang(row.mainlang);
 								var lang = new Lang(row.mainlang);
 								var webhook_lang = new Lang(settings.lang, 'rcscript.webhook');
 								var webhook_lang = new Lang(settings.lang, 'rcscript.webhook');
 								var diff = [];
 								var diff = [];
@@ -645,6 +708,17 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 								if ( newChannel ) {
 								if ( newChannel ) {
 									diff.push(lang.get('rcscript.channel') + ` ~~<#${row.channel}>~~ → <#${settings.channel}>`);
 									diff.push(lang.get('rcscript.channel') + ` ~~<#${row.channel}>~~ → <#${settings.channel}>`);
 									webhook_diff.push(webhook_lang.get('dashboard.channel'));
 									webhook_diff.push(webhook_lang.get('dashboard.channel'));
+									webhook_changes.channel = settings.channel;
+								}
+								if ( settings.name && row.name !== settings.name ) {
+									diff.push(lang.get('rcscript.name') + ` ~~\`${row.name}\`~~ → \`${settings.name}\``);
+									webhook_diff.push(webhook_lang.get('dashboard.name', settings.name));
+									webhook_changes.name = settings.name;
+								}
+								if ( settings.avatar && row.avatar !== settings.avatar ) {
+									diff.push(lang.get('rcscript.avatar') + ` <${settings.avatar}>`);
+									webhook_diff.push(webhook_lang.get('dashboard.avatar'));
+									webhook_changes.avatar = settings.avatar;
 								}
 								}
 								if ( row.wiki !== wiki.href ) {
 								if ( row.wiki !== wiki.href ) {
 									diff.push(lang.get('rcscript.wiki') + ` ~~<${row.wiki}>~~ → <${wiki.href}>`);
 									diff.push(lang.get('rcscript.wiki') + ` ~~<${row.wiki}>~~ → <${wiki.href}>`);
@@ -667,12 +741,14 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 									diff.push(lang.get('rcscript.feeds') + ` ~~*\`${lang.get('rcscript.' + ( row.postid === '-1' ? 'disabled' : 'enabled' ))}\`*~~ → *\`${lang.get('rcscript.' + ( enableFeeds ? 'enabled' : 'disabled' ))}\`*`);
 									diff.push(lang.get('rcscript.feeds') + ` ~~*\`${lang.get('rcscript.' + ( row.postid === '-1' ? 'disabled' : 'enabled' ))}\`*~~ → *\`${lang.get('rcscript.' + ( enableFeeds ? 'enabled' : 'disabled' ))}\`*`);
 									webhook_diff.push(webhook_lang.get('dashboard.' + ( enableFeeds ? 'enabled_feeds' : 'disabled_feeds' )));
 									webhook_diff.push(webhook_lang.get('dashboard.' + ( enableFeeds ? 'enabled_feeds' : 'disabled_feeds' )));
 								}
 								}
-								if ( newChannel ) return sendMsg( {
-									type: 'moveWebhook',
+								if ( Object.keys(webhook_changes).length ) return sendMsg( {
+									type: 'editWebhook',
 									guild: guild,
 									guild: guild,
 									webhook: row.webhook,
 									webhook: row.webhook,
-									channel: settings.channel,
-									reason: lang.get('rcscript.audit_reason_move'),
+									channel: webhook_changes.channel,
+									name: webhook_changes.name,
+									avatar: webhook_changes.avatar,
+									reason: lang.get('rcscript.audit_reason_edit'),
 									text: webhook_lang.get('dashboard.updated') + '\n' + webhook_diff.join('\n')
 									text: webhook_lang.get('dashboard.updated') + '\n' + webhook_diff.join('\n')
 								} ).then( webhook => {
 								} ).then( webhook => {
 									if ( !webhook ) return Promise.reject();
 									if ( !webhook ) return Promise.reject();
@@ -686,15 +762,17 @@ function update_rcscript(res, userSettings, guild, type, settings) {
 										console.log( '- Dashboard: Error while notifying the guild: ' + error );
 										console.log( '- Dashboard: Error while notifying the guild: ' + error );
 									} );
 									} );
 								}, error => {
 								}, error => {
-									console.log( '- Dashboard: Error while moving the webhook: ' + error );
+									console.log( '- Dashboard: Error while editing the webhook: ' + error );
 									return Promise.reject();
 									return Promise.reject();
 								} ).catch( () => {
 								} ).catch( () => {
-									diff.shift();
-									webhook_diff.shift();
+									Object.keys(webhook_changes).forEach( () => {
+										diff.shift();
+										webhook_diff.shift();
+									} );
 									if ( !diff.length ) {
 									if ( !diff.length ) {
 										return res(`/guild/${guild}/rcscript/${type}`, 'savefail');
 										return res(`/guild/${guild}/rcscript/${type}`, 'savefail');
 									}
 									}
-									res(`/guild/${guild}/rcscript/${type}`, 'movefail');
+									res(`/guild/${guild}/rcscript/${type}`, 'webhookfail');
 									diff.shift();
 									diff.shift();
 									webhook_diff.shift();
 									webhook_diff.shift();
 									got.post( 'https://discord.com/api/webhooks/' + row.webhook, {
 									got.post( 'https://discord.com/api/webhooks/' + row.webhook, {

+ 3 - 3
dashboard/slash.js

@@ -9,13 +9,13 @@ const fieldset = {
 	permission: '<span title="@UNKNOWN">@UNKNOWN:</span>'
 	permission: '<span title="@UNKNOWN">@UNKNOWN:</span>'
 	+ '<div class="wb-settings-permission">'
 	+ '<div class="wb-settings-permission">'
 	+ '<input type="radio" id="wb-settings-permission-0" name="permission" value="0" required>'
 	+ '<input type="radio" id="wb-settings-permission-0" name="permission" value="0" required>'
-	+ '<label for="wb-settings-permission-0" class="wb-settings-permission-deny">Deny</label>'
+	+ '<label for="wb-settings-permission-0" class="wb-settings-permission-deny radio-label">Deny</label>'
 	+ '</div><div class="wb-settings-permission">'
 	+ '</div><div class="wb-settings-permission">'
 	+ '<input type="radio" id="wb-settings-permission-1" name="permission" value="1" required>'
 	+ '<input type="radio" id="wb-settings-permission-1" name="permission" value="1" required>'
-	+ '<label for="wb-settings-permission-1" class="wb-settings-permission-allow">Allow</label>'
+	+ '<label for="wb-settings-permission-1" class="wb-settings-permission-allow radio-label">Allow</label>'
 	+ '</div><div class="wb-settings-permission">'
 	+ '</div><div class="wb-settings-permission">'
 	+ '<input type="radio" id="wb-settings-permission-default" name="permission" value="" required>'
 	+ '<input type="radio" id="wb-settings-permission-default" name="permission" value="" required>'
-	+ '<label for="wb-settings-permission-default" class="wb-settings-permission-default">Default</label>'
+	+ '<label for="wb-settings-permission-default" class="wb-settings-permission-default radio-label">Default</label>'
 	+ '</div>',
 	+ '</div>',
 	save: '<input type="submit" id="wb-settings-save" name="save_settings">'
 	save: '<input type="submit" id="wb-settings-save" name="save_settings">'
 };
 };

+ 46 - 4
dashboard/src/index.css

@@ -89,10 +89,42 @@ a[alt]:hover:after {
 }
 }
 .scrollbar {
 .scrollbar {
 	-ms-overflow-style: none;
 	-ms-overflow-style: none;
-	scrollbar-width: none;
+	scrollbar-width: thin;
+	scrollbar-color: transparent transparent;
+}
+.scrollbar:hover {
+	scrollbar-color: #202225 transparent;
+}
+.theme-light .scrollbar:hover {
+	scrollbar-color: rgba(79,84,92,0.3) transparent;
 }
 }
 .scrollbar::-webkit-scrollbar {
 .scrollbar::-webkit-scrollbar {
-	display: none;
+	width: 8px;
+	height: 8px;
+}
+.scrollbar::-webkit-scrollbar-corner {
+	background-color: transparent;
+}
+.scrollbar::-webkit-scrollbar-thumb, .scrollbar::-webkit-scrollbar-track {
+	visibility: hidden;
+}
+.scrollbar:hover::-webkit-scrollbar-thumb, .scrollbar:hover::-webkit-scrollbar-track {
+	visibility: visible;
+}
+.scrollbar::-webkit-scrollbar-thumb {
+	background-clip: padding-box;
+	border: 2px solid transparent;
+	border-radius: 4px;
+	background-color: #202225;
+	min-height: 40px;
+}
+.theme-light .scrollbar::-webkit-scrollbar-thumb {
+	background-color: rgba(79,84,92,0.3);
+}
+.scrollbar::-webkit-scrollbar-track {
+	border-color: transparent;
+	background-color: transparent;
+	border: 2px solid transparent;
 }
 }
 #sidebar {
 #sidebar {
 	position: fixed;
 	position: fixed;
@@ -129,6 +161,7 @@ a[alt]:hover:after {
 	border-radius: 50%;
 	border-radius: 50%;
 	width: 48px;
 	width: 48px;
 	height: 48px;
 	height: 48px;
+	object-fit: cover;
 	display: flex;
 	display: flex;
 	align-items: center;
 	align-items: center;
 	justify-content: center;
 	justify-content: center;
@@ -223,7 +256,7 @@ a[alt]:hover:after {
 }
 }
 .channel {
 .channel {
 	padding: 3px 8px;
 	padding: 3px 8px;
-	margin: 0 8px 2px 12px;
+	margin: 0 0 2px 12px;
 	min-height: 26px;
 	min-height: 26px;
 	border-radius: 4px;
 	border-radius: 4px;
 	display: flex;
 	display: flex;
@@ -520,11 +553,14 @@ legend {
 fieldset > div {
 fieldset > div {
 	margin: 10px 0;
 	margin: 10px 0;
 }
 }
-fieldset label,
+fieldset label:not(.radio-label),
 fieldset span {
 fieldset span {
 	display: inline-block;
 	display: inline-block;
 	min-width: 20%;
 	min-width: 20%;
 }
 }
+fieldset label.radio-label {
+	padding-right: 5px;
+}
 fieldset label div {
 fieldset label div {
 	padding-top: 30px;
 	padding-top: 30px;
 	padding-right: 10px;
 	padding-right: 10px;
@@ -548,10 +584,16 @@ fieldset textarea {
 	color: #2e3338;
 	color: #2e3338;
 	background-color: #ebedef;
 	background-color: #ebedef;
 }
 }
+#wb-settings-avatar-preview-img {
+	width: 128px;
+	height: 128px;
+	background: #32353b;
+}
 .wb-settings-display:first-of-type,
 .wb-settings-display:first-of-type,
 .wb-settings-permission:first-of-type {
 .wb-settings-permission:first-of-type {
 	display: inline-block;
 	display: inline-block;
 }
 }
+#wb-settings-avatar-preview-img,
 .wb-settings-display:not(:first-of-type),
 .wb-settings-display:not(:first-of-type),
 .wb-settings-permission:not(:first-of-type),
 .wb-settings-permission:not(:first-of-type),
 .wb-settings-additional-select,
 .wb-settings-additional-select,

+ 99 - 14
dashboard/src/index.js

@@ -39,7 +39,7 @@ for ( var b = 0; b < baseSelect.length; b++ ) {
 			} );
 			} );
 		}
 		}
 	}
 	}
-	if ( baseSelect[b].parentNode.querySelector('button.addmore') ) {
+	if ( baseSelect[b].parentElement.parentElement.querySelector('button.addmore') ) {
 		baseSelect[b].addEventListener( 'input', toggleOption );
 		baseSelect[b].addEventListener( 'input', toggleOption );
 		toggleOption.call(baseSelect[b]);
 		toggleOption.call(baseSelect[b]);
 	}
 	}
@@ -48,20 +48,45 @@ for ( var b = 0; b < baseSelect.length; b++ ) {
 /** @type {HTMLCollectionOf<HTMLButtonElement>} */
 /** @type {HTMLCollectionOf<HTMLButtonElement>} */
 var addmore = document.getElementsByClassName('addmore');
 var addmore = document.getElementsByClassName('addmore');
 for ( var j = 0; j < addmore.length; j++ ) {
 for ( var j = 0; j < addmore.length; j++ ) {
+	/** @this HTMLButtonElement */
 	addmore[j].onclick = function() {
 	addmore[j].onclick = function() {
-		/** @type {HTMLSelectElement} */
+		/** @type {HTMLDivElement} */
 		var clone = this.previousElementSibling.cloneNode(true);
 		var clone = this.previousElementSibling.cloneNode(true);
 		clone.classList.add('wb-settings-additional-select');
 		clone.classList.add('wb-settings-additional-select');
-		clone.removeAttribute('id');
-		clone.required = false;
-		clone.childNodes.forEach( function(child) {
+		if ( clone.firstElementChild.tagName === 'LABEL' ) clone.removeChild(clone.firstElementChild);
+		/** @type {HTMLSelectElement} */
+		var cloneSelect = clone.firstElementChild;
+		var newName = cloneSelect.name.replace( /^([a-z]+-)(\d)$/, function(fullname, base, id) {
+			return base + (+id + 1);
+		} );
+		cloneSelect.name = newName;
+		cloneSelect.removeAttribute('id');
+		cloneSelect.required = false;
+		cloneSelect.childNodes.forEach( function(child) {
 			child.hidden = false;
 			child.hidden = false;
 			child.selected = false;
 			child.selected = false;
+			child.defaultSelected = false;
 		} );
 		} );
-		clone.querySelector('option.defaultSelect').selected = true;
-		clone.addEventListener( 'input', toggleOption );
+		cloneSelect.querySelector('option.defaultSelect').defaultSelected = true;
+		cloneSelect.querySelector('option.defaultSelect').selected = true;
+		cloneSelect.addEventListener( 'input', toggleOption );
+		cloneSelect.name
+		cloneSelect.htmlFor
+		cloneSelect.id
+		if ( clone.children.length === 5 ) {
+			clone.children.item(1).name = newName + '-change';
+			clone.children.item(1).id = 'wb-settings-' + newName + '-add';
+			clone.children.item(1).checked = false;
+			clone.children.item(2).htmlFor = 'wb-settings-' + newName + '-add';
+			clone.children.item(3).name = newName + '-change';
+			clone.children.item(3).id = 'wb-settings-' + newName + '-remove';
+			clone.children.item(3).checked = false;
+			clone.children.item(4).htmlFor = 'wb-settings-' + newName + '-remove';
+			clone.children.item(1).defaultChecked = true;
+			clone.children.item(1).checked = true;
+		}
 		this.before(clone);
 		this.before(clone);
-		toggleOption.call(clone);
+		toggleOption.call(cloneSelect);
 	};
 	};
 }
 }
 
 
@@ -71,12 +96,13 @@ function toggleOption() {
 	var options = [];
 	var options = [];
 	/** @type {HTMLOptionElement[]} */
 	/** @type {HTMLOptionElement[]} */
 	var selected = [];
 	var selected = [];
-	var allSelect = this.parentNode.querySelectorAll('select');
+	var allSelect = this.parentElement.parentElement.querySelectorAll('select');
 	allSelect.forEach( function(select) {
 	allSelect.forEach( function(select) {
 		options.push(...select.options);
 		options.push(...select.options);
 		selected.push(...select.selectedOptions);
 		selected.push(...select.selectedOptions);
 	} );
 	} );
-	var button = this.parentNode.querySelector('button.addmore');
+	/** @type {HTMLButtonElement} */
+	var button = this.parentElement.parentElement.querySelector('button.addmore');
 	if ( selected.some( function(option) {
 	if ( selected.some( function(option) {
 		if ( option && option.value ) return false;
 		if ( option && option.value ) return false;
 		else return true;
 		else return true;
@@ -98,15 +124,17 @@ function toggleOption() {
 	} );
 	} );
 }
 }
 
 
+var divTemp = document.createElement('div');
+divTemp.innerHTML = '<input type="url" value="invalid">';
+const validationMessageInvalidURL = divTemp.firstChild.validationMessage;
+
 /** @type {HTMLInputElement} */
 /** @type {HTMLInputElement} */
 const wiki = document.getElementById('wb-settings-wiki');
 const wiki = document.getElementById('wb-settings-wiki');
 if ( wiki ) {
 if ( wiki ) {
 	wiki.addEventListener( 'input', function() {
 	wiki.addEventListener( 'input', function() {
 		if ( !/^(?:https?:)?\/\//.test(this.value) ) {
 		if ( !/^(?:https?:)?\/\//.test(this.value) ) {
 			if ( this.validity.valid ) {
 			if ( this.validity.valid ) {
-				var divTemp = document.createElement('div');
-				divTemp.innerHTML = '<input type="url" value="invalid">';
-				this.setCustomValidity(divTemp.firstChild.validationMessage);
+				this.setCustomValidity(validationMessageInvalidURL);
 			}
 			}
 		}
 		}
 		else this.setCustomValidity('');
 		else this.setCustomValidity('');
@@ -288,6 +316,58 @@ if ( wiki ) {
 	}
 	}
 }
 }
 
 
+/** @type {HTMLInputElement} */
+const avatar = document.getElementById('wb-settings-avatar');
+if ( avatar ) {
+	avatar.addEventListener( 'input', function() {
+		if ( !/^(?:https?:)?\/\//.test(this.value) ) {
+			if ( this.validity.valid ) {
+				this.setCustomValidity(validationMessageInvalidURL);
+			}
+		}
+		else this.setCustomValidity('');
+	} );
+	/** @type {HTMLButtonElement} */
+	const avatarbutton = document.getElementById('wb-settings-avatar-preview');
+	if ( avatarbutton ) {
+		const avatarpreview = document.createElement('img');
+		avatarpreview.id = 'wb-settings-avatar-preview-img';
+		avatarpreview.classList.add('avatar');
+		const validContentTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
+		avatarbutton.onclick = function() {
+			if ( !avatar.value ) return;
+			if ( !avatar.validity.valid ) return avatar.reportValidity();
+			if ( avatar.value === avatar.defaultValue ) {
+				avatarpreview.src = avatar.value;
+				avatarbutton.after(avatarpreview);
+				return;
+			}
+			fetch( avatar.value, {
+				method: 'HEAD',
+				referrer: ''
+			} ).then( function(response) {
+				if ( !validContentTypes.includes( response.headers.get('content-type') ) ) {
+					var invalidContentType = lang('avatar.content_type').replace( /\$1/g, response.headers.get('content-type') );
+					avatar.setCustomValidity(invalidContentType + '\n' + validContentTypes.join(', ') );
+					avatar.reportValidity();
+					return console.log( 'Invalid content type:', response.headers.get('content-type') );
+				}
+				avatarpreview.src = avatar.value;
+				avatarbutton.after(avatarpreview);
+				
+			}, function(error) {
+				console.log(error);
+				avatar.setCustomValidity(lang('avatar.invalid_url'));
+				avatar.reportValidity();
+			} );
+		};
+		if ( avatar.value ) {
+			avatarpreview.src = avatar.value;
+			avatarbutton.after(avatarpreview);
+		}
+	}
+}
+
 /** @type {HTMLInputElement} */
 /** @type {HTMLInputElement} */
 const logall = document.getElementById('wb-settings-flag_logall');
 const logall = document.getElementById('wb-settings-flag_logall');
 if ( logall ) {
 if ( logall ) {
@@ -410,6 +490,9 @@ if ( addRole && addRoleButton ) addRoleButton.onclick = function() {
 		newPermissionDiv0.lastElementChild.htmlFor = 'wb-settings-permission-' + addRole.value + '-0';
 		newPermissionDiv0.lastElementChild.htmlFor = 'wb-settings-permission-' + addRole.value + '-0';
 		newPermissionDiv1.lastElementChild.htmlFor = 'wb-settings-permission-' + addRole.value + '-1';
 		newPermissionDiv1.lastElementChild.htmlFor = 'wb-settings-permission-' + addRole.value + '-1';
 		newPermissionDiv2.lastElementChild.htmlFor = 'wb-settings-permission-' + addRole.value + '-default';
 		newPermissionDiv2.lastElementChild.htmlFor = 'wb-settings-permission-' + addRole.value + '-default';
+		newPermissionDiv0.lastElementChild.classList.add('wb-settings-permission-deny', 'radio-label');
+		newPermissionDiv1.lastElementChild.classList.add('wb-settings-permission-allow', 'radio-label');
+		newPermissionDiv2.lastElementChild.classList.add('wb-settings-permission-default', 'radio-label');
 		newPermissionDiv0.lastElementChild.textContent = i18nSlashPermission.deny;
 		newPermissionDiv0.lastElementChild.textContent = i18nSlashPermission.deny;
 		newPermissionDiv1.lastElementChild.textContent = i18nSlashPermission.allow;
 		newPermissionDiv1.lastElementChild.textContent = i18nSlashPermission.allow;
 		newPermissionDiv2.lastElementChild.textContent = i18nSlashPermission.default;
 		newPermissionDiv2.lastElementChild.textContent = i18nSlashPermission.default;
@@ -454,6 +537,7 @@ if ( textAreas.length ) {
 		var end = textArea.selectionEnd;
 		var end = textArea.selectionEnd;
 		var valueBefore = ( this.dataset?.before || this.innerText );
 		var valueBefore = ( this.dataset?.before || this.innerText );
 		var valueAfter = ( this.dataset?.after || '' );
 		var valueAfter = ( this.dataset?.after || '' );
+		if ( (textArea.textLength - (end - start)) + (valueBefore.length + valueAfter.length) > textArea.maxLength ) return document.getSelection().selectAllChildren(this);
 		if ( valueAfter ) {
 		if ( valueAfter ) {
 			textArea.value = textArea.value.substring(0, start) + valueBefore + textArea.value.substring(start, end) + valueAfter + textArea.value.substring(end);
 			textArea.value = textArea.value.substring(0, start) + valueBefore + textArea.value.substring(start, end) + valueAfter + textArea.value.substring(end);
 			textArea.selectionStart = start + valueBefore.length;
 			textArea.selectionStart = start + valueBefore.length;
@@ -478,6 +562,7 @@ if ( textAreas.length ) {
 		var end = this.selectionEnd;
 		var end = this.selectionEnd;
 		if ( this.value.substring(0, start).includes( '```' ) && this.value.substring(end).includes( '```' ) ) {
 		if ( this.value.substring(0, start).includes( '```' ) && this.value.substring(end).includes( '```' ) ) {
 			e.preventDefault();
 			e.preventDefault();
+			if ( this.textLength > this.maxLength ) return;
 			this.value = this.value.substring(0, start) + '\t' + this.value.substring(end);
 			this.value = this.value.substring(0, start) + '\t' + this.value.substring(end);
 			this.selectionStart = this.selectionEnd = start + 1;
 			this.selectionStart = this.selectionEnd = start + 1;
 		}
 		}
@@ -485,7 +570,7 @@ if ( textAreas.length ) {
 
 
 	/** @this HTMLTextAreaElement */
 	/** @this HTMLTextAreaElement */
 	function updateTextLength() {
 	function updateTextLength() {
-		this.labels.item(0).children.item(0).textContent = this.value.length + ' / ' + this.maxLength;
+		this.labels.item(0).children.item(0).textContent = this.textLength + ' / ' + this.maxLength;
 	}
 	}
 }
 }
 
 

+ 6 - 1
dashboard/src/lang.js

@@ -48,4 +48,9 @@ var langOptions = Object.keys(allLangs).map( function(lang) {
 langDropdown.append(...langOptions);
 langDropdown.append(...langOptions);
 langSelector.append(langDropdown);
 langSelector.append(langDropdown);
 channellist.after(langSelector);
 channellist.after(langSelector);
-channellist.setAttribute('style', 'bottom: 32px;');
+channellist.setAttribute('style', 'bottom: 32px;');
+var selectedChannel = channellist.querySelector('.channel.selected');
+if ( selectedChannel ) {
+	var selectedChannelOffset = channellist.offsetHeight - selectedChannel.offsetTop;
+	if ( selectedChannelOffset < 64 ) channellist.scrollBy(0, 64 - selectedChannelOffset);
+}

+ 4 - 4
dashboard/util.js

@@ -309,11 +309,11 @@ function createNotice($, notice, dashboardLang, args = []) {
 				note = $('<div>').text(dashboardLang.get('notice.savefail.note_' + args[0]));
 				note = $('<div>').text(dashboardLang.get('notice.savefail.note_' + args[0]));
 			}
 			}
 			break;
 			break;
-		case 'movefail':
+		case 'webhookfail':
 			type = 'info';
 			type = 'info';
-			title.text(dashboardLang.get('notice.movefail.title'));
-			text.text(dashboardLang.get('notice.movefail.text'));
-			note = $('<div>').text(dashboardLang.get('notice.movefail.note'));
+			title.text(dashboardLang.get('notice.webhookfail.title'));
+			text.text(dashboardLang.get('notice.webhookfail.text'));
+			note = $('<div>').text(dashboardLang.get('notice.webhookfail.note'));
 			break;
 			break;
 		case 'refreshfail':
 		case 'refreshfail':
 			type = 'error';
 			type = 'error';

+ 123 - 70
dashboard/verification.js

@@ -4,11 +4,19 @@ const {got, db, slashCommands, sendMsg, createNotice, escapeText, hasPerm} = req
 const slashCommand = slashCommands.find( slashCommand => slashCommand.name === 'verify' );
 const slashCommand = slashCommands.find( slashCommand => slashCommand.name === 'verify' );
 
 
 const fieldset = {
 const fieldset = {
-	channel: '<label for="wb-settings-channel">Channel:</label>'
-	+ '<select id="wb-settings-channel" name="channel" required></select>'
+	channel: '<div>'
+	+ '<label for="wb-settings-channel">Channel:</label>'
+	+ '<select id="wb-settings-channel" name="channel-0" required></select>'
+	+ '</div>'
 	+ '<button type="button" id="wb-settings-channel-more" class="addmore">Add more</button>',
 	+ '<button type="button" id="wb-settings-channel-more" class="addmore">Add more</button>',
-	role: '<label for="wb-settings-role">Role:</label>'
-	+ '<select id="wb-settings-role" name="role" required></select>'
+	role: '<div>'
+	+ '<label for="wb-settings-role">Role:</label>'
+	+ '<select id="wb-settings-role" name="role-0" required></select>'
+	+ '<input type="radio" id="wb-settings-role-0-add" name="role-0-change" value="+">'
+	+ '<label for="wb-settings-role-0-add" class="radio-label">Add</label>'
+	+ '<input type="radio" id="wb-settings-role-0-remove" name="role-0-change" value="-">'
+	+ '<label for="wb-settings-role-0-remove" class="radio-label">Remove</label>'
+	+ '</div>'
 	+ '<button type="button" id="wb-settings-role-more" class="addmore">Add more</button>',
 	+ '<button type="button" id="wb-settings-role-more" class="addmore">Add more</button>',
 	usergroup: '<label for="wb-settings-usergroup">Wiki user group:</label>'
 	usergroup: '<label for="wb-settings-usergroup">Wiki user group:</label>'
 	+ '<input type="text" id="wb-settings-usergroup" name="usergroup" list="wb-settings-usergroup-list" autocomplete="on">'
 	+ '<input type="text" id="wb-settings-usergroup" name="usergroup" list="wb-settings-usergroup-list" autocomplete="on">'
@@ -32,13 +40,13 @@ const fieldset = {
 	+ '</div><div class="wb-settings-postcount">'
 	+ '</div><div class="wb-settings-postcount">'
 	+ '<span>Only Fandom wikis:</span>'
 	+ '<span>Only Fandom wikis:</span>'
 	+ '<input type="radio" id="wb-settings-postcount-and" name="posteditcount" value="and" required>'
 	+ '<input type="radio" id="wb-settings-postcount-and" name="posteditcount" value="and" required>'
-	+ '<label for="wb-settings-postcount-and">Require both edit and post count.</label>'
+	+ '<label for="wb-settings-postcount-and" class="radio-label">Require both edit and post count.</label>'
 	+ '</div><div class="wb-settings-postcount">'
 	+ '</div><div class="wb-settings-postcount">'
 	+ '<input type="radio" id="wb-settings-postcount-or" name="posteditcount" value="or" required>'
 	+ '<input type="radio" id="wb-settings-postcount-or" name="posteditcount" value="or" required>'
-	+ '<label for="wb-settings-postcount-or">Require either edit or post count.</label>'
+	+ '<label for="wb-settings-postcount-or" class="radio-label">Require either edit or post count.</label>'
 	+ '</div><div class="wb-settings-postcount">'
 	+ '</div><div class="wb-settings-postcount">'
 	+ '<input type="radio" id="wb-settings-postcount-both" name="posteditcount" value="both" required>'
 	+ '<input type="radio" id="wb-settings-postcount-both" name="posteditcount" value="both" required>'
-	+ '<label for="wb-settings-postcount-both">Require combined edit and post count.</label>'
+	+ '<label for="wb-settings-postcount-both" class="radio-label">Require combined edit and post count.</label>'
 	+ '</div>',
 	+ '</div>',
 	accountage: '<label for="wb-settings-accountage">Account age (in days):</label>'
 	accountage: '<label for="wb-settings-accountage">Account age (in days):</label>'
 	+ '<input type="number" id="wb-settings-accountage" name="accountage" min="0" max="1000000" required>',
 	+ '<input type="number" id="wb-settings-accountage" name="accountage" min="0" max="1000000" required>',
@@ -101,13 +109,14 @@ function createForm($, header, dashboardLang, settings, guildChannels, guildRole
 				return $(`<option class="wb-settings-channel-${guildChannel}">`).val(guildChannel).text(`${guildChannel} – #UNKNOWN`).addClass('wb-settings-error');
 				return $(`<option class="wb-settings-channel-${guildChannel}">`).val(guildChannel).text(`${guildChannel} – #UNKNOWN`).addClass('wb-settings-error');
 			} )
 			} )
 		);
 		);
-		if ( settingsChannels.length > 1 ) channel.find('#wb-settings-channel').after(
-			...settingsChannels.slice(1).map( guildChannel => {
+		if ( settingsChannels.length > 1 ) channel.find('div').after(
+			...settingsChannels.slice(1).map( (guildChannel, i) => {
 				var additionalChannel = channel.find('#wb-settings-channel').clone();
 				var additionalChannel = channel.find('#wb-settings-channel').clone();
-				additionalChannel.addClass('wb-settings-additional-select');
 				additionalChannel.find(`.wb-settings-channel-default`).removeAttr('hidden');
 				additionalChannel.find(`.wb-settings-channel-default`).removeAttr('hidden');
 				additionalChannel.find(`.wb-settings-channel-${guildChannel}`).attr('selected', '');
 				additionalChannel.find(`.wb-settings-channel-${guildChannel}`).attr('selected', '');
-				return additionalChannel.removeAttr('id').removeAttr('required');
+				additionalChannel.removeAttr('id').removeAttr('required');
+				additionalChannel.attr('name', 'channel-' + (i + 1));
+				return $('<div>').addClass('wb-settings-additional-select').append(additionalChannel);
 			} )
 			} )
 		);
 		);
 		channel.find(`#wb-settings-channel .wb-settings-channel-${settingsChannels[0]}`).attr('selected', '');
 		channel.find(`#wb-settings-channel .wb-settings-channel-${settingsChannels[0]}`).attr('selected', '');
@@ -118,11 +127,13 @@ function createForm($, header, dashboardLang, settings, guildChannels, guildRole
 	}
 	}
 	fields.push(channel);
 	fields.push(channel);
 	let role = $('<div>').append(fieldset.role);
 	let role = $('<div>').append(fieldset.role);
-	role.find('label').text(dashboardLang.get('verification.form.role'));
+	role.find('label').eq(0).text(dashboardLang.get('verification.form.role'));
+	role.find('label').eq(1).text(dashboardLang.get('verification.form.role_add'));
+	role.find('label').eq(2).text(dashboardLang.get('verification.form.role_remove'));
 	role.find('#wb-settings-role').append(
 	role.find('#wb-settings-role').append(
 		$('<option class="wb-settings-role-default defaultSelect" hidden>').val('').text(dashboardLang.get('verification.form.select_role')),
 		$('<option class="wb-settings-role-default defaultSelect" hidden>').val('').text(dashboardLang.get('verification.form.select_role')),
 		...guildRoles.filter( guildRole => {
 		...guildRoles.filter( guildRole => {
-			return guildRole.lower || settings.role.split('|').includes( guildRole.id );
+			return guildRole.lower || settings.role.replace( /-/g, '' ).split('|').includes( guildRole.id );
 		} ).map( guildRole => {
 		} ).map( guildRole => {
 			var optionRole = $(`<option class="wb-settings-role-${guildRole.id}">`).val(guildRole.id);
 			var optionRole = $(`<option class="wb-settings-role-${guildRole.id}">`).val(guildRole.id);
 			if ( !guildRole.lower ) optionRole.addClass('wb-settings-error');
 			if ( !guildRole.lower ) optionRole.addClass('wb-settings-error');
@@ -130,30 +141,44 @@ function createForm($, header, dashboardLang, settings, guildChannels, guildRole
 		} )
 		} )
 	);
 	);
 	if ( settings.role ) {
 	if ( settings.role ) {
-		let settingsRoles = settings.role.split('|');
+		let settingsRoles = settings.role.split('|').map( guildRole => {
+			if ( !guildRole.startsWith( '-' ) ) return {id: guildRole, suffix: 'add'};
+			return {id: guildRole.replace( '-', '' ), suffix: 'remove'};
+		} );
 		role.find('#wb-settings-role').append(
 		role.find('#wb-settings-role').append(
 			...settingsRoles.filter( guildRole => {
 			...settingsRoles.filter( guildRole => {
-				return !role.find(`.wb-settings-role-${guildRole}`).length;
+				return !role.find(`.wb-settings-role-${guildRole.id}`).length;
 			} ).map( guildRole => {
 			} ).map( guildRole => {
-				return $(`<option class="wb-settings-role-${guildRole}">`).val(guildRole).text(`${guildRole} – @UNKNOWN`).addClass('wb-settings-error');
+				return $(`<option class="wb-settings-role-${guildRole.id}">`).val(guildRole.id).text(`${guildRole.id} – @UNKNOWN`).addClass('wb-settings-error');
 			} )
 			} )
 		);
 		);
-		if ( settingsRoles.length > 1 ) role.find('#wb-settings-role').after(
-			...settingsRoles.slice(1).map( guildRole => {
-				var additionalRole = role.find('#wb-settings-role').clone();
-				additionalRole.addClass('wb-settings-additional-select');
+		if ( settingsRoles.length > 1 ) role.find('div').after(
+			...settingsRoles.slice(1).map( (guildRole, i) => {
+				var id = i + 1;
+				var additionalDiv = role.find('div').clone();
+				additionalDiv.find('label').eq(0).remove();
+				var additionalRole = additionalDiv.find('#wb-settings-role');
 				additionalRole.find(`.wb-settings-role-default`).removeAttr('hidden');
 				additionalRole.find(`.wb-settings-role-default`).removeAttr('hidden');
-				additionalRole.find(`.wb-settings-role-${guildRole}`).attr('selected', '');
-				return additionalRole.removeAttr('id').removeAttr('required');
+				additionalRole.find(`.wb-settings-role-${guildRole.id}`).attr('selected', '');
+				additionalRole.removeAttr('id').removeAttr('required').attr('name', 'role-' + id);
+				additionalDiv.find('input').attr('name', 'role-' + id + '-change');
+				additionalDiv.find('input').eq(0).attr('id', 'wb-settings-role-' + id + '-add');
+				additionalDiv.find('label').eq(0).attr('for', 'wb-settings-role-' + id + '-add');
+				additionalDiv.find('input').eq(1).attr('id', 'wb-settings-role-' + id + '-remove');
+				additionalDiv.find('label').eq(1).attr('for', 'wb-settings-role-' + id + '-remove');
+				additionalDiv.find(`#wb-settings-role-${id}-${guildRole.suffix}`).attr('checked', '');
+				return additionalDiv.addClass('wb-settings-additional-select');
 			} )
 			} )
 		);
 		);
-		role.find(`#wb-settings-role .wb-settings-role-${settingsRoles[0]}`).attr('selected', '');
+		role.find(`#wb-settings-role .wb-settings-role-${settingsRoles[0].id}`).attr('selected', '');
+		role.find(`#wb-settings-role-0-${settingsRoles[0].suffix}`).attr('checked', '');
 	}
 	}
 	else {
 	else {
 		if ( role.find(`.wb-settings-role-${settings.defaultrole}`).length ) {
 		if ( role.find(`.wb-settings-role-${settings.defaultrole}`).length ) {
 			role.find(`.wb-settings-role-${settings.defaultrole}`).attr('selected', '');
 			role.find(`.wb-settings-role-${settings.defaultrole}`).attr('selected', '');
 		}
 		}
 		else role.find('.wb-settings-role-default').attr('selected', '');
 		else role.find('.wb-settings-role-default').attr('selected', '');
+		role.find('#wb-settings-role-0-add').attr('checked', '');
 		role.find('button.addmore').attr('hidden', '');
 		role.find('button.addmore').attr('hidden', '');
 	}
 	}
 	fields.push(role);
 	fields.push(role);
@@ -206,7 +231,7 @@ function createForm($, header, dashboardLang, settings, guildChannels, guildRole
 	var form = $('<fieldset>').append(...fields);
 	var form = $('<fieldset>').append(...fields);
 	if ( readonly ) {
 	if ( readonly ) {
 		form.find('input').attr('readonly', '');
 		form.find('input').attr('readonly', '');
-		form.find('input[type="checkbox"], option, optgroup').attr('disabled', '');
+		form.find('input[type="checkbox"], input[type="radio"]:not(:checked), option, optgroup').attr('disabled', '');
 		form.find('input[type="submit"], button.addmore').remove();
 		form.find('input[type="submit"], button.addmore').remove();
 	}
 	}
 	form.find('button.addmore').text(dashboardLang.get('verification.form.more'));
 	form.find('button.addmore').text(dashboardLang.get('verification.form.more'));
@@ -255,7 +280,7 @@ function dashboard_verification(res, $, guild, args, dashboardLang) {
 		$('#channellist #verification').after(
 		$('#channellist #verification').after(
 			...rows.map( row => {
 			...rows.map( row => {
 				let text = `${row.configid} - ${( guild.roles.find( role => {
 				let text = `${row.configid} - ${( guild.roles.find( role => {
-					return role.id === row.role.split('|')[0];
+					return role.id === row.role.replace( /-/g, '' ).split('|')[0];
 				} )?.name || guild.channels.find( channel => {
 				} )?.name || guild.channels.find( channel => {
 					return channel.id === row.channel.split('|')[1];
 					return channel.id === row.channel.split('|')[1];
 				} )?.name || row.usergroup.split('|')[( row.usergroup.startsWith('AND|') ? 1 : 0 )] )}`;
 				} )?.name || row.usergroup.split('|')[( row.usergroup.startsWith('AND|') ? 1 : 0 )] )}`;
@@ -391,8 +416,6 @@ function dashboard_verification(res, $, guild, args, dashboardLang) {
  * @param {String} guild - The id of the guild
  * @param {String} guild - The id of the guild
  * @param {String|Number} type - The setting to change
  * @param {String|Number} type - The setting to change
  * @param {Object} settings - The new settings
  * @param {Object} settings - The new settings
- * @param {String[]} settings.channel
- * @param {String[]} settings.role
  * @param {String[]} [settings.usergroup]
  * @param {String[]} [settings.usergroup]
  * @param {String} [settings.usergroup_and]
  * @param {String} [settings.usergroup_and]
  * @param {Number} settings.editcount
  * @param {Number} settings.editcount
@@ -411,8 +434,20 @@ function update_verification(res, userSettings, guild, type, settings) {
 	if ( !settings.save_settings === !settings.delete_settings ) {
 	if ( !settings.save_settings === !settings.delete_settings ) {
 		return res(`/guild/${guild}/verification/${type}`, 'savefail');
 		return res(`/guild/${guild}/verification/${type}`, 'savefail');
 	}
 	}
+	/** @type {String[]} */
+	var channels = [];
+	/** @type {{id: String, prefix: String}[]} */
+	var roles = [];
 	if ( settings.save_settings ) {
 	if ( settings.save_settings ) {
-		if ( !/^[\d|]+ [\d|]+$/.test(`${settings.channel} ${settings.role}`) ) {
+		channels = Object.keys(settings).filter( channel => {
+			return /^channel-\d$/.test(channel) && /^\d+$/.test(settings[channel]);
+		} ).map( channel => settings[channel] );
+		roles = Object.keys(settings).filter( role => {
+			return /^role-\d$/.test(role) && /^\d+$/.test(settings[role]);
+		} ).map( role => {
+			return {id: settings[role], prefix: ( settings[role + '-change'] === '-' ? '-' : '' )};
+		} );
+		if ( !channels.length || !roles.length ) {
 			return res(`/guild/${guild}/verification/${type}`, 'savefail');
 			return res(`/guild/${guild}/verification/${type}`, 'savefail');
 		}
 		}
 		if ( !/^\d+ \d+$/.test(`${settings.editcount} ${settings.accountage}`) ) {
 		if ( !/^\d+ \d+$/.test(`${settings.editcount} ${settings.accountage}`) ) {
@@ -421,18 +456,9 @@ function update_verification(res, userSettings, guild, type, settings) {
 		if ( !( ['and','or','both'].includes( settings.posteditcount ) && ( /^\d+$/.test(settings.postcount) || settings.posteditcount === 'both' ) ) ) {
 		if ( !( ['and','or','both'].includes( settings.posteditcount ) && ( /^\d+$/.test(settings.postcount) || settings.posteditcount === 'both' ) ) ) {
 			return res(`/guild/${guild}/verification/${type}`, 'savefail');
 			return res(`/guild/${guild}/verification/${type}`, 'savefail');
 		}
 		}
-		settings.channel = settings.channel.split('|').filter( (channel, i, self) => {
-			return ( channel.length && self.indexOf(channel) === i );
-		} );
-		if ( !settings.channel.length || settings.channel.length > 10 ) {
-			return res(`/guild/${guild}/verification/${type}`, 'savefail');
-		}
-		settings.role = settings.role.split('|').filter( (role, i, self) => {
-			return ( role.length && self.indexOf(role) === i );
+		channels = channels.filter( (channel, i, self) => {
+			return self.indexOf(channel) === i;
 		} );
 		} );
-		if ( !settings.role.length || settings.role.length > 10 ) {
-			return res(`/guild/${guild}/verification/${type}`, 'savefail');
-		}
 		if ( !settings.usergroup ) settings.usergroup = 'user';
 		if ( !settings.usergroup ) settings.usergroup = 'user';
 		settings.usergroup = settings.usergroup.replace( /_/g, ' ' ).trim().toLowerCase();
 		settings.usergroup = settings.usergroup.replace( /_/g, ' ' ).trim().toLowerCase();
 		settings.usergroup = settings.usergroup.split(/\s*[,|]\s*/).map( usergroup => {
 		settings.usergroup = settings.usergroup.split(/\s*[,|]\s*/).map( usergroup => {
@@ -458,13 +484,13 @@ function update_verification(res, userSettings, guild, type, settings) {
 		}
 		}
 		if ( type === 'new' ) {
 		if ( type === 'new' ) {
 			let curGuild = userSettings.guilds.isMember.get(guild);
 			let curGuild = userSettings.guilds.isMember.get(guild);
-			if ( settings.channel.some( channel => {
+			if ( channels.some( channel => {
 				return !curGuild.channels.some( guildChannel => {
 				return !curGuild.channels.some( guildChannel => {
 					return ( guildChannel.id === channel && !guildChannel.isCategory );
 					return ( guildChannel.id === channel && !guildChannel.isCategory );
 				} );
 				} );
-			} ) || settings.role.some( role => {
+			} ) || roles.some( role => {
 				return !curGuild.roles.some( guildRole => {
 				return !curGuild.roles.some( guildRole => {
-					return ( guildRole.id === role && guildRole.lower );
+					return ( guildRole.id === role.id && guildRole.lower );
 				} );
 				} );
 			} ) ) return res(`/guild/${guild}/verification/new`, 'savefail');
 			} ) ) return res(`/guild/${guild}/verification/new`, 'savefail');
 		}
 		}
@@ -517,7 +543,12 @@ function update_verification(res, userSettings, guild, type, settings) {
 				var text = lang.get('verification.dashboard.removed', `<@${userSettings.user.id}>`, type);
 				var text = lang.get('verification.dashboard.removed', `<@${userSettings.user.id}>`, type);
 				if ( row ) {
 				if ( row ) {
 					text += '\n' + lang.get('verification.channel') + ' <#' + row.channel.split('|').filter( channel => channel.length ).join('>, <#') + '>';
 					text += '\n' + lang.get('verification.channel') + ' <#' + row.channel.split('|').filter( channel => channel.length ).join('>, <#') + '>';
-					text += '\n' + lang.get('verification.role') + ' <@&' + row.role.split('|').join('>, <@&') + '>';
+					let rolesRow = [
+						row.role.split('|').filter( role => !role.startsWith( '-' ) ),
+						row.role.split('|').filter( role => role.startsWith( '-' ) ).map( role => role.replace( '-', '' ) )
+					];
+					if ( rolesRow[0].length ) text += '\n' + lang.get('verification.role_add') + ' <@&' + rolesRow[0].join('>, <@&') + '>';
+					if ( rolesRow[1].length ) text += '\n' + lang.get('verification.role_remove') + ' <@&' + rolesRow[1].join('>, <@&') + '>';
 					if ( row.postcount === null ) {
 					if ( row.postcount === null ) {
 						text += '\n' + lang.get('verification.posteditcount') + ' `' + row.editcount + '`';
 						text += '\n' + lang.get('verification.posteditcount') + ' `' + row.editcount + '`';
 					}
 					}
@@ -586,7 +617,7 @@ function update_verification(res, userSettings, guild, type, settings) {
 					if ( configid === i ) configid++;
 					if ( configid === i ) configid++;
 					else break;
 					else break;
 				}
 				}
-				db.query( 'INSERT INTO verification(guild, configid, channel, role, editcount, postcount, usergroup, accountage, rename) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)', [guild, configid, '|' + settings.channel.join('|') + '|', settings.role.join('|'), settings.editcount, settings.postcount, settings.usergroup.join('|'), settings.accountage, ( settings.rename ? 1 : 0 )] ).then( () => {
+				db.query( 'INSERT INTO verification(guild, configid, channel, role, editcount, postcount, usergroup, accountage, rename) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)', [guild, configid, '|' + channels.join('|') + '|', roles.map( role => role.prefix + role.id ).join('|'), settings.editcount, settings.postcount, settings.usergroup.join('|'), settings.accountage, ( settings.rename ? 1 : 0 )] ).then( () => {
 					console.log( `- Dashboard: Verification successfully added: ${guild}#${configid}` );
 					console.log( `- Dashboard: Verification successfully added: ${guild}#${configid}` );
 					res(`/guild/${guild}/verification/${configid}`, 'save');
 					res(`/guild/${guild}/verification/${configid}`, 'save');
 					if ( !row.count.length && slashCommand?.id ) got.put( 'https://discord.com/api/v8/applications/' + process.env.bot + '/guilds/' + guild + '/commands/' + slashCommand.id + '/permissions', {
 					if ( !row.count.length && slashCommand?.id ) got.put( 'https://discord.com/api/v8/applications/' + process.env.bot + '/guilds/' + guild + '/commands/' + slashCommand.id + '/permissions', {
@@ -614,8 +645,13 @@ function update_verification(res, userSettings, guild, type, settings) {
 					} );
 					} );
 					var lang = new Lang(row.lang);
 					var lang = new Lang(row.lang);
 					var text = lang.get('verification.dashboard.added', `<@${userSettings.user.id}>`, configid);
 					var text = lang.get('verification.dashboard.added', `<@${userSettings.user.id}>`, configid);
-					text += '\n' + lang.get('verification.channel') + ' <#' + settings.channel.join('>, <#') + '>';
-					text += '\n' + lang.get('verification.role') + ' <@&' + settings.role.join('>, <@&') + '>';
+					text += '\n' + lang.get('verification.channel') + ' <#' + channels.join('>, <#') + '>';
+					let rolesRow = [
+						roles.filter( role => !role.prefix ).map( role => '<@&' + role.id + '>' ),
+						roles.filter( role => role.prefix ).map( role => '<@&' + role.id + '>' )
+					];
+					if ( rolesRow[0].length ) text += '\n' + lang.get('verification.role_add') + ' ' + rolesRow[0].join(', ');
+					if ( rolesRow[1].length ) text += '\n' + lang.get('verification.role_remove') + ' ' + rolesRow[1].join(', ');
 					if ( settings.postcount === null ) {
 					if ( settings.postcount === null ) {
 						text += '\n' + lang.get('verification.posteditcount') + ' `' + settings.editcount + '`';
 						text += '\n' + lang.get('verification.posteditcount') + ' `' + settings.editcount + '`';
 					}
 					}
@@ -631,22 +667,22 @@ function update_verification(res, userSettings, guild, type, settings) {
 					if ( settings.rename && !hasPerm(response.botPermissions, 'MANAGE_NICKNAMES') ) {
 					if ( settings.rename && !hasPerm(response.botPermissions, 'MANAGE_NICKNAMES') ) {
 						text += '\n\n' + lang.get('verification.rename_no_permission', `<@${process.env.bot}>`);
 						text += '\n\n' + lang.get('verification.rename_no_permission', `<@${process.env.bot}>`);
 					}
 					}
-					if ( settings.role.some( role => {
+					if ( roles.some( role => {
 						return !userSettings.guilds.isMember.get(guild).roles.some( guildRole => {
 						return !userSettings.guilds.isMember.get(guild).roles.some( guildRole => {
-							return ( guildRole.id === role && guildRole.lower );
+							return ( guildRole.id === role.id && guildRole.lower );
 						} );
 						} );
 					} ) ) {
 					} ) ) {
 						text += '\n';
 						text += '\n';
-						settings.role.forEach( role => {
+						roles.forEach( role => {
 							if ( !userSettings.guilds.isMember.get(guild).roles.some( guildRole => {
 							if ( !userSettings.guilds.isMember.get(guild).roles.some( guildRole => {
-								return ( guildRole.id === role );
+								return ( guildRole.id === role.id );
 							} ) ) {
 							} ) ) {
-								text += '\n' + lang.get('verification.role_deleted', `<@&${role}>`);
+								text += '\n' + lang.get('verification.role_deleted', `<@&${role.id}>`);
 							}
 							}
 							else if ( userSettings.guilds.isMember.get(guild).roles.some( guildRole => {
 							else if ( userSettings.guilds.isMember.get(guild).roles.some( guildRole => {
-								return ( guildRole.id === role && !guildRole.lower );
+								return ( guildRole.id === role.id && !guildRole.lower );
 							} ) ) {
 							} ) ) {
-								text += '\n' + lang.get('verification.role_too_high', `<@&${role}>`, `<@${process.env.bot}>`);
+								text += '\n' + lang.get('verification.role_too_high', `<@&${role.id}>`, `<@${process.env.bot}>`);
 							}
 							}
 						} );
 						} );
 					}
 					}
@@ -667,9 +703,15 @@ function update_verification(res, userSettings, guild, type, settings) {
 		return db.query( 'SELECT wiki, lang, verification.channel, verification.role, editcount, postcount, usergroup, accountage, rename FROM discord LEFT JOIN verification ON discord.guild = verification.guild AND verification.configid = $1 WHERE discord.guild = $2 AND discord.channel IS NULL', [type, guild] ).then( ({rows:[row]}) => {
 		return db.query( 'SELECT wiki, lang, verification.channel, verification.role, editcount, postcount, usergroup, accountage, rename FROM discord LEFT JOIN verification ON discord.guild = verification.guild AND verification.configid = $1 WHERE discord.guild = $2 AND discord.channel IS NULL', [type, guild] ).then( ({rows:[row]}) => {
 			if ( !row?.channel ) return res(`/guild/${guild}/verification`, 'savefail');
 			if ( !row?.channel ) return res(`/guild/${guild}/verification`, 'savefail');
 			row.channel = row.channel.split('|').filter( channel => channel.length );
 			row.channel = row.channel.split('|').filter( channel => channel.length );
-			var newChannel = settings.channel.filter( channel => !row.channel.includes( channel ) );
-			row.role = row.role.split('|');
-			var newRole = settings.role.filter( role => !row.role.includes( role ) );
+			var newChannel = channels.filter( channel => !row.channel.includes( channel ) );
+			/** @type {String[][]} */
+			var rolesRow = [
+				row.role.split('|').filter( role => !role.startsWith( '-' ) ),
+				row.role.split('|').filter( role => role.startsWith( '-' ) ).map( role => role.replace( '-', '' ) )
+			];
+			var newRole = roles.filter( role => {
+				return !rolesRow[0].includes( role.id ) && !rolesRow[1].includes( role.id );
+			} );
 			row.usergroup = row.usergroup.split('|');
 			row.usergroup = row.usergroup.split('|');
 			var newUsergroup = settings.usergroup.filter( group => !row.usergroup.includes( group ) );
 			var newUsergroup = settings.usergroup.filter( group => !row.usergroup.includes( group ) );
 			if ( newChannel.length || newRole.length ) {
 			if ( newChannel.length || newRole.length ) {
@@ -680,7 +722,7 @@ function update_verification(res, userSettings, guild, type, settings) {
 					} );
 					} );
 				} ) || newRole.some( role => {
 				} ) || newRole.some( role => {
 					return !curGuild.roles.some( guildRole => {
 					return !curGuild.roles.some( guildRole => {
-						return ( guildRole.id === role && guildRole.lower );
+						return ( guildRole.id === role.id && guildRole.lower );
 					} );
 					} );
 				} ) ) return res(`/guild/${guild}/verification/${type}`, 'savefail');
 				} ) ) return res(`/guild/${guild}/verification/${type}`, 'savefail');
 			}
 			}
@@ -717,14 +759,25 @@ function update_verification(res, userSettings, guild, type, settings) {
 				var lang = new Lang(row.lang);
 				var lang = new Lang(row.lang);
 				var diff = [];
 				var diff = [];
 				if ( newChannel.length || row.channel.some( channel => {
 				if ( newChannel.length || row.channel.some( channel => {
-					return !settings.channel.includes( channel );
+					return !channels.includes( channel );
+				} ) ) {
+					diff.push(lang.get('verification.channel') + ` ~~<#${row.channel.join('>, <#')}>~~ → <#${channels.join('>, <#')}>`);
+				}
+				if ( roles.some( role => {
+					if ( role.prefix ) return false;
+					return !rolesRow[0].includes( role.id );
+				} ) || rolesRow[0].some( roleid => {
+					return !roles.some( role => !role.prefix && role.id === roleid );
 				} ) ) {
 				} ) ) {
-					diff.push(lang.get('verification.channel') + ` ~~<#${row.channel.join('>, <#')}>~~ → <#${settings.channel.join('>, <#')}>`);
+					diff.push(lang.get('verification.role_add') + ' ~~' + ( rolesRow[0].length ? '<@&' + rolesRow[0].join('>, <@&') + '>' : '*`' + lang.get('verification.role_none') + '`*' ) + '~~ → ' + ( roles.some( role => !role.prefix ) ? roles.filter( role => !role.prefix ).map( role => '<@&' + role.id + '>' ).join(', ') : '*`' + lang.get('verification.role_none') + '`*' ));
 				}
 				}
-				if ( newRole.length || row.role.some( role => {
-					return !settings.role.includes( role );
+				if ( roles.some( role => {
+					if ( !role.prefix ) return false;
+					return !rolesRow[1].includes( role.id );
+				} ) || rolesRow[1].some( roleid => {
+					return !roles.some( role => role.prefix && role.id === roleid );
 				} ) ) {
 				} ) ) {
-					diff.push(lang.get('verification.role') + ` ~~<@&${row.role.join('>, <@&')}>~~ → <@&${settings.role.join('>, <@&')}>`);
+					diff.push(lang.get('verification.role_remove') + ' ~~' + ( rolesRow[1].length ? '<@&' + rolesRow[1].join('>, <@&') + '>' : '*`' + lang.get('verification.role_none') + '`*' ) + '~~ → ' + ( roles.some( role => role.prefix ) ? roles.filter( role => role.prefix ).map( role => '<@&' + role.id + '>' ).join(', ') : '*`' + lang.get('verification.role_none') + '`*' ));
 				}
 				}
 				if ( row.postcount !== settings.postcount && ( row.postcount === null || settings.postcount === null ) ) {
 				if ( row.postcount !== settings.postcount && ( row.postcount === null || settings.postcount === null ) ) {
 					if ( row.postcount === null ) {
 					if ( row.postcount === null ) {
@@ -762,7 +815,7 @@ function update_verification(res, userSettings, guild, type, settings) {
 					diff.push(lang.get('verification.rename') + ` ~~*\`${lang.get('verification.' + ( row.rename ? 'enabled' : 'disabled'))}\`*~~ → *\`${lang.get('verification.' + ( settings.rename ? 'enabled' : 'disabled'))}\`*`);
 					diff.push(lang.get('verification.rename') + ` ~~*\`${lang.get('verification.' + ( row.rename ? 'enabled' : 'disabled'))}\`*~~ → *\`${lang.get('verification.' + ( settings.rename ? 'enabled' : 'disabled'))}\`*`);
 				}
 				}
 				if ( !diff.length ) return res(`/guild/${guild}/verification/${type}`, 'save');
 				if ( !diff.length ) return res(`/guild/${guild}/verification/${type}`, 'save');
-				db.query( 'UPDATE verification SET channel = $1, role = $2, editcount = $3, postcount = $4, usergroup = $5, accountage = $6, rename = $7 WHERE guild = $8 AND configid = $9', ['|' + settings.channel.join('|') + '|', settings.role.join('|'), settings.editcount, settings.postcount, settings.usergroup.join('|'), settings.accountage, ( settings.rename ? 1 : 0 ), guild, type] ).then( () => {
+				db.query( 'UPDATE verification SET channel = $1, role = $2, editcount = $3, postcount = $4, usergroup = $5, accountage = $6, rename = $7 WHERE guild = $8 AND configid = $9', ['|' + channels.join('|') + '|', roles.map( role => role.prefix + role.id ).join('|'), settings.editcount, settings.postcount, settings.usergroup.join('|'), settings.accountage, ( settings.rename ? 1 : 0 ), guild, type] ).then( () => {
 					console.log( `- Dashboard: Verification successfully updated: ${guild}#${type}` );
 					console.log( `- Dashboard: Verification successfully updated: ${guild}#${type}` );
 					res(`/guild/${guild}/verification/${type}`, 'save');
 					res(`/guild/${guild}/verification/${type}`, 'save');
 					var text = lang.get('verification.dashboard.updated', `<@${userSettings.user.id}>`, type);
 					var text = lang.get('verification.dashboard.updated', `<@${userSettings.user.id}>`, type);
@@ -771,22 +824,22 @@ function update_verification(res, userSettings, guild, type, settings) {
 					if ( settings.rename && !hasPerm(response.botPermissions, 'MANAGE_NICKNAMES') ) {
 					if ( settings.rename && !hasPerm(response.botPermissions, 'MANAGE_NICKNAMES') ) {
 						text += '\n\n' + lang.get('verification.rename_no_permission', `<@${process.env.bot}>`);
 						text += '\n\n' + lang.get('verification.rename_no_permission', `<@${process.env.bot}>`);
 					}
 					}
-					if ( settings.role.some( role => {
+					if ( roles.some( role => {
 						return !userSettings.guilds.isMember.get(guild).roles.some( guildRole => {
 						return !userSettings.guilds.isMember.get(guild).roles.some( guildRole => {
-							return ( guildRole.id === role && guildRole.lower );
+							return ( guildRole.id === role.id && guildRole.lower );
 						} );
 						} );
 					} ) ) {
 					} ) ) {
 						text += '\n';
 						text += '\n';
-						settings.role.forEach( role => {
+						roles.forEach( role => {
 							if ( !userSettings.guilds.isMember.get(guild).roles.some( guildRole => {
 							if ( !userSettings.guilds.isMember.get(guild).roles.some( guildRole => {
-								return ( guildRole.id === role );
+								return ( guildRole.id === role.id );
 							} ) ) {
 							} ) ) {
-								text += '\n' + lang.get('verification.role_deleted', `<@&${role}>`);
+								text += '\n' + lang.get('verification.role_deleted', `<@&${role.id}>`);
 							}
 							}
 							else if ( userSettings.guilds.isMember.get(guild).roles.some( guildRole => {
 							else if ( userSettings.guilds.isMember.get(guild).roles.some( guildRole => {
-								return ( guildRole.id === role && !guildRole.lower );
+								return ( guildRole.id === role.id && !guildRole.lower );
 							} ) ) {
 							} ) ) {
-								text += '\n' + lang.get('verification.role_too_high', `<@&${role}>`, `<@${process.env.bot}>`);
+								text += '\n' + lang.get('verification.role_too_high', `<@&${role.id}>`, `<@${process.env.bot}>`);
 							}
 							}
 						} );
 						} );
 					}
 					}

+ 155 - 65
functions/verify.js

@@ -200,8 +200,8 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 					queryuser.editcount = body.query.usercontribs.length;
 					queryuser.editcount = body.query.usercontribs.length;
 					if ( body.continue?.uccontinue ) queryuser.editcount++;
 					if ( body.continue?.uccontinue ) queryuser.editcount++;
 				}
 				}
-				var roles = [];
-				var missing = [];
+				var addRoles = [new Set(), new Set()];
+				var removeRoles = [new Set(), new Set()];
 				var verified = false;
 				var verified = false;
 				var rename = false;
 				var rename = false;
 				var accountage = ( Date.now() - new Date(queryuser.registration) ) / 86400000;
 				var accountage = ( Date.now() - new Date(queryuser.registration) ) / 86400000;
@@ -215,13 +215,18 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 					if ( row.postcount === null ) matchEditcount = ( ( queryuser.editcount + queryuser.postcount ) >= row.editcount );
 					if ( row.postcount === null ) matchEditcount = ( ( queryuser.editcount + queryuser.postcount ) >= row.editcount );
 					else if ( row.postcount < 0 ) matchEditcount = ( queryuser.editcount >= row.editcount || queryuser.postcount >= Math.abs(row.postcount) );
 					else if ( row.postcount < 0 ) matchEditcount = ( queryuser.editcount >= row.editcount || queryuser.postcount >= Math.abs(row.postcount) );
 					else matchEditcount = ( queryuser.editcount >= row.editcount && queryuser.postcount >= row.postcount );
 					else matchEditcount = ( queryuser.editcount >= row.editcount && queryuser.postcount >= row.postcount );
-					if ( matchEditcount && row.usergroup.split('|')[and_or]( usergroup => queryuser.groups.includes( usergroup ) ) && accountage >= row.accountage && row.role.split('|').some( role => !roles.includes( role ) ) ) {
+					if ( matchEditcount && row.usergroup.split('|')[and_or]( usergroup => queryuser.groups.includes( usergroup ) ) && accountage >= row.accountage ) {
 						verified = true;
 						verified = true;
 						if ( row.rename ) rename = true;
 						if ( row.rename ) rename = true;
 						row.role.split('|').forEach( role => {
 						row.role.split('|').forEach( role => {
-							if ( !roles.includes( role ) ) {
-								if ( channel.guild.roles.cache.has(role) && channel.guild.me.roles.highest.comparePositionTo(role) > 0 ) roles.push(role);
-								else if ( !missing.includes( role ) ) missing.push(role);
+							var modifyRoles = addRoles;
+							if ( role.startsWith( '-' ) ) {
+								role = role.replace( '-', '' );
+								modifyRoles = removeRoles;
+							}
+							if ( !modifyRoles[0].has(role) ) {
+								if ( channel.guild.roles.cache.has(role) && channel.guild.me.roles.highest.comparePositionTo(role) > 0 ) modifyRoles[0].add(role);
+								else if ( !modifyRoles[1].has(role) ) modifyRoles[1].add(role);
 							}
 							}
 						} );
 						} );
 					}
 					}
@@ -229,10 +234,22 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 				if ( verified ) {
 				if ( verified ) {
 					embed.setColor('#00FF00').setDescription( lang.get('verify.user_verified', member.toString(), '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) + ( rename ? '\n' + lang.get('verify.user_renamed', queryuser.gender) : '' ) );
 					embed.setColor('#00FF00').setDescription( lang.get('verify.user_verified', member.toString(), '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) + ( rename ? '\n' + lang.get('verify.user_renamed', queryuser.gender) : '' ) );
 					var text = lang.get('verify.user_verified_reply', escapeFormatting(username), queryuser.gender);
 					var text = lang.get('verify.user_verified_reply', escapeFormatting(username), queryuser.gender);
+					removeRoles[0].forEach( role => addRoles[0].delete(role) );
+					removeRoles[1].forEach( role => addRoles[1].delete(role) );
+					var changeRoles = [];
+					if ( addRoles[0].size + removeRoles[0].size === 1 ) {
+						if ( addRoles[0].size === 1 ) changeRoles.push('add', [...addRoles[0]][0]);
+						else changeRoles.push('remove', [...removeRoles[0]][0]);
+					}
+					else {
+						let roles = new Set([...member.roles.cache.filter( role => {
+							return !removeRoles[0].has(role.id);
+						} ).keys(), ...addRoles[0]]);
+						changeRoles.push('set', [...roles]);
+					}
 					var verify_promise = [
 					var verify_promise = [
-						member.roles.add( roles, lang.get('verify.audit_reason', username) ).catch( error => {
+						member.roles[changeRoles[0]]( changeRoles[1], lang.get('verify.audit_reason', username) ).catch( error => {
 							log_error(error);
 							log_error(error);
-							embed.setColor('#008800');
 							comment.push(lang.get('verify.failed_roles'));
 							comment.push(lang.get('verify.failed_roles'));
 						} )
 						} )
 					];
 					];
@@ -240,24 +257,30 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 						if ( channel.guild.me.roles.highest.comparePositionTo(member.roles.highest) > 0 ) {
 						if ( channel.guild.me.roles.highest.comparePositionTo(member.roles.highest) > 0 ) {
 							verify_promise.push(member.setNickname( username.substring(0, 32), lang.get('verify.audit_reason', username) ).catch( error => {
 							verify_promise.push(member.setNickname( username.substring(0, 32), lang.get('verify.audit_reason', username) ).catch( error => {
 								log_error(error);
 								log_error(error);
-								embed.setColor('#008800');
 								comment.push(lang.get('verify.failed_rename', queryuser.gender));
 								comment.push(lang.get('verify.failed_rename', queryuser.gender));
 							} ));
 							} ));
 						}
 						}
-						else {
-							embed.setColor('#008800');
-							comment.push(lang.get('verify.failed_rename', queryuser.gender));
-						}
+						else comment.push(lang.get('verify.failed_rename', queryuser.gender));
 					}
 					}
 					return Promise.all(verify_promise).then( () => {
 					return Promise.all(verify_promise).then( () => {
+						var addRolesMentions = [
+							[...addRoles[0]].map( role => '<@&' + role + '>' ),
+							[...addRoles[1]].map( role => '<@&' + role + '>' )
+						];
+						var removeRolesMentions = [
+							[...removeRoles[0]].map( role => '<@&' + role + '>' ),
+							[...removeRoles[1]].map( role => '<@&' + role + '>' )
+						];
 						var useLogging = false;
 						var useLogging = false;
 						if ( verifynotice.logchannel ) {
 						if ( verifynotice.logchannel ) {
 							useLogging = true;
 							useLogging = true;
 							result.logging.channel = verifynotice.logchannel.id;
 							result.logging.channel = verifynotice.logchannel.id;
 							if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
 							if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
 								let logembed = new MessageEmbed(embed);
 								let logembed = new MessageEmbed(embed);
-								if ( roles.length ) logembed.addField( lang.get('verify.qualified'), roles.map( role => '<@&' + role + '>' ).join('\n') );
-								if ( missing.length ) logembed.setColor('#008800').addField( lang.get('verify.qualified_error'), missing.map( role => '<@&' + role + '>' ).join('\n') );
+								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') );
+								if ( removeRolesMentions[0].length ) logembed.addField( lang.get('verify.qualified_remove'), removeRolesMentions[0].join('\n') );
+								if ( removeRolesMentions[1].length ) logembed.setColor('#008800').addField( lang.get('verify.qualified_remove_error'), removeRolesMentions[1].join('\n') );
 								if ( comment.length ) logembed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
 								if ( comment.length ) logembed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
 								result.logging.embed = logembed;
 								result.logging.embed = logembed;
 							}
 							}
@@ -265,8 +288,10 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 								let logtext = '🔸 ' + lang.get('verify.user_verified', member.toString(), escapeFormatting(username), queryuser.gender);
 								let logtext = '🔸 ' + lang.get('verify.user_verified', member.toString(), escapeFormatting(username), queryuser.gender);
 								if ( rename ) logtext += '\n' + lang.get('verify.user_renamed', queryuser.gender);
 								if ( rename ) logtext += '\n' + lang.get('verify.user_renamed', queryuser.gender);
 								logtext += '\n<' + pagelink + '>';
 								logtext += '\n<' + pagelink + '>';
-								if ( roles.length ) logtext += '\n**' + lang.get('verify.qualified') + '** ' + roles.map( role => '<@&' + role + '>' ).join(', ');
-								if ( missing.length ) logtext += '\n**' + lang.get('verify.qualified_error') + '** ' + missing.map( role => '<@&' + role + '>' ).join(', ');
+								if ( addRolesMentions[0].length ) logtext += '\n**' + lang.get('verify.qualified_add') + '** ' + addRolesMentions[0].join(', ');
+								if ( addRolesMentions[1].length ) logtext += '\n**' + lang.get('verify.qualified_add_error') + '** ' + addRolesMentions[1].join(', ');
+								if ( removeRolesMentions[0].length ) logtext += '\n**' + lang.get('verify.qualified_remove') + '** ' + removeRolesMentions[0].join(', ');
+								if ( removeRolesMentions[1].length ) logtext += '\n**' + lang.get('verify.qualified_remove_error') + '** ' + removeRolesMentions[1].join(', ');
 								if ( comment.length ) logtext += '\n**' + lang.get('verify.notice') + '** ' + comment.join('\n**' + lang.get('verify.notice') + '** ');
 								if ( comment.length ) logtext += '\n**' + lang.get('verify.notice') + '** ' + comment.join('\n**' + lang.get('verify.notice') + '** ');
 								result.logging.content = logtext;
 								result.logging.content = logtext;
 							}
 							}
@@ -278,14 +303,19 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 							dateformat: lang.get('dateformat')
 							dateformat: lang.get('dateformat')
 						}).trim() : '' );
 						}).trim() : '' );
 						if ( channel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
 						if ( channel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
-							if ( roles.length ) embed.addField( lang.get('verify.qualified'), roles.map( role => '<@&' + role + '>' ).join('\n') );
-							if ( missing.length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.qualified_error'), missing.map( role => '<@&' + role + '>' ).join('\n') );
+							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') );
+							if ( removeRolesMentions[1].length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.qualified_remove_error'), removeRolesMentions[1].join('\n') );
 							if ( comment.length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
 							if ( comment.length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
 							if ( onsuccess ) embed.addField( lang.get('verify.notice'), onsuccess );
 							if ( onsuccess ) embed.addField( lang.get('verify.notice'), onsuccess );
 						}
 						}
 						else {
 						else {
-							if ( roles.length ) text += '\n\n' + lang.get('verify.qualified') + ' ' + roles.map( role => '<@&' + role + '>' ).join(', ');
-							if ( missing.length && !useLogging ) text += '\n\n' + lang.get('verify.qualified_error') + ' ' + missing.map( role => '<@&' + role + '>' ).join(', ');
+							text += '\n';
+							if ( addRolesMentions[0].length ) text += '\n**' + lang.get('verify.qualified_add') + '** ' + addRolesMentions[0].join(', ');
+							if ( addRolesMentions[1].length && !useLogging ) text += '\n**' + lang.get('verify.qualified_add_error') + '** ' + addRolesMentions[1].join(', ');
+							if ( removeRolesMentions[0].length ) text += '\n**' + lang.get('verify.qualified_remove') + '** ' + removeRolesMentions[0].join(', ');
+							if ( removeRolesMentions[1].length && !useLogging ) text += '\n**' + lang.get('verify.qualified_remove_error') + '** ' + removeRolesMentions[1].join(', ');
 							if ( comment.length && !useLogging ) text += '\n\n' + comment.join('\n');
 							if ( comment.length && !useLogging ) text += '\n\n' + comment.join('\n');
 							if ( onsuccess ) text += '\n\n**' + lang.get('verify.notice') + '** ' + onsuccess;
 							if ( onsuccess ) text += '\n\n**' + lang.get('verify.notice') + '** ' + onsuccess;
 						}
 						}
@@ -402,8 +432,8 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 				return;
 				return;
 			}
 			}
 			
 			
-			var roles = [];
-			var missing = [];
+			var addRoles = [new Set(), new Set()];
+			var removeRoles = [new Set(), new Set()];
 			var verified = false;
 			var verified = false;
 			var rename = false;
 			var rename = false;
 			var accountage = ( Date.now() - new Date(queryuser.registration) ) / 86400000;
 			var accountage = ( Date.now() - new Date(queryuser.registration) ) / 86400000;
@@ -413,13 +443,18 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 					row.usergroup = row.usergroup.replace( 'AND|', '' );
 					row.usergroup = row.usergroup.replace( 'AND|', '' );
 					and_or = 'every';
 					and_or = 'every';
 				}
 				}
-				if ( queryuser.editcount >= row.editcount && row.usergroup.split('|')[and_or]( usergroup => queryuser.groups.includes( usergroup ) ) && accountage >= row.accountage && row.role.split('|').some( role => !roles.includes( role ) ) ) {
+				if ( queryuser.editcount >= row.editcount && row.usergroup.split('|')[and_or]( usergroup => queryuser.groups.includes( usergroup ) ) && accountage >= row.accountage ) {
 					verified = true;
 					verified = true;
 					if ( row.rename ) rename = true;
 					if ( row.rename ) rename = true;
 					row.role.split('|').forEach( role => {
 					row.role.split('|').forEach( role => {
-						if ( !roles.includes( role ) ) {
-							if ( channel.guild.roles.cache.has(role) && channel.guild.me.roles.highest.comparePositionTo(role) > 0 ) roles.push(role);
-							else if ( !missing.includes( role ) ) missing.push(role);
+						var modifyRoles = addRoles;
+						if ( role.startsWith( '-' ) ) {
+							role = role.replace( '-', '' );
+							modifyRoles = removeRoles;
+						}
+						if ( !modifyRoles[0].has(role) ) {
+							if ( channel.guild.roles.cache.has(role) && channel.guild.me.roles.highest.comparePositionTo(role) > 0 ) modifyRoles[0].add(role);
+							else if ( !modifyRoles[1].has(role) ) modifyRoles[1].add(role);
 						}
 						}
 					} );
 					} );
 				}
 				}
@@ -427,10 +462,22 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 			if ( verified ) {
 			if ( verified ) {
 				embed.setColor('#00FF00').setDescription( lang.get('verify.user_verified', member.toString(), '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) + ( rename ? '\n' + lang.get('verify.user_renamed', queryuser.gender) : '' ) );
 				embed.setColor('#00FF00').setDescription( lang.get('verify.user_verified', member.toString(), '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) + ( rename ? '\n' + lang.get('verify.user_renamed', queryuser.gender) : '' ) );
 				var text = lang.get('verify.user_verified_reply', escapeFormatting(username), queryuser.gender);
 				var text = lang.get('verify.user_verified_reply', escapeFormatting(username), queryuser.gender);
+				removeRoles[0].forEach( role => addRoles[0].delete(role) );
+				removeRoles[1].forEach( role => addRoles[1].delete(role) );
+				var changeRoles = [];
+				if ( addRoles[0].size + removeRoles[0].size === 1 ) {
+					if ( addRoles[0].size === 1 ) changeRoles.push('add', [...addRoles[0]][0]);
+					else changeRoles.push('remove', [...removeRoles[0]][0]);
+				}
+				else {
+					let roles = new Set([...member.roles.cache.filter( role => {
+						return !removeRoles[0].has(role.id);
+					} ).keys(), ...addRoles[0]]);
+					changeRoles.push('set', [...roles]);
+				}
 				var verify_promise = [
 				var verify_promise = [
-					member.roles.add( roles, lang.get('verify.audit_reason', username) ).catch( error => {
+					member.roles[changeRoles[0]]( changeRoles[1], lang.get('verify.audit_reason', username) ).catch( error => {
 						log_error(error);
 						log_error(error);
-						embed.setColor('#008800');
 						comment.push(lang.get('verify.failed_roles'));
 						comment.push(lang.get('verify.failed_roles'));
 					} )
 					} )
 				];
 				];
@@ -438,24 +485,30 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 					if ( channel.guild.me.roles.highest.comparePositionTo(member.roles.highest) > 0 ) {
 					if ( channel.guild.me.roles.highest.comparePositionTo(member.roles.highest) > 0 ) {
 						verify_promise.push(member.setNickname( username.substring(0, 32), lang.get('verify.audit_reason', username) ).catch( error => {
 						verify_promise.push(member.setNickname( username.substring(0, 32), lang.get('verify.audit_reason', username) ).catch( error => {
 							log_error(error);
 							log_error(error);
-							embed.setColor('#008800');
 							comment.push(lang.get('verify.failed_rename', queryuser.gender));
 							comment.push(lang.get('verify.failed_rename', queryuser.gender));
 						} ));
 						} ));
 					}
 					}
-					else {
-						embed.setColor('#008800');
-						comment.push(lang.get('verify.failed_rename', queryuser.gender));
-					}
+					else comment.push(lang.get('verify.failed_rename', queryuser.gender));
 				}
 				}
 				return Promise.all(verify_promise).then( () => {
 				return Promise.all(verify_promise).then( () => {
+					var addRolesMentions = [
+						[...addRoles[0]].map( role => '<@&' + role + '>' ),
+						[...addRoles[1]].map( role => '<@&' + role + '>' )
+					];
+					var removeRolesMentions = [
+						[...removeRoles[0]].map( role => '<@&' + role + '>' ),
+						[...removeRoles[1]].map( role => '<@&' + role + '>' )
+					];
 					var useLogging = false;
 					var useLogging = false;
 					if ( verifynotice.logchannel ) {
 					if ( verifynotice.logchannel ) {
 						useLogging = true;
 						useLogging = true;
 						result.logging.channel = verifynotice.logchannel.id;
 						result.logging.channel = verifynotice.logchannel.id;
 						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
 						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
 							var logembed = new MessageEmbed(embed);
 							var logembed = new MessageEmbed(embed);
-							if ( roles.length ) logembed.addField( lang.get('verify.qualified'), roles.map( role => '<@&' + role + '>' ).join('\n') );
-							if ( missing.length ) logembed.setColor('#008800').addField( lang.get('verify.qualified_error'), missing.map( role => '<@&' + role + '>' ).join('\n') );
+							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') );
+							if ( removeRolesMentions[0].length ) logembed.addField( lang.get('verify.qualified_remove'), removeRolesMentions[0].join('\n') );
+							if ( removeRolesMentions[1].length ) logembed.setColor('#008800').addField( lang.get('verify.qualified_remove_error'), removeRolesMentions[1].join('\n') );
 							if ( comment.length ) logembed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
 							if ( comment.length ) logembed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
 							result.logging.embed = logembed;
 							result.logging.embed = logembed;
 						}
 						}
@@ -463,8 +516,10 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 							var logtext = '🔸 ' + lang.get('verify.user_verified', member.toString(), escapeFormatting(username), queryuser.gender);
 							var logtext = '🔸 ' + lang.get('verify.user_verified', member.toString(), escapeFormatting(username), queryuser.gender);
 							if ( rename ) logtext += '\n' + lang.get('verify.user_renamed', queryuser.gender);
 							if ( rename ) logtext += '\n' + lang.get('verify.user_renamed', queryuser.gender);
 							logtext += '\n<' + pagelink + '>';
 							logtext += '\n<' + pagelink + '>';
-							if ( roles.length ) logtext += '\n**' + lang.get('verify.qualified') + '** ' + roles.map( role => '<@&' + role + '>' ).join(', ');
-							if ( missing.length ) logtext += '\n**' + lang.get('verify.qualified_error') + '** ' + missing.map( role => '<@&' + role + '>' ).join(', ');
+							if ( addRolesMentions[0].length ) logtext += '\n**' + lang.get('verify.qualified_add') + '** ' + addRolesMentions[0].join(', ');
+							if ( addRolesMentions[1].length ) logtext += '\n**' + lang.get('verify.qualified_add_error') + '** ' + addRolesMentions[1].join(', ');
+							if ( removeRolesMentions[0].length ) logtext += '\n**' + lang.get('verify.qualified_remove') + '** ' + removeRolesMentions[0].join(', ');
+							if ( removeRolesMentions[1].length ) logtext += '\n**' + lang.get('verify.qualified_remove_error') + '** ' + removeRolesMentions[1].join(', ');
 							if ( comment.length ) logtext += '\n**' + lang.get('verify.notice') + '** ' + comment.join('\n**' + lang.get('verify.notice') + '** ');
 							if ( comment.length ) logtext += '\n**' + lang.get('verify.notice') + '** ' + comment.join('\n**' + lang.get('verify.notice') + '** ');
 							result.logging.content = logtext;
 							result.logging.content = logtext;
 						}
 						}
@@ -475,14 +530,19 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 						dateformat: lang.get('dateformat')
 						dateformat: lang.get('dateformat')
 					}).trim() : '' );
 					}).trim() : '' );
 					if ( channel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
 					if ( channel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
-						if ( roles.length ) embed.addField( lang.get('verify.qualified'), roles.map( role => '<@&' + role + '>' ).join('\n') );
-						if ( missing.length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.qualified_error'), missing.map( role => '<@&' + role + '>' ).join('\n') );
+						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') );
+						if ( removeRolesMentions[1].length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.qualified_remove_error'), removeRolesMentions[1].join('\n') );
 						if ( comment.length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
 						if ( comment.length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
 						if ( onsuccess ) embed.addField( lang.get('verify.notice'), onsuccess );
 						if ( onsuccess ) embed.addField( lang.get('verify.notice'), onsuccess );
 					}
 					}
 					else {
 					else {
-						if ( roles.length ) text += '\n\n' + lang.get('verify.qualified') + ' ' + roles.map( role => '<@&' + role + '>' ).join(', ');
-						if ( missing.length && !useLogging ) text += '\n\n' + lang.get('verify.qualified_error') + ' ' + missing.map( role => '<@&' + role + '>' ).join(', ');
+						text += '\n';
+						if ( addRolesMentions[0].length ) text += '\n**' + lang.get('verify.qualified_add') + '** ' + addRolesMentions[0].join(', ');
+						if ( addRolesMentions[1].length && !useLogging ) text += '\n**' + lang.get('verify.qualified_add_error') + '** ' + addRolesMentions[1].join(', ');
+						if ( removeRolesMentions[0].length ) text += '\n**' + lang.get('verify.qualified_remove') + '** ' + removeRolesMentions[0].join(', ');
+						if ( removeRolesMentions[1].length && !useLogging ) text += '\n**' + lang.get('verify.qualified_remove_error') + '** ' + removeRolesMentions[1].join(', ');
 						if ( comment.length && !useLogging ) text += '\n\n' + comment.join('\n');
 						if ( comment.length && !useLogging ) text += '\n\n' + comment.join('\n');
 						if ( onsuccess ) text += '\n\n**' + lang.get('verify.notice') + '** ' + onsuccess;
 						if ( onsuccess ) text += '\n\n**' + lang.get('verify.notice') + '** ' + onsuccess;
 					}
 					}
@@ -643,8 +703,8 @@ global.verifyOauthUser = function(state, access_token, settings) {
 			}
 			}
 			queryuser.groups.push(...body.query.globaluserinfo.groups);
 			queryuser.groups.push(...body.query.globaluserinfo.groups);
 
 
-			var roles = [];
-			var missing = [];
+			var addRoles = [new Set(), new Set()];
+			var removeRoles = [new Set(), new Set()];
 			var verified = false;
 			var verified = false;
 			var rename = false;
 			var rename = false;
 			var accountage = ( Date.now() - new Date(queryuser.registration) ) / 86400000;
 			var accountage = ( Date.now() - new Date(queryuser.registration) ) / 86400000;
@@ -654,13 +714,18 @@ global.verifyOauthUser = function(state, access_token, settings) {
 					row.usergroup = row.usergroup.replace( 'AND|', '' );
 					row.usergroup = row.usergroup.replace( 'AND|', '' );
 					and_or = 'every';
 					and_or = 'every';
 				}
 				}
-				if ( queryuser.editcount >= row.editcount && row.usergroup.split('|')[and_or]( usergroup => queryuser.groups.includes( usergroup ) ) && accountage >= row.accountage && row.role.split('|').some( role => !roles.includes( role ) ) ) {
+				if ( queryuser.editcount >= row.editcount && row.usergroup.split('|')[and_or]( usergroup => queryuser.groups.includes( usergroup ) ) && accountage >= row.accountage ) {
 					verified = true;
 					verified = true;
 					if ( row.rename ) rename = true;
 					if ( row.rename ) rename = true;
 					row.role.split('|').forEach( role => {
 					row.role.split('|').forEach( role => {
-						if ( !roles.includes( role ) ) {
-							if ( channel.guild.roles.cache.has(role) && channel.guild.me.roles.highest.comparePositionTo(role) > 0 ) roles.push(role);
-							else if ( !missing.includes( role ) ) missing.push(role);
+						var modifyRoles = addRoles;
+						if ( role.startsWith( '-' ) ) {
+							role = role.replace( '-', '' );
+							modifyRoles = removeRoles;
+						}
+						if ( !modifyRoles[0].has(role) ) {
+							if ( channel.guild.roles.cache.has(role) && channel.guild.me.roles.highest.comparePositionTo(role) > 0 ) modifyRoles[0].add(role);
+							else if ( !modifyRoles[1].has(role) ) modifyRoles[1].add(role);
 						}
 						}
 					} );
 					} );
 				}
 				}
@@ -669,10 +734,22 @@ global.verifyOauthUser = function(state, access_token, settings) {
 				embed.setColor('#00FF00').setDescription( lang.get('verify.user_verified', member.toString(), '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) + ( rename ? '\n' + lang.get('verify.user_renamed', queryuser.gender) : '' ) );
 				embed.setColor('#00FF00').setDescription( lang.get('verify.user_verified', member.toString(), '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) + ( rename ? '\n' + lang.get('verify.user_renamed', queryuser.gender) : '' ) );
 				var text = lang.get('verify.user_verified_reply', escapeFormatting(username), queryuser.gender);
 				var text = lang.get('verify.user_verified_reply', escapeFormatting(username), queryuser.gender);
 				var comment = [];
 				var comment = [];
+				removeRoles[0].forEach( role => addRoles[0].delete(role) );
+				removeRoles[1].forEach( role => addRoles[1].delete(role) );
+				var changeRoles = [];
+				if ( addRoles[0].size + removeRoles[0].size === 1 ) {
+					if ( addRoles[0].size === 1 ) changeRoles.push('add', [...addRoles[0]][0]);
+					else changeRoles.push('remove', [...removeRoles[0]][0]);
+				}
+				else {
+					let roles = new Set([...member.roles.cache.filter( role => {
+						return !removeRoles[0].has(role.id);
+					} ).keys(), ...addRoles[0]]);
+					changeRoles.push('set', [...roles]);
+				}
 				var verify_promise = [
 				var verify_promise = [
-					member.roles.add( roles, lang.get('verify.audit_reason', username) ).catch( error => {
+					member.roles[changeRoles[0]]( changeRoles[1], lang.get('verify.audit_reason', username) ).catch( error => {
 						log_error(error);
 						log_error(error);
-						embed.setColor('#008800');
 						comment.push(lang.get('verify.failed_roles'));
 						comment.push(lang.get('verify.failed_roles'));
 					} )
 					} )
 				];
 				];
@@ -680,16 +757,20 @@ global.verifyOauthUser = function(state, access_token, settings) {
 					if ( channel.guild.me.roles.highest.comparePositionTo(member.roles.highest) > 0 ) {
 					if ( channel.guild.me.roles.highest.comparePositionTo(member.roles.highest) > 0 ) {
 						verify_promise.push(member.setNickname( username.substring(0, 32), lang.get('verify.audit_reason', username) ).catch( error => {
 						verify_promise.push(member.setNickname( username.substring(0, 32), lang.get('verify.audit_reason', username) ).catch( error => {
 							log_error(error);
 							log_error(error);
-							embed.setColor('#008800');
 							comment.push(lang.get('verify.failed_rename', queryuser.gender));
 							comment.push(lang.get('verify.failed_rename', queryuser.gender));
 						} ));
 						} ));
 					}
 					}
-					else {
-						embed.setColor('#008800');
-						comment.push(lang.get('verify.failed_rename', queryuser.gender));
-					}
+					else comment.push(lang.get('verify.failed_rename', queryuser.gender));
 				}
 				}
 				return Promise.all(verify_promise).then( () => {
 				return Promise.all(verify_promise).then( () => {
+					var addRolesMentions = [
+						[...addRoles[0]].map( role => '<@&' + role + '>' ),
+						[...addRoles[1]].map( role => '<@&' + role + '>' )
+					];
+					var removeRolesMentions = [
+						[...removeRoles[0]].map( role => '<@&' + role + '>' ),
+						[...removeRoles[1]].map( role => '<@&' + role + '>' )
+					];
 					var useLogging = false;
 					var useLogging = false;
 					var logembed;
 					var logembed;
 					var logtext = '';
 					var logtext = '';
@@ -697,16 +778,20 @@ global.verifyOauthUser = function(state, access_token, settings) {
 						useLogging = true;
 						useLogging = true;
 						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
 						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
 							logembed = new MessageEmbed(embed);
 							logembed = new MessageEmbed(embed);
-							if ( roles.length ) logembed.addField( lang.get('verify.qualified'), roles.map( role => '<@&' + role + '>' ).join('\n') );
-							if ( missing.length ) logembed.setColor('#008800').addField( lang.get('verify.qualified_error'), missing.map( role => '<@&' + role + '>' ).join('\n') );
+							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') );
+							if ( removeRolesMentions[0].length ) logembed.addField( lang.get('verify.qualified_remove'), removeRolesMentions[0].join('\n') );
+							if ( removeRolesMentions[1].length ) logembed.setColor('#008800').addField( lang.get('verify.qualified_remove_error'), removeRolesMentions[1].join('\n') );
 							if ( comment.length ) logembed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
 							if ( comment.length ) logembed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
 						}
 						}
 						else {
 						else {
 							logtext = '🔸 ' + lang.get('verify.user_verified', member.toString(), escapeFormatting(username), queryuser.gender);
 							logtext = '🔸 ' + lang.get('verify.user_verified', member.toString(), escapeFormatting(username), queryuser.gender);
 							if ( rename ) logtext += '\n' + lang.get('verify.user_renamed', queryuser.gender);
 							if ( rename ) logtext += '\n' + lang.get('verify.user_renamed', queryuser.gender);
 							logtext += '\n<' + pagelink + '>';
 							logtext += '\n<' + pagelink + '>';
-							if ( roles.length ) logtext += '\n**' + lang.get('verify.qualified') + '** ' + roles.map( role => '<@&' + role + '>' ).join(', ');
-							if ( missing.length ) logtext += '\n**' + lang.get('verify.qualified_error') + '** ' + missing.map( role => '<@&' + role + '>' ).join(', ');
+							if ( addRolesMentions[0].length ) logtext += '\n**' + lang.get('verify.qualified_add') + '** ' + addRolesMentions[0].join(', ');
+							if ( addRolesMentions[1].length ) logtext += '\n**' + lang.get('verify.qualified_add_error') + '** ' + addRolesMentions[1].join(', ');
+							if ( removeRolesMentions[0].length ) logtext += '\n**' + lang.get('verify.qualified_remove') + '** ' + removeRolesMentions[0].join(', ');
+							if ( removeRolesMentions[1].length ) logtext += '\n**' + lang.get('verify.qualified_remove_error') + '** ' + removeRolesMentions[1].join(', ');
 							if ( comment.length ) logtext += '\n**' + lang.get('verify.notice') + '** ' + comment.join('\n**' + lang.get('verify.notice') + '** ');
 							if ( comment.length ) logtext += '\n**' + lang.get('verify.notice') + '** ' + comment.join('\n**' + lang.get('verify.notice') + '** ');
 						}
 						}
 					}
 					}
@@ -717,14 +802,19 @@ global.verifyOauthUser = function(state, access_token, settings) {
 						dateformat: lang.get('dateformat')
 						dateformat: lang.get('dateformat')
 					}).trim() : '' );
 					}).trim() : '' );
 					if ( channel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
 					if ( channel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
-						if ( roles.length ) embed.addField( lang.get('verify.qualified'), roles.map( role => '<@&' + role + '>' ).join('\n') );
-						if ( missing.length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.qualified_error'), missing.map( role => '<@&' + role + '>' ).join('\n') );
+						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') );
+						if ( removeRolesMentions[1].length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.qualified_remove_error'), removeRolesMentions[1].join('\n') );
 						if ( comment.length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
 						if ( comment.length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
 						if ( onsuccess ) embed.addField( lang.get('verify.notice'), onsuccess );
 						if ( onsuccess ) embed.addField( lang.get('verify.notice'), onsuccess );
 					}
 					}
 					else {
 					else {
-						if ( roles.length ) text += '\n\n' + lang.get('verify.qualified') + ' ' + roles.map( role => '<@&' + role + '>' ).join(', ');
-						if ( missing.length && !useLogging ) text += '\n\n' + lang.get('verify.qualified_error') + ' ' + missing.map( role => '<@&' + role + '>' ).join(', ');
+						text += '\n';
+						if ( addRolesMentions[0].length ) text += '\n**' + lang.get('verify.qualified_add') + '** ' + addRolesMentions[0].join(', ');
+						if ( addRolesMentions[1].length && !useLogging ) text += '\n**' + lang.get('verify.qualified_add_error') + '** ' + addRolesMentions[1].join(', ');
+						if ( removeRolesMentions[0].length ) text += '\n**' + lang.get('verify.qualified_remove') + '** ' + removeRolesMentions[0].join(', ');
+						if ( removeRolesMentions[1].length && !useLogging ) text += '\n**' + lang.get('verify.qualified_remove_error') + '** ' + removeRolesMentions[1].join(', ');
 						if ( comment.length && !useLogging ) text += '\n\n' + comment.join('\n');
 						if ( comment.length && !useLogging ) text += '\n\n' + comment.join('\n');
 						if ( onsuccess ) text += '\n\n**' + lang.get('verify.notice') + '** ' + onsuccess;
 						if ( onsuccess ) text += '\n\n**' + lang.get('verify.notice') + '** ' + onsuccess;
 					}
 					}
@@ -818,7 +908,7 @@ global.verifyOauthUser = function(state, access_token, settings) {
 							dmEmbed.fields.forEach( field => {
 							dmEmbed.fields.forEach( field => {
 								field.value = field.value.replace( /<@&(\d+)>/g, (mention, id) => {
 								field.value = field.value.replace( /<@&(\d+)>/g, (mention, id) => {
 									if ( !channel.guild.roles.cache.has(id) ) return mention;
 									if ( !channel.guild.roles.cache.has(id) ) return mention;
-									return '@' + channel.guild.roles.cache.get(id)?.name;
+									return escapeFormatting('@' + channel.guild.roles.cache.get(id)?.name);
 								} );
 								} );
 							} );
 							} );
 							member.send(channel.toString() + '; ' + content, Object.assign({}, options, {embed: dmEmbed})).then( message => {
 							member.send(channel.toString() + '; ' + content, Object.assign({}, options, {embed: dmEmbed})).then( message => {
@@ -838,7 +928,7 @@ global.verifyOauthUser = function(state, access_token, settings) {
 					dmEmbed.fields.forEach( field => {
 					dmEmbed.fields.forEach( field => {
 						field.value = field.value.replace( /<@&(\d+)>/g, (mention, id) => {
 						field.value = field.value.replace( /<@&(\d+)>/g, (mention, id) => {
 							if ( !channel.guild.roles.cache.has(id) ) return mention;
 							if ( !channel.guild.roles.cache.has(id) ) return mention;
-							return '@' + channel.guild.roles.cache.get(id)?.name;
+							return escapeFormatting('@' + channel.guild.roles.cache.get(id)?.name);
 						} );
 						} );
 					} );
 					} );
 					member.send(channel.toString() + '; ' + content, Object.assign({}, options, {embed: dmEmbed})).then( message => {
 					member.send(channel.toString() + '; ' + content, Object.assign({}, options, {embed: dmEmbed})).then( message => {

+ 0 - 4
i18n/bn.json

@@ -494,7 +494,6 @@
         "all_inactive": "আপনি উইকি পরিবর্তন অর ফিড-আধারিত পরিবর্তন এক বাড়ে অক্ষম রাখতে পারবেন না।",
         "all_inactive": "আপনি উইকি পরিবর্তন অর ফিড-আধারিত পরিবর্তন এক বাড়ে অক্ষম রাখতে পারবেন না।",
         "audit_reason": "\"$1\"এর জন্যে রিসেন্ট চেঞ্জেস ওয়েবহুক",
         "audit_reason": "\"$1\"এর জন্যে রিসেন্ট চেঞ্জেস ওয়েবহুক",
         "audit_reason_delete": "রিসেন্ট চেঞ্জেস ওয়েবহুক সরিয়ে দেওয়া হয়েছে",
         "audit_reason_delete": "রিসেন্ট চেঞ্জেস ওয়েবহুক সরিয়ে দেওয়া হয়েছে",
-        "audit_reason_move": "রিসেন্ট চেঞ্জেস ওয়েবহুকটিকে সরানো হয়ে গেছে",
         "blocked": "এই উইকিকে রিসেন্ট চেঞ্জেস ওয়েবহুকের মধ্যে হওয়ার থেকে ব্ল'ক করা হয়েছে!",
         "blocked": "এই উইকিকে রিসেন্ট চেঞ্জেস ওয়েবহুকের মধ্যে হওয়ার থেকে ব্ল'ক করা হয়েছে!",
         "blocked_reason": "এই উইকিকে `$1`কারণের জন্যে রিসেন্ট চেঞ্জেস ওয়েবহুকের মধ্যে হওয়ার থেকে ব্ল'ক করা হয়েছে!",
         "blocked_reason": "এই উইকিকে `$1`কারণের জন্যে রিসেন্ট চেঞ্জেস ওয়েবহুকের মধ্যে হওয়ার থেকে ব্ল'ক করা হয়েছে!",
         "channel": "চ্যানেল:",
         "channel": "চ্যানেল:",
@@ -763,7 +762,6 @@
         "posteditcount": "সম্পাদনা আর পোস্টের মাত্রা মিলিয়ে:",
         "posteditcount": "সম্পাদনা আর পোস্টের মাত্রা মিলিয়ে:",
         "rename": "নিকনেম বদলান:",
         "rename": "নিকনেম বদলান:",
         "rename_no_permission": "**$1এর কাছে উইকি সদস্যনামে জোর করার জন্যে `Manage Nicknames` অনুমতি নেই!**",
         "rename_no_permission": "**$1এর কাছে উইকি সদস্যনামে জোর করার জন্যে `Manage Nicknames` অনুমতি নেই!**",
-        "role": "রোল:",
         "role_deleted": "**মনে হয় রোল $1 আর উপস্থিত নয়!**",
         "role_deleted": "**মনে হয় রোল $1 আর উপস্থিত নয়!**",
         "role_managed": "এই রোল দেওয়া যাবে না।",
         "role_managed": "এই রোল দেওয়া যাবে না।",
         "role_max": "আপনি কিছু বেশিই রোল দিয়ে দিয়েছেন।",
         "role_max": "আপনি কিছু বেশিই রোল দিয়ে দিয়েছেন।",
@@ -793,8 +791,6 @@
         "help_subpage": "অনুগ্রহ করে নিজের ডিসকর্ড ট্যাগ ($1) নিজের Discord উপপৃষ্ঠে লাগান:",
         "help_subpage": "অনুগ্রহ করে নিজের ডিসকর্ড ট্যাগ ($1) নিজের Discord উপপৃষ্ঠে লাগান:",
         "missing": "এই চ্যানেলের জন্যে কোনও ভেরিফিকেশন নেই।",
         "missing": "এই চ্যানেলের জন্যে কোনও ভেরিফিকেশন নেই।",
         "notice": "বিজ্ঞপ্তি:",
         "notice": "বিজ্ঞপ্তি:",
-        "qualified": "এগুলি রোল পাবে:",
-        "qualified_error": "এগুলি পাওয়া যেতো, কিন্তু লাগানো যায়নি:",
         "user_blocked": "**উইকি ব্যাবহারকারি $1 ব্লক্ড!**",
         "user_blocked": "**উইকি ব্যাবহারকারি $1 ব্লক্ড!**",
         "user_blocked_reply": "আপনি লিংক করা উইকি ব্যাবহারকারি **\"$1\" ব্লক্ড!**",
         "user_blocked_reply": "আপনি লিংক করা উইকি ব্যাবহারকারি **\"$1\" ব্লক্ড!**",
         "user_disabled": "**উইকি ব্যাবহারকারি $1 অক্ষম!**",
         "user_disabled": "**উইকি ব্যাবহারকারি $1 অক্ষম!**",

+ 65 - 57
i18n/de.json

@@ -265,31 +265,31 @@
             "rcscript": {
             "rcscript": {
                 "add": {
                 "add": {
                     "cmd": "rcscript add [<Wiki>]",
                     "cmd": "rcscript add [<Wiki>]",
-                    "desc": "Ich füge einen neuen Letzte Änderungen-Webhook hinzu."
+                    "desc": "Ich füge einen neuen Letzte Änderungen-WebHook hinzu."
                 },
                 },
                 "default": {
                 "default": {
                     "cmd": "rcscript",
                     "cmd": "rcscript",
-                    "desc": "Ich ändere den Letzte Änderungen-Webhook."
+                    "desc": "Ich ändere den Letzte Änderungen-WebHook."
                 },
                 },
                 "delete": {
                 "delete": {
                     "cmd": "rcscript delete",
                     "cmd": "rcscript delete",
-                    "desc": "Ich lösche den Letzte Änderungen-Webhook."
+                    "desc": "Ich lösche den Letzte Änderungen-WebHook."
                 },
                 },
                 "display": {
                 "display": {
                     "cmd": "rcscript display <neuer Anzeigemodus>",
                     "cmd": "rcscript display <neuer Anzeigemodus>",
-                    "desc": "Ich ändere den Anzeigemodus für den Letzte Änderungen-Webhook."
+                    "desc": "Ich ändere den Anzeigemodus für den Letzte Änderungen-WebHook."
                 },
                 },
                 "feeds": {
                 "feeds": {
                     "cmd": "rcscript feeds",
                     "cmd": "rcscript feeds",
-                    "desc": "Ich ändere ob Feeds-basierte Änderungen von einen Fandom-Wiki angezeigt werden sollen für den Letzte Änderungen-Webhook."
+                    "desc": "Ich ändere ob Feeds-basierte Änderungen von einen Fandom-Wiki angezeigt werden sollen für den Letzte Änderungen-WebHook."
                 },
                 },
                 "lang": {
                 "lang": {
                     "cmd": "rcscript lang <neue Sprache>",
                     "cmd": "rcscript lang <neue Sprache>",
-                    "desc": "Ich ändere die Sprache für den Letzte Änderungen-Webhook."
+                    "desc": "Ich ändere die Sprache für den Letzte Änderungen-WebHook."
                 },
                 },
                 "wiki": {
                 "wiki": {
                     "cmd": "rcscript wiki <neues Wiki>",
                     "cmd": "rcscript wiki <neues Wiki>",
-                    "desc": "Ich ändere das Wiki für den Letzte Änderungen-Webhook."
+                    "desc": "Ich ändere das Wiki für den Letzte Änderungen-WebHook."
                 }
                 }
             },
             },
             "search": {
             "search": {
@@ -488,35 +488,36 @@
         "on": "ich bin auf diesem Server nun pausiert und werde die meisten Befehle ignorieren!"
         "on": "ich bin auf diesem Server nun pausiert und werde die meisten Befehle ignorieren!"
     },
     },
     "rcscript": {
     "rcscript": {
-        "ad": "Du möchtest letzte Änderungen direkt in Discord? Nutze `$1rcscript` um einen Letzte Änderungen-Webhook basierend auf **$2** zu deinen Diesocrd server hinzuzufügen!",
-        "add_more": "Füge mehr Letzte Änderungen-Webhooks hinzu:",
-        "added": "ein Letzte Änderungen-Webhook wurde hinzugefügt für:",
+        "ad": "Du möchtest letzte Änderungen direkt in Discord? Nutze `$1rcscript` um einen Letzte Änderungen-WebHook basierend auf **$2** zu deinen Diesocrd server hinzuzufügen!",
+        "add_more": "Füge mehr Letzte Änderungen-WebHooks hinzu:",
+        "added": "ein Letzte Änderungen-WebHook wurde hinzugefügt für:",
         "all_inactive": "du kannst Wiki-Änderungen und Feeds-basierte Änderungen nicht gleichzeitig deaktiviert haben.",
         "all_inactive": "du kannst Wiki-Änderungen und Feeds-basierte Änderungen nicht gleichzeitig deaktiviert haben.",
-        "audit_reason": "Letzte Änderungen-Webhook für \"$1\"",
-        "audit_reason_delete": "Letzte Änderungen-Webhook entfernt",
-        "audit_reason_move": "Letzte Änderungen-Webhook verschoben",
-        "blocked": "diese Wiki wurde davon gesperrt als Letzte Änderungen-Webhook hinzugefügt zu werden!",
-        "blocked_reason": "diese Wiki wurde wegen `$1` davon gesperrt als Letzte Änderungen-Webhook hinzugefügt zu werden!",
+        "audit_reason": "Letzte Änderungen-WebHook für \"$1\"",
+        "audit_reason_delete": "Letzte Änderungen-WebHook entfernt",
+        "audit_reason_edit": "Letzte Änderungen-WebHook aktualisiert",
+        "avatar": "WebHook-Avatar:",
+        "blocked": "diese Wiki wurde davon gesperrt als Letzte Änderungen-WebHook hinzugefügt zu werden!",
+        "blocked_reason": "diese Wiki wurde wegen `$1` davon gesperrt als Letzte Änderungen-WebHook hinzugefügt zu werden!",
         "channel": "Kanal:",
         "channel": "Kanal:",
-        "current": "dies sind die aktuellen Letzte Änderungen-Webhooks für diesen Server:",
-        "current_display": "der Anzeigemodus für diesen Webhook ist:",
-        "current_lang": "die Sprache für diesen Webhook ist:",
-        "current_selected": "dies ist der Letzte Änderungen-Webhook `$1` für diesen Server:",
-        "current_wiki": "das Wiki für diesen Webhook ist:",
+        "current": "dies sind die aktuellen Letzte Änderungen-WebHooks für diesen Server:",
+        "current_display": "der Anzeigemodus für diesen WebHook ist:",
+        "current_lang": "die Sprache für diesen WebHook ist:",
+        "current_selected": "dies ist der Letzte Änderungen-WebHook `$1` für diesen Server:",
+        "current_wiki": "das Wiki für diesen WebHook ist:",
         "dashboard": {
         "dashboard": {
-            "added": "$1 hat den Letzte Änderungen-Webhook mit der ID `$2` hinzugefügt.",
-            "removed": "$1 hat den Letzte Änderungen-Webhook mit der ID `$2` gelöscht.",
-            "updated": "$1 hat den Letzte Änderungen-Webhook mit der ID `$2` bearbeitet."
+            "added": "$1 hat den Letzte Änderungen-WebHook mit der ID `$2` hinzugefügt.",
+            "removed": "$1 hat den Letzte Änderungen-WebHook mit der ID `$2` gelöscht.",
+            "updated": "$1 hat den Letzte Änderungen-WebHook mit der ID `$2` bearbeitet."
         },
         },
-        "delete": "Lösche diesen Letzte Änderungen-Webhook:",
-        "deleted": "der Letzte Änderungen-Webhook wurde gelöscht.",
+        "delete": "Lösche diesen Letzte Änderungen-WebHook:",
+        "deleted": "der Letzte Änderungen-WebHook wurde gelöscht.",
         "disabled": "deaktiviert",
         "disabled": "deaktiviert",
-        "disabled_feeds": "die Feeds-basierten Änderungen, wie Diskussionen, Nachrichtenseiten und Artikelkommentare, für diesen Webhook wurden deaktiviert.",
-        "disabled_rc": "die Wiki-Änderungen für diesen Webhook wurden deaktiviert.",
+        "disabled_feeds": "die Feeds-basierten Änderungen, wie Diskussionen, Nachrichtenseiten und Artikelkommentare, für diesen WebHook wurden deaktiviert.",
+        "disabled_rc": "die Wiki-Änderungen für diesen WebHook wurden deaktiviert.",
         "display": "Anzeigemodus:",
         "display": "Anzeigemodus:",
         "enabled": "aktiviert",
         "enabled": "aktiviert",
-        "enabled_feeds": "die Feeds-basierten Änderungen, wie Diskussionen, Nachrichtenseiten und Artikelkommentare, für diesen Webhook wurden aktiviert.",
-        "enabled_rc": "die Wiki-Änderungen für diesen Webhook wurden aktiviert.",
+        "enabled_feeds": "die Feeds-basierten Änderungen, wie Diskussionen, Nachrichtenseiten und Artikelkommentare, für diesen WebHook wurden aktiviert.",
+        "enabled_rc": "die Wiki-Änderungen für diesen WebHook wurden aktiviert.",
         "feeds": "Feeds-basierte Änderungen:",
         "feeds": "Feeds-basierte Änderungen:",
         "help_display_compact": "Kompakte Textnachrichten mit Inline-Links.",
         "help_display_compact": "Kompakte Textnachrichten mit Inline-Links.",
         "help_display_diff": "Einbettungen mit Bildvorschau und Bearbeitungsunterschieden.",
         "help_display_diff": "Einbettungen mit Bildvorschau und Bearbeitungsunterschieden.",
@@ -526,26 +527,28 @@
         "help_lang": "Bisher mögliche Sprachen sind:",
         "help_lang": "Bisher mögliche Sprachen sind:",
         "help_wiki": "Link zu einer MediaWiki Website, wie `https://<Wiki>.fandom.com/`",
         "help_wiki": "Link zu einer MediaWiki Website, wie `https://<Wiki>.fandom.com/`",
         "lang": "Sprache:",
         "lang": "Sprache:",
-        "max_entries": "du hast bereits die maximale Anzahl an Letzte Änderungen-Webhooks erreicht.",
-        "missing": "es ist noch kein Letzte Änderungen-Webhook für diesen Server vorhanden.",
+        "max_entries": "du hast bereits die maximale Anzahl an Letzte Änderungen-WebHooks erreicht.",
+        "missing": "es ist noch kein Letzte Änderungen-WebHook für diesen Server vorhanden.",
+        "name": "WebHook-Name:",
         "new_lang": "<neue Sprache>",
         "new_lang": "<neue Sprache>",
         "new_wiki": "<Link zum Wiki>",
         "new_wiki": "<Link zum Wiki>",
-        "no_feeds": "das Wiki für diesen Webhook hat keine Feeds-basierten Funktionen, wie Diskussionen, Nachrichtenseiten oder Artikelkommentare, aktiviert.",
+        "no_feeds": "das Wiki für diesen WebHook hat keine Feeds-basierten Funktionen, wie Diskussionen, Nachrichtenseiten oder Artikelkommentare, aktiviert.",
         "noadmin": "du benötigst die `WebHooks verwalten`-Berechtigung für diesen Befehl!",
         "noadmin": "du benötigst die `WebHooks verwalten`-Berechtigung für diesen Befehl!",
         "rc": "Wiki-Änderungen:",
         "rc": "Wiki-Änderungen:",
-        "sysmessage": "die Systemnachricht `$1` muss die Server-ID `$2` sein um einen Letzte Änderungen-Webhook hinzuzufügen.",
-        "title": "Letzte Änderungen-Webhook",
+        "sysmessage": "die Systemnachricht `$1` muss die Server-ID `$2` sein um einen Letzte Änderungen-WebHook hinzuzufügen.",
+        "title": "Letzte Änderungen-WebHook",
         "toggle": "(umschalten)",
         "toggle": "(umschalten)",
-        "updated_display": "der Anzeigemodus für diesen Webhook wurde geändert zu:",
-        "updated_lang": "die Sprache für diesen Webhook wurde geändert zu:",
-        "updated_wiki": "das Wiki für diesen Webhook wurde geändert zu:",
+        "updated_display": "der Anzeigemodus für diesen WebHook wurde geändert zu:",
+        "updated_lang": "die Sprache für diesen WebHook wurde geändert zu:",
+        "updated_wiki": "das Wiki für diesen WebHook wurde geändert zu:",
         "webhook": {
         "webhook": {
-            "blocked": "Dieser Letzte Änderungen-Webhook wird gelöscht, da das Wiki gesperrt wurde!",
+            "blocked": "Dieser Letzte Änderungen-WebHook wird gelöscht, da das Wiki gesperrt wurde!",
             "blocked_help": "Du kannst auf dem [Support-Server]($1) nach mehr Details fragen.",
             "blocked_help": "Du kannst auf dem [Support-Server]($1) nach mehr Details fragen.",
-            "blocked_reason": "Dieser Letzte Änderungen-Webhook wird gelöscht, da das Wiki wegen `$1` gesperrt wurde!",
-            "created": "Ein Letzte Änderungen-Webhook für $1 wurde zu diesem Kanal hinzugefügt.",
+            "blocked_reason": "Dieser Letzte Änderungen-WebHook wird gelöscht, da das Wiki wegen `$1` gesperrt wurde!",
+            "created": "Ein Letzte Änderungen-WebHook für $1 wurde zu diesem Kanal hinzugefügt.",
             "dashboard": {
             "dashboard": {
-                "channel": "• Der Webhook wurde in diesen Kanal verschoben.",
+                "avatar": "• Der WebHook-Avatar wurde geändert.",
+                "channel": "• Der WebHook wurde in diesen Kanal verschoben.",
                 "disabled_feeds": "• Die Feeds-basierten Änderungen, wie Diskussionen, Nachrichtenseiten und Artikelkommentare, wurden deaktiviert.",
                 "disabled_feeds": "• Die Feeds-basierten Änderungen, wie Diskussionen, Nachrichtenseiten und Artikelkommentare, wurden deaktiviert.",
                 "disabled_rc": "• Die Wiki-Änderungen wurden deaktiviert.",
                 "disabled_rc": "• Die Wiki-Änderungen wurden deaktiviert.",
                 "display_compact": "• Der Anzeigemodus wurde zu kompakten Textnachrichten mit Inline-Links geändert.",
                 "display_compact": "• Der Anzeigemodus wurde zu kompakten Textnachrichten mit Inline-Links geändert.",
@@ -555,22 +558,23 @@
                 "enabled_feeds": "• Die Feeds-basierten Änderungen, wie Diskussionen, Nachrichtenseiten und Artikelkommentare, wurden aktiviert.",
                 "enabled_feeds": "• Die Feeds-basierten Änderungen, wie Diskussionen, Nachrichtenseiten und Artikelkommentare, wurden aktiviert.",
                 "enabled_rc": "• Die Wiki-Änderungen wurden aktiviert.",
                 "enabled_rc": "• Die Wiki-Änderungen wurden aktiviert.",
                 "lang": "• Die Sprache wurde zu $1 geändert.",
                 "lang": "• Die Sprache wurde zu $1 geändert.",
-                "updated": "Dieser Letzte Änderungen-Webhook wurde bearbeitet:",
+                "name": "• Der WebHook-Name wurde zu „$1“ geändert.",
+                "updated": "Dieser Letzte Änderungen-WebHook wurde bearbeitet:",
                 "wiki": "• Das Wiki wurde zu $1 geändert."
                 "wiki": "• Das Wiki wurde zu $1 geändert."
             },
             },
-            "deleted": "Dieser Letzte Änderungen-Webhook wird gelöscht.",
-            "disabled_feeds": "Die Feeds-basierten Änderungen, wie Diskussionen, Nachrichtenseiten und Artikelkommentare, wurden für diesen Letzte Änderungen-Webhook deaktiviert.",
-            "disabled_rc": "Die Wiki-Änderungen wurden für diesen Letzte Änderungen-Webhook deaktiviert.",
-            "enabled_feeds": "Die Feeds-basierten Änderungen, wie Diskussionen, Nachrichtenseiten und Artikelkommentare, wurden für diesen Letzte Änderungen-Webhook aktiviert.",
-            "enabled_rc": "Die Wiki-Änderungen wurden für diesen Letzte Änderungen-Webhook aktiviert.",
-            "updated_display_compact": "Der Anzeigemodus für diesen Letzte Änderungen-Webhook wurde zu kompakten Textnachrichten mit Inline-Links geändert.",
-            "updated_display_diff": "Der Anzeigemodus für diesen Letzte Änderungen-Webhook wurde zu Einbettungen mit Bildvorschau und Bearbeitungsunterschieden geändert.",
-            "updated_display_embed": "Der Anzeigemodus für diesen Letzte Änderungen-Webhook wurde zu Einbettungen mit Bearbeitungsmarkierungen und Kategorieänderungen geändert.",
-            "updated_display_image": "Der Anzeigemodus für diesen Letzte Änderungen-Webhook wurde zu Einbettungen mit Bildvorschau geändert.",
-            "updated_lang": "Die Sprache für diesen Letzte Änderungen-Webhook wurde zu `$1` geändert.",
-            "updated_wiki": "Das Wiki für diesen Letzte Änderungen-Webhook wurde zu $1 geändert."
+            "deleted": "Dieser Letzte Änderungen-WebHook wird gelöscht.",
+            "disabled_feeds": "Die Feeds-basierten Änderungen, wie Diskussionen, Nachrichtenseiten und Artikelkommentare, wurden für diesen Letzte Änderungen-WebHook deaktiviert.",
+            "disabled_rc": "Die Wiki-Änderungen wurden für diesen Letzte Änderungen-WebHook deaktiviert.",
+            "enabled_feeds": "Die Feeds-basierten Änderungen, wie Diskussionen, Nachrichtenseiten und Artikelkommentare, wurden für diesen Letzte Änderungen-WebHook aktiviert.",
+            "enabled_rc": "Die Wiki-Änderungen wurden für diesen Letzte Änderungen-WebHook aktiviert.",
+            "updated_display_compact": "Der Anzeigemodus für diesen Letzte Änderungen-WebHook wurde zu kompakten Textnachrichten mit Inline-Links geändert.",
+            "updated_display_diff": "Der Anzeigemodus für diesen Letzte Änderungen-WebHook wurde zu Einbettungen mit Bildvorschau und Bearbeitungsunterschieden geändert.",
+            "updated_display_embed": "Der Anzeigemodus für diesen Letzte Änderungen-WebHook wurde zu Einbettungen mit Bearbeitungsmarkierungen und Kategorieänderungen geändert.",
+            "updated_display_image": "Der Anzeigemodus für diesen Letzte Änderungen-WebHook wurde zu Einbettungen mit Bildvorschau geändert.",
+            "updated_lang": "Die Sprache für diesen Letzte Änderungen-WebHook wurde zu `$1` geändert.",
+            "updated_wiki": "Das Wiki für diesen Letzte Änderungen-WebHook wurde zu $1 geändert."
         },
         },
-        "webhook_failed": "der Webhook konnten leider nicht erstellt werden, bitte versuche es später erneut.",
+        "webhook_failed": "der WebHook konnten leider nicht erstellt werden, bitte versuche es später erneut.",
         "wiki": "Wiki:"
         "wiki": "Wiki:"
     },
     },
     "search": {
     "search": {
@@ -771,11 +775,13 @@
         "posteditcount": "Bearbeitungs- und Diskussionsbeitragslimit zusammen:",
         "posteditcount": "Bearbeitungs- und Diskussionsbeitragslimit zusammen:",
         "rename": "Ändere Nickname:",
         "rename": "Ändere Nickname:",
         "rename_no_permission": "**$1 fehlt die `Nicknames verwalten` Berechtigung um Wiki-Benutzernamen zu erzwingen!**",
         "rename_no_permission": "**$1 fehlt die `Nicknames verwalten` Berechtigung um Wiki-Benutzernamen zu erzwingen!**",
-        "role": "Rolle:",
+        "role_add": "Rolle zum Vergeben:",
         "role_deleted": "**Die Rolle $1 scheint nicht mehr zu existieren!**",
         "role_deleted": "**Die Rolle $1 scheint nicht mehr zu existieren!**",
         "role_managed": "die angegebe Rolle kann nicht vergeben werden.",
         "role_managed": "die angegebe Rolle kann nicht vergeben werden.",
         "role_max": "du hast zu viele Rollen angegeben.",
         "role_max": "du hast zu viele Rollen angegeben.",
         "role_missing": "die angegebe Rolle existiert nicht.",
         "role_missing": "die angegebe Rolle existiert nicht.",
+        "role_none": "keine",
+        "role_remove": "Rolle zum Entfernen:",
         "role_too_high": "**Die Rolle $1 ist zu hoch für $2 um sie zu vergeben!**",
         "role_too_high": "**Die Rolle $1 ist zu hoch für $2 um sie zu vergeben!**",
         "save_failed": "die Verifizierungen konnten leider nicht gespeichert werden, bitte versuche es später erneut.",
         "save_failed": "die Verifizierungen konnten leider nicht gespeichert werden, bitte versuche es später erneut.",
         "success": "Hinweis bei Erfolg:",
         "success": "Hinweis bei Erfolg:",
@@ -810,8 +816,10 @@
         "oauth_message_dm": "Bitte nutze diesen Link um deinen Wiki-Account für $1 zu authentifizieren.",
         "oauth_message_dm": "Bitte nutze diesen Link um deinen Wiki-Account für $1 zu authentifizieren.",
         "oauth_private": "dieses Wiki nutzt OAuth2 zur Verifizierung. Bitte erlaube Direktnachrichten von diesem Server oder nutze den `/verify`-Befehl damit ich dir privat einen Authentifizierungslink senden kann.",
         "oauth_private": "dieses Wiki nutzt OAuth2 zur Verifizierung. Bitte erlaube Direktnachrichten von diesem Server oder nutze den `/verify`-Befehl damit ich dir privat einen Authentifizierungslink senden kann.",
         "oauth_used": "*Verifiziert über OAuth2*",
         "oauth_used": "*Verifiziert über OAuth2*",
-        "qualified": "Qualifiziert für:",
-        "qualified_error": "Qualifiziert für, aber kann nicht hinzugefügt werden:",
+        "qualified_add": "Hinzugefügt zu:",
+        "qualified_add_error": "Kann nicht hinzugefügt werden zu:",
+        "qualified_remove": "Entfernt von:",
+        "qualified_remove_error": "Kann nicht entfernt werden von:",
         "user_blocked": "**{{GENDER:$2|Der Wiki-Benutzer|Die Wiki-Benutzerin}} $1 ist gesperrt!**",
         "user_blocked": "**{{GENDER:$2|Der Wiki-Benutzer|Die Wiki-Benutzerin}} $1 ist gesperrt!**",
         "user_blocked_reply": "{{GENDER:$2|der|die}} von dir verlinkte {{GENDER:$2|Wiki-Benutzer|Wiki-Benutzerin}} **„$1“ ist gesperrt!**",
         "user_blocked_reply": "{{GENDER:$2|der|die}} von dir verlinkte {{GENDER:$2|Wiki-Benutzer|Wiki-Benutzerin}} **„$1“ ist gesperrt!**",
         "user_disabled": "**Das Benutzerkonto $1 ist deaktiviert!**",
         "user_disabled": "**Das Benutzerkonto $1 ist deaktiviert!**",

+ 12 - 4
i18n/en.json

@@ -494,7 +494,8 @@
         "all_inactive": "you can't have wiki changes and feeds based changes disabled at the same time.",
         "all_inactive": "you can't have wiki changes and feeds based changes disabled at the same time.",
         "audit_reason": "Recent changes webhook for \"$1\"",
         "audit_reason": "Recent changes webhook for \"$1\"",
         "audit_reason_delete": "Removed recent changes webhook",
         "audit_reason_delete": "Removed recent changes webhook",
-        "audit_reason_move": "Moved recent changes webhook",
+        "audit_reason_edit": "Updated recent changes webhook",
+        "avatar": "Webhook avatar:",
         "blocked": "this wiki has been blocked from being added as a recent changes webhook!",
         "blocked": "this wiki has been blocked from being added as a recent changes webhook!",
         "blocked_reason": "this wiki has been blocked from being added as a recent changes webhook for `$1`!",
         "blocked_reason": "this wiki has been blocked from being added as a recent changes webhook for `$1`!",
         "channel": "Channel:",
         "channel": "Channel:",
@@ -528,6 +529,7 @@
         "lang": "Language:",
         "lang": "Language:",
         "max_entries": "you already reached the maximal amount of recent changes webhooks.",
         "max_entries": "you already reached the maximal amount of recent changes webhooks.",
         "missing": "there are no recent changes webhooks for this server yet.",
         "missing": "there are no recent changes webhooks for this server yet.",
+        "name": "Webhook name:",
         "new_lang": "<new language>",
         "new_lang": "<new language>",
         "new_wiki": "<link to wiki>",
         "new_wiki": "<link to wiki>",
         "no_feeds": "the wiki for this webhook has no feeds based features, like discussions, message walls or article comments, enabled.",
         "no_feeds": "the wiki for this webhook has no feeds based features, like discussions, message walls or article comments, enabled.",
@@ -545,6 +547,7 @@
             "blocked_reason": "This recent changes webhook will be deleted because the wiki has been blocked for `$1`!",
             "blocked_reason": "This recent changes webhook will be deleted because the wiki has been blocked for `$1`!",
             "created": "A recent changes webhook for $1 has been added to this channel.",
             "created": "A recent changes webhook for $1 has been added to this channel.",
             "dashboard": {
             "dashboard": {
+                "avatar": "• The webhook avatar has been changed.",
                 "channel": "• The webhook has been moved to this channel.",
                 "channel": "• The webhook has been moved to this channel.",
                 "disabled_feeds": "• The feeds based changes, like discussions, message walls and article comments, have been disabled.",
                 "disabled_feeds": "• The feeds based changes, like discussions, message walls and article comments, have been disabled.",
                 "disabled_rc": "• The wiki changes have been disabled.",
                 "disabled_rc": "• The wiki changes have been disabled.",
@@ -555,6 +558,7 @@
                 "enabled_feeds": "• The feeds based changes, like discussions, message walls and article comments, have been enabled.",
                 "enabled_feeds": "• The feeds based changes, like discussions, message walls and article comments, have been enabled.",
                 "enabled_rc": "• The wiki changes have been enabled.",
                 "enabled_rc": "• The wiki changes have been enabled.",
                 "lang": "• The language has been changed to $1.",
                 "lang": "• The language has been changed to $1.",
+                "name": "• The webhook name has been changed to \"$1\".",
                 "updated": "This recent changes webhook has been updated:",
                 "updated": "This recent changes webhook has been updated:",
                 "wiki": "• The wiki has been changed to $1."
                 "wiki": "• The wiki has been changed to $1."
             },
             },
@@ -771,11 +775,13 @@
         "posteditcount": "Edit and post count combined:",
         "posteditcount": "Edit and post count combined:",
         "rename": "Change nickname:",
         "rename": "Change nickname:",
         "rename_no_permission": "**$1 is missing the `Manage Nicknames` permission to force wiki usernames!**",
         "rename_no_permission": "**$1 is missing the `Manage Nicknames` permission to force wiki usernames!**",
-        "role": "Role:",
+        "role_add": "Role to add:",
         "role_deleted": "**The role $1 doesn't seem to exist anymore!**",
         "role_deleted": "**The role $1 doesn't seem to exist anymore!**",
         "role_managed": "the provided role can't be assigned.",
         "role_managed": "the provided role can't be assigned.",
         "role_max": "you provided too many roles.",
         "role_max": "you provided too many roles.",
         "role_missing": "the provided role does not exist.",
         "role_missing": "the provided role does not exist.",
+        "role_none": "none",
+        "role_remove": "Role to remove:",
         "role_too_high": "**The role $1 is too high for $2 to assign!**",
         "role_too_high": "**The role $1 is too high for $2 to assign!**",
         "save_failed": "sadly the verification couldn't be saved, please try again later.",
         "save_failed": "sadly the verification couldn't be saved, please try again later.",
         "success": "Success notice:",
         "success": "Success notice:",
@@ -810,8 +816,10 @@
         "oauth_message_dm": "Please use this link to authenticate your wiki account for $1.",
         "oauth_message_dm": "Please use this link to authenticate your wiki account for $1.",
         "oauth_private": "the wiki uses OAuth2 for verification. Please enable direct messages from this server or use the `/verify` command so I can send you an authentication link privately.",
         "oauth_private": "the wiki uses OAuth2 for verification. Please enable direct messages from this server or use the `/verify` command so I can send you an authentication link privately.",
         "oauth_used": "*Verified using OAuth2*",
         "oauth_used": "*Verified using OAuth2*",
-        "qualified": "Qualified for:",
-        "qualified_error": "Qualified for, but can't add:",
+        "qualified_add": "Added to:",
+        "qualified_add_error": "Can't be added to:",
+        "qualified_remove": "Removed from:",
+        "qualified_remove_error": "Can't be removed from:",
         "user_blocked": "**The wiki user $1 is blocked!**",
         "user_blocked": "**The wiki user $1 is blocked!**",
         "user_blocked_reply": "your linked wiki user **\"$1\" is blocked!**",
         "user_blocked_reply": "your linked wiki user **\"$1\" is blocked!**",
         "user_disabled": "**The wiki account $1 is disabled!**",
         "user_disabled": "**The wiki account $1 is disabled!**",

+ 0 - 4
i18n/es.json

@@ -494,7 +494,6 @@
         "all_inactive": "no puedes deshabilitar los cambios al wiki y los cambios basados en feeds al mismo tiempo.",
         "all_inactive": "no puedes deshabilitar los cambios al wiki y los cambios basados en feeds al mismo tiempo.",
         "audit_reason": "Webhook de cambios recientes para \"$1\"",
         "audit_reason": "Webhook de cambios recientes para \"$1\"",
         "audit_reason_delete": "Webhook de cambios recientes eliminado",
         "audit_reason_delete": "Webhook de cambios recientes eliminado",
-        "audit_reason_move": "Webhook de cambios recientes movido",
         "blocked": "¡este wiki ha sido bloqueado para que no se agregue como un webhook de cambios recientes!",
         "blocked": "¡este wiki ha sido bloqueado para que no se agregue como un webhook de cambios recientes!",
         "blocked_reason": "¡este wiki ha sido bloqueado para que no se agregue como un webhook de cambios recientes por `$1`!",
         "blocked_reason": "¡este wiki ha sido bloqueado para que no se agregue como un webhook de cambios recientes por `$1`!",
         "channel": "Canal:",
         "channel": "Canal:",
@@ -768,7 +767,6 @@
         "posteditcount": "Número de ediciones y publicaciones combinadas:",
         "posteditcount": "Número de ediciones y publicaciones combinadas:",
         "rename": "Cambiar alias:",
         "rename": "Cambiar alias:",
         "rename_no_permission": "**¡A $1 le falta el permiso `Manage Nicknames` para imponer nombres de usuario wiki!**",
         "rename_no_permission": "**¡A $1 le falta el permiso `Manage Nicknames` para imponer nombres de usuario wiki!**",
-        "role": "Rol:",
         "role_deleted": "**¡El rol $1 parece que ya no existe!**",
         "role_deleted": "**¡El rol $1 parece que ya no existe!**",
         "role_managed": "no se puede asignar el rol proporcionado.",
         "role_managed": "no se puede asignar el rol proporcionado.",
         "role_max": "proporcionaste demasiados roles.",
         "role_max": "proporcionaste demasiados roles.",
@@ -800,8 +798,6 @@
         "help_subpage": "Por favor agrega tu etiqueta de Discord ($1) a tu subpágina de Discord en el wiki:",
         "help_subpage": "Por favor agrega tu etiqueta de Discord ($1) a tu subpágina de Discord en el wiki:",
         "missing": "no hay verificaciones configuradas en este canal.",
         "missing": "no hay verificaciones configuradas en este canal.",
         "notice": "Aviso:",
         "notice": "Aviso:",
-        "qualified": "Calificado para:",
-        "qualified_error": "Calificado para, pero no puede agregar:",
         "user_blocked": "**El usuario wiki $1 está bloqueado!**",
         "user_blocked": "**El usuario wiki $1 está bloqueado!**",
         "user_blocked_reply": "¡tu usuario wiki vinculado **\"$1\" está bloqueado!**",
         "user_blocked_reply": "¡tu usuario wiki vinculado **\"$1\" está bloqueado!**",
         "user_disabled": "**¡La cuenta wiki $1 está deshabilitada!**",
         "user_disabled": "**¡La cuenta wiki $1 está deshabilitada!**",

+ 0 - 4
i18n/fr.json

@@ -488,7 +488,6 @@
         "all_inactive": "vous ne pouvez pas avoir les modifications de fils de discussion et de modifications de pages désactivés en même temps.",
         "all_inactive": "vous ne pouvez pas avoir les modifications de fils de discussion et de modifications de pages désactivés en même temps.",
         "audit_reason": "Intégration des modifications récentes pour \"$1\"",
         "audit_reason": "Intégration des modifications récentes pour \"$1\"",
         "audit_reason_delete": "Intégration des modifications récentes retirée",
         "audit_reason_delete": "Intégration des modifications récentes retirée",
-        "audit_reason_move": "Intégration des modifications récentes déplacée",
         "blocked": "ce wiki a été bloqué de l'utilisation de l'intégration des modifications récentes !",
         "blocked": "ce wiki a été bloqué de l'utilisation de l'intégration des modifications récentes !",
         "blocked_reason": "ce wiki a été bloqué de l'utilisation de l'intégration des modifications récentes pour `$1` !",
         "blocked_reason": "ce wiki a été bloqué de l'utilisation de l'intégration des modifications récentes pour `$1` !",
         "channel": "Salon :",
         "channel": "Salon :",
@@ -757,7 +756,6 @@
         "posteditcount": "Nombre de modification et post combinés :",
         "posteditcount": "Nombre de modification et post combinés :",
         "rename": "Changer le surnom :",
         "rename": "Changer le surnom :",
         "rename_no_permission": "**$1 a besoin de la permission `Gérer les pseudos` pour mettre les pseudos wiki !**",
         "rename_no_permission": "**$1 a besoin de la permission `Gérer les pseudos` pour mettre les pseudos wiki !**",
-        "role": "Rôle :",
         "role_deleted": "**Le rôle $1 ne semble plus exister !**",
         "role_deleted": "**Le rôle $1 ne semble plus exister !**",
         "role_managed": "le rôle fourni ne peut pas être attribué.",
         "role_managed": "le rôle fourni ne peut pas être attribué.",
         "role_max": "vous avez fourni trop de rôles.",
         "role_max": "vous avez fourni trop de rôles.",
@@ -787,8 +785,6 @@
         "help_subpage": "Veuillez rajouter votre tag Discord ($1) à votre sous-page Discord sur le wiki :",
         "help_subpage": "Veuillez rajouter votre tag Discord ($1) à votre sous-page Discord sur le wiki :",
         "missing": "il n'y a aucune vérification mise en place pour ce salon.",
         "missing": "il n'y a aucune vérification mise en place pour ce salon.",
         "notice": "Note :",
         "notice": "Note :",
-        "qualified": "Qualifié pour :",
-        "qualified_error": "Qualifié pour, mais ne peut pas rajouter :",
         "user_blocked": "**L'utilisateur wiki $1 est bloqué !**",
         "user_blocked": "**L'utilisateur wiki $1 est bloqué !**",
         "user_blocked_reply": "votre utilisateur wiki lié **\"$1\" est bloqué !**",
         "user_blocked_reply": "votre utilisateur wiki lié **\"$1\" est bloqué !**",
         "user_disabled": "**Le compte wiki $1 est désactivé !**",
         "user_disabled": "**Le compte wiki $1 est désactivé !**",

+ 14 - 4
i18n/hi.json

@@ -494,7 +494,8 @@
         "all_inactive": "आप विकी-बदलाव और फीड-आधारित बदलावों को एक साथ सक्षम नहीं रख सकतें।",
         "all_inactive": "आप विकी-बदलाव और फीड-आधारित बदलावों को एक साथ सक्षम नहीं रख सकतें।",
         "audit_reason": "\"$1\" के लिए रीसेंट चेंजेस वेबहुक",
         "audit_reason": "\"$1\" के लिए रीसेंट चेंजेस वेबहुक",
         "audit_reason_delete": "रीसेंट चेंजेस वेबहुक हटा दिया गया",
         "audit_reason_delete": "रीसेंट चेंजेस वेबहुक हटा दिया गया",
-        "audit_reason_move": "रीसेंट चेंजेस वेबहुक मूव कर दिया गया है",
+        "audit_reason_edit": "रीसेंट चेंजेस वेबहुक को अपडेट किया गया",
+        "avatar": "वेबहुक का अवतार:",
         "blocked": "इस विकी को रीसेंट चेंजेस वेबहुक बनाने से ब्लॉक कर दिया गया है!",
         "blocked": "इस विकी को रीसेंट चेंजेस वेबहुक बनाने से ब्लॉक कर दिया गया है!",
         "blocked_reason": "इस विकी को `$1` के लिए रीसेंट चेंजेस वेबहुक बनाने से ब्लॉक कर दिया गया है!",
         "blocked_reason": "इस विकी को `$1` के लिए रीसेंट चेंजेस वेबहुक बनाने से ब्लॉक कर दिया गया है!",
         "channel": "चैनल:",
         "channel": "चैनल:",
@@ -528,6 +529,7 @@
         "lang": "भाषा:",
         "lang": "भाषा:",
         "max_entries": "आप रीसेंट चेंजेस वेबहुक के अधिकतम संख्या तक पहुँच चुके हैं।",
         "max_entries": "आप रीसेंट चेंजेस वेबहुक के अधिकतम संख्या तक पहुँच चुके हैं।",
         "missing": "इस सर्वर के लिए अब तक कोई रीसेंट चेंजेस वेबहुक नहीं बना है।",
         "missing": "इस सर्वर के लिए अब तक कोई रीसेंट चेंजेस वेबहुक नहीं बना है।",
+        "name": "वेबहुक का नाम:",
         "new_lang": "<नई भाषा>",
         "new_lang": "<नई भाषा>",
         "new_wiki": "<विकी का लिंक>",
         "new_wiki": "<विकी का लिंक>",
         "no_feeds": "इस वेबहुक के विकी पर कोई फीड-आधारित बदलाव, जैसे डिसकशंस, मैसेज वॉल और आर्टिकल कमेंट, सक्षम नहीं है।",
         "no_feeds": "इस वेबहुक के विकी पर कोई फीड-आधारित बदलाव, जैसे डिसकशंस, मैसेज वॉल और आर्टिकल कमेंट, सक्षम नहीं है।",
@@ -545,6 +547,7 @@
             "blocked_reason": "इस रीसेंट चेंजेस वेबहुक को डिलीट कर दिया जाएगा क्योंकि विकी को `$1` के लिए ब्लॉक कर दिया गया है!",
             "blocked_reason": "इस रीसेंट चेंजेस वेबहुक को डिलीट कर दिया जाएगा क्योंकि विकी को `$1` के लिए ब्लॉक कर दिया गया है!",
             "created": "$1 के लिए एक रीसेंट चेंजेस वेबहुक को इस चैनल पर जोड़ दिया गया है।",
             "created": "$1 के लिए एक रीसेंट चेंजेस वेबहुक को इस चैनल पर जोड़ दिया गया है।",
             "dashboard": {
             "dashboard": {
+                "avatar": "• वेबहुक के अवतार को बदल दिया गया है।",
                 "channel": "• वेबहुक को इस चैनल पर लाया गया है।",
                 "channel": "• वेबहुक को इस चैनल पर लाया गया है।",
                 "disabled_feeds": "• फीड-आधारित बदलावों, जैसे डिसकशंस, मैसेज वॉल, और आर्टिकल कमेंट, को अक्षम कर दिया गया है।",
                 "disabled_feeds": "• फीड-आधारित बदलावों, जैसे डिसकशंस, मैसेज वॉल, और आर्टिकल कमेंट, को अक्षम कर दिया गया है।",
                 "disabled_rc": "• विकी बदलावों को अक्षम कर दिया गया है।",
                 "disabled_rc": "• विकी बदलावों को अक्षम कर दिया गया है।",
@@ -555,6 +558,7 @@
                 "enabled_feeds": "• फीड-आधारित बदलावों, जैसे डिसकशंस, मैसेज वॉल, और आर्टिकल कमेंट, को सक्षम कर दिया गया है।",
                 "enabled_feeds": "• फीड-आधारित बदलावों, जैसे डिसकशंस, मैसेज वॉल, और आर्टिकल कमेंट, को सक्षम कर दिया गया है।",
                 "enabled_rc": "• विकी बदलावों को सक्षम कर दिया गया है।",
                 "enabled_rc": "• विकी बदलावों को सक्षम कर दिया गया है।",
                 "lang": "• भाषा को $1 में बदल दिया गया है।",
                 "lang": "• भाषा को $1 में बदल दिया गया है।",
+                "name": "• वेबहुक के नाम को \"$1\" में बदल दिया गया है।",
                 "updated": "रीसेंट चेंजेस वेबहुक को अपडेट किया गया है:",
                 "updated": "रीसेंट चेंजेस वेबहुक को अपडेट किया गया है:",
                 "wiki": "• विकी को $1 में बदल दिया गया है।"
                 "wiki": "• विकी को $1 में बदल दिया गया है।"
             },
             },
@@ -750,6 +754,8 @@
         "disabled": "अक्षम",
         "disabled": "अक्षम",
         "editcount": "सम्पादना की मात्रा:",
         "editcount": "सम्पादना की मात्रा:",
         "enabled": "सक्षम",
         "enabled": "सक्षम",
+        "flag_logall": "असफल वेरिफिकेशनों को लॉग करना:",
+        "flag_private": "वेरिफिकेशन कमांड के जवाब को व्यक्तिगत रूप में भेजना:",
         "indays": "(दिनों में)",
         "indays": "(दिनों में)",
         "logging": "लॉग करने के लिए चैनल:",
         "logging": "लॉग करने के लिए चैनल:",
         "match": "आवश्यकता में न आने के लिए सूचना:",
         "match": "आवश्यकता में न आने के लिए सूचना:",
@@ -769,11 +775,13 @@
         "posteditcount": "सम्पादना और पोस्ट की मात्रा को मिलाकर:",
         "posteditcount": "सम्पादना और पोस्ट की मात्रा को मिलाकर:",
         "rename": "निकनेम बदलें:",
         "rename": "निकनेम बदलें:",
         "rename_no_permission": "**$1 के पास विकी यूज़रनेम पर मजबूर करने के लिए `Manage Nicknames` अनुमति नहीं है!**",
         "rename_no_permission": "**$1 के पास विकी यूज़रनेम पर मजबूर करने के लिए `Manage Nicknames` अनुमति नहीं है!**",
-        "role": "रोल:",
+        "role_add": "जोड़ने के लिए रोल:",
         "role_deleted": "**शायद रोल $1 अब मौजूद नहीं है!**",
         "role_deleted": "**शायद रोल $1 अब मौजूद नहीं है!**",
         "role_managed": "इस रोल को नहीं दिया जा सकता।",
         "role_managed": "इस रोल को नहीं दिया जा सकता।",
         "role_max": "आपने कुछ ज़्यादा ही रोल दे दिए हैं।",
         "role_max": "आपने कुछ ज़्यादा ही रोल दे दिए हैं।",
         "role_missing": "यह रोल मौजूद नहीं है।",
         "role_missing": "यह रोल मौजूद नहीं है।",
+        "role_none": "कुछ नहीं",
+        "role_remove": "हटाने के लिए रोल:",
         "role_too_high": "**रोल $1 $2 को देने के लिए कुछ ज़्यादा ही ऊँचा है!**",
         "role_too_high": "**रोल $1 $2 को देने के लिए कुछ ज़्यादा ही ऊँचा है!**",
         "save_failed": "बदकिस्मती से वेरिफिकेशन सेव नहीं हो पाया। कृपया थोड़ी देर बाद कोशिश करें।",
         "save_failed": "बदकिस्मती से वेरिफिकेशन सेव नहीं हो पाया। कृपया थोड़ी देर बाद कोशिश करें।",
         "success": "सफलता की सूचना:",
         "success": "सफलता की सूचना:",
@@ -808,8 +816,10 @@
         "oauth_message_dm": "कृपया $1 के लिए अपने विकि अकाउंट को ऑथेंटिकेट करने के लिए इस लिंक का इस्तेमाल करें।",
         "oauth_message_dm": "कृपया $1 के लिए अपने विकि अकाउंट को ऑथेंटिकेट करने के लिए इस लिंक का इस्तेमाल करें।",
         "oauth_private": "यह विकि वेरिफिकेशन के लिए OAuth2 का इस्तेमाल करता है। कृपया सर्वर से डायरेक्ट मैसेज सक्षम करें या `/verify` कमांड का इस्तेमाल करें ताकि मैं आपको व्यक्तिगत रूप से एक ऑथेंटिकेशन लिंक भेज सकूँ।",
         "oauth_private": "यह विकि वेरिफिकेशन के लिए OAuth2 का इस्तेमाल करता है। कृपया सर्वर से डायरेक्ट मैसेज सक्षम करें या `/verify` कमांड का इस्तेमाल करें ताकि मैं आपको व्यक्तिगत रूप से एक ऑथेंटिकेशन लिंक भेज सकूँ।",
         "oauth_used": "*OAuth2 द्वारा वेरिफाई किया गया*",
         "oauth_used": "*OAuth2 द्वारा वेरिफाई किया गया*",
-        "qualified": "इनके लिए अर्हता प्राप्त किया:",
-        "qualified_error": "इनके लिए अर्हता प्राप्त किया पर जोड़े न जा सकें:",
+        "qualified_add": "इसमें जोड़ा गया है:",
+        "qualified_add_error": "इसमें जोड़ा नहीं गया है:",
+        "qualified_remove": "इससे हटाया गया है:",
+        "qualified_remove_error": "इससे हटाया नहीं गया है:",
         "user_blocked": "**विकी सदस्य $1 ब्लॉक्ड है!**",
         "user_blocked": "**विकी सदस्य $1 ब्लॉक्ड है!**",
         "user_blocked_reply": "आप द्वारा लिंक किया गया विकी सदस्य **\"$1\" ब्लॉक्ड है!**",
         "user_blocked_reply": "आप द्वारा लिंक किया गया विकी सदस्य **\"$1\" ब्लॉक्ड है!**",
         "user_disabled": "**विकी अकाउंट $1 अक्षम है!**",
         "user_disabled": "**विकी अकाउंट $1 अक्षम है!**",

+ 133 - 9
i18n/ja.json

@@ -97,7 +97,7 @@
             " "
             " "
         ],
         ],
         "user": [
         "user": [
-            "ユーザー",
+            "利用者",
             "利用者",
             "利用者",
             " ",
             " ",
             " ",
             " ",
@@ -120,7 +120,7 @@
             "bytes": "$1 {{PLURAL:$2|バイト|バイト}} $3",
             "bytes": "$1 {{PLURAL:$2|バイト|バイト}} $3",
             "comment": "コメント:",
             "comment": "コメント:",
             "editor": "編集者:",
             "editor": "編集者:",
-            "minor": "(m)",
+            "minor": "()",
             "more": "その他にも",
             "more": "その他にも",
             "removed": "削除:",
             "removed": "削除:",
             "size": "差分:",
             "size": "差分:",
@@ -132,7 +132,7 @@
     },
     },
     "discussion": {
     "discussion": {
         "image": "画像を見る",
         "image": "画像を見る",
-        "main": "議論",
+        "main": "ディスカッション",
         "post": "ポスト",
         "post": "ポスト",
         "tags": "タグ:",
         "tags": "タグ:",
         "votes": "$1 {{PLURAL:$2|票|票}} ($3%)"
         "votes": "$1 {{PLURAL:$2|票|票}} ($3%)"
@@ -178,11 +178,25 @@
             },
             },
             "discussion": {
             "discussion": {
                 "post": {
                 "post": {
-                    "cmd": "discussion post <キーワード>"
+                    "cmd": "discussion post <キーワード>",
+                    "desc": "Fandom Wikiの一致する投稿されたディスカッションへのリンクで答えます。"
+                },
+                "thread": {
+                    "cmd": "discussion <キーワード>",
+                    "desc": "Fandom Wikiの一致するディスカッションスレッドへのリンクでお答えします。"
                 }
                 }
             },
             },
+            "fandom": {
+                "cmd": "?<wiki> <キーワード>",
+                "desc": "名前のついたFandom wikiの一致する記事へのリンクでお答えします:`https://<wiki>.fandom.com/`"
+            },
+            "gamepedia": {
+                "cmd": "!<wiki> <キーワード>",
+                "desc": "名前のついたGamepedia wikiの一致する記事へのリンクでお答えします: `https://<wiki>.gamepedia.com/`"
+            },
             "help": {
             "help": {
                 "admin": {
                 "admin": {
+                    "cmd": "help admin",
                     "desc": "Wiki-Botが全ての管理者コマンドを一覧表示します。"
                     "desc": "Wiki-Botが全ての管理者コマンドを一覧表示します。"
                 },
                 },
                 "command": {
                 "command": {
@@ -190,11 +204,17 @@
                     "desc": "コマンドの動作を知りたいですか?私がお答えしましょう!"
                     "desc": "コマンドの動作を知りたいですか?私がお答えしましょう!"
                 },
                 },
                 "default": {
                 "default": {
+                    "cmd": "help",
                     "desc": "Wiki-Botが実行できる全てのコマンドの一覧を表示します。"
                     "desc": "Wiki-Botが実行できる全てのコマンドの一覧を表示します。"
                 }
                 }
             },
             },
+            "info": {
+                "cmd": "info",
+                "desc": "自己紹介をします。"
+            },
             "inline": {
             "inline": {
                 "link": {
                 "link": {
+                    "cmd": "[[<ページ名>]]",
                     "desc": "Wiki-BotがWikiの記事への直リンクを返します。"
                     "desc": "Wiki-BotがWikiの記事への直リンクを返します。"
                 },
                 },
                 "template": {
                 "template": {
@@ -204,6 +224,7 @@
             },
             },
             "minecraft": {
             "minecraft": {
                 "bug": {
                 "bug": {
+                    "cmd": "bug <Minecraft バグ>",
                     "desc": "Wiki-BotがMinecraftバグトラッカーへのリンクを返します。"
                     "desc": "Wiki-BotがMinecraftバグトラッカーへのリンクを返します。"
                 },
                 },
                 "command": {
                 "command": {
@@ -217,7 +238,7 @@
             },
             },
             "mwprojects": {
             "mwprojects": {
                 "cmd": "!!<wiki> <キーワード>",
                 "cmd": "!!<wiki> <キーワード>",
-                "desc": "Wiki-Botが該当した記事のMediaWikiプロジェクトのページへのリンクを返します。例:`$1!!en.wikipedia.org Cookie`"
+                "desc": "Wiki-Botが該当した記事のMediaWikiプロジェクトのページへのリンクを返します。例:`$1!!ja.wikipedia.org クッキー`"
             },
             },
             "overview": {
             "overview": {
                 "cmd": "overview",
                 "cmd": "overview",
@@ -244,11 +265,11 @@
             "rcscript": {
             "rcscript": {
                 "add": {
                 "add": {
                     "cmd": "rcscript add [<wiki>]",
                     "cmd": "rcscript add [<wiki>]",
-                    "desc": "新しい最近の更のウェブフックを追加します。"
+                    "desc": "新しい最近の更のウェブフックを追加します。"
                 },
                 },
                 "default": {
                 "default": {
                     "cmd": "rcscript",
                     "cmd": "rcscript",
-                    "desc": "最近の更のウェブフックを変更します。"
+                    "desc": "最近の更のウェブフックを変更します。"
                 },
                 },
                 "delete": {
                 "delete": {
                     "cmd": "rcscript delete",
                     "cmd": "rcscript delete",
@@ -256,12 +277,115 @@
                 },
                 },
                 "display": {
                 "display": {
                     "cmd": "rcscript display <新規 表示モード>",
                     "cmd": "rcscript display <新規 表示モード>",
-                    "desc": "Wiki-BOTの最近の更新Webhookの表示モードを変更します。"
+                    "desc": "Wiki-Botの最近の更新Webhookの表示モードを変更します。"
                 },
                 },
                 "feeds": {
                 "feeds": {
                     "cmd": "rcscript feeds",
                     "cmd": "rcscript feeds",
-                    "desc": "Fandom Wikiの更新された議論のWebhookを最近の更新Webhookへ切り替えます。"
+                    "desc": "Fandom Wikiの更新されたディスカッションのウェブフックを最近の更新のウェブフックへ切り替えます。"
+                },
+                "lang": {
+                    "cmd": "rcscript lang <新規言語>",
+                    "desc": "Wiki-Botの最近の更新のウェブフックの言語を変更します。"
+                },
+                "wiki": {
+                    "cmd": "rcscript wiki <新規wiki>",
+                    "desc": "Web-Botの最近の更新ウェブフックのWikiを変更します。"
+                }
+            },
+            "search": {
+                "cmd": "search <検索キーワード>",
+                "desc": "Wiki-BotがWikiの検索ワードに該当した記事のリンクを返します。"
+            },
+            "settings": {
+                "channel": {
+                    "cmd": "settings channel",
+                    "desc": "Wiki-Botの現在のチャンネルのオーバーライドを変更します。"
+                },
+                "default": {
+                    "cmd": "settings",
+                    "desc": "このサーバーのWiki-Botの設定を変更します。"
+                },
+                "inline": {
+                    "cmd": "settings inline toggle",
+                    "desc": "このサーバーのWiki-Botのインラインコマンドを切り替えます。"
+                },
+                "lang": {
+                    "cmd": "settings lang <言語>",
+                    "desc": "このサーバーのWiki-Botの言語を変更します。"
+                },
+                "prefix": {
+                    "cmd": "settings prefix <プレフィックス>",
+                    "desc": "このサーバーのプレフィックスを変更します。"
+                },
+                "role": {
+                    "cmd": "settings role <権限>",
+                    "desc": "このサーバーのWiki-Botがコマンドを使用するための最低限の権限を設定します。"
+                },
+                "wiki": {
+                    "cmd": "settings wiki <wiki>",
+                    "desc": "このサーバーのWiki-BotのデフォルトのWikiを設定します。"
+                }
+            },
+            "test": {
+                "cmd": "test",
+                "desc": "私が活動していれば答えます!そうでなければしません。"
+            },
+            "user": {
+                "cmd": "ユーザー:<利用者名>",
+                "desc": "利用者に関する情報を表示します。"
+            },
+            "verification": {
+                "accountage": {
+                    "cmd": "verification <id> accountage <新規アカウントの登録日からの日数>",
+                    "desc": "アカウント作成から認証までの最短期間(日数)を変更します。"
+                },
+                "add": {
+                    "cmd": "verification add <ロール>",
+                    "desc": "新しい認証を追加します。`|`で区切られたリストを受け取ります。"
+                },
+                "channel": {
+                    "cmd": "verification <id> channel <新しいチャンネル>",
+                    "desc": "認証チャンネルを変更します。`|`で区切られたリストを受け取ります。"
+                },
+                "default": {
+                    "cmd": "verification",
+                    "desc": "`$1verify`コマンドで使用する認証方法を変更します。"
+                },
+                "delete": {
+                    "cmd": "verification <id> delete",
+                    "desc": "認証を削除します。"
+                },
+                "editcount": {
+                    "cmd": "verification <id> editcount <編集回数>",
+                    "desc": "認証のための最低編集回数を変更します。"
+                },
+                "postcount": {
+                    "cmd": "verification <id> postcount <新規投稿数>",
+                    "desc": "認証のためのディスカッションの最低投稿回数を変更します。\n\t• 投稿数と編集数のどちらかにチェックが入るように、マイナスの数値を入力してください。\n\t• 投稿数と編集数の両方をまとめてチェックする場合は、`null`を入力してください。"
+                },
+                "rename": {
+                    "cmd": "verification <id> rename",
+                    "desc": "Discordのユーザー名を認証時のWiki上のメンバー名に変更するかどうかを変更します。"
+                },
+                "role": {
+                    "cmd": "verification <id> role <新規ロール>",
+                    "desc": "認証のためのロールを変更します。`|`で区切られたリストを受け取ります。"
+                },
+                "usergroup": {
+                    "cmd": "verification <id> usergroup <新規ユーザーグループ>",
+                    "desc": "認証のためのユーザーグループを変更します。`|`で区切られたリストを指定します。\n\t•リストの最初の要素に `AND` を入れると、リスト内のすべてのユーザーグループが認証の対象となります。"
                 }
                 }
+            },
+            "verify": {
+                "cmd": "verify <Wikiユーザー名>",
+                "desc": "このコマンドを使用して、DiscordアカウントとWikiアカウントを照合し、Wikiアカウントに一致するロールを取得します。"
+            },
+            "voice": {
+                "cmd": "voice",
+                "desc": "このボイスチャンネルの全員に特定のロールを与えるようにします。"
+            },
+            "wikia": {
+                "cmd": "??<wiki> <検索キーワード>"
             }
             }
         }
         }
     },
     },

+ 0 - 4
i18n/ko.json

@@ -485,7 +485,6 @@
         "all_inactive": "위키 최근 바뀜과 피드 기반 바뀜이 동시에 비활성화되면 안 됩니다.",
         "all_inactive": "위키 최근 바뀜과 피드 기반 바뀜이 동시에 비활성화되면 안 됩니다.",
         "audit_reason": "\"$1\" 위키의 최근 바뀜 웹훅",
         "audit_reason": "\"$1\" 위키의 최근 바뀜 웹훅",
         "audit_reason_delete": "삭제된 최근 바뀜 웹훅",
         "audit_reason_delete": "삭제된 최근 바뀜 웹훅",
-        "audit_reason_move": "이동된 최근 바뀜 웹훅",
         "blocked": "이 위키는 최근 바뀜 웹훅으로 추가되지 못하도록 차단되어 있습니다!",
         "blocked": "이 위키는 최근 바뀜 웹훅으로 추가되지 못하도록 차단되어 있습니다!",
         "blocked_reason": "이 위키는 `$1` 때문에 최근 바뀜 웹훅으로 추가되지 못하도록 차단되어 있습니다!",
         "blocked_reason": "이 위키는 `$1` 때문에 최근 바뀜 웹훅으로 추가되지 못하도록 차단되어 있습니다!",
         "channel": "채널:",
         "channel": "채널:",
@@ -740,7 +739,6 @@
         "posteditcount": "편집 횟수와 게시글 횟수 통합:",
         "posteditcount": "편집 횟수와 게시글 횟수 통합:",
         "rename": "닉네임 변경하기:",
         "rename": "닉네임 변경하기:",
         "rename_no_permission": "**$1 봇은 위키 계정 이름을 강제하기 위해 `닉네임 관리하기` 권한이 필요해요!**",
         "rename_no_permission": "**$1 봇은 위키 계정 이름을 강제하기 위해 `닉네임 관리하기` 권한이 필요해요!**",
-        "role": "역할:",
         "role_deleted": "**$1 역할이 더이상 존재하지 않는 것 같아요!**",
         "role_deleted": "**$1 역할이 더이상 존재하지 않는 것 같아요!**",
         "role_managed": "제공한 역할을 부여할 수 없었어요.",
         "role_managed": "제공한 역할을 부여할 수 없었어요.",
         "role_max": "역할을 너무 많이 지정했어요.",
         "role_max": "역할을 너무 많이 지정했어요.",
@@ -770,8 +768,6 @@
         "help_subpage": "디스코드 태그($1)를 사용자 문서의 Discord 하위문서에 생성해 주세요:",
         "help_subpage": "디스코드 태그($1)를 사용자 문서의 Discord 하위문서에 생성해 주세요:",
         "missing": "이 채널에 인증이 설정되어 있지 않아요.",
         "missing": "이 채널에 인증이 설정되어 있지 않아요.",
         "notice": "공지:",
         "notice": "공지:",
-        "qualified": "적합:",
-        "qualified_error": "적합하지만 추가할 수 없음:",
         "user_blocked": "**$1 사용자는 차단되어 있습니다!**",
         "user_blocked": "**$1 사용자는 차단되어 있습니다!**",
         "user_blocked_reply": "연결된 위키 계정 **\"$1\" 이(가) 차단되어 있습니다!**",
         "user_blocked_reply": "연결된 위키 계정 **\"$1\" 이(가) 차단되어 있습니다!**",
         "user_disabled": "**$1 사용자는 비활성화되어 있습니다!**",
         "user_disabled": "**$1 사용자는 비활성화되어 있습니다!**",

+ 0 - 1
i18n/nl.json

@@ -443,7 +443,6 @@
         "new_role": "<nieuwe rol>",
         "new_role": "<nieuwe rol>",
         "new_usergroup": "<nieuwe gebruikersgroep>",
         "new_usergroup": "<nieuwe gebruikersgroep>",
         "or": "of",
         "or": "of",
-        "role": "Rol:",
         "role_managed": "de opgegeven rol kan niet toegekend worden.",
         "role_managed": "de opgegeven rol kan niet toegekend worden.",
         "role_max": "je hebt te veel rollen opgegeven.",
         "role_max": "je hebt te veel rollen opgegeven.",
         "role_missing": "de opgegeven rol bestaat niet.",
         "role_missing": "de opgegeven rol bestaat niet.",

+ 20 - 5
i18n/pl.json

@@ -494,7 +494,8 @@
         "all_inactive": "śledzenie ostatnich zmian oraz dyskusji nie może być wyłączone jednocześnie.",
         "all_inactive": "śledzenie ostatnich zmian oraz dyskusji nie może być wyłączone jednocześnie.",
         "audit_reason": "Webhook ostatnich zmian dla „$1”",
         "audit_reason": "Webhook ostatnich zmian dla „$1”",
         "audit_reason_delete": "Usunięto webhook ostatnich zmian",
         "audit_reason_delete": "Usunięto webhook ostatnich zmian",
-        "audit_reason_move": "Przeniesiono webhook ostatnich zmian",
+        "audit_reason_edit": "Zaktualizowano webhook ostatnich zmian",
+        "avatar": "Awatar webhooka:",
         "blocked": "ta wiki nie może zostać dodana do webhooka ostatnich zmian gdyż została zablokowana!",
         "blocked": "ta wiki nie może zostać dodana do webhooka ostatnich zmian gdyż została zablokowana!",
         "blocked_reason": "zablokowano możliwość dodawania tej wiki do webhooka ostatnich zmian z powodu `$1`!",
         "blocked_reason": "zablokowano możliwość dodawania tej wiki do webhooka ostatnich zmian z powodu `$1`!",
         "channel": "Kanał:",
         "channel": "Kanał:",
@@ -528,6 +529,7 @@
         "lang": "Język:",
         "lang": "Język:",
         "max_entries": "osiągnięto maksymalną ilość webhooków ostatnich zmian.",
         "max_entries": "osiągnięto maksymalną ilość webhooków ostatnich zmian.",
         "missing": "obecnie nie istnieją żadne webhooki ostatnich zmian na tym serwerze.",
         "missing": "obecnie nie istnieją żadne webhooki ostatnich zmian na tym serwerze.",
+        "name": "Nazwa webhooka:",
         "new_lang": "<nowy język>",
         "new_lang": "<nowy język>",
         "new_wiki": "<link do wiki>",
         "new_wiki": "<link do wiki>",
         "no_feeds": "wiki dla tego webhooka nie ma włączonej usługi Dyskusji.",
         "no_feeds": "wiki dla tego webhooka nie ma włączonej usługi Dyskusji.",
@@ -545,6 +547,7 @@
             "blocked_reason": "Ten webhook ostatnich zmian zostanie usunięty gdyż wiki została zablokowana za `$1`!",
             "blocked_reason": "Ten webhook ostatnich zmian zostanie usunięty gdyż wiki została zablokowana za `$1`!",
             "created": "Webhook ostatnich zmian dla $1 został dodany do tego kanału.",
             "created": "Webhook ostatnich zmian dla $1 został dodany do tego kanału.",
             "dashboard": {
             "dashboard": {
+                "avatar": "• Awatar webhooka został zmieniony.",
                 "channel": "• Webhook został przeniesiony do tego kanału.",
                 "channel": "• Webhook został przeniesiony do tego kanału.",
                 "disabled_feeds": "• Zmiany bazowane na technologii Feedów takie jak dyskusje, tablice wiadomości oraz komentarze do artykułów zostały wyłączone.",
                 "disabled_feeds": "• Zmiany bazowane na technologii Feedów takie jak dyskusje, tablice wiadomości oraz komentarze do artykułów zostały wyłączone.",
                 "disabled_rc": "• Ostatnie zmiany na wiki zostały wyłączone.",
                 "disabled_rc": "• Ostatnie zmiany na wiki zostały wyłączone.",
@@ -555,6 +558,7 @@
                 "enabled_feeds": "• Zmiany bazowane na technologii Feedów takie jak dyskusje, tablice wiadomości oraz komentarze do artykułów zostały włączone.",
                 "enabled_feeds": "• Zmiany bazowane na technologii Feedów takie jak dyskusje, tablice wiadomości oraz komentarze do artykułów zostały włączone.",
                 "enabled_rc": "• Ostatnie zmiany na wiki zostały włączone.",
                 "enabled_rc": "• Ostatnie zmiany na wiki zostały włączone.",
                 "lang": "• Język został zmieniony na $1.",
                 "lang": "• Język został zmieniony na $1.",
+                "name": "• Nazwa webhooka została zmieniona na „$1”.",
                 "updated": "Webhook ostatnich zmian został zaktualizowany:",
                 "updated": "Webhook ostatnich zmian został zaktualizowany:",
                 "wiki": "• Wiki została zmieniona na $1."
                 "wiki": "• Wiki została zmieniona na $1."
             },
             },
@@ -590,6 +594,7 @@
         "special": "Zawartość tej strony specjalnej:"
         "special": "Zawartość tej strony specjalnej:"
     },
     },
     "settings": {
     "settings": {
+        "button": "Otwórz panel sterowania",
         "channel current": "obecne ustawienia dla tego kanału:",
         "channel current": "obecne ustawienia dla tego kanału:",
         "channel lang": "językiem tego kanału jest:",
         "channel lang": "językiem tego kanału jest:",
         "channel langchanged": "zmieniono język kanału na:",
         "channel langchanged": "zmieniono język kanału na:",
@@ -739,17 +744,21 @@
         "current_selected": "to weryfikacja `$1` dla tego serwera:",
         "current_selected": "to weryfikacja `$1` dla tego serwera:",
         "dashboard": {
         "dashboard": {
             "added": "$1 dodał weryfikację z id `$2`.",
             "added": "$1 dodał weryfikację z id `$2`.",
+            "added_notice": "$1 dodał(a) dodatkowe powiadomienia weryfikacji.",
             "removed": "$1 usunął weryfikację z id `$2`.",
             "removed": "$1 usunął weryfikację z id `$2`.",
-            "updated": "$1 zaktualizował weryfikację z id `$2`."
+            "updated": "$1 zaktualizował weryfikację z id `$2`.",
+            "updated_notice": "$1 zaktualizował(a) dodatkowe powiadomienia weryfikacji."
         },
         },
         "delete_current": "Usuń tę weryfikację:",
         "delete_current": "Usuń tę weryfikację:",
         "deleted": "weryfikacja została usunięta.",
         "deleted": "weryfikacja została usunięta.",
         "disabled": "wyłączone",
         "disabled": "wyłączone",
         "editcount": "Ilość edycji:",
         "editcount": "Ilość edycji:",
         "enabled": "włączone",
         "enabled": "włączone",
+        "flag_logall": "Logowanie nieudanych weryfikacji:",
         "flag_private": "Wysyłanie odpowiedzi do komendy weryfikacyjnej prywatnie:",
         "flag_private": "Wysyłanie odpowiedzi do komendy weryfikacyjnej prywatnie:",
         "indays": "(w dniach)",
         "indays": "(w dniach)",
         "logging": "Kanał logujący:",
         "logging": "Kanał logujący:",
+        "match": "Powiadomienie o niespełnionych wymaganiach:",
         "max_entries": "osiągnięto maksymalną ilość weryfikacji.",
         "max_entries": "osiągnięto maksymalną ilość weryfikacji.",
         "missing": "na tym serwerze nie istnieją żadne weryfikacje na tę chwilę.",
         "missing": "na tym serwerze nie istnieją żadne weryfikacje na tę chwilę.",
         "new_accountage": "<nowy wiek konta>",
         "new_accountage": "<nowy wiek konta>",
@@ -765,11 +774,13 @@
         "posteditcount": "Łączona ilość edycji oraz postów w dyskusjach:",
         "posteditcount": "Łączona ilość edycji oraz postów w dyskusjach:",
         "rename": "Zmień nazwę użytkownika:",
         "rename": "Zmień nazwę użytkownika:",
         "rename_no_permission": "**$1 wymaga uprawnienia `Zarządzanie pseudonimami` do zmiany nazw użytkowników na wiki!**",
         "rename_no_permission": "**$1 wymaga uprawnienia `Zarządzanie pseudonimami` do zmiany nazw użytkowników na wiki!**",
-        "role": "Rola:",
+        "role_add": "Role do dodania:",
         "role_deleted": "**Nie wygląda na to aby rola $1 nadal istniała!**",
         "role_deleted": "**Nie wygląda na to aby rola $1 nadal istniała!**",
         "role_managed": "podana rola nie może zostać ustawiona.",
         "role_managed": "podana rola nie może zostać ustawiona.",
         "role_max": "podano zbyt dużo ról.",
         "role_max": "podano zbyt dużo ról.",
         "role_missing": "podana rola nie istnieje.",
         "role_missing": "podana rola nie istnieje.",
+        "role_none": "brak",
+        "role_remove": "Role do usunięcia:",
         "role_too_high": "**Rola $1 jest wyżej niż najwyższa rola $2, dlatego nie będzie możliwe jej ustawienie!**",
         "role_too_high": "**Rola $1 jest wyżej niż najwyższa rola $2, dlatego nie będzie możliwe jej ustawienie!**",
         "save_failed": "niestety, weryfikacja nie mogła zostać zapisana, spróbuj ponownie później.",
         "save_failed": "niestety, weryfikacja nie mogła zostać zapisana, spróbuj ponownie później.",
         "toggle": "(przełącz)",
         "toggle": "(przełącz)",
@@ -781,6 +792,8 @@
     },
     },
     "verify": {
     "verify": {
         "audit_reason": "Zweryfikowano jako „$1”",
         "audit_reason": "Zweryfikowano jako „$1”",
+        "button_again": "Sprawdź ponownie",
+        "button_wrong_user": "Nie możesz sprawdzić tej weryfikacji ponownie, to weryfikacja $1!",
         "discord": "{{GENDER:$1|Użytkownik Discord|Użytkowniczka Discord|Użytkownik/Użytkowniczka Discord}}:",
         "discord": "{{GENDER:$1|Użytkownik Discord|Użytkowniczka Discord|Użytkownik/Użytkowniczka Discord}}:",
         "empty": "*puste*",
         "empty": "*puste*",
         "error": "Weryfikacja nie powiodła się z powodu błędu.",
         "error": "Weryfikacja nie powiodła się z powodu błędu.",
@@ -801,8 +814,10 @@
         "oauth_message_dm": "Użyj tego linku aby uwierzytelnić swoje konto wiki dla $1.",
         "oauth_message_dm": "Użyj tego linku aby uwierzytelnić swoje konto wiki dla $1.",
         "oauth_private": "wiki używa OAuth2 do weryfikacji. Aby się uwierzytelnić, zezwól na komunikację przez prywatne wiadomości dla tego serwera lub użyj komendy `/verify` aby zweryfikować się za pomocą linku wysłanego prywatnie.",
         "oauth_private": "wiki używa OAuth2 do weryfikacji. Aby się uwierzytelnić, zezwól na komunikację przez prywatne wiadomości dla tego serwera lub użyj komendy `/verify` aby zweryfikować się za pomocą linku wysłanego prywatnie.",
         "oauth_used": "*Zweryfikowano z użyciem OAuth2*",
         "oauth_used": "*Zweryfikowano z użyciem OAuth2*",
-        "qualified": "Pozwala na otrzymanie ról:",
-        "qualified_error": "Pozwala na otrzymanie ról, których nie udało się dodać:",
+        "qualified_add": "Dodano do:",
+        "qualified_add_error": "Nie udało się dodać:",
+        "qualified_remove": "Usunięto z:",
+        "qualified_remove_error": "Nie można usunąć z:",
         "user_blocked": "**Konto {{GENDER:$2|użytkownika|użytkowniczki|użytkownika/użytkowniczki}} $1 jest zablokowane!**",
         "user_blocked": "**Konto {{GENDER:$2|użytkownika|użytkowniczki|użytkownika/użytkowniczki}} $1 jest zablokowane!**",
         "user_blocked_reply": "konto zalinkowanego {{GENDER:$2|użytkownika|użytkowniczki|użytkownika/użytkowniczki}} wiki **„$1” jest zablokowane!**",
         "user_blocked_reply": "konto zalinkowanego {{GENDER:$2|użytkownika|użytkowniczki|użytkownika/użytkowniczki}} wiki **„$1” jest zablokowane!**",
         "user_disabled": "**Konto wiki $1 jest wyłączone!**",
         "user_disabled": "**Konto wiki $1 jest wyłączone!**",

+ 9 - 4
i18n/pt-br.json

@@ -494,7 +494,6 @@
         "all_inactive": "você não pode ter mudanças da wiki e alterações baseadas em feeds desativadas ao mesmo tempo.",
         "all_inactive": "você não pode ter mudanças da wiki e alterações baseadas em feeds desativadas ao mesmo tempo.",
         "audit_reason": "Mudanças recentes no webhook para \"$1\"",
         "audit_reason": "Mudanças recentes no webhook para \"$1\"",
         "audit_reason_delete": "Removido o webhook das mudanças recentes",
         "audit_reason_delete": "Removido o webhook das mudanças recentes",
-        "audit_reason_move": "Movido o webhook das mudanças recentes",
         "blocked": "essa wiki foi impedido de ser adicionado como um webhook de mudanças recentes!",
         "blocked": "essa wiki foi impedido de ser adicionado como um webhook de mudanças recentes!",
         "blocked_reason": "essa wiki foi bloqueada de ser adicionado como um webhook de mudanças recentes para `$1`!",
         "blocked_reason": "essa wiki foi bloqueada de ser adicionado como um webhook de mudanças recentes para `$1`!",
         "channel": "Canal:",
         "channel": "Canal:",
@@ -750,6 +749,8 @@
         "disabled": "desativado",
         "disabled": "desativado",
         "editcount": "Contagem de edições:",
         "editcount": "Contagem de edições:",
         "enabled": "ativado",
         "enabled": "ativado",
+        "flag_logall": "Registro de verificações malsucedidas:",
+        "flag_private": "Envio de respostas de comando de verificação em particular:",
         "indays": "(em dias)",
         "indays": "(em dias)",
         "logging": "Canal de registro:",
         "logging": "Canal de registro:",
         "match": "Aviso de requisitos ausentes:",
         "match": "Aviso de requisitos ausentes:",
@@ -769,11 +770,13 @@
         "posteditcount": "Edição e contagem de postagens combinadas:",
         "posteditcount": "Edição e contagem de postagens combinadas:",
         "rename": "Mudar apelido:",
         "rename": "Mudar apelido:",
         "rename_no_permission": "**$1 não tem a permissão de `Gerenciar apelidos` para substituir o apelido pelos nomes de usuário da wiki!**",
         "rename_no_permission": "**$1 não tem a permissão de `Gerenciar apelidos` para substituir o apelido pelos nomes de usuário da wiki!**",
-        "role": "Cargo:",
+        "role_add": "Cargo para adicionar:",
         "role_deleted": "**O cargo $1 parece não existir mais! **",
         "role_deleted": "**O cargo $1 parece não existir mais! **",
         "role_managed": "o cargo fornecido não pode ser atribuído.",
         "role_managed": "o cargo fornecido não pode ser atribuído.",
         "role_max": "muitos cargos fornecidos.",
         "role_max": "muitos cargos fornecidos.",
         "role_missing": "o cargo fornecido não existe.",
         "role_missing": "o cargo fornecido não existe.",
+        "role_none": "nenhum",
+        "role_remove": "Cargo para remover:",
         "role_too_high": "**O cargo $1 é muito alto para o $2 atribuí-lo!**",
         "role_too_high": "**O cargo $1 é muito alto para o $2 atribuí-lo!**",
         "save_failed": "infelizmente a verificação não pôde ser salva, tente novamente mais tarde.",
         "save_failed": "infelizmente a verificação não pôde ser salva, tente novamente mais tarde.",
         "success": "Aviso de sucesso:",
         "success": "Aviso de sucesso:",
@@ -808,8 +811,10 @@
         "oauth_message_dm": "Por favor, use este link para autenticar sua conta de wiki para $1.",
         "oauth_message_dm": "Por favor, use este link para autenticar sua conta de wiki para $1.",
         "oauth_private": "a wiki usa OAuth2 para verificação. Ative as mensagens diretas deste servidor ou use o comando `/verify` para que eu possa enviar um link de autenticação de forma privada.",
         "oauth_private": "a wiki usa OAuth2 para verificação. Ative as mensagens diretas deste servidor ou use o comando `/verify` para que eu possa enviar um link de autenticação de forma privada.",
         "oauth_used": "*Verificado usando OAuth2 *",
         "oauth_used": "*Verificado usando OAuth2 *",
-        "qualified": "Qualificado para o cargo:",
-        "qualified_error": "Não foi possível adicionar, mas está qualificado para o cargo:",
+        "qualified_add": "Adicionar para:",
+        "qualified_add_error": "Não pode ser adicionado a:",
+        "qualified_remove": "Removido de:",
+        "qualified_remove_error": "Não pode ser removido de:",
         "user_blocked": "**{{GENDER:$2|O usuário|A usuária}} $1 está {{GENDER:$2|bloqueado|bloqueada}} na wiki!**",
         "user_blocked": "**{{GENDER:$2|O usuário|A usuária}} $1 está {{GENDER:$2|bloqueado|bloqueada}} na wiki!**",
         "user_blocked_reply": "Seu usuário vinculado da wiki **\"$1\" está bloqueado!**",
         "user_blocked_reply": "Seu usuário vinculado da wiki **\"$1\" está bloqueado!**",
         "user_disabled": "**A conta $1 está desabilitado na wiki!**",
         "user_disabled": "**A conta $1 está desabilitado na wiki!**",

+ 14 - 4
i18n/ru.json

@@ -494,7 +494,8 @@
         "all_inactive": "нельзя одновременно отключить отслеживание и правок на вики, и изменений в лентах.",
         "all_inactive": "нельзя одновременно отключить отслеживание и правок на вики, и изменений в лентах.",
         "audit_reason": "Вебхук свежих правок для \"$1\"",
         "audit_reason": "Вебхук свежих правок для \"$1\"",
         "audit_reason_delete": "Вебхук свежих правок удалён",
         "audit_reason_delete": "Вебхук свежих правок удалён",
-        "audit_reason_move": "Вебхук свежих правок перемещён",
+        "audit_reason_edit": "Вебхук свежих правок обновлён",
+        "avatar": "Аватар вебхука:",
         "blocked": "эта вики была блокирована от добавления к вебхуку свежих правок!",
         "blocked": "эта вики была блокирована от добавления к вебхуку свежих правок!",
         "blocked_reason": "эта вики была блокирована от добавления к вебхуку свежих правок по причине: `$1`!",
         "blocked_reason": "эта вики была блокирована от добавления к вебхуку свежих правок по причине: `$1`!",
         "channel": "Канал:",
         "channel": "Канал:",
@@ -528,6 +529,7 @@
         "lang": "Язык:",
         "lang": "Язык:",
         "max_entries": "вы уже достигли максимального количества вебхуков свежих правок.",
         "max_entries": "вы уже достигли максимального количества вебхуков свежих правок.",
         "missing": "на этом сервере ещё нет вебхуков свежих правок.",
         "missing": "на этом сервере ещё нет вебхуков свежих правок.",
+        "name": "Имя вебхука:",
         "new_lang": "<новый язык>",
         "new_lang": "<новый язык>",
         "new_wiki": "<ссылка на вики>",
         "new_wiki": "<ссылка на вики>",
         "no_feeds": "на вики для этого вебхука не поддерживаются или не включены функции на основе лент, такие как обсуждения, стены обсуждения и комментарии к статьям.",
         "no_feeds": "на вики для этого вебхука не поддерживаются или не включены функции на основе лент, такие как обсуждения, стены обсуждения и комментарии к статьям.",
@@ -545,6 +547,7 @@
             "blocked_reason": "Этот вебхук свежих правок будет удалён, потому что эта вики была блокирована от добавления к вебхуку свежих правок по причине: `$1`!",
             "blocked_reason": "Этот вебхук свежих правок будет удалён, потому что эта вики была блокирована от добавления к вебхуку свежих правок по причине: `$1`!",
             "created": "Вебхук свежих правок для $1 был создан в этом канале.",
             "created": "Вебхук свежих правок для $1 был создан в этом канале.",
             "dashboard": {
             "dashboard": {
+                "avatar": "• Аватар вебхука был изменён.",
                 "channel": "• Вебхук был перемещён в этот канал.",
                 "channel": "• Вебхук был перемещён в этот канал.",
                 "disabled_feeds": "• Отслеживание изменений в лентах, таких как обсуждения, стены обсуждения и комментарии к статьям, было отключено.",
                 "disabled_feeds": "• Отслеживание изменений в лентах, таких как обсуждения, стены обсуждения и комментарии к статьям, было отключено.",
                 "disabled_rc": "• Отслеживание правок на вики было отключено.",
                 "disabled_rc": "• Отслеживание правок на вики было отключено.",
@@ -555,6 +558,7 @@
                 "enabled_feeds": "• Отслеживание изменений в лентах, таких как обсуждения, стены обсуждения и комментарии к статьям, было включено.",
                 "enabled_feeds": "• Отслеживание изменений в лентах, таких как обсуждения, стены обсуждения и комментарии к статьям, было включено.",
                 "enabled_rc": "• Отслеживание правок на вики было включено.",
                 "enabled_rc": "• Отслеживание правок на вики было включено.",
                 "lang": "• Язык был изменён на $1.",
                 "lang": "• Язык был изменён на $1.",
+                "name": "• Имя вебхука было изменено на \"$1\".",
                 "updated": "Этот вебхук свежих правок был изменён:",
                 "updated": "Этот вебхук свежих правок был изменён:",
                 "wiki": "• Вики была изменена на $1."
                 "wiki": "• Вики была изменена на $1."
             },
             },
@@ -750,6 +754,8 @@
         "disabled": "отключено",
         "disabled": "отключено",
         "editcount": "Количество правок:",
         "editcount": "Количество правок:",
         "enabled": "включено",
         "enabled": "включено",
+        "flag_logall": "Запись неуспешных попыток верификации в журнал:",
+        "flag_private": "Отвечать на запросы верификации приватно:",
         "indays": "(в днях)",
         "indays": "(в днях)",
         "logging": "Канал для журнала:",
         "logging": "Канал для журнала:",
         "match": "Уведомление о несоответствии требованиям:",
         "match": "Уведомление о несоответствии требованиям:",
@@ -769,11 +775,13 @@
         "posteditcount": "Количество правок и количество постов, вместе взятые:",
         "posteditcount": "Количество правок и количество постов, вместе взятые:",
         "rename": "Изменить псевдоним:",
         "rename": "Изменить псевдоним:",
         "rename_no_permission": "**$1 отсутствует разрешение `Управлять никнеймами` для принудительного использования имён участников вики!**",
         "rename_no_permission": "**$1 отсутствует разрешение `Управлять никнеймами` для принудительного использования имён участников вики!**",
-        "role": "Роль:",
+        "role_add": "Добавить роль:",
         "role_deleted": "**Роль $1 больше не существует!**",
         "role_deleted": "**Роль $1 больше не существует!**",
         "role_managed": "данная роль не может быть назначена.",
         "role_managed": "данная роль не может быть назначена.",
         "role_max": "Вы достигли максимального кол-ва ролей.",
         "role_max": "Вы достигли максимального кол-ва ролей.",
         "role_missing": "такой роли не существует.",
         "role_missing": "такой роли не существует.",
+        "role_none": "нет",
+        "role_remove": "Убрать роль:",
         "role_too_high": "**Роль $1 слишком высока $2 для назначения!**",
         "role_too_high": "**Роль $1 слишком высока $2 для назначения!**",
         "save_failed": "к сожалению, проверка не сохранена, повторите попытку позже.",
         "save_failed": "к сожалению, проверка не сохранена, повторите попытку позже.",
         "success": "Уведомление об успехе:",
         "success": "Уведомление об успехе:",
@@ -808,8 +816,10 @@
         "oauth_message_dm": "Пройдите по этой ссылке, чтобы авторизовать свой вики-аккаунт для сервера $1.",
         "oauth_message_dm": "Пройдите по этой ссылке, чтобы авторизовать свой вики-аккаунт для сервера $1.",
         "oauth_private": "данная вики использует OAuth2 для верификации. Разрешите личные сообщения от участников сервера или используйте команду `/verify`, чтобы я смог приватно отправить вам ссылку для верификации.",
         "oauth_private": "данная вики использует OAuth2 для верификации. Разрешите личные сообщения от участников сервера или используйте команду `/verify`, чтобы я смог приватно отправить вам ссылку для верификации.",
         "oauth_used": "*Верифицирован с помощью OAuth2*",
         "oauth_used": "*Верифицирован с помощью OAuth2*",
-        "qualified": "Оценен для:",
-        "qualified_error": "Квалифицирован на, но не может быть добавлен:",
+        "qualified_add": "Добавлено:",
+        "qualified_add_error": "Не может быть добавлено:",
+        "qualified_remove": "Убрано:",
+        "qualified_remove_error": "Не может быть убрано:",
         "user_blocked": "**Wiki-пользователь $1 заблокирован.**",
         "user_blocked": "**Wiki-пользователь $1 заблокирован.**",
         "user_blocked_reply": "Ваш связанный wiki-пользователь **\"$1\" заблокирован!**",
         "user_blocked_reply": "Ваш связанный wiki-пользователь **\"$1\" заблокирован!**",
         "user_disabled": "**Wiki-пользователь $1 отключен!**",
         "user_disabled": "**Wiki-пользователь $1 отключен!**",

+ 15 - 4
i18n/sv.json

@@ -487,7 +487,6 @@
         "added": "En \"senaste ändringarna\"-webhook har nu blivit tillagt för:",
         "added": "En \"senaste ändringarna\"-webhook har nu blivit tillagt för:",
         "audit_reason": "\"Senaste ändringarna\"-webhook för \"$1\"",
         "audit_reason": "\"Senaste ändringarna\"-webhook för \"$1\"",
         "audit_reason_delete": "Tog bort \"senaste ändringarna\"-webhooken",
         "audit_reason_delete": "Tog bort \"senaste ändringarna\"-webhooken",
-        "audit_reason_move": "Flyttade \"senaste ändringarna\"-webhooken",
         "blocked": "denna wiki har blockerats från att lägga till en \"senaste ändringarna\"-webhook!",
         "blocked": "denna wiki har blockerats från att lägga till en \"senaste ändringarna\"-webhook!",
         "blocked_reason": "denna wiki har blockerats från att lägga till en \"senaste ändringarna\"-webhook på grund av`$1`!",
         "blocked_reason": "denna wiki har blockerats från att lägga till en \"senaste ändringarna\"-webhook på grund av`$1`!",
         "channel": "Kanal:",
         "channel": "Kanal:",
@@ -739,8 +738,11 @@
         "disabled": "inaktiverad",
         "disabled": "inaktiverad",
         "editcount": "Antal redigeringar:",
         "editcount": "Antal redigeringar:",
         "enabled": "aktiverad",
         "enabled": "aktiverad",
+        "flag_logall": "Loggning av misslyckade verifieringar:",
+        "flag_private": "Privat svar vid användning av verifieringskommandot:",
         "indays": "(i dagar)",
         "indays": "(i dagar)",
         "logging": "Logg kanal:",
         "logging": "Logg kanal:",
+        "match": "Meddelande om krav som saknas:",
         "max_entries": "du har redan nått den högsta gränsen för antal verifikationer.",
         "max_entries": "du har redan nått den högsta gränsen för antal verifikationer.",
         "missing": "det finns ännu inte några verifikationer för den här servern.",
         "missing": "det finns ännu inte några verifikationer för den här servern.",
         "new_accountage": "",
         "new_accountage": "",
@@ -757,11 +759,13 @@
         "posteditcount": "Antal ändringar och inlägg tillsammans:",
         "posteditcount": "Antal ändringar och inlägg tillsammans:",
         "rename": "Ändra smeknamn:",
         "rename": "Ändra smeknamn:",
         "rename_no_permission": "**$1 saker behörigheten `Ändra Smeknamn`, detta för att kunna tvinga wiki användarnamn!**",
         "rename_no_permission": "**$1 saker behörigheten `Ändra Smeknamn`, detta för att kunna tvinga wiki användarnamn!**",
-        "role": "Roll:",
+        "role_add": "Roll att lägga till:",
         "role_deleted": "**Rollen $1 ser inte ut att existera längre!**",
         "role_deleted": "**Rollen $1 ser inte ut att existera längre!**",
         "role_managed": "den angivna rollen kan inte tilldelas.",
         "role_managed": "den angivna rollen kan inte tilldelas.",
         "role_max": "du angav för många roller.",
         "role_max": "du angav för många roller.",
         "role_missing": "den angivna rollen existerar inte.",
         "role_missing": "den angivna rollen existerar inte.",
+        "role_none": "ingen",
+        "role_remove": "Roll att ta bort:",
         "role_too_high": "**Rolle $1 är för hög för att $2 ska kunna tilldela den!**",
         "role_too_high": "**Rolle $1 är för hög för att $2 ska kunna tilldela den!**",
         "save_failed": "verifikationen kunde tyvärr inte sparas, vänligen försök igen senare.",
         "save_failed": "verifikationen kunde tyvärr inte sparas, vänligen försök igen senare.",
         "updated": "verifikationen har uppdaterats:",
         "updated": "verifikationen har uppdaterats:",
@@ -789,8 +793,15 @@
         "help_subpage": "Lägg till din Discord-tagg ($1) på din Discord-undersida på wikin:",
         "help_subpage": "Lägg till din Discord-tagg ($1) på din Discord-undersida på wikin:",
         "missing": "det finns inte någon verifikation uppsatt för den här kanalen.",
         "missing": "det finns inte någon verifikation uppsatt för den här kanalen.",
         "notice": "Notera:",
         "notice": "Notera:",
-        "qualified": "Kvalifierad för:",
-        "qualified_error": "Kvalificerad för, men kan inte lägga till:",
+        "oauth_button": "Autentisera",
+        "oauth_message": "Använd [denna länk]($1) för att autentisera ditt wiki-konto.",
+        "oauth_message_dm": "Använd den här länken för att autentisera ditt wiki-konto för $1.",
+        "oauth_private": "wikinuses OAuth2 för verifiering. Aktivera direktmeddelanden från den här servern eller använd `/verify`-kommandot så att jag kan skicka en autentiseringslänk privat.",
+        "oauth_used": "* Verifierad med OAuth2 *",
+        "qualified_add": "Tillagd i:",
+        "qualified_add_error": "Kan inte läggas till i:",
+        "qualified_remove": "Borttagen från:",
+        "qualified_remove_error": "Kan inte tas bort från:",
         "user_blocked": "**Wiki-användaren $1 är blockerad!**",
         "user_blocked": "**Wiki-användaren $1 är blockerad!**",
         "user_blocked_reply": "din länkade Wiki-användare **\"$1\" är blockerad!**",
         "user_blocked_reply": "din länkade Wiki-användare **\"$1\" är blockerad!**",
         "user_disabled": "**Wiki-kontot $1 är inaktiverat!**",
         "user_disabled": "**Wiki-kontot $1 är inaktiverat!**",

+ 0 - 4
i18n/tr.json

@@ -484,7 +484,6 @@
         "all_inactive": "viki değişiklikleri ve yayın bazlı değişiklikleri aynı anda devre dışı bırakamazsın.",
         "all_inactive": "viki değişiklikleri ve yayın bazlı değişiklikleri aynı anda devre dışı bırakamazsın.",
         "audit_reason": "\"$1\" için son değişiklikler webhook'u",
         "audit_reason": "\"$1\" için son değişiklikler webhook'u",
         "audit_reason_delete": "Son değişiklikler webhook'u kaldırıldı",
         "audit_reason_delete": "Son değişiklikler webhook'u kaldırıldı",
-        "audit_reason_move": "Son değişiklikler webhook'u taşındı",
         "blocked": "bu vikinin son değişiklikler webhook'u olarak eklemesi yasaklanmış!",
         "blocked": "bu vikinin son değişiklikler webhook'u olarak eklemesi yasaklanmış!",
         "blocked_reason": "bu vikinin son değişiklikler webhook'u olarak eklemesi `$1` yüzünden yasaklanmış!",
         "blocked_reason": "bu vikinin son değişiklikler webhook'u olarak eklemesi `$1` yüzünden yasaklanmış!",
         "channel": "Kanal:",
         "channel": "Kanal:",
@@ -739,7 +738,6 @@
         "posteditcount": "Düzenleme ve paylaşım sayısı toplamı:",
         "posteditcount": "Düzenleme ve paylaşım sayısı toplamı:",
         "rename": "Kullanıcı adı değiştir:",
         "rename": "Kullanıcı adı değiştir:",
         "rename_no_permission": "**$1, viki kullanıcı isimlerini zorlamak için `Kullanıcı Adlarını Yönet`iznine sahip olmalı!**",
         "rename_no_permission": "**$1, viki kullanıcı isimlerini zorlamak için `Kullanıcı Adlarını Yönet`iznine sahip olmalı!**",
-        "role": "Rol:",
         "role_deleted": "**$1 rolü artık yok gibi duruyor!**",
         "role_deleted": "**$1 rolü artık yok gibi duruyor!**",
         "role_managed": "belirtilen rol atanamıyor.",
         "role_managed": "belirtilen rol atanamıyor.",
         "role_max": "çok fazla rol belirttin.",
         "role_max": "çok fazla rol belirttin.",
@@ -769,8 +767,6 @@
         "help_subpage": "Lütfen vikideki Discord alt sayfana Discord etiketini ($1) ekle:",
         "help_subpage": "Lütfen vikideki Discord alt sayfana Discord etiketini ($1) ekle:",
         "missing": "kanalda halihazırda bir doğrulama yok.",
         "missing": "kanalda halihazırda bir doğrulama yok.",
         "notice": "Uyarı:",
         "notice": "Uyarı:",
-        "qualified": "Nitelikli:",
-        "qualified_error": "Nitelikli ancak eklenemedi:",
         "user_blocked": "**Viki kullanıcısı $1 engellendi!**",
         "user_blocked": "**Viki kullanıcısı $1 engellendi!**",
         "user_blocked_reply": "bağlı olduğun viki kullanıcısı **\"$1\" engellendi!**",
         "user_blocked_reply": "bağlı olduğun viki kullanıcısı **\"$1\" engellendi!**",
         "user_disabled": "**Viki hesabı $1 devre dışı!**",
         "user_disabled": "**Viki hesabı $1 devre dışı!**",

binární
i18n/widgets/bn.png


binární
i18n/widgets/de.png


binární
i18n/widgets/en.png


binární
i18n/widgets/es.png


binární
i18n/widgets/fr.png


binární
i18n/widgets/hi.png


binární
i18n/widgets/it.png


binární
i18n/widgets/ja.png


binární
i18n/widgets/ko.png


binární
i18n/widgets/nl.png


binární
i18n/widgets/pl.png


binární
i18n/widgets/pt-br.png


binární
i18n/widgets/ru.png


binární
i18n/widgets/sv.png


binární
i18n/widgets/th.png


binární
i18n/widgets/tr.png


binární
i18n/widgets/uk.png


binární
i18n/widgets/vi.png


binární
i18n/widgets/zh-hans.png


binární
i18n/widgets/zh-hant.png


+ 12 - 4
i18n/zh-hans.json

@@ -494,7 +494,8 @@
         "all_inactive": "Wiki 式更改和推送式更改无法同时禁用。",
         "all_inactive": "Wiki 式更改和推送式更改无法同时禁用。",
         "audit_reason": "“$1”的最近更改 webhook",
         "audit_reason": "“$1”的最近更改 webhook",
         "audit_reason_delete": "最近更改 webhook 已删除",
         "audit_reason_delete": "最近更改 webhook 已删除",
-        "audit_reason_move": "已移动最近更改 webhook",
+        "audit_reason_edit": "最近更改 webhook 已更新",
+        "avatar": "Webhook 头像:",
         "blocked": "此 wiki 已经被禁止添加到最近更改 webhook !",
         "blocked": "此 wiki 已经被禁止添加到最近更改 webhook !",
         "blocked_reason": "此 wiki 由于 `$1` 已經被禁止添加到最近更改 webhook!",
         "blocked_reason": "此 wiki 由于 `$1` 已經被禁止添加到最近更改 webhook!",
         "channel": "频道:",
         "channel": "频道:",
@@ -528,6 +529,7 @@
         "lang": "语言:",
         "lang": "语言:",
         "max_entries": "已到达最近更改 webhook 的最大数量。",
         "max_entries": "已到达最近更改 webhook 的最大数量。",
         "missing": "本服务器内暂无最近更改 webhook。",
         "missing": "本服务器内暂无最近更改 webhook。",
+        "name": "Webhook 名称:",
         "new_lang": "<新语言>",
         "new_lang": "<新语言>",
         "new_wiki": "<至 wiki 的链接>",
         "new_wiki": "<至 wiki 的链接>",
         "no_feeds": "此 webhook 的 wiki 没有如讨论版、留言墙和条目评论等推送式的更改,已启用。",
         "no_feeds": "此 webhook 的 wiki 没有如讨论版、留言墙和条目评论等推送式的更改,已启用。",
@@ -545,6 +547,7 @@
             "blocked_reason": "由于此 wiki 已因 `$1` 被封禁,此最近更改 webhook 将被删除!",
             "blocked_reason": "由于此 wiki 已因 `$1` 被封禁,此最近更改 webhook 将被删除!",
             "created": "$1 的最近更改 webhook 已被添加到此频道。",
             "created": "$1 的最近更改 webhook 已被添加到此频道。",
             "dashboard": {
             "dashboard": {
+                "avatar": "• Webhook 的头像已被更改。",
                 "channel": "• Webhook 已被移动到此频道。",
                 "channel": "• Webhook 已被移动到此频道。",
                 "disabled_feeds": "• 已禁用讨论版、留言墙和条目评论等推送式的更改推送。",
                 "disabled_feeds": "• 已禁用讨论版、留言墙和条目评论等推送式的更改推送。",
                 "disabled_rc": "• 已禁用wiki式更改。",
                 "disabled_rc": "• 已禁用wiki式更改。",
@@ -555,6 +558,7 @@
                 "enabled_feeds": "• 已启用讨论版、留言墙和条目评论等推送式的更改推送。",
                 "enabled_feeds": "• 已启用讨论版、留言墙和条目评论等推送式的更改推送。",
                 "enabled_rc": "• 已启用wiki式更改。",
                 "enabled_rc": "• 已启用wiki式更改。",
                 "lang": "• 语言已被更改为 $1。",
                 "lang": "• 语言已被更改为 $1。",
+                "name": "• Webhook 的名称已被更改为“$1”。",
                 "updated": "此最近更改 webhook 已被更新:",
                 "updated": "此最近更改 webhook 已被更新:",
                 "wiki": "• wiki 已被更改为 $1。"
                 "wiki": "• wiki 已被更改为 $1。"
             },
             },
@@ -771,11 +775,13 @@
         "posteditcount": "编辑数和帖子数的总和:",
         "posteditcount": "编辑数和帖子数的总和:",
         "rename": "修改昵称:",
         "rename": "修改昵称:",
         "rename_no_permission": "**$1 缺少 `管理昵称` 权限来强制将暱称改为 wiki 用户名!**",
         "rename_no_permission": "**$1 缺少 `管理昵称` 权限来强制将暱称改为 wiki 用户名!**",
-        "role": "身份组:",
+        "role_add": "添加的身份组:",
         "role_deleted": "**身份组 $1 似乎不再存在!**",
         "role_deleted": "**身份组 $1 似乎不再存在!**",
         "role_managed": "提供的身份组权重过高,无法添加。",
         "role_managed": "提供的身份组权重过高,无法添加。",
         "role_max": "提供的身份组过多。",
         "role_max": "提供的身份组过多。",
         "role_missing": "提供的身份组不存在。",
         "role_missing": "提供的身份组不存在。",
+        "role_none": "无",
+        "role_remove": "移除的身份组:",
         "role_too_high": "**$1 身份组的权重过高,$2 无法添加!**",
         "role_too_high": "**$1 身份组的权重过高,$2 无法添加!**",
         "save_failed": "抱歉,无法保存验证方式,请稍后再试。",
         "save_failed": "抱歉,无法保存验证方式,请稍后再试。",
         "success": "成功提示:",
         "success": "成功提示:",
@@ -810,8 +816,10 @@
         "oauth_message_dm": "请使用此链接为$1验证您的wiki账号。",
         "oauth_message_dm": "请使用此链接为$1验证您的wiki账号。",
         "oauth_private": "此wiki使用OAuth2进行验证。请开启来自此服务器的私信或使用`/verify`命令以允许我向您私信验证链接。",
         "oauth_private": "此wiki使用OAuth2进行验证。请开启来自此服务器的私信或使用`/verify`命令以允许我向您私信验证链接。",
         "oauth_used": "*已通过OAuth2验证*",
         "oauth_used": "*已通过OAuth2验证*",
-        "qualified": "符合:",
-        "qualified_error": "符合,但无法添加:",
+        "qualified_add": "添加:",
+        "qualified_add_error": "无法添加:",
+        "qualified_remove": "移除:",
+        "qualified_remove_error": "无法移除:",
         "user_blocked": "**Wiki 用户 $1 已被封禁!**",
         "user_blocked": "**Wiki 用户 $1 已被封禁!**",
         "user_blocked_reply": "您连接的 wiki 用户**“$1”已被封禁!**",
         "user_blocked_reply": "您连接的 wiki 用户**“$1”已被封禁!**",
         "user_disabled": "**Wiki 用户 $1 已被禁用!**",
         "user_disabled": "**Wiki 用户 $1 已被禁用!**",

+ 12 - 4
i18n/zh-hant.json

@@ -494,7 +494,8 @@
         "all_inactive": "wiki變更和討論版變更無法同時停用。",
         "all_inactive": "wiki變更和討論版變更無法同時停用。",
         "audit_reason": "“$1”的近期變更webhook",
         "audit_reason": "“$1”的近期變更webhook",
         "audit_reason_delete": "已移除近期變更webhook",
         "audit_reason_delete": "已移除近期變更webhook",
-        "audit_reason_move": "已移動近期變更webhook",
+        "audit_reason_edit": "近期變更 webhook 已更新",
+        "avatar": "Webhook 頭像:",
         "blocked": "此 wiki 已經被禁止加入到近期變更webhook !",
         "blocked": "此 wiki 已經被禁止加入到近期變更webhook !",
         "blocked_reason": "此 wiki 已經被禁止加入到 `$1` 近期變更 webhook !",
         "blocked_reason": "此 wiki 已經被禁止加入到 `$1` 近期變更 webhook !",
         "channel": "頻道:",
         "channel": "頻道:",
@@ -528,6 +529,7 @@
         "lang": "語言:",
         "lang": "語言:",
         "max_entries": "已到達近期變更webhook的最大數量。",
         "max_entries": "已到達近期變更webhook的最大數量。",
         "missing": "本伺服器內暫無近期變更webhook。",
         "missing": "本伺服器內暫無近期變更webhook。",
+        "name": "Webhook 名稱:",
         "new_lang": "<新語言>",
         "new_lang": "<新語言>",
         "new_wiki": "<連結>",
         "new_wiki": "<連結>",
         "no_feeds": "此webhook的wiki沒有如討論版、留言牆和條目評論等基於推送頁的變更,已啟用。",
         "no_feeds": "此webhook的wiki沒有如討論版、留言牆和條目評論等基於推送頁的變更,已啟用。",
@@ -545,6 +547,7 @@
             "blocked_reason": "由於此wiki已因 `$1` 被封鎖,此近期變更webhook將被刪除!",
             "blocked_reason": "由於此wiki已因 `$1` 被封鎖,此近期變更webhook將被刪除!",
             "created": "$1 的近期變更webhook已新增至此頻道。",
             "created": "$1 的近期變更webhook已新增至此頻道。",
             "dashboard": {
             "dashboard": {
+                "avatar": "• 已變更webhook的頭像。",
                 "channel": "• 已將webhook至此頻道。",
                 "channel": "• 已將webhook至此頻道。",
                 "disabled_feeds": "• 已停用討論版、留言牆和條目評論等基於推送頁的變更推送。",
                 "disabled_feeds": "• 已停用討論版、留言牆和條目評論等基於推送頁的變更推送。",
                 "disabled_rc": "• 已停用wiki變更。",
                 "disabled_rc": "• 已停用wiki變更。",
@@ -555,6 +558,7 @@
                 "enabled_feeds": "• 已啟用討論版、留言牆和條目評論等基於推送頁的變更推送。",
                 "enabled_feeds": "• 已啟用討論版、留言牆和條目評論等基於推送頁的變更推送。",
                 "enabled_rc": "• 已啟用wiki變更。",
                 "enabled_rc": "• 已啟用wiki變更。",
                 "lang": "• 已將語言變更為 $1。",
                 "lang": "• 已將語言變更為 $1。",
+                "name": "• 已將webhook的名稱變更為“$1”。",
                 "updated": "此近期變更webhook已被變更:",
                 "updated": "此近期變更webhook已被變更:",
                 "wiki": "• wiki已被變更為 $1。"
                 "wiki": "• wiki已被變更為 $1。"
             },
             },
@@ -771,11 +775,13 @@
         "posteditcount": "編輯次數與貼文數的總和:",
         "posteditcount": "編輯次數與貼文數的總和:",
         "rename": "修改暱稱:",
         "rename": "修改暱稱:",
         "rename_no_permission": "**$1 缺少`管理暱稱`權限以強制將暱稱變更為wiki使用者名稱!**",
         "rename_no_permission": "**$1 缺少`管理暱稱`權限以強制將暱稱變更為wiki使用者名稱!**",
-        "role": "身分組:",
+        "role_add": "新增的身分組:",
         "role_deleted": "**身分組 $1 似乎不再存在!**",
         "role_deleted": "**身分組 $1 似乎不再存在!**",
         "role_managed": "提供的身分組權重過高,無法添加。",
         "role_managed": "提供的身分組權重過高,無法添加。",
         "role_max": "提供的身分組過多。",
         "role_max": "提供的身分組過多。",
         "role_missing": "提供的身分組不存在。",
         "role_missing": "提供的身分組不存在。",
+        "role_none": "無",
+        "role_remove": "移除的身分組:",
         "role_too_high": "**$1 身分組的權重過高,$2 無法添加!**",
         "role_too_high": "**$1 身分組的權重過高,$2 無法添加!**",
         "save_failed": "抱歉,無法保存驗證方式,請稍後再試。",
         "save_failed": "抱歉,無法保存驗證方式,請稍後再試。",
         "success": "成功提示:",
         "success": "成功提示:",
@@ -810,8 +816,10 @@
         "oauth_message_dm": "請使用此連結為$1驗證您的帳號。",
         "oauth_message_dm": "請使用此連結為$1驗證您的帳號。",
         "oauth_private": "此wiki使用OAuth2進行驗證。請開啟來自此伺服器的私訊或是使用`/verify`指令以允許我向您傳送驗證連結。",
         "oauth_private": "此wiki使用OAuth2進行驗證。請開啟來自此伺服器的私訊或是使用`/verify`指令以允許我向您傳送驗證連結。",
         "oauth_used": "*已透過OAuth2驗證*",
         "oauth_used": "*已透過OAuth2驗證*",
-        "qualified": "符合:",
-        "qualified_error": "符合,但無法添加:",
+        "qualified_add": "新增:",
+        "qualified_add_error": "無法新增:",
+        "qualified_remove": "移除:",
+        "qualified_remove_error": "無法移除:",
         "user_blocked": "**Wiki 使用者 $1 已被封鎖!**",
         "user_blocked": "**Wiki 使用者 $1 已被封鎖!**",
         "user_blocked_reply": "您連接的 wiki 使用者**“$1”已被封鎖!**",
         "user_blocked_reply": "您連接的 wiki 使用者**“$1”已被封鎖!**",
         "user_disabled": "**Wiki 使用者 $1 已被停用!**",
         "user_disabled": "**Wiki 使用者 $1 已被停用!**",

+ 35 - 33
main.js

@@ -102,7 +102,7 @@ if ( process.env.dashboard ) {
 						${JSON.stringify(message.data.guilds)}.map( id => {
 						${JSON.stringify(message.data.guilds)}.map( id => {
 							if ( this.guilds.cache.has(id) ) {
 							if ( this.guilds.cache.has(id) ) {
 								let guild = this.guilds.cache.get(id);
 								let guild = this.guilds.cache.get(id);
-								return guild.members.fetch('${message.data.member}').then( member => {
+								return guild.members.fetch(${JSON.stringify(message.data.member)}).then( member => {
 									return {
 									return {
 										patreon: global.patreons.hasOwnProperty(guild.id),
 										patreon: global.patreons.hasOwnProperty(guild.id),
 										memberCount: guild.memberCount,
 										memberCount: guild.memberCount,
@@ -157,17 +157,17 @@ if ( process.env.dashboard ) {
 					} );
 					} );
 					break;
 					break;
 				case 'getMember':
 				case 'getMember':
-					return manager.broadcastEval(`if ( this.guilds.cache.has('${message.data.guild}') ) {
-						let guild = this.guilds.cache.get('${message.data.guild}');
-						guild.members.fetch('${message.data.member}').then( member => {
+					return manager.broadcastEval(`if ( this.guilds.cache.has(${JSON.stringify(message.data.guild)}) ) {
+						let guild = this.guilds.cache.get(${JSON.stringify(message.data.guild)});
+						guild.members.fetch(${JSON.stringify(message.data.member)}).then( member => {
 							var response = {
 							var response = {
 								patreon: global.patreons.hasOwnProperty(guild.id),
 								patreon: global.patreons.hasOwnProperty(guild.id),
 								userPermissions: member.permissions.bitfield,
 								userPermissions: member.permissions.bitfield,
 								botPermissions: guild.me.permissions.bitfield
 								botPermissions: guild.me.permissions.bitfield
 							};
 							};
-							if ( '${( message.data.channel || '' )}' ) {
-								let channel = guild.channels.cache.get('${message.data.channel}');
-								if ( channel?.isText() || ( response.patreon && ${message.data.allowCategory} && channel?.type === 'category' ) ) {
+							if ( ${JSON.stringify(message.data.channel)} ) {
+								let channel = guild.channels.cache.get(${JSON.stringify(message.data.channel)});
+								if ( channel?.isText() || ( response.patreon && ${JSON.stringify(message.data.allowCategory)} && channel?.type === 'category' ) ) {
 									response.userPermissions = channel.permissionsFor(member).bitfield;
 									response.userPermissions = channel.permissionsFor(member).bitfield;
 									response.botPermissions = channel.permissionsFor(guild.me).bitfield;
 									response.botPermissions = channel.permissionsFor(guild.me).bitfield;
 									response.isCategory = ( channel.type === 'category' );
 									response.isCategory = ( channel.type === 'category' );
@@ -175,8 +175,8 @@ if ( process.env.dashboard ) {
 								}
 								}
 								else response.message = 'noChannel';
 								else response.message = 'noChannel';
 							}
 							}
-							if ( '${( message.data.newchannel || '' )}' ) {
-								let newchannel = guild.channels.cache.get('${message.data.newchannel}');
+							if ( ${JSON.stringify(message.data.newchannel)} ) {
+								let newchannel = guild.channels.cache.get(${JSON.stringify(message.data.newchannel)});
 								if ( newchannel?.isText() ) {
 								if ( newchannel?.isText() ) {
 									response.userPermissionsNew = newchannel.permissionsFor(member).bitfield;
 									response.userPermissionsNew = newchannel.permissionsFor(member).bitfield;
 									response.botPermissionsNew = newchannel.permissionsFor(guild.me).bitfield;
 									response.botPermissionsNew = newchannel.permissionsFor(guild.me).bitfield;
@@ -196,15 +196,15 @@ if ( process.env.dashboard ) {
 					} );
 					} );
 					break;
 					break;
 				case 'notifyGuild':
 				case 'notifyGuild':
-					return manager.broadcastEval(`if ( '${( message.data.prefix || '' )}' ) {
-						global.patreons['${message.data.guild}'] = '${message.data.prefix}';
+					return manager.broadcastEval(`if ( ${JSON.stringify(message.data.prefix)} ) {
+						global.patreons[${JSON.stringify(message.data.guild)}] = ${JSON.stringify(message.data.prefix)};
 					}
 					}
-					if ( '${( message.data.voice || '' )}' && global.voice.hasOwnProperty('${message.data.guild}') ) {
-						global.voice['${message.data.guild}'] = '${message.data.voice}';
+					if ( ${JSON.stringify(message.data.voice)} && global.voice.hasOwnProperty(${JSON.stringify(message.data.guild)}) ) {
+						global.voice[${JSON.stringify(message.data.guild)}] = ${JSON.stringify(message.data.voice)};
 					}
 					}
-					if ( this.guilds.cache.has('${message.data.guild}') ) {
-						let channel = this.guilds.cache.get('${message.data.guild}').publicUpdatesChannel;
-						if ( channel ) channel.send( \`${message.data.text.replace( /`/g, '\\`' )}\`, {
+					if ( this.guilds.cache.has(${JSON.stringify(message.data.guild)}) ) {
+						let channel = this.guilds.cache.get(${JSON.stringify(message.data.guild)}).publicUpdatesChannel;
+						if ( channel ) channel.send( ${JSON.stringify(message.data.text)}, {
 							embed: ${JSON.stringify(message.data.embed)},
 							embed: ${JSON.stringify(message.data.embed)},
 							files: ${JSON.stringify(message.data.file)},
 							files: ${JSON.stringify(message.data.file)},
 							allowedMentions: {parse: []}, split: true
 							allowedMentions: {parse: []}, split: true
@@ -216,14 +216,14 @@ if ( process.env.dashboard ) {
 					} );
 					} );
 					break;
 					break;
 				case 'createWebhook':
 				case 'createWebhook':
-					return manager.broadcastEval(`if ( this.guilds.cache.has('${message.data.guild}') ) {
-						let channel = this.guilds.cache.get('${message.data.guild}').channels.cache.get('${message.data.channel}');
-						if ( channel ) channel.createWebhook( \`${message.data.name.replace( /`/g, '\\`' )}\`, {
-							avatar: this.user.displayAvatarURL({format:'png',size:4096}),
-							reason: \`${message.data.reason.replace( /`/g, '\\`' )}\`
+					return manager.broadcastEval(`if ( this.guilds.cache.has(${JSON.stringify(message.data.guild)}) ) {
+						let channel = this.guilds.cache.get(${JSON.stringify(message.data.guild)}).channels.cache.get(${JSON.stringify(message.data.channel)});
+						if ( channel ) channel.createWebhook( ${JSON.stringify(message.data.name)}, {
+							avatar: ( ${JSON.stringify(message.data.avatar)} || this.user.displayAvatarURL({format:'png',size:4096}) ),
+							reason: ${JSON.stringify(message.data.reason)}
 						} ).then( webhook => {
 						} ).then( webhook => {
 							console.log( '- Dashboard: Webhook successfully created: ${message.data.guild}#${message.data.channel}' );
 							console.log( '- Dashboard: Webhook successfully created: ${message.data.guild}#${message.data.channel}' );
-							webhook.send( \`${message.data.text.replace( /`/g, '\\`' )}\` ).catch(log_error);
+							webhook.send( ${JSON.stringify(message.data.text)} ).catch(log_error);
 							return webhook.id + '/' + webhook.token;
 							return webhook.id + '/' + webhook.token;
 						}, error => {
 						}, error => {
 							console.log( '- Dashboard: Error while creating the webhook: ' + error );
 							console.log( '- Dashboard: Error while creating the webhook: ' + error );
@@ -236,20 +236,22 @@ if ( process.env.dashboard ) {
 						return dashboard.send( {id: message.id, data} );
 						return dashboard.send( {id: message.id, data} );
 					} );
 					} );
 					break;
 					break;
-				case 'moveWebhook':
-					return manager.broadcastEval(`if ( this.guilds.cache.has('${message.data.guild}') ) {
-						this.fetchWebhook(...'${message.data.webhook}'.split('/')).then( webhook => {
-							return webhook.edit( {
-								channel: '${message.data.channel}'
-							}, \`${message.data.reason.replace( /`/g, '\\`' )}\` ).then( newwebhook => {
-								console.log( '- Dashboard: Webhook successfully moved: ${message.data.guild}#${message.data.channel}' );
-								webhook.send( \`${message.data.text.replace( /`/g, '\\`' )}\` ).catch(log_error);
+				case 'editWebhook':
+					return manager.broadcastEval(`if ( this.guilds.cache.has(${JSON.stringify(message.data.guild)}) ) {
+						this.fetchWebhook(...${JSON.stringify(message.data.webhook.split('/'))}).then( webhook => {
+							var changes = {};
+							if ( ${JSON.stringify(message.data.channel)} ) changes.channel = ${JSON.stringify(message.data.channel)};
+							if ( ${JSON.stringify(message.data.name)} ) changes.name = ${JSON.stringify(message.data.name)};
+							if ( ${JSON.stringify(message.data.avatar)} ) changes.avatar = ${JSON.stringify(message.data.avatar)};
+							return webhook.edit( changes, ${JSON.stringify(message.data.reason)} ).then( newwebhook => {
+								console.log( '- Dashboard: Webhook successfully edited: ${message.data.guild}#' + ( ${JSON.stringify(message.data.channel)} || webhook.channelID ) );
+								webhook.send( ${JSON.stringify(message.data.text)} ).catch(log_error);
 								return true;
 								return true;
 							}, error => {
 							}, error => {
-								console.log( '- Dashboard: Error while moving the webhook: ' + error );
+								console.log( '- Dashboard: Error while editing the webhook: ' + error );
 							} );
 							} );
 						}, error => {
 						}, error => {
-							console.log( '- Dashboard: Error while moving the webhook: ' + error );
+							console.log( '- Dashboard: Error while editing the webhook: ' + error );
 						} );
 						} );
 					}`, shardIDForGuildID(message.data.guild, manager.totalShards)).then( result => {
 					}`, shardIDForGuildID(message.data.guild, manager.totalShards)).then( result => {
 						data.response = result;
 						data.response = result;
@@ -260,7 +262,7 @@ if ( process.env.dashboard ) {
 					} );
 					} );
 					break;
 					break;
 				case 'verifyUser':
 				case 'verifyUser':
-					return manager.broadcastEval(`global.verifyOauthUser('${message.data.state}', '${message.data.access_token}')`, message.data.state.split(' ')[1][0]).catch( error => {
+					return manager.broadcastEval(`global.verifyOauthUser(${JSON.stringify(message.data.state)}, ${JSON.stringify(message.data.access_token)})`, message.data.state.split(' ')[1][0]).catch( error => {
 						data.error = error.toString();
 						data.error = error.toString();
 					} ).finally( () => {
 					} ).finally( () => {
 						return dashboard.send( {id: message.id, data} );
 						return dashboard.send( {id: message.id, data} );

+ 201 - 186
package-lock.json

@@ -8,15 +8,15 @@
       "version": "4.0.0",
       "version": "4.0.0",
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
-        "cheerio": "^1.0.0-rc.6",
+        "cheerio": "^1.0.0-rc.10",
         "datetime-difference": "^1.0.2",
         "datetime-difference": "^1.0.2",
         "discord-oauth2": "^2.6.0",
         "discord-oauth2": "^2.6.0",
-        "discord.js": "^12.5.1",
+        "discord.js": "^12.5.3",
         "dotenv": "^10.0.0",
         "dotenv": "^10.0.0",
-        "full-icu": "^1.3.1",
-        "got": "^11.8.1",
+        "full-icu": "^1.3.4",
+        "got": "^11.8.2",
         "htmlparser2": "^6.1.0",
         "htmlparser2": "^6.1.0",
-        "npm": "^7.11.2",
+        "npm": "^7.17.0",
         "pg": "^8.6.0"
         "pg": "^8.6.0"
       },
       },
       "engines": {
       "engines": {
@@ -88,9 +88,9 @@
       }
       }
     },
     },
     "node_modules/@types/node": {
     "node_modules/@types/node": {
-      "version": "15.6.1",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.1.tgz",
-      "integrity": "sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA=="
+      "version": "15.12.2",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz",
+      "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww=="
     },
     },
     "node_modules/@types/responselike": {
     "node_modules/@types/responselike": {
       "version": "1.0.0",
       "version": "1.0.0",
@@ -138,16 +138,16 @@
       }
       }
     },
     },
     "node_modules/cacheable-request": {
     "node_modules/cacheable-request": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz",
-      "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==",
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz",
+      "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==",
       "dependencies": {
       "dependencies": {
         "clone-response": "^1.0.2",
         "clone-response": "^1.0.2",
         "get-stream": "^5.1.0",
         "get-stream": "^5.1.0",
         "http-cache-semantics": "^4.0.0",
         "http-cache-semantics": "^4.0.0",
         "keyv": "^4.0.0",
         "keyv": "^4.0.0",
         "lowercase-keys": "^2.0.0",
         "lowercase-keys": "^2.0.0",
-        "normalize-url": "^4.1.0",
+        "normalize-url": "^6.0.1",
         "responselike": "^2.0.0"
         "responselike": "^2.0.0"
       },
       },
       "engines": {
       "engines": {
@@ -155,12 +155,12 @@
       }
       }
     },
     },
     "node_modules/cheerio": {
     "node_modules/cheerio": {
-      "version": "1.0.0-rc.9",
-      "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.9.tgz",
-      "integrity": "sha512-QF6XVdrLONO6DXRF5iaolY+odmhj2CLj+xzNod7INPWMi/x9X4SOylH0S/vaPpX+AUU6t04s34SQNh7DbkuCng==",
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz",
+      "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==",
       "dependencies": {
       "dependencies": {
-        "cheerio-select": "^1.4.0",
-        "dom-serializer": "^1.3.1",
+        "cheerio-select": "^1.5.0",
+        "dom-serializer": "^1.3.2",
         "domhandler": "^4.2.0",
         "domhandler": "^4.2.0",
         "htmlparser2": "^6.1.0",
         "htmlparser2": "^6.1.0",
         "parse5": "^6.0.1",
         "parse5": "^6.0.1",
@@ -175,15 +175,15 @@
       }
       }
     },
     },
     "node_modules/cheerio-select": {
     "node_modules/cheerio-select": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.4.0.tgz",
-      "integrity": "sha512-sobR3Yqz27L553Qa7cK6rtJlMDbiKPdNywtR95Sj/YgfpLfy0u6CGJuaBKe5YE/vTc23SCRKxWSdlon/w6I/Ew==",
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz",
+      "integrity": "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==",
       "dependencies": {
       "dependencies": {
-        "css-select": "^4.1.2",
-        "css-what": "^5.0.0",
+        "css-select": "^4.1.3",
+        "css-what": "^5.0.1",
         "domelementtype": "^2.2.0",
         "domelementtype": "^2.2.0",
         "domhandler": "^4.2.0",
         "domhandler": "^4.2.0",
-        "domutils": "^2.6.0"
+        "domutils": "^2.7.0"
       },
       },
       "funding": {
       "funding": {
         "url": "https://github.com/sponsors/fb55"
         "url": "https://github.com/sponsors/fb55"
@@ -209,9 +209,9 @@
       }
       }
     },
     },
     "node_modules/css-select": {
     "node_modules/css-select": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.2.tgz",
-      "integrity": "sha512-nu5ye2Hg/4ISq4XqdLY2bEatAcLIdt3OYGFc9Tm9n7VSlFBcfRv0gBNksHRgSdUDQGtN3XrZ94ztW+NfzkFSUw==",
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz",
+      "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==",
       "dependencies": {
       "dependencies": {
         "boolbase": "^1.0.0",
         "boolbase": "^1.0.0",
         "css-what": "^5.0.0",
         "css-what": "^5.0.0",
@@ -342,9 +342,9 @@
       }
       }
     },
     },
     "node_modules/domutils": {
     "node_modules/domutils": {
-      "version": "2.6.0",
-      "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.6.0.tgz",
-      "integrity": "sha512-y0BezHuy4MDYxh6OvolXYsH+1EMGmFbwv5FKW7ovwMG6zTPWqNPq3WF9ayZssFq+UlKdffGLbOEaghNdaOm1WA==",
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz",
+      "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==",
       "dependencies": {
       "dependencies": {
         "dom-serializer": "^1.0.1",
         "dom-serializer": "^1.0.1",
         "domelementtype": "^2.2.0",
         "domelementtype": "^2.2.0",
@@ -495,19 +495,19 @@
       }
       }
     },
     },
     "node_modules/mime-db": {
     "node_modules/mime-db": {
-      "version": "1.47.0",
-      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz",
-      "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==",
+      "version": "1.48.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz",
+      "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==",
       "engines": {
       "engines": {
         "node": ">= 0.6"
         "node": ">= 0.6"
       }
       }
     },
     },
     "node_modules/mime-types": {
     "node_modules/mime-types": {
-      "version": "2.1.30",
-      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz",
-      "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==",
+      "version": "2.1.31",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz",
+      "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==",
       "dependencies": {
       "dependencies": {
-        "mime-db": "1.47.0"
+        "mime-db": "1.48.0"
       },
       },
       "engines": {
       "engines": {
         "node": ">= 0.6"
         "node": ">= 0.6"
@@ -530,17 +530,20 @@
       }
       }
     },
     },
     "node_modules/normalize-url": {
     "node_modules/normalize-url": {
-      "version": "4.5.1",
-      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz",
-      "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==",
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.0.1.tgz",
+      "integrity": "sha512-VU4pzAuh7Kip71XEmO9aNREYAdMHFGTVj/i+CaTImS8x0i1d3jUZkXhqluy/PRgjPLMgsLQulYY3PJ/aSbSjpQ==",
       "engines": {
       "engines": {
-        "node": ">=8"
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
       }
       }
     },
     },
     "node_modules/npm": {
     "node_modules/npm": {
-      "version": "7.15.0",
-      "resolved": "https://registry.npmjs.org/npm/-/npm-7.15.0.tgz",
-      "integrity": "sha512-GIXNqy3obii54oPF0gbcBNq4aYuB/Ovuu/uYp1eS4nij2PEDMnoOh6RoSv2MDvAaB4a+JbpX/tjDxLO7JAADgQ==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/npm/-/npm-7.17.0.tgz",
+      "integrity": "sha512-yNzj4vQellvUGiBM/SzhfT89EV0vb7iILjTehSydTY/IgK2Vjk7/7J8WNJ2ysqcgfLY21ptI/j7uknt15IbbKQ==",
       "bundleDependencies": [
       "bundleDependencies": [
         "@npmcli/arborist",
         "@npmcli/arborist",
         "@npmcli/ci-detect",
         "@npmcli/ci-detect",
@@ -611,7 +614,7 @@
         "write-file-atomic"
         "write-file-atomic"
       ],
       ],
       "dependencies": {
       "dependencies": {
-        "@npmcli/arborist": "^2.6.0",
+        "@npmcli/arborist": "^2.6.1",
         "@npmcli/ci-detect": "^1.2.0",
         "@npmcli/ci-detect": "^1.2.0",
         "@npmcli/config": "^2.2.0",
         "@npmcli/config": "^2.2.0",
         "@npmcli/run-script": "^1.8.5",
         "@npmcli/run-script": "^1.8.5",
@@ -636,7 +639,7 @@
         "leven": "^3.1.0",
         "leven": "^3.1.0",
         "libnpmaccess": "^4.0.2",
         "libnpmaccess": "^4.0.2",
         "libnpmdiff": "^2.0.4",
         "libnpmdiff": "^2.0.4",
-        "libnpmexec": "^1.1.1",
+        "libnpmexec": "^1.2.0",
         "libnpmfund": "^1.1.0",
         "libnpmfund": "^1.1.0",
         "libnpmhook": "^6.0.2",
         "libnpmhook": "^6.0.2",
         "libnpmorg": "^2.0.2",
         "libnpmorg": "^2.0.2",
@@ -645,7 +648,7 @@
         "libnpmsearch": "^3.1.1",
         "libnpmsearch": "^3.1.1",
         "libnpmteam": "^2.0.3",
         "libnpmteam": "^2.0.3",
         "libnpmversion": "^1.2.0",
         "libnpmversion": "^1.2.0",
-        "make-fetch-happen": "^8.0.14",
+        "make-fetch-happen": "^9.0.1",
         "minipass": "^3.1.3",
         "minipass": "^3.1.3",
         "minipass-pipeline": "^1.2.4",
         "minipass-pipeline": "^1.2.4",
         "mkdirp": "^1.0.4",
         "mkdirp": "^1.0.4",
@@ -654,10 +657,10 @@
         "node-gyp": "^7.1.2",
         "node-gyp": "^7.1.2",
         "nopt": "^5.0.0",
         "nopt": "^5.0.0",
         "npm-audit-report": "^2.1.5",
         "npm-audit-report": "^2.1.5",
-        "npm-package-arg": "^8.1.2",
+        "npm-package-arg": "^8.1.4",
         "npm-pick-manifest": "^6.1.1",
         "npm-pick-manifest": "^6.1.1",
         "npm-profile": "^5.0.3",
         "npm-profile": "^5.0.3",
-        "npm-registry-fetch": "^10.1.2",
+        "npm-registry-fetch": "^11.0.0",
         "npm-user-validate": "^1.0.1",
         "npm-user-validate": "^1.0.1",
         "npmlog": "~4.1.2",
         "npmlog": "~4.1.2",
         "opener": "^1.5.2",
         "opener": "^1.5.2",
@@ -688,7 +691,7 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/@npmcli/arborist": {
     "node_modules/npm/node_modules/@npmcli/arborist": {
-      "version": "2.6.0",
+      "version": "2.6.2",
       "inBundle": true,
       "inBundle": true,
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
@@ -708,7 +711,7 @@
         "npm-install-checks": "^4.0.0",
         "npm-install-checks": "^4.0.0",
         "npm-package-arg": "^8.1.0",
         "npm-package-arg": "^8.1.0",
         "npm-pick-manifest": "^6.1.0",
         "npm-pick-manifest": "^6.1.0",
-        "npm-registry-fetch": "^10.0.0",
+        "npm-registry-fetch": "^11.0.0",
         "pacote": "^11.2.6",
         "pacote": "^11.2.6",
         "parse-conflict-json": "^1.1.1",
         "parse-conflict-json": "^1.1.1",
         "promise-all-reject-late": "^1.0.0",
         "promise-all-reject-late": "^1.0.0",
@@ -1453,19 +1456,6 @@
         "node": "*"
         "node": "*"
       }
       }
     },
     },
-    "node_modules/npm/node_modules/form-data": {
-      "version": "2.3.3",
-      "inBundle": true,
-      "license": "MIT",
-      "dependencies": {
-        "asynckit": "^0.4.0",
-        "combined-stream": "^1.0.6",
-        "mime-types": "^2.1.12"
-      },
-      "engines": {
-        "node": ">= 0.12"
-      }
-    },
     "node_modules/npm/node_modules/fs-minipass": {
     "node_modules/npm/node_modules/fs-minipass": {
       "version": "2.1.0",
       "version": "2.1.0",
       "inBundle": true,
       "inBundle": true,
@@ -1671,7 +1661,7 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/iconv-lite": {
     "node_modules/npm/node_modules/iconv-lite": {
-      "version": "0.6.2",
+      "version": "0.6.3",
       "inBundle": true,
       "inBundle": true,
       "license": "MIT",
       "license": "MIT",
       "optional": true,
       "optional": true,
@@ -1892,14 +1882,14 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/libnpmaccess": {
     "node_modules/npm/node_modules/libnpmaccess": {
-      "version": "4.0.2",
+      "version": "4.0.3",
       "inBundle": true,
       "inBundle": true,
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
         "aproba": "^2.0.0",
         "aproba": "^2.0.0",
         "minipass": "^3.1.1",
         "minipass": "^3.1.1",
         "npm-package-arg": "^8.1.2",
         "npm-package-arg": "^8.1.2",
-        "npm-registry-fetch": "^10.0.0"
+        "npm-registry-fetch": "^11.0.0"
       },
       },
       "engines": {
       "engines": {
         "node": ">=10"
         "node": ">=10"
@@ -1924,7 +1914,7 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/libnpmexec": {
     "node_modules/npm/node_modules/libnpmexec": {
-      "version": "1.1.1",
+      "version": "1.2.0",
       "inBundle": true,
       "inBundle": true,
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
@@ -1953,24 +1943,24 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/libnpmhook": {
     "node_modules/npm/node_modules/libnpmhook": {
-      "version": "6.0.2",
+      "version": "6.0.3",
       "inBundle": true,
       "inBundle": true,
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
         "aproba": "^2.0.0",
         "aproba": "^2.0.0",
-        "npm-registry-fetch": "^10.0.0"
+        "npm-registry-fetch": "^11.0.0"
       },
       },
       "engines": {
       "engines": {
         "node": ">=10"
         "node": ">=10"
       }
       }
     },
     },
     "node_modules/npm/node_modules/libnpmorg": {
     "node_modules/npm/node_modules/libnpmorg": {
-      "version": "2.0.2",
+      "version": "2.0.3",
       "inBundle": true,
       "inBundle": true,
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
         "aproba": "^2.0.0",
         "aproba": "^2.0.0",
-        "npm-registry-fetch": "^10.0.0"
+        "npm-registry-fetch": "^11.0.0"
       },
       },
       "engines": {
       "engines": {
         "node": ">=10"
         "node": ">=10"
@@ -1990,13 +1980,13 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/libnpmpublish": {
     "node_modules/npm/node_modules/libnpmpublish": {
-      "version": "4.0.1",
+      "version": "4.0.2",
       "inBundle": true,
       "inBundle": true,
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
         "normalize-package-data": "^3.0.2",
         "normalize-package-data": "^3.0.2",
         "npm-package-arg": "^8.1.2",
         "npm-package-arg": "^8.1.2",
-        "npm-registry-fetch": "^10.0.0",
+        "npm-registry-fetch": "^11.0.0",
         "semver": "^7.1.3",
         "semver": "^7.1.3",
         "ssri": "^8.0.1"
         "ssri": "^8.0.1"
       },
       },
@@ -2005,23 +1995,23 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/libnpmsearch": {
     "node_modules/npm/node_modules/libnpmsearch": {
-      "version": "3.1.1",
+      "version": "3.1.2",
       "inBundle": true,
       "inBundle": true,
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
-        "npm-registry-fetch": "^10.0.0"
+        "npm-registry-fetch": "^11.0.0"
       },
       },
       "engines": {
       "engines": {
         "node": ">=10"
         "node": ">=10"
       }
       }
     },
     },
     "node_modules/npm/node_modules/libnpmteam": {
     "node_modules/npm/node_modules/libnpmteam": {
-      "version": "2.0.3",
+      "version": "2.0.4",
       "inBundle": true,
       "inBundle": true,
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
         "aproba": "^2.0.0",
         "aproba": "^2.0.0",
-        "npm-registry-fetch": "^10.0.0"
+        "npm-registry-fetch": "^11.0.0"
       },
       },
       "engines": {
       "engines": {
         "node": ">=10"
         "node": ">=10"
@@ -2051,12 +2041,12 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/make-fetch-happen": {
     "node_modules/npm/node_modules/make-fetch-happen": {
-      "version": "8.0.14",
+      "version": "9.0.2",
       "inBundle": true,
       "inBundle": true,
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
         "agentkeepalive": "^4.1.3",
         "agentkeepalive": "^4.1.3",
-        "cacache": "^15.0.5",
+        "cacache": "^15.2.0",
         "http-cache-semantics": "^4.1.0",
         "http-cache-semantics": "^4.1.0",
         "http-proxy-agent": "^4.0.1",
         "http-proxy-agent": "^4.0.1",
         "https-proxy-agent": "^5.0.0",
         "https-proxy-agent": "^5.0.0",
@@ -2067,6 +2057,7 @@
         "minipass-fetch": "^1.3.2",
         "minipass-fetch": "^1.3.2",
         "minipass-flush": "^1.0.5",
         "minipass-flush": "^1.0.5",
         "minipass-pipeline": "^1.2.4",
         "minipass-pipeline": "^1.2.4",
+        "negotiator": "^0.6.2",
         "promise-retry": "^2.0.1",
         "promise-retry": "^2.0.1",
         "socks-proxy-agent": "^5.0.0",
         "socks-proxy-agent": "^5.0.0",
         "ssri": "^8.0.0"
         "ssri": "^8.0.0"
@@ -2076,7 +2067,7 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/mime-db": {
     "node_modules/npm/node_modules/mime-db": {
-      "version": "1.47.0",
+      "version": "1.48.0",
       "inBundle": true,
       "inBundle": true,
       "license": "MIT",
       "license": "MIT",
       "engines": {
       "engines": {
@@ -2084,11 +2075,11 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/mime-types": {
     "node_modules/npm/node_modules/mime-types": {
-      "version": "2.1.30",
+      "version": "2.1.31",
       "inBundle": true,
       "inBundle": true,
       "license": "MIT",
       "license": "MIT",
       "dependencies": {
       "dependencies": {
-        "mime-db": "1.47.0"
+        "mime-db": "1.48.0"
       },
       },
       "engines": {
       "engines": {
         "node": ">= 0.6"
         "node": ">= 0.6"
@@ -2231,6 +2222,14 @@
       "inBundle": true,
       "inBundle": true,
       "license": "ISC"
       "license": "ISC"
     },
     },
+    "node_modules/npm/node_modules/negotiator": {
+      "version": "0.6.2",
+      "inBundle": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
     "node_modules/npm/node_modules/node-gyp": {
     "node_modules/npm/node_modules/node-gyp": {
       "version": "7.1.2",
       "version": "7.1.2",
       "inBundle": true,
       "inBundle": true,
@@ -2318,7 +2317,7 @@
       "license": "ISC"
       "license": "ISC"
     },
     },
     "node_modules/npm/node_modules/npm-package-arg": {
     "node_modules/npm/node_modules/npm-package-arg": {
-      "version": "8.1.2",
+      "version": "8.1.4",
       "inBundle": true,
       "inBundle": true,
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
@@ -2359,23 +2358,22 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/npm-profile": {
     "node_modules/npm/node_modules/npm-profile": {
-      "version": "5.0.3",
+      "version": "5.0.4",
       "inBundle": true,
       "inBundle": true,
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
-        "npm-registry-fetch": "^10.0.0"
+        "npm-registry-fetch": "^11.0.0"
       },
       },
       "engines": {
       "engines": {
         "node": ">=10"
         "node": ">=10"
       }
       }
     },
     },
     "node_modules/npm/node_modules/npm-registry-fetch": {
     "node_modules/npm/node_modules/npm-registry-fetch": {
-      "version": "10.1.2",
+      "version": "11.0.0",
       "inBundle": true,
       "inBundle": true,
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
-        "lru-cache": "^6.0.0",
-        "make-fetch-happen": "^8.0.9",
+        "make-fetch-happen": "^9.0.1",
         "minipass": "^3.1.3",
         "minipass": "^3.1.3",
         "minipass-fetch": "^1.3.0",
         "minipass-fetch": "^1.3.0",
         "minipass-json-stream": "^1.0.1",
         "minipass-json-stream": "^1.0.1",
@@ -2457,7 +2455,7 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/pacote": {
     "node_modules/npm/node_modules/pacote": {
-      "version": "11.3.3",
+      "version": "11.3.4",
       "inBundle": true,
       "inBundle": true,
       "license": "ISC",
       "license": "ISC",
       "dependencies": {
       "dependencies": {
@@ -2474,7 +2472,7 @@
         "npm-package-arg": "^8.0.1",
         "npm-package-arg": "^8.0.1",
         "npm-packlist": "^2.1.4",
         "npm-packlist": "^2.1.4",
         "npm-pick-manifest": "^6.0.0",
         "npm-pick-manifest": "^6.0.0",
-        "npm-registry-fetch": "^10.0.0",
+        "npm-registry-fetch": "^11.0.0",
         "promise-retry": "^2.0.1",
         "promise-retry": "^2.0.1",
         "read-package-json-fast": "^2.0.1",
         "read-package-json-fast": "^2.0.1",
         "rimraf": "^3.0.2",
         "rimraf": "^3.0.2",
@@ -2507,7 +2505,7 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/path-parse": {
     "node_modules/npm/node_modules/path-parse": {
-      "version": "1.0.6",
+      "version": "1.0.7",
       "inBundle": true,
       "inBundle": true,
       "license": "MIT"
       "license": "MIT"
     },
     },
@@ -2692,6 +2690,19 @@
         "node": ">= 6"
         "node": ">= 6"
       }
       }
     },
     },
+    "node_modules/npm/node_modules/request/node_modules/form-data": {
+      "version": "2.3.3",
+      "inBundle": true,
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 0.12"
+      }
+    },
     "node_modules/npm/node_modules/request/node_modules/tough-cookie": {
     "node_modules/npm/node_modules/request/node_modules/tough-cookie": {
       "version": "2.5.0",
       "version": "2.5.0",
       "inBundle": true,
       "inBundle": true,
@@ -2831,7 +2842,7 @@
       }
       }
     },
     },
     "node_modules/npm/node_modules/spdx-license-ids": {
     "node_modules/npm/node_modules/spdx-license-ids": {
-      "version": "3.0.7",
+      "version": "3.0.9",
       "inBundle": true,
       "inBundle": true,
       "license": "CC0-1.0"
       "license": "CC0-1.0"
     },
     },
@@ -3264,9 +3275,9 @@
       }
       }
     },
     },
     "node_modules/prism-media": {
     "node_modules/prism-media": {
-      "version": "1.2.9",
-      "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.9.tgz",
-      "integrity": "sha512-UHCYuqHipbTR1ZsXr5eg4JUmHER8Ss4YEb9Azn+9zzJ7/jlTtD1h0lc4g6tNx3eMlB8Mp6bfll0LPMAV4R6r3Q==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.1.tgz",
+      "integrity": "sha512-nyYAa3KB4qteJIqdguKmwxTJgy55xxUtkJ3uRnOvO5jO+frci+9zpRXw6QZVcfDeva3S654fU9+26P2OSTzjHw==",
       "peerDependencies": {
       "peerDependencies": {
         "@discordjs/opus": "^0.5.0",
         "@discordjs/opus": "^0.5.0",
         "ffmpeg-static": "^4.2.7 || ^3.0.0 || ^2.4.0",
         "ffmpeg-static": "^4.2.7 || ^3.0.0 || ^2.4.0",
@@ -3375,9 +3386,9 @@
       }
       }
     },
     },
     "node_modules/tslib": {
     "node_modules/tslib": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
-      "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w=="
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
     },
     },
     "node_modules/tweetnacl": {
     "node_modules/tweetnacl": {
       "version": "1.0.3",
       "version": "1.0.3",
@@ -3477,9 +3488,9 @@
       }
       }
     },
     },
     "@types/node": {
     "@types/node": {
-      "version": "15.6.1",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.1.tgz",
-      "integrity": "sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA=="
+      "version": "15.12.2",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz",
+      "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww=="
     },
     },
     "@types/responselike": {
     "@types/responselike": {
       "version": "1.0.0",
       "version": "1.0.0",
@@ -3518,26 +3529,26 @@
       "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="
       "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="
     },
     },
     "cacheable-request": {
     "cacheable-request": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz",
-      "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==",
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz",
+      "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==",
       "requires": {
       "requires": {
         "clone-response": "^1.0.2",
         "clone-response": "^1.0.2",
         "get-stream": "^5.1.0",
         "get-stream": "^5.1.0",
         "http-cache-semantics": "^4.0.0",
         "http-cache-semantics": "^4.0.0",
         "keyv": "^4.0.0",
         "keyv": "^4.0.0",
         "lowercase-keys": "^2.0.0",
         "lowercase-keys": "^2.0.0",
-        "normalize-url": "^4.1.0",
+        "normalize-url": "^6.0.1",
         "responselike": "^2.0.0"
         "responselike": "^2.0.0"
       }
       }
     },
     },
     "cheerio": {
     "cheerio": {
-      "version": "1.0.0-rc.9",
-      "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.9.tgz",
-      "integrity": "sha512-QF6XVdrLONO6DXRF5iaolY+odmhj2CLj+xzNod7INPWMi/x9X4SOylH0S/vaPpX+AUU6t04s34SQNh7DbkuCng==",
+      "version": "1.0.0-rc.10",
+      "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz",
+      "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==",
       "requires": {
       "requires": {
-        "cheerio-select": "^1.4.0",
-        "dom-serializer": "^1.3.1",
+        "cheerio-select": "^1.5.0",
+        "dom-serializer": "^1.3.2",
         "domhandler": "^4.2.0",
         "domhandler": "^4.2.0",
         "htmlparser2": "^6.1.0",
         "htmlparser2": "^6.1.0",
         "parse5": "^6.0.1",
         "parse5": "^6.0.1",
@@ -3546,15 +3557,15 @@
       }
       }
     },
     },
     "cheerio-select": {
     "cheerio-select": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.4.0.tgz",
-      "integrity": "sha512-sobR3Yqz27L553Qa7cK6rtJlMDbiKPdNywtR95Sj/YgfpLfy0u6CGJuaBKe5YE/vTc23SCRKxWSdlon/w6I/Ew==",
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz",
+      "integrity": "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==",
       "requires": {
       "requires": {
-        "css-select": "^4.1.2",
-        "css-what": "^5.0.0",
+        "css-select": "^4.1.3",
+        "css-what": "^5.0.1",
         "domelementtype": "^2.2.0",
         "domelementtype": "^2.2.0",
         "domhandler": "^4.2.0",
         "domhandler": "^4.2.0",
-        "domutils": "^2.6.0"
+        "domutils": "^2.7.0"
       }
       }
     },
     },
     "clone-response": {
     "clone-response": {
@@ -3574,9 +3585,9 @@
       }
       }
     },
     },
     "css-select": {
     "css-select": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.2.tgz",
-      "integrity": "sha512-nu5ye2Hg/4ISq4XqdLY2bEatAcLIdt3OYGFc9Tm9n7VSlFBcfRv0gBNksHRgSdUDQGtN3XrZ94ztW+NfzkFSUw==",
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz",
+      "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==",
       "requires": {
       "requires": {
         "boolbase": "^1.0.0",
         "boolbase": "^1.0.0",
         "css-what": "^5.0.0",
         "css-what": "^5.0.0",
@@ -3664,9 +3675,9 @@
       }
       }
     },
     },
     "domutils": {
     "domutils": {
-      "version": "2.6.0",
-      "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.6.0.tgz",
-      "integrity": "sha512-y0BezHuy4MDYxh6OvolXYsH+1EMGmFbwv5FKW7ovwMG6zTPWqNPq3WF9ayZssFq+UlKdffGLbOEaghNdaOm1WA==",
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz",
+      "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==",
       "requires": {
       "requires": {
         "dom-serializer": "^1.0.1",
         "dom-serializer": "^1.0.1",
         "domelementtype": "^2.2.0",
         "domelementtype": "^2.2.0",
@@ -3776,16 +3787,16 @@
       "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
       "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
     },
     },
     "mime-db": {
     "mime-db": {
-      "version": "1.47.0",
-      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz",
-      "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw=="
+      "version": "1.48.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz",
+      "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ=="
     },
     },
     "mime-types": {
     "mime-types": {
-      "version": "2.1.30",
-      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz",
-      "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==",
+      "version": "2.1.31",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz",
+      "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==",
       "requires": {
       "requires": {
-        "mime-db": "1.47.0"
+        "mime-db": "1.48.0"
       }
       }
     },
     },
     "mimic-response": {
     "mimic-response": {
@@ -3799,16 +3810,16 @@
       "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
       "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
     },
     },
     "normalize-url": {
     "normalize-url": {
-      "version": "4.5.1",
-      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz",
-      "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA=="
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.0.1.tgz",
+      "integrity": "sha512-VU4pzAuh7Kip71XEmO9aNREYAdMHFGTVj/i+CaTImS8x0i1d3jUZkXhqluy/PRgjPLMgsLQulYY3PJ/aSbSjpQ=="
     },
     },
     "npm": {
     "npm": {
-      "version": "7.15.0",
-      "resolved": "https://registry.npmjs.org/npm/-/npm-7.15.0.tgz",
-      "integrity": "sha512-GIXNqy3obii54oPF0gbcBNq4aYuB/Ovuu/uYp1eS4nij2PEDMnoOh6RoSv2MDvAaB4a+JbpX/tjDxLO7JAADgQ==",
+      "version": "7.17.0",
+      "resolved": "https://registry.npmjs.org/npm/-/npm-7.17.0.tgz",
+      "integrity": "sha512-yNzj4vQellvUGiBM/SzhfT89EV0vb7iILjTehSydTY/IgK2Vjk7/7J8WNJ2ysqcgfLY21ptI/j7uknt15IbbKQ==",
       "requires": {
       "requires": {
-        "@npmcli/arborist": "^2.6.0",
+        "@npmcli/arborist": "^2.6.1",
         "@npmcli/ci-detect": "^1.2.0",
         "@npmcli/ci-detect": "^1.2.0",
         "@npmcli/config": "^2.2.0",
         "@npmcli/config": "^2.2.0",
         "@npmcli/run-script": "^1.8.5",
         "@npmcli/run-script": "^1.8.5",
@@ -3833,7 +3844,7 @@
         "leven": "^3.1.0",
         "leven": "^3.1.0",
         "libnpmaccess": "^4.0.2",
         "libnpmaccess": "^4.0.2",
         "libnpmdiff": "^2.0.4",
         "libnpmdiff": "^2.0.4",
-        "libnpmexec": "^1.1.1",
+        "libnpmexec": "^1.2.0",
         "libnpmfund": "^1.1.0",
         "libnpmfund": "^1.1.0",
         "libnpmhook": "^6.0.2",
         "libnpmhook": "^6.0.2",
         "libnpmorg": "^2.0.2",
         "libnpmorg": "^2.0.2",
@@ -3842,7 +3853,7 @@
         "libnpmsearch": "^3.1.1",
         "libnpmsearch": "^3.1.1",
         "libnpmteam": "^2.0.3",
         "libnpmteam": "^2.0.3",
         "libnpmversion": "^1.2.0",
         "libnpmversion": "^1.2.0",
-        "make-fetch-happen": "^8.0.14",
+        "make-fetch-happen": "^9.0.1",
         "minipass": "^3.1.3",
         "minipass": "^3.1.3",
         "minipass-pipeline": "^1.2.4",
         "minipass-pipeline": "^1.2.4",
         "mkdirp": "^1.0.4",
         "mkdirp": "^1.0.4",
@@ -3851,10 +3862,10 @@
         "node-gyp": "^7.1.2",
         "node-gyp": "^7.1.2",
         "nopt": "^5.0.0",
         "nopt": "^5.0.0",
         "npm-audit-report": "^2.1.5",
         "npm-audit-report": "^2.1.5",
-        "npm-package-arg": "^8.1.2",
+        "npm-package-arg": "^8.1.4",
         "npm-pick-manifest": "^6.1.1",
         "npm-pick-manifest": "^6.1.1",
         "npm-profile": "^5.0.3",
         "npm-profile": "^5.0.3",
-        "npm-registry-fetch": "^10.1.2",
+        "npm-registry-fetch": "^11.0.0",
         "npm-user-validate": "^1.0.1",
         "npm-user-validate": "^1.0.1",
         "npmlog": "~4.1.2",
         "npmlog": "~4.1.2",
         "opener": "^1.5.2",
         "opener": "^1.5.2",
@@ -3878,7 +3889,7 @@
       },
       },
       "dependencies": {
       "dependencies": {
         "@npmcli/arborist": {
         "@npmcli/arborist": {
-          "version": "2.6.0",
+          "version": "2.6.2",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
             "@npmcli/installed-package-contents": "^1.0.7",
             "@npmcli/installed-package-contents": "^1.0.7",
@@ -3897,7 +3908,7 @@
             "npm-install-checks": "^4.0.0",
             "npm-install-checks": "^4.0.0",
             "npm-package-arg": "^8.1.0",
             "npm-package-arg": "^8.1.0",
             "npm-pick-manifest": "^6.1.0",
             "npm-pick-manifest": "^6.1.0",
-            "npm-registry-fetch": "^10.0.0",
+            "npm-registry-fetch": "^11.0.0",
             "pacote": "^11.2.6",
             "pacote": "^11.2.6",
             "parse-conflict-json": "^1.1.1",
             "parse-conflict-json": "^1.1.1",
             "promise-all-reject-late": "^1.0.0",
             "promise-all-reject-late": "^1.0.0",
@@ -4412,15 +4423,6 @@
           "version": "0.6.1",
           "version": "0.6.1",
           "bundled": true
           "bundled": true
         },
         },
-        "form-data": {
-          "version": "2.3.3",
-          "bundled": true,
-          "requires": {
-            "asynckit": "^0.4.0",
-            "combined-stream": "^1.0.6",
-            "mime-types": "^2.1.12"
-          }
-        },
         "fs-minipass": {
         "fs-minipass": {
           "version": "2.1.0",
           "version": "2.1.0",
           "bundled": true,
           "bundled": true,
@@ -4567,7 +4569,7 @@
           }
           }
         },
         },
         "iconv-lite": {
         "iconv-lite": {
-          "version": "0.6.2",
+          "version": "0.6.3",
           "bundled": true,
           "bundled": true,
           "optional": true,
           "optional": true,
           "requires": {
           "requires": {
@@ -4720,13 +4722,13 @@
           "bundled": true
           "bundled": true
         },
         },
         "libnpmaccess": {
         "libnpmaccess": {
-          "version": "4.0.2",
+          "version": "4.0.3",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
             "aproba": "^2.0.0",
             "aproba": "^2.0.0",
             "minipass": "^3.1.1",
             "minipass": "^3.1.1",
             "npm-package-arg": "^8.1.2",
             "npm-package-arg": "^8.1.2",
-            "npm-registry-fetch": "^10.0.0"
+            "npm-registry-fetch": "^11.0.0"
           }
           }
         },
         },
         "libnpmdiff": {
         "libnpmdiff": {
@@ -4744,7 +4746,7 @@
           }
           }
         },
         },
         "libnpmexec": {
         "libnpmexec": {
-          "version": "1.1.1",
+          "version": "1.2.0",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
             "@npmcli/arborist": "^2.3.0",
             "@npmcli/arborist": "^2.3.0",
@@ -4768,19 +4770,19 @@
           }
           }
         },
         },
         "libnpmhook": {
         "libnpmhook": {
-          "version": "6.0.2",
+          "version": "6.0.3",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
             "aproba": "^2.0.0",
             "aproba": "^2.0.0",
-            "npm-registry-fetch": "^10.0.0"
+            "npm-registry-fetch": "^11.0.0"
           }
           }
         },
         },
         "libnpmorg": {
         "libnpmorg": {
-          "version": "2.0.2",
+          "version": "2.0.3",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
             "aproba": "^2.0.0",
             "aproba": "^2.0.0",
-            "npm-registry-fetch": "^10.0.0"
+            "npm-registry-fetch": "^11.0.0"
           }
           }
         },
         },
         "libnpmpack": {
         "libnpmpack": {
@@ -4793,29 +4795,29 @@
           }
           }
         },
         },
         "libnpmpublish": {
         "libnpmpublish": {
-          "version": "4.0.1",
+          "version": "4.0.2",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
             "normalize-package-data": "^3.0.2",
             "normalize-package-data": "^3.0.2",
             "npm-package-arg": "^8.1.2",
             "npm-package-arg": "^8.1.2",
-            "npm-registry-fetch": "^10.0.0",
+            "npm-registry-fetch": "^11.0.0",
             "semver": "^7.1.3",
             "semver": "^7.1.3",
             "ssri": "^8.0.1"
             "ssri": "^8.0.1"
           }
           }
         },
         },
         "libnpmsearch": {
         "libnpmsearch": {
-          "version": "3.1.1",
+          "version": "3.1.2",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
-            "npm-registry-fetch": "^10.0.0"
+            "npm-registry-fetch": "^11.0.0"
           }
           }
         },
         },
         "libnpmteam": {
         "libnpmteam": {
-          "version": "2.0.3",
+          "version": "2.0.4",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
             "aproba": "^2.0.0",
             "aproba": "^2.0.0",
-            "npm-registry-fetch": "^10.0.0"
+            "npm-registry-fetch": "^11.0.0"
           }
           }
         },
         },
         "libnpmversion": {
         "libnpmversion": {
@@ -4837,11 +4839,11 @@
           }
           }
         },
         },
         "make-fetch-happen": {
         "make-fetch-happen": {
-          "version": "8.0.14",
+          "version": "9.0.2",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
             "agentkeepalive": "^4.1.3",
             "agentkeepalive": "^4.1.3",
-            "cacache": "^15.0.5",
+            "cacache": "^15.2.0",
             "http-cache-semantics": "^4.1.0",
             "http-cache-semantics": "^4.1.0",
             "http-proxy-agent": "^4.0.1",
             "http-proxy-agent": "^4.0.1",
             "https-proxy-agent": "^5.0.0",
             "https-proxy-agent": "^5.0.0",
@@ -4852,20 +4854,21 @@
             "minipass-fetch": "^1.3.2",
             "minipass-fetch": "^1.3.2",
             "minipass-flush": "^1.0.5",
             "minipass-flush": "^1.0.5",
             "minipass-pipeline": "^1.2.4",
             "minipass-pipeline": "^1.2.4",
+            "negotiator": "^0.6.2",
             "promise-retry": "^2.0.1",
             "promise-retry": "^2.0.1",
             "socks-proxy-agent": "^5.0.0",
             "socks-proxy-agent": "^5.0.0",
             "ssri": "^8.0.0"
             "ssri": "^8.0.0"
           }
           }
         },
         },
         "mime-db": {
         "mime-db": {
-          "version": "1.47.0",
+          "version": "1.48.0",
           "bundled": true
           "bundled": true
         },
         },
         "mime-types": {
         "mime-types": {
-          "version": "2.1.30",
+          "version": "2.1.31",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
-            "mime-db": "1.47.0"
+            "mime-db": "1.48.0"
           }
           }
         },
         },
         "minimatch": {
         "minimatch": {
@@ -4957,6 +4960,10 @@
           "version": "0.0.8",
           "version": "0.0.8",
           "bundled": true
           "bundled": true
         },
         },
+        "negotiator": {
+          "version": "0.6.2",
+          "bundled": true
+        },
         "node-gyp": {
         "node-gyp": {
           "version": "7.1.2",
           "version": "7.1.2",
           "bundled": true,
           "bundled": true,
@@ -5016,7 +5023,7 @@
           "bundled": true
           "bundled": true
         },
         },
         "npm-package-arg": {
         "npm-package-arg": {
-          "version": "8.1.2",
+          "version": "8.1.4",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
             "hosted-git-info": "^4.0.1",
             "hosted-git-info": "^4.0.1",
@@ -5045,18 +5052,17 @@
           }
           }
         },
         },
         "npm-profile": {
         "npm-profile": {
-          "version": "5.0.3",
+          "version": "5.0.4",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
-            "npm-registry-fetch": "^10.0.0"
+            "npm-registry-fetch": "^11.0.0"
           }
           }
         },
         },
         "npm-registry-fetch": {
         "npm-registry-fetch": {
-          "version": "10.1.2",
+          "version": "11.0.0",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
-            "lru-cache": "^6.0.0",
-            "make-fetch-happen": "^8.0.9",
+            "make-fetch-happen": "^9.0.1",
             "minipass": "^3.1.3",
             "minipass": "^3.1.3",
             "minipass-fetch": "^1.3.0",
             "minipass-fetch": "^1.3.0",
             "minipass-json-stream": "^1.0.1",
             "minipass-json-stream": "^1.0.1",
@@ -5109,7 +5115,7 @@
           }
           }
         },
         },
         "pacote": {
         "pacote": {
-          "version": "11.3.3",
+          "version": "11.3.4",
           "bundled": true,
           "bundled": true,
           "requires": {
           "requires": {
             "@npmcli/git": "^2.0.1",
             "@npmcli/git": "^2.0.1",
@@ -5125,7 +5131,7 @@
             "npm-package-arg": "^8.0.1",
             "npm-package-arg": "^8.0.1",
             "npm-packlist": "^2.1.4",
             "npm-packlist": "^2.1.4",
             "npm-pick-manifest": "^6.0.0",
             "npm-pick-manifest": "^6.0.0",
-            "npm-registry-fetch": "^10.0.0",
+            "npm-registry-fetch": "^11.0.0",
             "promise-retry": "^2.0.1",
             "promise-retry": "^2.0.1",
             "read-package-json-fast": "^2.0.1",
             "read-package-json-fast": "^2.0.1",
             "rimraf": "^3.0.2",
             "rimraf": "^3.0.2",
@@ -5147,7 +5153,7 @@
           "bundled": true
           "bundled": true
         },
         },
         "path-parse": {
         "path-parse": {
-          "version": "1.0.6",
+          "version": "1.0.7",
           "bundled": true
           "bundled": true
         },
         },
         "performance-now": {
         "performance-now": {
@@ -5283,6 +5289,15 @@
             "uuid": "^3.3.2"
             "uuid": "^3.3.2"
           },
           },
           "dependencies": {
           "dependencies": {
+            "form-data": {
+              "version": "2.3.3",
+              "bundled": true,
+              "requires": {
+                "asynckit": "^0.4.0",
+                "combined-stream": "^1.0.6",
+                "mime-types": "^2.1.12"
+              }
+            },
             "tough-cookie": {
             "tough-cookie": {
               "version": "2.5.0",
               "version": "2.5.0",
               "bundled": true,
               "bundled": true,
@@ -5377,7 +5392,7 @@
           }
           }
         },
         },
         "spdx-license-ids": {
         "spdx-license-ids": {
-          "version": "3.0.7",
+          "version": "3.0.9",
           "bundled": true
           "bundled": true
         },
         },
         "sshpk": {
         "sshpk": {
@@ -5706,9 +5721,9 @@
       }
       }
     },
     },
     "prism-media": {
     "prism-media": {
-      "version": "1.2.9",
-      "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.9.tgz",
-      "integrity": "sha512-UHCYuqHipbTR1ZsXr5eg4JUmHER8Ss4YEb9Azn+9zzJ7/jlTtD1h0lc4g6tNx3eMlB8Mp6bfll0LPMAV4R6r3Q==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.1.tgz",
+      "integrity": "sha512-nyYAa3KB4qteJIqdguKmwxTJgy55xxUtkJ3uRnOvO5jO+frci+9zpRXw6QZVcfDeva3S654fU9+26P2OSTzjHw==",
       "requires": {}
       "requires": {}
     },
     },
     "pump": {
     "pump": {
@@ -5775,9 +5790,9 @@
       }
       }
     },
     },
     "tslib": {
     "tslib": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
-      "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w=="
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
     },
     },
     "tweetnacl": {
     "tweetnacl": {
       "version": "1.0.3",
       "version": "1.0.3",

+ 5 - 5
package.json

@@ -15,15 +15,15 @@
     "node": ">=14.0.0"
     "node": ">=14.0.0"
   },
   },
   "dependencies": {
   "dependencies": {
-    "cheerio": "^1.0.0-rc.6",
+    "cheerio": "^1.0.0-rc.10",
     "datetime-difference": "^1.0.2",
     "datetime-difference": "^1.0.2",
     "discord-oauth2": "^2.6.0",
     "discord-oauth2": "^2.6.0",
-    "discord.js": "^12.5.1",
+    "discord.js": "^12.5.3",
     "dotenv": "^10.0.0",
     "dotenv": "^10.0.0",
-    "full-icu": "^1.3.1",
-    "got": "^11.8.1",
+    "full-icu": "^1.3.4",
+    "got": "^11.8.2",
     "htmlparser2": "^6.1.0",
     "htmlparser2": "^6.1.0",
-    "npm": "^7.11.2",
+    "npm": "^7.17.0",
     "pg": "^8.6.0"
     "pg": "^8.6.0"
   },
   },
   "repository": {
   "repository": {

+ 9 - 7
util/edit_diff.js

@@ -52,13 +52,14 @@ function diffParser(html, more, whitespace) {
 			}
 			}
 		},
 		},
 		onclosetag: (tagname) => {
 		onclosetag: (tagname) => {
-			current_tag = '';
 			if ( tagname === 'ins' ) current_tag = 'tda';
 			if ( tagname === 'ins' ) current_tag = 'tda';
 			if ( tagname === 'del' ) current_tag = 'tdd';
 			if ( tagname === 'del' ) current_tag = 'tdd';
+			if ( tagname === 'td' ) current_tag = '';
 			if ( tagname === 'tr' ) {
 			if ( tagname === 'tr' ) {
 				if ( last_ins !== null ) {
 				if ( last_ins !== null ) {
 					ins_length++;
 					ins_length++;
-					if ( empty && last_ins.trim().length && !last_ins.includes( '**' ) ) {
+					if ( empty && last_ins.trim().length ) {
+						if ( last_ins.includes( '**' ) ) last_ins = last_ins.replace( /\*\*/g, '__' );
 						ins_length += 4;
 						ins_length += 4;
 						last_ins = '**' + last_ins + '**';
 						last_ins = '**' + last_ins + '**';
 					}
 					}
@@ -68,7 +69,8 @@ function diffParser(html, more, whitespace) {
 				}
 				}
 				if ( last_del !== null ) {
 				if ( last_del !== null ) {
 					del_length++;
 					del_length++;
-					if ( empty && last_del.trim().length && !last_del.includes( '~~' ) ) {
+					if ( empty && last_del.trim().length ) {
+						if ( last_del.includes( '~~' ) ) last_del = last_del.replace( /\~\~/g, '__' );
 						del_length += 4;
 						del_length += 4;
 						last_del = '~~' + last_del + '~~';
 						last_del = '~~' + last_del + '~~';
 					}
 					}
@@ -84,13 +86,13 @@ function diffParser(html, more, whitespace) {
 	parser.end();
 	parser.end();
 	var compare = ['', ''];
 	var compare = ['', ''];
 	if ( small_prev_del.length ) {
 	if ( small_prev_del.length ) {
-		if ( small_prev_del.replace( /\~\~/g, '' ).trim().length ) {
-			compare[0] = small_prev_del.replace( /\~\~\~\~/g, '' );
+		if ( small_prev_del.replace( /\~\~|__/g, '' ).trim().length ) {
+			compare[0] = small_prev_del.replace( /\~\~\~\~|____/g, '' );
 		} else compare[0] = whitespace;
 		} else compare[0] = whitespace;
 	}
 	}
 	if ( small_prev_ins.length ) {
 	if ( small_prev_ins.length ) {
-		if ( small_prev_ins.replace( /\*\*/g, '' ).trim().length ) {
-			compare[1] = small_prev_ins.replace( /\*\*\*\*/g, '' );
+		if ( small_prev_ins.replace( /\*\*|__/g, '' ).trim().length ) {
+			compare[1] = small_prev_ins.replace( /\*\*\*\*|____/g, '' );
 		} else compare[1] = whitespace;
 		} else compare[1] = whitespace;
 	}
 	}
 	return compare;
 	return compare;