Skip to content

Commit

Permalink
fix: added discord notifier
Browse files Browse the repository at this point in the history
  • Loading branch information
0xGorilla committed Oct 29, 2024
1 parent 6c704b2 commit 537ef50
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 192 deletions.
3 changes: 1 addition & 2 deletions packages/automated-dispute/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { z } from "zod";

const ConfigSchema = z.object({
DISCORD_BOT_TOKEN: z.string().min(1),
DISCORD_CHANNEL_ID: z.string().min(1),
DISCORD_WEBHOOK: z.string().min(1),
});

export const config = ConfigSchema.parse(process.env);
1 change: 1 addition & 0 deletions packages/automated-dispute/src/exceptions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from "./eboActor/index.js";
export * from "./eboProcessor/index.js";
export * from "./eboRegistry/index.js";

export * from "./notificationFailure.exception.js";
export * from "./invalidActorState.exception.js";
export * from "./invalidDisputeStatus.exception.js";
export * from "./requestAlreadyHandled.exception.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class NotificationFailureException extends Error {
constructor(message: string) {
super(`Failed to send notification: ${message}`);
this.name = "NotificationFailureException";
}
}
22 changes: 11 additions & 11 deletions packages/automated-dispute/src/interfaces/notificationService.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/**
* Interface representing a notification service capable of sending error notifications.
*/
export interface IMessage {
title: string;
subtitle?: string;
description?: string;
username?: string;
avatarUrl?: string;
actionUrl?: string;
}

export interface NotificationService {
/**
* Sends an error notification along with optional contextual information.
*
* @param error - The error object containing information about the error that occurred.
* @param context - Additional context or data related to the error
* @returns A promise that resolves when the notification process is complete.
*/
notifyError(error: Error, context: any): Promise<void>;
// send(message: IMessage): Promise<void>;
notifyError(err: Error, context: unknown): Promise<void>;
}
129 changes: 61 additions & 68 deletions packages/automated-dispute/src/services/discordNotifier.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,36 @@
import { ILogger } from "@ebo-agent/shared";
import { Client, IntentsBitField, TextChannel } from "discord.js";
import type { APIEmbed, JSONEncodable } from "discord.js";
import { WebhookClient, WebhookMessageCreateOptions } from "discord.js";
import { stringify } from "viem";

import { NotificationService } from "../interfaces/index.js";
import { NotificationFailureException } from "../exceptions/index.js";
import { IMessage, NotificationService } from "../interfaces/index.js";

interface DiscordNotifierConfig {
discordBotToken: string;
discordChannelId: string;
}
export type WebhookMessage = WebhookMessageCreateOptions & {
content: string;
};

/**
* A notifier class for sending error notifications to a Discord channel.
* TODO: Refactor me, this was a quick and dirty implementation
*
* `notifiyError` and `formatErrorMessage` should not exist.
* `send` should be the method to use across the codebase. (added as comment here and in the interface)
* If there is a new Notifier service (Ex. TelegramNotifier) it should implement the same interface (NotificationService).
*/
export class DiscordNotifier implements NotificationService {
private client: Client;
private config: DiscordNotifierConfig;
private logger: ILogger;
private client: WebhookClient;

private constructor(client: Client, config: DiscordNotifierConfig, logger: ILogger) {
this.client = client;
this.config = config;
this.logger = logger;
constructor(url: string) {
this.client = new WebhookClient({ url });
}

/**
* Creates an instance of the DiscordNotifier.
* @param {DiscordNotifierConfig} config - The configuration object for the DiscordNotifier.
* @param {ILogger} logger - The logger instance.
* @returns {Promise<DiscordNotifier>} A promise that resolves to a DiscordNotifier instance.
*/
public static async create(
config: DiscordNotifierConfig,
logger: ILogger,
): Promise<DiscordNotifier> {
const intents = new IntentsBitField().add(
IntentsBitField.Flags.Guilds,
IntentsBitField.Flags.GuildMessages,
);
const client = new Client({ intents });
async notifyError(err: Error, context: unknown): Promise<void> {
const message = this.formatErrorMessage(err, context);

const webhookMessage = this.buildWebhookMessage({ title: message });
try {
await client.login(config.discordBotToken);

await new Promise<void>((resolve, reject) => {
client.once("ready", () => {
logger.info("Discord bot is ready");

resolve();
});

client.once("error", (error: Error) => {
reject(error);
});
});
await this.client.send(webhookMessage);
} catch (error) {
logger.error(`Failed to initialize Discord notifier: ${error}`);

throw error;
}

return new DiscordNotifier(client, config, logger);
}

/**
* Sends an error notification to the specified Discord channel.
* @param {Error} error - The error to notify about.
* @param {any} context - Additional context information.
* @returns {Promise<void>} A promise that resolves when the message is sent.
*/
public async notifyError(error: Error, context: any): Promise<void> {
try {
const channel = await this.client.channels.fetch(this.config.discordChannelId);
if (!channel || !channel.isTextBased()) {
throw new Error("Discord channel not found or is not text-based");
}
const errorMessage = this.formatErrorMessage(error, context);
await (channel as TextChannel).send(errorMessage);
this.logger.info("Error notification sent to Discord");
} catch (err) {
this.logger.error(`Failed to send error notification to Discord: ${err}`);
throw new NotificationFailureException("An error occured with Discord client.");
}
}

Expand All @@ -95,4 +47,45 @@ export class DiscordNotifier implements NotificationService {
2,
)}\n\`\`\``;
}

// /**
// * Sends a webhook message to Discord.
// * @param {WebhookMessage} message - The message to send.
// * @returns A promise that resolves when the message is sent.
// */
// async send(message: IMessage) {
// const webhookMessage = this.buildWebhookMessage(message);
// try {
// await this.client.send(webhookMessage);
// } catch (error) {
// throw new NotificationFailureException("An error occured with Discord client.");
// }
// }

/**
* Builds a Discord webhookMessage for an arbitrage opportunity.
* @param {ArbitrageOpportunity} data - The arbitrage data.
* @returns {WebhookMessage} The built Discord webhook message.
*/
buildWebhookMessage(message: IMessage): WebhookMessage {
return {
username: message.username || "EBO Agent",
content: message.title,
avatarURL:
message.avatarUrl || "https://i.ibb.co/x3HV1Tj/The-Graph-GRT-Symbol-Color.png", // TODO: change to a better image source
// embeds: [this.buildWebhookMessageEmbed(message)],
} as WebhookMessage;
}

buildWebhookMessageEmbed(message: IMessage) {
const title = message.subtitle;
const description = message.description;

return {
title,
description,
color: 2326507,
url: message.actionUrl,
} as APIEmbed | JSONEncodable<APIEmbed>;
}
}
Loading

0 comments on commit 537ef50

Please sign in to comment.