Browse Source

add private verification option

Markus-Rost 4 years ago
parent
commit
49e6149bbe
9 changed files with 403 additions and 169 deletions
  1. 23 2
      cmds/verify.js
  2. 2 0
      dashboard/i18n/en.json
  3. 20 0
      dashboard/src/index.js
  4. 2 2
      dashboard/util.js
  5. 25 6
      dashboard/verification.js
  6. 11 1
      database.js
  7. 277 126
      functions/verify.js
  8. 2 0
      i18n/en.json
  9. 41 32
      interactions/verify.js

+ 23 - 2
cmds/verify.js

@@ -22,7 +22,7 @@ function cmd_verify(lang, msg, args, line, wiki) {
 		return;
 	}
 	
-	db.query( 'SELECT role, editcount, postcount, usergroup, accountage, rename FROM verification WHERE guild = $1 AND channel LIKE $2 ORDER BY configid ASC', [msg.guild.id, '%|' + msg.channel.id + '|%'] ).then( ({rows}) => {
+	db.query( 'SELECT logchannel, flags, onsuccess, onmatch, role, editcount, postcount, usergroup, accountage, rename FROM verification LEFT JOIN verifynotice ON verification.guild = verifynotice.guild WHERE verification.guild = $1 AND channel LIKE $2 ORDER BY configid ASC', [msg.guild.id, '%|' + msg.channel.id + '|%'] ).then( ({rows}) => {
 		if ( !rows.length ) {
 			if ( msg.onlyVerifyCommand ) return;
 			return msg.replyMsg( lang.get('verify.missing') + ( msg.isAdmin() ? '\n`' + ( patreons[msg.guild.id] || process.env.prefix ) + 'verification`' : '' ) );
@@ -66,6 +66,7 @@ function cmd_verify(lang, msg, args, line, wiki) {
 				} ).then( message => {
 					msg.reactEmoji('📩');
 					allowDelete(message, msg.author.id);
+					msg.delete({timeout: 60000, reason: lang.get('verify.footer')}).catch(log_error);
 				}, error => {
 					if ( error?.code === 50007 ) { // CANNOT_MESSAGE_USER
 						return msg.replyMsg( lang.get('verify.oauth_private') );
@@ -123,6 +124,7 @@ function cmd_verify(lang, msg, args, line, wiki) {
 					} ).then( message => {
 						msg.reactEmoji('📩');
 						allowDelete(message, msg.author.id);
+						msg.delete({timeout: 60000, reason: lang.get('verify.footer')}).catch(log_error);
 					}, error => {
 						if ( error?.code === 50007 ) { // CANNOT_MESSAGE_USER
 							return msg.replyMsg( lang.get('verify.oauth_private') );
@@ -147,7 +149,26 @@ function cmd_verify(lang, msg, args, line, wiki) {
 							}
 						]
 					});
-					msg.replyMsg( result.content, options, false, false ).then( message => {
+					if ( result.send_private ) {
+						msg.member.send( msg.channel.toString() + ', ' + result.content, {embed: options.embed, components: []} ).then( message => {
+							msg.reactEmoji('📩');
+							allowDelete(message, msg.author.id);
+							msg.delete({timeout: 60000, reason: lang.get('verify.footer')}).catch(log_error);
+						}, error => {
+							if ( error?.code === 50007 ) { // CANNOT_MESSAGE_USER
+								return msg.replyMsg( result.content, options, false, false );
+							}
+							log_error(error);
+							msg.reactEmoji('error');
+						} );
+						if ( result.logging.channel && msg.guild.channels.cache.has(result.logging.channel) ) {
+							msg.guild.channels.cache.get(result.logging.channel).send(result.logging.content, {
+								embed: result.logging.embed,
+								allowedMentions: {parse: []}
+							}).catch(log_error);
+						}
+					}
+					else msg.replyMsg( result.content, options, false, false ).then( message => {
 						if ( !result.logging.channel || !msg.guild.channels.cache.has(result.logging.channel) ) return;
 						if ( message ) {
 							if ( result.logging.embed ) result.logging.embed.addField(message.url, '<#' + msg.channel.id + '>');

+ 2 - 0
dashboard/i18n/en.json

@@ -215,6 +215,8 @@
             "confirm": "Do you really want to delete the verification?",
             "editcount": "Minimal edit count:",
             "entry": "Verification #$1",
+            "flag_logall": "Log unsuccessful verifications:",
+            "flag_private": "Private command responses:",
             "logging": "Logging channel:",
             "match": "Missing requirements notice:",
             "match_placeholder": "Markdown text on matching Discord tags but not fulfilling any requirements for roles.",

+ 20 - 0
dashboard/src/index.js

@@ -288,6 +288,26 @@ if ( wiki ) {
 	}
 }
 
+/** @type {HTMLInputElement} */
+const logall = document.getElementById('wb-settings-flag_logall');
+if ( logall ) {
+	/** @type {HTMLDivElement} */
+	const hidelogall = document.getElementById('wb-settings-logall-hide');
+	/** @type {HTMLSelectElement} */
+	const logchannel = document.getElementById('wb-settings-channel');
+	if ( logchannel ) logchannel.addEventListener( 'input', function() {
+		if ( this.value ) {
+			hidelogall.style.visibility = '';
+			logall.disabled = false;
+			if ( hidelogall.style.display ) hidelogall.style.display = '';
+		}
+		else {
+			hidelogall.style.visibility = 'hidden';
+			logall.disabled = true;
+		}
+	} );
+}
+
 /** @type {HTMLInputElement} */
 const usergroup = document.getElementById('wb-settings-usergroup');
 if ( usergroup ) {

+ 2 - 2
dashboard/util.js

@@ -375,10 +375,10 @@ const permissions = {
  */
 function hasPerm(all = 0, ...permission) {
 	if ( (all & permissions.ADMINISTRATOR) === permissions.ADMINISTRATOR ) return true;
-	return permission.map( perm => {
+	return permission.every( perm => {
 		let bit = permissions[perm];
 		return ( (all & bit) === bit );
-	} ).every( perm => perm );
+	} );
 }
 
 module.exports = {got, db, oauth, slashCommands, sessionData, settingsData, oauthVerify, sendMsg, addWidgets, createNotice, escapeText, hasPerm};

+ 25 - 6
dashboard/verification.js

@@ -290,7 +290,7 @@ function dashboard_verification(res, $, guild, args, dashboardLang) {
 		}
 		else if ( args[4] === 'notice' && rows.length ) {
 			$(`.channel#channel-notice`).addClass('selected');
-			return db.query( 'SELECT logchannel, onsuccess, onmatch FROM verifynotice WHERE guild = $1', [guild.id] ).then( ({rows:[row]}) => {
+			return db.query( 'SELECT logchannel, flags, onsuccess, onmatch FROM verifynotice WHERE guild = $1', [guild.id] ).then( ({rows:[row]}) => {
 				let curCat = null;
 				$('<form id="wb-settings" method="post" enctype="application/x-www-form-urlencoded">').append(
 					$('<h2>').text(dashboardLang.get('verification.form.notice')),
@@ -315,7 +315,15 @@ function dashboard_verification(res, $, guild, args, dashboardLang) {
 									if ( catChannel.is('optgroup') && !catChannel.children('option').length ) return false;
 									return true;
 								} )
-							)
+							),
+							$('<div id="wb-settings-logall-hide">').append(
+								$('<label for="wb-settings-flag_logall">').text(dashboardLang.get('verification.form.flag_logall')),
+								$('<input type="checkbox" id="wb-settings-flag_logall" name="flag_logall">').attr('checked', ( (row.flags & 1 << 1) === 1 << 1 ? '' : null ))
+							).attr('style', ( row?.logchannel ? '' : 'display: none;'))
+						),
+						$('<div>').append(
+							$('<label for="wb-settings-flag_private">').text(dashboardLang.get('verification.form.flag_private')),
+							$('<input type="checkbox" id="wb-settings-flag_private" name="flag_private">').attr('checked', ( (row.flags & 1 << 0) === 1 << 0 ? '' : null ))
 						),
 						$('<div>').append(
 							$('<label for="wb-settings-success">').text(dashboardLang.get('verification.form.success')).append(
@@ -810,6 +818,8 @@ function update_verification(res, userSettings, guild, type, settings) {
  * @param {String} type - The setting to change
  * @param {Object} settings - The new settings
  * @param {String} [settings.channel]
+ * @param {String} [settings.flag_logall]
+ * @param {String} [settings.flag_private]
  * @param {String} [settings.success]
  * @param {String} [settings.match]
  * @param {String} settings.save_settings
@@ -849,18 +859,21 @@ function update_notices(res, userSettings, guild, type, settings) {
 			return res(`/guild/${guild}/verification/${type}`, 'savefail');
 		}
 		return db.connect().then( client => {
-			return client.query( 'SELECT logchannel, onsuccess, onmatch FROM verifynotice WHERE guild = $1', [guild] ).then( ({rows:[row]}) => {
+			return client.query( 'SELECT logchannel, flags, onsuccess, onmatch FROM verifynotice WHERE guild = $1', [guild] ).then( ({rows:[row]}) => {
+				var flags = ( settings.flag_private ? 1 << 0 : 0 ) + ( settings.flag_logall ? 1 << 1 : 0 );
 				if ( !row ) {
 					if ( !( settings.channel || settings.success || settings.match ) ) {
 						return res(`/guild/${guild}/verification/${type}`, 'save');
 					}
-					return client.query( 'INSERT INTO verifynotice(guild, logchannel, onsuccess, onmatch) VALUES($1, $2, $3, $4)', [guild, settings.channel, settings.success, settings.match] ).then( () => {
+					return client.query( 'INSERT INTO verifynotice(guild, logchannel, flags, onsuccess, onmatch) VALUES($1, $2, $3, $4, $5)', [guild, settings.channel, flags, settings.success, settings.match] ).then( () => {
 						console.log( `- Dashboard: Verification notices successfully added: ${guild}` );
 						res(`/guild/${guild}/verification/${type}`, 'save');
 						return client.query( 'SELECT lang FROM discord WHERE guild = $1 AND channel IS NULL', [guild] ).then( ({rows:[channel]}) => {
 							var lang = new Lang(channel?.lang);
 							var text = lang.get('verification.dashboard.added_notice', `<@${userSettings.user.id}>`) + '\n';
 							if ( settings.channel ) text += `${lang.get('verification.logging')} <#${settings.channel}>\n`;
+							if ( settings.flag_logall ) text += `${lang.get('verification.flag_logall')} *\`${lang.get('verification.enabled')}\`*\n`;
+							if ( settings.flag_private ) text += `${lang.get('verification.flag_private')} *\`${lang.get('verification.enabled')}\`*\n`;
 							if ( settings.success ) text += `${lang.get('verification.success')} \`\`\`md\n${settings.success.replace( /```/g, '`ˋ`' )}\n\`\`\``;
 							if ( settings.match ) text += `${lang.get('verification.match')} \`\`\`md\n${settings.match.replace( /```/g, '`ˋ`' )}\n\`\`\``;
 							text += `<${new URL(`/guild/${guild}/verification/${type}`, process.env.dashboard).href}>`;
@@ -880,10 +893,10 @@ function update_notices(res, userSettings, guild, type, settings) {
 						return res(`/guild/${guild}/verification/${type}`, 'savefail');
 					} );
 				}
-				if ( settings.channel === row.logchannel && settings.success === row.onsuccess && settings.match === row.onmatch ) {
+				if ( settings.channel === row.logchannel && flags === row.flags && settings.success === row.onsuccess && settings.match === row.onmatch ) {
 					return res(`/guild/${guild}/verification/${type}`, 'save');
 				}
-				return client.query( 'UPDATE verifynotice SET logchannel = $1, onsuccess = $2, onmatch = $3 WHERE guild = $4', [settings.channel, settings.success, settings.match, guild] ).then( () => {
+				return client.query( 'UPDATE verifynotice SET logchannel = $1, flags = $2, onsuccess = $3, onmatch = $4 WHERE guild = $5', [settings.channel, flags, settings.success, settings.match, guild] ).then( () => {
 					console.log( `- Dashboard: Verification notices successfully updated: ${guild}` );
 					res(`/guild/${guild}/verification/${type}`, 'save');
 					return client.query( 'SELECT lang FROM discord WHERE guild = $1 AND channel IS NULL', [guild] ).then( ({rows:[channel]}) => {
@@ -892,6 +905,12 @@ function update_notices(res, userSettings, guild, type, settings) {
 						if ( settings.channel !== row.logchannel ) {
 							text += lang.get('verification.logging') + ' ~~' + ( row.logchannel ? `<#${row.logchannel}>` : `*\`${lang.get('verification.disabled')}\`*` ) + '~~ → ' + ( settings.channel ? `<#${settings.channel}>` : `*\`${lang.get('verification.disabled')}\`*` ) + '\n';
 						}
+						if ( ( (flags & 1 << 1) === 1 << 1 ) !== ( (row.flags & 1 << 1) === 1 << 1 ) ) {
+							text += lang.get('verification.flag_logall') + ' ~~*`' + lang.get('verification.' + ( (row.flags & 1 << 1) === 1 << 1 ? 'enabled' : 'disabled')) + '`*~~ → *`' + lang.get('verification.' + ( settings.flag_logall ? 'enabled' : 'disabled')) + '`*\n';
+						}
+						if ( ( (flags & 1 << 0) === 1 << 0 ) !== ( (row.flags & 1 << 0) === 1 << 0 ) ) {
+							text += lang.get('verification.flag_private') + ' ~~*`' + lang.get('verification.' + ( (row.flags & 1 << 0) === 1 << 0 ? 'enabled' : 'disabled')) + '`*~~ → *`' + lang.get('verification.' + ( settings.flag_private ? 'enabled' : 'disabled')) + '`*\n';
+						}
 						if ( settings.success !== row.onsuccess ) {
 							text += lang.get('verification.success') + ' ' + ( row.onsuccess ? '~~```md\n' + row.onsuccess.replace( /\\/g, '\\$&' ).replace( /```/g, '`ˋ`' ) + '\n```~~' : `~~*\`${lang.get('verification.disabled')}\`*~~ → ` ) + ( settings.success ? '```md\n' + settings.success.replace( /\\/g, '\\$&' ).replace( /```/g, '`ˋ`' ) + '\n```' : ` → *\`${lang.get('verification.disabled')}\`*\n` );
 						}

+ 11 - 1
database.js

@@ -90,7 +90,9 @@ CREATE TABLE verifynotice (
                        REFERENCES discord (main) ON DELETE CASCADE,
     logchannel TEXT,
     onsuccess  TEXT,
-    onmatch    TEXT
+    onmatch    TEXT,
+    flags      INTEGER NOT NULL
+                       DEFAULT 0
 );
 
 CREATE INDEX idx_verifynotice_guild ON verifynotice (
@@ -159,6 +161,14 @@ CREATE INDEX idx_verifynotice_guild ON verifynotice (
 
 COMMIT TRANSACTION;
 ALTER DATABASE "${process.env.PGDATABASE}" SET my.version TO 2;
+`,`
+BEGIN TRANSACTION;
+
+ALTER TABLE verifynotice
+ADD COLUMN flags INTEGER NOT NULL DEFAULT 0;
+
+COMMIT TRANSACTION;
+ALTER DATABASE "${process.env.PGDATABASE}" SET my.version TO 3;
 `];
 
 module.exports = db.connect().then( () => {

+ 277 - 126
functions/verify.js

@@ -16,13 +16,24 @@ const toTitle = require('../util/wiki.js').toTitle;
  * @param {import('../util/wiki.js')} wiki - The wiki for the message.
  * @param {Object[]} rows - The verification settings.
  * @param {String} [old_username] - The username before the search.
- * @returns {Promise<{content:String,embed:MessageEmbed,add_button:Boolean,reaction:String,oauth:String[],logging:{channel:String,content:String,embed?:MessageEmbed}}>}
+ * @returns {Promise<{content:String,embed:MessageEmbed,add_button:Boolean,send_private:Boolean,reaction:String,oauth:String[],logging:{channel:String,content:String,embed?:MessageEmbed}}>}
  */
 function verify(lang, channel, member, username, wiki, rows, old_username = '') {
+	/** @type {{logchannel:import('discord.js').TextChannel,flags:Number,onsuccess:String,onmatch:String}} */
+	var verifynotice = {
+		logchannel: rows[0].logchannel,
+		flags: rows[0].flags,
+		onsuccess: rows[0].onsuccess,
+		onmatch: rows[0].onmatch
+	};
+	verifynotice.logchannel = ( verifynotice.logchannel ? channel.guild.channels.cache.filter( logchannel => {
+		return ( logchannel.isGuild() && logchannel.permissionsFor(channel.guild.me).has(['VIEW_CHANNEL', 'SEND_MESSAGES']) );
+	} ).get(verifynotice.logchannel) : null );
 	var embed = new MessageEmbed().setFooter( lang.get('verify.footer') ).setTimestamp();
 	var result = {
 		content: '', embed,
 		add_button: channel.permissionsFor(channel.guild.me).has('EMBED_LINKS'),
+		send_private: ( (verifynotice.flags & 1 << 0) === 1 << 0 ),
 		reaction: '', oauth: [],
 		logging: {
 			channel: '',
@@ -88,6 +99,19 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 		if ( queryuser.blockexpiry ) {
 			embed.setColor('#FF0000').setDescription( lang.get('verify.user_blocked', '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) );
 			result.content = lang.get('verify.user_blocked_reply', escapeFormatting(username), queryuser.gender);
+			if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
+				result.logging.channel = verifynotice.logchannel.id;
+				if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
+					let logembed = new MessageEmbed(embed);
+					logembed.addField( lang.get('verify.discord', 'unknown'), escapeFormatting(member.user.tag) + ` (${member.toString()})`, true );
+					result.logging.embed = logembed;
+				}
+				else {
+					let logtext = '🔸 ' + lang.get('verify.user_blocked', escapeFormatting(username), queryuser.gender) + ` (${member.toString()})`;
+					logtext += '\n<' + pagelink + '>';
+					result.logging.content = logtext;
+				}
+			}
 			result.add_button = false;
 			return;
 		}
@@ -153,6 +177,17 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 				embed.addField( lang.get('verify.discord', ( authortag === discordname ? queryuser.gender : 'unknown' )), authortag, true ).addField( lang.get('verify.wiki', queryuser.gender), ( discordname || lang.get('verify.empty') ), true );
 				if ( authortag !== discordname ) {
 					embed.setColor('#FFFF00').setDescription( lang.get('verify.user_failed', member.toString(), '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) );
+					if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
+						result.logging.channel = verifynotice.logchannel.id;
+						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
+							result.logging.embed = new MessageEmbed(embed);
+						}
+						else {
+							let logtext = '🔸 ' + lang.get('verify.user_failed', member.toString(), escapeFormatting(username), queryuser.gender);
+							logtext += '\n<' + pagelink + '>';
+							result.logging.content = logtext;
+						}
+					}
 					var help_link = '';
 					if ( wiki.isGamepedia() ) help_link = lang.get('verify.help_gamepedia') + '?c=' + ( patreons[channel.guild.id] && patreons[channel.guild.id] !== process.env.prefix ? encodeURIComponent( patreons[channel.guild.id] + 'verify' ) : 'wb' ) + ( channel.name !== 'verification' ? '&ch=' + encodeURIComponent( channel.name ) : '' ) + '&user=' + toTitle(username) + '&discord=' + encodeURIComponent( member.user.username ) + '&tag=' + member.user.discriminator;
 					else if ( wiki.isFandom() ) help_link = lang.get('verify.help_fandom') + '/' + toTitle(username) + '?c=' + ( patreons[channel.guild.id] && patreons[channel.guild.id] !== process.env.prefix ? encodeURIComponent( patreons[channel.guild.id] + 'verify' ) : 'wb' ) + ( channel.name !== 'verification' ? '&ch=' + encodeURIComponent( channel.name ) : '' ) + '&user=' + encodeURIComponent( member.user.username ) + '&tag=' + member.user.discriminator + '&useskin=oasis';
@@ -194,27 +229,11 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 				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) : '' ) );
 					var text = lang.get('verify.user_verified_reply', escapeFormatting(username), queryuser.gender);
-					var verifynotice = {
-						logchannel: '',
-						onsuccess: ''
-					};
 					var verify_promise = [
 						member.roles.add( roles, lang.get('verify.audit_reason', username) ).catch( error => {
 							log_error(error);
 							embed.setColor('#008800');
 							comment.push(lang.get('verify.failed_roles'));
-						} ),
-						db.query( 'SELECT logchannel, onsuccess FROM verifynotice WHERE guild = $1', [channel.guild.id] ).then( ({rows:[row]}) => {
-							if ( !row ) return;
-							verifynotice.logchannel = row.logchannel;
-							if ( row.onsuccess ) verifynotice.onsuccess = parseNotice(row.onsuccess, {
-								editcount: queryuser.editcount,
-								postcount: queryuser.postcount,
-								accountage: Math.trunc(accountage),
-								dateformat: lang.get('dateformat')
-							}).trim();
-						}, dberror => {
-							console.log( '- Error while getting the notices: ' + dberror );
 						} )
 					];
 					if ( rename && member.displayName !== username ) {
@@ -231,20 +250,19 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 						}
 					}
 					return Promise.all(verify_promise).then( () => {
-						var logchannel = ( verifynotice.logchannel ? channel.guild.channels.cache.get(verifynotice.logchannel) : null );
 						var useLogging = false;
-						if ( logchannel && logchannel.isGuild() && logchannel.permissionsFor(channel.guild.me).has(['VIEW_CHANNEL', 'SEND_MESSAGES']) ) {
+						if ( verifynotice.logchannel ) {
 							useLogging = true;
-							result.logging.channel = logchannel.id;
-							if ( logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
-								var logembed = new MessageEmbed(embed);
+							result.logging.channel = verifynotice.logchannel.id;
+							if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
+								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 ( comment.length ) logembed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
 								result.logging.embed = logembed;
 							}
 							else {
-								var 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);
 								logtext += '\n<' + pagelink + '>';
 								if ( roles.length ) logtext += '\n**' + lang.get('verify.qualified') + '** ' + roles.map( role => '<@&' + role + '>' ).join(', ');
@@ -253,17 +271,23 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 								result.logging.content = logtext;
 							}
 						}
+						var onsuccess = ( verifynotice.onsuccess ? parseNotice(verifynotice.onsuccess, {
+							editcount: queryuser.editcount,
+							postcount: queryuser.postcount,
+							accountage: Math.trunc(accountage),
+							dateformat: lang.get('dateformat')
+						}).trim() : '' );
 						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 ( comment.length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
-							if ( verifynotice.onsuccess ) embed.addField( lang.get('verify.notice'), verifynotice.onsuccess );
+							if ( onsuccess ) embed.addField( lang.get('verify.notice'), onsuccess );
 						}
 						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(', ');
 							if ( comment.length && !useLogging ) text += '\n\n' + comment.join('\n');
-							if ( verifynotice.onsuccess ) text += '\n\n**' + lang.get('verify.notice') + '** ' + verifynotice.onsuccess;
+							if ( onsuccess ) text += '\n\n**' + lang.get('verify.notice') + '** ' + onsuccess;
 						}
 						result.content = text;
 						result.add_button = false;
@@ -272,21 +296,29 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 				
 				embed.setColor('#FFFF00').setDescription( lang.get('verify.user_matches', member.toString(), '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) );
 				result.content = lang.get('verify.user_matches_reply', escapeFormatting(username), queryuser.gender);
-				
-				return db.query( 'SELECT onmatch FROM verifynotice WHERE guild = $1', [channel.guild.id] ).then( ({rows:[row]}) => {
-					if ( !row?.onmatch ) return;
-					var onmatch = parseNotice(row.onmatch, {
-						editcount: queryuser.editcount,
-						postcount: queryuser.postcount,
-						accountage: Math.trunc(accountage),
-						dateformat: lang.get('dateformat')
-					});
-					if ( !onmatch.trim() ) return;
-					if ( channel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) embed.addField( lang.get('verify.notice'), onmatch );
-					else result.content += '\n\n**' + lang.get('verify.notice') + '** ' + onmatch;
-				}, dberror => {
-					console.log( '- Error while getting the notices: ' + dberror );
-				} );
+
+				if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
+					result.logging.channel = verifynotice.logchannel.id;
+					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
+						result.logging.embed = new MessageEmbed(embed);
+					}
+					else {
+						let logtext = '🔸 ' + lang.get('verify.user_matches', member.toString(), escapeFormatting(username), queryuser.gender);
+						logtext += '\n<' + pagelink + '>';
+						result.logging.content = logtext;
+					}
+				}
+
+				if ( !verifynotice.onmatch ) return;
+				var onmatch = parseNotice(verifynotice.onmatch, {
+					editcount: queryuser.editcount,
+					postcount: queryuser.postcount,
+					accountage: Math.trunc(accountage),
+					dateformat: lang.get('dateformat')
+				});
+				if ( !onmatch.trim() ) return;
+				if ( channel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) embed.addField( lang.get('verify.notice'), onmatch );
+				else result.content += '\n\n**' + lang.get('verify.notice') + '** ' + onmatch;
 			}, error => {
 				if ( error ) console.log( '- Error while getting the Discord tag: ' + error );
 				embed.setColor('#000000').setDescription( lang.get('verify.error') );
@@ -296,6 +328,19 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 		}, error => {
 			embed.setColor('#FF0000').setDescription( error.desc );
 			result.content = error.reply;
+			if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
+				result.logging.channel = verifynotice.logchannel.id;
+				if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
+					let logembed = new MessageEmbed(embed);
+					logembed.addField( lang.get('verify.discord', 'unknown'), escapeFormatting(member.user.tag) + ` (${member.toString()})`, true );
+					result.logging.embed = logembed;
+				}
+				else {
+					let logtext = '🔸 ' + error.desc + ` (${member.toString()})`;
+					logtext += '\n<' + pagelink + '>';
+					result.logging.content = logtext;
+				}
+			}
 			result.add_button = false;
 		} );
 		
@@ -312,6 +357,19 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 				if ( mwbody.query.globaluserinfo.locked !== undefined ) {
 					embed.setColor('#FF0000').setDescription( lang.get('verify.user_gblocked', '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) );
 					result.content = lang.get('verify.user_gblocked_reply', escapeFormatting(username), queryuser.gender);
+					if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
+						result.logging.channel = verifynotice.logchannel.id;
+						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
+							let logembed = new MessageEmbed(embed);
+							logembed.addField( lang.get('verify.discord', 'unknown'), escapeFormatting(member.user.tag) + ` (${member.toString()})`, true );
+							result.logging.embed = logembed;
+						}
+						else {
+							let logtext = '🔸 ' + lang.get('verify.user_gblocked', escapeFormatting(username), queryuser.gender) + ` (${member.toString()})`;
+							logtext += '\n<' + pagelink + '>';
+							result.logging.content = logtext;
+						}
+					}
 					result.add_button = false;
 					return;
 				}
@@ -328,6 +386,17 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 			embed.addField( lang.get('verify.discord', ( authortag === discordname ? queryuser.gender : 'unknown' )), authortag, true ).addField( lang.get('verify.wiki', queryuser.gender), ( discordname || lang.get('verify.empty') ), true );
 			if ( authortag !== discordname ) {
 				embed.setColor('#FFFF00').setDescription( lang.get('verify.user_failed', member.toString(), '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) );
+				if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
+					result.logging.channel = verifynotice.logchannel.id;
+					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
+						result.logging.embed = new MessageEmbed(embed);
+					}
+					else {
+						let logtext = '🔸 ' + lang.get('verify.user_failed', member.toString(), escapeFormatting(username), queryuser.gender);
+						logtext += '\n<' + pagelink + '>';
+						result.logging.content = logtext;
+					}
+				}
 				embed.addField( lang.get('verify.notice'), lang.get('verify.help_subpage', '**`' + member.user.tag + '`**', queryuser.gender) + '\n' + wiki.toLink('Special:MyPage/Discord', 'action=edit') );
 				result.content = lang.get('verify.user_failed_reply', escapeFormatting(username), queryuser.gender);
 				return;
@@ -358,26 +427,11 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 			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) : '' ) );
 				var text = lang.get('verify.user_verified_reply', escapeFormatting(username), queryuser.gender);
-				var verifynotice = {
-					logchannel: '',
-					onsuccess: ''
-				};
 				var verify_promise = [
 					member.roles.add( roles, lang.get('verify.audit_reason', username) ).catch( error => {
 						log_error(error);
 						embed.setColor('#008800');
 						comment.push(lang.get('verify.failed_roles'));
-					} ),
-					db.query( 'SELECT logchannel, onsuccess FROM verifynotice WHERE guild = $1', [channel.guild.id] ).then( ({rows:[row]}) => {
-						if ( !row ) return;
-						verifynotice.logchannel = row.logchannel;
-						if ( row.onsuccess ) verifynotice.onsuccess = parseNotice(row.onsuccess, {
-							editcount: queryuser.editcount,
-							accountage: Math.trunc(accountage),
-							dateformat: lang.get('dateformat')
-						}).trim();
-					}, dberror => {
-						console.log( '- Error while getting the notices: ' + dberror );
 					} )
 				];
 				if ( rename && member.displayName !== username ) {
@@ -394,12 +448,11 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 					}
 				}
 				return Promise.all(verify_promise).then( () => {
-					var logchannel = ( verifynotice.logchannel ? channel.guild.channels.cache.get(verifynotice.logchannel) : null );
 					var useLogging = false;
-					if ( logchannel && logchannel.isGuild() && logchannel.permissionsFor(channel.guild.me).has(['VIEW_CHANNEL', 'SEND_MESSAGES']) ) {
+					if ( verifynotice.logchannel ) {
 						useLogging = true;
-						result.logging.channel = logchannel.id;
-						if ( logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
+						result.logging.channel = verifynotice.logchannel.id;
+						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
 							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') );
@@ -416,17 +469,22 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 							result.logging.content = logtext;
 						}
 					}
+					var onsuccess = ( verifynotice.onsuccess ? parseNotice(verifynotice.onsuccess, {
+						editcount: queryuser.editcount,
+						accountage: Math.trunc(accountage),
+						dateformat: lang.get('dateformat')
+					}).trim() : '' );
 					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 ( comment.length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
-						if ( verifynotice.onsuccess ) embed.addField( lang.get('verify.notice'), verifynotice.onsuccess );
+						if ( onsuccess ) embed.addField( lang.get('verify.notice'), onsuccess );
 					}
 					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(', ');
 						if ( comment.length && !useLogging ) text += '\n\n' + comment.join('\n');
-						if ( verifynotice.onsuccess ) text += '\n\n**' + lang.get('verify.notice') + '** ' + verifynotice.onsuccess;
+						if ( onsuccess ) text += '\n\n**' + lang.get('verify.notice') + '** ' + onsuccess;
 					}
 					result.content = text;
 					result.add_button = false;
@@ -435,20 +493,16 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
 			
 			embed.setColor('#FFFF00').setDescription( lang.get('verify.user_matches', member.toString(), '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) );
 			result.content = lang.get('verify.user_matches_reply', escapeFormatting(username), queryuser.gender);
-				
-			return db.query( 'SELECT onmatch FROM verifynotice WHERE guild = $1', [channel.guild.id] ).then( ({rows:[row]}) => {
-				if ( !row?.onmatch ) return;
-				var onmatch = parseNotice(row.onmatch, {
-					editcount: queryuser.editcount,
-					accountage: Math.trunc(accountage),
-					dateformat: lang.get('dateformat')
-				});
-				if ( !onmatch.trim() ) return;
-				if ( channel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) embed.addField( lang.get('verify.notice'), onmatch );
-				else result.content += '\n\n**' + lang.get('verify.notice') + '** ' + onmatch;
-			}, dberror => {
-				console.log( '- Error while getting the notices: ' + dberror );
-			} );
+			
+			if ( !verifynotice.onmatch ) return;
+			var onmatch = parseNotice(verifynotice.onmatch, {
+				editcount: queryuser.editcount,
+				accountage: Math.trunc(accountage),
+				dateformat: lang.get('dateformat')
+			});
+			if ( !onmatch.trim() ) return;
+			if ( channel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) embed.addField( lang.get('verify.notice'), onmatch );
+			else result.content += '\n\n**' + lang.get('verify.notice') + '** ' + onmatch;
 		}, error => {
 			console.log( '- Error while getting the Discord tag: ' + error );
 			embed.setColor('#000000').setDescription( lang.get('verify.error') );
@@ -474,6 +528,7 @@ function verify(lang, channel, member, username, wiki, rows, old_username = '')
  * @param {import('discord.js').TextChannel} settings.channel - The channel.
  * @param {String} settings.username - The username.
  * @param {String} settings.user - The user id.
+ * @param {String} [settings.token] - The webhook token.
  * @param {Function} settings.send - The function to edit the message.
  */
 global.verifyOauthUser = function(state, access_token, settings) {
@@ -486,7 +541,7 @@ global.verifyOauthUser = function(state, access_token, settings) {
 	var username = settings.username;
 	if ( !username && !channel.permissionsFor(channel.guild.me).has(['VIEW_CHANNEL', 'SEND_MESSAGES']) ) return;
 	Promise.all([
-		db.query( 'SELECT configid, channel, role, editcount, postcount, usergroup, accountage, rename FROM verification WHERE guild = $1 AND channel LIKE $2 ORDER BY configid ASC', [channel.guild.id, '%|' + channel.id + '|%'] ).then( ({rows}) => {
+		db.query( 'SELECT logchannel, flags, onsuccess, onmatch, role, editcount, postcount, usergroup, accountage, rename FROM verification LEFT JOIN verifynotice ON verification.guild = verifynotice.guild WHERE verification.guild = $1 AND channel LIKE $2 ORDER BY configid ASC', [channel.guild.id, '%|' + channel.id + '|%'] ).then( ({rows}) => {
 			if ( !rows.length ) return Promise.reject();
 			return db.query( 'SELECT wiki, lang FROM discord WHERE guild = $1 AND (channel = $2 OR channel = $3 OR channel IS NULL) ORDER BY channel DESC NULLS LAST LIMIT 1', [channel.guild.id, channel.id, '#' + channel.parentID] ).then( ({rows: [row]}) => {
 				return {
@@ -513,6 +568,11 @@ global.verifyOauthUser = function(state, access_token, settings) {
 		} ) : null )
 	]).then( ([{rows, wiki, lang}, member]) => {
 		if ( !username || ( settings.wiki && settings.wiki !== wiki.href ) ) return settings.send?.();
+		/** @type {{logchannel:import('discord.js').TextChannel,flags:Number,onsuccess:String,onmatch:String}} */
+		var verifynotice = ( rows[0] || {} );
+		verifynotice.logchannel = ( verifynotice.logchannel ? channel.guild.channels.cache.filter( logchannel => {
+			return ( logchannel.isGuild() && logchannel.permissionsFor(channel.guild.me).has(['VIEW_CHANNEL', 'SEND_MESSAGES']) );
+		} ).get(verifynotice.logchannel) : null );
 		got.get( wiki + 'api.php?action=query&meta=siteinfo|globaluserinfo&siprop=general&guiprop=groups&guiuser=' + encodeURIComponent( username ) + '&list=users&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + encodeURIComponent( username ) + '&format=json' ).then( response => {
 			var body = response.body;
 			if ( body && body.warnings ) log_warn(body.warnings);
@@ -539,11 +599,47 @@ global.verifyOauthUser = function(state, access_token, settings) {
 			embed.setTitle( escapeFormatting(username) ).setURL( pagelink );
 			if ( queryuser.blockexpiry ) {
 				embed.setColor('#FF0000').setDescription( lang.get('verify.user_blocked', '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) );
-				return ( settings.send ? settings : channel ).send(member.toString() + ', ' + lang.get('verify.user_blocked_reply', escapeFormatting(username), queryuser.gender), {embed, allowedMentions}).catch(log_error);
+				return sendMessage(lang.get('verify.user_blocked_reply', escapeFormatting(username), queryuser.gender), {embed, allowedMentions}).then( msg => {
+					if ( (verifynotice.flags & 1 << 1) !== 1 << 1 || !verifynotice.logchannel ) return;
+					let logembed;
+					let logtext = '';
+					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
+						logembed = new MessageEmbed(embed);
+						logembed.addField( lang.get('verify.discord', 'unknown'), escapeFormatting(member.user.tag) + ` (${member.toString()})`, true );
+						if ( msg ) logembed.addField(msg.url, '<#' + channel.id + '>');
+					}
+					else {
+						logtext = '🔸 ' + lang.get('verify.user_blocked', escapeFormatting(username), queryuser.gender) + ` (${member.toString()})`;
+						logtext += '\n<' + pagelink + '>';
+						if ( msg ) logtext += '\n<#' + channel.id + '> – <' + msg.url + '>';
+					}
+					verifynotice.logchannel.send(logtext, {
+						embed: logembed,
+						allowedMentions: {parse: []}
+					}).catch(log_error);
+				}, log_error );
 			}
 			if ( body.query.globaluserinfo.locked !== undefined ) {
 				embed.setColor('#FF0000').setDescription( lang.get('verify.user_gblocked', '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) );
-				return ( settings.send ? settings : channel ).send(member.toString() + ', ' + lang.get('verify.user_gblocked_reply', escapeFormatting(username), queryuser.gender), {embed, allowedMentions}).catch(log_error);
+				return sendMessage(lang.get('verify.user_gblocked_reply', escapeFormatting(username), queryuser.gender), {embed, allowedMentions}).then( msg => {
+					if ( (verifynotice.flags & 1 << 1) !== 1 << 1 || !verifynotice.logchannel ) return;
+					let logembed;
+					let logtext = '';
+					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
+						logembed = new MessageEmbed(embed);
+						logembed.addField( lang.get('verify.discord', 'unknown'), escapeFormatting(member.user.tag) + ` (${member.toString()})`, true );
+						if ( msg ) logembed.addField(msg.url, '<#' + channel.id + '>');
+					}
+					else {
+						logtext = '🔸 ' + lang.get('verify.user_gblocked', escapeFormatting(username), queryuser.gender) + ` (${member.toString()})`;
+						logtext += '\n<' + pagelink + '>';
+						if ( msg ) logtext += '\n<#' + channel.id + '> – <' + msg.url + '>';
+					}
+					verifynotice.logchannel.send(logtext, {
+						embed: logembed,
+						allowedMentions: {parse: []}
+					}).catch(log_error);
+				}, log_error );
 			}
 			queryuser.groups.push(...body.query.globaluserinfo.groups);
 
@@ -573,26 +669,11 @@ 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) : '' ) );
 				var text = lang.get('verify.user_verified_reply', escapeFormatting(username), queryuser.gender);
 				var comment = [];
-				var verifynotice = {
-					logchannel: '',
-					onsuccess: ''
-				};
 				var verify_promise = [
 					member.roles.add( roles, lang.get('verify.audit_reason', username) ).catch( error => {
 						log_error(error);
 						embed.setColor('#008800');
 						comment.push(lang.get('verify.failed_roles'));
-					} ),
-					db.query( 'SELECT logchannel, onsuccess FROM verifynotice WHERE guild = $1', [channel.guild.id] ).then( ({rows:[row]}) => {
-						if ( !row ) return;
-						verifynotice.logchannel = row.logchannel;
-						if ( row.onsuccess ) verifynotice.onsuccess = parseNotice(row.onsuccess, {
-							editcount: queryuser.editcount,
-							accountage: Math.trunc(accountage),
-							dateformat: lang.get('dateformat')
-						}).trim();
-					}, dberror => {
-						console.log( '- Error while getting the notices: ' + dberror );
 					} )
 				];
 				if ( rename && member.displayName !== username ) {
@@ -609,13 +690,12 @@ global.verifyOauthUser = function(state, access_token, settings) {
 					}
 				}
 				return Promise.all(verify_promise).then( () => {
-					var logchannel = ( verifynotice.logchannel ? channel.guild.channels.cache.get(verifynotice.logchannel) : null );
 					var useLogging = false;
 					var logembed;
 					var logtext = '';
-					if ( logchannel && logchannel.isGuild() && logchannel.permissionsFor(channel.guild.me).has(['VIEW_CHANNEL', 'SEND_MESSAGES']) ) {
+					if ( verifynotice.logchannel ) {
 						useLogging = true;
-						if ( logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
+						if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
 							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') );
@@ -630,25 +710,31 @@ global.verifyOauthUser = function(state, access_token, settings) {
 							if ( comment.length ) logtext += '\n**' + lang.get('verify.notice') + '** ' + comment.join('\n**' + lang.get('verify.notice') + '** ');
 						}
 					}
+					var onsuccess = ( verifynotice.onsuccess ? parseNotice(verifynotice.onsuccess, {
+						editcount: queryuser.editcount,
+						postcount: queryuser.postcount,
+						accountage: Math.trunc(accountage),
+						dateformat: lang.get('dateformat')
+					}).trim() : '' );
 					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 ( comment.length && !useLogging ) embed.setColor('#008800').addField( lang.get('verify.notice'), comment.join('\n') );
-						if ( verifynotice.onsuccess ) embed.addField( lang.get('verify.notice'), verifynotice.onsuccess );
+						if ( onsuccess ) embed.addField( lang.get('verify.notice'), onsuccess );
 					}
 					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(', ');
 						if ( comment.length && !useLogging ) text += '\n\n' + comment.join('\n');
-						if ( verifynotice.onsuccess ) text += '\n\n**' + lang.get('verify.notice') + '** ' + verifynotice.onsuccess;
+						if ( onsuccess ) text += '\n\n**' + lang.get('verify.notice') + '** ' + onsuccess;
 					}
-					return ( settings.send ? settings : channel ).send(member.toString() + ', ' + text, {embed, allowedMentions}).then( msg => {
+					return sendMessage(text, {embed, allowedMentions}).then( msg => {
 						if ( !useLogging ) return;
 						if ( msg ) {
 							if ( logembed ) logembed.addField(msg.url, '<#' + channel.id + '>');
 							else logtext += '\n<#' + channel.id + '> – <' + msg.url + '>';
 						}
-						logchannel.send(logtext, {
+						verifynotice.logchannel.send(logtext, {
 							embed: logembed,
 							allowedMentions: {parse: []}
 						}).catch(log_error);
@@ -657,37 +743,102 @@ global.verifyOauthUser = function(state, access_token, settings) {
 			}
 			
 			embed.setColor('#FFFF00').setDescription( lang.get('verify.user_matches', member.toString(), '[' + escapeFormatting(username) + '](' + pagelink + ')', queryuser.gender) );
-				
-			return db.query( 'SELECT onmatch FROM verifynotice WHERE guild = $1', [channel.guild.id] ).then( ({rows:[row]}) => {
-				if ( !row?.onmatch ) return;
-				var onmatch = parseNotice(row.onmatch, {
+
+			let logembed;
+			let logtext = '';
+			if ( (verifynotice.flags & 1 << 1) === 1 << 1 && verifynotice.logchannel ) {
+				if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
+					logembed = new MessageEmbed(embed);
+				}
+				else {
+					logtext = '🔸 ' + lang.get('verify.user_matches', member.toString(), escapeFormatting(username), queryuser.gender);
+					logtext += '\n<' + pagelink + '>';
+				}
+			}
+			
+			var noticeContent = '';
+			if ( verifynotice.onmatch ) {
+				let onmatch = parseNotice(verifynotice.onmatch, {
 					editcount: queryuser.editcount,
 					accountage: Math.trunc(accountage),
 					dateformat: lang.get('dateformat')
 				});
-				if ( !onmatch.trim() ) return;
-				if ( channel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) embed.addField( lang.get('verify.notice'), onmatch );
-				else return '\n\n**' + lang.get('verify.notice') + '** ' + onmatch;
-			}, dberror => {
-				console.log( '- Error while getting the notices: ' + dberror );
-			} ).then( (noticeContent = '') => {
-				var components = [
-					{
-						type: 1,
-						components: [
-							{
-								type: 2,
-								style: 1,
-								label: lang.get('verify.button_again'),
-								emoji: {id: null, name: '🔂'},
-								custom_id: 'verify_again',
-								disabled: false
-							}
-						]
+				if ( onmatch.trim() ) {
+					if ( channel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) embed.addField( lang.get('verify.notice'), onmatch );
+					else noticeContent = '\n\n**' + lang.get('verify.notice') + '** ' + onmatch;
+				}
+			}
+			var components = [
+				{
+					type: 1,
+					components: [
+						{
+							type: 2,
+							style: 1,
+							label: lang.get('verify.button_again'),
+							emoji: {id: null, name: '🔂'},
+							custom_id: 'verify_again',
+							disabled: false
+						}
+					]
+				}
+			];
+			return sendMessage(lang.get('verify.user_matches_reply', escapeFormatting(username), queryuser.gender) + noticeContent, {embed, allowedMentions, components}).then( msg => {
+				if ( !logtext && !logembed ) return;
+				if ( msg ) {
+					if ( verifynotice.logchannel.permissionsFor(channel.guild.me).has('EMBED_LINKS') ) {
+						logembed.addField(msg.url, '<#' + channel.id + '>');
 					}
-				];
-				return ( settings.send ? settings : channel ).send(member.toString() + ', ' + lang.get('verify.user_matches_reply', escapeFormatting(username), queryuser.gender) + noticeContent, {embed, allowedMentions, components}).catch(log_error);
-			} );
+					else logtext += '\n<#' + channel.id + '> – <' + msg.url + '>';
+				}
+				verifynotice.logchannel.send(logtext, {
+					embed: logembed,
+					allowedMentions: {parse: []}
+				}).catch(log_error);
+			}, log_error );
+
+			function sendMessage(content, options) {
+				var msg;
+				if ( settings.send ) msg = settings.send(member.toString() + ', ' + content, options);
+				else if ( settings.token ) {
+					msg = channel.client.api.webhooks(channel.client.user.id, settings.token).post( {
+						data: {
+							content: member.toString() + ', ' + content,
+							allowed_mentions: options.allowed_mentions,
+							embeds: ( options.embed ? [options.embed] : [] ),
+							components: ( options.components || [] ),
+							flags: ( (verifynotice.flags & 1 << 0) === 1 << 0 ? 64 : 0 )
+						}
+					} ).then( message => {
+						if ( (verifynotice.flags & 1 << 0) === 1 << 0 ) return;
+						return channel.messages.add(message);
+					}, () => {
+						if ( (verifynotice.flags & 1 << 0) === 1 << 0 ) {
+							member.send(channel.toString() + ', ' + content, options).then( message => {
+								allowDelete(message, member.id);
+							}, error => {
+								if ( error?.code === 50007 ) { // CANNOT_MESSAGE_USER
+									return channel.send(member.toString() + ', ' + content, options);
+								}
+								log_error(error);
+							} );
+						}
+						else return channel.send(member.toString() + ', ' + content, options);
+					} );
+				}
+				else if ( (verifynotice.flags & 1 << 0) === 1 << 0 ) {
+					member.send(channel.toString() + ', ' + content, options).then( message => {
+						allowDelete(message, member.id);
+					}, error => {
+						if ( error?.code === 50007 ) { // CANNOT_MESSAGE_USER
+							return channel.send(member.toString() + ', ' + content, options);
+						}
+						log_error(error);
+					} );
+				}
+				else msg = channel.send(member.toString() + ', ' + content, options);
+				return msg;
+			}
 		}, error => {
 			console.log( '- Error while getting the user: ' + error );
 			settings.send?.();

+ 2 - 0
i18n/en.json

@@ -750,6 +750,8 @@
         "disabled": "disabled",
         "editcount": "Edit count:",
         "enabled": "enabled",
+        "flag_logall": "Logging of unsuccessful verifications:",
+        "flag_private": "Send verification command responses privately:",
         "indays": "(in days)",
         "logging": "Logging channel:",
         "match": "Missing requirements notice:",

+ 41 - 32
interactions/verify.js

@@ -40,7 +40,7 @@ function slash_verify(interaction, lang, wiki, channel) {
 		} ).catch(log_error);
 	}
 	
-	return db.query( 'SELECT role, editcount, postcount, usergroup, accountage, rename FROM verification WHERE guild = $1 AND channel LIKE $2 ORDER BY configid ASC', [interaction.guild_id, '%|' + interaction.channel_id + '|%'] ).then( ({rows}) => {
+	return db.query( 'SELECT logchannel, flags, onsuccess, onmatch, role, editcount, postcount, usergroup, accountage, rename FROM verification LEFT JOIN verifynotice ON verification.guild = verifynotice.guild WHERE verification.guild = $1 AND channel LIKE $2 ORDER BY configid ASC', [interaction.guild_id, '%|' + interaction.channel_id + '|%'] ).then( ({rows}) => {
 		if ( !rows.length ) return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
 			data: {
 				type: 4,
@@ -63,7 +63,8 @@ function slash_verify(interaction, lang, wiki, channel) {
 				}
 				oauthVerify.set(state, {
 					state, wiki: wiki.href, channel,
-					user: interaction.user.id
+					user: interaction.user.id,
+					token: interaction.token
 				});
 				interaction.client.shard.send({id: 'verifyUser', state});
 				let oauthURL = wiki + 'rest.php/oauth2/authorize?' + new URLSearchParams({
@@ -131,7 +132,7 @@ function slash_verify(interaction, lang, wiki, channel) {
 				type: 5,
 				data: {
 					allowed_mentions,
-					flags: 0
+					flags: ( (rows[0].flags & 1 << 0) === 1 << 0 ? 64 : 0 )
 				}
 			}
 		} ).then( () => {
@@ -144,35 +145,38 @@ function slash_verify(interaction, lang, wiki, channel) {
 						}
 						oauthVerify.set(state, {
 							state, wiki: wiki.href, channel,
-							user: interaction.user.id
+							user: interaction.user.id,
+							token: interaction.token
 						});
 						interaction.client.shard.send({id: 'verifyUser', state});
 						let oauthURL = wiki + 'rest.php/oauth2/authorize?' + new URLSearchParams({
 							response_type: 'code', redirect_uri: new URL('/oauth/mw', process.env.dashboard).href,
 							client_id: process.env['oauth_' + ( result.oauth[1] || result.oauth[0] )], state
 						}).toString();
-						return interaction.client.api.webhooks(interaction.application_id, interaction.token).messages('@original').delete().then( () => {
-							return interaction.client.api.webhooks(interaction.application_id, interaction.token).post( {
-								data: {
-									content: reply + lang.get('verify.oauth_message', '<' + oauthURL + '>'),
-									allowed_mentions,
+						let message = {
+							content: reply + lang.get('verify.oauth_message', '<' + oauthURL + '>'),
+							allowed_mentions,
+							components: [
+								{
+									type: 1,
 									components: [
 										{
-											type: 1,
-											components: [
-												{
-													type: 2,
-													style: 5,
-													label: lang.get('verify.oauth_button'),
-													emoji: {id: null, name: '🔗'},
-													url: oauthURL,
-													disabled: false
-												}
-											]
+											type: 2,
+											style: 5,
+											label: lang.get('verify.oauth_button'),
+											emoji: {id: null, name: '🔗'},
+											url: oauthURL,
+											disabled: false
 										}
-									],
-									flags: 64
+									]
 								}
+							]
+						}
+						if ( result.send_private ) return sendMessage(interaction, message, channel, false);
+						message.flags = 64;
+						return interaction.client.api.webhooks(interaction.application_id, interaction.token).messages('@original').delete().then( () => {
+							return interaction.client.api.webhooks(interaction.application_id, interaction.token).post( {
+								data: message
 							} ).catch(log_error);
 						}, log_error );
 					}
@@ -182,7 +186,7 @@ function slash_verify(interaction, lang, wiki, channel) {
 						allowed_mentions,
 						components: []
 					};
-					if ( result.add_button ) message.components.push({
+					if ( result.add_button && !result.send_private ) message.components.push({
 						type: 1,
 						components: [
 							{
@@ -202,7 +206,7 @@ function slash_verify(interaction, lang, wiki, channel) {
 					}
 					return sendMessage(interaction, message, channel, false).then( msg => {
 						if ( !result.logging.channel || !channel.guild.channels.cache.has(result.logging.channel) ) return;
-						if ( msg ) {
+						if ( msg && !result.send_private ) {
 							if ( result.logging.embed ) result.logging.embed.addField(msg.url, '<#' + channel.id + '>');
 							else result.logging.content += '\n<#' + channel.id + '> – <' + msg.url + '>';
 						}
@@ -278,7 +282,7 @@ function slash_verify(interaction, lang, wiki, channel) {
 			} ).catch(log_error);
 		}, log_error);
 	}
-	return db.query( 'SELECT role, editcount, postcount, usergroup, accountage, rename FROM verification WHERE guild = $1 AND channel LIKE $2 ORDER BY configid ASC', [interaction.guild_id, '%|' + interaction.channel_id + '|%'] ).then( ({rows}) => {
+	return db.query( 'SELECT logchannel, flags, onsuccess, onmatch, role, editcount, postcount, usergroup, accountage, rename FROM verification LEFT JOIN verifynotice ON verification.guild = verifynotice.guild WHERE verification.guild = $1 AND channel LIKE $2 ORDER BY configid ASC', [interaction.guild_id, '%|' + interaction.channel_id + '|%'] ).then( ({rows}) => {
 		if ( !rows.length || !channel.guild.me.permissions.has('MANAGE_ROLES') ) {
 			return interaction.client.api.interactions(interaction.id, interaction.token).callback.post( {
 				data: {type: 6}
@@ -311,8 +315,8 @@ function slash_verify(interaction, lang, wiki, channel) {
 							embeds: ( options.embed ? [options.embed] : [] ),
 							components: ( options.components ? options.components : [] )
 						};
-						sendMessage(interaction, message, channel, false);
-						return interaction.client.api.webhooks(interaction.application_id, interaction.token).post( {
+						var msg = sendMessage(interaction, message, channel, false);
+						interaction.client.api.webhooks(interaction.application_id, interaction.token).post( {
 							data: {
 								content, allowed_mentions,
 								embeds: ( options.embed ? [options.embed] : [] ),
@@ -320,6 +324,7 @@ function slash_verify(interaction, lang, wiki, channel) {
 								flags: 64
 							}
 						} ).catch(log_error);
+						return msg;
 					}
 				});
 			}, log_error );
@@ -336,7 +341,8 @@ function slash_verify(interaction, lang, wiki, channel) {
 				}
 				oauthVerify.set(state, {
 					state, wiki: wiki.href, channel,
-					user: interaction.user.id
+					user: interaction.user.id,
+					token: interaction.token
 				});
 				interaction.client.shard.send({id: 'verifyUser', state});
 				let oauthURL = wiki + 'rest.php/oauth2/authorize?' + new URLSearchParams({
@@ -392,7 +398,8 @@ function slash_verify(interaction, lang, wiki, channel) {
 						}
 						oauthVerify.set(state, {
 							state, wiki: wiki.href, channel,
-							user: interaction.user.id
+							user: interaction.user.id,
+							token: interaction.token
 						});
 						interaction.client.shard.send({id: 'verifyUser', state});
 						let oauthURL = wiki + 'rest.php/oauth2/authorize?' + new URLSearchParams({
@@ -455,9 +462,11 @@ function slash_verify(interaction, lang, wiki, channel) {
 					});
 					sendMessage(interaction, message, channel, false);
 					if ( result.logging.channel && channel.guild.channels.cache.has(result.logging.channel) ) {
-						let msg_url = `https://discord.com/channels/${channel.guild.id}/${channel.id}/${interaction.message.id}`;
-						if ( result.logging.embed ) result.logging.embed.addField(msg_url, '<#' + channel.id + '>');
-						else result.logging.content += '\n<#' + channel.id + '> – <' + msg_url + '>';
+						if ( !result.send_private ) {
+							let msg_url = `https://discord.com/channels/${channel.guild.id}/${channel.id}/${interaction.message.id}`;
+							if ( result.logging.embed ) result.logging.embed.addField(msg_url, '<#' + channel.id + '>');
+							else result.logging.content += '\n<#' + channel.id + '> – <' + msg_url + '>';
+						}
 						channel.guild.channels.cache.get(result.logging.channel).send(result.logging.content, {
 							embed: result.logging.embed,
 							allowedMentions: {parse: []}