Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable bulk actions by Jira filter #140

Draft
wants to merge 34 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3f764b4
add bulk emoji
chandler05 Dec 4, 2020
a0ae9b7
Add bulk command
chandler05 Dec 4, 2020
544d4a8
Formatting and messages
chandler05 Dec 4, 2020
6c0d3a0
Semicolon
chandler05 Dec 4, 2020
635731f
Await
chandler05 Dec 4, 2020
2dfc80d
Error
chandler05 Dec 4, 2020
9b36413
values
chandler05 Dec 5, 2020
6e6eeff
Update BulkCommand.ts
chandler05 Dec 5, 2020
37870ff
Change to arrays
chandler05 Dec 5, 2020
74580c3
spaces
chandler05 Dec 5, 2020
4e6bd21
Update BulkCommand.ts
chandler05 Dec 5, 2020
68ff39d
Merge branch 'master' into feature/bulk-actions
chandler05 Dec 5, 2020
9d04629
Merge branch 'master' into feature/bulk-actions
chandler05 Dec 6, 2020
63e0168
Rework system to work better
chandler05 Dec 6, 2020
da2fb91
Catch promise
chandler05 Dec 6, 2020
367a984
A-WAIT
chandler05 Dec 6, 2020
208f71e
Allow resolving through !jira bulk
chandler05 Dec 6, 2020
69a9820
Update src/events/request/RequestBulkRemoveEventHandler.ts
chandler05 Dec 7, 2020
9c7d8c3
Update src/commands/BulkCommand.ts
chandler05 Dec 7, 2020
27af05f
Fixes batch
chandler05 Dec 7, 2020
fc3b18c
merge branch
chandler05 Dec 7, 2020
93ef5d1
Add EmojiUtil
chandler05 Dec 7, 2020
bb6612f
Initialize properly, add deletion of messages, add to requests util
chandler05 Dec 7, 2020
a2cf642
Let to const
chandler05 Dec 7, 2020
acf0a4c
Remove bulk event handlers
chandler05 Dec 7, 2020
fed0a2b
Merge branch 'master' into feature/bulk-actions
chandler05 Dec 7, 2020
021c83c
Fix poll command not working
SPGoding Dec 7, 2020
ad442bd
Changes
chandler05 Dec 8, 2020
b139334
Merge branch 'master' into feature/bulk-actions
chandler05 May 4, 2021
3bdcbf4
Fix merge errors
chandler05 May 5, 2021
b21e1b2
Merge branch 'master' into feature/bulk-actions
chandler05 May 14, 2021
af6a76c
Refactor command to produce different results in different cases, sto…
chandler05 May 14, 2021
fa0fbb0
Fix some formatting
chandler05 May 14, 2021
a2f84c7
Changes
chandler05 May 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ forbiddenTicketPrefix: '!'

requiredTicketPrefix: ''

filterRemovalTimeout: 300000

embedDeletionEmoji: '🗑️'

maxSearchResults: 5
Expand All @@ -38,9 +40,11 @@ request:
- ☑️
- ❌
- 💬
- 📁

ignorePrependResponseMessageEmoji: ✅
ignoreResolutionEmoji: 💬
bulkEmoji: 📁

resolveDelay: 10000
progressMessageAddDelay: 10000
Expand Down
6 changes: 6 additions & 0 deletions config/template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ forbiddenTicketPrefix: <string>
# When omitted or empty, no prefix is required for posting embeds.
requiredTicketPrefix: <string>

# The time (in milliseconds) the bot will wait before removing a '!jira bulk' filter
filterRemovalTimeout: <number>

# An emoji or emoji ID which, when reacted to a bot embed, deletes it.
embedDeletionEmoji: <string>

Expand Down Expand Up @@ -106,6 +109,9 @@ request:
# An emoji or emoji ID which, when used, doesn't trigger the response template message.
ignorePrependResponseMessageEmoji: <string>

# An emoji or emoji ID which indicates a request message to be added to a bulk action.
bulkEmoji: <string>

# The amount of time in milliseconds between a volunteer reacts to the message and the bot deletes its message.
resolveDelay: <number>

Expand Down
6 changes: 6 additions & 0 deletions src/BotConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class RequestConfig {
public suggestedEmoji: string[];
public ignorePrependResponseMessageEmoji: string;
public ignoreResolutionEmoji: string;
public bulkEmoji: string;
public resolveDelay: number;
public progressMessageAddDelay: number;
public prependResponseMessage: PrependResponseMessageType;
Expand All @@ -52,6 +53,7 @@ export class RequestConfig {
this.suggestedEmoji = getOrDefault( 'request.suggestedEmoji', [] );
this.ignorePrependResponseMessageEmoji = config.get( 'request.ignorePrependResponseMessageEmoji' );
this.ignoreResolutionEmoji = config.get( 'request.ignoreResolutionEmoji' );
this.bulkEmoji = config.get( 'request.bulkEmoji' );

this.resolveDelay = config.get( 'request.resolveDelay' );
this.progressMessageAddDelay = config.get( 'request.progressMessageAddDelay' );
Expand Down Expand Up @@ -113,6 +115,8 @@ export default class BotConfig {
public static requiredTicketPrefix: string;
public static forbiddenTicketPrefix: string;

public static filterRemovalTimeout: number;

public static embedDeletionEmoji: string;

public static maxSearchResults: number;
Expand Down Expand Up @@ -140,6 +144,8 @@ export default class BotConfig {
this.forbiddenTicketPrefix = getOrDefault( 'forbiddenTicketPrefix', '' );
this.requiredTicketPrefix = getOrDefault( 'requiredTicketPrefix', '' );

this.filterRemovalTimeout = config.get( 'filterRemovalTimeout' );

this.embedDeletionEmoji = getOrDefault( 'embedDeletionEmoji', '' );

this.maxSearchResults = config.get( 'maxSearchResults' );
Expand Down
86 changes: 86 additions & 0 deletions src/commands/BulkCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Message, MessageReaction, User } from 'discord.js';
import PrefixCommand from './PrefixCommand';
import Command from './Command';
import { RequestsUtil } from '../util/RequestsUtil';
import { EmojiUtil } from '../util/EmojiUtil';
import BotConfig from '../BotConfig';
import RequestResolveEventHandler from '../events/request/RequestResolveEventHandler';
import MojiraBot from '../MojiraBot';

export default class BulkCommand extends PrefixCommand {
public readonly aliases = ['bulk', 'filter'];

public static currentBulkReactions = new Map<User, Message[]>();

public async run( message: Message, args: string ): Promise<boolean> {
let emoji: string;

if ( args.length ) {
emoji = EmojiUtil.getEmoji( args );
if ( !emoji ) {
await message.channel.send( `**Error:** ${ args } is not a valid emoji.` );
return false;
}
}

const ticketKeys: string[] = [];
let firstMentioned: string;

try {
const bulkMessages = BulkCommand.currentBulkReactions.get( message.author );
const originMessages: Message[] = [];
if ( bulkMessages ) {
for ( const bulk of bulkMessages ) {
originMessages.push( await RequestsUtil.getOriginMessage( bulk ) );

if ( emoji ) {
let reaction: MessageReaction;
if ( bulk.reactions.cache.has( emoji ) ) {
reaction = bulk.reactions.cache.get( emoji );
} else {
reaction = await bulk.react( emoji );
}
if ( emoji != BotConfig.request.bulkEmoji ) {
await new RequestResolveEventHandler( MojiraBot.client.user.id ).onEvent( reaction, message.author );
return true;
} else {
await bulk.reactions.cache.get( BotConfig.request.bulkEmoji ).users.remove( message.author );
}
}
}
originMessages.forEach( origin => ticketKeys.push( ...RequestsUtil.getTickets( origin.content ) ) );
firstMentioned = ticketKeys[0];
if ( emoji == BotConfig.request.bulkEmoji ) {
BulkCommand.currentBulkReactions.delete( message.author );
return true;
}
} else {
return false;
}
} catch ( err ) {
Command.logger.error( err );
return false;
}

const filter = `https://bugs.mojang.com/browse/${ firstMentioned }?jql=key%20in(${ ticketKeys.join( '%2C' ) })`;

try {
await message.channel.send( `${ message.author.toString() } ${ filter }` );
} catch ( err ) {
Command.logger.error( err );
return false;
}

try {
await message.react( '✅' );
} catch ( err ) {
Command.logger.error( err );
}

return true;
}

public asString( args: string ): string {
return `!jira bulk ${ args }`;
}
}
2 changes: 2 additions & 0 deletions src/commands/CommandRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import BugCommand from './BugCommand';
import BulkCommand from './BulkCommand';
import HelpCommand from './HelpCommand';
import PingCommand from './PingCommand';
import MooCommand from './MooCommand';
Expand All @@ -11,6 +12,7 @@ import TipsCommand from './TipsCommand';

export default class CommandRegistry {
public static BUG_COMMAND = new BugCommand();
public static BULK_COMMAND = new BulkCommand();
public static HELP_COMMAND = new HelpCommand();
public static MENTION_COMMAND = new MentionCommand();
public static MOO_COMMAND = new MooCommand();
Expand Down
17 changes: 3 additions & 14 deletions src/commands/PollCommand.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import PrefixCommand from './PrefixCommand';
import { Message, TextChannel, DMChannel, MessageEmbed, NewsChannel } from 'discord.js';
import Command from './Command';
import emojiRegex = require( 'emoji-regex/text.js' );
import { EmojiUtil } from '../util/EmojiUtil';
import PermissionRegistry from '../permissions/PermissionRegistry';
import { ReactionsUtil } from '../util/ReactionsUtil';

interface PollOption {
emoji: string;
emojiName?: string;
rawEmoji: string;
text: string;
}
Expand Down Expand Up @@ -103,26 +102,16 @@ export default class PollCommand extends PrefixCommand {

const optionArgs = /^\s*(\S+)\s+(.+)\s*$/.exec( option );

const customEmoji = /^<a?:(\w+):(\d+)>/;
const unicodeEmoji = emojiRegex();

if ( !optionArgs ) {
await this.sendSyntaxMessage( message.channel, 'Invalid options' );
return false;
}

const emoji = optionArgs[1];
if ( customEmoji.test( emoji ) || unicodeEmoji.test( emoji ) ) {
let emojiName = emoji;
let rawEmoji = emoji;
const emojiMatch = customEmoji.exec( emoji );
if ( emojiMatch ) {
emojiName = emojiMatch[1];
rawEmoji = emojiMatch[2];
}
const rawEmoji = EmojiUtil.getEmoji( emoji );
if ( rawEmoji ) {
options.push( {
emoji: emoji,
emojiName: emojiName,
rawEmoji: rawEmoji,
text: optionArgs[2],
} );
Expand Down
43 changes: 26 additions & 17 deletions src/events/request/RequestResolveEventHandler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { MessageReaction, User } from 'discord.js';
import * as log4js from 'log4js';
import BotConfig, { PrependResponseMessageType } from '../../BotConfig';
import BulkCommand from '../../commands/BulkCommand';
import ResolveRequestMessageTask from '../../tasks/ResolveRequestMessageTask';
import TaskScheduler from '../../tasks/TaskScheduler';
import { RequestsUtil } from '../../util/RequestsUtil';
Expand All @@ -27,26 +28,34 @@ export default class RequestResolveEventHandler implements EventHandler<'message
this.logger.info( `User ${ user.tag } added '${ reaction.emoji.name }' reaction to request message '${ reaction.message.id }'` );

TaskScheduler.clearMessageTasks( reaction.message );
await reaction.message.edit( reaction.message.embeds[0].setColor( RequestsUtil.getEmbedColor( user ) ) );

if ( BotConfig.request.prependResponseMessage == PrependResponseMessageType.WhenResolved
&& BotConfig.request.ignorePrependResponseMessageEmoji !== reaction.emoji.name ) {
const origin = await RequestsUtil.getOriginMessage( reaction.message );
if ( origin ) {
try {
await reaction.message.edit( RequestsUtil.getResponseMessage( origin ) );
} catch ( error ) {
this.logger.error( error );

if ( BotConfig.request.bulkEmoji !== reaction.emoji.name ) {
if ( BotConfig.request.prependResponseMessage == PrependResponseMessageType.WhenResolved
&& BotConfig.request.ignorePrependResponseMessageEmoji !== reaction.emoji.name ) {
const origin = await RequestsUtil.getOriginMessage( reaction.message );
if ( origin ) {
try {
await reaction.message.edit( RequestsUtil.getResponseMessage( origin ) );
} catch ( error ) {
this.logger.error( error );
}
}
}
}

if ( BotConfig.request.ignoreResolutionEmoji !== reaction.emoji.name ) {
TaskScheduler.addOneTimeMessageTask(
reaction.message,
new ResolveRequestMessageTask( reaction.emoji, user ),
BotConfig.request.resolveDelay || 0
);
if ( BotConfig.request.ignoreResolutionEmoji !== reaction.emoji.name ) {
await reaction.message.edit( reaction.message.embeds[0].setColor( RequestsUtil.getEmbedColor( user ) ) );
TaskScheduler.addOneTimeMessageTask(
reaction.message,
new ResolveRequestMessageTask( reaction.emoji, user, this.botUserId ),
BotConfig.request.resolveDelay || 0
);
}
} else {
if ( !BulkCommand.currentBulkReactions.has( user ) ) {
BulkCommand.currentBulkReactions.set( user, [ reaction.message ] );
} else {
BulkCommand.currentBulkReactions.get( user ).push( reaction.message );
}
}
};
}
26 changes: 15 additions & 11 deletions src/events/request/RequestUnresolveEventHandler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { MessageReaction, User } from 'discord.js';
import * as log4js from 'log4js';
import BotConfig, { PrependResponseMessageType } from '../../BotConfig';
import BulkCommand from '../../commands/BulkCommand';
import TaskScheduler from '../../tasks/TaskScheduler';
import DiscordUtil from '../../util/DiscordUtil';
import { RequestsUtil } from '../../util/RequestsUtil';
Expand Down Expand Up @@ -28,19 +29,22 @@ export default class RequestUnresolveEventHandler implements EventHandler<'messa

this.logger.info( `User ${ user.tag } removed '${ emoji.name }' reaction from request message '${ message.id }'` );

await message.edit( message.embeds[0].setColor( RequestsUtil.getEmbedColor() ) );

if ( BotConfig.request.prependResponseMessage == PrependResponseMessageType.WhenResolved ) {
try {
await message.edit( '' );
} catch ( error ) {
this.logger.error( error );
if ( BotConfig.request.bulkEmoji !== emoji.name ) {
await message.edit( message.embeds[0].setColor( RequestsUtil.getEmbedColor() ) );
if ( BotConfig.request.prependResponseMessage == PrependResponseMessageType.WhenResolved ) {
try {
await message.edit( '' );
} catch ( error ) {
this.logger.error( error );
}
}
}

if ( message.reactions.cache.size <= BotConfig.request.suggestedEmoji.length ) {
this.logger.info( `Cleared message task for request message '${ message.id }'` );
TaskScheduler.clearMessageTasks( message );
if ( message.reactions.cache.size <= BotConfig.request.suggestedEmoji.length ) {
this.logger.info( `Cleared message task for request message '${ message.id }'` );
TaskScheduler.clearMessageTasks( message );
}
} else if ( BulkCommand.currentBulkReactions.has( user ) ) {
BulkCommand.currentBulkReactions.set( user, BulkCommand.currentBulkReactions.get( user ).filter( stored => stored != message ) );
}
};
}
15 changes: 14 additions & 1 deletion src/tasks/ResolveRequestMessageTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ import DiscordUtil from '../util/DiscordUtil';
import { RequestsUtil } from '../util/RequestsUtil';
import MessageTask from './MessageTask';
import * as log4js from 'log4js';
import BulkCommand from '../commands/BulkCommand';

export default class ResolveRequestMessageTask extends MessageTask {
private static logger = log4js.getLogger( 'ResolveRequestMessageTask' );

private readonly emoji: EmojiResolvable;
private readonly user: User;
private readonly botUserId: string;

constructor( emoji: EmojiResolvable, user: User ) {
constructor( emoji: EmojiResolvable, user: User, botUserId: string ) {
super();
this.emoji = emoji;
this.user = user;
this.botUserId = botUserId;
}

public async run( copy: Message ): Promise<void> {
Expand All @@ -33,6 +36,16 @@ export default class ResolveRequestMessageTask extends MessageTask {

if ( origin ) {
try {
await origin.reactions.cache.forEach( async reaction => {
if ( reaction.emoji.name == BotConfig.request.bulkEmoji ) {
const users = await reaction.users.fetch();
users.forEach( user => {
if ( user.id != this.botUserId ) {
BulkCommand.currentBulkReactions.set( user, BulkCommand.currentBulkReactions.get( user ).filter( message => origin != message ) );
}
} );
}
} );
await origin.reactions.removeAll();
} catch ( error ) {
ResolveRequestMessageTask.logger.error( error );
Expand Down
17 changes: 17 additions & 0 deletions src/util/EmojiUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import emojiRegex = require( 'emoji-regex/text.js' );

export class EmojiUtil {
public static getEmoji( args: string ): string {
const customEmoji = /^<a?:(.+):(\d+)>/;
const unicodeEmoji = emojiRegex();
let rawEmoji: string;
if ( customEmoji.test( args ) ) {
rawEmoji = customEmoji.exec( args )[2];
} else if ( unicodeEmoji.test( args ) ) {
rawEmoji = args;
} else {
return undefined;
}
return rawEmoji;
}
}
Loading