Open Unyxos opened 1 year ago
Documentation to that: https://discord.com/developers/docs/resources/webhook
I could imagine that it could be used as general webhook system. ( That it isnt discord.com.... locked ) But its on the discords format. ( well known everywhere )
Hey @Unyxos, thank you for raising the feature request. Our team will brainstorm around it, and we will keep you updated on any developments.
Thanks!
Really need webhook to integrate Plane to my system.
+1
Here's a serverless framework cloudfunction function that i wrote, haven't tested it yet, but something like this please, like 200 events "free" per month, and x amount after per unit cost to run the cloud function, plus whatever your cut would be:
index.mjs:
'use strict';
import axios from 'axios';
import striptags from 'striptags';
// https://discord.com/developers/docs/resources/webhook#execute-webhook
// https://docs.plane.so/webhooks/introduction#how-webhook-works
// https://app.plane.so/<your-org>/projects/<project-id>/modules/<item-id>
// {
// "event": "module",
// "action": "create",
// "webhook_id": "xyz",
// "workspace_id": "blah",
// "data": {
// "id": "<item-id>",
// "created_at": "2024-03-03T18:04:25.779869Z",
// "updated_at": "2024-03-03T18:04:25.779883Z",
// "name": "test webhook / module create",
// "description": "",
// "description_text": null,
// "description_html": null,
// "start_date": null,
// "target_date": null,
// "status": "backlog",
// "view_props": {},
// "sort_order": 15535,
// "external_source": null,
// "external_id": null,
// "created_by": "<your-user-id>",
// "updated_by": "<your-user-id>",
// "project": "<project-id>",
// "workspace": "blah",
// "lead": null,
// "members": []
// }
// }
let ORG_NAME = 'xxx'
const discordWebhookId = process.env['DISCORD_WEBHOOK_ID'] || 'xxx';
const discordWebhookToken = process.env['DISCORD_WEBHOOK_ID'] || 'xxx';
export const http = async (request, response) => {
console.log(JSON.stringify(request.body))
console.log(JSON.stringify(request.headers))
if (
request.headers['x-plane-delivery']
&& request.headers['x-plane-event']
&& request.headers['x-plane-signature']
&& request.body.webhook_id === "xxx"
) {
let {event, action, data} = request.body;
if (action == 'create' || action == 'update') {
let discordResponse = await sendPlaneEventToDiscord(event, action, data, discordWebhookId, discordWebhookToken);
}
response.status(200).send(request.body);
}
};
export const event = (event, callback) => {
callback();
};
/**
* Transforms a Plane.so webhook event to a Discord message format and sends it.
*
* @param {string} planeEvent - The Plane.so webhook event: "issue", "module", "project", ???
* @param {string} planeAction - The Plane.so webhook event object.
* @param {object} planeData - The Plane.so webhook event object.
* @param {string} discordWebhookId - The Discord webhook ID.
* @param {string} discordWebhookToken - The Discord webhook token.
*/
async function sendPlaneEventToDiscord(planeEvent, planeAction, planeData, discordWebhookId, discordWebhookToken) {
// Construct the Discord webhook URL
const webhookUrl = `https://discord.com/api/webhooks/${discordWebhookId}/${discordWebhookToken}`;
let url = ''
if (planeEvent == 'issue' || planeEvent == 'module') {
url = `https://app.plane.so/${ORG_NAME}/projects/${planeData.project}/${planeEvent}s/${planeData.id}`;
} else if (planeEvent == 'issue_comment') {
url = `https://app.plane.so/${ORG_NAME}/projects/${planeData.project}/issues/${planeData.issue}`;
}
let projects = {
'e5e79a51xxx9': 'ACME',
'05f9c563xxx9ac': 'project2',
}
let PROJECT = projects[planeData.project] || 'unknown';
// Transform the Plane.so event to a Discord message format
const discordMessage = {
content: `${planeEvent} ${planeAction}`,
embeds: [{
title: `${planeEvent} ${planeAction}`,
description: `Project **${PROJECT}**\n\n**Description:** ${planeData.description_stripped || planeData.name || striptags(planeData.comment_html) || 'No description provided.'}`,
color: 3447003, // You can set a color code for the embed
fields: [
{ name: "Updated At", value: planeData.updated_at || 'None', inline: true },
{ name: "Event ID", value: planeData.id || 'None', inline: true },
{ name: "Url", value: url || 'None', inline: true},
{ name: "User ID", value: planeData.updated_by || 'None', inline: true},
]
}],
};
try {
// Make a POST request to the Discord webhook URL
// console.log(`Send data to: ${webhookUrl} : ${JSON.stringify(discordMessage)}`)
const response = await axios.post(webhookUrl, discordMessage);
console.log('Message sent to Discord:', response.data);
return response;
} catch (error) {
console.error('Error sending message to Discord:', error.response ? error.response.data : error.message);
}
}
package.json:
{
"name": "plane-to-discord-webhook",
"version": "0.1.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "serverless.com",
"license": "MIT",
"devDependencies": {
"serverless-google-cloudfunctions": "*"
},
"dependencies": {
"axios": "^1.6.7"
}
}
serverless.yml:
service: plane-to-discord-webhook
provider:
name: google
stage: dev
runtime: nodejs20
region: us-central1
project: <your-google-project-id>
# The GCF credentials can be a little tricky to set up. Luckily we've documented this for you here:
# https://serverless.com/framework/docs/providers/google/guide/credentials/
#
# the path to the credentials file needs to be absolute
credentials: ~/.gcloud/keyfile.json
frameworkVersion: '3'
plugins:
- serverless-google-cloudfunctions
# needs more granular excluding in production as only the serverless provider npm
# package should be excluded (and not the whole node_modules directory)
package:
exclude:
- node_modules/**
- .gitignore
- .git/**
functions:
exampleplanetodiscord:
handler: http
events:
- http: path
# NOTE: the following uses an "event" event (pubSub event in this case).
# Please create the corresponding resources in the Google Cloud
# before deploying this service through Serverless
#second:
# handler: event
# events:
# - event:
# eventType: providers/cloud.pubsub/eventTypes/topic.publish
# resource: projects/*/topics/my-topic
# you can define resources, templates etc. the same way you would in a
# Google Cloud deployment configuration
#resources:
# resources:
# - type: storage.v1.bucket
# name: my-serverless-service-bucket
# imports:
# - path: my_template.jinja
Okay, tested it, Here it is in action:
Notes:
1) unable to add credentials to the webhook, so the endpoints are unauthenticated and open to the public internet :(. 2) The id's being passed to the webhook don't make it very appealing, so have to hardcode usernames, etc in a object/dictionary 3) the views do not have a consistent url path scheme, so it's not possible to directly link issue comments 4) the webhooks do not have a manual retry mechanism for me to use, or response log to look at to debug it
Is there an existing issue for this?
Summary
Allow to receive projects' updates in a Discord server using a webhook integration
Why should this be worked on?
The idea would be to be able to setup a discord webhook to receive updates from projects in a discord server, similarly to how Gitlab does it when new changes happen in projects : https://docs.gitlab.com/ee/user/project/integrations/discord_notifications.html
It'd be helpful to "track" updates from other team members when using Discord as the team's discussion tool :)