Skip to content

Commit

Permalink
feat(lockdown): Add lockdown command
Browse files Browse the repository at this point in the history
  • Loading branch information
Marco (Valandur) committed Dec 1, 2019
1 parent 1fb4b5f commit 8c8d286
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 8 deletions.
4 changes: 4 additions & 0 deletions i18n/bot/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,10 @@
"reactionRole": {
"noMessageFoundInDatabase": "cmd.reactionRole.noMessageFoundInDatabase",
"unknownEmoji": "Unknown emoji. The bot must be in the guild where the emoji comes from!"
},
"lockdown": {
"channelUnlocked": "cmd.lockdown.channelUnlocked",
"channelLockedDown": "cmd.lockdown.channelLockedDown"
}
},
"JOIN_LEAVE_EMBEDS_IS_PREMIUM": "Using an embed as your join/leave message is a premium feature. Premium does not seem to be active on this server. Please contact us to learn more about how to purchase premium.",
Expand Down
3 changes: 2 additions & 1 deletion src/framework/models/ScheduledAction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export enum ScheduledActionType {
unmute = 'unmute'
unmute = 'unmute',
unlock = 'unlock'
}

export class ScheduledAction {
Expand Down
9 changes: 7 additions & 2 deletions src/framework/services/DatabaseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { PremiumSubscription } from '../models/PremiumSubscription';
import { PremiumSubscriptionGuild } from '../models/PremiumSubscriptionGuild';
import { Role } from '../models/Role';
import { RolePermission } from '../models/RolePermission';
import { ScheduledAction } from '../models/ScheduledAction';
import { ScheduledAction, ScheduledActionType } from '../models/ScheduledAction';

const GLOBAL_SHARD_ID = 0;

Expand Down Expand Up @@ -819,8 +819,13 @@ export class DatabaseService {
public async getScheduledAction(guildId: string, id: number) {
return this.findOne<ScheduledAction>(guildId, TABLE.scheduledActions, '`id` = ?', [id]);
}
public async getScheduledActionsForGuildByType(guildId: string, type: ScheduledActionType) {
return this.findMany<ScheduledAction>(guildId, TABLE.scheduledActions, '`guildId` = ? AND `actionType` = ?', [
guildId,
type
]);
}
public async getScheduledActionsForGuilds(guildIds: string[]) {
// TODO: Fix
return this.findManyOnAllShards<ScheduledAction>(TABLE.scheduledActions, '`guildId` IN (?)', guildIds);
}
public async saveScheduledAction(action: Partial<ScheduledAction>) {
Expand Down
27 changes: 22 additions & 5 deletions src/framework/services/Scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export class SchedulerService {
this.client = client;
this.scheduledActionTimers = new Map();
this.scheduledActionFunctions = {
[ScheduledActionType.unmute]: (g, a) => this.unmute(g, a)
[ScheduledActionType.unmute]: (g, a) => this.unmute(g, a),
[ScheduledActionType.unlock]: null
};
}

Expand All @@ -39,7 +40,13 @@ export class SchedulerService {
reason: reason
});
const action = await this.client.db.getScheduledAction(guildId, newId);
this.createTimer(action);
if (action.date !== null) {
this.createTimer(action);
}
}

public async getScheduledActionsOfType(guildId: string, type: ScheduledActionType) {
return this.client.db.getScheduledActionsForGuildByType(guildId, type);
}

private createTimer(action: ScheduledAction) {
Expand All @@ -52,7 +59,10 @@ export class SchedulerService {
}

try {
await this.scheduledActionFunctions[action.actionType](guild, action.args);
const scheduledFunc = this.scheduledActionFunctions[action.actionType];
if (scheduledFunc) {
await scheduledFunc(guild, action.args);
}
await this.client.db.removeScheduledAction(action.guildId, action.id);
} catch (error) {
withScope(scope => {
Expand All @@ -66,16 +76,19 @@ export class SchedulerService {
this.scheduledActionTimers.set(action.id, timer);
}

private async removeTimer(actionId: number) {
public async removeScheduledAction(guildId: string, actionId: number) {
const timer = this.scheduledActionTimers.get(actionId);
if (timer) {
clearTimeout(timer);
this.scheduledActionTimers.delete(actionId);
}

await this.client.db.removeScheduledAction(guildId, actionId);
}

private async scheduleScheduledActions() {
const actions = await this.client.db.getScheduledActionsForGuilds(this.client.guilds.map(g => g.id));
let actions = await this.client.db.getScheduledActionsForGuilds(this.client.guilds.map(g => g.id));
actions = actions.filter(a => a.date !== null);
console.log(`Scheduling ${actions.length} actions from db`);
actions.forEach(action => this.createTimer(action));
}
Expand All @@ -98,4 +111,8 @@ export class SchedulerService {

await member.removeRole(roleId, 'Timed unmute');
}

private async unlock(guild: Guild, { channelId, roleId }: { channelId: string; roleId: string }) {
console.log('SCHEDULED TASK: UNLOCK', guild.id, channelId, roleId);
}
}
125 changes: 125 additions & 0 deletions src/moderation/commands/mod/lockdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { Channel, Message, PermissionOverwrite, Role, TextChannel } from 'eris';
import { Duration } from 'moment';

import { IMClient } from '../../../client';
import { Command, Context } from '../../../framework/commands/Command';
import { ScheduledActionType } from '../../../framework/models/ScheduledAction';
import { ChannelResolver, DurationResolver } from '../../../framework/resolvers';
import { CommandGroup, ModerationCommand } from '../../../types';

const SEND_MESSAGES = 0x00000800;
const NOT_SEND_MESSAGES = 0x7ffff7ff;

// tslint:disable: no-bitwise
export default class extends Command {
public constructor(client: IMClient) {
super(client, {
name: ModerationCommand.lockdown,
aliases: [],
args: [
{
name: 'channel',
resolver: ChannelResolver,
required: false
}
],
flags: [
{
name: 'timeout',
resolver: DurationResolver,
short: 't'
}
],
group: CommandGroup.Moderation,
defaultAdminOnly: true,
guildOnly: true
});
}

public async action(
message: Message,
[channel]: [Channel],
{ timeout }: { timeout: Duration },
{ guild, me, settings, t }: Context
): Promise<any> {
channel = channel || message.channel;

if (!(channel instanceof TextChannel)) {
await this.sendReply(message, t('cmd.lockdown.notATextChannel'));
return;
}

// Get lowest role that has write permissions
const scheduledUnlockActions = await this.client.scheduler.getScheduledActionsOfType(
guild.id,
ScheduledActionType.unlock
);
const scheduledUnlockAction = scheduledUnlockActions.find(action => action.args.channelId === channel.id);

if (scheduledUnlockAction) {
const override = channel.permissionOverwrites.get(scheduledUnlockAction.args.roleId);
const newAllow = scheduledUnlockAction.args.wasAllowed ? SEND_MESSAGES : 0;
await this.client.editChannelPermission(
scheduledUnlockAction.args.channelId,
scheduledUnlockAction.args.roleId,
override ? override.allow | newAllow : newAllow,
override ? override.deny & NOT_SEND_MESSAGES : 0,
'role',
'Channel lockdown'
);
await this.client.scheduler.removeScheduledAction(guild.id, scheduledUnlockAction.id);

await this.sendReply(message, t('cmd.lockdown.channelUnlocked'));

return;
}

let lowestRole: Role | null = null;
let lowestOverride: PermissionOverwrite | null = null;
for (const [id, value] of channel.permissionOverwrites) {
if (value.type === 'member') {
continue;
}

if ((value.deny & SEND_MESSAGES) === SEND_MESSAGES) {
continue;
}

const role = guild.roles.get(id);
if (lowestRole && lowestRole.position < role.position) {
continue;
}

lowestRole = role;
lowestOverride = value;
}

if (!lowestRole) {
await this.sendReply(message, t('cmd.lockdown.noSuitingRoleFound'));
}

await this.client.scheduler.addScheduledAction(
guild.id,
ScheduledActionType.unlock,
{
channelId: channel.id,
roleId: lowestRole.id,
wasAllowed: !!(lowestOverride.allow & SEND_MESSAGES)
},
null,
'Unlock from timed `!lockdown` command'
);

await this.client.editChannelPermission(channel.id, me.id, SEND_MESSAGES, 0, 'member', 'Channel lockdown');
await this.client.editChannelPermission(
channel.id,
lowestRole.id,
lowestOverride.allow & NOT_SEND_MESSAGES,
lowestOverride.deny | SEND_MESSAGES,
'role',
'Channel lockdown'
);

await this.sendReply(message, t('cmd.lockdown.channelLockedDown'));
}
}
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export enum ModerationCommand {
unhoist = 'unhoist',
unmute = 'unmute',
warn = 'warn',
lockdown = 'lockdown',

clean = 'clean',
cleanText = 'cleanText',
Expand Down

0 comments on commit 8c8d286

Please sign in to comment.