2020-07-05 05:00:54 +03:00
|
|
|
/**
|
|
|
|
* @file Utility functions that are plugin-instance-specific (i.e. use PluginData)
|
|
|
|
*/
|
|
|
|
|
2023-04-01 18:33:09 +03:00
|
|
|
import {
|
|
|
|
GuildMember,
|
|
|
|
Message,
|
|
|
|
MessageCreateOptions,
|
|
|
|
MessageMentionOptions,
|
|
|
|
PermissionsBitField,
|
|
|
|
TextBasedChannel,
|
|
|
|
} from "discord.js";
|
2020-07-30 00:29:44 +03:00
|
|
|
import * as t from "io-ts";
|
2023-04-01 12:58:17 +01:00
|
|
|
import {
|
|
|
|
AnyPluginData,
|
|
|
|
CommandContext,
|
|
|
|
ConfigValidationError,
|
|
|
|
ExtendedMatchParams,
|
|
|
|
GuildPluginData,
|
|
|
|
PluginOverrideCriteria,
|
2023-05-08 21:33:40 +03:00
|
|
|
helpers,
|
2023-04-01 12:58:17 +01:00
|
|
|
} from "knub";
|
2020-10-11 14:19:39 +03:00
|
|
|
import { logger } from "./logger";
|
2023-04-01 12:58:17 +01:00
|
|
|
import { isStaff } from "./staff";
|
2021-06-06 23:51:32 +02:00
|
|
|
import { TZeppelinKnub } from "./types";
|
2023-04-01 12:58:17 +01:00
|
|
|
import { errorMessage, successMessage, tNullable } from "./utils";
|
2021-06-06 23:51:32 +02:00
|
|
|
import { Tail } from "./utils/typeUtils";
|
2023-05-08 21:33:40 +03:00
|
|
|
import { StrictValidationError, parseIoTsSchema } from "./validatorUtils";
|
2020-07-05 05:00:54 +03:00
|
|
|
|
|
|
|
const { getMemberLevel } = helpers;
|
|
|
|
|
2021-05-31 21:12:24 +02:00
|
|
|
export function canActOn(
|
|
|
|
pluginData: GuildPluginData<any>,
|
|
|
|
member1: GuildMember,
|
|
|
|
member2: GuildMember,
|
|
|
|
allowSameLevel = false,
|
2023-04-01 18:33:09 +03:00
|
|
|
allowAdmins = false,
|
2021-05-31 21:12:24 +02:00
|
|
|
) {
|
|
|
|
if (member2.id === pluginData.client.user!.id) {
|
2020-07-05 05:00:54 +03:00
|
|
|
return false;
|
|
|
|
}
|
2023-04-01 18:33:09 +03:00
|
|
|
const isOwnerOrAdmin =
|
|
|
|
member2.id === member2.guild.ownerId || member2.permissions.has(PermissionsBitField.Flags.Administrator);
|
|
|
|
if (isOwnerOrAdmin && !allowAdmins) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-07-05 05:00:54 +03:00
|
|
|
|
|
|
|
const ourLevel = getMemberLevel(pluginData, member1);
|
|
|
|
const memberLevel = getMemberLevel(pluginData, member2);
|
|
|
|
return allowSameLevel ? ourLevel >= memberLevel : ourLevel > memberLevel;
|
|
|
|
}
|
|
|
|
|
2021-05-23 14:35:16 +03:00
|
|
|
export async function hasPermission(
|
|
|
|
pluginData: AnyPluginData<any>,
|
|
|
|
permission: string,
|
|
|
|
matchParams: ExtendedMatchParams,
|
|
|
|
) {
|
|
|
|
const config = await pluginData.config.getMatchingConfig(matchParams);
|
2020-07-23 00:37:33 +03:00
|
|
|
return helpers.hasPermission(config, permission);
|
|
|
|
}
|
|
|
|
|
2020-11-09 20:03:57 +02:00
|
|
|
const PluginOverrideCriteriaType: t.Type<PluginOverrideCriteria<unknown>> = t.recursion(
|
|
|
|
"PluginOverrideCriteriaType",
|
|
|
|
() =>
|
|
|
|
t.partial({
|
|
|
|
channel: tNullable(t.union([t.string, t.array(t.string)])),
|
|
|
|
category: tNullable(t.union([t.string, t.array(t.string)])),
|
|
|
|
level: tNullable(t.union([t.string, t.array(t.string)])),
|
|
|
|
user: tNullable(t.union([t.string, t.array(t.string)])),
|
|
|
|
role: tNullable(t.union([t.string, t.array(t.string)])),
|
|
|
|
|
|
|
|
all: tNullable(t.array(PluginOverrideCriteriaType)),
|
|
|
|
any: tNullable(t.array(PluginOverrideCriteriaType)),
|
|
|
|
not: tNullable(PluginOverrideCriteriaType),
|
|
|
|
|
|
|
|
extra: t.unknown,
|
|
|
|
}),
|
2020-07-30 00:29:44 +03:00
|
|
|
);
|
|
|
|
|
2021-05-23 18:10:23 +03:00
|
|
|
const validTopLevelOverrideKeys = [
|
|
|
|
"channel",
|
|
|
|
"category",
|
2021-09-05 20:58:26 +03:00
|
|
|
"thread",
|
|
|
|
"is_thread",
|
2021-05-23 18:10:23 +03:00
|
|
|
"level",
|
|
|
|
"user",
|
|
|
|
"role",
|
|
|
|
"all",
|
|
|
|
"any",
|
|
|
|
"not",
|
|
|
|
"extra",
|
|
|
|
"config",
|
|
|
|
];
|
|
|
|
|
2020-07-30 00:29:44 +03:00
|
|
|
const BasicPluginStructureType = t.type({
|
|
|
|
enabled: tNullable(t.boolean),
|
|
|
|
config: tNullable(t.unknown),
|
2020-11-09 20:03:57 +02:00
|
|
|
overrides: tNullable(t.array(t.union([PluginOverrideCriteriaType, t.type({ config: t.unknown })]))),
|
2020-07-30 00:29:44 +03:00
|
|
|
replaceDefaultOverrides: tNullable(t.boolean),
|
|
|
|
});
|
|
|
|
|
2020-07-30 20:40:00 +03:00
|
|
|
export function strictValidationErrorToConfigValidationError(err: StrictValidationError) {
|
|
|
|
return new ConfigValidationError(
|
|
|
|
err
|
|
|
|
.getErrors()
|
2021-09-11 19:06:51 +03:00
|
|
|
.map((e) => e.toString())
|
2020-07-30 20:40:00 +03:00
|
|
|
.join("\n"),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-04-01 12:58:17 +01:00
|
|
|
export function makeIoTsConfigParser<Schema extends t.Type<any>>(schema: Schema): (input: unknown) => t.TypeOf<Schema> {
|
|
|
|
return (input: unknown) => {
|
2023-04-02 03:18:55 +03:00
|
|
|
try {
|
|
|
|
return parseIoTsSchema(schema, input);
|
|
|
|
} catch (err) {
|
|
|
|
if (err instanceof StrictValidationError) {
|
|
|
|
throw strictValidationErrorToConfigValidationError(err);
|
|
|
|
}
|
|
|
|
throw err;
|
2020-07-05 05:00:54 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-06-02 23:41:05 +02:00
|
|
|
export async function sendSuccessMessage(
|
2021-01-17 21:05:11 +02:00
|
|
|
pluginData: AnyPluginData<any>,
|
2023-04-01 12:58:17 +01:00
|
|
|
channel: TextBasedChannel,
|
2021-01-17 21:05:11 +02:00
|
|
|
body: string,
|
2021-05-31 21:12:24 +02:00
|
|
|
allowedMentions?: MessageMentionOptions,
|
2021-01-17 21:05:11 +02:00
|
|
|
): Promise<Message | undefined> {
|
2020-10-01 01:43:38 +03:00
|
|
|
const emoji = pluginData.fullConfig.success_emoji || undefined;
|
2021-01-17 21:24:23 +02:00
|
|
|
const formattedBody = successMessage(body, emoji);
|
2023-04-01 12:58:17 +01:00
|
|
|
const content: MessageCreateOptions = allowedMentions
|
2021-01-17 21:24:23 +02:00
|
|
|
? { content: formattedBody, allowedMentions }
|
|
|
|
: { content: formattedBody };
|
2021-06-02 23:41:05 +02:00
|
|
|
|
2021-01-17 21:21:18 +02:00
|
|
|
return channel
|
2021-06-30 04:56:56 +02:00
|
|
|
.send({ ...content }) // Force line break
|
2021-09-11 19:06:51 +03:00
|
|
|
.catch((err) => {
|
2023-04-01 12:58:17 +01:00
|
|
|
const channelInfo = "guild" in channel ? `${channel.id} (${channel.guild.id})` : channel.id;
|
2021-01-17 21:21:18 +02:00
|
|
|
logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`);
|
|
|
|
return undefined;
|
|
|
|
});
|
2020-07-05 05:00:54 +03:00
|
|
|
}
|
|
|
|
|
2021-06-02 23:41:05 +02:00
|
|
|
export async function sendErrorMessage(
|
2021-01-17 21:21:18 +02:00
|
|
|
pluginData: AnyPluginData<any>,
|
2023-04-01 12:58:17 +01:00
|
|
|
channel: TextBasedChannel,
|
2021-01-17 21:21:18 +02:00
|
|
|
body: string,
|
2021-05-31 21:12:24 +02:00
|
|
|
allowedMentions?: MessageMentionOptions,
|
2021-01-17 21:21:18 +02:00
|
|
|
): Promise<Message | undefined> {
|
2020-10-01 01:43:38 +03:00
|
|
|
const emoji = pluginData.fullConfig.error_emoji || undefined;
|
2021-01-17 21:24:23 +02:00
|
|
|
const formattedBody = errorMessage(body, emoji);
|
2023-04-01 12:58:17 +01:00
|
|
|
const content: MessageCreateOptions = allowedMentions
|
2021-01-17 21:24:23 +02:00
|
|
|
? { content: formattedBody, allowedMentions }
|
|
|
|
: { content: formattedBody };
|
2021-06-02 23:41:05 +02:00
|
|
|
|
2021-01-17 21:21:18 +02:00
|
|
|
return channel
|
2021-06-30 04:56:56 +02:00
|
|
|
.send({ ...content }) // Force line break
|
2021-09-11 19:06:51 +03:00
|
|
|
.catch((err) => {
|
2023-04-01 12:58:17 +01:00
|
|
|
const channelInfo = "guild" in channel ? `${channel.id} (${channel.guild.id})` : channel.id;
|
2021-01-17 21:21:18 +02:00
|
|
|
logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`);
|
|
|
|
return undefined;
|
|
|
|
});
|
2020-07-05 05:00:54 +03:00
|
|
|
}
|
2020-07-06 01:54:26 +03:00
|
|
|
|
2020-10-01 01:43:38 +03:00
|
|
|
export function getBaseUrl(pluginData: AnyPluginData<any>) {
|
2020-07-06 01:54:26 +03:00
|
|
|
const knub = pluginData.getKnubInstance() as TZeppelinKnub;
|
2023-04-01 12:58:17 +01:00
|
|
|
// @ts-expect-error
|
2020-07-06 01:54:26 +03:00
|
|
|
return knub.getGlobalConfig().url;
|
|
|
|
}
|
2020-07-22 22:33:10 +02:00
|
|
|
|
2020-10-01 01:43:38 +03:00
|
|
|
export function isOwner(pluginData: AnyPluginData<any>, userId: string) {
|
2020-07-22 22:33:10 +02:00
|
|
|
const knub = pluginData.getKnubInstance() as TZeppelinKnub;
|
2023-04-01 12:58:17 +01:00
|
|
|
// @ts-expect-error
|
2021-09-04 22:11:08 +03:00
|
|
|
const owners = knub.getGlobalConfig()?.owners;
|
2020-07-22 22:33:10 +02:00
|
|
|
if (!owners) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return owners.includes(userId);
|
|
|
|
}
|
2020-07-30 03:21:07 +03:00
|
|
|
|
2022-08-06 21:31:13 +03:00
|
|
|
export const isStaffPreFilter = (_, context: CommandContext<any>) => {
|
|
|
|
return isStaff(context.message.author.id);
|
2020-07-30 03:21:07 +03:00
|
|
|
};
|
2020-08-19 00:19:12 +03:00
|
|
|
|
|
|
|
type AnyFn = (...args: any[]) => any;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a public plugin function out of a function with pluginData as the first parameter
|
|
|
|
*/
|
|
|
|
export function mapToPublicFn<T extends AnyFn>(inputFn: T) {
|
2021-09-11 19:06:51 +03:00
|
|
|
return (pluginData) => {
|
2020-08-19 00:19:12 +03:00
|
|
|
return (...args: Tail<Parameters<typeof inputFn>>): ReturnType<typeof inputFn> => {
|
|
|
|
return inputFn(pluginData, ...args);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|