3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-05-10 20:35:02 +00:00

feat: use webhooks for logs when possible

This commit is contained in:
Dragory 2021-11-02 19:59:30 +02:00
parent 1081d1b361
commit 55a39e0758
No known key found for this signature in database
GPG key ID: 5F387BA66DF8AAC1
12 changed files with 318 additions and 29 deletions

View file

@ -0,0 +1,39 @@
import { PluginOptions, typedGuildCommand } from "knub";
import { GuildPingableRoles } from "../../data/GuildPingableRoles";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { ConfigSchema, InternalPosterPluginType } from "./types";
import {
getPhishermanDomainInfo,
hasPhishermanMasterAPIKey,
phishermanApiKeyIsValid,
reportTrackedDomainsToPhisherman,
} from "../../data/Phisherman";
import { mapToPublicFn } from "../../pluginUtils";
import { Webhooks } from "../../data/Webhooks";
import { Queue } from "../../Queue";
import { sendMessage } from "./functions/sendMessage";
const defaultOptions: PluginOptions<InternalPosterPluginType> = {
config: {},
overrides: [],
};
export const InternalPosterPlugin = zeppelinGuildPlugin<InternalPosterPluginType>()({
name: "internal_poster",
showInDocs: false,
configSchema: ConfigSchema,
defaultOptions,
// prettier-ignore
public: {
sendMessage: mapToPublicFn(sendMessage),
},
async beforeLoad(pluginData) {
pluginData.state.webhooks = new Webhooks();
pluginData.state.queue = new Queue();
pluginData.state.missingPermissions = false;
pluginData.state.webhookClientCache = new Map();
},
});

View file

@ -0,0 +1,48 @@
import { GuildPluginData } from "knub";
import { InternalPosterPluginType } from "../types";
import { NewsChannel, Permissions, TextChannel } from "discord.js";
import { isDiscordAPIError } from "../../../utils";
type WebhookInfo = [id: string, token: string];
export async function getOrCreateWebhookForChannel(
pluginData: GuildPluginData<InternalPosterPluginType>,
channel: TextChannel | NewsChannel,
): Promise<WebhookInfo | null> {
// tslint:disable-next-line:no-console FIXME: Here for debugging purposes
console.log(`getOrCreateWebhookForChannel(${channel.id})`);
// Database cache
const fromDb = await pluginData.state.webhooks.findByChannelId(channel.id);
if (fromDb) {
return [fromDb.id, fromDb.token];
}
if (pluginData.state.missingPermissions) {
return null;
}
// Create new webhook
const member = pluginData.client.user && pluginData.guild.members.cache.get(pluginData.client.user.id);
if (!member || member.permissions.has(Permissions.FLAGS.MANAGE_WEBHOOKS)) {
try {
const webhook = await channel.createWebhook(`Zephook ${channel.id}`);
await pluginData.state.webhooks.create({
id: webhook.id,
guild_id: pluginData.guild.id,
channel_id: channel.id,
token: webhook.token!,
});
return [webhook.id, webhook.token!];
} catch (err) {
if (isDiscordAPIError(err) && err.code === 50013) {
pluginData.state.missingPermissions = true;
console.warn(`Error ${err.code} when trying to create webhook for ${pluginData.guild.id}`);
return null;
}
throw err;
}
}
return null;
}

View file

@ -0,0 +1,71 @@
import { Message, MessageOptions, NewsChannel, TextChannel, WebhookClient } from "discord.js";
import { GuildPluginData } from "knub";
import { InternalPosterPluginType } from "../types";
import { getOrCreateWebhookForChannel } from "./getOrCreateWebhookForChannel";
import { APIMessage } from "discord-api-types";
import { isDiscordAPIError } from "../../../utils";
export type InternalPosterMessageResult = {
id: string;
channelId: string;
};
/**
* Sends a message using a webhook or direct API requests, preferring webhooks when possible.
*/
export async function sendMessage(
pluginData: GuildPluginData<InternalPosterPluginType>,
channel: TextChannel | NewsChannel,
content: MessageOptions,
): Promise<InternalPosterMessageResult> {
return pluginData.state.queue.add(async () => {
if (!pluginData.state.webhookClientCache.has(channel.id)) {
const webhookInfo = await getOrCreateWebhookForChannel(pluginData, channel);
if (webhookInfo) {
const client = new WebhookClient({
id: webhookInfo[0],
token: webhookInfo[1],
});
pluginData.state.webhookClientCache.set(channel.id, client);
} else {
pluginData.state.webhookClientCache.set(channel.id, null);
}
}
const webhookClient = pluginData.state.webhookClientCache.get(channel.id);
if (webhookClient) {
return webhookClient
.send({
...content,
...(pluginData.client.user && {
username: pluginData.client.user.username,
avatarURL: pluginData.client.user.avatarURL() || pluginData.client.user.defaultAvatarURL,
}),
})
.then((apiMessage) => ({
id: apiMessage.id,
channelId: apiMessage.channel_id,
}))
.catch(async (err) => {
// Unknown Webhook
if (isDiscordAPIError(err) && err.code === 10015) {
await pluginData.state.webhooks.delete(webhookClient.id);
pluginData.state.webhookClientCache.delete(channel.id);
// Fallback to regular message for this log message
return channel.send(content).then((message) => ({
id: message.id,
channelId: message.channelId,
}));
}
throw err;
});
}
return channel.send(content).then((message) => ({
id: message.id,
channelId: message.channelId,
}));
});
}

View file

@ -0,0 +1,22 @@
import * as t from "io-ts";
import { BasePluginType } from "knub";
import { Webhooks } from "../../data/Webhooks";
import { Queue } from "../../Queue";
import { WebhookClient } from "discord.js";
export const ConfigSchema = t.type({});
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
// <channelId, webhookUrl>
type ChannelWebhookMap = Map<string, string>;
export interface InternalPosterPluginType extends BasePluginType {
config: TConfigSchema;
state: {
queue: Queue;
webhooks: Webhooks;
missingPermissions: boolean;
webhookClientCache: Map<string, WebhookClient | null>;
};
}