From 5afe3ce3fe18e8044d7e1effd8d30e37f97a48d0 Mon Sep 17 00:00:00 2001 From: Dark <7890309+DarkView@users.noreply.github.com> Date: Sun, 2 Feb 2020 17:01:03 +0100 Subject: [PATCH] Made Alerts infinite until timed out, added -active and changed cmd sigm --- backend/src/data/GuildVCAlerts.ts | 3 +- backend/src/data/entities/VCAlert.ts | 2 + ...0654617890-AddActiveFollowsToLocateUser.ts | 18 ++ backend/src/plugins/LocateUser.ts | 163 +++++++++++++++--- 4 files changed, 159 insertions(+), 27 deletions(-) create mode 100644 backend/src/migrations/1580654617890-AddActiveFollowsToLocateUser.ts diff --git a/backend/src/data/GuildVCAlerts.ts b/backend/src/data/GuildVCAlerts.ts index 9da16dec..6acc35e9 100644 --- a/backend/src/data/GuildVCAlerts.ts +++ b/backend/src/data/GuildVCAlerts.ts @@ -50,7 +50,7 @@ export class GuildVCAlerts extends BaseGuildRepository { }); } - async add(requestorId: string, userId: string, channelId: string, expiresAt: string, body: string) { + async add(requestorId: string, userId: string, channelId: string, expiresAt: string, body: string, active: boolean) { await this.allAlerts.insert({ guild_id: this.guildId, requestor_id: requestorId, @@ -58,6 +58,7 @@ export class GuildVCAlerts extends BaseGuildRepository { channel_id: channelId, expires_at: expiresAt, body, + active, }); } } diff --git a/backend/src/data/entities/VCAlert.ts b/backend/src/data/entities/VCAlert.ts index 4bdf965e..e627afa6 100644 --- a/backend/src/data/entities/VCAlert.ts +++ b/backend/src/data/entities/VCAlert.ts @@ -17,4 +17,6 @@ export class VCAlert { @Column() expires_at: string; @Column() body: string; + + @Column() active: boolean; } diff --git a/backend/src/migrations/1580654617890-AddActiveFollowsToLocateUser.ts b/backend/src/migrations/1580654617890-AddActiveFollowsToLocateUser.ts new file mode 100644 index 00000000..430b10de --- /dev/null +++ b/backend/src/migrations/1580654617890-AddActiveFollowsToLocateUser.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; + +export class AddActiveFollowsToLocateUser1580654617890 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumn( + "vc_alerts", + new TableColumn({ + name: "active", + type: "boolean", + isNullable: false, + }), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn("vc_alerts", "active"); + } +} diff --git a/backend/src/plugins/LocateUser.ts b/backend/src/plugins/LocateUser.ts index c79ff368..c0b8790c 100644 --- a/backend/src/plugins/LocateUser.ts +++ b/backend/src/plugins/LocateUser.ts @@ -1,10 +1,10 @@ import { decorators as d, IPluginOptions, getInviteLink, logger } from "knub"; -import { trimPluginDescription, ZeppelinPlugin } from "./ZeppelinPlugin"; +import { trimPluginDescription, ZeppelinPlugin, CommandInfo } from "./ZeppelinPlugin"; import humanizeDuration from "humanize-duration"; -import { Message, Member, Guild, TextableChannel, VoiceChannel, Channel, User } from "eris"; +import { Message, Member, Guild, TextableChannel, VoiceChannel, Channel, User, Command } from "eris"; import { GuildVCAlerts } from "../data/GuildVCAlerts"; import moment from "moment-timezone"; -import { resolveMember, sorter, createChunkedMessage, errorMessage, successMessage, MINUTES } from "../utils"; +import { resolveMember, sorter, createChunkedMessage, MINUTES, SECONDS } from "../utils"; import * as t from "io-ts"; const ConfigSchema = t.type({ @@ -13,7 +13,7 @@ const ConfigSchema = t.type({ }); type TConfigSchema = t.TypeOf; -const ALERT_LOOP_TIME = 30 * 1000; +const ALERT_LOOP_TIME = 30 * SECONDS; export class LocatePlugin extends ZeppelinPlugin { public static pluginName = "locate_user"; @@ -29,8 +29,9 @@ export class LocatePlugin extends ZeppelinPlugin { }; private alerts: GuildVCAlerts; - private outdatedAlertsTimeout; + private outdatedAlertsTimeout: NodeJS.Timeout; private usersWithAlerts: string[] = []; + private unloaded = false; public static getStaticDefaultOptions(): IPluginOptions { return { @@ -56,6 +57,11 @@ export class LocatePlugin extends ZeppelinPlugin { this.fillActiveAlertsList(); } + onUnload() { + clearTimeout(this.outdatedAlertsTimeout); + this.unloaded = true; + } + async outdatedAlertsLoop() { const outdatedAlerts = await this.alerts.getOutdatedAlerts(); @@ -64,7 +70,9 @@ export class LocatePlugin extends ZeppelinPlugin { await this.removeUserIdFromActiveAlerts(alert.user_id); } - this.outdatedAlertsTimeout = setTimeout(() => this.outdatedAlertsLoop(), ALERT_LOOP_TIME); + if (!this.unloaded) { + this.outdatedAlertsTimeout = setTimeout(() => this.outdatedAlertsLoop(), ALERT_LOOP_TIME); + } } async fillActiveAlertsList() { @@ -80,8 +88,12 @@ export class LocatePlugin extends ZeppelinPlugin { @d.command("where", "", { aliases: ["w"], extra: { - info: { + info: { description: "Posts an instant invite to the voice channel that `` is in", + basicUsage: "!where @Dark", + parameterDescriptions: { + member: "The member that we want to find", + }, }, }, }) @@ -91,32 +103,94 @@ export class LocatePlugin extends ZeppelinPlugin { sendWhere(this.guild, member, msg.channel, `${msg.member.mention} |`); } - @d.command("vcalert", " ", { - overloads: [" ", ""], + @d.command("vcalert", " [reminder:string$]", { aliases: ["vca"], + options: [ + { + name: "duration", + shortcut: "d", + type: "delay", + }, + { + name: "active", + shortcut: "a", + isSwitch: true, + }, + ], extra: { - info: { + info: { description: "Sets up an alert that notifies you any time `` switches or joins voice channels", + basicUsage: "!vca @Dark", + examples: trimPluginDescription(` + To get an alert for 1 hour: + \`!vca 108552944961454080 -d 1h\` + + To get an alert for 2 hours and 30 minutes with the reminder "Earrape": + \`!vca 108552944961454080 -d 2h30m Earrape\` or \`!vca 108552944961454080 Earrape -d 1h\` + + To get an alert for 3 days and be moved to the channel: + \`!vca 108552944961454080 -d 3d -a\` + `), + optionDescriptions: { + duration: "How long the alert shall be active. The alert will be automatically deleted after this time", + active: " A switch that, when true, will move you to the channel the user joined", + }, + parameterDescriptions: { + member: "The server member we want to set as the alerts target", + reminder: "Any text that will be displayed every time the alert triggers", + }, }, }, }) @d.permission("can_alert") - async vcalertCmd(msg: Message, args: { member: Member; duration?: number; reminder?: string }) { + async vcalertCmd(msg: Message, args: { member: Member; reminder?: string; duration?: number; active?: boolean }) { const time = args.duration || 10 * MINUTES; const alertTime = moment().add(time, "millisecond"); const body = args.reminder || "None"; + const active = args.active || false; - this.alerts.add(msg.author.id, args.member.id, msg.channel.id, alertTime.format("YYYY-MM-DD HH:mm:ss"), body); + if (time < 30 * SECONDS) { + this.sendErrorMessage(msg.channel, "Sorry, but the minimum duration for an alert is 30 seconds!"); + return; + } + + this.alerts.add( + msg.author.id, + args.member.id, + msg.channel.id, + alertTime.format("YYYY-MM-DD HH:mm:ss"), + body, + active, + ); if (!this.usersWithAlerts.includes(args.member.id)) { this.usersWithAlerts.push(args.member.id); } - msg.channel.createMessage( - `If ${args.member.mention} joins or switches VC in the next ${humanizeDuration(time)} i will notify you`, - ); + if (active) { + this.sendSuccessMessage( + msg.channel, + `Every time ${args.member.mention} joins or switches VC in the next ${humanizeDuration( + time, + )} i will notify and move you.\nPlease make sure to be in a voice channel, otherwise i cannot move you!`, + ); + } else { + this.sendSuccessMessage( + msg.channel, + `Every time ${args.member.mention} joins or switches VC in the next ${humanizeDuration( + time, + )} i will notify you`, + ); + } } - @d.command("vcalerts") + @d.command("vcalerts", [], { + aliases: ["vca"], + extra: { + info: { + description: "Displays all of your active alerts ordered by expiration time", + }, + }, + }) @d.permission("can_alert") async listVcalertCmd(msg: Message) { const alerts = await this.alerts.getAlertsByRequestorId(msg.member.id); @@ -130,22 +204,29 @@ export class LocatePlugin extends ZeppelinPlugin { const lines = Array.from(alerts.entries()).map(([i, alert]) => { const num = i + 1; const paddedNum = num.toString().padStart(longestNum, " "); - return `\`${paddedNum}.\` \`${alert.expires_at}\` Member: <@!${alert.user_id}> Reminder: \`${alert.body}\``; + return `\`${paddedNum}.\` \`${alert.expires_at}\` Target: <@!${alert.user_id}> Reminder: \`${ + alert.body + }\` Active: ${alert.active.valueOf()}`; }); createChunkedMessage(msg.channel, lines.join("\n")); } @d.command("vcalerts delete", "", { - aliases: ["vcalerts d"], + aliases: ["vcalerts d", "vca d"], + extra: { + info: { + description: + "Deletes the alert at the position .\nThe value needed for can be found using `!vcalerts`", + }, + }, }) @d.permission("can_alert") async deleteVcalertCmd(msg: Message, args: { num: number }) { const alerts = await this.alerts.getAlertsByRequestorId(msg.member.id); alerts.sort(sorter("expires_at")); - const lastNum = alerts.length + 1; - if (args.num > lastNum || args.num < 0) { - msg.channel.createMessage(errorMessage("Unknown alert")); + if (args.num > alerts.length || args.num < 0) { + this.sendErrorMessage(msg.channel, "Unknown alert!"); return; } @@ -159,7 +240,6 @@ export class LocatePlugin extends ZeppelinPlugin { async userJoinedVC(member: Member, channel: Channel) { if (this.usersWithAlerts.includes(member.id)) { this.sendAlerts(member.id); - await this.removeUserIdFromActiveAlerts(member.id); } } @@ -167,10 +247,22 @@ export class LocatePlugin extends ZeppelinPlugin { async userSwitchedVC(member: Member, newChannel: Channel, oldChannel: Channel) { if (this.usersWithAlerts.includes(member.id)) { this.sendAlerts(member.id); - await this.removeUserIdFromActiveAlerts(member.id); } } + @d.event("voiceChannelLeave") + async userLeftVC(member: Member, channel: Channel) { + const triggeredAlerts = await this.alerts.getAlertsByUserId(member.id); + const voiceChannel = channel as VoiceChannel; + + triggeredAlerts.forEach(alert => { + const txtChannel = this.bot.getChannel(alert.channel_id) as TextableChannel; + txtChannel.createMessage( + `🔴 <@!${alert.requestor_id}> the user <@!${alert.user_id}> disconnected out of \`${voiceChannel.name}\``, + ); + }); + } + @d.event("guildBanAdd") async onGuildBanAdd(_, user: User) { const alerts = await this.alerts.getAlertsByUserId(user.id); @@ -185,8 +277,11 @@ export class LocatePlugin extends ZeppelinPlugin { triggeredAlerts.forEach(alert => { const prepend = `<@!${alert.requestor_id}>, an alert requested by you has triggered!\nReminder: \`${alert.body}\`\n`; - sendWhere(this.guild, member, this.bot.getChannel(alert.channel_id) as TextableChannel, prepend); - this.alerts.delete(alert.id); + const txtChannel = this.bot.getChannel(alert.channel_id) as TextableChannel; + sendWhere(this.guild, member, txtChannel, prepend); + if (alert.active) { + this.moveMember(alert.requestor_id, member, txtChannel); + } }); } @@ -196,6 +291,22 @@ export class LocatePlugin extends ZeppelinPlugin { this.usersWithAlerts.splice(index, 1); } } + + async moveMember(toMoveID: string, target: Member, errorChannel: TextableChannel) { + const modMember: Member = await this.bot.getRESTGuildMember(this.guildId, toMoveID); + if (modMember.voiceState.channelID != null) { + try { + await modMember.edit({ + channelID: target.voiceState.channelID, + }); + } catch (e) { + this.sendErrorMessage(errorChannel, "Failed to move you. Are you in a voice channel?"); + return; + } + } else { + this.sendErrorMessage(errorChannel, "Failed to move you. Are you in a voice channel?"); + } + } } export async function sendWhere(guild: Guild, member: Member, channel: TextableChannel, prepend: string) { @@ -212,7 +323,7 @@ export async function sendWhere(guild: Guild, member: Member, channel: TextableC return; } channel.createMessage( - prepend + ` ${member.mention} is in the following channel: ${voice.name} ${getInviteLink(invite)}`, + prepend + ` ${member.mention} is in the following channel: \`${voice.name}\` ${getInviteLink(invite)}`, ); } }