Skip to content

Commit 9272453

Browse files
authored
Listen to all reactions (#156)
* Enable message and user partials * Refactor fetching partial structures * Remove async from abstract methods
1 parent 94263a9 commit 9272453

16 files changed

+96
-63
lines changed

src/MojiraBot.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default class MojiraBot {
2626
public static logger = log4js.getLogger( 'MojiraBot' );
2727

2828
public static client: Client = new Client( {
29-
partials: ['REACTION'],
29+
partials: ['MESSAGE', 'REACTION', 'USER'],
3030
ws: {
3131
intents: Intents.NON_PRIVILEGED,
3232
},
@@ -72,7 +72,6 @@ export default class MojiraBot {
7272
} catch ( error ) {
7373
MojiraBot.logger.error( error );
7474
}
75-
await DiscordUtil.getMessage( channel, group.message );
7675
} catch ( err ) {
7776
this.logger.error( err );
7877
}
@@ -92,11 +91,13 @@ export default class MojiraBot {
9291
if ( requestChannel instanceof TextChannel && internalChannel instanceof TextChannel ) {
9392
requestChannels.push( requestChannel );
9493
internalChannels.set( requestChannelId, internalChannelId );
94+
9595
// https://stackoverflow.com/questions/55153125/fetch-more-than-100-messages
9696
const allMessages: Message[] = [];
9797
let lastId: string | undefined;
98-
// eslint-disable-next-line no-constant-condition
99-
while ( true ) {
98+
let continueSearch = true;
99+
100+
while ( continueSearch ) {
100101
const options: ChannelLogsQueryOptions = { limit: 50 };
101102
if ( lastId ) {
102103
options.before = lastId;
@@ -105,7 +106,7 @@ export default class MojiraBot {
105106
allMessages.push( ...messages.array() );
106107
lastId = messages.last()?.id;
107108
if ( messages.size !== 50 || !lastId ) {
108-
break;
109+
continueSearch = false;
109110
}
110111
}
111112
this.logger.info( `Fetched ${ allMessages.length } messages from "${ internalChannel.name }"` );
@@ -131,17 +132,6 @@ export default class MojiraBot {
131132
}
132133
}
133134

134-
if ( BotConfig.request.logChannel ) {
135-
try {
136-
const logChannel = await DiscordUtil.getChannel( BotConfig.request.logChannel );
137-
if ( logChannel instanceof TextChannel ) {
138-
await logChannel.messages.fetch( { limit: 100 } );
139-
}
140-
} catch ( err ) {
141-
this.logger.error( err );
142-
}
143-
}
144-
145135
const newRequestHandler = new RequestEventHandler( internalChannels );
146136
for ( const requestChannel of requestChannels ) {
147137
this.logger.info( `Catching up on requests from #${ requestChannel.name }...` );

src/commands/Command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default abstract class Command {
2626
* @param messageText The text that came with the message
2727
*/
2828
public abstract test( messageText: string ): boolean | string | string[];
29-
public abstract async run( message: Message, args: string | string[] ): Promise<boolean>;
29+
public abstract run( message: Message, args: string | string[] ): Promise<boolean>;
3030

3131
public abstract asString( args: string | string[] ): string;
3232
}

src/events/message/MessageDeleteEventHandler.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Message } from 'discord.js';
22
import BotConfig from '../../BotConfig';
3+
import DiscordUtil from '../../util/DiscordUtil';
34
import EventHandler from '../EventHandler';
45
import RequestDeleteEventHandler from '../request/RequestDeleteEventHandler';
56

@@ -18,6 +19,8 @@ export default class MessageDeleteEventHandler implements EventHandler<'messageD
1819

1920
// This syntax is used to ensure that `this` refers to the `MessageDeleteEventHandler` object
2021
public onEvent = async ( message: Message ): Promise<void> => {
22+
message = await DiscordUtil.fetchMessage( message );
23+
2124
if (
2225
// Don't handle non-default messages
2326
message.type !== 'DEFAULT'

src/events/message/MessageEventHandler.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Message } from 'discord.js';
22
import BotConfig from '../../BotConfig';
33
import CommandExecutor from '../../commands/CommandExecutor';
4+
import DiscordUtil from '../../util/DiscordUtil';
45
import EventHandler from '../EventHandler';
56
import RequestEventHandler from '../request/RequestEventHandler';
67

@@ -19,6 +20,8 @@ export default class MessageEventHandler implements EventHandler<'message'> {
1920

2021
// This syntax is used to ensure that `this` refers to the `MessageEventHandler` object
2122
public onEvent = async ( message: Message ): Promise<void> => {
23+
message = await DiscordUtil.fetchMessage( message );
24+
2225
if (
2326
// Don't reply to webhooks
2427
message.webhookID

src/events/message/MessageUpdateEventHandler.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Message } from 'discord.js';
33
import BotConfig from '../../BotConfig';
44
import RequestDeleteEventHandler from '../request/RequestDeleteEventHandler';
55
import RequestEventHandler from '../request/RequestEventHandler';
6+
import DiscordUtil from '../../util/DiscordUtil';
67

78
export default class MessageUpdateEventHandler implements EventHandler<'messageUpdate'> {
89
public readonly eventName = 'messageUpdate';
@@ -21,6 +22,9 @@ export default class MessageUpdateEventHandler implements EventHandler<'messageU
2122

2223
// This syntax is used to ensure that `this` refers to the `MessageUpdateEventHandler` object
2324
public onEvent = async ( oldMessage: Message, newMessage: Message ): Promise<void> => {
25+
oldMessage = await DiscordUtil.fetchMessage( oldMessage );
26+
newMessage = await DiscordUtil.fetchMessage( newMessage );
27+
2428
if (
2529
// Don't handle non-default messages
2630
oldMessage.type !== 'DEFAULT'

src/events/reaction/ReactionAddEventHandler.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import RequestReactionRemovalEventHandler from '../request/RequestReactionRemova
88
import RoleSelectEventHandler from '../roles/RoleSelectEventHandler';
99
import MentionDeleteEventHandler from '../mention/MentionDeleteEventHandler';
1010
import MojiraBot from '../../MojiraBot';
11+
import DiscordUtil from '../../util/DiscordUtil';
1112

1213
export default class ReactionAddEventHandler implements DiscordEventHandler<'messageReactionAdd'> {
1314
public readonly eventName = 'messageReactionAdd';
@@ -28,31 +29,32 @@ export default class ReactionAddEventHandler implements DiscordEventHandler<'mes
2829
}
2930

3031
// This syntax is used to ensure that `this` refers to the `ReactionAddEventHandler` object
31-
public onEvent = async ( messageReaction: MessageReaction, user: User ): Promise<void> => {
32+
public onEvent = async ( reaction: MessageReaction, user: User ): Promise<void> => {
3233
// Do not react to own reactions
3334
if ( user.id === this.botUserId ) return;
3435

35-
if ( messageReaction.partial ) {
36-
messageReaction = await messageReaction.fetch();
37-
}
36+
reaction = await DiscordUtil.fetchReaction( reaction );
37+
user = await DiscordUtil.fetchUser( user );
38+
39+
const message = await DiscordUtil.fetchMessage( reaction.message );
3840

39-
MojiraBot.logger.debug( `User ${ user.tag } reacted with ${ messageReaction.emoji } to message ${ messageReaction.message.id }` );
41+
MojiraBot.logger.debug( `User ${ user.tag } reacted with ${ reaction.emoji } to message ${ message.id }` );
4042

41-
if ( BotConfig.roleGroups.find( g => g.message === messageReaction.message.id ) ) {
43+
if ( BotConfig.roleGroups.find( g => g.message === message.id ) ) {
4244
// Handle role selection
43-
return this.roleSelectHandler.onEvent( messageReaction, user );
44-
} else if ( BotConfig.request.internalChannels.includes( messageReaction.message.channel.id ) ) {
45+
return this.roleSelectHandler.onEvent( reaction, user );
46+
} else if ( BotConfig.request.internalChannels.includes( message.channel.id ) ) {
4547
// Handle resolving user request
46-
return this.requestResolveEventHandler.onEvent( messageReaction, user );
47-
} else if ( BotConfig.request.channels.includes( messageReaction.message.channel.id ) ) {
48+
return this.requestResolveEventHandler.onEvent( reaction, user );
49+
} else if ( BotConfig.request.channels.includes( message.channel.id ) ) {
4850
// Handle removing user reactions in the request channels
49-
return this.requestReactionRemovalEventHandler.onEvent( messageReaction, user );
50-
} else if ( BotConfig.request.logChannel.includes( messageReaction.message.channel.id ) ) {
51+
return this.requestReactionRemovalEventHandler.onEvent( reaction, user );
52+
} else if ( BotConfig.request.logChannel.includes( message.channel.id ) ) {
5153
// Handle reopening a user request
52-
return this.requestReopenEventHandler.onEvent( messageReaction, user );
53-
} else if ( messageReaction.message.author.id === this.botUserId && messageReaction.emoji.name === BotConfig.embedDeletionEmoji ) {
54+
return this.requestReopenEventHandler.onEvent( reaction, user );
55+
} else if ( reaction.message.author.id === this.botUserId && reaction.emoji.name === BotConfig.embedDeletionEmoji ) {
5456
// Handle deleting bot embed
55-
return this.mentionDeleteEventHandler.onEvent( messageReaction, user );
57+
return this.mentionDeleteEventHandler.onEvent( reaction, user );
5658
}
5759
};
5860
}

src/events/reaction/ReactionRemoveEventHandler.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import EventHandler from '../EventHandler';
44
import RequestUnresolveEventHandler from '../request/RequestUnresolveEventHandler';
55
import RoleRemoveEventHandler from '../roles/RoleRemoveEventHandler';
66
import MojiraBot from '../../MojiraBot';
7+
import DiscordUtil from '../../util/DiscordUtil';
78

89
export default class ReactionRemoveEventHandler implements EventHandler<'messageReactionRemove'> {
910
public readonly eventName = 'messageReactionRemove';
@@ -18,21 +19,22 @@ export default class ReactionRemoveEventHandler implements EventHandler<'message
1819
}
1920

2021
// This syntax is used to ensure that `this` refers to the `ReactionRemoveEventHandler` object
21-
public onEvent = async ( messageReaction: MessageReaction, user: User ): Promise<void> => {
22+
public onEvent = async ( reaction: MessageReaction, user: User ): Promise<void> => {
2223
if ( user.id === this.botUserId ) return;
2324

24-
if ( messageReaction.partial ) {
25-
messageReaction = await messageReaction.fetch();
26-
}
25+
reaction = await DiscordUtil.fetchReaction( reaction );
26+
user = await DiscordUtil.fetchUser( user );
27+
28+
const message = await DiscordUtil.fetchMessage( reaction.message );
2729

28-
MojiraBot.logger.debug( `User ${ user.tag } removed reaction ${ messageReaction.emoji } to message ${ messageReaction.message.id }` );
30+
MojiraBot.logger.debug( `User ${ user.tag } removed reaction ${ reaction.emoji } to message ${ message.id }` );
2931

30-
if ( BotConfig.roleGroups.find( g => g.message === messageReaction.message.id ) ) {
32+
if ( BotConfig.roleGroups.find( g => g.message === message.id ) ) {
3133
// Handle role removal
32-
return this.roleRemoveHandler.onEvent( messageReaction, user );
33-
} else if ( BotConfig.request.internalChannels.includes( messageReaction.message.channel.id ) ) {
34+
return this.roleRemoveHandler.onEvent( reaction, user );
35+
} else if ( BotConfig.request.internalChannels.includes( message.channel.id ) ) {
3436
// Handle unresolving user request
35-
return this.requestUnresolveEventHandler.onEvent( messageReaction, user );
37+
return this.requestUnresolveEventHandler.onEvent( reaction, user );
3638
}
3739
};
3840
}

src/events/request/RequestReactionRemovalEventHandler.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { MessageReaction, User } from 'discord.js';
22
import * as log4js from 'log4js';
3+
import DiscordUtil from '../../util/DiscordUtil';
34
import EventHandler from '../EventHandler';
45

56
export default class RequestReactionRemovalEventHandler implements EventHandler<'messageReactionAdd'> {
@@ -9,10 +10,12 @@ export default class RequestReactionRemovalEventHandler implements EventHandler<
910

1011
// This syntax is used to ensure that `this` refers to the `RequestResolveEventHandler` object
1112
public onEvent = async ( reaction: MessageReaction, user: User ): Promise<void> => {
12-
this.logger.info( `User ${ user.tag } added '${ reaction.emoji.name }' reaction to request message '${ reaction.message.id }'` );
13-
const guildMember = reaction.message.guild.member( user );
13+
const message = await DiscordUtil.fetchMessage( reaction.message );
1414

15-
if ( !guildMember.permissionsIn( reaction.message.channel ).has( 'ADD_REACTIONS' ) ) {
15+
this.logger.info( `User ${ user.tag } added '${ reaction.emoji.name }' reaction to request message '${ message.id }'` );
16+
const guildMember = message.guild.member( user );
17+
18+
if ( !guildMember.permissionsIn( message.channel ).has( 'ADD_REACTIONS' ) ) {
1619
await reaction.users.remove( user );
1720
}
1821
};

src/events/request/RequestReopenEventHandler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export default class RequestReopenEventHandler implements EventHandler<'messageR
2121
public onEvent = async ( { message }: MessageReaction, user: User ): Promise<void> => {
2222
this.logger.info( `User ${ user.tag } is reopening the request message '${ message.id }'` );
2323

24+
message = await DiscordUtil.fetchMessage( message );
25+
2426
const requestMessage = await RequestsUtil.getOriginMessage( message );
2527

2628
const logChannel = await DiscordUtil.getChannel( BotConfig.request.logChannel );

src/events/request/RequestUnresolveEventHandler.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { MessageReaction, User } from 'discord.js';
22
import * as log4js from 'log4js';
33
import BotConfig, { PrependResponseMessageType } from '../../BotConfig';
44
import TaskScheduler from '../../tasks/TaskScheduler';
5-
import { RequestsUtil } from '../../util/RequestsUtil'
5+
import DiscordUtil from '../../util/DiscordUtil';
6+
import { RequestsUtil } from '../../util/RequestsUtil';
67
import EventHandler from '../EventHandler';
78

89
export default class RequestUnresolveEventHandler implements EventHandler<'messageReactionRemove'> {
@@ -14,6 +15,8 @@ export default class RequestUnresolveEventHandler implements EventHandler<'messa
1415
public onEvent = async ( { emoji, message }: MessageReaction, user: User ): Promise<void> => {
1516
this.logger.info( `User ${ user.tag } removed '${ emoji.name }' reaction from request message '${ message.id }'` );
1617

18+
message = await DiscordUtil.fetchMessage( message );
19+
1720
await message.edit( message.embeds[0].setColor( RequestsUtil.getEmbedColor() ) );
1821

1922
if ( BotConfig.request.prependResponseMessage == PrependResponseMessageType.WhenResolved ) {

src/events/roles/RoleRemoveEventHandler.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ export default class RoleRemoveEventHandler implements EventHandler<'messageReac
1010
private logger = log4js.getLogger( 'RoleRemoveEventHandler' );
1111

1212
// This syntax is used to ensure that `this` refers to the `RoleRemoveEventHandler` object
13-
public onEvent = async ( messageReaction: MessageReaction, user: User ): Promise<void> => {
14-
this.logger.info( `User ${ user.tag } removed '${ messageReaction.emoji.name }' reaction from role message` );
13+
public onEvent = async ( reaction: MessageReaction, user: User ): Promise<void> => {
14+
this.logger.info( `User ${ user.tag } removed '${ reaction.emoji.name }' reaction from role message` );
1515

16-
const group = BotConfig.roleGroups.find( searchedGroup => searchedGroup.message === messageReaction.message.id );
17-
const role = group.roles.find( searchedRole => searchedRole.emoji === messageReaction.emoji.id || searchedRole.emoji === messageReaction.emoji.name );
16+
const group = BotConfig.roleGroups.find( searchedGroup => searchedGroup.message === reaction.message.id );
17+
const role = group.roles.find( searchedRole => searchedRole.emoji === reaction.emoji.id || searchedRole.emoji === reaction.emoji.name );
1818

1919
if ( !role ) return;
2020

21-
const member = await DiscordUtil.getMember( messageReaction.message.guild, user.id );
21+
const member = await DiscordUtil.getMember( reaction.message.guild, user.id );
2222
if ( member ) {
2323
try {
2424
await member.roles.remove( role.id );

src/events/roles/RoleSelectEventHandler.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,29 @@ export default class RoleSelectEventHandler implements EventHandler<'messageReac
1010
private logger = log4js.getLogger( 'RoleSelectEventHandler' );
1111

1212
// This syntax is used to ensure that `this` refers to the `RoleSelectEventHandler` object
13-
public onEvent = async ( messageReaction: MessageReaction, user: User ): Promise<void> => {
14-
this.logger.info( `User ${ user.tag } added '${ messageReaction.emoji.name }' reaction to role message` );
13+
public onEvent = async ( reaction: MessageReaction, user: User ): Promise<void> => {
14+
this.logger.info( `User ${ user.tag } added '${ reaction.emoji.name }' reaction to role message` );
1515

16-
const group = BotConfig.roleGroups.find( searchedGroup => searchedGroup.message === messageReaction.message.id );
17-
const role = group.roles.find( searchedRole => searchedRole.emoji === messageReaction.emoji.id || searchedRole.emoji === messageReaction.emoji.name );
16+
const group = BotConfig.roleGroups.find( searchedGroup => searchedGroup.message === reaction.message.id );
17+
const role = group.roles.find( searchedRole => searchedRole.emoji === reaction.emoji.id || searchedRole.emoji === reaction.emoji.name );
1818

1919
if ( !role ) {
2020
try {
21-
await messageReaction.users.remove( user );
21+
await reaction.users.remove( user );
2222
} catch ( error ) {
2323
this.logger.error( error );
2424
}
2525
return;
2626
}
2727

28-
const member = await DiscordUtil.getMember( messageReaction.message.guild, user.id );
28+
const member = await DiscordUtil.getMember( reaction.message.guild, user.id );
2929

3030
if ( group.radio ) {
3131
// Remove other reactions.
32-
for ( const reaction of messageReaction.message.reactions.cache.values() ) {
33-
if ( reaction.emoji.id !== role.emoji ) {
32+
for ( const otherReaction of reaction.message.reactions.cache.values() ) {
33+
if ( otherReaction.emoji.id !== role.emoji ) {
3434
try {
35-
await reaction.users.remove( user );
35+
await otherReaction.users.remove( user );
3636
} catch ( error ) {
3737
this.logger.error( error );
3838
}

src/mentions/Mention.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ import * as log4js from 'log4js';
44
export abstract class Mention {
55
public static logger = log4js.getLogger( 'Mention' );
66

7-
abstract async getEmbed(): Promise<MessageEmbed>;
7+
abstract getEmbed(): Promise<MessageEmbed>;
88
}

src/tasks/MessageTask.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Message } from 'discord.js';
22

33
export default abstract class MessageTask {
4-
public abstract async run( message: Message ): Promise<void>;
4+
public abstract run( message: Message ): Promise<void>;
55
}

src/tasks/Task.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export default abstract class Task {
2-
public abstract async run(): Promise<void>;
2+
public abstract run(): Promise<void>;
33
}

src/util/DiscordUtil.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import MojiraBot from '../MojiraBot';
2-
import { TextChannel, Message, Channel, Guild, GuildMember } from 'discord.js';
2+
import { TextChannel, Message, Channel, Guild, GuildMember, MessageReaction, User } from 'discord.js';
33

44
export default class DiscordUtil {
55
public static async getChannel( channelId: string ): Promise<Channel> {
@@ -25,4 +25,25 @@ export default class DiscordUtil {
2525

2626
return await guild.members.fetch( userId );
2727
}
28+
29+
public static async fetchMessage( message: Message ): Promise<Message> {
30+
if ( message.partial ) {
31+
message = await message.fetch();
32+
}
33+
return message;
34+
}
35+
36+
public static async fetchReaction( reaction: MessageReaction ): Promise<MessageReaction> {
37+
if ( reaction.partial ) {
38+
reaction = await reaction.fetch();
39+
}
40+
return reaction;
41+
}
42+
43+
public static async fetchUser( user: User ): Promise<User> {
44+
if ( user.partial ) {
45+
user = await user.fetch();
46+
}
47+
return user;
48+
}
2849
}

0 commit comments

Comments
 (0)