Skip to content

Commit

Permalink
feat: refactor dao notifier to use discord webhook
Browse files Browse the repository at this point in the history
Co-authored-by: PsicoThePato <[email protected]>
  • Loading branch information
steinerkelvin and PsicoThePato committed Oct 16, 2024
1 parent 56d2223 commit 2a7fda8
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 43 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ DISCORD_API_ENDPOINT="your-discord-api-endpoint"

# Comrads
COMMUNITY_VALIDATOR_MNEMONIC=""

DAO_NOTIFIER_DISCORD_WEBHOOK_URL=""
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
],
"cSpell.words": [
"comrads",
"ipfs",
"keypair",
"nanos",
"netuid",
Expand Down
13 changes: 13 additions & 0 deletions apps/commune-worker/src/common/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { z } from "zod";

export const parseEnvOrExit = <O>(envSchema: z.ZodType<O>) => (env: unknown) => {
const result = envSchema.safeParse(env);

if (!result.success) {
console.error("❌ Invalid environment variables:");
console.error(JSON.stringify(result.error.format(), null, 2));
process.exit(1);
}

return result.data;
}
37 changes: 37 additions & 0 deletions apps/commune-worker/src/discord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import axios from "axios";

const webhookUrl =

Check failure on line 3 in apps/commune-worker/src/discord.ts

View workflow job for this annotation

GitHub Actions / lint

'webhookUrl' is assigned a value but never used. Allowed unused vars must match /^_/u
"https://discord.com/api/webhooks/1296153912126214204/Vs7WKkAs4xyjkou7LV0moKEOlow6YgHEwZ2mvWvLT6lp9gNw3pRivXAvqTnDpWrztNNS";

export interface EmbedField {
name: string;
value: string;
inline?: boolean;
}

export interface Embed {
title?: string;
description?: string;
color?: number;
fields?: EmbedField[];
thumbnail?: { url: string };
image?: { url: string };
footer?: { text: string; icon_url?: string };
timestamp?: string;
}

export interface WebhookPayload {
content?: string;
username?: string;
avatar_url?: string;
embeds?: Embed[];
}

export async function sendDiscordWebhook(
webhookUrl: string,
payload: WebhookPayload
): Promise<void> {
return await axios.post(webhookUrl, payload, {
headers: { "Content-Type": "application/json" },
});
}
135 changes: 92 additions & 43 deletions apps/commune-worker/src/workers/notify-dao-applications.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import axios from "axios";
import { z } from "zod";

import type { DaoApplications, GovernanceModeType } from "@commune-ts/types";
import {
Expand All @@ -10,48 +10,21 @@ import {

import type { WorkerProps } from "../common";
import type { NewNotification } from "../db";
import { getApplications } from "../common";
import type { Embed, WebhookPayload } from "../discord";
import { getApplications, sleep } from "../common";
import { parseEnvOrExit } from "../common/env";
import { addSeenProposal, getProposalIdsByType } from "../db";
import { sendDiscordWebhook } from "../discord";

const THUMBNAIL_URL = "https://i.imgur.com/6hJKhMu.gif";

export const env = parseEnvOrExit(
z.object({
DAO_NOTIFIER_DISCORD_WEBHOOK_URL: z.string().min(1),
}),
)(process.env);

export async function notifyNewApplicationsWorker(props: WorkerProps) {
async function pushNotification(proposal: DaoApplications) {
const r = parseIpfsUri(proposal.data);
const cid = flattenResult(r);
if (cid === null) {
console.log(`Failed to parse ${proposal.id} cid`);
} else {
const url = buildIpfsGatewayUrl(cid);
const metadata = await processDaoMetadata(url, proposal.id);
const resolved_metadata = flattenResult(metadata);
if (resolved_metadata === null) {
console.log(`Failed to get metadata on proposal ${proposal.id}`);
} else {
const notification = {
discord_uid: `${resolved_metadata.discord_id}`,
app_id: `${proposal.id}`,
application_url: `https://governance.communeai.org/dao/${proposal.id}`,
};
const headers = {
"X-token": process.env.DISCORD_API_TOKEN,
};
const seen_proposal: NewNotification = {
governanceModel: p_type,
proposalId: proposal.id,
};
await axios
.post(`${process.env.DISCORD_API_ENDPOINT}`, notification, {
headers,
})
.then(async function (response) {
await addSeenProposal(seen_proposal);
})
.catch((reason) => console.log(`Reject bc ${reason}`));
console.log("pushed notification"); // actually to call the discord endpoint and etc
return;
}
}
return;
}
const pending_apps = Object.values(
await getApplications(props.api, ["Pending"]),
);
Expand All @@ -61,8 +34,84 @@ export async function notifyNewApplicationsWorker(props: WorkerProps) {
const unseen_proposals = pending_apps.filter(
(application) => !proposalsSet.has(application.id),
);
const notifications_promises = unseen_proposals.map(pushNotification);
await Promise.all(notifications_promises).catch((error) =>
console.log(`Failed to notify proposal for reason: ${error}`),

for (const unseen_proposal of unseen_proposals) {
await pushNotification(unseen_proposal, p_type);
await sleep(1_000);
}
}

async function pushNotification(
proposal: DaoApplications,
pType: GovernanceModeType,
) {
const r = parseIpfsUri(proposal.data);
const cid = flattenResult(r);
if (cid === null) {
console.warn(`Failed to parse ${proposal.id} cid`);
return;
}

const url = buildIpfsGatewayUrl(cid);
const metadata = await processDaoMetadata(url, proposal.id);
const resolved_metadata = flattenResult(metadata);
if (resolved_metadata === null) {
console.warn(`Failed to get metadata on proposal ${proposal.id}`);
return;
}

const notification = {
discord_uid: `${resolved_metadata.discord_id}`,
app_id: `${proposal.id}`,
application_url: `https://governance.communeai.org/dao/${proposal.id}`,
};
const seen_proposal: NewNotification = {
governanceModel: pType,
proposalId: proposal.id,
};

const discordMessage = buildDiscordMessage(
notification.discord_uid,
String(proposal.id),
notification.application_url,
);

await sendDiscordWebhook(
env.DAO_NOTIFIER_DISCORD_WEBHOOK_URL,
discordMessage,
);

await addSeenProposal(seen_proposal);
}

function buildDiscordMessage(
discordId: string,
appId: string,
applicationUrl: string,
) {
const embed: Embed = {
title: "New Pending DAO Application",
description: "A new DAO application has been submitted",
color: 0x00ff00, // Green color
fields: [
{ name: "Application URL", value: applicationUrl },
{ name: "Applicant", value: `<@${discordId}>` },
{ name: "Application ID", value: `${appId}` },
],
thumbnail: { url: THUMBNAIL_URL },
// image: { url: 'https://example.com/image.png' },
footer: {
text: "Please review and discuss the application on our website.",
},
// timestamp: new Date().toISOString(),
};

const payload: WebhookPayload = {
content: "",
username: "ComDAO",
avatar_url: "https://example.com/avatar.png",
embeds: [embed],
};

return payload;
}

0 comments on commit 2a7fda8

Please sign in to comment.