Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions webhook/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"title": "Webhook System",
"author": "Reeperk",
"version": 1,
"enabled": true,
"default_aboutme": "A Webhook System"
}
82 changes: 82 additions & 0 deletions webhook/functions/plugin_onLoad.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { app, io, serverconfig } from "../../../index.mjs";
import { generateId, sanitizeInput, getCastingMemberObject } from "../../../modules/functions/main.mjs";
import { saveChatMessage } from "../../../modules/functions/io.mjs";
import express from "express";
import fs from "fs";
import path from "path";

const WEBHOOKS_FILE = path.join(process.cwd(), "configs", "webhooks.json");

export function onLoad() {
console.log("[Webhook Plugin] Loading Webhook API Routes...");

if (!fs.existsSync(WEBHOOKS_FILE)) {
fs.writeFileSync(WEBHOOKS_FILE, JSON.stringify({}));
}

app.post('/api/webhooks/:token', express.json(), async (req, res) => {
try {
const token = req.params.token;
const webhooks = JSON.parse(fs.readFileSync(WEBHOOKS_FILE, 'utf-8'));

let webhook = Object.values(webhooks).find(wh => wh.token === token);

if (!webhook) {
return res.status(404).json({ error: "Webhook not found" });
}

const content = req.body.content;
if (!content) {
return res.status(400).json({ error: "Message content is required" });
}

let messageid = generateId(12);
let whId = webhook.id;
let authorName = req.body.username || webhook.name || "Webhook";
let authorAvatar = req.body.avatar_url || webhook.avatar || "";

let fakeUserId = `webhook-${whId}`;

let pluginConfig = JSON.parse(fs.readFileSync(path.join(process.cwd(), "plugins", "webhook", "config.json"), 'utf-8'));

serverconfig.servermembers[fakeUserId] = {
id: fakeUserId,
name: authorName,
icon: authorAvatar,
isOnline: false,
status: "",
aboutme: pluginConfig.default_aboutme || "A Webhook",
joined: Date.now()
};

let member = {
group: webhook.group,
category: webhook.category,
channel: webhook.channel,
message: sanitizeInput(content),
timestamp: new Date().getTime(),
messageId: messageid,
reply: { messageId: null },
room: `${webhook.group}-${webhook.category}-${webhook.channel}`,
author: {
id: fakeUserId,
name: authorName,
icon: authorAvatar,
isWebhook: true
}
};

member.author = Object.assign(member.author, getCastingMemberObject(serverconfig.servermembers[fakeUserId]));
member = Object.assign(member, getCastingMemberObject(member));

await saveChatMessage(member, null);
io.emit("messageCreate", member);

res.status(204).send();
} catch (e) {
console.error("[Webhook Plugin] Error in webhook receiver:", e);
res.status(500).json({ error: "Internal Server Error" });
}
});

}
71 changes: 71 additions & 0 deletions webhook/sockets/webhookSocket.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import fs from "fs";
import path from "path";
import { generateId, validateMemberId } from "../../../modules/functions/main.mjs";
import { hasPermission } from "../../../modules/functions/chat/main.mjs";

const WEBHOOKS_FILE = path.join(process.cwd(), "configs", "webhooks.json");

export default (socket) => {
socket.on('getWebhooks', (data, response) => {
if (validateMemberId(data?.id, socket, data?.token) === true) {
if (!hasPermission(data.id, "manageChannels", data.channelId)) {
return response({ type: 'error', error: "No permission" });
}

const channelId = data.channelId;
const webhooks = fs.existsSync(WEBHOOKS_FILE) ? JSON.parse(fs.readFileSync(WEBHOOKS_FILE, 'utf-8')) : {};

let channelWebhooks = Object.values(webhooks).filter(wh => wh.channel === channelId);
response({ type: 'success', data: channelWebhooks });
}
});

socket.on('createWebhook', (data, response) => {
if (validateMemberId(data?.id, socket, data?.token) === true) {
if (!hasPermission(data.id, "manageChannels", data.channelId)) {
return response({ type: 'error', error: "No permission" });
}

const group = data.group;
const category = data.category;
const channelId = data.channelId;
const name = data.name || "New Webhook";

const webhooks = fs.existsSync(WEBHOOKS_FILE) ? JSON.parse(fs.readFileSync(WEBHOOKS_FILE, 'utf-8')) : {};
let whId = generateId(8);
let whToken = generateId(32);

let newWh = {
id: whId,
token: whToken,
group: group,
category: category,
channel: channelId,
name: name,
avatar: ""
};

webhooks[whId] = newWh;
fs.writeFileSync(WEBHOOKS_FILE, JSON.stringify(webhooks, null, 2));

response({ type: 'success', data: newWh });
}
});

socket.on('deleteWebhook', (data, response) => {
if (validateMemberId(data?.id, socket, data?.token) === true) {
if (!hasPermission(data.id, "manageChannels", data.channelId)) {
return response({ type: 'error', error: "No permission" });
}
const webhooks = fs.existsSync(WEBHOOKS_FILE) ? JSON.parse(fs.readFileSync(WEBHOOKS_FILE, 'utf-8')) : {};

if (webhooks[data.webhookId]) {
delete webhooks[data.webhookId];
fs.writeFileSync(WEBHOOKS_FILE, JSON.stringify(webhooks, null, 2));
response({ type: 'success' });
} else {
response({ type: 'error', error: "Not found" });
}
}
});
};
91 changes: 91 additions & 0 deletions webhook/web/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
if (!window.webhookPluginLoaded) {
window.webhookPluginLoaded = true;

document.addEventListener("pagechange", (e) => {
if (e.detail.page === "channel-info") {
setTimeout(injectWebhookUI, 100);
}
});

if (window.location.href.includes("page=channel-info")) {
setTimeout(injectWebhookUI, 500);
}

function injectWebhookUI() {
let channelSettings = document.getElementById("channel_settings");
if (!channelSettings) return;

if (document.getElementById("channel_webhooks")) return;

let webhookHtml = `
<br>
<h3>Webhooks</h3>
<div id="channel_webhooks"></div>
<button id="createWebhookBtn" onclick="window.webhookPlugin.createWebhook()" style="padding: 8px; margin-top: 10px; background: hsl(from var(--main) h s calc(l * 6)); border: none; border-radius: 4px; color: white; cursor: pointer;">Create Webhook</button>
<br><br>
`;
channelSettings.insertAdjacentHTML("afterend", webhookHtml);

window.webhookPlugin.loadWebhooks();
}

window.webhookPlugin = {
loadWebhooks: function () {
let channelId = new URLSearchParams(window.location.search).get("id");
if (!channelId) return;

socket.emit("getWebhooks", { id: UserManager.getID(), token: UserManager.getToken(), channelId: channelId }, (response) => {
if (response.type === "success") {
let container = document.getElementById("channel_webhooks");
if (!container) return;
container.innerHTML = "";
for (let wh of response.data) {
container.innerHTML += `
<div style="background: hsl(from var(--main) h s calc(l * 3)); padding: 10px; margin-top: 5px; border-radius: 5px; display: flex; justify-content: space-between; align-items: center;">
<div>
<strong>${wh.name}</strong><br>
<span style="font-size: 11px; color: gray; user-select: all;">${window.location.origin}/api/webhooks/${wh.token}</span>
</div>
<button onclick="window.webhookPlugin.deleteWebhook('${wh.id}')" style="background: indianred; border: none; padding: 5px 10px; border-radius: 4px; color: white; cursor: pointer;">Delete</button>
</div>
`;
}
}
});
},
createWebhook: function () {
let name = prompt("Webhook Name:");
if (!name) return;

let channelId = new URLSearchParams(window.location.search).get("id");
if (!channelId) return;

getChannelTree().then(channels => {
let channelConfigPath = getChannelPathFromGroupConfig(channels, channelId);
if (!channelConfigPath) return alert("Channel not found in config");
socket.emit("createWebhook", {
id: UserManager.getID(),
token: UserManager.getToken(),
group: channelConfigPath.groupId,
category: channelConfigPath.categoryId,
channelId: channelId,
name: name
}, (response) => {
if (response.type === "success") {
window.webhookPlugin.loadWebhooks();
} else {
alert(response.error);
}
});
});
},
deleteWebhook: function (whId) {
if (!confirm("Delete webhook?")) return;
let channelId = new URLSearchParams(window.location.search).get("id");

socket.emit("deleteWebhook", { id: UserManager.getID(), token: UserManager.getToken(), webhookId: whId, channelId: channelId }, (res) => {
if (res.type === "success") window.webhookPlugin.loadWebhooks();
});
}
};
}