Merge branch 'DarkView-knub30' into knub30
This commit is contained in:
commit
ae2f2516eb
17 changed files with 389 additions and 1 deletions
|
@ -3,6 +3,7 @@ import { guildPlugins } from "../plugins/availablePlugins";
|
|||
import { notFound } from "./responses";
|
||||
import { indentLines } from "../utils";
|
||||
import { getPluginName } from "knub/dist/plugins/pluginUtils";
|
||||
import { ZeppelinPluginBlueprint } from "src/plugins/ZeppelinPluginBlueprint";
|
||||
|
||||
function formatConfigSchema(schema) {
|
||||
if (schema._tag === "InterfaceType" || schema._tag === "PartialType") {
|
||||
|
@ -46,7 +47,8 @@ export function initDocs(app: express.Express) {
|
|||
});
|
||||
|
||||
app.get("/docs/plugins/:pluginName", (req: express.Request, res: express.Response) => {
|
||||
const plugin = docsPlugins.find(obj => getPluginName(obj) === req.params.pluginName);
|
||||
// prettier-ignore
|
||||
const plugin = docsPlugins.find(obj => getPluginName(obj) === req.params.pluginName) as ZeppelinPluginBlueprint<any>;
|
||||
if (!plugin) {
|
||||
return notFound(res);
|
||||
}
|
||||
|
|
66
backend/src/plugins/LocateUser/LocateUserPlugin.ts
Normal file
66
backend/src/plugins/LocateUser/LocateUserPlugin.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { PluginOptions } from "knub";
|
||||
import { LocateUserPluginType, ConfigSchema } from "./types";
|
||||
import { zeppelinPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { GuildVCAlerts } from "src/data/GuildVCAlerts";
|
||||
import { outdatedAlertsLoop } from "./utils/outdatedLoop";
|
||||
import { fillActiveAlertsList } from "./utils/fillAlertsList";
|
||||
import { WhereCmd } from "./commands/WhereCmd";
|
||||
import { FollowCmd } from "./commands/FollowCmd";
|
||||
import { ListFollowCmd, DeleteFollowCmd } from "./commands/ListFollowCmd";
|
||||
import { ChannelJoinEvt, ChannelSwitchEvt } from "./events/ChannelJoinEvt";
|
||||
import { ChannelLeaveEvt } from "./events/ChannelLeaveEvt";
|
||||
import { GuildBanAddEvt } from "./events/GuildBanAddEvt";
|
||||
|
||||
const defaultOptions: PluginOptions<LocateUserPluginType> = {
|
||||
config: {
|
||||
can_where: false,
|
||||
can_alert: false,
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
level: ">=50",
|
||||
config: {
|
||||
can_where: true,
|
||||
can_alert: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const LocateUserPlugin = zeppelinPlugin<LocateUserPluginType>()("locate_user", {
|
||||
configSchema: ConfigSchema,
|
||||
defaultOptions,
|
||||
|
||||
// prettier-ignore
|
||||
commands: [
|
||||
WhereCmd,
|
||||
FollowCmd,
|
||||
ListFollowCmd,
|
||||
DeleteFollowCmd,
|
||||
],
|
||||
|
||||
// prettier-ignore
|
||||
events: [
|
||||
ChannelJoinEvt,
|
||||
ChannelSwitchEvt,
|
||||
ChannelLeaveEvt,
|
||||
GuildBanAddEvt
|
||||
],
|
||||
|
||||
onLoad(pluginData) {
|
||||
const { state, guild } = pluginData;
|
||||
|
||||
state.alerts = GuildVCAlerts.getGuildInstance(guild.id);
|
||||
state.outdatedAlertsTimeout = null;
|
||||
state.usersWithAlerts = [];
|
||||
state.unloaded = false;
|
||||
|
||||
outdatedAlertsLoop(pluginData);
|
||||
fillActiveAlertsList(pluginData);
|
||||
},
|
||||
|
||||
onUnload(pluginData) {
|
||||
clearTimeout(pluginData.state.outdatedAlertsTimeout);
|
||||
pluginData.state.unloaded = true;
|
||||
},
|
||||
});
|
63
backend/src/plugins/LocateUser/commands/FollowCmd.ts
Normal file
63
backend/src/plugins/LocateUser/commands/FollowCmd.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { locateUserCommand } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import moment from "moment-timezone";
|
||||
import humanizeDuration from "humanize-duration";
|
||||
import { MINUTES, SECONDS } from "src/utils";
|
||||
import { sendSuccessMessage } from "src/pluginUtils";
|
||||
|
||||
export const FollowCmd = locateUserCommand({
|
||||
trigger: ["follow", "f"],
|
||||
description: "Sets up an alert that notifies you any time `<member>` switches or joins voice channels",
|
||||
usage: "!f 108552944961454080",
|
||||
permission: "can_alert",
|
||||
|
||||
signature: {
|
||||
member: ct.resolvedMember(),
|
||||
reminder: ct.string({ required: false, rest: true }),
|
||||
|
||||
duration: ct.delay({ option: true, shortcut: "d" }),
|
||||
active: ct.bool({ option: true, shortcut: "a" }),
|
||||
},
|
||||
|
||||
async run({ message: msg, args, pluginData }) {
|
||||
const time = args.duration || 10 * MINUTES;
|
||||
const alertTime = moment().add(time, "millisecond");
|
||||
const body = args.reminder || "None";
|
||||
const active = args.active || false;
|
||||
|
||||
if (time < 30 * SECONDS) {
|
||||
this.sendErrorMessage(msg.channel, "Sorry, but the minimum duration for an alert is 30 seconds!");
|
||||
return;
|
||||
}
|
||||
|
||||
await pluginData.state.alerts.add(
|
||||
msg.author.id,
|
||||
args.member.id,
|
||||
msg.channel.id,
|
||||
alertTime.format("YYYY-MM-DD HH:mm:ss"),
|
||||
body,
|
||||
active,
|
||||
);
|
||||
if (!pluginData.state.usersWithAlerts.includes(args.member.id)) {
|
||||
pluginData.state.usersWithAlerts.push(args.member.id);
|
||||
}
|
||||
|
||||
if (active) {
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
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 {
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`Every time ${args.member.mention} joins or switches VC in the next ${humanizeDuration(
|
||||
time,
|
||||
)} i will notify you`,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
57
backend/src/plugins/LocateUser/commands/ListFollowCmd.ts
Normal file
57
backend/src/plugins/LocateUser/commands/ListFollowCmd.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { locateUserCommand } from "../types";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "src/pluginUtils";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { sorter, createChunkedMessage } from "src/utils";
|
||||
|
||||
export const ListFollowCmd = locateUserCommand({
|
||||
trigger: ["follows", "fs"],
|
||||
description: "Displays all of your active alerts ordered by expiration time",
|
||||
usage: "!fs",
|
||||
permission: "can_alert",
|
||||
|
||||
async run({ message: msg, pluginData }) {
|
||||
const alerts = await pluginData.state.alerts.getAlertsByRequestorId(msg.member.id);
|
||||
if (alerts.length === 0) {
|
||||
sendErrorMessage(pluginData, msg.channel, "You have no active alerts!");
|
||||
return;
|
||||
}
|
||||
|
||||
alerts.sort(sorter("expires_at"));
|
||||
const longestNum = (alerts.length + 1).toString().length;
|
||||
const lines = Array.from(alerts.entries()).map(([i, alert]) => {
|
||||
const num = i + 1;
|
||||
const paddedNum = num.toString().padStart(longestNum, " ");
|
||||
return `\`${paddedNum}.\` \`${alert.expires_at}\` **Target:** <@!${alert.user_id}> **Reminder:** \`${
|
||||
alert.body
|
||||
}\` **Active:** ${alert.active.valueOf()}`;
|
||||
});
|
||||
await createChunkedMessage(msg.channel, lines.join("\n"));
|
||||
},
|
||||
});
|
||||
|
||||
export const DeleteFollowCmd = locateUserCommand({
|
||||
trigger: ["follows delete", "fs d"],
|
||||
description:
|
||||
"Deletes the alert at the position <num>.\nThe value needed for <num> can be found using `!follows` (`!fs`)",
|
||||
usage: "!fs d <num>",
|
||||
permission: "can_alert",
|
||||
|
||||
signature: {
|
||||
num: ct.number({ required: true }),
|
||||
},
|
||||
|
||||
async run({ message: msg, args, pluginData }) {
|
||||
const alerts = await pluginData.state.alerts.getAlertsByRequestorId(msg.member.id);
|
||||
alerts.sort(sorter("expires_at"));
|
||||
|
||||
if (args.num > alerts.length || args.num <= 0) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Unknown alert!");
|
||||
return;
|
||||
}
|
||||
|
||||
const toDelete = alerts[args.num - 1];
|
||||
await pluginData.state.alerts.delete(toDelete.id);
|
||||
|
||||
sendSuccessMessage(pluginData, msg.channel, "Alert deleted");
|
||||
},
|
||||
});
|
20
backend/src/plugins/LocateUser/commands/WhereCmd.ts
Normal file
20
backend/src/plugins/LocateUser/commands/WhereCmd.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { locateUserCommand } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { resolveMember } from "src/utils";
|
||||
import { sendWhere } from "../utils/sendWhere";
|
||||
|
||||
export const WhereCmd = locateUserCommand({
|
||||
trigger: ["where", "w"],
|
||||
description: "Posts an instant invite to the voice channel that `<member>` is in",
|
||||
usage: "!w 108552944961454080",
|
||||
permission: "can_where",
|
||||
|
||||
signature: {
|
||||
member: ct.resolvedMember(),
|
||||
},
|
||||
|
||||
async run({ message: msg, args, pluginData }) {
|
||||
const member = await resolveMember(pluginData.client, pluginData.guild, args.member.id);
|
||||
sendWhere.call(this, pluginData.guild, member, msg.channel, `${msg.member.mention} | `);
|
||||
},
|
||||
});
|
22
backend/src/plugins/LocateUser/events/ChannelJoinEvt.ts
Normal file
22
backend/src/plugins/LocateUser/events/ChannelJoinEvt.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { locateUserEvent } from "../types";
|
||||
import { sendAlerts } from "../utils/sendAlerts";
|
||||
|
||||
export const ChannelJoinEvt = locateUserEvent({
|
||||
event: "voiceChannelJoin",
|
||||
|
||||
async listener(meta) {
|
||||
if (meta.pluginData.state.usersWithAlerts.includes(meta.args.member.id)) {
|
||||
sendAlerts(meta.pluginData, meta.args.member.id);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const ChannelSwitchEvt = locateUserEvent({
|
||||
event: "voiceChannelSwitch",
|
||||
|
||||
async listener(meta) {
|
||||
if (meta.pluginData.state.usersWithAlerts.includes(meta.args.member.id)) {
|
||||
sendAlerts(meta.pluginData, meta.args.member.id);
|
||||
}
|
||||
},
|
||||
});
|
19
backend/src/plugins/LocateUser/events/ChannelLeaveEvt.ts
Normal file
19
backend/src/plugins/LocateUser/events/ChannelLeaveEvt.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { locateUserEvent } from "../types";
|
||||
import { sendAlerts } from "../utils/sendAlerts";
|
||||
import { VoiceChannel, TextableChannel } from "eris";
|
||||
|
||||
export const ChannelLeaveEvt = locateUserEvent({
|
||||
event: "voiceChannelLeave",
|
||||
|
||||
async listener(meta) {
|
||||
const triggeredAlerts = await meta.pluginData.state.alerts.getAlertsByUserId(meta.args.member.id);
|
||||
const voiceChannel = meta.args.oldChannel as VoiceChannel;
|
||||
|
||||
triggeredAlerts.forEach(alert => {
|
||||
const txtChannel = meta.pluginData.client.getChannel(alert.channel_id) as TextableChannel;
|
||||
txtChannel.createMessage(
|
||||
`🔴 <@!${alert.requestor_id}> the user <@!${alert.user_id}> disconnected out of \`${voiceChannel.name}\``,
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
12
backend/src/plugins/LocateUser/events/GuildBanAddEvt.ts
Normal file
12
backend/src/plugins/LocateUser/events/GuildBanAddEvt.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { locateUserEvent } from "../types";
|
||||
|
||||
export const GuildBanAddEvt = locateUserEvent({
|
||||
event: "guildBanAdd",
|
||||
|
||||
async listener(meta) {
|
||||
const alerts = await meta.pluginData.state.alerts.getAlertsByUserId(meta.args.user.id);
|
||||
alerts.forEach(alert => {
|
||||
meta.pluginData.state.alerts.delete(alert.id);
|
||||
});
|
||||
},
|
||||
});
|
15
backend/src/plugins/LocateUser/types.ts
Normal file
15
backend/src/plugins/LocateUser/types.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import * as t from "io-ts";
|
||||
import { BasePluginType, command, eventListener } from "knub";
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
can_where: t.boolean,
|
||||
can_alert: t.boolean,
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface LocateUserPluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
}
|
||||
|
||||
export const locateUserCommand = command<LocateUserPluginType>();
|
||||
export const locateUserEvent = eventListener<LocateUserPluginType>();
|
11
backend/src/plugins/LocateUser/utils/createOrReuseInvite.ts
Normal file
11
backend/src/plugins/LocateUser/utils/createOrReuseInvite.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { VoiceChannel } from "eris";
|
||||
|
||||
export async function createOrReuseInvite(vc: VoiceChannel) {
|
||||
const existingInvites = await vc.getInvites();
|
||||
|
||||
if (existingInvites.length !== 0) {
|
||||
return existingInvites[0];
|
||||
} else {
|
||||
return vc.createInvite(undefined);
|
||||
}
|
||||
}
|
9
backend/src/plugins/LocateUser/utils/fillAlertsList.ts
Normal file
9
backend/src/plugins/LocateUser/utils/fillAlertsList.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export async function fillActiveAlertsList(pluginData) {
|
||||
const allAlerts = await pluginData.state.alerts.getAllGuildAlerts();
|
||||
|
||||
allAlerts.forEach(alert => {
|
||||
if (!pluginData.state.usersWithAlerts.includes(alert.user_id)) {
|
||||
pluginData.state.usersWithAlerts.push(alert.user_id);
|
||||
}
|
||||
});
|
||||
}
|
25
backend/src/plugins/LocateUser/utils/moveMember.ts
Normal file
25
backend/src/plugins/LocateUser/utils/moveMember.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { Member, TextableChannel } from "eris";
|
||||
import { PluginData } from "knub";
|
||||
import { LocateUserPluginType } from "../types";
|
||||
import { sendErrorMessage } from "src/pluginUtils";
|
||||
|
||||
export async function moveMember(
|
||||
pluginData: PluginData<LocateUserPluginType>,
|
||||
toMoveID: string,
|
||||
target: Member,
|
||||
errorChannel: TextableChannel,
|
||||
) {
|
||||
const modMember: Member = await this.bot.getRESTGuildMember(pluginData.guild.id, toMoveID);
|
||||
if (modMember.voiceState.channelID != null) {
|
||||
try {
|
||||
await modMember.edit({
|
||||
channelID: target.voiceState.channelID,
|
||||
});
|
||||
} catch (e) {
|
||||
sendErrorMessage(pluginData, errorChannel, "Failed to move you. Are you in a voice channel?");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
sendErrorMessage(pluginData, errorChannel, "Failed to move you. Are you in a voice channel?");
|
||||
}
|
||||
}
|
17
backend/src/plugins/LocateUser/utils/outdatedLoop.ts
Normal file
17
backend/src/plugins/LocateUser/utils/outdatedLoop.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { SECONDS } from "src/utils";
|
||||
import { removeUserIdFromActiveAlerts } from "./removeUserIdFromActiveAlerts";
|
||||
|
||||
const ALERT_LOOP_TIME = 30 * SECONDS;
|
||||
|
||||
export async function outdatedAlertsLoop(pluginData) {
|
||||
const outdatedAlerts = await pluginData.state.alerts.getOutdatedAlerts();
|
||||
|
||||
for (const alert of outdatedAlerts) {
|
||||
await pluginData.state.alerts.delete(alert.id);
|
||||
await removeUserIdFromActiveAlerts(pluginData, alert.user_id);
|
||||
}
|
||||
|
||||
if (!pluginData.state.unloaded) {
|
||||
pluginData.state.outdatedAlertsTimeout = setTimeout(() => this.outdatedAlertsLoop(pluginData), ALERT_LOOP_TIME);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export async function removeUserIdFromActiveAlerts(pluginData, userId: string) {
|
||||
const index = pluginData.state.usersWithAlerts.indexOf(userId);
|
||||
if (index > -1) {
|
||||
pluginData.state.usersWithAlerts.splice(index, 1);
|
||||
}
|
||||
}
|
20
backend/src/plugins/LocateUser/utils/sendAlerts.ts
Normal file
20
backend/src/plugins/LocateUser/utils/sendAlerts.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { PluginData } from "knub";
|
||||
import { LocateUserPluginType } from "../types";
|
||||
import { resolveMember } from "src/utils";
|
||||
import { sendWhere } from "./sendWhere";
|
||||
import { TextableChannel } from "eris";
|
||||
import { moveMember } from "./moveMember";
|
||||
|
||||
export async function sendAlerts(pluginData: PluginData<LocateUserPluginType>, userId: string) {
|
||||
const triggeredAlerts = await pluginData.state.alerts.getAlertsByUserId(userId);
|
||||
const member = await resolveMember(pluginData.client, pluginData.guild, userId);
|
||||
|
||||
triggeredAlerts.forEach(alert => {
|
||||
const prepend = `<@!${alert.requestor_id}>, an alert requested by you has triggered!\nReminder: \`${alert.body}\`\n`;
|
||||
const txtChannel = pluginData.client.getChannel(alert.channel_id) as TextableChannel;
|
||||
sendWhere.call(this, pluginData.guild, member, txtChannel, prepend);
|
||||
if (alert.active) {
|
||||
moveMember(pluginData, alert.requestor_id, member, txtChannel);
|
||||
}
|
||||
});
|
||||
}
|
22
backend/src/plugins/LocateUser/utils/sendWhere.ts
Normal file
22
backend/src/plugins/LocateUser/utils/sendWhere.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { Guild, Member, TextableChannel, VoiceChannel } from "eris";
|
||||
import { getInviteLink } from "knub/dist/helpers";
|
||||
import { createOrReuseInvite } from "./createOrReuseInvite";
|
||||
|
||||
export async function sendWhere(guild: Guild, member: Member, channel: TextableChannel, prepend: string) {
|
||||
const voice = guild.channels.get(member.voiceState.channelID) as VoiceChannel;
|
||||
|
||||
if (voice == null) {
|
||||
channel.createMessage(prepend + "That user is not in a channel");
|
||||
} else {
|
||||
let invite = null;
|
||||
try {
|
||||
invite = await createOrReuseInvite(voice);
|
||||
} catch (e) {
|
||||
this.sendErrorMessage(channel, "Cannot create an invite to that channel!");
|
||||
return;
|
||||
}
|
||||
channel.createMessage(
|
||||
prepend + `${member.mention} is in the following channel: \`${voice.name}\` ${getInviteLink(invite)}`,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
import { UtilityPlugin } from "./Utility/UtilityPlugin";
|
||||
import { LocateUserPlugin } from "./LocateUser/LocateUserPlugin";
|
||||
|
||||
// prettier-ignore
|
||||
export const guildPlugins = [
|
||||
LocateUserPlugin,
|
||||
UtilityPlugin,
|
||||
];
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue