UNPKG

shockbs

Version:

Unlimited, Unrestricted Free AI models API, including GPT-4. Games for discord bots and wrapper for the ShockBS API

327 lines (297 loc) 11.6 kB
const fetch = require("node-fetch"); const { connected, getToken, request } = require("../../utilman.js"); const stringSimilarity = require("string-similarity"); const { ButtonStyle, EmbedBuilder, ButtonBuilder, ActionRowBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder } = require("discord.js"); module.exports = class gpt4Chat { constructor(options = {}) { connected(true); if (!options.model) { throw new ReferenceError("options.model was missing"); } if (typeof options.model !== "string") { throw new TypeError("options.model must be a string"); } const compareResults = stringSimilarity .findBestMatch(options.model.toLowerCase(), ["gpt-4", "gpt-3.5", "gpt-3"]) .ratings.filter(result => result.rating >= 0.8) .sort((a, b) => b.rating - a.rating); if (compareResults.length > 0) { options.model = compareResults[0].target; } else { throw new ReferenceError("options.model must be one of the available models"); } options.replyMention = options.replyMention || false; if (typeof options.replyMention !== "boolean") { throw new TypeError("options.replyMention must be a boolean"); } if (!options.maxInteractions) { throw new ReferenceError("options.maxInteractions was missing"); } if (typeof options.maxInteractions !== "number") { throw new TypeError("options.maxInteractions must be a number"); } if (options.maxInteractions < 2) { throw new Error("options.maxInteractions must be greater than 2, if you need text generation, use gptText function instead"); } if (options.maxInteractions > 30) { throw new Error("options.maxInteractions should not be greater than 30"); } options.components = options.components || []; if (!Array.isArray(options.components)) { throw new TypeError("options.components should be an array"); } if (options.dashboard) { if (options.dashboard.enabled) { if (typeof options.dashboard.enabled !== "boolean") { throw new TypeError("options.dashboard.enabled must be a boolean."); } let dash = options.dashboard; dash.buttonStyle = dash.buttonStyle || ButtonStyle.Primary; if (typeof dash.buttonStyle !== "number") { throw new TypeError(`${dash.buttonStyle} is not a number`); } dash.buttonText = dash.buttonText || "Dashboard"; if (typeof dash.buttonText !== "string") { throw new TypeError("options.dashboard.buttonText must be a string"); } if (!dash.buttonText.length) { throw new Error("options.dashboard.buttonText cannot be empty"); } dash.clearConversationOnSwitchModel = dash.clearConversationOnSwitchModel ?? true; if (typeof dash.clearConversationOnSwitchModel !== "boolean") { throw new TypeError(`${dash.clearConversationOnSwitchModel} is not a boolean`); } this.data = new Map(); const buttons = new ActionRowBuilder().addComponents( new ButtonBuilder({ label: dash.buttonText, style: dash.buttonStyle, custom_id: "api.shockbs.is-a.dev chat" }) ); options.components.unshift(buttons); options.dashboard = dash; } else { throw new Error("options.dashboard.enabled is required."); } } else { throw new Error("options.dashboard is required."); } options.custom = options.custom || null; if (options.custom && typeof options.custom !== "string") { throw new TypeError("options.custom must be a string"); } if (options.custom && options.custom.length > 1500) { throw new ReferenceError("The length of options.custom should not be greater than 1500."); } options.embed = options.embed || {}; options.embed.color = options.embed.color || "#34FFC2"; if (typeof options.embed.color !== "string") { throw new TypeError("options.embed.color must be a string"); } options.blacklistedUsers = options.blacklistedUsers || []; if (!Array.isArray(options.blacklistedUsers)) { throw new TypeError("options.blacklistedUsers must be an array"); } if (options.blacklistedUsers.length && options.blacklistedUsers.includes("880084860327313459")) { throw new Error("880084860327313459 is the ID of ShockBS, the owner of ShockBS API, and hence should not be blacklisted."); } options.blacklistedUsers.forEach(key => { if (typeof key !== "string") { throw new TypeError(`${key} is not a string`); } if (key.includes(" ") || key.includes('\n')) { throw new Error(`"${key}" contains blank spaces or new lines`); } if (isNaN(Number(key))) { throw new Error(`ID should only contain numbers. Referring to "${key}"`); } }); this.model = options.model; this.options = options; this.cache = new Map(); this.token = getToken(); } async clear() { return this.cache.clear(); } async clearConversation(id) { if (!id) { throw new ReferenceError("ID is not provided"); } if (typeof id !== "string") { throw new TypeError("ID must be a string"); } if (!this.cache.has(id)) { throw new Error(`${id} Not Found`); } try { await this.cache.delete(id); return true; } catch (e) { return false; } } async getData(id) { if (!id) { throw new ReferenceError("ID is not provided"); } if (typeof id !== "string") { throw new TypeError("ID must be a string"); } if (!this.cache.has(id)) { throw new Error(`${id} Not Found`); } return this.cache.get(id); } async getCount(id) { if (!id) { throw new ReferenceError("ID is not provided"); } if (typeof id !== "string") { throw new TypeError("ID must be a string"); } if (!this.cache.has(id)) { throw new Error(`${id} Not Found`); } return this.cache.get(id).length / 2; } async handleMessage(message) { if (!message) { throw new Error("Message not provided"); } if (!message.content?.length) { throw new Error("Message must have content"); } if (this.options.blacklistedUsers.length && this.options.blacklistedUsers.includes(message.author.id)) { return message.reply({ content: `Sorry, you are blacklisted from using this feature.`, allowedMentions: { repliedUser: true } }); } let data = this.cache.get(message.author.id) || []; let data2 = this.data.get(message.author.id) || { model: this.model, count: 0 }; if (!this.cache.has(message.author.id)) { this.cache.set(message.author.id, []); } if (!this.data.has(message.author.id)) { this.data.set(message.author.id, data2); } data.push({ role: "user", content: message.cleanContent }); const { message: msg } = await request({ method: "post", route: "ai/chat", body: JSON.stringify({ data, model: data2.model, custom: this.options.custom }), reply: message.reply }); data.push({ role: "shock", content: msg }); data2.count ++ if (data2.count > this.options.maxInteractions) { this.cache.delete(message.author.id); this.data.delete(message.author.id); } else { this.cache.set(message.author.id, data); this.data.set(message.author.id, data2); } if (msg.length < 4097) { return message.reply({ embeds: [new EmbedBuilder().setColor(this.options.embed.color).setDescription(msg)], components: this.options.components, allowedMentions: { repliedUser: this.options.replyMention, parse: [], users: [], roles: [] } }); } else { const chunks = []; let i = 0; while (i < msg.length) { chunks.push(msg.substring(i, i + 4096)); i += 4096; } for (const chunk of chunks) { message.reply({ embeds: [new EmbedBuilder().setColor(this.options.embed.color).setDescription(chunk)], components: this.options.components, allowedMentions: { repliedUser: this.options.replyMention, parse: [], users: [], roles: [] } }); } } } async handleInteraction(interaction) { if (!interaction.customId || typeof interaction.customId !== "string") { throw new ReferenceError("'interaction' must be from interactionCreate"); } if (!interaction.customId.startsWith("api.shockbs.is-a.dev chat")) return; let data = this.data.get(interaction.user.id) || { model: this.model, count: 0 }; if (!this.data.has(interaction.user.id)) { this.data.set(interaction.user.id, data); } if (interaction.customId === "api.shockbs.is-a.dev chat") { if (!interaction.replied) await interaction.deferReply({ephemeral:true}) return interaction.editReply(reply(data)); } if (interaction.replied) { throw new Error("interaction should not be replied"); } if (interaction.customId.includes("models")) { if (this.cache.has(interaction.user.id)) await this.cache.delete(interaction.user.id); data.count = 0; this.data.set(interaction.user.id, data); return interaction.update(reply(data)); } const customId = interaction.customId.replace("api.shockbs.is-a.dev chat ", ""); switch (interaction.customId.replace("api.shockbs.is-a.dev chat ", "")) { case "clear": { this.data.delete(interaction.user.id); this.cache.delete(interaction.user.id); return interaction.update({ content: "Cleared data successfully", embeds: [], components: [], allowedMentions: { repliedUser: true } }); break; } /* case "models": { data.model = interaction.values[0]; if (this.options.dashboard.clearConversationOnSwitchModel) { if (this.cache.has(interaction.user.id)) await this.cache.delete(interaction.user.id); data.count = 0; } this.data.set(interaction.user.id, data); return interaction.update(reply(data)); break; }*/ case "chat": { return interaction.update(reply(data)); break; } } } }; function reply(data) { return { embeds: [ new EmbedBuilder() .setColor("#00FFDE") .setTitle("AI Dashboard") .setURL("https://docs.shockbs.is-a.dev/pckg/models/AiChat") // Please respect the license, you are not allowed to remove this. ], allowedMentions: { repliedUser: false }, components: [ new ActionRowBuilder().addComponents( new ButtonBuilder({ style: ButtonStyle.Danger, custom_id: "api.shockbs.is-a.dev chat clear", label: `(${data.count}) Clear Chat`, disabled: data.count <= 0 }), new ButtonBuilder({ style: ButtonStyle.Secondary, emoji: "🔄", custom_id: "api.shockbs.is-a.dev chat chat" }) ), new ActionRowBuilder().addComponents( new StringSelectMenuBuilder() .setCustomId("api.shockbs.is-a.dev chat models") .setPlaceholder("Switch Models") .addOptions( new StringSelectMenuOptionBuilder() .setValue("gpt-4") .setLabel("GPT-4") .setDefault(data.model === "gpt-4"), new StringSelectMenuOptionBuilder() .setValue("gpt-3.5") .setLabel("GPT-3.5") .setDefault(data.model === "gpt-3.5"), new StringSelectMenuOptionBuilder() .setValue("gpt-3") .setLabel("GPT-3") .setDefault(data.model === "gpt-3") ) ) ] } }