diff --git a/.gitignore b/.gitignore index 95b863a..5f6800b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ config.json node_modules +debug.log diff --git a/structures/DataManager.js b/structures/DataManager.js index 5ee477a..26bf34c 100644 --- a/structures/DataManager.js +++ b/structures/DataManager.js @@ -2,5 +2,5 @@ export class DataManager { /** @type {import('./client/client.js').Client} */ client; - constructor(c) { this.client = c; } + constructor(c) { this.client = c; Object.defineProperty(this, 'client', { enumerable: false }); } } \ No newline at end of file diff --git a/structures/client/client.js b/structures/client/client.js index 989cc7f..02425ca 100644 --- a/structures/client/client.js +++ b/structures/client/client.js @@ -154,7 +154,6 @@ export class Client extends EventEmitter { this.axiosCustom.interceptors.response.use((response) => { return response; }, function (err) { - // console.log(err); throw `REQUEST FAILED WITH STATUS CODE ${err.response.status} AND REASON "${err.response.data.message}"`; }); diff --git a/structures/gateway/dispatch.js b/structures/gateway/dispatch.js index 9bf794e..7f0120a 100644 --- a/structures/gateway/dispatch.js +++ b/structures/gateway/dispatch.js @@ -1,3 +1,6 @@ +/** + * @enum {string} + */ export default Object.freeze({ ApplicationCommandPermissionsUpdate: "APPLICATION_COMMAND_PERMISSIONS_UPDATE", ChannelCreate: "CHANNEL_CREATE", diff --git a/structures/gateway/intents.js b/structures/gateway/intents.js index 231d483..47a2735 100644 --- a/structures/gateway/intents.js +++ b/structures/gateway/intents.js @@ -1,3 +1,6 @@ +/** + * @enum {number} + */ export const gateWayIntents = Object.freeze({ Guilds: 1 << 0, GuildMembers: 1 << 1, diff --git a/structures/guilds/Channel.js b/structures/guilds/Channel.js index 859a2b8..fb825ed 100644 --- a/structures/guilds/Channel.js +++ b/structures/guilds/Channel.js @@ -65,23 +65,26 @@ export class Channel extends DataManager { */ async send(inp) { return new Promise(async (resolve) => { - const content = (typeof inp == 'string') ? inp : inp.content; - - var embds = undefined; - if (inp.embeds) { - embds = []; - for (const i of inp.embeds) { - embds.push(i.toJSON()); + try { + var embds = undefined; + if (inp.embeds) { + embds = []; + for (const i of inp.embeds) { + embds.push(i.toJSON()); + } } + + var toSend = (typeof inp == 'string') ? { content: inp } : structuredClone(inp); + toSend["embeds"] = embds; + toSend["message_reference"] = inp.message_reference || undefined; + toSend = Object.fromEntries(Object.entries(toSend).filter((o) => o[1] != undefined)); + + const response = await this.client.axiosCustom.post(`/channels/${this.id}/messages`, toSend); + + resolve(new message(response.data, this.client)); + } catch (err) { + throw err; } - - const toSend = (typeof inp == 'string') ? { content: inp } : structuredClone(inp); - toSend["embeds"] = embds; - toSend["message_reference"] = inp.message_reference || undefined; - - const response = await this.client.axiosCustom.post(`/channels/${this.id}/messages`, toSend); - - resolve(new message(response.data, this.client)); }); } diff --git a/structures/interactions/Button.js b/structures/interactions/Button.js index 8f7ada5..12175cc 100644 --- a/structures/interactions/Button.js +++ b/structures/interactions/Button.js @@ -27,10 +27,19 @@ export class Button { toObj() { var obj = {type: 2}; for (const i in this) { + if (i == "url" && this.style != MessageButtonStyles.LINK) { + if (this.url != undefined) throw "CUSTOM ID MISSING"; + } + if (i == "custom_id" && this.style == MessageButtonStyles.LINK) { + if (this.custom_id != undefined) throw "BUTTONS OF TYPE \"LINK\" CAN NOT HAVE A CUSTOM ID"; + } obj[i] = this[i]; } return obj; } - constructor() {} + /** + * @param {MessageButtonStyles} style + */ + constructor(style = undefined) { this.style = style; } } \ No newline at end of file diff --git a/structures/interactions/ButtonStyles.js b/structures/interactions/ButtonStyles.js index 626d356..7c7d5e5 100644 --- a/structures/interactions/ButtonStyles.js +++ b/structures/interactions/ButtonStyles.js @@ -1,3 +1,6 @@ +/** + * @enum {number} + */ export const MessageButtonStyles = Object.freeze({ PRIMARY: 1, SECONDARY: 2, diff --git a/structures/interactions/MessageSelectMenu.js b/structures/interactions/MessageSelectMenu.js index d4f88f4..2e35826 100644 --- a/structures/interactions/MessageSelectMenu.js +++ b/structures/interactions/MessageSelectMenu.js @@ -1,5 +1,12 @@ +import { DataManager } from '../DataManager.js'; +import { Client } from '../client/client.js'; +import { Channel } from '../guilds/Channel.js'; +import { guildRole } from '../guilds/guildRoles.js'; +import member from '../guilds/member.js'; +import user from '../messages/User.js'; import {messageChannelTypes} from '../messages/messageChannelTypes.js'; + export class StringMenuComponent { /** @type {String} */ label; @@ -23,8 +30,50 @@ export class StringMenuComponent { } return obj; } + + constructor(data = undefined) { + if (!data) return; + + for (const k in this) { + if (data[k]) this[k] = data[k]; + } + } } + +export class userMenuComponent { + /** @type {String[]} */ + values; + + /** @type {user[]} */ + users; + + /** @type {member[]} */ + members; + + constructor(data) { + this.users = []; + this.members = []; + this.values = data["values"]; + const resolved = data['resolved']; + + const mems = resolved['members']; + const usrs = resolved['members']; + + for (const k in usrs) { + this.users.push(new user(usrs[k])); + } + + for (const k in mems) { + this.members.push(new member(mems[k], mems[k]['roles'])); + } + } +} + + +/** + * @enum {number} + */ export const SelectMenuTypes = Object.freeze({ TEXT: 3, USER: 5, @@ -34,7 +83,7 @@ export const SelectMenuTypes = Object.freeze({ }); -class BaseSelectMenu { +class BaseSelectMenu extends DataManager { #type; /** @type {String} */ @@ -62,48 +111,110 @@ class BaseSelectMenu { return obj; } - constructor(type) { this.#type = type; this.min_values = 1; this.max_values = 1; } + constructor(type, data = undefined, client = undefined) { + super(client); + this.#type = type; + this.min_values = 1; + this.max_values = 1; + this.client = client; + + if (data) { + for (const k in this) { + if (data[k] != undefined) { + this[k] = data[k]; + } + } + } + } } + export class StringSelectMenu extends BaseSelectMenu { - /** @type {StringMenuComponent[]} */ - options; + /** @type {StringMenuComponent[] | String[]} */ + data; + + /** @type {Boolean} */ + isRet; toObj() { const obj = super.toObj(); obj["options"] = []; - for (const k of this.options) { + for (const k of this.data) { obj["options"].push(k.toJSON()); } return obj; } - constructor() { super(SelectMenuTypes.TEXT); this.options = []; } + constructor(dataRaw = undefined, client = undefined) { + super(SelectMenuTypes.TEXT, dataRaw, client); + + this.data = (dataRaw) ? dataRaw.values : []; + } } + export class ChannelSelectMenu extends BaseSelectMenu { - /** @type { messageChannelTypes[] } */ - options; + /** @type { Channel[] } */ + data; toObj() { const obj = super.toObj(); + obj["channel_types"] = []; - for (const k of this.options) { + for (const k of this.data) { obj["channel_types"].push(k); } + return obj; } - constructor() { super(SelectMenuTypes.CHANNELS); this.options = []; } + constructor(dataRaw = undefined, client = undefined) { + super(SelectMenuTypes.CHANNELS, undefined, client); + if (dataRaw) { + this.custom_id = dataRaw?.custom_id; + this.data = []; + + for (const key in dataRaw.resolved.channels) { + const channelRaw = dataRaw.resolved.channels[key]; + + if (this.client.guilds.has(channelRaw.guild_id)) { + const guild = this.client.guilds.get(channelRaw.guild_id); + this.data.push(guild.channels.cache.get(key)); + } + } + + } else this.data = []; //(dataRaw) ? [new loop through channels here(dataRaw)] : []; + } } export class userSelectMenu extends BaseSelectMenu { - constructor() { super(SelectMenuTypes.USER); } + /** @type {userMenuComponent[]} */ + data; + + constructor(dataRaw = undefined, client = undefined) { + super(SelectMenuTypes.USER, dataRaw, client); + if (dataRaw) { + this.custom_id = dataRaw?.custom_id; + this.data = [new userMenuComponent(dataRaw)]; + } + } } export class RoleSelectMenu extends BaseSelectMenu { - constructor() { super(SelectMenuTypes.ROLE); } + /** @type {Map} */ + data; + + constructor(dataRaw = undefined, client = undefined) { + super(SelectMenuTypes.ROLE, dataRaw, client); + this.data = new Map(); + + if (dataRaw) { + for (const key in dataRaw.resolved.roles) { + this.data.set(key, new guildRole(dataRaw.resolved.roles[key])); + } + } + } } diff --git a/structures/interactions/createInteraction.js b/structures/interactions/createInteraction.js index 766d425..b288259 100644 --- a/structures/interactions/createInteraction.js +++ b/structures/interactions/createInteraction.js @@ -1,11 +1,45 @@ import { Modal } from "./Modal.js"; import { Interaction } from "./interaction.js"; import { interactionTypes } from "./interactionTypes.js"; +import * as msgMenu from './MessageSelectMenu.js'; +/* inp.data +{ values: [ 'llllll' ], custom_id: 'temp', component_type: 3 } -function selectMenuTypes(inp) { - +{ + values: [ '720349017829015633' ], + resolved: { + users: { '720349017829015633': [Object] }, + members: { '720349017829015633': [Object] } + }, + custom_id: 'userMenu', + component_type: 5 } +*/ + + +function createSelectMenu(inp, client) { + // console.log(inp.data);//.filter(o => o.type in msgMenu.SelectMenuTypes); + switch (inp.data.component_type) { + case msgMenu.SelectMenuTypes.CHANNELS: + return new msgMenu.ChannelSelectMenu(inp.data, client); + + case msgMenu.SelectMenuTypes.MENTIONABLE: + throw "MENTIONABLE MENUS NOT CURRENTLY SUPPORTED"; + + case msgMenu.SelectMenuTypes.ROLE: + return new msgMenu.RoleSelectMenu(inp.data, client); + + case msgMenu.SelectMenuTypes.TEXT: + return new msgMenu.StringSelectMenu(inp.data, client); + + case msgMenu.SelectMenuTypes.USER: + return new msgMenu.userSelectMenu(inp.data); + + default: console.log("DEFAULT", inp.data); + } +} + export function createInteraction(intRaw, client) { switch (intRaw.type) { @@ -13,8 +47,7 @@ export function createInteraction(intRaw, client) { return new Interaction(intRaw, client); case interactionTypes.MessageComponent: - console.log(intRaw.message.components); - return null; + return createSelectMenu(intRaw, client); case interactionTypes.ModalSubmit: return new Modal(intRaw, client); diff --git a/structures/interactions/interactionTypes.js b/structures/interactions/interactionTypes.js index a36911c..56c3b4c 100644 --- a/structures/interactions/interactionTypes.js +++ b/structures/interactions/interactionTypes.js @@ -1,3 +1,6 @@ +/** + * @enum {number} + */ export const interactionTypes = Object.freeze({ Ping: 1, ApplicationCommand: 2, diff --git a/structures/messages/messageChannelTypes.js b/structures/messages/messageChannelTypes.js index f571b3e..a3a6dce 100644 --- a/structures/messages/messageChannelTypes.js +++ b/structures/messages/messageChannelTypes.js @@ -1,6 +1,8 @@ //Blatantly stolen from https://github.com/discordjs/discord-api-types/blob/main/gateway/v10.ts - +/** + * @enum {number} + */ export const messageChannelTypes = Object.freeze({ /** * A text channel within a guild diff --git a/tests/Buttontests.js b/tests/Buttontests.js index 72d9967..c612d23 100644 --- a/tests/Buttontests.js +++ b/tests/Buttontests.js @@ -10,25 +10,38 @@ import { ChannelSelectMenu, StringMenuComponent, StringSelectMenu, userSelectMen */ export async function buttonTests(mog) { var m = new message(); - // const comp = new Button(); - // comp.style = MessageButtonStyles.SUCCESS; - // comp.label = "HELLO WORLD"; - // comp.custom_id = "temptemp"; - const c = new StringSelectMenu(); - const comp2 = new StringMenuComponent(); - comp2.value = 'llllll'; - comp2.label = 'llllll'; - c.options.push(comp2); - c.custom_id = "temp"; + const btnSuccess = new Button(); + btnSuccess.style = MessageButtonStyles.SUCCESS; + btnSuccess.label = "SUCCESS"; + btnSuccess.custom_id = "tempbtnsu"; + + const btnDanger = new Button(); + btnDanger.style = MessageButtonStyles.DANGER; + btnDanger.label = "HELLO WORLD"; + btnDanger.custom_id = "tempbtnda"; + + const btnSecondary = new Button(); + btnSecondary.style = MessageButtonStyles.SECONDARY; + btnSecondary.label = "HELLO WORLD"; + btnSecondary.custom_id = "tempbtnse"; + + const btnPrim = new Button(); + btnPrim.style = MessageButtonStyles.PRIMARY; + btnPrim.label = "HELLO WORLD"; + btnPrim.custom_id = "temppr"; + + const btnLink = new Button(); + btnLink.style = MessageButtonStyles.LINK; + btnLink.label = "HELLO WORLD"; + btnLink.url = 'https://www.google.com/'; - const comp3 = new userSelectMenu(); - comp3.custom_id = "userMenu"; - const row = new MessageActionRow(); - // row.addComponent(comp); - // row.addComponent(c); - row.addComponent(comp3); + row.addComponent(btnSuccess); + row.addComponent(btnDanger); + row.addComponent(btnSecondary); + row.addComponent(btnPrim); + row.addComponent(btnLink); m.addComponents(row); m.content = "OOGA BOOGA"; diff --git a/tests/menuTests.js b/tests/menuTests.js new file mode 100644 index 0000000..40110c4 --- /dev/null +++ b/tests/menuTests.js @@ -0,0 +1,56 @@ +import { Button } from "../structures/interactions/Button.js"; +import { Channel } from "../structures/guilds/Channel.js"; +import { message } from "../structures/messages/message.js"; +import { MessageButtonStyles } from "../structures/interactions/ButtonStyles.js"; +import { MessageActionRow } from "../structures/messages/MessageActionRow.js"; +import { ChannelSelectMenu, RoleSelectMenu, StringMenuComponent, StringSelectMenu, userSelectMenu } from "../structures/interactions/MessageSelectMenu.js"; + +/** + * @param {message} mog + */ +export async function createMenuTests(mog) { + var m = new message(); + + const sMenu = new StringSelectMenu(); + const stringComp = new StringMenuComponent(); + stringComp.value = 'llllll'; + stringComp.label = 'llllll'; + + const stringComp2 = new StringMenuComponent(); + stringComp2.value = 'pppppp'; + stringComp2.label = 'pppppp'; + + sMenu.data.push(stringComp); + sMenu.data.push(stringComp2); + sMenu.custom_id = "temp"; + sMenu.max_values = 2; + + const uMenu = new userSelectMenu(); + uMenu.custom_id = "userMenu"; + + const cMenu = new ChannelSelectMenu(); + cMenu.custom_id = "channelMenu"; + + const rMenu = new RoleSelectMenu(); + rMenu.custom_id = "roleMenu"; + + const row = new MessageActionRow(); + row.addComponent(uMenu); + m.addComponents(row); + + const row2 = new MessageActionRow(); + row2.addComponent(sMenu); + m.addComponents(row2); + + const row3 = new MessageActionRow(); + row3.addComponent(rMenu); + m.addComponents(row3); + + m.content = "OOGA BOOGA"; + mog.reply(m); +} + + +export async function handleMenuTests(menu) { + +} \ No newline at end of file diff --git a/tests/test.js b/tests/test.js index e7dad91..1626a2b 100644 --- a/tests/test.js +++ b/tests/test.js @@ -1,8 +1,12 @@ +import switchConsoleDefault from '../utils/consoleToFile.js'; import { Client, gateWayIntents, message, Interaction } from '../structures/types.js'; import config from '../config.json' assert { type: 'json' }; import { buttonTests } from './Buttontests.js'; +import { createMenuTests } from './menuTests.js'; const { bottoken } = config; +// switchConsoleDefault(); + var c = new Client({ intents: [ gateWayIntents.Guilds, @@ -26,6 +30,8 @@ c.login(bottoken); c.on('messageRecieved', /**@param {message} message*/ async (message) => { if (message.content == 'buttontest') { return buttonTests(message); + } else if (message.content == 'menutest') { + return createMenuTests(message); } (await import('./messageTests.js')).default(message); }); diff --git a/utils/consoleToFile.js b/utils/consoleToFile.js new file mode 100644 index 0000000..d844cd5 --- /dev/null +++ b/utils/consoleToFile.js @@ -0,0 +1,17 @@ +import fs from 'fs'; +import util from 'util' +import path from 'path'; +import {fileURLToPath} from 'url'; + +export default function writeInit() { + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + + var log_file = fs.createWriteStream(path.join(__dirname, '../debug.log'), {flags : 'w'}); + var log_stdout = process.stdout; + + console.log = function(d) { + log_file.write(util.format(d) + '\n'); + log_stdout.write(util.format(d) + '\n'); + }; +} \ No newline at end of file