From 9faf4a315e739de4a08bd5e280eb2c2ce8e892a7 Mon Sep 17 00:00:00 2001 From: ION606 Date: Sun, 19 Mar 2023 18:02:21 -0400 Subject: [PATCH] Added message reply, embeds, and bot login capabilities --- structures/client/client.js | 75 +++++++++------ structures/client/handleEvents.js | 4 +- structures/guilds/channel.js | 17 ---- structures/messages/author.js | 37 +++++++ structures/messages/embed.js | 127 ++++++++++++++++++++++++ structures/messages/message.js | 154 ++++++++++++++++++++++++++++-- structures/types.js | 7 ++ utils/color_functions.js | 24 +++++ utils/colors.js | 32 +++++++ 9 files changed, 422 insertions(+), 55 deletions(-) delete mode 100644 structures/guilds/channel.js create mode 100644 structures/messages/author.js create mode 100644 structures/messages/embed.js create mode 100644 structures/types.js create mode 100644 utils/color_functions.js create mode 100644 utils/colors.js diff --git a/structures/client/client.js b/structures/client/client.js index 50982ff..a4944d8 100644 --- a/structures/client/client.js +++ b/structures/client/client.js @@ -5,6 +5,7 @@ var WebSocketClient = require('websocket').client; const WebSocketConnection = require('websocket').connection; const handleResponses = require('./handleEvents.js'); const { EventEmitter } = require('events'); +const axios = require('axios'); @@ -20,6 +21,9 @@ class Client extends EventEmitter { /** @type {String} */ #token; + + /** @type {String} */ + id; /** @type {WebSocketConnection}*/ connection; @@ -56,8 +60,8 @@ class Client extends EventEmitter { var idObj = { op: 2, d: { - token: this.#token, - intents: this.gwintents.value, //61440, + token: this.#token.replace("Bot ", ""), + intents: iCount, //61440, properties: { os: "linux", browser: "ion_", @@ -86,42 +90,53 @@ class Client extends EventEmitter { /** * @param {String} token */ - async login(token) { - this.ws = new WebSocketClient({maxReceivedFrameSize: Infinity}); - this.#token = token; + async login(token, isUser = false) { + if (!isUser) token = "Bot " + token; + + return new Promise((resolve, reject) => { + this.ws = new WebSocketClient({maxReceivedFrameSize: Infinity}); + this.#token = token; - this.ws.on('connect', async (connection) => { - connection.on('message', async (msg) => { - const data = JSON.parse(msg.utf8Data); + this.ws.on('connect', async (connection) => { + connection.on('message', async (msg) => { + const data = JSON.parse(msg.utf8Data); - const response = await handleResponses(data); + const response = await handleResponses(data, token); - if (response.op == 10) { this.#startHeartBeat(response.heartBeat, token); } - else if (response.op == 0) { - if (response.t == gateWayEvents.Ready) { - this.user_profile = response.profile; - this.user_settings = response.config; - this.ready(); + if (response.op == 10) { this.#startHeartBeat(response.heartBeat, token); } + else if (response.op == 0) { + if (response.t == gateWayEvents.Ready) { + this.user_profile = response.profile; + this.user_settings = response.config; + this.id = response.profile.id; + this.ready(); + } + else if (response.t == gateWayEvents.MessageCreate) { + if (data["d"]["author"]["id"] != this.user_profile.id){ + this.messageRecieved(response.message); + } + } + else console.log(response.t); + } else { + console.log(response.t); } - else if (response.t == gateWayEvents.MessageCreate) { - this.messageRecieved(response.message); - } - else console.log(response.t); - } else { - console.log(response.t); - } + }); + + connection.on('close', (code, desc) => { + console.log(`CONNECTION CLOSED WITH CODE ${code}\nREASON:\n ${desc}`); + }); + + connection.on('error', (err) => { + reject(err); + }); + + this.connection = connection; }); - connection.on('close', (code, desc) => { - console.log(`CONNECTION CLOSED WITH CODE ${code}\nREASON:\n ${desc}`); - }); + this.ws.on('connectFailed', (err) => { reject(err); }); - this.connection = connection; + this.ws.connect("wss://gateway.discord.gg/?v=10&encoding=json"); }); - - this.ws.on('connectFailed', (err) => { console.error(err); }) - - this.ws.connect("wss://gateway.discord.gg/?v=10&encoding=json"); } } diff --git a/structures/client/handleEvents.js b/structures/client/handleEvents.js index 57ade4b..4fda25d 100644 --- a/structures/client/handleEvents.js +++ b/structures/client/handleEvents.js @@ -8,7 +8,7 @@ const { message } = require('../messages/message.js'); * @param {Object} msg * @returns {Promise} */ -module.exports = async function handleEvents(msgObj) { +module.exports = async function handleEvents(msgObj, token) { return new Promise((resolve, reject) => { const op = msgObj["op"]; const t = msgObj["t"]; @@ -18,7 +18,7 @@ module.exports = async function handleEvents(msgObj) { if (op == 0 && t == gateWayEvents.Ready) { resolve({op: op, t: t, config: msgObj["d"]["user_settings"], profile: msgObj["d"]["user"] }); } else if (t == gateWayEvents.MessageCreate) { - const msg = new message(msgObj["d"]); + const msg = new message(msgObj["d"], token); resolve({op: op, t: t, message: msg}); } diff --git a/structures/guilds/channel.js b/structures/guilds/channel.js deleted file mode 100644 index 459aaab..0000000 --- a/structures/guilds/channel.js +++ /dev/null @@ -1,17 +0,0 @@ -const axios = require("axios").create({baseUrl: "https://jsonplaceholder.typicode.com/"}); - -class Channel { - /** @type {String} */ - id; - - /** - * @param {Object} inp - */ - async send(inp) { - const toSend = (typeof inp == 'string') ? {content: inp} : inp; - const response = axios.post(`${id}/messages`, { - // Authorization: - - }); - } -} \ No newline at end of file diff --git a/structures/messages/author.js b/structures/messages/author.js new file mode 100644 index 0000000..6f3af77 --- /dev/null +++ b/structures/messages/author.js @@ -0,0 +1,37 @@ +class msgAuthor { + /** @type {String} */ + id; + + /** @type {String} */ + username; + + /** @type {String} */ + global_name; + + /** @type {String} */ + display_name; + + /** @type {String} */ + avatar; + + /** @type {String} */ + avatar_decoration; + + /** @type {String} */ + discriminator; + + /** @type {Number} */ + public_flags; + + constructor(obj) { + for (const k in this) { + if (obj[k]) { + this[k] = obj[k]; + } + } + } +} + + + +module.exports = msgAuthor; \ No newline at end of file diff --git a/structures/messages/embed.js b/structures/messages/embed.js new file mode 100644 index 0000000..4f81961 --- /dev/null +++ b/structures/messages/embed.js @@ -0,0 +1,127 @@ +const colConvert = require('../../utils/color_functions.js'); + +class Embed { + /** @type {Number} */ + color; + + /** @type {String} */ + title; + + /** @type {String} */ + description; + + /** @type {String} */ + url; + + /** @type {{name: String, iconUrl: String, url: String}} */ + author; + + /** @type {[{name: String, value: String, inline?: Boolean}]} */ + fields; + + /** @type {String} */ + image; + + /** @type {String} */ + thumbnail; + + /** @type {Boolean} */ + attatchTimeStamp; + + /** @type {{text: String, iconUrl: String}} */ + footer; + + //#region Setters + + /** + * @param {String} colorStr + */ + setColor(colorStr) { + this.color = colConvert(colorStr); + return this; + } + + /** + * @param {String} title + */ + setTitle(title) { + this.title = title; + return this; + } + + /** + * @param {String} desc + */ + setDescription(desc) { + this.description = desc; + return this; + } + + /** + * @param {String} url + */ + setUr(url) { + this.url = url; + return this; + } + + /** + * @param {{name: String, iconUrl: String, url: String}} author + */ + setAuthor(author) { + this.author = author; + return this; + } + + /** + * @param {[{name: String, value: String, inline?: Boolean}]} fields + */ + setFields(fields) { + this.fields = fields; + return this; + } + + /** + * @param {String} url + */ + setImage(url) { + this.image = url; + return this; + } + + /** + * @param {String} url + */ + setThumbnail(url) { + this.thumbnail = url; + return this; + } + + setTimeStamp() { + this.attatchTimeStamp = !this.attatchTimeStamp; + return this; + } + + /** + * @param {{text: String, iconUrl: String}} footer + */ + setFooter(footer) { + this.footer = footer; + return this; + } + + /** + * @returns {Object} + */ + toJSON() { + var retObj = {}; + for (const k in this) { + if (this[k] != undefined) retObj[k] = this[k]; + } + + return retObj; + } + //#endregion +} + +module.exports = Embed; \ No newline at end of file diff --git a/structures/messages/message.js b/structures/messages/message.js index df617a2..f5737b3 100644 --- a/structures/messages/message.js +++ b/structures/messages/message.js @@ -1,10 +1,56 @@ const messageChannelTypes = require('./messageChannelTypes.js'); +const author = require('./author.js'); +const axios = require('axios'); +const Embed = require('./embed'); + + +class Channel { + /** @type {String} */ + id; + + /** @type {String} */ + #token; + + constructor(token, id) { + this.#token = token; + this.id = id; + } + + /** + * @param {Object} inp + * @returns {message} + */ + async send(inp) { + return new Promise(async (resolve) => { + const toSend = (typeof inp == 'string') ? inp : inp.content; + const config = { + headers: { + Authorization: this.#token + } + } + + var embds = undefined; + if (inp.embeds) { + embds = []; + for (const i of inp.embeds) { + embds.push(i.toJSON()); + } + } + + const response = await axios.post(`https://discord.com/api/channels/${this.id}/messages`, { + content: toSend, + message_reference: inp.message_reference || undefined, + embeds: embds + }, config); + + resolve(new message(response.data, this.#token)); + }); + } +} class message { - - - /** @type {Object} */ + /** @type {author} */ author; /** @type {Object} */ @@ -12,6 +58,21 @@ class message { /** @type {Object[]} */ mentions; + + /** @type {Object[]} */ + mention_roles; + + /** @type {Boolean} */ + mention_everyone; + + /** @type {message} */ + referenced_message; + + /** @type {String} */ + timestamp; + + /** @type {Boolean} */ + pinned; /** @type {Object} */ member; @@ -25,26 +86,107 @@ class message { /** @type {Object[]} */ attachments; + /** @type {Object[]} */ + embeds; + /** @type {String} */ guild_id; /** @type {Object} */ type; + /** @type {Channel} */ + channel + + /** @type {String} */ + #token; + + + /** + * @param {String} content + * @returns {Promise} + */ + reply(inp) { + return new Promise(async (resolve, reject) => { + const refObj = { + message_id: this.id, + channel_id: this.channel.id, + guild_id: this.guild_id, + fail_if_not_exists: false //when sending, whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message, default true + } + + const toSend = (typeof inp == 'string') ? {content: inp} : inp; + toSend['message_reference'] = refObj; + + resolve(await this.channel.send(toSend)); + }); + } + + + delete() { + return new Promise(async (resolve, reject) => { + try { + const config = { + headers: { + Authorization: this.#token + } + } + const response = await axios.delete(`https://discord.com/api/channels/${this.channel.id}/messages/${this.id}`, config); + + resolve(response.data); + } catch(err) { + reject(err); + } + + }) + } + + + /** + * @param {{content: String, embeds: [Object]} | String} inp + */ + async edit(inp) { + return new Promise(async (resolve, reject) => { + const newMsg = (typeof inp == "string") ? {content: inp} : inp; + + const config = { + headers: { + Authorization: this.#token + } + } + + axios.patch(`https://discord.com/api/channels/${this.channel.id}/messages/${this.id}`, newMsg, config).then((response) => { + resolve(response.data); + }).catch((err) => { + reject(`REQUEST FAILED WITH CODE ${err.response.data.code} AND THE FOLLOWING REASON:\n${err.response.data.message}`); + }); + }); + } + /** * @param {Object} msgRaw */ - constructor(msgRaw) { + constructor(msgRaw, token) { + this.#token = token; + for (const k in this) { if (msgRaw[k] != undefined) { if (k == 'type') { this.type = (msgRaw['guild_id']) ? msgRaw[k] : 1; - } else { + } + else if (k == "author") { + this[k] = new author(msgRaw[k]); + } + else { + if (k == 'channel_id') { + this.channel = new Channel(this.#token, msgRaw[k]); + } + this[k] = msgRaw[k]; } } - } + } } } diff --git a/structures/types.js b/structures/types.js new file mode 100644 index 0000000..bf2fc7a --- /dev/null +++ b/structures/types.js @@ -0,0 +1,7 @@ +const {message} = require('./messages/message'); +const {Client, gateWayIntents} = require('./client/client.js'); +const Embed = require('./messages/embed'); +const messageChannelTypes = require('./messages/messageChannelTypes'); + + +module.exports = { message, Client, gateWayIntents, Embed, messageChannelTypes } \ No newline at end of file diff --git a/utils/color_functions.js b/utils/color_functions.js new file mode 100644 index 0000000..54532f8 --- /dev/null +++ b/utils/color_functions.js @@ -0,0 +1,24 @@ +const Colors = require('./colors.js'); + +/** + * Resolves a ColorResolvable into a color number. + * @param {String} color Color to resolve + * @returns {Number} A color + */ +function resolveColor(color) { + if (typeof color === 'string') { + if (color === 'Random') return Math.floor(Math.random() * (0xffffff + 1)); + if (color === 'Default') return 0; + color = Colors[color] ?? parseInt(color.replace('#', ''), 16); + } else if (Array.isArray(color)) { + color = (color[0] << 16) + (color[1] << 8) + color[2]; + } + + if (color < 0 || color > 0xffffff) throw new RangeError('COLOR_RANGE'); + else if (Number.isNaN(color)) throw new TypeError('COLOR_CONVERT'); + + return color; +} + + +module.exports = resolveColor; \ No newline at end of file diff --git a/utils/colors.js b/utils/colors.js new file mode 100644 index 0000000..f5ab0a8 --- /dev/null +++ b/utils/colors.js @@ -0,0 +1,32 @@ +module.exports = { + Default: 0x000000, + White: 0xffffff, + Aqua: 0x1abc9c, + Green: 0x57f287, + Blue: 0x3498db, + Yellow: 0xfee75c, + Purple: 0x9b59b6, + LuminousVividPink: 0xe91e63, + Fuchsia: 0xeb459e, + Gold: 0xf1c40f, + Orange: 0xe67e22, + Red: 0xed4245, + Grey: 0x95a5a6, + Navy: 0x34495e, + DarkAqua: 0x11806a, + DarkGreen: 0x1f8b4c, + DarkBlue: 0x206694, + DarkPurple: 0x71368a, + DarkVividPink: 0xad1457, + DarkGold: 0xc27c0e, + DarkOrange: 0xa84300, + DarkRed: 0x992d22, + DarkGrey: 0x979c9f, + DarkerGrey: 0x7f8c8d, + LightGrey: 0xbcc0c0, + DarkNavy: 0x2c3e50, + Blurple: 0x5865f2, + Greyple: 0x99aab5, + DarkButNotBlack: 0x2c2f33, + NotQuiteBlack: 0x23272a, +}; \ No newline at end of file