Skip to content

Commit

Permalink
Limit daily requests for each user per channel (#200)
Browse files Browse the repository at this point in the history
  • Loading branch information
chandler05 authored Jun 12, 2021
1 parent 3875a5b commit 2416845
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 11 deletions.
5 changes: 5 additions & 0 deletions config/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ request:
- '683038914306506841' # bedrock-pending-requests
- '683040112191340560' # other-pending-requests
- '692382871578607767' # dungeons-pending-requests
requestLimits:
- 30 # Limit for # java-requests
- 30 # Limit for # bedrock-requests
- 45 # Limit for # other-requests
- 30 # Limit for # dungeons-requests
testingRequestChannels:
- '740188001052917801'
- '807240396445843516'
Expand Down
10 changes: 10 additions & 0 deletions config/template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ request:
- <string>
- ...

# The number of allowed requests per user per day.
# If the number of requests created by a single user exceeds this amount, new requests will not be forwarded to the internal channels.
# The length of this array MUST be the same as the length of `channels`.
# Setting to a negative number will remove the request limit
# Optional; empty by default.
requestLimits:
- <number>
- <number>
- ...

# The IDs of the server's testing request channels.
# Optional; empty by default.
testingRequestChannels:
Expand Down
2 changes: 2 additions & 0 deletions src/BotConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export enum PrependResponseMessageType {
export class RequestConfig {
public channels: string[];
public internalChannels: string[];
public requestLimits: number[];
public testingRequestChannels: string[];
public logChannel: string;

Expand All @@ -37,6 +38,7 @@ export class RequestConfig {
constructor() {
this.channels = getOrDefault( 'request.channels', [] );
this.internalChannels = this.channels.length ? config.get( 'request.internalChannels' ) : getOrDefault( 'request.internalChannels', [] );
this.requestLimits = this.channels.length ? config.get( 'request.requestLimits' ) : getOrDefault( 'request.requestLimits', [] );
this.testingRequestChannels = getOrDefault( 'request.testingRequestChannels', [] );
this.logChannel = config.get( 'request.logChannel' );

Expand Down
9 changes: 6 additions & 3 deletions src/MojiraBot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,20 @@ export default class MojiraBot {

const requestChannels: TextChannel[] = [];
const internalChannels = new Map<string, string>();
const requestLimits = new Map<string, number>();

if ( BotConfig.request.channels ) {
for ( let i = 0; i < BotConfig.request.channels.length; i++ ) {
const requestChannelId = BotConfig.request.channels[i];
const internalChannelId = BotConfig.request.internalChannels[i];
const requestLimit = BotConfig.request.requestLimits[i];
try {
const requestChannel = await DiscordUtil.getChannel( requestChannelId );
const internalChannel = await DiscordUtil.getChannel( internalChannelId );
if ( requestChannel instanceof TextChannel && internalChannel instanceof TextChannel ) {
requestChannels.push( requestChannel );
internalChannels.set( requestChannelId, internalChannelId );
requestLimits.set( requestChannelId, requestLimit )

// https://stackoverflow.com/questions/55153125/fetch-more-than-100-messages
const allMessages: Message[] = [];
Expand Down Expand Up @@ -138,7 +141,7 @@ export default class MojiraBot {
}
}

const newRequestHandler = new RequestEventHandler( internalChannels );
const newRequestHandler = new RequestEventHandler( internalChannels, requestLimits );
for ( const requestChannel of requestChannels ) {
this.logger.info( `Catching up on requests from #${ requestChannel.name }...` );

Expand Down Expand Up @@ -187,9 +190,9 @@ export default class MojiraBot {
this.logger.info( 'Fully caught up on requests.' );
}

EventRegistry.add( new ReactionAddEventHandler( this.client.user.id, internalChannels ) );
EventRegistry.add( new ReactionAddEventHandler( this.client.user.id, internalChannels, requestLimits ) );
EventRegistry.add( new ReactionRemoveEventHandler( this.client.user.id ) );
EventRegistry.add( new MessageEventHandler( this.client.user.id, internalChannels ) );
EventRegistry.add( new MessageEventHandler( this.client.user.id, internalChannels, requestLimits ) );
EventRegistry.add( new MessageUpdateEventHandler( this.client.user.id, internalChannels ) );
EventRegistry.add( new MessageDeleteEventHandler( this.client.user.id, internalChannels ) );

Expand Down
4 changes: 2 additions & 2 deletions src/events/message/MessageEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ export default class MessageEventHandler implements EventHandler<'message'> {
private readonly testingRequestEventHandler: TestingRequestEventHandler;
private readonly internalProgressEventHandler: InternalProgressEventHandler;

constructor( botUserId: string, internalChannels: Map<string, string> ) {
constructor( botUserId: string, internalChannels: Map<string, string>, requestLimits: Map<string, number> ) {
this.botUserId = botUserId;

this.requestEventHandler = new RequestEventHandler( internalChannels );
this.requestEventHandler = new RequestEventHandler( internalChannels, requestLimits );
this.testingRequestEventHandler = new TestingRequestEventHandler();
this.internalProgressEventHandler = new InternalProgressEventHandler();
}
Expand Down
4 changes: 2 additions & 2 deletions src/events/reaction/ReactionAddEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ export default class ReactionAddEventHandler implements DiscordEventHandler<'mes
private readonly requestReopenEventHandler: RequestReopenEventHandler;
private readonly mentionDeleteEventHandler = new MentionDeleteEventHandler();

constructor( botUserId: string, internalChannels: Map<string, string> ) {
constructor( botUserId: string, internalChannels: Map<string, string>, requestLimits: Map<string, number> ) {
this.botUserId = botUserId;

const requestEventHandler = new RequestEventHandler( internalChannels );
const requestEventHandler = new RequestEventHandler( internalChannels, requestLimits );
this.requestResolveEventHandler = new RequestResolveEventHandler( botUserId );
this.requestReopenEventHandler = new RequestReopenEventHandler( botUserId, requestEventHandler );
}
Expand Down
38 changes: 34 additions & 4 deletions src/events/request/RequestEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ export default class RequestEventHandler implements EventHandler<'message'> {
*/
private readonly internalChannels: Map<string, string>;

constructor( internalChannels: Map<string, string> ) {
/**
* A map from request channel IDs to request limit numbers.
*/
private readonly requestLimits: Map<string, number>;

constructor( internalChannels: Map<string, string>, requestLimits: Map<string, number> ) {
this.internalChannels = internalChannels;
this.requestLimits = requestLimits;
}

// This syntax is used to ensure that `this` refers to the `RequestEventHandler` object
Expand Down Expand Up @@ -78,6 +84,33 @@ export default class RequestEventHandler implements EventHandler<'message'> {
}
}

const requestLimit = this.requestLimits.get( origin.channel.id );
const internalChannelId = this.internalChannels.get( origin.channel.id );
const internalChannel = await DiscordUtil.getChannel( internalChannelId );

if ( requestLimit && requestLimit >= 0 && internalChannel instanceof TextChannel ) {
const internalChannelUserMessages = internalChannel.messages.cache
.filter( message => message.embeds.length > 0 && message.embeds[0].author.name == origin.author.tag )
.filter( message => new Date().valueOf() - message.embeds[0].timestamp.valueOf() <= 86400000 );
if ( internalChannelUserMessages.size >= requestLimit ) {
try {
await origin.react( BotConfig.request.invalidTicketEmoji );
} catch ( error ) {
this.logger.error( error );
}

try {
const warning = await origin.channel.send( `${ origin.author }, you have posted a lot of requests today that are still pending. Please wait for these requests to be resolved before posting more.` );

const timeout = BotConfig.request.warningLifetime;
await warning.delete( { timeout } );
} catch ( error ) {
this.logger.error( error );
}
return;
}
}

if ( BotConfig.request.waitingEmoji ) {
try {
await origin.react( BotConfig.request.waitingEmoji );
Expand All @@ -86,9 +119,6 @@ export default class RequestEventHandler implements EventHandler<'message'> {
}
}

const internalChannelId = this.internalChannels.get( origin.channel.id );
const internalChannel = await DiscordUtil.getChannel( internalChannelId );

if ( internalChannel && internalChannel instanceof TextChannel ) {
const embed = new MessageEmbed()
.setColor( RequestsUtil.getEmbedColor() )
Expand Down
1 change: 1 addition & 0 deletions src/events/request/RequestUpdateEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default class RequestUpdateEventHandler implements EventHandler<'messageU
if ( result.channelId === oldMessage.channel.id && result.messageId === oldMessage.id ) {
try {
const embed = internalMessage.embeds[0];
embed.setAuthor( oldMessage.author.tag, oldMessage.author.avatarURL() );
embed.setDescription( RequestsUtil.getRequestDescription( newMessage ) );
await internalMessage.edit( embed );
} catch ( error ) {
Expand Down

0 comments on commit 2416845

Please sign in to comment.