Skip to content

Commit

Permalink
Update template colors & add slash cmds
Browse files Browse the repository at this point in the history
  • Loading branch information
DEVTomatoCake committed Nov 3, 2023
1 parent 28db045 commit f1bfdcd
Show file tree
Hide file tree
Showing 77 changed files with 839 additions and 671 deletions.
133 changes: 132 additions & 1 deletion bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,143 @@ const { botToken } = require("./config.json")
bot.login(botToken)
module.exports = bot

const oauth = require("./util/oauth.js")
const pool = require("./util/setupDB.js")

const registerSlashcommands = () => {

Check failure on line 20 in bot.js

View workflow job for this annotation

GitHub Actions / Codestandards

'registerSlashcommands' is assigned a value but never used. Allowed unused vars must match /_/u
bot.application.commands.set([{
name: "hook",
type: Discord.ApplicationCommandType.ChatInput,
description: "Create and edit GitHub event webhooks",
defaultMemberPermissions: ["ManageGuild"],
dmPermission: false,
options: [{
name: "create",
type: Discord.ApplicationCommandOptionType.Subcommand,
description: "Create a new webhook",
options: [{
name: "name",
type: Discord.ApplicationCommandOptionType.String,
description: "The name of the webhook",
maxLength: 32,
required: true
},{
name: "events",
type: Discord.ApplicationCommandOptionType.String,
description: "Events to trigger the webhook, separated by commas",
maxLength: 220
},{
name: "actions",
type: Discord.ApplicationCommandOptionType.String,
description: "Actions (of events) to trigger the webhook, separated by commas",
maxLength: 220
}]
},{
name: "edit-message",
type: Discord.ApplicationCommandOptionType.Subcommand,
description: "Edit the message sent on an event",
options: [{
name: "hook",
type: Discord.ApplicationCommandOptionType.String,
description: "The webhook to edit",
autocomplete: true,
required: true
}]
},{
name: "delete",
type: Discord.ApplicationCommandOptionType.Subcommand,
description: "Delete a webhook",
options: [{
name: "hook",
type: Discord.ApplicationCommandOptionType.String,
description: "The webhook to delete",
autocomplete: true,
required: true
}]
}]
}])
}

bot.on("ready", () => {
bot.user.setPresence({activities: [{name: "Custom Status", state: "Customizable GitHub hooks!", type: Discord.ActivityType.Custom}], status: "dnd"})
bot.user.setPresence({activities: [{name: "Custom Status", state: "Customizable GitHub hooks!", type: Discord.ActivityType.Custom}], status: "online"})
//registerSlashcommands()
})

bot.on("guildCreate", guild => {
const embed = new Discord.MessageEmbed()
.setColor(Discord.Colors.Green)
.setTitle(guild.name)
.setThumbnail(guild.iconURL())
.addField("Member count", "" + guild.memberCount, true)
.addField("Owner", "<@" + guild.ownerId + ">", true)
bot.channels.cache.get("1169875077110693951").send({embeds: [embed]})
})

bot.on("guildDelete", guild => {
pool.query("DELETE FROM `hook` WHERE `guild` = ?", [guild.id])

const embed = new Discord.MessageEmbed()
.setColor(Discord.Colors.Red)
.setTitle(guild.name)
.setThumbnail(guild.iconURL())
.addField("Member count", "" + guild.memberCount, true)
.addField("Owner", "<@" + guild.ownerId + ">", true)
bot.channels.cache.get("1169875077110693951").send({embeds: [embed]})
})

const channelOrWebhookRow = new Discord.ActionRowBuilder()
.addComponents(
new Discord.ChannelSelectMenuBuilder()
.setCustomId("setup_channel")
.setChannelTypes([Discord.ChannelType.GuildText, Discord.ChannelType.GuildAnnouncement, Discord.ChannelType.GuildVoice, Discord.ChannelType.GuildStageVoice, Discord.ChannelType.GuildForum])
)

bot.on("interactionCreate", async interaction => {
if (interaction.type == Discord.InteractionType.ApplicationCommand) {
if (interaction.commandName == "hook") {
const subcommand = interaction.options.getSubcommand()

if (subcommand == "create") {
const msg = await interaction.reply({
content: "You're creating the webhook **" + Discord.escapeMarkdown(interaction.options.getString("name")) + "**.\n\nNow, choose the channel to send the messages to.\n**Tip**: Start typing and the channels matching your input will appear.",
components: [channelOrWebhookRow],
fetchReply: true,
ephemeral: true
})

msg.createMessageComponentCollector({componentType: Discord.ComponentType.ChannelSelect}).on("collect", async i => {
let id = oauth.generateToken(8)
while (true) {

Check failure on line 123 in bot.js

View workflow job for this annotation

GitHub Actions / Codestandards

Unexpected constant condition
const [rows] = await pool.query("SELECT * FROM `hook` WHERE `id` = ?", [id])
if (rows.length == 0) break
id = oauth.generateToken(8)
}

const secret = oauth.generateToken()
await pool.query(
"INSERT INTO `hook` (`id`, `name`, `server`, `webhook`, `channel`, `message`, `secret`, `filterEvent`, `filterAction`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
[id, interaction.options.getString("name"), interaction.guild.id, null, i.values[0], null, secret,
interaction.options.getString("events") ? JSON.stringify(interaction.options.getString("events").split(",")) : null,
interaction.options.getString("actions") ? JSON.stringify(interaction.options.getString("actions").split(",")) : null]
)

i.reply({
content: "Successfully created the hook!\n\nYou can now use it on GitHub to receive events in <#" + i.values[0] + ">.\n\n" +
"Full POST URL: `https://disgithook-api.tomatenkuchen.com/hook/" + id + "/" + secret +
"`\nMore secure POST URL with secret in header: `https://disgithook-api.tomatenkuchen.com/hook/" + id + "`\nSecret: `" + secret + "`",
ephemeral: true
})
})
} else if (subcommand == "delete") {
const [rows] = await pool.query("SELECT * FROM `hook` WHERE `id` = ? AND `server` = ?", [interaction.options.getString("hook"), interaction.guild.id])
if (rows.length == 0) return interaction.reply({content: "The hook `" + interaction.options.getString("hook") + "` doesn't exist or doesn't belong to this server.", ephemeral: true})

await pool.query("DELETE FROM `hook` WHERE `id` = ?", [interaction.options.getString("hook")])
interaction.reply({content: "Successfully deleted the hook **" + rows[0].name + "**.", ephemeral: true})
}
}
} else if (interaction.type == Discord.InteractionType.ApplicationCommandAutocomplete) {
const [rows] = await pool.query("SELECT * FROM `hook` WHERE `server` = ?", [interaction.guild.id])
interaction.respond(rows.slice(0, 25).map(hook => ({name: hook.name, value: hook.id})))
}
})
15 changes: 8 additions & 7 deletions generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ const path = require("node:path")

Object.keys(events).forEach(event => {
fs.writeFile(path.join(__dirname, "templates", event + ".js"),
events[event].length == 0 ?
"const color = require(\"../util/color.js\")\n\n" +
(events[event].length == 0 ?
"module.exports = [\n" +
"\t{\n" +
"\t\tembeds: [{\n" +
Expand All @@ -86,26 +87,26 @@ Object.keys(events).forEach(event => {
"\t\t\t\ticon_url: \"{{ sender.avatar_url }}\"\n" +
"\t\t\t},\n" +
"\t\t\ttitle: \"`" + event + "`\",\n" +
"\t\t\tcolor: 0\n" +
"\t\t\tcolor: color(\"black\")\n" +
"\t\t}]\n" +
"\t}\n" +
"]\n"
:
"module.exports = [\n" +
"module.exports = [\n\t" +
events[event].map(action => {
return "\t{\n" +
return "{\n" +
"\t\taction: \"" + action + "\",\n" +
"\t\tembeds: [{\n" +
"\t\t\tauthor: {\n" +
"\t\t\t\tname: \"{{ sender.login }}\",\n" +
"\t\t\t\ticon_url: \"{{ sender.avatar_url }}\"\n" +
"\t\t\t},\n" +
"\t\t\ttitle: \"`" + event + "` (`" + action + "`)\",\n" +
"\t\t\tcolor: 0\n" +
"\t\t\tcolor: color(\"" + (action == "created" || action == "resolved" || action == "approved" ? "green" : (action == "deleted" || action == "rejected" ? "red" : "black")) + "\")\n" +
"\t\t}]\n" +
"\t}"
}).join(",\n") +
"]\n",
}).join(",") +
"\n]\n"),
e => {
if (e) throw e
}
Expand Down
32 changes: 19 additions & 13 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const bot = require("./bot.js")

const { botId, botSecret, userAgent, domain, port, cookieSecret } = require("./config.json")

const path = require("node:path")
const encode = s => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;")

const oauth = require("./util/oauth.js")
Expand All @@ -25,12 +26,12 @@ app.use((req, res, next) => {
if (!req.headers["cf-connecting-ip"]) return res.status(400).send("Direct access is not allowed")
const ip = "%" + req.headers["cf-connecting-ip"]

if (req.path.startsWith("/hook/")) return next()

if (ratelimit30s[ip] && ratelimit30s[ip] >= 30) return res.status(429).send("Too many requests in the last 30 seconds")
if (ratelimit5m[ip] && ratelimit5m[ip] >= 130) return res.status(429).send("Too many requests in the last 5 minutes")
if (ratelimitGlobal5m >= 800) return res.status(429).send("Too many requests in the last 5 minutes")

if (req.path.startsWith("/hook/")) return next()

if (ratelimit30s[ip]) ratelimit30s[ip]++
else ratelimit30s[ip] = 1
if (ratelimit5m[ip]) ratelimit5m[ip]++
Expand Down Expand Up @@ -107,8 +108,8 @@ app.post("/servers/:id/hooks", async (req, res) => {

const secret = oauth.generateToken()
await pool.query(
"INSERT INTO `hook` (`id`, `server`, `webhook`, `name`, `avatar`, `username`, `channel`, `message`, `secret`, `filterEvent`, `filterAction`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
[id, req.params.id, req.body.webhook, req.body.name, req.body.avatar, req.body.username, req.body.channel, req.body.message, secret, req.body.filterEvent, req.body.filterAction]
"INSERT INTO `hook` (`id`, `name`, `server`, `webhook`, `channel`, `message`, `secret`, `filterEvent`, `filterAction`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
[id, req.body.name, req.params.id, req.body.webhook, req.body.channel, req.body.message, secret, JSON.stringify(req.body.filterEvent), JSON.stringify(req.body.filterAction)]
)
res.send({success: true, id, secret})
})
Expand All @@ -127,8 +128,8 @@ app.post("/servers/:id/hooks/:hook", async (req, res) => {
if (hook.server != req.params.id) return res.status(401).send({success: false, error: "Invalid server ID"})

await pool.query(
"UPDATE `hook` SET `webhook` = ?, `name` = ?, `avatar` = ?, `username` = ?, `channel` = ?, `message` = ?, `filterEvent` = ?, `filterAction` = ? WHERE `id` = ?",
[req.body.webhook, req.body.name, req.body.avatar, req.body.username, req.body.channel, req.body.message, req.body.filterEvent, req.body.filterAction, req.params.hook]
"UPDATE `hook` SET `webhook` = ?, `name` = ?, `channel` = ?, `message` = ?, `filterEvent` = ?, `filterAction` = ? WHERE `id` = ?",
[req.body.webhook, req.body.name, req.body.channel, req.body.message, JSON.stringify(req.body.filterEvent), JSON.stringify(req.body.filterAction), req.params.hook]
)
res.send({success: true})
})
Expand Down Expand Up @@ -238,17 +239,18 @@ const hookFunc = async (req, res) => {

const githubEvent = req.headers["x-github-event"]
if (githubEvent == "ping") return res.sendStatus(204)
if (hook.filterEvent && !hook.filterEvent.includes(githubEvent)) return res.status(202).send({success: true, info: "Event " + encode(githubEvent) + " is disabled in settings for this hook"})
if (hook.filterEvent && !JSON.parse(hook.filterEvent).includes(githubEvent)) return res.status(202).send({success: true, info: "Event " + encode(githubEvent) + " is disabled in settings for this hook"})

const data = req.body
const action = data.action
if (hook.filterAction && !hook.filterAction.includes(action)) return res.status(202).send({success: true, info: "Action " + encode(action) + " is disabled in settings for this hook"})
if (hook.filterAction && !JSON.parse(hook.filterAction).includes(action)) return res.status(202).send({success: true, info: "Action " + encode(action) + " is disabled in settings for this hook"})

let message = hook.message
let message = hook.message || require(path.join(__dirname, "templates", githubEvent + ".js")).find(msg => msg.action == action) || require(path.join(__dirname, "templates", githubEvent + ".js"))[0]
console.log(message)
const recursiveFunc = (obj, path = "") => {

Check failure on line 250 in index.js

View workflow job for this annotation

GitHub Actions / Codestandards

'path' is already declared in the upper scope on line 6 column 7
for (const property in obj) {
if (typeof obj[property] == "object") recursiveFunc(obj[property], path + property + ".")
// Possible syntax: {sender.login} or {{ sender.login }}
// Possible syntax: {sender.login} or {{ sender.login }} or something in between
else message = message.replace(new RegExp("{{? ?" + path + property + " ?}}?", "gi"), obj[property])
}
}
Expand All @@ -261,7 +263,12 @@ const hookFunc = async (req, res) => {
return res.status(500).send("Invalid JSON in message: " + encode(e.message))
}

if (Array.isArray(parsed)) parsed = parsed.find(msg => msg.event == githubEvent && msg.action == action) || parsed.find(msg => msg.event == githubEvent) || parsed[0]
if (Array.isArray(parsed)) parsed = parsed.find(msg => {
if (!Array.isArray(msg.event)) msg.event = [msg.event]
if (!Array.isArray(msg.action)) msg.action = [msg.action]

return msg.event.includes(githubEvent) && msg.action.includes(action)
}) || parsed.find(msg => msg.event == githubEvent) || parsed[0]
if (!parsed || Object.keys(parsed).length == 0) return res.status(500).send({success: false, error: "Empty JSON in message"})

if (hook.webhook) {
Expand All @@ -273,13 +280,12 @@ const hookFunc = async (req, res) => {
if (channel) {
await channel.send(parsed)
res.sendStatus(204)
} else res.status(500).send({success: false, error: "Unable to send message of hook " + hook.id + " because the channel " + hook.channel + " does not exist"})
} else res.status(500).send({success: false, error: "Unable to send message because the channel " + hook.channel + " doesn't exist"})
}
}

app.post("/hook/:id/:secret", hookFunc)
app.post("/hook/:id", async (req, res) => {
console.log(req.headers)
if (!req.get("X-Hub-Signature-256")) return res.status(401).send("Missing X-Hub-Signature-256 header")

req.params.secret = req.get("X-Hub-Signature-256")
Expand Down
12 changes: 7 additions & 5 deletions templates/branch_protection_configuration.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const color = require("../util/color.js")

module.exports = [
{
action: "disabled",
Expand All @@ -7,17 +9,17 @@ module.exports = [
icon_url: "{{ sender.avatar_url }}"
},
title: "`branch_protection_configuration` (`disabled`)",
color: 0
color: color("black")
}]
},
{
},{
action: "enabled",
embeds: [{
author: {
name: "{{ sender.login }}",
icon_url: "{{ sender.avatar_url }}"
},
title: "`branch_protection_configuration` (`enabled`)",
color: 0
color: color("black")
}]
}]
}
]
17 changes: 9 additions & 8 deletions templates/branch_protection_rule.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const color = require("../util/color.js")

module.exports = [
{
action: "created",
Expand All @@ -7,28 +9,27 @@ module.exports = [
icon_url: "{{ sender.avatar_url }}"
},
title: "`branch_protection_rule` (`created`)",
color: 0
color: color("green")
}]
},
{
},{
action: "edited",
embeds: [{
author: {
name: "{{ sender.login }}",
icon_url: "{{ sender.avatar_url }}"
},
title: "`branch_protection_rule` (`edited`)",
color: 0
color: color("black")
}]
},
{
},{
action: "deleted",
embeds: [{
author: {
name: "{{ sender.login }}",
icon_url: "{{ sender.avatar_url }}"
},
title: "`branch_protection_rule` (`deleted`)",
color: 0
color: color("red")
}]
}]
}
]
Loading

0 comments on commit f1bfdcd

Please sign in to comment.