2019-05-02 08:47:44 +03:00
import { decorators as d , IPluginOptions , logger } from "knub" ;
2019-01-03 04:15:18 +02:00
import { GuildChannel , Message , TextChannel , Constants as ErisConstants , User } from "eris" ;
2019-05-04 19:18:16 +03:00
import {
convertDelayStringToMS ,
createChunkedMessage ,
errorMessage ,
noop ,
stripObjectToScalars ,
successMessage ,
UnknownUser ,
} from "../utils" ;
2018-12-15 17:04:04 +02:00
import { GuildSlowmodes } from "../data/GuildSlowmodes" ;
import humanizeDuration from "humanize-duration" ;
2019-03-04 21:44:04 +02:00
import { ZeppelinPlugin } from "./ZeppelinPlugin" ;
2019-03-16 12:32:25 +02:00
import { SavedMessage } from "../data/entities/SavedMessage" ;
import { GuildSavedMessages } from "../data/GuildSavedMessages" ;
2019-05-02 08:47:44 +03:00
import DiscordRESTError from "eris/lib/errors/DiscordRESTError" ; // tslint:disable-line
2019-05-04 19:18:16 +03:00
import { GuildLogs } from "../data/GuildLogs" ;
import { LogType } from "../data/LogType" ;
2019-07-21 21:15:52 +03:00
import * as t from "io-ts" ;
const ConfigSchema = t . type ( {
use_native_slowmode : t.boolean ,
can_manage : t.boolean ,
is_affected : t.boolean ,
} ) ;
type TConfigSchema = t . TypeOf < typeof ConfigSchema > ;
2018-12-15 17:04:04 +02:00
2019-04-15 14:01:49 +03:00
const NATIVE_SLOWMODE_LIMIT = 6 * 60 * 60 ; // 6 hours
2019-04-20 17:36:28 +03:00
const MAX_SLOWMODE = 60 * 60 * 24 * 365 * 100 ; // 100 years
2019-05-02 18:34:15 +03:00
const BOT_SLOWMODE_CLEAR_INTERVAL = 60 * 1000 ;
2019-04-13 01:56:11 +03:00
2019-07-21 21:15:52 +03:00
export class SlowmodePlugin extends ZeppelinPlugin < TConfigSchema > {
2019-01-13 23:37:53 +02:00
public static pluginName = "slowmode" ;
2019-08-22 02:58:32 +03:00
public static configSchema = ConfigSchema ;
public static pluginInfo = {
prettyName : "Slowmode" ,
} ;
2019-01-03 06:15:28 +02:00
2018-12-15 17:04:04 +02:00
protected slowmodes : GuildSlowmodes ;
2019-03-16 12:32:25 +02:00
protected savedMessages : GuildSavedMessages ;
2019-05-04 19:18:16 +03:00
protected logs : GuildLogs ;
2018-12-15 17:04:04 +02:00
protected clearInterval ;
2019-03-16 12:32:25 +02:00
private onMessageCreateFn ;
2019-08-22 01:22:26 +03:00
public static getStaticDefaultOptions ( ) : IPluginOptions < TConfigSchema > {
2018-12-15 17:04:04 +02:00
return {
2019-03-16 11:26:50 +02:00
config : {
use_native_slowmode : true ,
2019-03-04 21:44:04 +02:00
2019-04-13 01:44:18 +03:00
can_manage : false ,
is_affected : true ,
2018-12-15 17:04:04 +02:00
} ,
overrides : [
{
level : ">=50" ,
2019-04-13 01:44:18 +03:00
config : {
can_manage : true ,
is_affected : false ,
2019-02-17 15:19:55 +02:00
} ,
} ,
] ,
2018-12-15 17:04:04 +02:00
} ;
}
onLoad() {
2019-05-25 21:25:34 +03:00
this . slowmodes = GuildSlowmodes . getGuildInstance ( this . guildId ) ;
this . savedMessages = GuildSavedMessages . getGuildInstance ( this . guildId ) ;
2019-05-04 19:18:16 +03:00
this . logs = new GuildLogs ( this . guildId ) ;
2019-05-02 18:34:15 +03:00
this . clearInterval = setInterval ( ( ) = > this . clearExpiredSlowmodes ( ) , BOT_SLOWMODE_CLEAR_INTERVAL ) ;
2019-03-16 12:32:25 +02:00
this . onMessageCreateFn = this . onMessageCreate . bind ( this ) ;
this . savedMessages . events . on ( "create" , this . onMessageCreateFn ) ;
2018-12-15 17:04:04 +02:00
}
onUnload() {
clearInterval ( this . clearInterval ) ;
2019-03-16 12:32:25 +02:00
this . savedMessages . events . off ( "create" , this . onMessageCreateFn ) ;
2018-12-15 17:04:04 +02:00
}
/ * *
2019-03-16 11:26:50 +02:00
* Applies a bot - maintained slowmode to the specified user id on the specified channel .
2018-12-15 17:04:04 +02:00
* This sets the channel permissions so the user is unable to send messages there , and saves the slowmode in the db .
* /
2019-03-16 11:26:50 +02:00
async applyBotSlowmodeToUserId ( channel : GuildChannel & TextChannel , userId : string ) {
2018-12-15 17:04:04 +02:00
// Deny sendMessage permission from the user. If there are existing permission overwrites, take those into account.
const existingOverride = channel . permissionOverwrites . get ( userId ) ;
const newDeniedPermissions =
( existingOverride ? existingOverride.deny : 0 ) | ErisConstants . Permissions . sendMessages ;
const newAllowedPermissions =
( existingOverride ? existingOverride.allow : 0 ) & ~ ErisConstants . Permissions . sendMessages ;
2019-05-02 08:47:44 +03:00
try {
2019-05-02 17:47:18 +03:00
await channel . editPermission ( userId , newAllowedPermissions , newDeniedPermissions , "member" ) ;
2019-05-02 08:47:44 +03:00
} catch ( e ) {
2019-05-04 19:18:16 +03:00
const user = this . bot . users . get ( userId ) || new UnknownUser ( { id : userId } ) ;
2019-05-02 08:47:44 +03:00
if ( e instanceof DiscordRESTError && e . code === 50013 ) {
logger . warn (
2019-11-02 22:11:26 +02:00
` Missing permissions to apply bot slowmode to user ${ userId } on channel ${ channel . name } ( ${ channel . id } ) on server ${ this . guild . name } ( ${ this . guildId } ) ` ,
2019-05-02 08:47:44 +03:00
) ;
2019-05-04 19:18:16 +03:00
this . logs . log ( LogType . BOT_ALERT , {
body : ` Missing permissions to apply bot slowmode to {userMention(user)} in {channelMention(channel)} ` ,
user : stripObjectToScalars ( user ) ,
channel : stripObjectToScalars ( channel ) ,
} ) ;
2019-05-02 17:47:18 +03:00
} else {
2019-05-04 19:18:16 +03:00
this . logs . log ( LogType . BOT_ALERT , {
body : ` Failed to apply bot slowmode to {userMention(user)} in {channelMention(channel)} ` ,
user : stripObjectToScalars ( user ) ,
channel : stripObjectToScalars ( channel ) ,
} ) ;
2019-05-02 17:47:18 +03:00
throw e ;
2019-05-02 08:47:44 +03:00
}
}
2019-05-02 17:47:18 +03:00
await this . slowmodes . addSlowmodeUser ( channel . id , userId ) ;
2018-12-15 17:04:04 +02:00
}
/ * *
2019-03-16 11:26:50 +02:00
* Clears bot - maintained slowmode from the specified user id on the specified channel .
2018-12-15 17:04:04 +02:00
* This reverts the channel permissions changed above and clears the database entry .
* /
2019-05-11 06:22:26 +03:00
async clearBotSlowmodeFromUserId ( channel : GuildChannel & TextChannel , userId : string , force = false ) {
try {
// Remove permission overrides from the channel for this user
// Previously we diffed the overrides so we could clear the "send messages" override without touching other
// overrides. Unfortunately, it seems that was a bit buggy - we didn't always receive the event for the changed
// overrides and then we also couldn't diff against them. For consistency's sake, we just delete the override now.
await channel . deletePermission ( userId ) ;
} catch ( e ) {
if ( ! force ) {
throw e ;
2018-12-15 17:04:04 +02:00
}
}
await this . slowmodes . clearSlowmodeUser ( channel . id , userId ) ;
}
/ * *
2019-03-16 11:26:50 +02:00
* Disable slowmode on the specified channel . Clears any existing slowmode perms .
2018-12-15 17:04:04 +02:00
* /
2019-03-16 11:26:50 +02:00
async disableBotSlowmodeForChannel ( channel : GuildChannel & TextChannel ) {
2018-12-15 17:04:04 +02:00
// Disable channel slowmode
2019-03-16 11:26:50 +02:00
await this . slowmodes . deleteChannelSlowmode ( channel . id ) ;
2018-12-15 17:04:04 +02:00
// Remove currently applied slowmodes
2019-03-16 11:26:50 +02:00
const users = await this . slowmodes . getChannelSlowmodeUsers ( channel . id ) ;
2018-12-15 17:04:04 +02:00
const failedUsers = [ ] ;
for ( const slowmodeUser of users ) {
try {
2019-03-16 11:26:50 +02:00
await this . clearBotSlowmodeFromUserId ( channel , slowmodeUser . user_id ) ;
2018-12-15 17:04:04 +02:00
} catch ( e ) {
// Removing the slowmode failed. Record this so the permissions can be changed manually, and remove the database entry.
failedUsers . push ( slowmodeUser . user_id ) ;
2019-03-16 11:26:50 +02:00
await this . slowmodes . clearSlowmodeUser ( channel . id , slowmodeUser . user_id ) ;
2018-12-15 17:04:04 +02:00
}
}
2019-03-16 11:26:50 +02:00
return { failedUsers } ;
}
/ * *
* COMMAND : Disable slowmode on the specified channel
* /
@d . command ( "slowmode disable" , "<channel:channel>" )
2019-04-13 01:44:18 +03:00
@d . permission ( "can_manage" )
2019-03-16 11:26:50 +02:00
async disableSlowmodeCmd ( msg : Message , args : { channel : GuildChannel & TextChannel } ) {
const botSlowmode = await this . slowmodes . getChannelSlowmode ( args . channel . id ) ;
const hasNativeSlowmode = args . channel . rateLimitPerUser ;
if ( ! botSlowmode && hasNativeSlowmode === 0 ) {
msg . channel . createMessage ( errorMessage ( "Channel is not on slowmode!" ) ) ;
return ;
}
const initMsg = await msg . channel . createMessage ( "Disabling slowmode..." ) ;
// Disable bot-maintained slowmode
let failedUsers = [ ] ;
if ( botSlowmode ) {
const result = await this . disableBotSlowmodeForChannel ( args . channel ) ;
failedUsers = result . failedUsers ;
}
// Disable native slowmode
if ( hasNativeSlowmode ) {
await args . channel . edit ( { rateLimitPerUser : 0 } ) ;
}
2018-12-15 17:04:04 +02:00
if ( failedUsers . length ) {
msg . channel . createMessage (
successMessage (
2019-03-16 11:26:50 +02:00
` Slowmode disabled! Failed to clear slowmode from the following users: \ n \ n<@! ${ failedUsers . join ( ">\n<@!" ) } > ` ,
2019-02-17 15:19:55 +02:00
) ,
2018-12-15 17:04:04 +02:00
) ;
} else {
msg . channel . createMessage ( successMessage ( "Slowmode disabled!" ) ) ;
initMsg . delete ( ) . catch ( noop ) ;
}
}
2019-01-03 04:15:18 +02:00
/ * *
* COMMAND : Clear slowmode from a specific user on a specific channel
* /
2019-05-11 06:22:26 +03:00
@d . command ( "slowmode clear" , "<channel:channel> <user:resolvedUserLoose>" , {
options : [
{
name : "force" ,
2020-01-04 16:00:21 +11:00
isSwitch : true ,
2019-05-11 06:22:26 +03:00
} ,
] ,
} )
2019-04-13 01:44:18 +03:00
@d . permission ( "can_manage" )
2019-05-11 06:22:26 +03:00
async clearSlowmodeCmd ( msg : Message , args : { channel : GuildChannel & TextChannel ; user : User ; force? : boolean } ) {
2019-01-03 04:15:18 +02:00
const channelSlowmode = await this . slowmodes . getChannelSlowmode ( args . channel . id ) ;
if ( ! channelSlowmode ) {
msg . channel . createMessage ( errorMessage ( "Channel doesn't have slowmode!" ) ) ;
return ;
}
2019-05-04 19:18:16 +03:00
try {
2019-05-11 06:22:26 +03:00
await this . clearBotSlowmodeFromUserId ( args . channel , args . user . id , args . force ) ;
2019-05-04 19:18:16 +03:00
} catch ( e ) {
return this . sendErrorMessage (
msg . channel ,
` Failed to clear slowmode from ** ${ args . user . username } # ${ args . user . discriminator } ** in <# ${ args . channel . id } > ` ,
) ;
}
this . sendSuccessMessage (
msg . channel ,
` Slowmode cleared from ** ${ args . user . username } # ${ args . user . discriminator } ** in <# ${ args . channel . id } > ` ,
2019-01-03 04:15:18 +02:00
) ;
}
2019-03-16 12:32:25 +02:00
@d . command ( "slowmode list" )
2019-04-13 01:44:18 +03:00
@d . permission ( "can_manage" )
2019-03-16 12:32:25 +02:00
async slowmodeListCmd ( msg : Message ) {
const channels = this . guild . channels ;
const slowmodes : Array < { channel : GuildChannel ; seconds : number ; native : boolean } > = [ ] ;
for ( const channel of channels . values ( ) ) {
if ( ! ( channel instanceof TextChannel ) ) continue ;
// Bot slowmode
const botSlowmode = await this . slowmodes . getChannelSlowmode ( channel . id ) ;
if ( botSlowmode ) {
slowmodes . push ( { channel , seconds : botSlowmode.slowmode_seconds , native : false } ) ;
continue ;
}
// Native slowmode
if ( channel . rateLimitPerUser ) {
slowmodes . push ( { channel , seconds : channel.rateLimitPerUser , native : true } ) ;
continue ;
}
}
if ( slowmodes . length ) {
const lines = slowmodes . map ( slowmode = > {
const humanized = humanizeDuration ( slowmode . seconds * 1000 ) ;
const type = slowmode . native ? "native slowmode" : "bot slowmode" ;
return ` <# ${ slowmode . channel . id } > ** ${ humanized } ** ${ type } ` ;
} ) ;
createChunkedMessage ( msg . channel , lines . join ( "\n" ) ) ;
} else {
msg . channel . createMessage ( errorMessage ( "No active slowmodes!" ) ) ;
}
}
2019-05-07 22:24:42 +03:00
@d . command ( "slowmode" , "[channel:channel]" )
@d . permission ( "can_manage" )
async showSlowmodeCmd ( msg : Message , args : { channel : GuildChannel & TextChannel } ) {
const channel = args . channel || msg . channel ;
if ( channel == null || ! ( channel instanceof TextChannel ) ) {
msg . channel . createMessage ( errorMessage ( "Channel must be a text channel" ) ) ;
return ;
}
let currentSlowmode = channel . rateLimitPerUser ;
let isNative = true ;
if ( ! currentSlowmode ) {
const botSlowmode = await this . slowmodes . getChannelSlowmode ( channel . id ) ;
if ( botSlowmode ) {
currentSlowmode = botSlowmode . slowmode_seconds ;
isNative = false ;
}
}
if ( currentSlowmode ) {
const humanized = humanizeDuration ( channel . rateLimitPerUser * 1000 ) ;
const slowmodeType = isNative ? "native" : "bot-maintained" ;
msg . channel . createMessage ( ` The current slowmode of <# ${ channel . id } > is ** ${ humanized } ** ( ${ slowmodeType } ) ` ) ;
} else {
msg . channel . createMessage ( "Channel is not on slowmode" ) ;
}
}
2018-12-15 17:04:04 +02:00
/ * *
2019-03-16 11:26:50 +02:00
* COMMAND : Set slowmode for the specified channel
2018-12-15 17:04:04 +02:00
* /
2019-04-13 02:23:45 +03:00
@d . command ( "slowmode" , "<channel:channel> <time:string>" , {
overloads : [ "<time:string>" ] ,
} )
2019-04-13 01:44:18 +03:00
@d . permission ( "can_manage" )
2018-12-15 17:04:04 +02:00
async slowmodeCmd ( msg : Message , args : { channel? : GuildChannel & TextChannel ; time : string } ) {
const channel = args . channel || msg . channel ;
if ( channel == null || ! ( channel instanceof TextChannel ) ) {
msg . channel . createMessage ( errorMessage ( "Channel must be a text channel" ) ) ;
return ;
}
2019-04-14 14:05:16 +03:00
const seconds = Math . ceil ( convertDelayStringToMS ( args . time , "s" ) / 1000 ) ;
2019-04-13 01:56:11 +03:00
const useNativeSlowmode = this . getConfigForChannel ( channel ) . use_native_slowmode && seconds <= NATIVE_SLOWMODE_LIMIT ;
2019-03-16 11:26:50 +02:00
2019-04-20 17:36:28 +03:00
if ( seconds === 0 ) {
2019-04-20 19:33:39 +03:00
return this . disableSlowmodeCmd ( msg , { channel } ) ;
2019-04-20 17:36:28 +03:00
}
if ( seconds > MAX_SLOWMODE ) {
this . sendErrorMessage ( msg . channel , ` Sorry, slowmodes can be at most 100 years long. Maybe 99 would be enough? ` ) ;
return ;
}
2019-03-16 11:26:50 +02:00
if ( useNativeSlowmode ) {
// Native slowmode
// If there is an existing bot-maintained slowmode, disable that first
const existingBotSlowmode = await this . slowmodes . getChannelSlowmode ( channel . id ) ;
if ( existingBotSlowmode ) {
await this . disableBotSlowmodeForChannel ( channel ) ;
}
// Set slowmode
2019-05-07 22:24:42 +03:00
try {
await channel . edit ( {
rateLimitPerUser : seconds ,
} ) ;
} catch ( e ) {
return this . sendErrorMessage ( msg . channel , "Failed to set native slowmode (check permissions)" ) ;
}
2019-03-16 11:26:50 +02:00
} else {
// Bot-maintained slowmode
// If there is an existing native slowmode, disable that first
if ( channel . rateLimitPerUser ) {
await channel . edit ( {
rateLimitPerUser : 0 ,
} ) ;
}
await this . slowmodes . setChannelSlowmode ( channel . id , seconds ) ;
}
2018-12-15 17:04:04 +02:00
const humanizedSlowmodeTime = humanizeDuration ( seconds * 1000 ) ;
2019-03-16 11:26:50 +02:00
const slowmodeType = useNativeSlowmode ? "native slowmode" : "bot-maintained slowmode" ;
2019-05-07 22:24:42 +03:00
this . sendSuccessMessage (
msg . channel ,
` Set ${ humanizedSlowmodeTime } slowmode for <# ${ channel . id } > ( ${ slowmodeType } ) ` ,
2018-12-15 17:04:04 +02:00
) ;
}
/ * *
2019-03-16 11:26:50 +02:00
* EVENT : On every message , check if the channel has a bot - maintained slowmode . If it does , apply slowmode to the user .
2018-12-15 17:04:04 +02:00
* If the user already had slowmode but was still able to send a message ( e . g . sending a lot of messages at once ) ,
* remove the messages sent after slowmode was applied .
* /
2019-03-16 12:32:25 +02:00
async onMessageCreate ( msg : SavedMessage ) {
if ( msg . is_bot ) return ;
const channel = this . guild . channels . get ( msg . channel_id ) as GuildChannel & TextChannel ;
if ( ! channel ) return ;
// Don't apply slowmode if the lock was interrupted earlier (e.g. the message was caught by word filters)
const thisMsgLock = await this . locks . acquire ( ` message- ${ msg . id } ` ) ;
if ( thisMsgLock . interrupted ) return ;
2019-05-02 18:14:36 +03:00
// Check if this channel even *has* a bot-maintained slowmode
const channelSlowmode = await this . slowmodes . getChannelSlowmode ( channel . id ) ;
if ( ! channelSlowmode ) return thisMsgLock . unlock ( ) ;
2019-03-16 12:32:25 +02:00
// Make sure this user is affected by the slowmode
2019-05-02 08:21:11 +03:00
const member = await this . getMember ( msg . user_id ) ;
2019-04-15 14:11:58 +03:00
const isAffected = this . hasPermission ( "is_affected" , { channelId : channel.id , userId : msg.user_id , member } ) ;
2019-03-16 12:32:25 +02:00
if ( ! isAffected ) return thisMsgLock . unlock ( ) ;
2018-12-15 17:04:04 +02:00
2019-03-16 12:32:25 +02:00
// Delete any extra messages sent after a slowmode was already applied
const userHasSlowmode = await this . slowmodes . userHasSlowmode ( channel . id , msg . user_id ) ;
2018-12-15 17:04:04 +02:00
if ( userHasSlowmode ) {
2019-03-16 12:32:25 +02:00
const message = await channel . getMessage ( msg . id ) ;
if ( message ) {
message . delete ( ) ;
return thisMsgLock . interrupt ( ) ;
}
return thisMsgLock . unlock ( ) ;
2018-12-15 17:04:04 +02:00
}
2019-03-16 12:32:25 +02:00
await this . applyBotSlowmodeToUserId ( channel , msg . user_id ) ;
thisMsgLock . unlock ( ) ;
2018-12-15 17:04:04 +02:00
}
/ * *
2019-03-16 11:26:50 +02:00
* Clears all expired bot - maintained user slowmodes in this guild
2018-12-15 17:04:04 +02:00
* /
async clearExpiredSlowmodes() {
const expiredSlowmodeUsers = await this . slowmodes . getExpiredSlowmodeUsers ( ) ;
for ( const user of expiredSlowmodeUsers ) {
const channel = this . guild . channels . get ( user . channel_id ) ;
if ( ! channel ) {
await this . slowmodes . clearSlowmodeUser ( user . channel_id , user . user_id ) ;
continue ;
}
2019-05-11 06:22:26 +03:00
try {
await this . clearBotSlowmodeFromUserId ( channel as GuildChannel & TextChannel , user . user_id ) ;
} catch ( e ) {
logger . error ( e ) ;
const realUser = this . bot . users . get ( user . user_id ) || new UnknownUser ( { id : user.user_id } ) ;
this . logs . log ( LogType . BOT_ALERT , {
body : ` Failed to clear slowmode permissions from {userMention(user)} in {channelMention(channel)} ` ,
user : stripObjectToScalars ( realUser ) ,
channel : stripObjectToScalars ( channel ) ,
} ) ;
}
2018-12-15 17:04:04 +02:00
}
}
}