Add custom argument types for deep-resolved users/members, use these in some places. Deprecate unknownUser (constant) and replace with instances of UnknownUser (class).
This commit is contained in:
parent
2d690da92b
commit
ab83e83d42
6 changed files with 151 additions and 111 deletions
40
src/customArgumentTypes.ts
Normal file
40
src/customArgumentTypes.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { convertDelayStringToMS, resolveMember, resolveUser, UnknownUser } from "./utils";
|
||||
import { CommandArgumentTypeError } from "knub";
|
||||
import { Client, GuildChannel, Message } from "eris";
|
||||
|
||||
export const customArgumentTypes = {
|
||||
delay(value) {
|
||||
const result = convertDelayStringToMS(value);
|
||||
if (result == null) {
|
||||
throw new CommandArgumentTypeError(`Could not convert ${value} to a delay`);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
async resolvedUser(value, msg, bot: Client) {
|
||||
const result = resolveUser(bot, value);
|
||||
if (result == null || result instanceof UnknownUser) {
|
||||
throw new CommandArgumentTypeError(`User \`${value}\` was not found`);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
async resolvedUserLoose(value, msg, bot: Client) {
|
||||
const result = resolveUser(bot, value);
|
||||
if (result == null) {
|
||||
throw new CommandArgumentTypeError(`Invalid user: ${value}`);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
async resolvedMember(value, msg: Message, bot: Client) {
|
||||
if (!(msg.channel instanceof GuildChannel)) return null;
|
||||
|
||||
const result = await resolveMember(bot, msg.channel.guild, value);
|
||||
if (result == null) {
|
||||
throw new CommandArgumentTypeError(`Member \`${value}\` was not found or they have left the server`);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
16
src/index.ts
16
src/index.ts
|
@ -4,7 +4,7 @@ import yaml from "js-yaml";
|
|||
import fs from "fs";
|
||||
const fsp = fs.promises;
|
||||
|
||||
import { Knub, logger, PluginError, CommandArgumentTypeError, Plugin } from "knub";
|
||||
import { Knub, logger, PluginError, Plugin } from "knub";
|
||||
import { SimpleError } from "./SimpleError";
|
||||
|
||||
require("dotenv").config();
|
||||
|
@ -74,8 +74,9 @@ import { AutoReactionsPlugin } from "./plugins/AutoReactionsPlugin";
|
|||
import { PingableRolesPlugin } from "./plugins/PingableRolesPlugin";
|
||||
import { SelfGrantableRolesPlugin } from "./plugins/SelfGrantableRolesPlugin";
|
||||
import { RemindersPlugin } from "./plugins/Reminders";
|
||||
import { convertDelayStringToMS, errorMessage, successMessage } from "./utils";
|
||||
import { errorMessage, successMessage } from "./utils";
|
||||
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
|
||||
import { customArgumentTypes } from "./customArgumentTypes";
|
||||
|
||||
// Run latest database migrations
|
||||
logger.info("Running database migrations");
|
||||
|
@ -178,16 +179,7 @@ connect().then(async conn => {
|
|||
threshold: 200,
|
||||
},
|
||||
|
||||
customArgumentTypes: {
|
||||
delay(value) {
|
||||
const result = convertDelayStringToMS(value);
|
||||
if (result == null) {
|
||||
throw new CommandArgumentTypeError(`Could not convert ${value} to a delay`);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
},
|
||||
customArgumentTypes,
|
||||
|
||||
sendSuccessMessageFn(channel, body) {
|
||||
channel.createMessage(successMessage(body));
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
findRelevantAuditLogEntry,
|
||||
noop,
|
||||
stripObjectToScalars,
|
||||
unknownUser,
|
||||
UnknownUser,
|
||||
useMediaUrls,
|
||||
} from "../utils";
|
||||
import DefaultLogMessages from "../data/DefaultLogMessages.json";
|
||||
|
@ -256,7 +256,7 @@ export class LogsPlugin extends ZeppelinPlugin<ILogsPluginConfig> {
|
|||
ErisConstants.AuditLogActions.MEMBER_BAN_ADD,
|
||||
user.id,
|
||||
);
|
||||
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : unknownUser;
|
||||
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : new UnknownUser();
|
||||
|
||||
this.guildLogs.log(
|
||||
LogType.MEMBER_BAN,
|
||||
|
@ -275,7 +275,7 @@ export class LogsPlugin extends ZeppelinPlugin<ILogsPluginConfig> {
|
|||
ErisConstants.AuditLogActions.MEMBER_BAN_REMOVE,
|
||||
user.id,
|
||||
);
|
||||
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : unknownUser;
|
||||
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : new UnknownUser();
|
||||
|
||||
this.guildLogs.log(
|
||||
LogType.MEMBER_UNBAN,
|
||||
|
@ -308,7 +308,7 @@ export class LogsPlugin extends ZeppelinPlugin<ILogsPluginConfig> {
|
|||
ErisConstants.AuditLogActions.MEMBER_ROLE_UPDATE,
|
||||
member.id,
|
||||
);
|
||||
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : unknownUser;
|
||||
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : new UnknownUser();
|
||||
|
||||
if (addedRoles.length && removedRoles.length) {
|
||||
// Roles added *and* removed
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
stripObjectToScalars,
|
||||
successMessage,
|
||||
trimLines,
|
||||
UnknownUser,
|
||||
} from "../utils";
|
||||
import { GuildLogs } from "../data/GuildLogs";
|
||||
import { LogType } from "../data/LogType";
|
||||
|
@ -187,16 +188,10 @@ export class UtilityPlugin extends ZeppelinPlugin<IUtilityPluginConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
@d.command("level", "[userId:string]")
|
||||
@d.command("level", "[member:resolvedMember]")
|
||||
@d.permission("can_level")
|
||||
async levelCmd(msg: Message, args) {
|
||||
const member = args.userId ? this.guild.members.get(args.userId) : msg.member;
|
||||
|
||||
if (!member) {
|
||||
msg.channel.createMessage(errorMessage("Member not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
async levelCmd(msg: Message, args: { member?: Member }) {
|
||||
const member = args.member || msg.member;
|
||||
const level = this.getMemberLevel(member);
|
||||
msg.channel.createMessage(`The permission level of ${member.username}#${member.discriminator} is **${level}**`);
|
||||
}
|
||||
|
@ -413,15 +408,17 @@ export class UtilityPlugin extends ZeppelinPlugin<IUtilityPluginConfig> {
|
|||
}, CLEAN_COMMAND_DELETE_DELAY);
|
||||
}
|
||||
|
||||
@d.command("info", "<userId:userId>")
|
||||
@d.command("info", "[user:resolvedUserLoose]")
|
||||
@d.permission("can_info")
|
||||
async infoCmd(msg: Message, args: { userId: string }) {
|
||||
async infoCmd(msg: Message, args: { user?: User | UnknownUser }) {
|
||||
const user = args.user || msg.author;
|
||||
const member = user && (await this.getMember(user.id));
|
||||
|
||||
const embed: EmbedOptions = {
|
||||
fields: [],
|
||||
};
|
||||
|
||||
const user = this.bot.users.get(args.userId);
|
||||
if (user) {
|
||||
if (user && !(user instanceof UnknownUser)) {
|
||||
const createdAt = moment(user.createdAt);
|
||||
const accountAge = humanizeDuration(moment().valueOf() - user.createdAt, {
|
||||
largest: 2,
|
||||
|
@ -444,7 +441,6 @@ export class UtilityPlugin extends ZeppelinPlugin<IUtilityPluginConfig> {
|
|||
embed.title = `Unknown user`;
|
||||
}
|
||||
|
||||
const member = this.guild.members.get(args.userId);
|
||||
if (member) {
|
||||
const joinedAt = moment(member.joinedAt);
|
||||
const joinAge = humanizeDuration(moment().valueOf() - member.joinedAt, {
|
||||
|
@ -476,7 +472,7 @@ export class UtilityPlugin extends ZeppelinPlugin<IUtilityPluginConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
const cases = (await this.cases.getByUserId(args.userId)).filter(c => !c.is_hidden);
|
||||
const cases = (await this.cases.getByUserId(user.id)).filter(c => !c.is_hidden);
|
||||
|
||||
if (cases.length > 0) {
|
||||
cases.sort((a, b) => {
|
||||
|
@ -501,16 +497,16 @@ export class UtilityPlugin extends ZeppelinPlugin<IUtilityPluginConfig> {
|
|||
msg.channel.createMessage({ embed });
|
||||
}
|
||||
|
||||
@d.command(/(?:nickname|nick) reset/, "<target:member>")
|
||||
@d.command(/(?:nickname|nick) reset/, "<member:resolvedMember>")
|
||||
@d.permission("can_nickname")
|
||||
async nicknameResetCmd(msg: Message, args: { target: Member; nickname: string }) {
|
||||
if (msg.member.id !== args.target.id && !this.canActOn(msg.member, args.target)) {
|
||||
async nicknameResetCmd(msg: Message, args: { member: Member }) {
|
||||
if (msg.member.id !== args.member.id && !this.canActOn(msg.member, args.member)) {
|
||||
msg.channel.createMessage(errorMessage("Cannot reset nickname: insufficient permissions"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await args.target.edit({
|
||||
await args.member.edit({
|
||||
nick: "",
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -518,13 +514,13 @@ export class UtilityPlugin extends ZeppelinPlugin<IUtilityPluginConfig> {
|
|||
return;
|
||||
}
|
||||
|
||||
msg.channel.createMessage(successMessage(`Nickname of <@!${args.target.id}> is now reset`));
|
||||
msg.channel.createMessage(successMessage(`The nickname of <@!${args.member.id}> has been reset`));
|
||||
}
|
||||
|
||||
@d.command(/nickname|nick/, "<target:member> <nickname:string$>")
|
||||
@d.command(/nickname|nick/, "<member:resolvedMember> <nickname:string$>")
|
||||
@d.permission("can_nickname")
|
||||
async nicknameCmd(msg: Message, args: { target: Member; nickname: string }) {
|
||||
if (msg.member.id !== args.target.id && !this.canActOn(msg.member, args.target)) {
|
||||
async nicknameCmd(msg: Message, args: { member: Member; nickname: string }) {
|
||||
if (msg.member.id !== args.member.id && !this.canActOn(msg.member, args.member)) {
|
||||
msg.channel.createMessage(errorMessage("Cannot change nickname: insufficient permissions"));
|
||||
return;
|
||||
}
|
||||
|
@ -535,8 +531,10 @@ export class UtilityPlugin extends ZeppelinPlugin<IUtilityPluginConfig> {
|
|||
return;
|
||||
}
|
||||
|
||||
const oldNickname = args.member.nick || "<none>";
|
||||
|
||||
try {
|
||||
await args.target.edit({
|
||||
await args.member.edit({
|
||||
nick: args.nickname,
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -544,7 +542,9 @@ export class UtilityPlugin extends ZeppelinPlugin<IUtilityPluginConfig> {
|
|||
return;
|
||||
}
|
||||
|
||||
msg.channel.createMessage(successMessage(`Changed nickname of <@!${args.target.id}> to ${args.nickname}`));
|
||||
msg.channel.createMessage(
|
||||
successMessage(`Changed nickname of <@!${args.member.id}> from **${oldNickname}** to **${args.nickname}**`),
|
||||
);
|
||||
}
|
||||
|
||||
@d.command("server")
|
||||
|
@ -673,7 +673,7 @@ export class UtilityPlugin extends ZeppelinPlugin<IUtilityPluginConfig> {
|
|||
msg.channel.createMessage(`Message source: ${url}`);
|
||||
}
|
||||
|
||||
@d.command("vcmove", "<member:Member> <channel:string$>")
|
||||
@d.command("vcmove", "<member:resolvedMember> <channel:string$>")
|
||||
@d.permission("can_vcmove")
|
||||
async vcmoveCmd(msg: Message, args: { member: Member; channel: string }) {
|
||||
let channel: VoiceChannel;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { IBasePluginConfig, IPluginOptions, Plugin } from "knub";
|
||||
import { PluginRuntimeError } from "../PluginRuntimeError";
|
||||
import Ajv, { ErrorObject } from "ajv";
|
||||
import { createUnknownUser, isSnowflake, isUnicodeEmoji, UnknownUser } from "../utils";
|
||||
import { isSnowflake, isUnicodeEmoji, resolveMember, resolveUser, UnknownUser } from "../utils";
|
||||
import { Member, User } from "eris";
|
||||
|
||||
export class ZeppelinPlugin<TConfig extends {} = IBasePluginConfig> extends Plugin<TConfig> {
|
||||
|
@ -81,59 +81,10 @@ export class ZeppelinPlugin<TConfig extends {} = IBasePluginConfig> extends Plug
|
|||
* Resolves a user from the passed string. The passed string can be a user id, a user mention, a full username (with discrim), etc.
|
||||
*/
|
||||
async resolveUser(userResolvable: string): Promise<User | UnknownUser> {
|
||||
if (userResolvable == null) {
|
||||
return createUnknownUser();
|
||||
}
|
||||
|
||||
let userId;
|
||||
|
||||
// A user mention?
|
||||
const mentionMatch = userResolvable.match(/^<@!?(\d+)>$/);
|
||||
if (mentionMatch) {
|
||||
userId = mentionMatch[1];
|
||||
}
|
||||
|
||||
// A non-mention, full username?
|
||||
if (!userId) {
|
||||
const usernameMatch = userResolvable.match(/^@?([^#]+)#(\d{4})$/);
|
||||
if (usernameMatch) {
|
||||
const user = this.bot.users.find(u => u.username === usernameMatch[1] && u.discriminator === usernameMatch[2]);
|
||||
userId = user.id;
|
||||
}
|
||||
}
|
||||
|
||||
// Just a user ID?
|
||||
if (!userId) {
|
||||
const idMatch = userResolvable.match(/^\d+$/);
|
||||
if (!idMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
userId = userResolvable;
|
||||
}
|
||||
|
||||
const cachedUser = this.bot.users.find(u => u.id === userId);
|
||||
if (cachedUser) return cachedUser;
|
||||
|
||||
try {
|
||||
const freshUser = await this.bot.getRESTUser(userId);
|
||||
return freshUser;
|
||||
} catch (e) {} // tslint:disable-line
|
||||
|
||||
return createUnknownUser({ id: userId });
|
||||
return resolveUser(this.bot, userResolvable);
|
||||
}
|
||||
|
||||
async getMember(userId: string): Promise<Member> {
|
||||
// See if we have the member cached...
|
||||
let member = this.guild.members.get(userId);
|
||||
|
||||
// If not, fetch it from the API
|
||||
if (!member) {
|
||||
try {
|
||||
member = await this.bot.getRESTGuildMember(this.guildId, userId);
|
||||
} catch (e) {} // tslint:disable-line
|
||||
}
|
||||
|
||||
return member;
|
||||
async getMember(memberResolvable: string): Promise<Member> {
|
||||
return resolveMember(this.bot, this.guild, memberResolvable);
|
||||
}
|
||||
}
|
||||
|
|
89
src/utils.ts
89
src/utils.ts
|
@ -1,4 +1,4 @@
|
|||
import { Client, Emoji, Guild, GuildAuditLogEntry, TextableChannel, TextChannel, User } from "eris";
|
||||
import { Client, Emoji, Guild, GuildAuditLogEntry, Member, TextableChannel, TextChannel, User } from "eris";
|
||||
import url from "url";
|
||||
import tlds from "tlds";
|
||||
import emojiRegex from "emoji-regex";
|
||||
|
@ -234,7 +234,7 @@ export function getRoleMentions(str: string) {
|
|||
* Disables link previews in the given string by wrapping links in < >
|
||||
*/
|
||||
export function disableLinkPreviews(str: string): string {
|
||||
return str.replace(/(?<!\<)(https?:\/\/\S+)/gi, "<$1>");
|
||||
return str.replace(/(?<!<)(https?:\/\/\S+)/gi, "<$1>");
|
||||
}
|
||||
|
||||
export function deactivateMentions(content: string): string {
|
||||
|
@ -496,19 +496,76 @@ export function ucfirst(str) {
|
|||
return str[0].toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
export type UnknownUser = {
|
||||
id: string;
|
||||
username: string;
|
||||
discriminator: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
export class UnknownUser {
|
||||
public id: string = null;
|
||||
public username = "Unknown";
|
||||
public discriminator = "0000";
|
||||
|
||||
export const unknownUser: UnknownUser = {
|
||||
id: null,
|
||||
username: "Unknown",
|
||||
discriminator: "0000",
|
||||
};
|
||||
|
||||
export function createUnknownUser(props = {}): UnknownUser {
|
||||
return { ...unknownUser, ...props };
|
||||
constructor(props = {}) {
|
||||
for (const key in props) {
|
||||
this[key] = props[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveUser(bot: Client, value: string): Promise<User | UnknownUser> {
|
||||
if (value == null || typeof value !== "string") {
|
||||
return new UnknownUser();
|
||||
}
|
||||
|
||||
let userId;
|
||||
|
||||
// A user mention?
|
||||
const mentionMatch = value.match(/^<@!?(\d+)>$/);
|
||||
if (mentionMatch) {
|
||||
userId = mentionMatch[1];
|
||||
}
|
||||
|
||||
// A non-mention, full username?
|
||||
if (!userId) {
|
||||
const usernameMatch = value.match(/^@?([^#]+)#(\d{4})$/);
|
||||
if (usernameMatch) {
|
||||
const user = bot.users.find(u => u.username === usernameMatch[1] && u.discriminator === usernameMatch[2]);
|
||||
userId = user.id;
|
||||
}
|
||||
}
|
||||
|
||||
// Just a user ID?
|
||||
if (!userId) {
|
||||
const idMatch = value.match(/^\d+$/);
|
||||
if (!idMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
userId = value;
|
||||
}
|
||||
|
||||
const cachedUser = bot.users.find(u => u.id === userId);
|
||||
if (cachedUser) return cachedUser;
|
||||
|
||||
try {
|
||||
const freshUser = await bot.getRESTUser(userId);
|
||||
bot.users.add(freshUser, bot);
|
||||
return freshUser;
|
||||
} catch (e) {} // tslint:disable-line
|
||||
|
||||
return new UnknownUser({ id: userId });
|
||||
}
|
||||
|
||||
export async function resolveMember(bot: Client, guild: Guild, value: string): Promise<Member> {
|
||||
// Start by resolving the user
|
||||
const user = await resolveUser(bot, value);
|
||||
if (!user) return null;
|
||||
|
||||
// See if we have the member cached...
|
||||
let member = guild.members.get(user.id);
|
||||
|
||||
// If not, fetch it from the API
|
||||
if (!member) {
|
||||
try {
|
||||
member = await bot.getRESTGuildMember(guild.id, user.id);
|
||||
} catch (e) {} // tslint:disable-line
|
||||
}
|
||||
|
||||
return member;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue