/** * @name PluginRepo * @author DevilBro * @authorId 278543574059057154 * @version 2.5.4 * @description Allows you to download all Plugins from BD's Website within Discord * @invite Jx3TjNS * @donate https://www.paypal.me/MircoWittrien * @patreon https://www.patreon.com/MircoWittrien * @website https://mwittrien.github.io/ * @source https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/PluginRepo/ * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/PluginRepo/PluginRepo.plugin.js */ module.exports = (_ => { const changeLog = { }; return !window.BDFDB_Global || (!window.BDFDB_Global.loaded && !window.BDFDB_Global.started) ? class { constructor (meta) {for (let key in meta) this[key] = meta[key];} getName () {return this.name;} getAuthor () {return this.author;} getVersion () {return this.version;} getDescription () {return `The Library Plugin needed for ${this.name} is missing. Open the Plugin Settings to download it. \n\n${this.description}`;} downloadLibrary () { require("request").get("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js", (e, r, b) => { if (!e && b && r.statusCode == 200) require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.showToast("Finished downloading BDFDB Library", {type: "success"})); else BdApi.alert("Error", "Could not download BDFDB Library Plugin. Try again later or download it manually from GitHub: https://mwittrien.github.io/downloader/?library"); }); } load () { if (!window.BDFDB_Global || !Array.isArray(window.BDFDB_Global.pluginQueue)) window.BDFDB_Global = Object.assign({}, window.BDFDB_Global, {pluginQueue: []}); if (!window.BDFDB_Global.downloadModal) { window.BDFDB_Global.downloadModal = true; BdApi.showConfirmationModal("Library Missing", `The Library Plugin needed for ${this.name} is missing. Please click "Download Now" to install it.`, { confirmText: "Download Now", cancelText: "Cancel", onCancel: _ => {delete window.BDFDB_Global.downloadModal;}, onConfirm: _ => { delete window.BDFDB_Global.downloadModal; this.downloadLibrary(); } }); } if (!window.BDFDB_Global.pluginQueue.includes(this.name)) window.BDFDB_Global.pluginQueue.push(this.name); } start () {this.load();} stop () {} getSettingsPanel () { let template = document.createElement("template"); template.innerHTML = `
The Library Plugin needed for ${this.name} is missing.\nPlease click Download Now to install it.
`; template.content.firstElementChild.querySelector("a").addEventListener("click", this.downloadLibrary); return template.content.firstElementChild; } } : (([Plugin, BDFDB]) => { var _this; var list; var loading, cachedPlugins, grabbedPlugins, updateInterval; var searchString, searchTimeout, forcedSort, forcedOrder, showOnlyOutdated; var favorites = []; const pluginStates = { INSTALLED: 0, OUTDATED: 1, DOWNLOADABLE: 2 }; const buttonData = { INSTALLED: { backgroundColor: "var(--status-positive)", icon: "CHECKMARK", text: "installed" }, OUTDATED: { backgroundColor: "var(--status-danger)", icon: "CLOSE", text: "outdated" }, DOWNLOADABLE: { backgroundColor: "var(--bdfdb-blurple)", icon: "DOWNLOAD", text: "download" } }; const reverseSorts = [ "RELEASEDATE", "DOWNLOADS", "LIKES", "FAV" ]; const sortKeys = { NAME: "Name", AUTHORNAME: "Author", VERSION: "Version", DESCRIPTION: "Description", RELEASEDATE: "Release Date", STATE: "Update State", DOWNLOADS: "Downloads", LIKES: "Likes", FAV: "Favorites" }; const orderKeys = { ASC: "ascending", DESC: "descending" }; const pluginRepoIcon = ``; const RepoListComponent = class PluginList extends BdApi.React.Component { componentDidMount() { list = this; BDFDB.TimeUtils.timeout(_ => { forcedSort = null; forcedOrder = null; showOnlyOutdated = false; }, 5000); } componentWillUnmount() { list = null; } filterPlugins() { let plugins = grabbedPlugins.map(plugin => { if (plugin.failed) return; const installedPlugin = _this.getInstalledPlugin(plugin); const state = installedPlugin ? (plugin.version && _this.compareVersions(plugin.version, _this.getString(installedPlugin.plugin && installedPlugin.plugin.version || installedPlugin.version)) ? pluginStates.OUTDATED : pluginStates.INSTALLED) : pluginStates.DOWNLOADABLE; return Object.assign(plugin, { search: [plugin.name, plugin.version, plugin.authorname, plugin.description, plugin.tags].flat(10).filter(n => typeof n == "string").join(" ").toUpperCase(), description: plugin.description || "No Description found", fav: favorites.includes(plugin.id) && 1, new: state == pluginStates.DOWNLOADABLE && !cachedPlugins.includes(plugin.id) && 1, state: state }); }).filter(n => n); if (!this.props.updated) plugins = plugins.filter(plugin => plugin.state != pluginStates.INSTALLED); if (!this.props.outdated) plugins = plugins.filter(plugin => plugin.state != pluginStates.OUTDATED); if (!this.props.downloadable) plugins = plugins.filter(plugin => plugin.state != pluginStates.DOWNLOADABLE); if (searchString) { let usedSearchString = searchString.toUpperCase(); let spacelessUsedSearchString = usedSearchString.replace(/\s/g, ""); plugins = plugins.filter(plugin => plugin.search.indexOf(usedSearchString) > -1 || plugin.search.indexOf(spacelessUsedSearchString) > -1); } BDFDB.ArrayUtils.keySort(plugins, this.props.sortKey.toLowerCase()); if (this.props.orderKey == "DESC") plugins.reverse(); if (reverseSorts.includes(this.props.sortKey)) plugins.reverse(); return plugins; } render() { if (!this.props.tab) this.props.tab = "Plugins"; const entries = (!loading.is && grabbedPlugins.length ? this.filterPlugins() : []); return BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN._repo, children: [ BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCNS._repolistheader + BDFDB.disCN.settingswindowcontentcolumndefault, children: [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { className: BDFDB.disCN.marginbottom4, align: BDFDB.LibraryComponents.Flex.Align.CENTER, children: [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { grow: 1, shrink: 0, children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormTitle, { tag: BDFDB.LibraryComponents.FormComponents.FormTags.H1, className: BDFDB.disCN.marginreset, children: `Plugin Repo — ${loading.is ? 0 : entries.length || 0}/${loading.is ? 0 : grabbedPlugins.length}` }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SearchBar, { autoFocus: true, query: searchString, onChange: value => { if (loading.is) return; BDFDB.TimeUtils.clear(searchTimeout); searchTimeout = BDFDB.TimeUtils.timeout(_ => { searchString = value.replace(/[<|>]/g, ""); BDFDB.ReactUtils.forceUpdate(this); }, 1000); }, onClear: _ => { if (loading.is) return; searchString = ""; BDFDB.ReactUtils.forceUpdate(this); } }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, { size: BDFDB.LibraryComponents.Button.Sizes.TINY, children: BDFDB.LanguageUtils.LibraryStrings.check_for_updates, onClick: _ => { if (loading.is) return; loading = {is: false, timeout: null, amount: 0}; _this.loadPlugins(true); } }) ] }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { className: BDFDB.disCNS.tabbarcontainer + BDFDB.disCN.tabbarcontainerbottom, align: BDFDB.LibraryComponents.Flex.Align.CENTER, children: [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TabBar, { className: BDFDB.disCN.tabbar, itemClassName: BDFDB.disCN.tabbaritem, type: BDFDB.LibraryComponents.TabBar.Types.TOP, selectedItem: this.props.tab, items: [{value: "Plugins"}, {value: BDFDB.LanguageUtils.LanguageStrings.SETTINGS}], onItemSelect: value => { this.props.tab = value; BDFDB.ReactUtils.forceUpdate(this); } }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.QuickSelect, { label: BDFDB.LanguageUtils.LibraryStrings.sort_by + ":", value: { label: sortKeys[this.props.sortKey], value: this.props.sortKey }, options: Object.keys(sortKeys).map(key => ({ label: sortKeys[key], value: key })), onChange: key => { this.props.sortKey = key; BDFDB.ReactUtils.forceUpdate(this); } }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.QuickSelect, { label: BDFDB.LanguageUtils.LibraryStrings.order + ":", value: { label: BDFDB.LanguageUtils.LibraryStrings[orderKeys[this.props.orderKey]], value: this.props.orderKey }, options: Object.keys(orderKeys).map(key => ({ label: BDFDB.LanguageUtils.LibraryStrings[orderKeys[key]], value: key })), onChange: key => { this.props.orderKey = key; BDFDB.ReactUtils.forceUpdate(this); } }) }) ] }) ] }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Scrollers.Auto, { className: BDFDB.disCNS._repolistscroller + BDFDB.disCN.settingswindowcontentcolumndefault, children: [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, { tab: "Plugins", open: this.props.tab == "Plugins", render: false, children: loading.is ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { direction: BDFDB.LibraryComponents.Flex.Direction.VERTICAL, justify: BDFDB.LibraryComponents.Flex.Justify.CENTER, style: {marginTop: "50%"}, children: [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SpinnerComponents.Spinner, { type: BDFDB.LibraryComponents.SpinnerComponents.Types.WANDERING_CUBES }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextElement, { className: BDFDB.disCN.margintop20, style: {textAlign: "center"}, children: `${BDFDB.LanguageUtils.LibraryStringsFormat("loading", "Plugin Repo")} - ${BDFDB.LanguageUtils.LibraryStrings.please_wait}` }) ] }) : BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycards, children: entries.map(plugin => BDFDB.ReactUtils.createElement(RepoCardComponent, { data: plugin })).filter(n => n) }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, { tab: BDFDB.LanguageUtils.LanguageStrings.SETTINGS, open: this.props.tab == BDFDB.LanguageUtils.LanguageStrings.SETTINGS, render: false, children: [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelList, { title: "Show following Plugins", children: Object.keys(_this.defaults.filters).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { type: "Switch", plugin: _this, keys: ["filters", key], label: _this.defaults.filters[key].description, value: _this.settings.filters[key], onChange: value => { this.props[key] = _this.settings.filters[key] = value; BDFDB.ReactUtils.forceUpdate(this); } })) }), Object.keys(_this.defaults.general).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { type: "Switch", plugin: _this, keys: ["general", key], label: _this.defaults.general[key].description, value: _this.settings.general[key], onChange: value => { _this.settings.general[key] = value; BDFDB.ReactUtils.forceUpdate(this); } })) ].flat(10).filter(n => n) }) ] }) ] }); } }; const RepoCardComponent = class PluginCard extends BdApi.React.Component { render() { return BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycard, onMouseEnter: _ => { this.props.hovered = true; BDFDB.ReactUtils.forceUpdate(this); }, children: [ BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardheader, children: [ BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardcoverwrapper, children: [ this.props.data.thumbnailUrl && this.props.hovered && BDFDB.ReactUtils.createElement("img", { className: BDFDB.disCN.discoverycardcover, src: this.props.data.thumbnailUrl, onClick: _ => { const url = this.props.data.thumbnailUrl; const img = document.createElement("img"); img.addEventListener("load", function() { BDFDB.LibraryModules.ModalUtils.openModal(modalData => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalRoot, Object.assign({ className: BDFDB.disCN.imagemodal }, modalData, { size: BDFDB.LibraryComponents.ModalComponents.ModalSize.DYNAMIC, "aria-label": BDFDB.LanguageUtils.LanguageStrings.IMAGE, children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ImageModal, { animated: false, src: url, original: url, width: this.width, height: this.height, className: BDFDB.disCN.imagemodalimage, shouldAnimate: true, renderLinkComponent: props => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Anchor, props) }) }), true)); }); img.src = url; } }), this.props.data.new && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.TextBadge, { className: BDFDB.disCN.discoverycardcoverbadge, style: { borderRadius: 3, textTransform: "uppercase", background: BDFDB.DiscordConstants.Colors.YELLOW }, text: BDFDB.LanguageUtils.LanguageStrings.NEW }) ] }), BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardiconwrapper, children: BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardicon, children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { nativeClass: true, iconSVG: `` }) }) }) ] }), BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardinfo, children: [ BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardtitle, children: [ BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardname, children: this.props.data.name }), this.props.data.latestSourceUrl && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { text: BDFDB.LanguageUtils.LanguageStrings.SCREENSHARE_SOURCE, children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Clickable, { className: BDFDB.disCN.discoverycardtitlebutton, children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { nativeClass: true, width: 16, height: 16, name: BDFDB.LibraryComponents.SvgIcon.Names.GITHUB }) }), onClick: _ => BDFDB.DiscordUtils.openLink(this.props.data.latestSourceUrl) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FavButton, { className: BDFDB.disCN.discoverycardtitlebutton, isFavorite: this.props.data.fav, onClick: value => { this.props.data.fav = value && 1; if (value) favorites.push(this.props.data.id); else BDFDB.ArrayUtils.remove(favorites, this.props.data.id, true); BDFDB.DataUtils.save(BDFDB.ArrayUtils.numSort(favorites).join(" "), _this, "favorites"); } }) ] }), BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardauthor, children: `by ${this.props.data.authorname}` }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Scrollers.Thin, { className: BDFDB.disCN.discoverycarddescription, children: this.props.data.description }), BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardfooter, children: [ BDFDB.ArrayUtils.is(this.props.data.tags) && this.props.data.tags.length && BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardtags, children: this.props.data.tags.map(tag => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.TextBadge, { className: BDFDB.disCN.discoverycardtag, style: {background: "var(--background-accent)"}, text: tag })) }), BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardcontrols, children: [ BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardstats, children: [ BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardstat, children: [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { className: BDFDB.disCN.discoverycardstaticon, width: 16, height: 16, name: BDFDB.LibraryComponents.SvgIcon.Names.DOWNLOAD }), this.props.data.downloads ] }), BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN.discoverycardstat, children: [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { className: BDFDB.disCN.discoverycardstaticon, width: 16, height: 16, name: BDFDB.LibraryComponents.SvgIcon.Names.HEART }), this.props.data.likes ] }) ] }), BDFDB.ReactUtils.createElement(RepoCardDownloadButtonComponent, { ...buttonData[(Object.entries(pluginStates).find(n => n[1] == this.props.data.state) || [])[0]], installed: this.props.data.state == pluginStates.INSTALLED, outdated: this.props.data.state == pluginStates.OUTDATED, onDownload: _ => { if (this.props.downloading) return; this.props.downloading = true; let loadingToast = BDFDB.NotificationUtils.toast(`${BDFDB.LanguageUtils.LibraryStringsFormat("loading", this.props.data.name)} - ${BDFDB.LanguageUtils.LibraryStrings.please_wait}`, {timeout: 0, ellipsis: true}); let autoloadKey = this.props.data.state == pluginStates.OUTDATED ? "startUpdated" : "startDownloaded"; BDFDB.LibraryRequires.request(this.props.data.rawSourceUrl, (error, response, body) => { if (error || !body) { delete this.props.downloading; loadingToast.close(); BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("download_fail", `Plugin "${this.props.data.name}"`), {type: "danger"}); } else BDFDB.LibraryRequires.fs.writeFile(BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getPluginsFolder(), this.props.data.rawSourceUrl.split("/").pop()), body, error2 => { delete this.props.downloading; loadingToast.close(); if (error2) BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("save_fail", `Plugin "${this.props.data.name}"`), {type: "danger"}); else { BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("save_success", `Plugin "${this.props.data.name}"`), {type: "success"}); if (_this.settings.general[autoloadKey]) BDFDB.TimeUtils.timeout(_ => { if (this.props.data.state == pluginStates.INSTALLED && BDFDB.BDUtils.isPluginEnabled(this.props.data.name) == false) { BDFDB.BDUtils.enablePlugin(this.props.data.name, false); BDFDB.LogUtils.log(BDFDB.LanguageUtils.LibraryStringsFormat("toast_plugin_started", this.props.data.name), _this); } }, 3000); this.props.data.state = pluginStates.INSTALLED; BDFDB.ReactUtils.forceUpdate(this); } }); }); }, onDelete: _ => { if (this.props.deleting) return; this.props.deleting = true; BDFDB.LibraryRequires.fs.unlinkSync(BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getPluginsFolder(), this.props.data.rawSourceUrl.split("/").pop())); delete this.props.deleting; BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("delete_success", `Plugin "${this.props.data.name}"`)); this.props.data.state = pluginStates.DOWNLOADABLE; BDFDB.ReactUtils.forceUpdate(this); } }) ] }) ] }) ] }) ] }); } }; const RepoCardDownloadButtonComponent = class PluginCardDownloadButton extends BdApi.React.Component { render() { const backgroundColor = this.props.doDelete ? buttonData.OUTDATED.backgroundColor : this.props.doUpdate ? buttonData.INSTALLED.backgroundColor : this.props.backgroundColor; return BDFDB.ReactUtils.createElement("button", { className: BDFDB.disCN.discoverycardbutton, style: {backgroundColor: BDFDB.DiscordConstants.Colors[backgroundColor] || backgroundColor}, children: [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { className: BDFDB.disCN.discoverycardstaticon, width: 16, height: 16, name: this.props.doDelete ? BDFDB.LibraryComponents.SvgIcon.Names.TRASH : this.props.doUpdate ? BDFDB.LibraryComponents.SvgIcon.Names.DOWNLOAD : BDFDB.LibraryComponents.SvgIcon.Names[this.props.icon] }), this.props.doDelete ? BDFDB.LanguageUtils.LanguageStrings.APPLICATION_CONTEXT_MENU_UNINSTALL : this.props.doUpdate ? BDFDB.LanguageUtils.LanguageStrings.GAME_ACTION_BUTTON_UPDATE : (BDFDB.LanguageUtils.LibraryStringsCheck[this.props.text] ? BDFDB.LanguageUtils.LibraryStrings[this.props.text] : BDFDB.LanguageUtils.LanguageStrings[this.props.text]) ], onClick: _ => { if (this.props.doDelete) typeof this.props.onDelete == "function" && this.props.onDelete(); else if (!this.props.installed) typeof this.props.onDownload == "function" && this.props.onDownload(); }, onMouseEnter: this.props.installed ? (_ => { this.props.doDelete = true; BDFDB.ReactUtils.forceUpdate(this); }) : this.props.outdated ? (_ => { this.props.doUpdate = true; BDFDB.ReactUtils.forceUpdate(this); }) : (_ => {}), onMouseLeave: this.props.installed ? (_ => { this.props.doDelete = false; BDFDB.ReactUtils.forceUpdate(this); }) : this.props.outdated ? (_ => { this.props.doUpdate = false; BDFDB.ReactUtils.forceUpdate(this); }) : (_ => {}) }); } }; return class PluginRepo extends Plugin { onLoad () { _this = this; loading = {is: false, timeout: null, amount: 0}; cachedPlugins = []; grabbedPlugins = []; searchString = ""; this.defaults = { general: { notifyOutdated: {value: true, autoload: false, description: "Shows a Notification when one of your Plugins is outdated"}, notifyNewEntries: {value: true, autoload: false, description: "Shows a Notification when there are new Entries in the Repo"}, startDownloaded: {value: false, autoload: true, description: "Starts new Plugins after Download"}, startUpdated: {value: false, autoload: true, description: "Starts updated Plugins after Download"} }, filters: { updated: {value: true, description: "Updated"}, outdated: {value: true, description: "Outdated"}, downloadable: {value: true, description: "Downloadable"}, } }; this.modulePatches = { before: [ "SettingsView", "StandardSidebarView" ], componentWillUnmount: [ "SettingsView" ] }; } onStart () { this.forceUpdateAll(); this.loadPlugins(); updateInterval = BDFDB.TimeUtils.interval(_ => this.checkForNewPlugins(), 1000*60*30); } onStop () { BDFDB.TimeUtils.clear(updateInterval); BDFDB.TimeUtils.clear(loading.timeout); this.forceUpdateAll(); BDFDB.DOMUtils.remove(BDFDB.dotCN._pluginreponotice, BDFDB.dotCN._pluginrepoloadingicon); } onSettingsClosed () { if (this.SettingsUpdated) { delete this.SettingsUpdated; this.forceUpdateAll(); } } forceUpdateAll () { favorites = BDFDB.DataUtils.load(this, "favorites"); favorites = (typeof favorites == "string" ? favorites.split(" ") : []).map(n => parseInt(n)).filter(n => !isNaN(n)); BDFDB.PatchUtils.forceAllUpdates(this); } onUserSettingsCogContextMenu (e) { BDFDB.TimeUtils.timeout(_ => { let [children, index] = BDFDB.ReactUtils.findParent(e.returnvalue, {props: [["label", ["BandagedBD", "BetterDiscord"]]]}); if (index > -1 && BDFDB.ArrayUtils.is(children[index].props.children)) { let item = BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { label: "Plugin Repo", id: BDFDB.ContextMenuUtils.createItemId(this.name, "repo"), action: _ => BDFDB.LibraryModules.UserSettingsUtils.open("pluginrepo") }); if (children[index].props.children.find(n => n && n.props && n.props.id == "themerepo-repo")) children[index].props.children.splice(children[index].props.children.length-1, 0, item); else children[index].props.children.push(item); } }); } processSettingsView (e) { if (e.node) searchString = ""; else if (e.component.prototype && !BDFDB.PatchUtils.isPatched(this, e.component.prototype, "getPredicateSections")) { BDFDB.PatchUtils.patch(this, e.component.prototype, "getPredicateSections", {after: e2 => { if (BDFDB.ArrayUtils.is(e2.returnValue) && e2.returnValue.findIndex(n => n.section && (n.section.toLowerCase() == "changelog" || n.section == BDFDB.DiscordConstants.UserSettingsSections.CHANGE_LOG || n.section.toLowerCase() == "logout" || n.section == BDFDB.DiscordConstants.UserSettingsSections.LOGOUT))) { e2.returnValue = e2.returnValue.filter(n => n.section != "pluginrepo"); let index = e2.returnValue.indexOf(e2.returnValue.find(n => n.section == "themes") || e2.returnValue.find(n => n.section == BDFDB.DiscordConstants.UserSettingsSections.DEVELOPER_OPTIONS) || e2.returnValue.find(n => n.section == BDFDB.DiscordConstants.UserSettingsSections.HYPESQUAD_ONLINE)); if (index > -1) { e2.returnValue.splice(index + 1, 0, { className: "pluginrepo-tab", section: "pluginrepo", label: "Plugin Repo", element: _ => { let options = Object.assign({}, this.settings.filters); options.updated = options.updated && !showOnlyOutdated; options.outdated = options.outdated || showOnlyOutdated; options.downloadable = options.downloadable && !showOnlyOutdated; options.sortKey = forcedSort || Object.keys(sortKeys)[0]; options.orderKey = forcedOrder || Object.keys(orderKeys)[0]; return BDFDB.ReactUtils.createElement(RepoListComponent, options, true); } }); if (!e2.returnValue.find(n => n.section == "plugins")) e2.returnValue.splice(index + 1, 0, {section: "DIVIDER"}); } } }}); } } processStandardSidebarView (e) { if (e.instance.props && e.instance.props.section == "pluginrepo") e.instance.props.contentType = "custom"; } loadPlugins (forceBanner) { BDFDB.DOMUtils.remove(BDFDB.dotCN._pluginrepoloadingicon); cachedPlugins = BDFDB.DataUtils.load(this, "cached"); cachedPlugins = (typeof cachedPlugins == "string" ? cachedPlugins.split(" ") : []).map(n => parseInt(n)).filter(n => !isNaN(n)); let loadingIcon; let newEntries = 0, outdatedEntries = 0, checkIndex = 0, checksRunning = 0, callbackCalled = false; const checkPlugin = _ => { if (checksRunning > 20) return; else if (grabbedPlugins.every(p => p.loaded || (!p.latestSourceUrl && !p.latest_source_url)) || !this.started || !loading.is) { if (!callbackCalled) { callbackCalled = true; if (!this.started) return BDFDB.TimeUtils.clear(loading.timeout); BDFDB.TimeUtils.clear(loading.timeout); BDFDB.DOMUtils.remove(loadingIcon, BDFDB.dotCN._pluginrepoloadingicon); loading = {is: false, timeout: null, amount: loading.amount}; BDFDB.LogUtils.log("Finished fetching Plugins", this); BDFDB.ReactUtils.forceUpdate(list); if ((this.settings.general.notifyOutdated || forceBanner) && outdatedEntries > 0) { let notice = document.querySelector(BDFDB.dotCN._pluginrepooutdatednotice); if (notice) notice.close(); BDFDB.NotificationUtils.notice(this.labels.notice_outdated_plugins.replace("{{var0}}", outdatedEntries), { type: "danger", className: BDFDB.disCNS._pluginreponotice + BDFDB.disCN._pluginrepooutdatednotice, customIcon: pluginRepoIcon.replace(/COLOR_[0-9]+/gi, "currentColor"), buttons: [{ contents: BDFDB.LanguageUtils.LanguageStrings.OPEN, close: true, onClick: _ => { showOnlyOutdated = true; BDFDB.LibraryModules.UserSettingsUtils.open("pluginrepo"); } }] }); } if (this.settings.general.notifyNewEntries && newEntries > 0) { let notice = document.querySelector(BDFDB.dotCN._pluginreponewentriesnotice); if (notice) notice.close(); BDFDB.NotificationUtils.notice(this.labels.notice_new_plugins.replace("{{var0}}", newEntries), { type: "success", className: BDFDB.disCNS._pluginreponotice + BDFDB.disCN._pluginreponewentriesnotice, customIcon: pluginRepoIcon.replace(/COLOR_[0-9]+/gi, "currentColor"), buttons: [{ contents: BDFDB.LanguageUtils.LanguageStrings.OPEN, close: true, onClick: _ => { forcedSort = "RELEASEDATE"; forcedOrder = "ASC"; BDFDB.LibraryModules.UserSettingsUtils.open("pluginrepo"); } }] }); } } return; } else if (checkIndex > grabbedPlugins.length) return; const plugin = grabbedPlugins[checkIndex++]; if (!plugin || (!plugin.latestSourceUrl && !plugin.latest_source_url)) checkPlugin(); else { checksRunning++; plugin.releasedate = new Date(plugin.releaseDate || plugin.release_date || 0).getTime(); plugin.latestSourceUrl = plugin.latestSourceUrl || plugin.latest_source_url; plugin.rawSourceUrl = plugin.latestSourceUrl.replace("https://github.com/", "https://raw.githubusercontent.com/").replace(/\/blob\/(.{32,})/i, "/$1"); plugin.thumbnailUrl = plugin.thumbnailUrl || plugin.thumbnail_url; plugin.thumbnailUrl = plugin.thumbnailUrl ? (plugin.thumbnailUrl.startsWith("https://") ? plugin.thumbnailUrl : `https://betterdiscord.app${plugin.thumbnailUrl}`) : ""; delete plugin.release_date; delete plugin.latest_source_url; delete plugin.thumbnail_url; BDFDB.LibraryRequires.request(plugin.rawSourceUrl, (error, response, body) => { if (error || !body || body.indexOf("404: Not Found") == 0) plugin.failed = true; else { const META = body.split("*/")[0]; plugin.name = BDFDB.StringUtils.upperCaseFirstChar((/@name\s+([^\t^\r^\n]+)|\/\/\**META.*["']name["']\s*:\s*["'](.+?)["']/i.exec(META) || []).filter(n => n)[1] || plugin.name || ""); plugin.authorname = (/@author\s+(.+)|\/\/\**META.*["']author["']\s*:\s*["'](.+?)["']/i.exec(META) || []).filter(n => n)[1] || plugin.author.display_name || plugin.author; const version = (/@version\s+(.+)|\/\/\**META.*["']version["']\s*:\s*["'](.+?)["']/i.exec(META) || []).filter(n => n)[1]; if (version) { plugin.version = version; const installedPlugin = this.getInstalledPlugin(plugin); if (installedPlugin && this.compareVersions(version, this.getString(installedPlugin.plugin && installedPlugin.plugin.version || installedPlugin.version))) outdatedEntries++; } if (!cachedPlugins.includes(plugin.id)) newEntries++; } plugin.loaded = true; let loadingTooltip = document.querySelector(BDFDB.dotCN._pluginrepoloadingtooltip); if (loadingTooltip) loadingTooltip.update(this.getLoadingTooltipText()); checksRunning--; checkPlugin(); }); } }; BDFDB.TimeUtils.timeout(_ => BDFDB.LibraryRequires.request("https://api.betterdiscord.app/v1/store/plugins", {bdVersion: true}, (error, response, body) => { if (!error && body && response.statusCode == 200) try { grabbedPlugins = BDFDB.ArrayUtils.keySort(JSON.parse(body).filter(n => n), "name"); BDFDB.DataUtils.save(BDFDB.ArrayUtils.numSort(grabbedPlugins.map(n => n.id)).join(" "), this, "cached"); loading = {is: true, timeout: BDFDB.TimeUtils.timeout(_ => { BDFDB.TimeUtils.clear(loading.timeout); if (this.started) { if (loading.is && loading.amount < 4) BDFDB.TimeUtils.timeout(_ => this.loadPlugins(), 10000); loading = {is: false, timeout: null, amount: loading.amount}; } }, 1200000), amount: loading.amount + 1}; loadingIcon = BDFDB.DOMUtils.create(pluginRepoIcon.replace(/COLOR_1/gi, "var(--bdfdb-blurple)").replace(/COLOR_2/gi, "#72767d")); BDFDB.DOMUtils.addClass(loadingIcon, BDFDB.disCN._pluginrepoloadingicon); loadingIcon.addEventListener("mouseenter", _ => { BDFDB.TooltipUtils.create(loadingIcon, this.getLoadingTooltipText(), { type: "left", className: BDFDB.disCN._pluginrepoloadingtooltip, delay: 500, style: "max-width: unset;" }); }); BDFDB.PluginUtils.addLoadingIcon(loadingIcon); BDFDB.ReactUtils.forceUpdate(list); for (let i = 0; i <= 20; i++) checkPlugin(); } catch (err) {BDFDB.NotificationUtils.toast("Failed to load Plugin Store", {type: "danger"});} if (response && response.statusCode == 403) BDFDB.NotificationUtils.toast("Failed to fetch Plugin Store from the Website Api due to DDoS Protection", {type: "danger"}); else if (response && response.statusCode == 404) BDFDB.NotificationUtils.toast("Failed to fetch Plugin Store from the Website Api due to Connection Issue", {type: "danger"}); }), 10000); } getLoadingTooltipText () { return BDFDB.LanguageUtils.LibraryStringsFormat("loading", `Plugin Repo - [${grabbedPlugins.filter(n => n.loaded).length}/${grabbedPlugins.length}]`); } getString (obj) { let string = ""; if (typeof obj == "string") string = obj; else if (obj && obj.props) { if (typeof obj.props.children == "string") string = obj.props.children; else if (Array.isArray(obj.props.children)) for (let c of obj.props.children) string += typeof c == "string" ? c : this.getString(c); } return string; } compareVersions (v1, v2) { return !(v1 == v2 || !BDFDB.NumberUtils.compareVersions(v1, v2)); } getInstalledPlugin (plugin) { if (!plugin || typeof plugin.authorname != "string") return; const iPlugin = BDFDB.BDUtils.getPlugin(plugin.name, false, true); if (iPlugin && (plugin.authorname.toUpperCase().indexOf(this.getString(iPlugin.author).toUpperCase()) > -1 || this.getString(iPlugin.author).toUpperCase().indexOf(plugin.authorname.toUpperCase()) > -1)) return iPlugin; else if (plugin.rawSourceUrl && window.BdApi && BdApi.Plugins && typeof BdApi.Plugins.getAll == "function") { const filename = plugin.rawSourceUrl.split("/").pop(); for (let p of BdApi.Plugins.getAll()) if (p.filename == filename && (plugin.authorname.toUpperCase().indexOf(this.getString(p.author).toUpperCase()) > -1 || this.getString(p.author).toUpperCase().indexOf(plugin.authorname.toUpperCase()) > -1)) return p; } } checkForNewPlugins () { BDFDB.LibraryRequires.request("https://api.betterdiscord.app/v1/store/plugins", {bdVersion: true}, (error, response, body) => { if (!error && body) try { if (JSON.parse(body).filter(n => n).length != grabbedPlugins.length) { loading = {is: false, timeout: null, amount: 0}; this.loadPlugins(); } } catch (err) {BDFDB.NotificationUtils.toast("Failed to load Plugin Store", {type: "danger"});} }); } setLabelsByLanguage () { switch (BDFDB.LanguageUtils.getLanguage().id) { case "bg": // Bulgarian return { list: "Списък", notice_failed_plugins: "Някои Plugins [{{var0}}] не можаха да бъдат заредени", notice_new_plugins: "Новите Plugins [{{var0}}] бяха добавени към Plugin Repo", notice_outdated_plugins: "Някои Plugins [{{var0}}] са остарели" }; case "da": // Danish return { list: "Liste", notice_failed_plugins: "Nogle Plugins [{{var0}}] kunne ikke indlæses", notice_new_plugins: "Nye Plugins [{{var0}}] er blevet føjet til Plugin Repo", notice_outdated_plugins: "Nogle Plugins [{{var0}}] er forældede" }; case "de": // German return { list: "Liste", notice_failed_plugins: "Einige Plugins [{{var0}}] konnten nicht geladen werden", notice_new_plugins: "Neue Plugins [{{var0}}] wurden zur Plugin Repo hinzugefügt", notice_outdated_plugins: "Einige Plugins [{{var0}}] sind veraltet" }; case "el": // Greek return { list: "Λίστα", notice_failed_plugins: "Δεν ήταν δυνατή η φόρτωση ορισμένων Πρόσθετων [{{var0}}] ", notice_new_plugins: "Προστέθηκαν νέα Πρόσθετα [{{var0}}] στο Αποθετήριο Προσθέτων", notice_outdated_plugins: "Ορισμένα Πρόσθετα [{{var0}}] είναι παλαιά" }; case "es": // Spanish return { list: "Lista", notice_failed_plugins: "Algunos Plugins [{{var0}}] no se pudieron cargar", notice_new_plugins: "Se han agregado nuevos Plugins [{{var0}}] a Plugin Repo", notice_outdated_plugins: "Algunas Plugins [{{var0}}] están desactualizadas" }; case "fi": // Finnish return { list: "Lista", notice_failed_plugins: "Joitain kohdetta Plugins [{{var0}}] ei voitu ladata", notice_new_plugins: "Uusi Plugins [{{var0}}] on lisätty Plugin Repo", notice_outdated_plugins: "Jotkut Plugins [{{var0}}] ovat vanhentuneita" }; case "fr": // French return { list: "Liste", notice_failed_plugins: "Certains Plugins [{{var0}}] n'ont pas pu être chargés", notice_new_plugins: "De nouveaux Plugins [{{var0}}] ont été ajoutés à Plugin Repo", notice_outdated_plugins: "Certains Plugins [{{var0}}] sont obsolètes" }; case "hr": // Croatian return { list: "Popis", notice_failed_plugins: "Neke datoteke Plugins [{{var0}}] nije moguće učitati", notice_new_plugins: "Novi Plugins [{{var0}}] dodani su u Plugin Repo", notice_outdated_plugins: "Neki su Plugins [{{var0}}] zastarjeli" }; case "hu": // Hungarian return { list: "Lista", notice_failed_plugins: "Néhány Plugins [{{var0}}] nem sikerült betölteni", notice_new_plugins: "Új Plugins [{{var0}}] hozzáadva a következőhöz: Plugin Repo", notice_outdated_plugins: "Néhány Plugins [{{var0}}] elavult" }; case "it": // Italian return { list: "Elenco", notice_failed_plugins: "Impossibile caricare alcuni Plugins [{{var0}}] ", notice_new_plugins: "Il nuovo Plugins [{{var0}}] è stato aggiunto a Plugin Repo", notice_outdated_plugins: "Alcuni Plugins [{{var0}}] non sono aggiornati" }; case "ja": // Japanese return { list: "リスト", notice_failed_plugins: "一部の Plugins [{{var0}}] を読み込めませんでした", notice_new_plugins: "新しい Plugins [{{var0}}] が Plugin Repo に追加されました", notice_outdated_plugins: "一部の Plugins [{{var0}}] は古くなっています" }; case "ko": // Korean return { list: "명부", notice_failed_plugins: "일부 Plugins [{{var0}}] 을 (를)로드 할 수 없습니다.", notice_new_plugins: "새 Plugins [{{var0}}] 이 Plugin Repo 에 추가되었습니다.", notice_outdated_plugins: "일부 Plugins [{{var0}}] 이 오래되었습니다." }; case "lt": // Lithuanian return { list: "Sąrašas", notice_failed_plugins: "Kai kurių Plugins [{{var0}}] nepavyko įkelti", notice_new_plugins: "Naujas Plugins [{{var0}}] pridėtas prie Plugin Repo", notice_outdated_plugins: "Kai kurie Plugins [{{var0}}] yra pasenę" }; case "nl": // Dutch return { list: "Lijst", notice_failed_plugins: "Sommige Plugins [{{var0}}] konden niet worden geladen", notice_new_plugins: "Nieuwe Plugins [{{var0}}] zijn toegevoegd aan de Plugin Repo", notice_outdated_plugins: "Sommige Plugins [{{var0}}] zijn verouderd" }; case "no": // Norwegian return { list: "Liste", notice_failed_plugins: "Noen Plugins [{{var0}}] kunne ikke lastes inn", notice_new_plugins: "Nye Plugins [{{var0}}] er lagt til i Plugin Repo", notice_outdated_plugins: "Noen Plugins [{{var0}}] er utdaterte" }; case "pl": // Polish return { list: "Lista", notice_failed_plugins: "Nie można załadować niektórych Plugins [{{var0}}] ", notice_new_plugins: "Nowe Plugins [{{var0}}] zostały dodane do Plugin Repo", notice_outdated_plugins: "Niektóre Plugins [{{var0}}] są nieaktualne" }; case "pt-BR": // Portuguese (Brazil) return { list: "Lista", notice_failed_plugins: "Algum Plugins [{{var0}}] não pôde ser carregado", notice_new_plugins: "Novo Plugins [{{var0}}] foi adicionado ao Plugin Repo", notice_outdated_plugins: "Alguns Plugins [{{var0}}] estão desatualizados" }; case "ro": // Romanian return { list: "Listă", notice_failed_plugins: "Unele Plugins [{{var0}}] nu au putut fi încărcate", notice_new_plugins: "Plugins [{{var0}}] nou au fost adăugate la Plugin Repo", notice_outdated_plugins: "Unele Plugins [{{var0}}] sunt învechite" }; case "ru": // Russian return { list: "Список", notice_failed_plugins: "Не удалось загрузить некоторые Plugins [{{var0}}] ", notice_new_plugins: "Новые Plugins [{{var0}}] добавлены в Plugin Repo", notice_outdated_plugins: "Некоторые Plugins [{{var0}}] устарели" }; case "sv": // Swedish return { list: "Lista", notice_failed_plugins: "Vissa Plugins [{{var0}}] kunde inte laddas", notice_new_plugins: "Nya Plugins [{{var0}}] har lagts till i Plugin Repo", notice_outdated_plugins: "Vissa Plugins [{{var0}}] är föråldrade" }; case "th": // Thai return { list: "รายการ", notice_failed_plugins: "ไม่สามารถโหลด Plugins [{{var0}}] บางรายการได้", notice_new_plugins: "เพิ่ม Plugins [{{var0}}] ใหม่ใน Plugin Repo แล้ว", notice_outdated_plugins: "Plugins [{{var0}}] บางรายการล้าสมัย" }; case "tr": // Turkish return { list: "Liste", notice_failed_plugins: "Bazı Plugins [{{var0}}] yüklenemedi", notice_new_plugins: "Yeni Plugins [{{var0}}], Plugin Repo 'ye eklendi", notice_outdated_plugins: "Bazı Plugins [{{var0}}] güncel değil" }; case "uk": // Ukrainian return { list: "Список", notice_failed_plugins: "Деякі Plugins [{{var0}}] не вдалося завантажити", notice_new_plugins: "Нові Plugins [{{var0}}] були додані до Plugin Repo", notice_outdated_plugins: "Деякі Plugins [{{var0}}] застарілі" }; case "vi": // Vietnamese return { list: "Danh sách", notice_failed_plugins: "Không thể tải một số Plugins [{{var0}}] ", notice_new_plugins: "Plugins [{{var0}}] mới đã được thêm vào Plugin Repo", notice_outdated_plugins: "Một số Plugins [{{var0}}] đã lỗi thời" }; case "zh-CN": // Chinese (China) return { list: "清单", notice_failed_plugins: "某些 Plugins [{{var0}}] 无法加载", notice_new_plugins: "新的 Plugins [{{var0}}] 已添加到 Plugin Repo", notice_outdated_plugins: "一些 Plugins [{{var0}}] 已过时" }; case "zh-TW": // Chinese (Taiwan) return { list: "清單", notice_failed_plugins: "某些 Plugins [{{var0}}] 無法加載", notice_new_plugins: "新的 Plugins [{{var0}}] 已添加到 Plugin Repo", notice_outdated_plugins: "一些 Plugins [{{var0}}] 已過時" }; default: // English return { list: "List", notice_failed_plugins: "Some Plugins [{{var0}}] could not be loaded", notice_new_plugins: "New Plugins [{{var0}}] have been added to the Plugin Repo", notice_outdated_plugins: "Some Plugins [{{var0}}] are outdated" }; } } }; })(window.BDFDB_Global.PluginUtils.buildPlugin(changeLog)); })();