diff --git a/webhook/config.json b/webhook/config.json new file mode 100644 index 0000000..726c94a --- /dev/null +++ b/webhook/config.json @@ -0,0 +1,7 @@ +{ + "title": "Webhook System", + "author": "Reeperk", + "version": 1, + "enabled": true, + "default_aboutme": "A Webhook System" +} \ No newline at end of file diff --git a/webhook/functions/plugin_onLoad.mjs b/webhook/functions/plugin_onLoad.mjs new file mode 100644 index 0000000..d16dca4 --- /dev/null +++ b/webhook/functions/plugin_onLoad.mjs @@ -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" }); + } + }); + +} diff --git a/webhook/sockets/webhookSocket.mjs b/webhook/sockets/webhookSocket.mjs new file mode 100644 index 0000000..71007f3 --- /dev/null +++ b/webhook/sockets/webhookSocket.mjs @@ -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" }); + } + } + }); +}; diff --git a/webhook/web/main.js b/webhook/web/main.js new file mode 100644 index 0000000..454c7bc --- /dev/null +++ b/webhook/web/main.js @@ -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 = ` +
+

Webhooks

+
+ +

+ `; + 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 += ` +
+
+ ${wh.name}
+ ${window.location.origin}/api/webhooks/${wh.token} +
+ +
+ `; + } + } + }); + }, + 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(); + }); + } + }; +}