diff --git a/docs/exports.md b/docs/exports.md index 3c33483..c69143c 100644 --- a/docs/exports.md +++ b/docs/exports.md @@ -6,8 +6,11 @@ If you have other resources that you wish to be able to use information availabl - [isRolePresent](#isrolepresent) - [getDiscordId](#getDiscordId) - [getRoles](#getroles) +- [getMemberHighestRole](#getMemberHighestRole) - [getName](#getname) - [log](#log) +- [registerWebhook](#registerWebhook) +- [sendWebhook](#sendWebhook) ### isRolePresent Returns a true/false boolean if a role is present for a role id or array of role-ids @@ -70,6 +73,26 @@ local roles = exports.zdiscord:getRoles(source); local roles = exports.zdiscord:getRoles("142831624868855808"); ``` +### getMemberHighestRole +Returns a name of the member's highest role, using a discord id or source + +```js +// JAVASCRIPT EXAMPLE +// Source +const roles = global.exports.zdiscord.getMemberHighestRole(global.source); + +// Discord ID +const roles = global.exports.zdiscord.getMemberHighestRole("142831624868855808"); +``` +```lua +-- LUA EXAMPLE +-- Source +local role = exports.zdiscord:getMemberHighestRole(source); + +-- Discord ID +local role = exports.zdiscord:getMemberHighestRole("142831624868855808"); +``` + ### getName Returns an string containing the discord name/nickname for a discord id or source @@ -106,3 +129,45 @@ global.exports.zdiscord.log("modlog", "UserA Banned UserB for Reason", true, "#F -- event, message, pingRole, color (optional) exports.zdiscord:log("modlog", "UserA Banned UserB for Reason", true, "#FF0000"); ``` + +### registerWebhook +register a webhook for usage + +```js +// JAVASCRIPT EXAMPLE +// name, webhook url +global.exports.zdiscord.registerWebhook("name", "https://discord.com/api/webhooks/id/key"); + +``` +```lua +-- LUA EXAMPLE +-- name, webhook url +exports.zdiscord:registerWebhook("name", "https://discord.com/api/webhooks/id/key"); +``` + +### sendWebhook +send a webhook message, using a name of a webhook you registered + +```js +// JAVASCRIPT EXAMPLE +// webhook name, { title, description, footer, username, avatarURL, color } +global.exports.zdiscord.sendWebhook("name", { + title: "title", + description: "desc", + footer: "footer", + username: "username", + color: "#FFFFFF" +}); + +``` +```lua +-- LUA EXAMPLE +-- webhook name, { title, description, footer, username, avatarURL, color } +exports.zdiscord:sendWebhook("name", { + title = "Chat Message", + description = "This is a chat message test!", + footer = "Wow Footer", + username = 'Chat', + color = "#FFFFFF" +}); +``` diff --git a/optional addons/status_message.js b/optional addons/status_message.js new file mode 100644 index 0000000..c429dc4 --- /dev/null +++ b/optional addons/status_message.js @@ -0,0 +1,119 @@ +/** + * This file is part of zdiscord. + * Copyright (C) 2021 Tony/zfbx + * source: + * + * This work is licensed under the Creative Commons + * Attribution-NonCommercial-ShareAlike 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ + * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * + * This Addon was created by [ItzDabbzz](https://github.com/ItzDabbzz) + * + * This addon sends a message with status updates every x number of minutes in a specified channel + * copy this into your `server/addons` folder and edit the channelId to the channel id you want messages sent. + */ + +const { MessageEmbed, MessageActionRow, MessageButton } = require("discord.js"); + + +class StatusMessage { + constructor(z) { + // Minutes + this.timerDelay = 5; + // Channel id to send server status updates + this.channelId = "000000000000000000"; + // City join ip + this.cityJoinURL = 'cfx.re/join/'; + // store message id so we can edit it later + this.messageId = null; + // object to store players in the city + this.inCity = {} + + + this.z = z; + on("zdiscord:ready", async () => { + this.post(); + this.start(); + }); + } + + async start() { + setInterval(() => { + this.post(); + }, 1000 * 60 * this.timerDelay); + + on("playerJoining", async (oldId) => { + const source = global.source; + const member = this.z.bot.getMemberFromSource(source); + if (!member) return; + const playerName = GetPlayerName(source); + this.inCity[member.id] = { + name: playerName, + id: source, + } + this.post(); + }) + + on("playerDropped", async (reason) => { + const source = global.source; + const member = this.z.bot.getMemberFromSource(source); + delete this.inCity[member.id]; + this.post(); + }) + } + + async post() { + try { + const channel = await this.z.bot.channels.fetch(this.channelId); + let playerMessage = `**Current Connected Player(s):**\n\n`; + + for (const [key, value] of Object.entries(this.inCity)) { + playerMessage += `[${value.id}] ${value.name} - <@${key}>\n`; + } + + if (Object.keys(this.inCity).length === 0) playerMessage = "No Players Online"; + + const embed = new MessageEmbed() + .setTitle(`${this.z.config.FiveMServerName}`) + .setDescription(`${playerMessage}\n`) + .addFields( + { + name: "Online Players", + value: `\`\`\`${GetNumPlayerIndices()}/${GetConvar("sv_maxClients", "48")}\`\`\``, + inline: true + }, + { + name: "Uptime", + value: `\`\`\`${z.utils.timeformat((GetGameTimer() / 1000))}\`\`\``, + inline: true + } + ) + .setColor("#00ff00") + .setTimestamp() + const join = new MessageButton() + .setLabel('Join City') + .setURL(this.cityJoinURL) + .setStyle('LINK') + const row = new MessageActionRow() + .addComponents(join); + + if (this.messageId) { + const message = await channel.messages.fetch(this.messageId); + message.edit({ embeds: [embed] }); + } else { + const message = await channel.send({ + embeds: [embed], + components: [row], + }); + this.messageId = message.id; + } + } + catch (e) { + console.error(e); + } + } + +} + +module.exports = StatusMessage; diff --git a/server/bot.js b/server/bot.js index 85e161c..00d4c94 100644 --- a/server/bot.js +++ b/server/bot.js @@ -9,7 +9,7 @@ * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. */ -const { Client, Collection, MessageEmbed, MessageActionRow, MessageButton } = require("discord.js"); +const { Client, Collection, MessageEmbed, MessageActionRow, MessageButton, WebhookClient } = require("discord.js"); const { readdirSync } = require("fs"); class Bot extends Client { @@ -28,6 +28,7 @@ class Bot extends Client { this.utils = z.utils; this.Embed = MessageEmbed; this.commands = new Collection(); + this.webhooks = new Collection(); this.arrayOfCommands = []; if (this.enabled) this.start(); @@ -186,6 +187,21 @@ class Bot extends Client { return member.roles.cache.map(r => r.id); } + /** Get the highest role of a discord member + * @param {number|object|string} member - source | member | discordid + * @returns {String|null} - Highest role object or null if no roles + */ + getMemberHighestRole(member) { + if (!member || !this.enabled) return null; + member = this.parseMember(member); + if (!member) return null; + const role = member.roles.cache + .sort((a, b) => b.position - a.position) + .first(); + if (!role) return null; + return role.name; + } + hasPermission(member, level) { switch (level) { case "mod": @@ -204,6 +220,63 @@ class Bot extends Client { } } + /** Register a new webhook with a friendly name + * @param {string} name - Friendly name to reference this webhook + * @param {string} url - Discord webhook url + * @returns {Promise} - True if registration successful, false if failed + */ + async registerWebhook(name, url) { + try { + const webhook = new WebhookClient({ url: url}); + this.webhooks.set(name, webhook); + return true; + } catch (error) { + this.log.error(`Failed to register webhook ${name}: ${error}`); + return false; + } + } + + /** Send a message through a registered webhook + * @param {string} name - Friendly name of the registered webhook + * @param {Object} options - Message options + * @param {string} [options.title] - Embed title + * @param {string} [options.description] - Embed description + * @param {string} [options.color] - Hex color code + * @param {string} [options.footer] - Footer text + * @param {string} [options.username] - Override webhook username + * @param {string} [options.avatarURL] - Override webhook avatar + * @returns {Promise} - True if message sent successfully, false if failed + */ + async sendWebhookMessage(name, options) { + const webhook = this.webhooks.get(name); + if (!webhook) { + this.log.error(`Webhook ${name} not found`); + return false; + } + + const embed = new MessageEmbed() + .setTitle(options.title || '') + .setDescription(options.description || '') + .setColor(options.color || '#ffffff') + .setTimestamp(); + + if (options.footer) { + embed.setFooter({ text: options.footer }); + } + + try { + await webhook.send({ + username: options.username || webhook.name, + avatarURL: options.avatarURL, + embeds: [embed] + }); + return true; + } catch (error) { + this.log.error(`Failed to send webhook message: ${error}`); + return false; + } + } + } module.exports = Bot; diff --git a/server/server.js b/server/server.js index 06e2501..8dc19b0 100644 --- a/server/server.js +++ b/server/server.js @@ -129,6 +129,10 @@ global.exports("getRoles", (identifier) => { return z.bot.getMemberRoles(identifier); }); +global.exports("getMemberHighestRole", (identifier) => { + return z.bot.getMemberHighestRole(identifier); +}); + global.exports("getName", (identifier) => { const member = z.bot.parseMember(identifier); return member.displayName || false; @@ -137,3 +141,11 @@ global.exports("getName", (identifier) => { global.exports("getDiscordId", (identifier) => { return z.utils.getPlayerDiscordId(identifier); }); + +global.exports("registerWebhook", (name, webhook) => { + return z.bot.registerWebhook(name, webhook); +}); + +global.exports("sendWebhook", (name, content) => { + return z.bot.sendWebhookMessage(name, content); +});