Utility: add !vcmove command

This commit is contained in:
Dragory 2019-02-17 16:45:29 +02:00
parent 30f34747d4
commit 4f5eb0689d
2 changed files with 98 additions and 0 deletions

View file

@ -1,10 +1,13 @@
import { decorators as d } from "knub"; import { decorators as d } from "knub";
import { Channel, EmbedOptions, Member, Message, Role, TextChannel, User, VoiceChannel } from "eris"; import { Channel, EmbedOptions, Member, Message, Role, TextChannel, User, VoiceChannel } from "eris";
import { import {
channelMentionRegex,
chunkArray, chunkArray,
embedPadding, embedPadding,
errorMessage, errorMessage,
isSnowflake,
noop, noop,
simpleClosestStringMatch,
stripObjectToScalars, stripObjectToScalars,
successMessage, successMessage,
trimLines, trimLines,
@ -49,6 +52,7 @@ export class UtilityPlugin extends ZeppelinPlugin {
nickname: false, nickname: false,
ping: false, ping: false,
source: false, source: false,
vcmove: false,
}, },
overrides: [ overrides: [
{ {
@ -61,6 +65,7 @@ export class UtilityPlugin extends ZeppelinPlugin {
info: true, info: true,
server: true, server: true,
nickname: true, nickname: true,
vcmove: true,
}, },
}, },
{ {
@ -553,6 +558,63 @@ export class UtilityPlugin extends ZeppelinPlugin {
msg.channel.createMessage(`Message source: ${url}`); msg.channel.createMessage(`Message source: ${url}`);
} }
@d.command("vcmove", "<member:Member> <channel:string$>")
@d.permission("vcmove")
async vcmoveCmd(msg: Message, args: { member: Member; channel: string }) {
let channel: VoiceChannel;
if (isSnowflake(args.channel)) {
// Snowflake -> resolve channel directly
const potentialChannel = this.guild.channels.get(args.channel);
if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) {
msg.channel.createMessage(errorMessage("Unknown or non-voice channel"));
return;
}
channel = potentialChannel;
} else if (channelMentionRegex.test(args.channel)) {
// Channel mention -> parse channel id and resolve channel from that
const channelId = args.channel.match(channelMentionRegex)[1];
const potentialChannel = this.guild.channels.get(channelId);
if (!potentialChannel || !(potentialChannel instanceof VoiceChannel)) {
msg.channel.createMessage(errorMessage("Unknown or non-voice channel"));
return;
}
channel = potentialChannel;
} else {
// Search string -> find closest matching voice channel name
const voiceChannels = this.guild.channels.filter(theChannel => {
return theChannel instanceof VoiceChannel;
}) as VoiceChannel[];
const closestMatch = simpleClosestStringMatch(args.channel, voiceChannels, ch => ch.name);
if (!closestMatch) {
msg.channel.createMessage(errorMessage("No matching voice channels"));
return;
}
channel = closestMatch;
}
if (!args.member.voiceState || !args.member.voiceState.channelID) {
msg.channel.createMessage(errorMessage("Member is not in a voice channel"));
return;
}
try {
await args.member.edit({
channelID: channel.id,
});
} catch (e) {
msg.channel.createMessage(errorMessage("Failed to move member"));
return;
}
msg.channel.createMessage(
successMessage(`**${args.member.user.username}#${args.member.user.discriminator}** moved to **${channel.name}**`),
);
}
@d.command("reload_guild") @d.command("reload_guild")
@d.permission("reload_guild") @d.permission("reload_guild")
reloadGuildCmd(msg: Message) { reloadGuildCmd(msg: Message) {

View file

@ -204,6 +204,7 @@ export const embedPadding = "\n" + emptyEmbedValue;
export const userMentionRegex = /<@!?([0-9]+)>/g; export const userMentionRegex = /<@!?([0-9]+)>/g;
export const roleMentionRegex = /<@&([0-9]+)>/g; export const roleMentionRegex = /<@&([0-9]+)>/g;
export const channelMentionRegex = /<#([0-9]+)>/g;
export function getUserMentions(str: string) { export function getUserMentions(str: string) {
const regex = new RegExp(userMentionRegex.source, "g"); const regex = new RegExp(userMentionRegex.source, "g");
@ -347,6 +348,41 @@ export function downloadFile(attachmentUrl: string, retries = 3): Promise<{ path
}); });
} }
type ItemWithRanking<T> = [T, number];
export function simpleClosestStringMatch<T>(searchStr, haystack: T[], getter = null): T {
const normalizedSearchStr = searchStr.toLowerCase();
// See if any haystack item contains a part of the search string
const itemsWithRankings: Array<ItemWithRanking<T>> = haystack.map(item => {
const itemStr: string = getter ? getter(item) : item;
const normalizedItemStr = itemStr.toLowerCase();
let i = 0;
do {
if (!normalizedItemStr.includes(normalizedSearchStr.slice(0, i + 1))) break;
i++;
} while (i < normalizedSearchStr.length);
if (i > 0 && normalizedItemStr.startsWith(normalizedSearchStr.slice(0, i))) {
// Slightly prioritize items that *start* with the search string
i += 0.5;
}
return [item, i] as ItemWithRanking<T>;
});
// Sort by best match
itemsWithRankings.sort((a, b) => {
return a[1] > b[1] ? -1 : 1;
});
if (itemsWithRankings[0][1] === 0) {
return null;
}
return itemsWithRankings[0][0];
}
export function noop() { export function noop() {
// IT'S LITERALLY NOTHING // IT'S LITERALLY NOTHING
} }