From 4bf0a1464e2b843fcced3b20ae2d2768ef74a650 Mon Sep 17 00:00:00 2001 From: chandler05 <66492208+chandler05@users.noreply.github.com> Date: Tue, 25 May 2021 15:35:59 -0500 Subject: [PATCH 01/27] Commit early verification stuff --- config/default.yml | 3 +++ config/template.yml | 3 +++ src/BotConfig.ts | 4 ++++ src/commands/CommandRegistry.ts | 2 ++ src/commands/VerifyCommand.ts | 42 +++++++++++++++++++++++++++++++++ 5 files changed, 54 insertions(+) create mode 100644 src/commands/VerifyCommand.ts diff --git a/config/default.yml b/config/default.yml index 27241077..25d6d972 100644 --- a/config/default.yml +++ b/config/default.yml @@ -16,6 +16,9 @@ embedDeletionEmoji: '🗑️' maxSearchResults: 5 +verificationTicket: MC-194393 +# WEB-XXXX + projects: - BDS - MC diff --git a/config/template.yml b/config/template.yml index 7cbee21f..e46cb219 100644 --- a/config/template.yml +++ b/config/template.yml @@ -43,6 +43,9 @@ embedDeletionEmoji: # The maximum number of results returned by the jira search command. maxSearchResults: +# The ticket ID in which users can leave a special token on to verify and link their Discord and Jira accounts +verificationTicket: + # The projects the bot should be able to find tickets for. projects: - diff --git a/src/BotConfig.ts b/src/BotConfig.ts index 2b1a37c5..84d5c694 100644 --- a/src/BotConfig.ts +++ b/src/BotConfig.ts @@ -117,6 +117,8 @@ export default class BotConfig { public static maxSearchResults: number; + public static verificationTicket: string; + public static projects: string[]; public static request: RequestConfig; @@ -144,6 +146,8 @@ export default class BotConfig { this.maxSearchResults = config.get( 'maxSearchResults' ); + this.verificationTicket = config.get( 'verificationTicket' ); + this.projects = config.get( 'projects' ); this.request = new RequestConfig(); diff --git a/src/commands/CommandRegistry.ts b/src/commands/CommandRegistry.ts index f448d89a..5c4d2f1a 100644 --- a/src/commands/CommandRegistry.ts +++ b/src/commands/CommandRegistry.ts @@ -8,6 +8,7 @@ import SendCommand from './SendCommand'; import SearchCommand from './SearchCommand'; import ShutdownCommand from './ShutdownCommand'; import TipsCommand from './TipsCommand'; +import VerifyCommand from './VerifyCommand'; export default class CommandRegistry { public static BUG_COMMAND = new BugCommand(); @@ -20,4 +21,5 @@ export default class CommandRegistry { public static SEARCH_COMMAND = new SearchCommand(); public static SHUTDOWN_COMMAND = new ShutdownCommand(); public static TIPS_COMMAND = new TipsCommand(); + public static VERIFY_COMMAND = new VerifyCommand(); } diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts new file mode 100644 index 00000000..9d152955 --- /dev/null +++ b/src/commands/VerifyCommand.ts @@ -0,0 +1,42 @@ +import { Message, MessageEmbed, User } from 'discord.js'; +import PrefixCommand from './PrefixCommand'; +import MojiraBot from '../MojiraBot'; +import BotConfig from '../BotConfig'; + +export default class VerifyCommand extends PrefixCommand { + public readonly aliases = ['verify', 'link']; + + public async run( message: Message, args: string ): Promise { + if ( args.length ) { + return false; + } + + try { + const allComments = new Map(); + const embed = new MessageEmbed(); + const token = this.randomString(15, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); + embed.setDescription( + `In order to verify, please comment the following token on the ticket ${ BotConfig.verificationTicket } using your Jira account.\n\nToken: ${ token }` + ) + const comments = await MojiraBot.jira.issueComments.getComments( { issueIdOrKey: BotConfig.verificationTicket } ); + message.author.send( embed ); + for ( const comment of comments.comments ) { + allComments.set(comment.author.name, comment.body); + } + } catch (e) { + console.log(e); + } + return true; + } + + // https://stackoverflow.com/questions/10726909/random-alpha-numeric-string-in-javascript + private randomString(length, chars) { + var result = ''; + for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]; + return result; + } + + public asString(): string { + return '!jira verify'; + } +} From 26380dd4e210a69d233ce848ad08dd55c83a032d Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Wed, 26 May 2021 20:39:17 -0400 Subject: [PATCH 02/27] Basic, but mostly working, verification --- config/main.yml | 6 + config/template.yml | 18 ++- src/BotConfig.ts | 8 ++ src/commands/VerifyCommand.ts | 92 +++++++++++--- src/events/message/MessageEventHandler.ts | 10 +- .../VerificationMessageEventHandler.ts | 112 ++++++++++++++++++ src/tasks/RemovePendingVerificationTask.ts | 25 ++++ 7 files changed, 251 insertions(+), 20 deletions(-) create mode 100644 src/events/verification/VerificationMessageEventHandler.ts create mode 100644 src/tasks/RemovePendingVerificationTask.ts diff --git a/config/main.yml b/config/main.yml index aa20b096..6970ccdb 100644 --- a/config/main.yml +++ b/config/main.yml @@ -114,3 +114,9 @@ versionFeeds: - released - unreleased - renamed + +verificationTicket: '' # Add a Mojira ticket ID here! +pendingVerificationChannel: '' # Add a pending channel here! +verificationLogChannel: '' # Add a log channel here! +verifiedRole: '' # Add a role here! +verificationInvalidationTime: 900000 \ No newline at end of file diff --git a/config/template.yml b/config/template.yml index e46cb219..1260ff0e 100644 --- a/config/template.yml +++ b/config/template.yml @@ -43,9 +43,6 @@ embedDeletionEmoji: # The maximum number of results returned by the jira search command. maxSearchResults: -# The ticket ID in which users can leave a special token on to verify and link their Discord and Jira accounts -verificationTicket: - # The projects the bot should be able to find tickets for. projects: - @@ -225,3 +222,18 @@ versionFeeds: - - ... + +# The ticket ID in which users can leave a special token on to verify and link their Discord and Jira accounts. +verificationTicket: + +# The channel that holds the pending verification request information. +pendingVerificationChannel: + +# The channel that holds the linked account information after accounts have been linked. +verificationLogChannel: + +# The role that is given to verified users. +verifiedRole: + +# The amount of time that a verification request can remain open until it expires, in milliseconds. +verificationInvalidationTime: \ No newline at end of file diff --git a/src/BotConfig.ts b/src/BotConfig.ts index 84d5c694..8fec1feb 100644 --- a/src/BotConfig.ts +++ b/src/BotConfig.ts @@ -118,6 +118,10 @@ export default class BotConfig { public static maxSearchResults: number; public static verificationTicket: string; + public static pendingVerificationChannel: string; + public static verificationLogChannel: string; + public static verifiedRole: string; + public static verificationInvalidationTime: number; public static projects: string[]; @@ -147,6 +151,10 @@ export default class BotConfig { this.maxSearchResults = config.get( 'maxSearchResults' ); this.verificationTicket = config.get( 'verificationTicket' ); + this.pendingVerificationChannel = config.get( 'pendingVerificationChannel' ); + this.verificationLogChannel = config.get( 'verificationLogChannel' ) + this.verifiedRole = config.get( 'verifiedRole' ) + this.verificationInvalidationTime = config.get( 'verificationInvalidationTime' ) this.projects = config.get( 'projects' ); diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts index 9d152955..c8e68376 100644 --- a/src/commands/VerifyCommand.ts +++ b/src/commands/VerifyCommand.ts @@ -1,36 +1,96 @@ -import { Message, MessageEmbed, User } from 'discord.js'; +import { Message, MessageEmbed, TextChannel, User } from 'discord.js'; import PrefixCommand from './PrefixCommand'; -import MojiraBot from '../MojiraBot'; import BotConfig from '../BotConfig'; +import Command from './Command'; +import DiscordUtil from '../util/DiscordUtil'; +import TaskScheduler from '../tasks/TaskScheduler'; +import RemovePendingVerificationTask from '../tasks/RemovePendingVerificationTask'; export default class VerifyCommand extends PrefixCommand { - public readonly aliases = ['verify', 'link']; + public readonly aliases = ['verify']; public async run( message: Message, args: string ): Promise { if ( args.length ) { return false; } + const pendingChannel = await DiscordUtil.getChannel( BotConfig.pendingVerificationChannel ); + + if ( pendingChannel instanceof TextChannel ) { + + let foundUser = false; + + const allMessages = await pendingChannel.messages.fetch({ limit: 100 }) + + allMessages.forEach( async thisMessage => { + if ( thisMessage.embeds === undefined ) return undefined; + if ( thisMessage.embeds[0].fields[0].value.replace(/[<>@!]/g, '' ) === message.author.id ) { + foundUser = true; + } + } ); + + try { + const role = await pendingChannel.guild.roles.fetch( BotConfig.verifiedRole ); + const targetUser = message.guild.members.fetch( message.author.id ); + + if ( (await targetUser).roles.cache.has( role.id ) ) { + message.reply( 'Your account has already been verified!' ) + message.react( '❌' ) + return false; + } + } catch ( error ) { + Command.logger.error( error ) + } + + if ( foundUser ) { + message.reply( 'You already have a pending verification request!' ) + message.react( '❌' ) + return false; + } + + } + try { - const allComments = new Map(); - const embed = new MessageEmbed(); - const token = this.randomString(15, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); - embed.setDescription( - `In order to verify, please comment the following token on the ticket ${ BotConfig.verificationTicket } using your Jira account.\n\nToken: ${ token }` - ) - const comments = await MojiraBot.jira.issueComments.getComments( { issueIdOrKey: BotConfig.verificationTicket } ); - message.author.send( embed ); - for ( const comment of comments.comments ) { - allComments.set(comment.author.name, comment.body); + const userEmbed = new MessageEmbed(); + const token = this.randomString(15, '23456789abcdeghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'); + + userEmbed.setDescription(`In order to verify, please comment the following token on the ticket [${ BotConfig.verificationTicket }](https://bugs.mojang.com/browse/${ BotConfig.verificationTicket }) using your Jira account. Make sure you only have added one comment to the ticket!\n\nToken: **${ token }**` + ); + + message.author.send( userEmbed ); + + if ( pendingChannel instanceof TextChannel ) { + + const pendingEmbed = new MessageEmbed() + .setColor( 'YELLOW' ) + .setAuthor( message.author.tag, message.author.avatarURL() ) + .addField( 'User', message.author, true ) + .addField( 'Token', token, true ) + .setTimestamp( new Date() ); + + const internalEmbed = await pendingChannel.send( pendingEmbed ) as Message; + + try { + TaskScheduler.addOneTimeMessageTask( + internalEmbed, + new RemovePendingVerificationTask(), + BotConfig.verificationInvalidationTime + ); + } catch ( error ) { + Command.logger.error( error ); + } } - } catch (e) { - console.log(e); + + message.react( '✅' ) + + } catch ( error ) { + Command.logger.error( error ) } return true; } // https://stackoverflow.com/questions/10726909/random-alpha-numeric-string-in-javascript - private randomString(length, chars) { + private randomString(length: number, chars: string) { var result = ''; for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]; return result; diff --git a/src/events/message/MessageEventHandler.ts b/src/events/message/MessageEventHandler.ts index 27d3cddd..c3671ed7 100644 --- a/src/events/message/MessageEventHandler.ts +++ b/src/events/message/MessageEventHandler.ts @@ -1,4 +1,4 @@ -import { Message } from 'discord.js'; +import { DMChannel, Message } from 'discord.js'; import BotConfig from '../../BotConfig'; import CommandExecutor from '../../commands/CommandExecutor'; import DiscordUtil from '../../util/DiscordUtil'; @@ -6,6 +6,7 @@ import EventHandler from '../EventHandler'; import RequestEventHandler from '../request/RequestEventHandler'; import TestingRequestEventHandler from '../request/TestingRequestEventHandler'; import InternalProgressEventHandler from '../internal/InternalProgressEventHandler'; +import VerificationMessageEventHandler from '../verification/VerificationMessageEventHandler'; export default class MessageEventHandler implements EventHandler<'message'> { public readonly eventName = 'message'; @@ -15,6 +16,7 @@ export default class MessageEventHandler implements EventHandler<'message'> { private readonly requestEventHandler: RequestEventHandler; private readonly testingRequestEventHandler: TestingRequestEventHandler; private readonly internalProgressEventHandler: InternalProgressEventHandler; + private readonly verificationMessageEventHandler: VerificationMessageEventHandler; constructor( botUserId: string, internalChannels: Map ) { this.botUserId = botUserId; @@ -22,6 +24,7 @@ export default class MessageEventHandler implements EventHandler<'message'> { this.requestEventHandler = new RequestEventHandler( internalChannels ); this.testingRequestEventHandler = new TestingRequestEventHandler(); this.internalProgressEventHandler = new InternalProgressEventHandler(); + this.verificationMessageEventHandler = new VerificationMessageEventHandler(); } // This syntax is used to ensure that `this` refers to the `MessageEventHandler` object @@ -56,6 +59,11 @@ export default class MessageEventHandler implements EventHandler<'message'> { await this.internalProgressEventHandler.onEvent( message ); // Don't reply in internal request channels + return; + } else if ( message.channel.type === "dm" ) { + // This message is a direct message + await this.verificationMessageEventHandler.onEvent( message ); + return; } diff --git a/src/events/verification/VerificationMessageEventHandler.ts b/src/events/verification/VerificationMessageEventHandler.ts new file mode 100644 index 00000000..63c93075 --- /dev/null +++ b/src/events/verification/VerificationMessageEventHandler.ts @@ -0,0 +1,112 @@ +import { Guild, Message, MessageEmbed, TextChannel } from 'discord.js'; +import * as log4js from 'log4js'; +import BotConfig from '../../BotConfig'; +import EventHandler from '../EventHandler'; +import MojiraBot from '../../MojiraBot'; +import DiscordUtil from '../../util/DiscordUtil'; + +export default class VerificationMessageEventHandler implements EventHandler<'message'> { + public readonly eventName = 'message'; + + private logger = log4js.getLogger( 'VerificationMessageEventHandler' ); + + public onEvent = async ( origin: Message ): Promise => { + const args = origin.content.split(/\s(.+)/) + if ( args[0] !== "link" || !args[1] ) { + // This message does not start with 'link' or does not contain a username argument + return; + } + + const username = args[1] + + try { + const allComments = new Map(); + const comments = await MojiraBot.jira.issueComments.getComments( { issueIdOrKey: BotConfig.verificationTicket } ); + for ( const comment of comments.comments ) { + allComments.set( comment.author.name, comment.body ); + } + + const pendingChannel = await DiscordUtil.getChannel( BotConfig.pendingVerificationChannel ); + const logChannel = await DiscordUtil.getChannel( BotConfig.verificationLogChannel ); + + if (pendingChannel instanceof TextChannel && logChannel instanceof TextChannel) { + + let foundEmbed = false + + const allMessages = await pendingChannel.messages.fetch({ limit: 100 }) + + allMessages.forEach( async message => { + + const embeds = message.embeds + if ( embeds.length == 0 ) return undefined; + + const userId = embeds[0].fields[0].value.replace(/[<>@!]/g, "") + if ( userId !== origin.author.id ) return false; + + const enteredComment = allComments.get( username ) + + if ( enteredComment === embeds[0].fields[1].value ) { + + this.logger.info( `Successfully verified user ${ origin.author.tag }` ) + foundEmbed = true + + if ( message.deletable ) { + try { + await message.delete(); + } catch ( error ) { + this.logger.error( error ); + } + } else { + this.logger.log( 'Failed to delete message' ); + } + + const logEmbed = new MessageEmbed() + .setColor( 'GREEN' ) + .setAuthor( origin.author.tag, origin.author.avatarURL() ) + .addField( 'Discord', origin.author, true ) + .addField( 'Mojira', `[${ username }](https://bugs.mojang.com/secure/ViewProfile.jspa?name=${ username })`, true ); + logChannel.send( logEmbed ); + + const userEmbed = new MessageEmbed() + .setColor( 'GREEN' ) + .setTitle( 'Your account has been verified!' ) + .setDescription( 'You have successfully linked your Mojira and Discord accounts.' ); + origin.author.send( userEmbed ); + + try { + const role = await logChannel.guild.roles.fetch( BotConfig.verifiedRole ); + const targetUser = message.guild.members.fetch( origin.author.id ); + + (await targetUser).roles.add( role ); + this.logger.info( `Added role ${ BotConfig.verifiedRole } to user ${ origin.author.tag }` ) + } catch ( error ) { + this.logger.error( error ) + } + + } else { + this.logger.info( `Failed to verify user ${ origin.author.tag }: Not a match` ) + try { + origin.author.send( `Failed to verify your account!` ) + } catch ( error ) { + this.logger.error( error ) + } + return false; + } + } ); + + if ( !foundEmbed ) { + try { + origin.author.send( 'Failed to verify your account! Did you send `jira verify` first?' ) + } catch ( error ) { + this.logger.error( error ) + } + } + + } + + + } catch ( error ) { + this.logger.error( error ); + } + }; +} diff --git a/src/tasks/RemovePendingVerificationTask.ts b/src/tasks/RemovePendingVerificationTask.ts new file mode 100644 index 00000000..edfd34d9 --- /dev/null +++ b/src/tasks/RemovePendingVerificationTask.ts @@ -0,0 +1,25 @@ +import { Message } from 'discord.js'; +import MessageTask from './MessageTask'; +import * as log4js from 'log4js'; +import DiscordUtil from '../util/DiscordUtil' + +export default class RemovePendingVerificationTask extends MessageTask { + private static logger = log4js.getLogger( 'RemovePendingVerificationTask' ); + + public async run( message: Message ): Promise { + // If the message is undefined or has been deleted, don't do anything + if ( message === undefined || message.deleted ) return; + + const user = DiscordUtil.getMember( message.guild, message.embeds[0].fields[0].value.replace( /[<>@!]/g, "" ) ) + + if ( message.deletable ) { + try { + await message.delete(); + (await user).send( 'Your verification token has expired! Send `!jira verify` again to obtain a new token.' ) + RemovePendingVerificationTask.logger.info( `Cleared pending verification by user ${ (await user).user.tag }` ) + } catch ( error ) { + RemovePendingVerificationTask.logger.error( error ); + } + } + } +} From bd41ae547bf3af4ffc2f714f98b2bb47c33d2685 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Wed, 26 May 2021 21:04:43 -0400 Subject: [PATCH 03/27] Fix some syntax issues --- src/commands/VerifyCommand.ts | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts index c8e68376..477e729b 100644 --- a/src/commands/VerifyCommand.ts +++ b/src/commands/VerifyCommand.ts @@ -1,4 +1,4 @@ -import { Message, MessageEmbed, TextChannel, User } from 'discord.js'; +import { Message, MessageEmbed, TextChannel } from 'discord.js'; import PrefixCommand from './PrefixCommand'; import BotConfig from '../BotConfig'; import Command from './Command'; @@ -19,12 +19,12 @@ export default class VerifyCommand extends PrefixCommand { if ( pendingChannel instanceof TextChannel ) { let foundUser = false; - - const allMessages = await pendingChannel.messages.fetch({ limit: 100 }) + + const allMessages = await pendingChannel.messages.fetch( { limit: 100 } ); allMessages.forEach( async thisMessage => { if ( thisMessage.embeds === undefined ) return undefined; - if ( thisMessage.embeds[0].fields[0].value.replace(/[<>@!]/g, '' ) === message.author.id ) { + if ( thisMessage.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) === message.author.id ) { foundUser = true; } } ); @@ -32,19 +32,19 @@ export default class VerifyCommand extends PrefixCommand { try { const role = await pendingChannel.guild.roles.fetch( BotConfig.verifiedRole ); const targetUser = message.guild.members.fetch( message.author.id ); - - if ( (await targetUser).roles.cache.has( role.id ) ) { - message.reply( 'Your account has already been verified!' ) - message.react( '❌' ) + + if ( ( await targetUser ).roles.cache.has( role.id ) ) { + await message.channel.send( `${ message.author }, your account has already been verified!` ); + await message.react( '❌' ); return false; } } catch ( error ) { - Command.logger.error( error ) + Command.logger.error( error ); } if ( foundUser ) { - message.reply( 'You already have a pending verification request!' ) - message.react( '❌' ) + await message.channel.send( `${ message.author }, you already have a pending verification request!` ); + await message.react( '❌' ) return false; } @@ -52,10 +52,9 @@ export default class VerifyCommand extends PrefixCommand { try { const userEmbed = new MessageEmbed(); - const token = this.randomString(15, '23456789abcdeghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'); + const token = this.randomString( 15, '23456789abcdeghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' ); - userEmbed.setDescription(`In order to verify, please comment the following token on the ticket [${ BotConfig.verificationTicket }](https://bugs.mojang.com/browse/${ BotConfig.verificationTicket }) using your Jira account. Make sure you only have added one comment to the ticket!\n\nToken: **${ token }**` - ); + userEmbed.setDescription( `In order to verify, please comment the following token on the ticket [${ BotConfig.verificationTicket }](https://bugs.mojang.com/browse/${ BotConfig.verificationTicket }) using your Jira account. Make sure you only have added one comment to the ticket!\n\nToken: **${ token }**` ); message.author.send( userEmbed ); @@ -90,9 +89,9 @@ export default class VerifyCommand extends PrefixCommand { } // https://stackoverflow.com/questions/10726909/random-alpha-numeric-string-in-javascript - private randomString(length: number, chars: string) { + private randomString( length: number, chars: string ): string { var result = ''; - for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]; + for ( var i = length; i > 0; --i ) result += chars[Math.floor( Math.random() * chars.length )]; return result; } From dd6f3b8d32a4911310731e5cb5b9cd2d68c0031b Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Wed, 26 May 2021 21:08:16 -0400 Subject: [PATCH 04/27] Semicolon! --- src/commands/VerifyCommand.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts index 477e729b..41b5ff10 100644 --- a/src/commands/VerifyCommand.ts +++ b/src/commands/VerifyCommand.ts @@ -44,17 +44,17 @@ export default class VerifyCommand extends PrefixCommand { if ( foundUser ) { await message.channel.send( `${ message.author }, you already have a pending verification request!` ); - await message.react( '❌' ) + await message.react( '❌' ); return false; } } try { - const userEmbed = new MessageEmbed(); const token = this.randomString( 15, '23456789abcdeghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' ); - userEmbed.setDescription( `In order to verify, please comment the following token on the ticket [${ BotConfig.verificationTicket }](https://bugs.mojang.com/browse/${ BotConfig.verificationTicket }) using your Jira account. Make sure you only have added one comment to the ticket!\n\nToken: **${ token }**` ); + const userEmbed = new MessageEmbed() + .setDescription( `In order to verify, please comment the following token on the ticket [${ BotConfig.verificationTicket }](https://bugs.mojang.com/browse/${ BotConfig.verificationTicket }) using your Jira account. Make sure you only have added one comment to the ticket!\n\nToken: **${ token }**` ); message.author.send( userEmbed ); @@ -90,8 +90,8 @@ export default class VerifyCommand extends PrefixCommand { // https://stackoverflow.com/questions/10726909/random-alpha-numeric-string-in-javascript private randomString( length: number, chars: string ): string { - var result = ''; - for ( var i = length; i > 0; --i ) result += chars[Math.floor( Math.random() * chars.length )]; + let result = ''; + for ( let i = length; i > 0; --i ) result += chars[Math.floor( Math.random() * chars.length )]; return result; } From 06247145db9b5bba779a51dff3fe8ab70b0f508b Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Wed, 26 May 2021 21:13:59 -0400 Subject: [PATCH 05/27] Spaces and awaits --- src/commands/VerifyCommand.ts | 2 +- .../VerificationMessageEventHandler.ts | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts index 41b5ff10..b002f1dc 100644 --- a/src/commands/VerifyCommand.ts +++ b/src/commands/VerifyCommand.ts @@ -56,7 +56,7 @@ export default class VerifyCommand extends PrefixCommand { const userEmbed = new MessageEmbed() .setDescription( `In order to verify, please comment the following token on the ticket [${ BotConfig.verificationTicket }](https://bugs.mojang.com/browse/${ BotConfig.verificationTicket }) using your Jira account. Make sure you only have added one comment to the ticket!\n\nToken: **${ token }**` ); - message.author.send( userEmbed ); + await message.author.send( userEmbed ); if ( pendingChannel instanceof TextChannel ) { diff --git a/src/events/verification/VerificationMessageEventHandler.ts b/src/events/verification/VerificationMessageEventHandler.ts index 63c93075..5d9ce18c 100644 --- a/src/events/verification/VerificationMessageEventHandler.ts +++ b/src/events/verification/VerificationMessageEventHandler.ts @@ -1,4 +1,4 @@ -import { Guild, Message, MessageEmbed, TextChannel } from 'discord.js'; +import { Message, MessageEmbed, TextChannel } from 'discord.js'; import * as log4js from 'log4js'; import BotConfig from '../../BotConfig'; import EventHandler from '../EventHandler'; @@ -49,7 +49,7 @@ export default class VerificationMessageEventHandler implements EventHandler<'me this.logger.info( `Successfully verified user ${ origin.author.tag }` ) foundEmbed = true - + if ( message.deletable ) { try { await message.delete(); @@ -65,18 +65,18 @@ export default class VerificationMessageEventHandler implements EventHandler<'me .setAuthor( origin.author.tag, origin.author.avatarURL() ) .addField( 'Discord', origin.author, true ) .addField( 'Mojira', `[${ username }](https://bugs.mojang.com/secure/ViewProfile.jspa?name=${ username })`, true ); - logChannel.send( logEmbed ); + await logChannel.send( logEmbed ); const userEmbed = new MessageEmbed() .setColor( 'GREEN' ) .setTitle( 'Your account has been verified!' ) .setDescription( 'You have successfully linked your Mojira and Discord accounts.' ); - origin.author.send( userEmbed ); + await origin.author.send( userEmbed ); try { const role = await logChannel.guild.roles.fetch( BotConfig.verifiedRole ); const targetUser = message.guild.members.fetch( origin.author.id ); - + (await targetUser).roles.add( role ); this.logger.info( `Added role ${ BotConfig.verifiedRole } to user ${ origin.author.tag }` ) } catch ( error ) { @@ -86,7 +86,7 @@ export default class VerificationMessageEventHandler implements EventHandler<'me } else { this.logger.info( `Failed to verify user ${ origin.author.tag }: Not a match` ) try { - origin.author.send( `Failed to verify your account!` ) + await origin.author.send( `Failed to verify your account!` ) } catch ( error ) { this.logger.error( error ) } @@ -96,15 +96,12 @@ export default class VerificationMessageEventHandler implements EventHandler<'me if ( !foundEmbed ) { try { - origin.author.send( 'Failed to verify your account! Did you send `jira verify` first?' ) + await origin.author.send( 'Failed to verify your account! Did you send `jira verify` first?' ) } catch ( error ) { this.logger.error( error ) } } - } - - } catch ( error ) { this.logger.error( error ); } From bfae410161783647f308657f5fa61a8ce9229b09 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Wed, 26 May 2021 21:20:37 -0400 Subject: [PATCH 06/27] Ah, there are the rest of the errors. --- src/commands/VerifyCommand.ts | 4 ++-- .../verification/VerificationMessageEventHandler.ts | 2 +- src/tasks/RemovePendingVerificationTask.ts | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts index b002f1dc..a6a60ec9 100644 --- a/src/commands/VerifyCommand.ts +++ b/src/commands/VerifyCommand.ts @@ -80,10 +80,10 @@ export default class VerifyCommand extends PrefixCommand { } } - message.react( '✅' ) + await message.react( '✅' ); } catch ( error ) { - Command.logger.error( error ) + Command.logger.error( error ); } return true; } diff --git a/src/events/verification/VerificationMessageEventHandler.ts b/src/events/verification/VerificationMessageEventHandler.ts index 5d9ce18c..3e1de87b 100644 --- a/src/events/verification/VerificationMessageEventHandler.ts +++ b/src/events/verification/VerificationMessageEventHandler.ts @@ -82,7 +82,7 @@ export default class VerificationMessageEventHandler implements EventHandler<'me } catch ( error ) { this.logger.error( error ) } - + } else { this.logger.info( `Failed to verify user ${ origin.author.tag }: Not a match` ) try { diff --git a/src/tasks/RemovePendingVerificationTask.ts b/src/tasks/RemovePendingVerificationTask.ts index edfd34d9..cb8465d0 100644 --- a/src/tasks/RemovePendingVerificationTask.ts +++ b/src/tasks/RemovePendingVerificationTask.ts @@ -1,7 +1,7 @@ import { Message } from 'discord.js'; import MessageTask from './MessageTask'; import * as log4js from 'log4js'; -import DiscordUtil from '../util/DiscordUtil' +import DiscordUtil from '../util/DiscordUtil'; export default class RemovePendingVerificationTask extends MessageTask { private static logger = log4js.getLogger( 'RemovePendingVerificationTask' ); @@ -9,14 +9,14 @@ export default class RemovePendingVerificationTask extends MessageTask { public async run( message: Message ): Promise { // If the message is undefined or has been deleted, don't do anything if ( message === undefined || message.deleted ) return; - - const user = DiscordUtil.getMember( message.guild, message.embeds[0].fields[0].value.replace( /[<>@!]/g, "" ) ) + + const user = DiscordUtil.getMember( message.guild, message.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) ); if ( message.deletable ) { try { await message.delete(); - (await user).send( 'Your verification token has expired! Send `!jira verify` again to obtain a new token.' ) - RemovePendingVerificationTask.logger.info( `Cleared pending verification by user ${ (await user).user.tag }` ) + await ( await user ).send( 'Your verification token has expired! Send `!jira verify` again to obtain a new token.' ); + RemovePendingVerificationTask.logger.info( `Cleared pending verification by user ${ ( await user ).user.tag }` ); } catch ( error ) { RemovePendingVerificationTask.logger.error( error ); } From 50ebd29c8ab8e3c92a3efb2ab7bb3df44b5bfd76 Mon Sep 17 00:00:00 2001 From: chandler05 <66492208+chandler05@users.noreply.github.com> Date: Wed, 26 May 2021 22:15:59 -0500 Subject: [PATCH 07/27] Adjustments to verification --- config/default.yml | 3 -- config/main.yml | 11 +++--- config/template.yml | 22 ++++++------ src/BotConfig.ts | 30 ++++++++++------ src/commands/VerifyCommand.ts | 16 ++++----- .../VerificationMessageEventHandler.ts | 34 +++++++++---------- 6 files changed, 62 insertions(+), 54 deletions(-) diff --git a/config/default.yml b/config/default.yml index 25d6d972..27241077 100644 --- a/config/default.yml +++ b/config/default.yml @@ -16,9 +16,6 @@ embedDeletionEmoji: '🗑️' maxSearchResults: 5 -verificationTicket: MC-194393 -# WEB-XXXX - projects: - BDS - MC diff --git a/config/main.yml b/config/main.yml index 6970ccdb..cb4fb34b 100644 --- a/config/main.yml +++ b/config/main.yml @@ -115,8 +115,9 @@ versionFeeds: - unreleased - renamed -verificationTicket: '' # Add a Mojira ticket ID here! -pendingVerificationChannel: '' # Add a pending channel here! -verificationLogChannel: '' # Add a log channel here! -verifiedRole: '' # Add a role here! -verificationInvalidationTime: 900000 \ No newline at end of file +verification: + verificationTicket: '' # Add a Mojira ticket ID here! + pendingVerificationChannel: '' # Add a pending channel here! + verificationLogChannel: '' # Add a log channel here! + verifiedRole: '' # Add a role here! + verificationInvalidationTime: 900000 \ No newline at end of file diff --git a/config/template.yml b/config/template.yml index 1260ff0e..3b6799b1 100644 --- a/config/template.yml +++ b/config/template.yml @@ -223,17 +223,19 @@ versionFeeds: - - ... -# The ticket ID in which users can leave a special token on to verify and link their Discord and Jira accounts. -verificationTicket: +# Settings for user verification on Jira. +verification: + # The ticket ID in which users can leave a special token on to verify and link their Discord and Jira accounts. + verificationTicket: -# The channel that holds the pending verification request information. -pendingVerificationChannel: + # The channel that holds the pending verification request information. + pendingVerificationChannel: -# The channel that holds the linked account information after accounts have been linked. -verificationLogChannel: + # The channel that holds the linked account information after accounts have been linked. + verificationLogChannel: -# The role that is given to verified users. -verifiedRole: + # The role that is given to verified users. + verifiedRole: -# The amount of time that a verification request can remain open until it expires, in milliseconds. -verificationInvalidationTime: \ No newline at end of file + # The amount of time that a verification request can remain open until it expires, in milliseconds. + verificationInvalidationTime: \ No newline at end of file diff --git a/src/BotConfig.ts b/src/BotConfig.ts index 8fec1feb..9fa92d1f 100644 --- a/src/BotConfig.ts +++ b/src/BotConfig.ts @@ -61,6 +61,22 @@ export class RequestConfig { } } +export class VerificationConfig { + public verificationTicket: string; + public pendingVerificationChannel: string; + public verificationLogChannel: string; + public verifiedRole: string; + public verificationInvalidationTime: number; + + constructor() { + this.verificationTicket = config.get( 'verification.verificationTicket' ); + this.pendingVerificationChannel = config.get( 'verification.pendingVerificationChannel' ); + this.verificationLogChannel = config.get( 'verification.verificationLogChannel' ) + this.verifiedRole = config.get( 'verification.verifiedRole' ) + this.verificationInvalidationTime = config.get( 'verification.verificationInvalidationTime' ) + } +} + export interface RoleConfig { emoji: string; title: string; @@ -117,16 +133,12 @@ export default class BotConfig { public static maxSearchResults: number; - public static verificationTicket: string; - public static pendingVerificationChannel: string; - public static verificationLogChannel: string; - public static verifiedRole: string; - public static verificationInvalidationTime: number; - public static projects: string[]; public static request: RequestConfig; + public static verification: VerificationConfig; + public static roleGroups: RoleGroupConfig[]; public static filterFeeds: FilterFeedConfig[]; @@ -150,11 +162,7 @@ export default class BotConfig { this.maxSearchResults = config.get( 'maxSearchResults' ); - this.verificationTicket = config.get( 'verificationTicket' ); - this.pendingVerificationChannel = config.get( 'pendingVerificationChannel' ); - this.verificationLogChannel = config.get( 'verificationLogChannel' ) - this.verifiedRole = config.get( 'verifiedRole' ) - this.verificationInvalidationTime = config.get( 'verificationInvalidationTime' ) + this.verification = new VerificationConfig(); this.projects = config.get( 'projects' ); diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts index a6a60ec9..27d51e01 100644 --- a/src/commands/VerifyCommand.ts +++ b/src/commands/VerifyCommand.ts @@ -14,7 +14,7 @@ export default class VerifyCommand extends PrefixCommand { return false; } - const pendingChannel = await DiscordUtil.getChannel( BotConfig.pendingVerificationChannel ); + const pendingChannel = await DiscordUtil.getChannel( BotConfig.verification.pendingVerificationChannel ); if ( pendingChannel instanceof TextChannel ) { @@ -24,16 +24,16 @@ export default class VerifyCommand extends PrefixCommand { allMessages.forEach( async thisMessage => { if ( thisMessage.embeds === undefined ) return undefined; - if ( thisMessage.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) === message.author.id ) { + if ( thisMessage.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) == message.author.id ) { foundUser = true; } } ); try { - const role = await pendingChannel.guild.roles.fetch( BotConfig.verifiedRole ); - const targetUser = message.guild.members.fetch( message.author.id ); + const role = await pendingChannel.guild.roles.fetch( BotConfig.verification.verifiedRole ); + const targetUser = await message.guild.members.fetch( message.author.id ); - if ( ( await targetUser ).roles.cache.has( role.id ) ) { + if ( targetUser.roles.cache.has( role.id ) ) { await message.channel.send( `${ message.author }, your account has already been verified!` ); await message.react( '❌' ); return false; @@ -54,7 +54,7 @@ export default class VerifyCommand extends PrefixCommand { const token = this.randomString( 15, '23456789abcdeghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' ); const userEmbed = new MessageEmbed() - .setDescription( `In order to verify, please comment the following token on the ticket [${ BotConfig.verificationTicket }](https://bugs.mojang.com/browse/${ BotConfig.verificationTicket }) using your Jira account. Make sure you only have added one comment to the ticket!\n\nToken: **${ token }**` ); + .setDescription( `In order to verify, please comment the following token on the ticket [${ BotConfig.verification.verificationTicket }](https://bugs.mojang.com/browse/${ BotConfig.verification.verificationTicket }) using your Jira account. Make sure you only have added one comment to the ticket!\nAfter you are done, please send \`link \` here and I will verify the account!\n\nToken: **${ token }**` ); await message.author.send( userEmbed ); @@ -67,13 +67,13 @@ export default class VerifyCommand extends PrefixCommand { .addField( 'Token', token, true ) .setTimestamp( new Date() ); - const internalEmbed = await pendingChannel.send( pendingEmbed ) as Message; + const internalEmbed = await pendingChannel.send( pendingEmbed ); try { TaskScheduler.addOneTimeMessageTask( internalEmbed, new RemovePendingVerificationTask(), - BotConfig.verificationInvalidationTime + BotConfig.verification.verificationInvalidationTime ); } catch ( error ) { Command.logger.error( error ); diff --git a/src/events/verification/VerificationMessageEventHandler.ts b/src/events/verification/VerificationMessageEventHandler.ts index 3e1de87b..525e5d2a 100644 --- a/src/events/verification/VerificationMessageEventHandler.ts +++ b/src/events/verification/VerificationMessageEventHandler.ts @@ -17,38 +17,38 @@ export default class VerificationMessageEventHandler implements EventHandler<'me return; } - const username = args[1] + const username = args[1]; try { const allComments = new Map(); - const comments = await MojiraBot.jira.issueComments.getComments( { issueIdOrKey: BotConfig.verificationTicket } ); + const comments = await MojiraBot.jira.issueComments.getComments( { issueIdOrKey: BotConfig.verification.verificationTicket } ); for ( const comment of comments.comments ) { allComments.set( comment.author.name, comment.body ); } - const pendingChannel = await DiscordUtil.getChannel( BotConfig.pendingVerificationChannel ); - const logChannel = await DiscordUtil.getChannel( BotConfig.verificationLogChannel ); + const pendingChannel = await DiscordUtil.getChannel( BotConfig.verification.pendingVerificationChannel ); + const logChannel = await DiscordUtil.getChannel( BotConfig.verification.verificationLogChannel ); - if (pendingChannel instanceof TextChannel && logChannel instanceof TextChannel) { + if ( pendingChannel instanceof TextChannel && logChannel instanceof TextChannel ) { - let foundEmbed = false + let foundEmbed = false; - const allMessages = await pendingChannel.messages.fetch({ limit: 100 }) + const allMessages = await pendingChannel.messages.fetch( { limit: 100 } ); allMessages.forEach( async message => { - const embeds = message.embeds + const embeds = message.embeds; if ( embeds.length == 0 ) return undefined; - const userId = embeds[0].fields[0].value.replace(/[<>@!]/g, "") + const userId = embeds[0].fields[0].value.replace(/[<>@!]/g, ""); if ( userId !== origin.author.id ) return false; - const enteredComment = allComments.get( username ) + const enteredComment = allComments.get( username ); - if ( enteredComment === embeds[0].fields[1].value ) { + if ( enteredComment == embeds[0].fields[1].value ) { - this.logger.info( `Successfully verified user ${ origin.author.tag }` ) - foundEmbed = true + this.logger.info( `Successfully verified user ${ origin.author.tag }` ); + foundEmbed = true; if ( message.deletable ) { try { @@ -74,11 +74,11 @@ export default class VerificationMessageEventHandler implements EventHandler<'me await origin.author.send( userEmbed ); try { - const role = await logChannel.guild.roles.fetch( BotConfig.verifiedRole ); - const targetUser = message.guild.members.fetch( origin.author.id ); + const role = await logChannel.guild.roles.fetch( BotConfig.verification.verifiedRole ); + const targetUser = await message.guild.members.fetch( origin.author.id ); - (await targetUser).roles.add( role ); - this.logger.info( `Added role ${ BotConfig.verifiedRole } to user ${ origin.author.tag }` ) + targetUser.roles.add( role ); + this.logger.info( `Added role ${ BotConfig.verification.verifiedRole } to user ${ origin.author.tag }` ) } catch ( error ) { this.logger.error( error ) } From 57ea559f148835c4174e83bece4c91639a932718 Mon Sep 17 00:00:00 2001 From: chandler05 <66492208+chandler05@users.noreply.github.com> Date: Thu, 27 May 2021 10:24:20 -0500 Subject: [PATCH 08/27] Fetch pending verifications on startup --- src/MojiraBot.ts | 24 ++++++++++++++++++++++++ src/commands/VerifyCommand.ts | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/MojiraBot.ts b/src/MojiraBot.ts index cfa9dd3e..2c6da881 100644 --- a/src/MojiraBot.ts +++ b/src/MojiraBot.ts @@ -86,6 +86,30 @@ export default class MojiraBot { const requestChannels: TextChannel[] = []; const internalChannels = new Map(); + if ( BotConfig.verification.pendingVerificationChannel ) { + const pendingChannel = await DiscordUtil.getChannel( BotConfig.verification.pendingVerificationChannel ); + if ( pendingChannel instanceof TextChannel ) { + // https://stackoverflow.com/questions/55153125/fetch-more-than-100-messages + const allMessages: Message[] = []; + let lastId: string | undefined; + let continueSearch = true; + + while ( continueSearch ) { + const options: ChannelLogsQueryOptions = { limit: 50 }; + if ( lastId ) { + options.before = lastId; + } + const messages = await pendingChannel.messages.fetch( options ); + allMessages.push( ...messages.array() ); + lastId = messages.last()?.id; + if ( messages.size !== 50 || !lastId ) { + continueSearch = false; + } + } + this.logger.info( `Fetched ${ allMessages.length } messages from "${ pendingChannel.name }"` ); + } + } + if ( BotConfig.request.channels ) { for ( let i = 0; i < BotConfig.request.channels.length; i++ ) { const requestChannelId = BotConfig.request.channels[i]; diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts index 27d51e01..da0c9253 100644 --- a/src/commands/VerifyCommand.ts +++ b/src/commands/VerifyCommand.ts @@ -20,7 +20,7 @@ export default class VerifyCommand extends PrefixCommand { let foundUser = false; - const allMessages = await pendingChannel.messages.fetch( { limit: 100 } ); + const allMessages = pendingChannel.messages.cache; allMessages.forEach( async thisMessage => { if ( thisMessage.embeds === undefined ) return undefined; From 9d5bd84e2321e96156475bc3f3c730e79f1f21e3 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Thu, 27 May 2021 22:36:30 -0400 Subject: [PATCH 09/27] Add a whois command Also, fix indentation issues that the ESLint found. --- src/commands/CommandRegistry.ts | 2 + src/commands/WhoisCommand.ts | 93 ++++++++++ .../VerificationMessageEventHandler.ts | 168 +++++++++--------- src/tasks/RemovePendingVerificationTask.ts | 2 +- 4 files changed, 178 insertions(+), 87 deletions(-) create mode 100644 src/commands/WhoisCommand.ts diff --git a/src/commands/CommandRegistry.ts b/src/commands/CommandRegistry.ts index 5c4d2f1a..d14c02c4 100644 --- a/src/commands/CommandRegistry.ts +++ b/src/commands/CommandRegistry.ts @@ -9,6 +9,7 @@ import SearchCommand from './SearchCommand'; import ShutdownCommand from './ShutdownCommand'; import TipsCommand from './TipsCommand'; import VerifyCommand from './VerifyCommand'; +import WhoisCommand from './WhoisCommand'; export default class CommandRegistry { public static BUG_COMMAND = new BugCommand(); @@ -22,4 +23,5 @@ export default class CommandRegistry { public static SHUTDOWN_COMMAND = new ShutdownCommand(); public static TIPS_COMMAND = new TipsCommand(); public static VERIFY_COMMAND = new VerifyCommand(); + public static WHOIS_COMMAND = new WhoisCommand(); } diff --git a/src/commands/WhoisCommand.ts b/src/commands/WhoisCommand.ts new file mode 100644 index 00000000..290a193b --- /dev/null +++ b/src/commands/WhoisCommand.ts @@ -0,0 +1,93 @@ +import { ChannelLogsQueryOptions, Message, MessageEmbed, TextChannel } from 'discord.js'; +import BotConfig from '../BotConfig'; +import DiscordUtil from '../util/DiscordUtil'; +import PrefixCommand from './PrefixCommand'; +import Command from './Command'; + +export default class WhoisCommand extends PrefixCommand { + public readonly aliases = ['who', 'whois']; + + public async run( message: Message, args: string ): Promise { + if ( !args.length ) { + return false; + } + + let fromDiscordWhois = false; + if ( args.startsWith( '<@' ) ) { + fromDiscordWhois = true; + } + + if ( message.deletable ) { + try { + await message.delete(); + } catch ( error ) { + Command.logger.error( error ); + } + } else { + Command.logger.log( 'Message not deletable' ); + } + + + const logChannel = await DiscordUtil.getChannel( BotConfig.verification.verificationLogChannel ); + const allMessages: Message[] = []; + let lastId: string | undefined; + let continueSearch = true; + + try { + while ( continueSearch ) { + const options: ChannelLogsQueryOptions = { limit: 50 }; + if ( lastId ) { + options.before = lastId; + } + if ( logChannel instanceof TextChannel ) { + + const messages = await logChannel.messages.fetch( options ); + allMessages.push( ...messages.array() ); + lastId = messages.last()?.id; + if ( messages.size !== 50 || !lastId ) { + continueSearch = false; + + for ( let i = 0; i < allMessages.length; i++ ) { + const content = allMessages[i].embeds; + if ( content === undefined ) continue; + + const discordId = content[0].fields[0].value; + const discordMember = await DiscordUtil.getMember( logChannel.guild, discordId.replace( /[<>!@]/g, '' ) ); + const mojiraMember = content[0].fields[1].value; + + if ( fromDiscordWhois ) { + if ( discordId.replace( /[<>!@]/g, '' ) != args.replace( /[<>!@]/g, '' ) ) continue; + + const embed = new MessageEmbed() + .setTitle( 'User information' ) + .setDescription( `${ discordMember.user }'s Mojira account is ${ mojiraMember } ` ) + .setFooter( message.author.tag, message.author.avatarURL() ); + await message.channel.send( embed ); + + return true; + } else { + if ( mojiraMember.split( '?name=' )[1].split( ')' )[0] != args ) continue; + + const embed = new MessageEmbed() + .setTitle( 'User information' ) + .setDescription( `${ mojiraMember }'s Discord account is ${ discordId }` ) + .setFooter( message.author.tag, message.author.avatarURL() ); + await message.channel.send( embed ); + + return true; + } + } + await message.channel.send( `${ args } has not been verified!` ); + } + } + } + } catch { + return false; + } + return true; + } + + public asString( args: string ): string { + return `!jira whois ${ args }`; + } +} diff --git a/src/events/verification/VerificationMessageEventHandler.ts b/src/events/verification/VerificationMessageEventHandler.ts index 525e5d2a..d74a0978 100644 --- a/src/events/verification/VerificationMessageEventHandler.ts +++ b/src/events/verification/VerificationMessageEventHandler.ts @@ -11,98 +11,94 @@ export default class VerificationMessageEventHandler implements EventHandler<'me private logger = log4js.getLogger( 'VerificationMessageEventHandler' ); public onEvent = async ( origin: Message ): Promise => { - const args = origin.content.split(/\s(.+)/) - if ( args[0] !== "link" || !args[1] ) { - // This message does not start with 'link' or does not contain a username argument - return; - } + const args = origin.content.split( /\s(.+)/ ); + if ( args[0] !== 'link' || !args[1] ) { + // This message does not start with 'link' or does not contain a username argument + return; + } - const username = args[1]; + const username = args[1]; - try { - const allComments = new Map(); + try { + const allComments = new Map(); const comments = await MojiraBot.jira.issueComments.getComments( { issueIdOrKey: BotConfig.verification.verificationTicket } ); - for ( const comment of comments.comments ) { + for ( const comment of comments.comments ) { allComments.set( comment.author.name, comment.body ); } - const pendingChannel = await DiscordUtil.getChannel( BotConfig.verification.pendingVerificationChannel ); - const logChannel = await DiscordUtil.getChannel( BotConfig.verification.verificationLogChannel ); - - if ( pendingChannel instanceof TextChannel && logChannel instanceof TextChannel ) { - - let foundEmbed = false; - - const allMessages = await pendingChannel.messages.fetch( { limit: 100 } ); - - allMessages.forEach( async message => { - - const embeds = message.embeds; - if ( embeds.length == 0 ) return undefined; - - const userId = embeds[0].fields[0].value.replace(/[<>@!]/g, ""); - if ( userId !== origin.author.id ) return false; - - const enteredComment = allComments.get( username ); - - if ( enteredComment == embeds[0].fields[1].value ) { - - this.logger.info( `Successfully verified user ${ origin.author.tag }` ); - foundEmbed = true; - - if ( message.deletable ) { - try { - await message.delete(); - } catch ( error ) { - this.logger.error( error ); - } - } else { - this.logger.log( 'Failed to delete message' ); - } - - const logEmbed = new MessageEmbed() - .setColor( 'GREEN' ) - .setAuthor( origin.author.tag, origin.author.avatarURL() ) - .addField( 'Discord', origin.author, true ) - .addField( 'Mojira', `[${ username }](https://bugs.mojang.com/secure/ViewProfile.jspa?name=${ username })`, true ); - await logChannel.send( logEmbed ); - - const userEmbed = new MessageEmbed() - .setColor( 'GREEN' ) - .setTitle( 'Your account has been verified!' ) - .setDescription( 'You have successfully linked your Mojira and Discord accounts.' ); - await origin.author.send( userEmbed ); - - try { - const role = await logChannel.guild.roles.fetch( BotConfig.verification.verifiedRole ); - const targetUser = await message.guild.members.fetch( origin.author.id ); - - targetUser.roles.add( role ); - this.logger.info( `Added role ${ BotConfig.verification.verifiedRole } to user ${ origin.author.tag }` ) - } catch ( error ) { - this.logger.error( error ) - } - - } else { - this.logger.info( `Failed to verify user ${ origin.author.tag }: Not a match` ) - try { - await origin.author.send( `Failed to verify your account!` ) - } catch ( error ) { - this.logger.error( error ) - } - return false; - } - } ); - - if ( !foundEmbed ) { - try { - await origin.author.send( 'Failed to verify your account! Did you send `jira verify` first?' ) - } catch ( error ) { - this.logger.error( error ) - } - } - } - } catch ( error ) { + const pendingChannel = await DiscordUtil.getChannel( BotConfig.verification.pendingVerificationChannel ); + const logChannel = await DiscordUtil.getChannel( BotConfig.verification.verificationLogChannel ); + + if ( pendingChannel instanceof TextChannel && logChannel instanceof TextChannel ) { + + let foundEmbed = false; + + const allMessages = await pendingChannel.messages.fetch( { limit: 100 } ); + + allMessages.forEach( async message => { + + const embeds = message.embeds; + if ( embeds.length == 0 ) return undefined; + + const userId = embeds[0].fields[0].value.replace( /[<>@!]/g, '' ); + if ( userId !== origin.author.id ) return false; + + const enteredComment = allComments.get( username ); + + if ( enteredComment == embeds[0].fields[1].value ) { + + this.logger.info( `Successfully verified user ${ origin.author.tag }` ); + foundEmbed = true; + + if ( message.deletable ) { + try { + await message.delete(); + } catch ( error ) { + this.logger.error( error ); + } + } else { + this.logger.log( 'Failed to delete message' ); + } + + const logEmbed = new MessageEmbed() + .setColor( 'GREEN' ) + .setAuthor( origin.author.tag, origin.author.avatarURL() ) + .addField( 'Discord', origin.author, true ) + .addField( 'Mojira', `[${ username }](https://bugs.mojang.com/secure/ViewProfile.jspa?name=${ username })`, true ) + .setTimestamp( new Date ); + await logChannel.send( logEmbed ); + + const userEmbed = new MessageEmbed() + .setColor( 'GREEN' ) + .setTitle( 'Your account has been verified!' ) + .setDescription( 'You have successfully linked your Mojira and Discord accounts.' ); + await origin.author.send( userEmbed ); + + try { + const role = await logChannel.guild.roles.fetch( BotConfig.verification.verifiedRole ); + const targetUser = await message.guild.members.fetch( origin.author.id ); + + await targetUser.roles.add( role ); + this.logger.info( `Added role ${ BotConfig.verification.verifiedRole } to user ${ origin.author.tag }` ); + } catch ( error ) { + this.logger.error( error ); + } + + } else { + this.logger.info( `Failed to verify user ${ origin.author.tag }: Not a match` ); + return false; + } + } ); + + if ( !foundEmbed ) { + try { + await origin.author.send( 'Failed to verify your account! Did you send `jira verify` first?' ); + } catch ( error ) { + this.logger.error( error ); + } + } + } + } catch ( error ) { this.logger.error( error ); } }; diff --git a/src/tasks/RemovePendingVerificationTask.ts b/src/tasks/RemovePendingVerificationTask.ts index cb8465d0..c56d4b4a 100644 --- a/src/tasks/RemovePendingVerificationTask.ts +++ b/src/tasks/RemovePendingVerificationTask.ts @@ -10,7 +10,7 @@ export default class RemovePendingVerificationTask extends MessageTask { // If the message is undefined or has been deleted, don't do anything if ( message === undefined || message.deleted ) return; - const user = DiscordUtil.getMember( message.guild, message.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) ); + const user = await DiscordUtil.getMember( message.guild, message.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) ); if ( message.deletable ) { try { From c386189c90d326433de0f446f27ad40867e6cc9b Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Fri, 28 May 2021 08:11:05 -0400 Subject: [PATCH 10/27] Changes to the whois command Fetching messages at startup, mostly, and improvements to embed creation. --- src/MojiraBot.ts | 24 +++++++++++ src/commands/WhoisCommand.ts | 83 +++++++++++------------------------- 2 files changed, 50 insertions(+), 57 deletions(-) diff --git a/src/MojiraBot.ts b/src/MojiraBot.ts index 2c6da881..703fb381 100644 --- a/src/MojiraBot.ts +++ b/src/MojiraBot.ts @@ -110,6 +110,30 @@ export default class MojiraBot { } } + if ( BotConfig.verification.verificationLogChannel ) { + const verificationLogChannel = await DiscordUtil.getChannel( BotConfig.verification.verificationLogChannel ); + if ( verificationLogChannel instanceof TextChannel ) { + // https://stackoverflow.com/questions/55153125/fetch-more-than-100-messages + const allMessages: Message[] = []; + let lastId: string | undefined; + let continueSearch = true; + + while ( continueSearch ) { + const options: ChannelLogsQueryOptions = { limit: 50 }; + if ( lastId ) { + options.before = lastId; + } + const messages = await verificationLogChannel.messages.fetch( options ); + allMessages.push( ...messages.array() ); + lastId = messages.last()?.id; + if ( messages.size !== 50 || !lastId ) { + continueSearch = false; + } + } + this.logger.info( `Fetched ${ allMessages.length } messages from "${ verificationLogChannel.name }"` ); + } + } + if ( BotConfig.request.channels ) { for ( let i = 0; i < BotConfig.request.channels.length; i++ ) { const requestChannelId = BotConfig.request.channels[i]; diff --git a/src/commands/WhoisCommand.ts b/src/commands/WhoisCommand.ts index 290a193b..3276f7d5 100644 --- a/src/commands/WhoisCommand.ts +++ b/src/commands/WhoisCommand.ts @@ -1,4 +1,4 @@ -import { ChannelLogsQueryOptions, Message, MessageEmbed, TextChannel } from 'discord.js'; +import { Message, MessageEmbed, TextChannel } from 'discord.js'; import BotConfig from '../BotConfig'; import DiscordUtil from '../util/DiscordUtil'; import PrefixCommand from './PrefixCommand'; @@ -17,72 +17,41 @@ export default class WhoisCommand extends PrefixCommand { fromDiscordWhois = true; } - if ( message.deletable ) { - try { - await message.delete(); - } catch ( error ) { - Command.logger.error( error ); - } - } else { - Command.logger.log( 'Message not deletable' ); - } - - const logChannel = await DiscordUtil.getChannel( BotConfig.verification.verificationLogChannel ); - const allMessages: Message[] = []; - let lastId: string | undefined; - let continueSearch = true; - try { - while ( continueSearch ) { - const options: ChannelLogsQueryOptions = { limit: 50 }; - if ( lastId ) { - options.before = lastId; - } - if ( logChannel instanceof TextChannel ) { - - const messages = await logChannel.messages.fetch( options ); - allMessages.push( ...messages.array() ); - lastId = messages.last()?.id; - if ( messages.size !== 50 || !lastId ) { - continueSearch = false; - - for ( let i = 0; i < allMessages.length; i++ ) { - const content = allMessages[i].embeds; - if ( content === undefined ) continue; + if ( logChannel instanceof TextChannel ) { + try { + logChannel.messages.cache.forEach( async thisMessage => { + const content = thisMessage.embeds; + if ( content.length == 0 ) return false; + const discordId = content[0].fields[0].value; + const discordMember = await DiscordUtil.getMember( logChannel.guild, discordId.replace( /[<>!@]/g, '' ) ); + const mojiraMember = content[0].fields[1].value; - const discordId = content[0].fields[0].value; - const discordMember = await DiscordUtil.getMember( logChannel.guild, discordId.replace( /[<>!@]/g, '' ) ); - const mojiraMember = content[0].fields[1].value; + const embed = new MessageEmbed() + .setTitle( 'User information' ); - if ( fromDiscordWhois ) { - if ( discordId.replace( /[<>!@]/g, '' ) != args.replace( /[<>!@]/g, '' ) ) continue; + if ( fromDiscordWhois ) { + if ( discordId.replace( /[<>!@]/g, '' ) != args.replace( /[<>!@]/g, '' ) ) return false; - const embed = new MessageEmbed() - .setTitle( 'User information' ) - .setDescription( `${ discordMember.user }'s Mojira account is ${ mojiraMember } ` ) - .setFooter( message.author.tag, message.author.avatarURL() ); - await message.channel.send( embed ); + embed.setDescription( `${ discordMember.user }'s Mojira account is ${ mojiraMember } ` ) + .setFooter( message.author.tag, message.author.avatarURL() ); + await message.channel.send( embed ); - return true; - } else { - if ( mojiraMember.split( '?name=' )[1].split( ')' )[0] != args ) continue; + return true; + } else { + if ( mojiraMember.split( '?name=' )[1].split( ')' )[0] != args ) return false; - const embed = new MessageEmbed() - .setTitle( 'User information' ) - .setDescription( `${ mojiraMember }'s Discord account is ${ discordId }` ) - .setFooter( message.author.tag, message.author.avatarURL() ); - await message.channel.send( embed ); + embed.setDescription( `${ mojiraMember }'s Discord account is ${ discordId }` ) + .setFooter( message.author.tag, message.author.avatarURL() ); + await message.channel.send( embed ); - return true; - } - } - await message.channel.send( `${ args } has not been verified!` ); + return true; } - } + } ); + } catch ( error ) { + Command.logger.error( error ); } - } catch { - return false; } return true; } From c17ed96ca48599ec33feddf1a4ceb85e440c362c Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Fri, 28 May 2021 12:19:10 -0400 Subject: [PATCH 11/27] Remove asterisks from Jira comment In case the user copy-and-pastes the token from the embed into the comment field under Visual mode, the boldness is copied too. Removing the asterisks fixes this problem. --- .../verification/VerificationMessageEventHandler.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/events/verification/VerificationMessageEventHandler.ts b/src/events/verification/VerificationMessageEventHandler.ts index d74a0978..6586739b 100644 --- a/src/events/verification/VerificationMessageEventHandler.ts +++ b/src/events/verification/VerificationMessageEventHandler.ts @@ -35,24 +35,24 @@ export default class VerificationMessageEventHandler implements EventHandler<'me const allMessages = await pendingChannel.messages.fetch( { limit: 100 } ); - allMessages.forEach( async message => { + allMessages.forEach( async thisMessage => { - const embeds = message.embeds; + const embeds = thisMessage.embeds; if ( embeds.length == 0 ) return undefined; const userId = embeds[0].fields[0].value.replace( /[<>@!]/g, '' ); if ( userId !== origin.author.id ) return false; - const enteredComment = allComments.get( username ); + const enteredComment = allComments.get( username ).replace( /\*/g, '' ); if ( enteredComment == embeds[0].fields[1].value ) { this.logger.info( `Successfully verified user ${ origin.author.tag }` ); foundEmbed = true; - if ( message.deletable ) { + if ( thisMessage.deletable ) { try { - await message.delete(); + await thisMessage.delete(); } catch ( error ) { this.logger.error( error ); } @@ -76,7 +76,7 @@ export default class VerificationMessageEventHandler implements EventHandler<'me try { const role = await logChannel.guild.roles.fetch( BotConfig.verification.verifiedRole ); - const targetUser = await message.guild.members.fetch( origin.author.id ); + const targetUser = await thisMessage.guild.members.fetch( thisMessage.author.id ); await targetUser.roles.add( role ); this.logger.info( `Added role ${ BotConfig.verification.verifiedRole } to user ${ origin.author.tag }` ); From b9caa1b4b6eb8af1ae333aac20a3c76aebb50906 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Fri, 28 May 2021 16:36:26 -0400 Subject: [PATCH 12/27] Add 'DISCORD-VERIFICATION-' to start of token name --- src/commands/VerifyCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts index da0c9253..f1dcaa3a 100644 --- a/src/commands/VerifyCommand.ts +++ b/src/commands/VerifyCommand.ts @@ -51,7 +51,7 @@ export default class VerifyCommand extends PrefixCommand { } try { - const token = this.randomString( 15, '23456789abcdeghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' ); + const token = 'DISCORD-VERIFICATION-' + this.randomString( 15, '23456789abcdeghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' ); const userEmbed = new MessageEmbed() .setDescription( `In order to verify, please comment the following token on the ticket [${ BotConfig.verification.verificationTicket }](https://bugs.mojang.com/browse/${ BotConfig.verification.verificationTicket }) using your Jira account. Make sure you only have added one comment to the ticket!\nAfter you are done, please send \`link \` here and I will verify the account!\n\nToken: **${ token }**` ); From 08541438d1d9c315d76b936ba1b000a8dd3da6c8 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Sat, 29 May 2021 08:58:56 -0400 Subject: [PATCH 13/27] Add support for temporary role removal In case this role ever gives access to extra channels, and the user uses the access incorrectly, it is possible to remove the role from them temporarily (or permanently, if you want it to). Also, this includes a very important fix to adding the Verified role initially. --- src/commands/VerifyCommand.ts | 1 + src/events/message/MessageEventHandler.ts | 6 +-- .../reaction/ReactionAddEventHandler.ts | 5 +++ .../reaction/ReactionRemoveEventHandler.ts | 5 +++ .../RemoveVerificationEventHandler.ts | 37 +++++++++++++++++++ .../ReplaceVerificationEventHandler.ts | 37 +++++++++++++++++++ .../VerificationMessageEventHandler.ts | 5 ++- 7 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 src/events/verification/RemoveVerificationEventHandler.ts create mode 100644 src/events/verification/ReplaceVerificationEventHandler.ts diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts index f1dcaa3a..916aec1f 100644 --- a/src/commands/VerifyCommand.ts +++ b/src/commands/VerifyCommand.ts @@ -65,6 +65,7 @@ export default class VerifyCommand extends PrefixCommand { .setAuthor( message.author.tag, message.author.avatarURL() ) .addField( 'User', message.author, true ) .addField( 'Token', token, true ) + .setFooter( 'Pending verification' ) .setTimestamp( new Date() ); const internalEmbed = await pendingChannel.send( pendingEmbed ); diff --git a/src/events/message/MessageEventHandler.ts b/src/events/message/MessageEventHandler.ts index c3671ed7..715d4b08 100644 --- a/src/events/message/MessageEventHandler.ts +++ b/src/events/message/MessageEventHandler.ts @@ -1,4 +1,4 @@ -import { DMChannel, Message } from 'discord.js'; +import { Message } from 'discord.js'; import BotConfig from '../../BotConfig'; import CommandExecutor from '../../commands/CommandExecutor'; import DiscordUtil from '../../util/DiscordUtil'; @@ -60,10 +60,10 @@ export default class MessageEventHandler implements EventHandler<'message'> { // Don't reply in internal request channels return; - } else if ( message.channel.type === "dm" ) { + } else if ( message.channel.type === 'dm' ) { // This message is a direct message await this.verificationMessageEventHandler.onEvent( message ); - + return; } diff --git a/src/events/reaction/ReactionAddEventHandler.ts b/src/events/reaction/ReactionAddEventHandler.ts index 8451bf4e..f5a1525b 100644 --- a/src/events/reaction/ReactionAddEventHandler.ts +++ b/src/events/reaction/ReactionAddEventHandler.ts @@ -7,6 +7,7 @@ import RequestResolveEventHandler from '../request/RequestResolveEventHandler'; import RequestReactionRemovalEventHandler from '../request/RequestReactionRemovalEventHandler'; import RoleSelectEventHandler from '../roles/RoleSelectEventHandler'; import MentionDeleteEventHandler from '../mention/MentionDeleteEventHandler'; +import RemoveVerificationEventHandler from '../verification/RemoveVerificationEventHandler'; import MojiraBot from '../../MojiraBot'; import DiscordUtil from '../../util/DiscordUtil'; @@ -20,6 +21,7 @@ export default class ReactionAddEventHandler implements DiscordEventHandler<'mes private readonly requestReactionRemovalEventHandler = new RequestReactionRemovalEventHandler(); private readonly requestReopenEventHandler: RequestReopenEventHandler; private readonly mentionDeleteEventHandler = new MentionDeleteEventHandler(); + private readonly removeVerificationEventHandler = new RemoveVerificationEventHandler(); constructor( botUserId: string, internalChannels: Map ) { this.botUserId = botUserId; @@ -56,6 +58,9 @@ export default class ReactionAddEventHandler implements DiscordEventHandler<'mes } else if ( reaction.message.author.id === this.botUserId && reaction.emoji.name === BotConfig.embedDeletionEmoji ) { // Handle deleting bot embed return this.mentionDeleteEventHandler.onEvent( reaction, user ); + } else if ( BotConfig.verification.verificationLogChannel.includes( message.channel.id ) ) { + // Handle removal of Verified role + return this.removeVerificationEventHandler.onEvent( reaction, user ); } }; } \ No newline at end of file diff --git a/src/events/reaction/ReactionRemoveEventHandler.ts b/src/events/reaction/ReactionRemoveEventHandler.ts index a25023d3..86f56deb 100644 --- a/src/events/reaction/ReactionRemoveEventHandler.ts +++ b/src/events/reaction/ReactionRemoveEventHandler.ts @@ -3,6 +3,7 @@ import BotConfig from '../../BotConfig'; import EventHandler from '../EventHandler'; import RequestUnresolveEventHandler from '../request/RequestUnresolveEventHandler'; import RoleRemoveEventHandler from '../roles/RoleRemoveEventHandler'; +import ReplaceVerificationEventHandler from '../verification/ReplaceVerificationEventHandler'; import MojiraBot from '../../MojiraBot'; import DiscordUtil from '../../util/DiscordUtil'; @@ -13,6 +14,7 @@ export default class ReactionRemoveEventHandler implements EventHandler<'message private readonly roleRemoveHandler = new RoleRemoveEventHandler(); private readonly requestUnresolveEventHandler: RequestUnresolveEventHandler; + private readonly replaceVerificationEventHandler = new ReplaceVerificationEventHandler(); constructor( botUserId: string ) { this.botUserId = botUserId; @@ -36,6 +38,9 @@ export default class ReactionRemoveEventHandler implements EventHandler<'message } else if ( BotConfig.request.internalChannels.includes( message.channel.id ) ) { // Handle unresolving user request return this.requestUnresolveEventHandler.onEvent( reaction, user ); + } else if ( BotConfig.verification.verificationLogChannel.includes( message.channel.id ) ) { + // Handle replacing Verified role + return this.replaceVerificationEventHandler.onEvent( reaction, user ); } }; } \ No newline at end of file diff --git a/src/events/verification/RemoveVerificationEventHandler.ts b/src/events/verification/RemoveVerificationEventHandler.ts new file mode 100644 index 00000000..f3844c9c --- /dev/null +++ b/src/events/verification/RemoveVerificationEventHandler.ts @@ -0,0 +1,37 @@ +import { MessageEmbed, MessageReaction, User } from 'discord.js'; +import * as log4js from 'log4js'; +import DiscordUtil from '../../util/DiscordUtil'; +import EventHandler from '../EventHandler'; +import BotConfig from '../../BotConfig'; + +export default class RemoveVerificationEventHandler implements EventHandler<'messageReactionAdd'> { + public readonly eventName = 'messageReactionAdd'; + + private logger = log4js.getLogger( 'RemoveVerificationEventHandler' ); + + public onEvent = async ( reaction: MessageReaction, user: User ): Promise => { + const message = await DiscordUtil.fetchMessage( reaction.message ); + const verifiedRole = await message.guild.roles.fetch( BotConfig.verification.verifiedRole ); + + if ( message.embeds.length == 0 ) return undefined; + + const targetUser = DiscordUtil.getMember( message.guild, message.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) ); + + this.logger.info( `User ${ user.tag } is attempting to remove the role '${ verifiedRole.name }' from user ${ ( await targetUser ).user.tag }` ); + + try { + const embed = new MessageEmbed( message.embeds[0] ) + .setColor( 'RED' ) + .setFooter( 'Unverified' ); + await message.edit( embed ); + } catch ( error ) { + this.logger.error( error ); + } + + try { + await ( await targetUser ).roles.remove( verifiedRole ); + } catch ( error ) { + this.logger.error( error ); + } + }; +} \ No newline at end of file diff --git a/src/events/verification/ReplaceVerificationEventHandler.ts b/src/events/verification/ReplaceVerificationEventHandler.ts new file mode 100644 index 00000000..4da45107 --- /dev/null +++ b/src/events/verification/ReplaceVerificationEventHandler.ts @@ -0,0 +1,37 @@ +import { MessageEmbed, MessageReaction, User } from 'discord.js'; +import * as log4js from 'log4js'; +import DiscordUtil from '../../util/DiscordUtil'; +import EventHandler from '../EventHandler'; +import BotConfig from '../../BotConfig'; + +export default class ReplaceVerificationEventHandler implements EventHandler<'messageReactionRemove'> { + public readonly eventName = 'messageReactionRemove'; + + private logger = log4js.getLogger( 'RemoveVerificationEventHandler' ); + + public onEvent = async ( reaction: MessageReaction, user: User ): Promise => { + const message = await DiscordUtil.fetchMessage( reaction.message ); + const verifiedRole = await message.guild.roles.fetch( BotConfig.verification.verifiedRole ); + + if ( message.embeds.length == 0 ) return undefined; + + const targetUser = DiscordUtil.getMember( message.guild, message.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) ); + + this.logger.info( `User ${ user.tag } is attempting to replace the role '${ verifiedRole.name }' for user ${ ( await targetUser ).user.tag }` ); + + try { + const embed = new MessageEmbed( message.embeds[0] ) + .setColor( 'GREEN' ) + .setFooter( 'Reverified' ); + await message.edit( embed ); + } catch ( error ) { + this.logger.error( error ); + } + + try { + await ( await targetUser ).roles.add( verifiedRole ); + } catch ( error ) { + this.logger.error( error ); + } + }; +} \ No newline at end of file diff --git a/src/events/verification/VerificationMessageEventHandler.ts b/src/events/verification/VerificationMessageEventHandler.ts index 6586739b..92c18dbe 100644 --- a/src/events/verification/VerificationMessageEventHandler.ts +++ b/src/events/verification/VerificationMessageEventHandler.ts @@ -65,6 +65,7 @@ export default class VerificationMessageEventHandler implements EventHandler<'me .setAuthor( origin.author.tag, origin.author.avatarURL() ) .addField( 'Discord', origin.author, true ) .addField( 'Mojira', `[${ username }](https://bugs.mojang.com/secure/ViewProfile.jspa?name=${ username })`, true ) + .setFooter( 'Verified' ) .setTimestamp( new Date ); await logChannel.send( logEmbed ); @@ -76,10 +77,10 @@ export default class VerificationMessageEventHandler implements EventHandler<'me try { const role = await logChannel.guild.roles.fetch( BotConfig.verification.verifiedRole ); - const targetUser = await thisMessage.guild.members.fetch( thisMessage.author.id ); + const targetUser = await thisMessage.guild.members.fetch( origin.author.id ); await targetUser.roles.add( role ); - this.logger.info( `Added role ${ BotConfig.verification.verifiedRole } to user ${ origin.author.tag }` ); + this.logger.info( `Added role '${ role.name }' to user ${ origin.author.tag }` ); } catch ( error ) { this.logger.error( error ); } From 5893eb8a0661f1450cb1d8b6175f9b1cdee5dda2 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Sat, 29 May 2021 11:21:32 -0400 Subject: [PATCH 14/27] Allow for link removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simply react to the verification log embed with ✂, and MojiraBot will delete the message, removing the account link. --- config/main.yml | 4 +- config/template.yml | 8 +++- src/BotConfig.ts | 10 +++-- .../RemoveVerificationEventHandler.ts | 38 ++++++++++++++----- .../ReplaceVerificationEventHandler.ts | 36 ++++++++++++++---- src/tasks/RemoveVerificationTask.ts | 20 ++++++++++ 6 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 src/tasks/RemoveVerificationTask.ts diff --git a/config/main.yml b/config/main.yml index cb4fb34b..e611d4f1 100644 --- a/config/main.yml +++ b/config/main.yml @@ -120,4 +120,6 @@ verification: pendingVerificationChannel: '' # Add a pending channel here! verificationLogChannel: '' # Add a log channel here! verifiedRole: '' # Add a role here! - verificationInvalidationTime: 900000 \ No newline at end of file + verificationInvalidationTime: 900000 + removeLinkEmoji: '✂️' + removeLinkWaitTime: 5000 \ No newline at end of file diff --git a/config/template.yml b/config/template.yml index 3b6799b1..46accc11 100644 --- a/config/template.yml +++ b/config/template.yml @@ -238,4 +238,10 @@ verification: verifiedRole: # The amount of time that a verification request can remain open until it expires, in milliseconds. - verificationInvalidationTime: \ No newline at end of file + verificationInvalidationTime: + + # The emoji which, when used to react to a verification link embed, removes the link. + removeLinkEmoji: + + # The amount of time before a verification link is removed, when the message is reacted to with the remove link emoji. + removeLinkWaitTime: \ No newline at end of file diff --git a/src/BotConfig.ts b/src/BotConfig.ts index 9fa92d1f..ba4f12c2 100644 --- a/src/BotConfig.ts +++ b/src/BotConfig.ts @@ -67,13 +67,17 @@ export class VerificationConfig { public verificationLogChannel: string; public verifiedRole: string; public verificationInvalidationTime: number; + public removeLinkEmoji: string; + public removeLinkWaitTime: number; constructor() { this.verificationTicket = config.get( 'verification.verificationTicket' ); this.pendingVerificationChannel = config.get( 'verification.pendingVerificationChannel' ); - this.verificationLogChannel = config.get( 'verification.verificationLogChannel' ) - this.verifiedRole = config.get( 'verification.verifiedRole' ) - this.verificationInvalidationTime = config.get( 'verification.verificationInvalidationTime' ) + this.verificationLogChannel = config.get( 'verification.verificationLogChannel' ); + this.verifiedRole = config.get( 'verification.verifiedRole' ); + this.verificationInvalidationTime = config.get( 'verification.verificationInvalidationTime' ); + this.removeLinkEmoji = config.get( 'verification.removeLinkEmoji' ); + this.removeLinkWaitTime = config.get( 'verification.removeLinkWaitTime' ); } } diff --git a/src/events/verification/RemoveVerificationEventHandler.ts b/src/events/verification/RemoveVerificationEventHandler.ts index f3844c9c..604bad47 100644 --- a/src/events/verification/RemoveVerificationEventHandler.ts +++ b/src/events/verification/RemoveVerificationEventHandler.ts @@ -3,6 +3,8 @@ import * as log4js from 'log4js'; import DiscordUtil from '../../util/DiscordUtil'; import EventHandler from '../EventHandler'; import BotConfig from '../../BotConfig'; +import TaskScheduler from '../../tasks/TaskScheduler'; +import RemoveVerificationTask from '../../tasks/RemoveVerificationTask'; export default class RemoveVerificationEventHandler implements EventHandler<'messageReactionAdd'> { public readonly eventName = 'messageReactionAdd'; @@ -17,15 +19,33 @@ export default class RemoveVerificationEventHandler implements EventHandler<'mes const targetUser = DiscordUtil.getMember( message.guild, message.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) ); - this.logger.info( `User ${ user.tag } is attempting to remove the role '${ verifiedRole.name }' from user ${ ( await targetUser ).user.tag }` ); - - try { - const embed = new MessageEmbed( message.embeds[0] ) - .setColor( 'RED' ) - .setFooter( 'Unverified' ); - await message.edit( embed ); - } catch ( error ) { - this.logger.error( error ); + if ( reaction.emoji.name !== BotConfig.verification.removeLinkEmoji ) { + this.logger.info( `User ${ user.tag } is attempting to remove the role '${ verifiedRole.name }' from user ${ ( await targetUser ).user.tag }` ); + + try { + const embed = new MessageEmbed( message.embeds[0] ) + .setColor( 'RED' ) + .setFooter( 'Unverified' ); + await message.edit( embed ); + } catch ( error ) { + this.logger.error( error ); + } + } else { + this.logger.info( `User ${ user.tag } is removing the verification link from the Discord account ${ ( await targetUser ).user.tag }` ); + try { + const embed = new MessageEmbed( message.embeds[0] ) + .setColor( 'DARK_RED' ) + .setFooter( 'Removing link' ); + await message.edit( embed ); + + TaskScheduler.addOneTimeMessageTask( + reaction.message, + new RemoveVerificationTask(), + BotConfig.verification.removeLinkWaitTime + ); + } catch ( error ) { + this.logger.error( error ); + } } try { diff --git a/src/events/verification/ReplaceVerificationEventHandler.ts b/src/events/verification/ReplaceVerificationEventHandler.ts index 4da45107..4e4ae668 100644 --- a/src/events/verification/ReplaceVerificationEventHandler.ts +++ b/src/events/verification/ReplaceVerificationEventHandler.ts @@ -3,6 +3,7 @@ import * as log4js from 'log4js'; import DiscordUtil from '../../util/DiscordUtil'; import EventHandler from '../EventHandler'; import BotConfig from '../../BotConfig'; +import TaskScheduler from '../../tasks/TaskScheduler'; export default class ReplaceVerificationEventHandler implements EventHandler<'messageReactionRemove'> { public readonly eventName = 'messageReactionRemove'; @@ -17,15 +18,34 @@ export default class ReplaceVerificationEventHandler implements EventHandler<'me const targetUser = DiscordUtil.getMember( message.guild, message.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) ); - this.logger.info( `User ${ user.tag } is attempting to replace the role '${ verifiedRole.name }' for user ${ ( await targetUser ).user.tag }` ); + if ( reaction.emoji.name !== BotConfig.verification.removeLinkEmoji ) { - try { - const embed = new MessageEmbed( message.embeds[0] ) - .setColor( 'GREEN' ) - .setFooter( 'Reverified' ); - await message.edit( embed ); - } catch ( error ) { - this.logger.error( error ); + this.logger.info( `User ${ user.tag } is replacing the role '${ verifiedRole.name }' for user ${ ( await targetUser ).user.tag }` ); + + try { + const embed = new MessageEmbed( message.embeds[0] ) + .setColor( 'GREEN' ) + .setFooter( 'Reverified' ); + await message.edit( embed ); + } catch ( error ) { + this.logger.error( error ); + } + + } else { + + this.logger.info( `User ${ user.tag } is cancelling the removal of the linked account of user ${ ( await targetUser ).user.tag }` ); + + try { + const embed = new MessageEmbed( message.embeds[0] ) + .setColor( 'GREEN' ) + .setFooter( 'Link removal cancelled' ); + await message.edit( embed ); + + this.logger.info( `Cleared message task for request message '${ message.id }'` ); + TaskScheduler.clearMessageTasks( message ); + } catch ( error ) { + this.logger.error( error ); + } } try { diff --git a/src/tasks/RemoveVerificationTask.ts b/src/tasks/RemoveVerificationTask.ts new file mode 100644 index 00000000..7fb775c1 --- /dev/null +++ b/src/tasks/RemoveVerificationTask.ts @@ -0,0 +1,20 @@ +import { Message } from 'discord.js'; +import MessageTask from './MessageTask'; +import * as log4js from 'log4js'; + +export default class RemoveVerificationTask extends MessageTask { + private static logger = log4js.getLogger( 'RemoveVerificationTask' ); + + public async run( message: Message ): Promise { + if ( message === undefined || message.deleted ) return; + + if ( message.deletable ) { + try { + await message.delete(); + RemoveVerificationTask.logger.info( `Removed verification link '${ message.id }'` ); + } catch ( error ) { + RemoveVerificationTask.logger.error( error ); + } + } + } +} \ No newline at end of file From da5316306d25653429e551eb4563329737fdfcdd Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Sun, 30 May 2021 16:11:15 -0400 Subject: [PATCH 15/27] Switch verification message handler to cache Previously, VerificationMessageEventHandler read directly from the channel. Now, it reads from the cache, saving time and resources. --- src/events/verification/VerificationMessageEventHandler.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/events/verification/VerificationMessageEventHandler.ts b/src/events/verification/VerificationMessageEventHandler.ts index 92c18dbe..7f5d41d3 100644 --- a/src/events/verification/VerificationMessageEventHandler.ts +++ b/src/events/verification/VerificationMessageEventHandler.ts @@ -4,6 +4,7 @@ import BotConfig from '../../BotConfig'; import EventHandler from '../EventHandler'; import MojiraBot from '../../MojiraBot'; import DiscordUtil from '../../util/DiscordUtil'; +import TaskScheduler from '../../tasks/TaskScheduler'; export default class VerificationMessageEventHandler implements EventHandler<'message'> { public readonly eventName = 'message'; @@ -33,7 +34,7 @@ export default class VerificationMessageEventHandler implements EventHandler<'me let foundEmbed = false; - const allMessages = await pendingChannel.messages.fetch( { limit: 100 } ); + const allMessages = pendingChannel.messages.cache; allMessages.forEach( async thisMessage => { @@ -50,6 +51,8 @@ export default class VerificationMessageEventHandler implements EventHandler<'me this.logger.info( `Successfully verified user ${ origin.author.tag }` ); foundEmbed = true; + TaskScheduler.clearMessageTasks( thisMessage ); + if ( thisMessage.deletable ) { try { await thisMessage.delete(); From 60f9c2b4ce1f70c7308d10995c5c51657635ce82 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Sun, 30 May 2021 16:14:09 -0400 Subject: [PATCH 16/27] Schedule deletion of pending verification on startup --- src/MojiraBot.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/MojiraBot.ts b/src/MojiraBot.ts index 703fb381..a6ffc5a2 100644 --- a/src/MojiraBot.ts +++ b/src/MojiraBot.ts @@ -14,6 +14,7 @@ import RequestResolveEventHandler from './events/request/RequestResolveEventHand import FilterFeedTask from './tasks/FilterFeedTask'; import TaskScheduler from './tasks/TaskScheduler'; import VersionFeedTask from './tasks/VersionFeedTask'; +import RemovePendingVerificationTask from './tasks/RemovePendingVerificationTask'; import DiscordUtil from './util/DiscordUtil'; import { RoleSelectionUtil } from './util/RoleSelectionUtil'; @@ -107,6 +108,23 @@ export default class MojiraBot { } } this.logger.info( `Fetched ${ allMessages.length } messages from "${ pendingChannel.name }"` ); + + // Schedule invalidation of pending verification requests + const handler = new RemovePendingVerificationTask(); + for ( const message of allMessages ) { + const expiration = BotConfig.verification.verificationInvalidationTime - ( Date.now() - message.createdTimestamp ); + if ( expiration > 0 ) { + this.logger.info( `Scheduling deletion of message ${ message.id } in ${ expiration } ms` ); + TaskScheduler.addOneTimeMessageTask( + message, + handler, + expiration + ); + } else { + this.logger.info( `Deleted pending verification request ${ message.id } (scheduled to be deleted ${ -expiration } ms ago)` ); + await message.delete(); + } + } } } From dffc9dbb58ad7c6ba878bdc8a278ad944fc81a86 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Mon, 31 May 2021 20:30:57 -0400 Subject: [PATCH 17/27] Improve checking for verification Previously, a user who had their verification temporarily removed could run !jira verify again to create a second log message, potentially causing problems. Now, running !jira verify also checks the log channel for a verification, preventing this. --- src/commands/VerifyCommand.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts index 916aec1f..f28eb25d 100644 --- a/src/commands/VerifyCommand.ts +++ b/src/commands/VerifyCommand.ts @@ -15,12 +15,15 @@ export default class VerifyCommand extends PrefixCommand { } const pendingChannel = await DiscordUtil.getChannel( BotConfig.verification.pendingVerificationChannel ); + const logChannel = await DiscordUtil.getChannel( BotConfig.verification.verificationLogChannel ); - if ( pendingChannel instanceof TextChannel ) { + if ( pendingChannel instanceof TextChannel && logChannel instanceof TextChannel ) { let foundUser = false; + let isVerified = false; const allMessages = pendingChannel.messages.cache; + const verifications = logChannel.messages.cache; allMessages.forEach( async thisMessage => { if ( thisMessage.embeds === undefined ) return undefined; @@ -29,12 +32,19 @@ export default class VerifyCommand extends PrefixCommand { } } ); + verifications.forEach( async thisMessage => { + if ( thisMessage.embeds === undefined ) return undefined; + if ( thisMessage.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) == message.author.id ) { + isVerified = true; + } + } ); + try { const role = await pendingChannel.guild.roles.fetch( BotConfig.verification.verifiedRole ); const targetUser = await message.guild.members.fetch( message.author.id ); - if ( targetUser.roles.cache.has( role.id ) ) { - await message.channel.send( `${ message.author }, your account has already been verified!` ); + if ( targetUser.roles.cache.has( role.id ) || isVerified ) { + await message.channel.send( `${ message.author }, you have already linked your accounts!` ); await message.react( '❌' ); return false; } From caa9c94f47b605903621670d9ba7fe3b20445750 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Mon, 31 May 2021 21:52:53 -0400 Subject: [PATCH 18/27] Update loop type in whois command Changed from forEach loop to for loop, to save time. --- src/commands/WhoisCommand.ts | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/commands/WhoisCommand.ts b/src/commands/WhoisCommand.ts index 3276f7d5..ecd0e7d1 100644 --- a/src/commands/WhoisCommand.ts +++ b/src/commands/WhoisCommand.ts @@ -7,11 +7,13 @@ import Command from './Command'; export default class WhoisCommand extends PrefixCommand { public readonly aliases = ['who', 'whois']; - public async run( message: Message, args: string ): Promise { + public async run( origin: Message, args: string ): Promise { if ( !args.length ) { return false; } + let foundEmbed = false; + let fromDiscordWhois = false; if ( args.startsWith( '<@' ) ) { fromDiscordWhois = true; @@ -20,10 +22,13 @@ export default class WhoisCommand extends PrefixCommand { const logChannel = await DiscordUtil.getChannel( BotConfig.verification.verificationLogChannel ); if ( logChannel instanceof TextChannel ) { + const cachedMessages = logChannel.messages.cache; + let loop = 0; try { - logChannel.messages.cache.forEach( async thisMessage => { + for ( loop = 0; loop < cachedMessages.size; loop++ ) { + const thisMessage = cachedMessages[loop]; const content = thisMessage.embeds; - if ( content.length == 0 ) return false; + if ( content.length == 0 ) continue; const discordId = content[0].fields[0].value; const discordMember = await DiscordUtil.getMember( logChannel.guild, discordId.replace( /[<>!@]/g, '' ) ); const mojiraMember = content[0].fields[1].value; @@ -32,23 +37,25 @@ export default class WhoisCommand extends PrefixCommand { .setTitle( 'User information' ); if ( fromDiscordWhois ) { - if ( discordId.replace( /[<>!@]/g, '' ) != args.replace( /[<>!@]/g, '' ) ) return false; + if ( discordId.replace( /[<>!@]/g, '' ) != args.replace( /[<>!@]/g, '' ) ) continue; + foundEmbed = true; embed.setDescription( `${ discordMember.user }'s Mojira account is ${ mojiraMember } ` ) - .setFooter( message.author.tag, message.author.avatarURL() ); - await message.channel.send( embed ); + .setFooter( origin.author.tag, origin.author.avatarURL() ); + await origin.channel.send( embed ); - return true; + return; } else { - if ( mojiraMember.split( '?name=' )[1].split( ')' )[0] != args ) return false; + if ( mojiraMember.split( '?name=' )[1].split( ')' )[0] != args ) continue; + foundEmbed = true; embed.setDescription( `${ mojiraMember }'s Discord account is ${ discordId }` ) - .setFooter( message.author.tag, message.author.avatarURL() ); - await message.channel.send( embed ); + .setFooter( origin.author.tag, origin.author.avatarURL() ); + await origin.channel.send( embed ); - return true; + return; } - } ); + } } catch ( error ) { Command.logger.error( error ); } From c23eb48ac7e9ad3cbbaabe50649d9edc70f05b88 Mon Sep 17 00:00:00 2001 From: chandler05 <66492208+chandler05@users.noreply.github.com> Date: Mon, 31 May 2021 23:02:18 -0500 Subject: [PATCH 19/27] Fix loop and send message on whois failure --- src/commands/WhoisCommand.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/commands/WhoisCommand.ts b/src/commands/WhoisCommand.ts index ecd0e7d1..f84fb054 100644 --- a/src/commands/WhoisCommand.ts +++ b/src/commands/WhoisCommand.ts @@ -23,10 +23,9 @@ export default class WhoisCommand extends PrefixCommand { if ( logChannel instanceof TextChannel ) { const cachedMessages = logChannel.messages.cache; - let loop = 0; try { - for ( loop = 0; loop < cachedMessages.size; loop++ ) { - const thisMessage = cachedMessages[loop]; + for ( const loop of cachedMessages ) { + const thisMessage = loop[1]; const content = thisMessage.embeds; if ( content.length == 0 ) continue; const discordId = content[0].fields[0].value; @@ -43,8 +42,6 @@ export default class WhoisCommand extends PrefixCommand { embed.setDescription( `${ discordMember.user }'s Mojira account is ${ mojiraMember } ` ) .setFooter( origin.author.tag, origin.author.avatarURL() ); await origin.channel.send( embed ); - - return; } else { if ( mojiraMember.split( '?name=' )[1].split( ')' )[0] != args ) continue; foundEmbed = true; @@ -52,10 +49,18 @@ export default class WhoisCommand extends PrefixCommand { embed.setDescription( `${ mojiraMember }'s Discord account is ${ discordId }` ) .setFooter( origin.author.tag, origin.author.avatarURL() ); await origin.channel.send( embed ); - - return; } } + + if ( !foundEmbed ) { + const embed = new MessageEmbed() + .setTitle( 'User information' ) + .setDescription( `User ${ args } not found` ); + await origin.channel.send( embed ); + return false; + } + + return true; } catch ( error ) { Command.logger.error( error ); } From fb36b022002c2d0d097c9ffc188c5d44363b1e92 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Tue, 1 Jun 2021 00:09:16 -0400 Subject: [PATCH 20/27] Add footer to 'user not found' embed Allows for deletion. --- src/commands/WhoisCommand.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commands/WhoisCommand.ts b/src/commands/WhoisCommand.ts index f84fb054..3fd73741 100644 --- a/src/commands/WhoisCommand.ts +++ b/src/commands/WhoisCommand.ts @@ -55,7 +55,8 @@ export default class WhoisCommand extends PrefixCommand { if ( !foundEmbed ) { const embed = new MessageEmbed() .setTitle( 'User information' ) - .setDescription( `User ${ args } not found` ); + .setDescription( `User ${ args } not found` ) + .setFooter( origin.author.tag, origin.author.avatarURL() ); await origin.channel.send( embed ); return false; } From 60667ba0516b9d5b4a34e49cfba4c20e43a877d1 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Tue, 1 Jun 2021 00:45:13 -0400 Subject: [PATCH 21/27] Add accounts to verification confirmation message It's nice to know that the link is correct. --- src/events/verification/VerificationMessageEventHandler.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/events/verification/VerificationMessageEventHandler.ts b/src/events/verification/VerificationMessageEventHandler.ts index 7f5d41d3..4b1faf2e 100644 --- a/src/events/verification/VerificationMessageEventHandler.ts +++ b/src/events/verification/VerificationMessageEventHandler.ts @@ -75,13 +75,14 @@ export default class VerificationMessageEventHandler implements EventHandler<'me const userEmbed = new MessageEmbed() .setColor( 'GREEN' ) .setTitle( 'Your account has been verified!' ) - .setDescription( 'You have successfully linked your Mojira and Discord accounts.' ); + .setDescription( 'You have successfully linked your Mojira and Discord accounts.' ) + .addField( 'Discord', origin.author, true ) + .addField( 'Mojira', `[${ username }](https://bugs.mojang.com/secure/ViewProfile.jspa?name=${ username })`, true ); await origin.author.send( userEmbed ); try { const role = await logChannel.guild.roles.fetch( BotConfig.verification.verifiedRole ); const targetUser = await thisMessage.guild.members.fetch( origin.author.id ); - await targetUser.roles.add( role ); this.logger.info( `Added role '${ role.name }' to user ${ origin.author.tag }` ); } catch ( error ) { @@ -96,7 +97,7 @@ export default class VerificationMessageEventHandler implements EventHandler<'me if ( !foundEmbed ) { try { - await origin.author.send( 'Failed to verify your account! Did you send `jira verify` first?' ); + await origin.author.send( 'Failed to verify your account! Did you send `jira verify` first? Did you type your username correctly? (It\'s case-sensitive!)' ); } catch ( error ) { this.logger.error( error ); } From 7500a9393d3630f8eea749c9ee5afcfd7921a60a Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Sun, 6 Jun 2021 14:14:06 -0400 Subject: [PATCH 22/27] Check for a Discord account linked to Mojira account --- .../VerificationMessageEventHandler.ts | 112 +++++++++++------- 1 file changed, 66 insertions(+), 46 deletions(-) diff --git a/src/events/verification/VerificationMessageEventHandler.ts b/src/events/verification/VerificationMessageEventHandler.ts index 4b1faf2e..d99c1d46 100644 --- a/src/events/verification/VerificationMessageEventHandler.ts +++ b/src/events/verification/VerificationMessageEventHandler.ts @@ -33,69 +33,89 @@ export default class VerificationMessageEventHandler implements EventHandler<'me if ( pendingChannel instanceof TextChannel && logChannel instanceof TextChannel ) { let foundEmbed = false; + let foundUser = false; const allMessages = pendingChannel.messages.cache; + const logMessages = logChannel.messages.cache; - allMessages.forEach( async thisMessage => { - + for ( const loop of logMessages ) { + const thisMessage = loop[1]; const embeds = thisMessage.embeds; - if ( embeds.length == 0 ) return undefined; + if ( embeds.length == 0 ) continue; + + const jiraAccount = embeds[0].fields[1].value.split( '?name=' )[1].split( ')' )[0]; + if ( jiraAccount == username ) { + await origin.author.send( 'There is already a Discord account linked to this Mojira account!' ); + foundUser = true; + } + } + if ( !foundUser ) { + allMessages.forEach( async thisMessage => { + + const embeds = thisMessage.embeds; + if ( embeds.length == 0 ) return undefined; + + const userId = embeds[0].fields[0].value.replace( /[<>@!]/g, '' ); + if ( userId !== origin.author.id ) return false; + + let enteredComment = allComments.get( username ); + if ( enteredComment !== undefined ) { + enteredComment = enteredComment.replace( /[*\s]/g, '' ); + } else { + return false; + } - const userId = embeds[0].fields[0].value.replace( /[<>@!]/g, '' ); - if ( userId !== origin.author.id ) return false; + if ( enteredComment == embeds[0].fields[1].value ) { - const enteredComment = allComments.get( username ).replace( /\*/g, '' ); + this.logger.info( `Successfully verified user ${ origin.author.tag }` ); + foundEmbed = true; - if ( enteredComment == embeds[0].fields[1].value ) { + TaskScheduler.clearMessageTasks( thisMessage ); - this.logger.info( `Successfully verified user ${ origin.author.tag }` ); - foundEmbed = true; + if ( thisMessage.deletable ) { + try { + await thisMessage.delete(); + } catch ( error ) { + this.logger.error( error ); + } + } else { + this.logger.log( 'Failed to delete message' ); + } - TaskScheduler.clearMessageTasks( thisMessage ); + const logEmbed = new MessageEmbed() + .setColor( 'GREEN' ) + .setAuthor( origin.author.tag, origin.author.avatarURL() ) + .addField( 'Discord', origin.author, true ) + .addField( 'Mojira', `[${ username }](https://bugs.mojang.com/secure/ViewProfile.jspa?name=${ username })`, true ) + .setFooter( 'Verified' ) + .setTimestamp( new Date ); + await logChannel.send( logEmbed ); + + const userEmbed = new MessageEmbed() + .setColor( 'GREEN' ) + .setTitle( 'Your account has been verified!' ) + .setDescription( 'You have successfully linked your Mojira and Discord accounts.' ) + .addField( 'Discord', origin.author, true ) + .addField( 'Mojira', `[${ username }](https://bugs.mojang.com/secure/ViewProfile.jspa?name=${ username })`, true ); + await origin.author.send( userEmbed ); - if ( thisMessage.deletable ) { try { - await thisMessage.delete(); + const role = await logChannel.guild.roles.fetch( BotConfig.verification.verifiedRole ); + const targetUser = await thisMessage.guild.members.fetch( origin.author.id ); + await targetUser.roles.add( role ); + this.logger.info( `Added role '${ role.name }' to user ${ origin.author.tag }` ); } catch ( error ) { this.logger.error( error ); } - } else { - this.logger.log( 'Failed to delete message' ); - } - const logEmbed = new MessageEmbed() - .setColor( 'GREEN' ) - .setAuthor( origin.author.tag, origin.author.avatarURL() ) - .addField( 'Discord', origin.author, true ) - .addField( 'Mojira', `[${ username }](https://bugs.mojang.com/secure/ViewProfile.jspa?name=${ username })`, true ) - .setFooter( 'Verified' ) - .setTimestamp( new Date ); - await logChannel.send( logEmbed ); - - const userEmbed = new MessageEmbed() - .setColor( 'GREEN' ) - .setTitle( 'Your account has been verified!' ) - .setDescription( 'You have successfully linked your Mojira and Discord accounts.' ) - .addField( 'Discord', origin.author, true ) - .addField( 'Mojira', `[${ username }](https://bugs.mojang.com/secure/ViewProfile.jspa?name=${ username })`, true ); - await origin.author.send( userEmbed ); - - try { - const role = await logChannel.guild.roles.fetch( BotConfig.verification.verifiedRole ); - const targetUser = await thisMessage.guild.members.fetch( origin.author.id ); - await targetUser.roles.add( role ); - this.logger.info( `Added role '${ role.name }' to user ${ origin.author.tag }` ); - } catch ( error ) { - this.logger.error( error ); + } else { + this.logger.info( `Failed to verify user ${ origin.author.tag }: Not a match` ); + return false; } + } ); + } - } else { - this.logger.info( `Failed to verify user ${ origin.author.tag }: Not a match` ); - return false; - } - } ); - - if ( !foundEmbed ) { + if ( !foundUser && !foundEmbed ) { try { await origin.author.send( 'Failed to verify your account! Did you send `jira verify` first? Did you type your username correctly? (It\'s case-sensitive!)' ); } catch ( error ) { From c77ffbf5b54211ba8f42bb97dfe9eaf4e2ad5d14 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Thu, 10 Jun 2021 11:00:43 -0400 Subject: [PATCH 23/27] Update help command commands Added help for the verify command into the help command's commands. --- src/commands/HelpCommand.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commands/HelpCommand.ts b/src/commands/HelpCommand.ts index daed20ab..cc5afd63 100644 --- a/src/commands/HelpCommand.ts +++ b/src/commands/HelpCommand.ts @@ -25,7 +25,9 @@ export default class HelpCommand extends PrefixCommand { \`!jira search \` - Searches for text and returns the results from the bug tracker. - \`!jira tips\` - Sends helpful info on how to use the bug tracker and this Discord server.` + \`!jira tips\` - Sends helpful info on how to use the bug tracker and this Discord server. + + \`!jira verify\` - Begins a process to link your Discord account with your Mojira account.` ) .setFooter( message.author.tag, message.author.avatarURL() ); await message.channel.send( embed ); From f6c9a6803903cef2786c7ac2f041589adf5d5886 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Thu, 10 Jun 2021 21:40:54 -0400 Subject: [PATCH 24/27] Set request footer to Mojira username --- src/events/request/RequestEventHandler.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/events/request/RequestEventHandler.ts b/src/events/request/RequestEventHandler.ts index 5bcd37f6..a3f27b53 100644 --- a/src/events/request/RequestEventHandler.ts +++ b/src/events/request/RequestEventHandler.ts @@ -101,6 +101,26 @@ export default class RequestEventHandler implements EventHandler<'message'> { ? RequestsUtil.getResponseMessage( origin ) : ''; + const thisUser = await DiscordUtil.getMember( origin.guild, origin.author.id ); + if ( thisUser.roles.cache.has( BotConfig.verification.verifiedRole ) ) { + const verificationLog = await DiscordUtil.getChannel( BotConfig.verification.verificationLogChannel ); + if ( !( verificationLog instanceof TextChannel ) ) return; + const verificationMessages = verificationLog.messages.cache; + + for ( const loop of verificationMessages ) { + const message = loop[1]; + + if ( message.embeds.length == 0 ) continue; + + const discordMemberId = message.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ); + if ( discordMemberId !== origin.author.id ) continue; + + const mojiraMember = message.embeds[0].fields[1].value.split( '?name=' )[1].split( ')' )[0]; + embed.setFooter( mojiraMember ); + break; + } + } + const copy = await internalChannel.send( response, embed ) as Message; if ( BotConfig.request.suggestedEmoji ) { From 9deebe6057b076a7d35f504734a56e50c9138861 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Fri, 11 Jun 2021 22:36:44 -0400 Subject: [PATCH 25/27] Switch Mojira username to a new embed field --- src/commands/VerifyCommand.ts | 4 ++-- src/events/request/RequestEventHandler.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts index f28eb25d..7297a645 100644 --- a/src/commands/VerifyCommand.ts +++ b/src/commands/VerifyCommand.ts @@ -26,14 +26,14 @@ export default class VerifyCommand extends PrefixCommand { const verifications = logChannel.messages.cache; allMessages.forEach( async thisMessage => { - if ( thisMessage.embeds === undefined ) return undefined; + if ( thisMessage.embeds.length == 0 ) return undefined; if ( thisMessage.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) == message.author.id ) { foundUser = true; } } ); verifications.forEach( async thisMessage => { - if ( thisMessage.embeds === undefined ) return undefined; + if ( thisMessage.embeds.length == 0 ) return undefined; if ( thisMessage.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) == message.author.id ) { isVerified = true; } diff --git a/src/events/request/RequestEventHandler.ts b/src/events/request/RequestEventHandler.ts index a3f27b53..5701167b 100644 --- a/src/events/request/RequestEventHandler.ts +++ b/src/events/request/RequestEventHandler.ts @@ -116,7 +116,7 @@ export default class RequestEventHandler implements EventHandler<'message'> { if ( discordMemberId !== origin.author.id ) continue; const mojiraMember = message.embeds[0].fields[1].value.split( '?name=' )[1].split( ')' )[0]; - embed.setFooter( mojiraMember ); + embed.addField( 'Mojira', `[${ mojiraMember }](https://bugs.mojang.com/secure/ViewProfile.jspa?name=${ mojiraMember })` ); break; } } From 44cd202203a0f4efd97d70900f8c0727473fc317 Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Fri, 11 Jun 2021 23:02:41 -0400 Subject: [PATCH 26/27] Replace forEach loops with for of loops --- src/commands/VerifyCommand.ts | 16 ++++++++++------ .../VerificationMessageEventHandler.ts | 14 ++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts index 7297a645..9a3bf14c 100644 --- a/src/commands/VerifyCommand.ts +++ b/src/commands/VerifyCommand.ts @@ -25,19 +25,23 @@ export default class VerifyCommand extends PrefixCommand { const allMessages = pendingChannel.messages.cache; const verifications = logChannel.messages.cache; - allMessages.forEach( async thisMessage => { - if ( thisMessage.embeds.length == 0 ) return undefined; + for ( const loop of allMessages ) { + const thisMessage = loop[1]; + if ( thisMessage.embeds.length == 0 ) continue; if ( thisMessage.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) == message.author.id ) { foundUser = true; + break; } - } ); + } - verifications.forEach( async thisMessage => { - if ( thisMessage.embeds.length == 0 ) return undefined; + for ( const loop of verifications ) { + const thisMessage = loop[1]; + if ( thisMessage.embeds.length == 0 ) continue; if ( thisMessage.embeds[0].fields[0].value.replace( /[<>@!]/g, '' ) == message.author.id ) { isVerified = true; + break; } - } ); + } try { const role = await pendingChannel.guild.roles.fetch( BotConfig.verification.verifiedRole ); diff --git a/src/events/verification/VerificationMessageEventHandler.ts b/src/events/verification/VerificationMessageEventHandler.ts index d99c1d46..8f7c6afb 100644 --- a/src/events/verification/VerificationMessageEventHandler.ts +++ b/src/events/verification/VerificationMessageEventHandler.ts @@ -50,19 +50,20 @@ export default class VerificationMessageEventHandler implements EventHandler<'me } } if ( !foundUser ) { - allMessages.forEach( async thisMessage => { + for ( const loop of allMessages ) { + const thisMessage = loop[1]; const embeds = thisMessage.embeds; - if ( embeds.length == 0 ) return undefined; + if ( embeds.length == 0 ) continue; const userId = embeds[0].fields[0].value.replace( /[<>@!]/g, '' ); - if ( userId !== origin.author.id ) return false; + if ( userId !== origin.author.id ) continue; let enteredComment = allComments.get( username ); if ( enteredComment !== undefined ) { enteredComment = enteredComment.replace( /[*\s]/g, '' ); } else { - return false; + continue; } if ( enteredComment == embeds[0].fields[1].value ) { @@ -107,12 +108,13 @@ export default class VerificationMessageEventHandler implements EventHandler<'me } catch ( error ) { this.logger.error( error ); } + break; } else { this.logger.info( `Failed to verify user ${ origin.author.tag }: Not a match` ); - return false; + continue; } - } ); + } } if ( !foundUser && !foundEmbed ) { From a5f859177122109075bcfa862946c535d5ac9c0c Mon Sep 17 00:00:00 2001 From: dericksonmark Date: Wed, 16 Jun 2021 12:10:13 -0400 Subject: [PATCH 27/27] Adjust token generation --- src/commands/VerifyCommand.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commands/VerifyCommand.ts b/src/commands/VerifyCommand.ts index 9a3bf14c..96d27e4d 100644 --- a/src/commands/VerifyCommand.ts +++ b/src/commands/VerifyCommand.ts @@ -65,7 +65,8 @@ export default class VerifyCommand extends PrefixCommand { } try { - const token = 'DISCORD-VERIFICATION-' + this.randomString( 15, '23456789abcdeghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' ); + const token = 'DISCORD-VERIFICATION-' + this.randomString( 15, '23456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' ); + // Omitted the following characters due to possible confusion: 0, 1, l, o, I, O const userEmbed = new MessageEmbed() .setDescription( `In order to verify, please comment the following token on the ticket [${ BotConfig.verification.verificationTicket }](https://bugs.mojang.com/browse/${ BotConfig.verification.verificationTicket }) using your Jira account. Make sure you only have added one comment to the ticket!\nAfter you are done, please send \`link \` here and I will verify the account!\n\nToken: **${ token }**` );