/** * @name BetterBottom * @author Ahlawat * @authorId 1025214794766221384 * @version 1.2.0 * @invite SgKSKyh9gY * @description Adds a slash command to convert text to bottom and send it. Converting bottom to text is also possible. * @website https://tharki-god.github.io/ * @source https://github.com/Tharki-God/BetterDiscordPlugins * @updateUrl https://tharki-god.github.io/BetterDiscordPlugins/BetterBottom.plugin.js */ /*@cc_on @if (@_jscript) var shell = WScript.CreateObject("WScript.Shell"); var fs = new ActiveXObject("Scripting.FileSystemObject"); var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\\BetterDiscord\\plugins"); var pathSelf = WScript.ScriptFullName; shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30); if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) { shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40); } else if (!fs.FolderExists(pathPlugins)) { shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10); } else if (shell.Popup("Should I move myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) { fs.MoveFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf))); shell.Exec("explorer " + pathPlugins); shell.Popup("I'm installed!", 0, "Successfully installed", 0x40); } WScript.Quit(); @else@*/ module.exports = (() => { const config = { info: { name: "BetterBottom", authors: [ { name: "Ahlawat", discord_id: "1025214794766221384", github_username: "Tharki-God", }, ], version: "1.2.0", description: "Adds a slash command to convert text to bottom and send it. Converting bottom to text is also possible.", github: "https://github.com/Tharki-God/BetterDiscordPlugins", github_raw: "https://tharki-god.github.io/BetterDiscordPlugins/BetterBottom.plugin.js", }, changelog: [ { title: "v0.0.1", items: ["Idea in mind"], }, { title: "v0.0.5", items: ["Base Model"], }, { title: "Initial Release v1.0.0", items: [ "This is the initial release of the plugin :)", "Getting cursed is part of life  ̄へ ̄", ], }, { title: "v1.1.1", items: ["Corrected text."], }, ], main: "BetterBottom.plugin.js", }; const RequiredLibs = [{ window: "ZeresPluginLibrary", filename: "0PluginLibrary.plugin.js", external: "https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js", downloadUrl: "https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js" }, { window: "BunnyLib", filename: "1BunnyLib.plugin.js", external: "https://github.com/Tharki-God/BetterDiscordPlugins", downloadUrl: "https://tharki-god.github.io/BetterDiscordPlugins/1BunnyLib.plugin.js" }, ]; class handleMissingLibrarys { load() { for (const Lib of RequiredLibs.filter(lib => !window.hasOwnProperty(lib.window))) BdApi.showConfirmationModal( "Library Missing", `The library plugin (${Lib.window}) needed for ${config.info.name} is missing. Please click Download Now to install it.`, { confirmText: "Download Now", cancelText: "Cancel", onConfirm: () => this.downloadLib(Lib), } ); } async downloadLib(Lib) { const fs = require("fs"); const path = require("path"); const { Plugins } = BdApi; const LibFetch = await fetch( Lib.downloadUrl ); if (!LibFetch.ok) return this.errorDownloadLib(Lib); const LibContent = await LibFetch.text(); try { await fs.writeFile( path.join(Plugins.folder, Lib.filename), LibContent, (err) => { if (err) return this.errorDownloadLib(Lib); } ); } catch (err) { return this.errorDownloadLib(Lib); } } errorDownloadZLib(Lib) { const { shell } = require("electron"); BdApi.showConfirmationModal( "Error Downloading", [ `${Lib.window} download failed. Manually install plugin library from the link below.`, ], { confirmText: "Download", cancelText: "Cancel", onConfirm: () => { shell.openExternal( Lib.external ); }, } ); } start() { } stop() { } } return RequiredLibs.some(m => !window.hasOwnProperty(m.window)) ? handleMissingLibrarys : (([Plugin, ZLibrary]) => { const { Logger, PluginUpdater, Utilities, DiscordModules: { MessageActions }, Settings: { SettingPanel, Switch, Textbox }, } = ZLibrary; const { LibraryUtils, ApplicationCommandAPI, LibraryRequires: { request }, LibraryModules: { ChannelPermissionStore, UploadModule, DiscordConstants } } = BunnyLib.build(config); const defaultSettings = { encoder: true, decoder: true, split: true, uploadAsFile: true, fileName: "bottom.txt", }; return class BetterBottom extends Plugin { constructor() { super(); this.settings = Utilities.loadData( config.info.name, "settings", defaultSettings ); } checkForUpdates() { try { PluginUpdater.checkForUpdate( config.info.name, config.info.version, config.info.github_raw ); } catch (err) { Logger.err("Plugin Updater could not be reached.", err); } } start() { this.checkForUpdates(); if (this.settings["encoder"]) this.addEncoder(); if (this.settings["decoder"]) this.addDecoder(); } addEncoder() { ApplicationCommandAPI.register(`${config.info.name}_encoder`, { name: "bottom encode", displayName: "bottom encode", displayDescription: "Convert text to bottom.", description: "Convert text to bottom.", type: 1, target: 1, execute: async ([send, toEncode], { channel }) => { try { const body = await this.bottom("encode", toEncode.value); if (body.message) return MessageActions.receiveMessage( channel.id, LibraryUtils.FakeMessage(channel.id, body.message) ); this.sendAccordingly(send.value, channel, body.encoded); } catch (err) { Logger.err(err); MessageActions.receiveMessage( channel.id, LibraryUtils.FakeMessage( channel.id, "Could not convert the text to bottom." ) ); } }, options: [ { description: "Whether you want to send this or not.", displayDescription: "Whether you want to send this or not.", displayName: "Send", name: "Send", required: true, type: 5, }, { description: "The text you want to encode.", displayDescription: "The text you want to encode.", displayName: "Text", name: "Text", required: true, type: 3, }, ], }); } addDecoder() { ApplicationCommandAPI.register(`${config.info.name}_decoder`, { name: "bottom decode", displayName: "bottom decode", displayDescription: "Convert bottom to text for understanding.", description: "Convert bottom to text for understanding.", type: 1, target: 1, execute: async ([send, toDecode], { channel }) => { try { const body = await this.bottom("decode", toDecode.value); if (body.message) return MessageActions.receiveMessage( channel.id, LibraryUtils.FakeMessage(channel.id, body.message) ); this.sendAccordingly(send.value, channel, body.decoded); } catch (err) { Logger.err(err); MessageActions.receiveMessage( channel.id, LibraryUtils.FakeMessage( channel.id, "Could not convert the bottom to text." ) ); } }, options: [ { description: "Whether you want to send this or not.", displayDescription: "Whether you want to send this or not.", displayName: "Send", name: "Send", required: true, type: 5, }, { description: "The bottom you want to decode.", displayDescription: "The bottom you want to decode.", displayName: "Bottom", name: "Bottom", required: true, type: 3, }, ], }); } async sendAccordingly(send, channel, content) { const splitMessages = content.match(LibraryUtils.characterLimit); if (!send) { for (const message of splitMessages) { MessageActions.receiveMessage( channel.id, LibraryUtils.FakeMessage(channel.id, message) ); } return; } if (content?.length < DiscordConstants?.qhL) return MessageActions.sendMessage( channel.id, { content: content, tts: false, bottom: true, invalidEmojis: [], validNonShortcutEmojis: [], }, undefined, {} ); if ( this.settings["split"] && (!channel.rateLimitPerUser || this.canSendSplitMessage(channel)) ) { for (const message of splitMessages) { MessageActions.sendMessage( channel.id, { content: message, tts: false, bottom: true, invalidEmojis: [], validNonShortcutEmojis: [], }, undefined, {} ); } return; } else if ( this.settings["uploadAsFile"] && channel.rateLimitPerUser && !this.canSendSplitMessage(channel) && this.canSendFiles(channel) ) { const txt = new Blob([content], { type: "text/plain" }); const fileToUpload = new File([txt], this.settings["fileName"]); UploadModule.upload({ channelId: channel.id, file: fileToUpload, draftType: null, message: "", }); } else MessageActions.receiveMessage( channel.id, LibraryUtils.FakeMessage( channel.id, "The message is too long to send.\n(Enable Split message and upload as file in the settings to be able to send longer messages.)" ) ); } canSendSplitMessage(channel) { return ( ChannelPermissionStore.can( DiscordConstants.Plq.MANAGE_MESSAGES, channel ) || ChannelPermissionStore.can( DiscordConstants.Plq.MANAGE_CHANNELS, channel ) ); } canSendFiles(channel) { return ChannelPermissionStore.can( DiscordConstants.Plq.ATTACH_FILES, channel ); } bottom(type, content) { return new Promise((resolve, reject) => { const options = [ `https://bottom.daggy.workers.dev/${encodeURI( type == "encode" ? `encode?text=${content}` : `decode?bottom=${content}` )}`, { json: true }, ]; request.get(...options, (err, res, body) => { if (err || (res.statusCode < 200 && res.statusCode > 400)) return reject("An unknown error occurred."); resolve(JSON.parse(body)); }); }); } onStop() { ApplicationCommandAPI.unregister(`${config.info.name}_encoder`); ApplicationCommandAPI.unregister(`${config.info.name}_decoder`); } getSettingsPanel() { return SettingPanel.build( this.saveSettings.bind(this), new Switch( "Encode", "Enable command to encode bottom.", this.settings["encoder"], (e) => { this.settings["encoder"] = e; } ), new Switch( "Decode", "Enable command to decode bottom.", this.settings["decoder"], (e) => { this.settings["decoder"] = e; } ), new Switch( "Split message", "Split a message into multiple messages if it is larger than the character limit and Slowmode is not enabled on the selected channel.", this.settings["split"], (e) => { this.settings["split"] = e; } ), new Switch( "Upload as file", "Upload a message as a file if it is larger than the character limit and Slowmode is enabled on the selected channel.", this.settings["uploadAsFile"], (e) => { this.settings["uploadAsFile"] = e; } ), new Textbox( "File name", "The file name (with extension) to use when uploading a message as a file.", this.settings["fileName"], (e) => { this.settings["fileName"] = e; } ) ); } saveSettings() { Utilities.saveData(config.info.name, "settings", this.settings); } }; })(ZLibrary.buildPlugin(config)); })(); /*@end@*/