diff --git a/Configs/BetterDiscord/plugins/ThemeRepo.config.json b/Configs/BetterDiscord/plugins/ThemeRepo.config.json new file mode 100644 index 0000000..d7d7ec0 --- /dev/null +++ b/Configs/BetterDiscord/plugins/ThemeRepo.config.json @@ -0,0 +1,16 @@ +{ + "all": { + "cached": "1 3 16 18 22 23 32 33 39 40 41 45 46 47 48 49 52 53 56 112 124 125 142 144 147 153 155 156 161 166 172 174 177 204 205 209 218 226 252 256 257 286 296 308 320 342 359 371 385 412 422 439 468 483 507 508 515 541 553 572 576 580 581 597 601 607 613 636 637 641 642 643 653 659 662 664 695 712 713 714 722 734 735 736 746 752 753 770 813 823 858 865 866 894 915 922 928 929 933 973 1006 1008 1014 1030 1033", + "filters": { + "updated": true, + "outdated": true, + "downloadable": true + }, + "general": { + "notifyOutdated": true, + "notifyNewEntries": true, + "startDownloaded": false, + "startUpdated": false + } + } +} \ No newline at end of file diff --git a/Configs/BetterDiscord/plugins/ThemeRepo.plugin.js b/Configs/BetterDiscord/plugins/ThemeRepo.plugin.js new file mode 100644 index 0000000..61c721a --- /dev/null +++ b/Configs/BetterDiscord/plugins/ThemeRepo.plugin.js @@ -0,0 +1,1262 @@ +/** + * @name ThemeRepo + * @author DevilBro + * @authorId 278543574059057154 + * @version 2.5.6 + * @description Allows you to download all Themes 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/ThemeRepo/ + * @updateUrl https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/ThemeRepo.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 () { + BdApi.Net.fetch("https://mwittrien.github.io/BetterDiscordAddons/Library/0BDFDB.plugin.js").then(r => { + if (!r || r.status != 200) throw new Error(); + else return r.text(); + }).then(b => { + if (!b) throw new Error(); + else return require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0BDFDB.plugin.js"), b, _ => BdApi.showToast("Finished downloading BDFDB Library", {type: "success"})); + }).catch(error => { + 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, cachedThemes, grabbedThemes, generatorThemes, updateInterval, errorState; + var searchString, searchTimeout, forcedSort, forcedOrder, showOnlyOutdated; + var updateGeneratorTimeout, forceRerenderGenerator, nativeCSS, nativeCSSvars; + + var favorites = []; + + const themeStates = { + 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 themeRepoIcon = ``; + + const RepoListComponent = class ThemeList extends BdApi.React.Component { + componentDidMount() { + list = this; + BDFDB.TimeUtils.timeout(_ => { + forcedSort = null; + forcedOrder = null; + showOnlyOutdated = false; + }, 5000); + } + componentWillUnmount() { + list = null; + } + filterThemes() { + let themes = grabbedThemes.map(theme => { + if (theme.failed) return; + const installedTheme = _this.getInstalledTheme(theme); + const state = installedTheme ? (theme.version && _this.compareVersions(theme.version, _this.getString(installedTheme.version)) ? themeStates.OUTDATED : themeStates.INSTALLED) : themeStates.DOWNLOADABLE; + return Object.assign(theme, { + search: [theme.name, theme.version, theme.authorname, theme.description, theme.tags].flat(10).filter(n => typeof n == "string").join(" ").toUpperCase(), + description: theme.description || "No Description found", + fav: favorites.includes(theme.id) && 1, + new: state == themeStates.DOWNLOADABLE && !cachedThemes.includes(theme.id) && 1, + state: state + }); + }).filter(n => n); + if (!this.props.updated) themes = themes.filter(theme => theme.state != themeStates.INSTALLED); + if (!this.props.outdated) themes = themes.filter(theme => theme.state != themeStates.OUTDATED); + if (!this.props.downloadable) themes = themes.filter(theme => theme.state != themeStates.DOWNLOADABLE); + if (searchString) { + let usedSearchString = searchString.toUpperCase(); + let spacelessUsedSearchString = usedSearchString.replace(/\s/g, ""); + themes = themes.filter(theme => theme.search.indexOf(usedSearchString) > -1 || theme.search.indexOf(spacelessUsedSearchString) > -1); + } + + BDFDB.ArrayUtils.keySort(themes, this.props.sortKey.toLowerCase()); + if (this.props.orderKey == "DESC") themes.reverse(); + if (reverseSorts.includes(this.props.sortKey)) themes.reverse(); + return themes; + } + createThemeFile(name, filename, body, autoloadKey) { + return new Promise(callback => BDFDB.LibraryRequires.fs.writeFile(BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getThemesFolder(), filename), body, error => { + if (error) { + callback(true); + BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("save_fail", `Theme "${name}"`), {type: "danger"}); + } + else { + BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("save_success", `Theme "${name}"`), {type: "success"}); + if (_this.settings.general[autoloadKey]) BDFDB.TimeUtils.timeout(_ => { + if (BDFDB.BDUtils.isThemeEnabled(name) == false) { + BDFDB.BDUtils.enableTheme(name, false); + BDFDB.LogUtils.log(BDFDB.LanguageUtils.LibraryStringsFormat("toast_plugin_started", name), _this); + } + }, 3000); + callback(); + } + })); + } + generateTheme(css) { + if (!css || !BDFDB.ObjectUtils.is(this.props.generatorValues)) return ""; + for (let inputId in this.props.generatorValues) if (this.props.generatorValues[inputId].value && this.props.generatorValues[inputId].value.trim() && this.props.generatorValues[inputId].value != this.props.generatorValues[inputId].oldValue) css = css.replace(new RegExp(`--${BDFDB.StringUtils.regEscape(inputId)}(\\s*):(\\s*)${BDFDB.StringUtils.regEscape(this.props.generatorValues[inputId].oldValue)}`,"g"),`--${inputId}$1: $2${this.props.generatorValues[inputId].value}`); + return css; + } + createFixerCSS(body) { + let oldCSS = body.replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\r/g, "\\r").split("REPLACE_CLASS_"); + let newCSS = oldCSS.shift(); + for (let str of oldCSS) { + let reg = /([A-z0-9_]+)(.*)/.exec(str); + newCSS += BDFDB.dotCN[reg[1]] + reg[2]; + } + return newCSS.replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\r/g, "\r"); + } + render() { + if (!this.props.tab) this.props.tab = "Themes"; + + const entries = (!loading.is && grabbedThemes.length ? this.filterThemes() : []); + + const emptyState = errorState ? { + lightSrc: "/assets/d6dfb89ab06b62044dbb.svg", + darkSrc: "/assets/8eeb59bba0a61cbffc41.svg", + text: "Could not load Theme Store due to an Issue with the BD Website" + } : !entries.length && !this.props.updated && !this.props.outdated && !this.props.downloadable ? { + text: `You disabled all Filter Options in the "${BDFDB.LanguageUtils.LanguageStrings.SETTINGS}" Tab` + } : !entries.length && searchString ? { + lightSrc: "/assets/75081bdaad2d359c1469.svg", + darkSrc: "/assets/45cd76fed34c8e398cc8.svg" + } : !entries.length ? {} : null; + + if (forceRerenderGenerator && this.props.tab == "Generator") BDFDB.TimeUtils.timeout(_ => { + forceRerenderGenerator = false; + BDFDB.ReactUtils.forceUpdate(this); + }); + + 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: `Theme Repo — ${loading.is ? 0 : entries.length || 0}/${loading.is ? 0 : grabbedThemes.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.loadThemes(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: "Themes"}, {value: "Generator"}, {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: "Themes", + open: this.props.tab == "Themes", + 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", "Theme Repo")} - ${BDFDB.LanguageUtils.LibraryStrings.please_wait}` + }) + ] + }) : emptyState ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.EmptyStateImage, emptyState) : BDFDB.ReactUtils.createElement("div", { + className: BDFDB.disCN.discoverycards, + children: entries.map(theme => BDFDB.ReactUtils.createElement(RepoCardComponent, { + data: theme + })).filter(n => n) + }) + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, { + tab: "Generator", + open: this.props.tab == "Generator", + render: false, + children: [ + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + type: "Select", + margin: 20, + label: "Choose a Generator Theme", + basis: "60%", + value: this.props.currentGenerator || "-----", + options: [{value: "-----", label: "-----"}, nativeCSSvars && {value: "nativediscord", label: "Discord"}].concat(generatorThemes.map(t => ({value: t.id, label: t.name || "-----"})).sort((x, y) => (x.label < y.label ? -1 : x.label > y.label ? 1 : 0))).filter(n => n), + onChange: value => { + let generatorTheme = generatorThemes.find(t => t.id == value); + if (generatorTheme || value == "nativediscord") { + if (this.props.currentGenerator) forceRerenderGenerator = true; + this.props.currentGenerator = value; + this.props.currentGeneratorIsNative = value == "nativediscord"; + this.props.generatorValues = {}; + } + else { + delete this.props.currentGenerator; + delete this.props.currentGeneratorIsNative; + delete this.props.generatorValues; + } + delete this.props.currentTheme; + BDFDB.ReactUtils.forceUpdate(this); + } + }), + !this.props.currentGenerator ? null : (forceRerenderGenerator ? 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.SettingsItem, { + className: BDFDB.disCN.marginbottom20, + type: "Button", + label: "Download generated Theme", + children: "Download", + onClick: _ => { + if (this.props.currentGeneratorIsNative) { + this.createThemeFile("Discord", "Discord.theme.css", `/**\n * @name Discord\n * @description Allow you to easily customize Discord's native Look \n * @author DevilBro\n * @version 1.0.0\n * @authorId 278543574059057154\n * @invite Jx3TjNS\n * @donate https://www.paypal.me/MircoWittrien\n * @patreon https://www.patreon.com/MircoWittrien\n */\n\n` + this.generateTheme(nativeCSSvars), "startDownloaded"); + } + else { + let generatorTheme = generatorThemes.find(t => t.id == this.props.currentGenerator); + if (generatorTheme) this.createThemeFile(generatorTheme.name, generatorTheme.rawSourceUrl.split("/").pop(), this.generateTheme(generatorTheme.fullCSS), "startDownloaded"); + } + } + }), + BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, { + className: BDFDB.disCN.marginbottom20 + }), + (_ => { + let generatorTheme = generatorThemes.find(t => t.id == this.props.currentGenerator); + let vars = this.props.currentGeneratorIsNative ? nativeCSSvars.split(".theme-dark, .theme-light") : ((generatorTheme || {}).fullCSS || "").split(":root"); + if (vars.length < 2) return null; + vars = vars[1].replace(/\t\(/g, " (").replace(/\r|\t| {2,}/g, "").replace(/\/\*\n*((?!\/\*|\*\/).|\n)*\n+((?!\/\*|\*\/).|\n)*\n*\*\//g, "").replace(/\n\/\*.*?\*\//g, "").replace(/\n/g, ""); + vars = vars.split("{"); + vars.shift(); + vars = vars.join("{").replace(/\s*(:|;|--|\*)\s*/g, "$1"); + vars = vars.split("}")[0]; + vars = (vars.endsWith(";") ? vars.slice(0, -1) : vars).slice(2).split(/;--|\*\/--/); + let inputRefs = []; + for (let varStr of vars) { + varStr = varStr.split(":"); + let varName = varStr.shift().trim(); + varStr = varStr.join(":").split(/;[^A-z0-9]|\/\*/); + let oldValue = varStr.shift().trim(); + if (oldValue) { + let childType = "text", childMode = ""; + let isColor = BDFDB.ColorUtils.getType(oldValue); + let isComp = !isColor && /^[0-9 ]+,[0-9 ]+,[0-9 ]+$/g.test(oldValue); + if (isColor || isComp) { + childType = "color"; + childMode = isComp && "comp"; + } + else { + let isUrlFile = /url\(.+\)/gi.test(oldValue); + let isFile = !isUrlFile && /(http(s)?):\/\/[(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/.test(oldValue); + if (isFile || isUrlFile) { + childType = "file"; + childMode = isUrlFile && "url"; + } + } + let varDescription = varStr.join("").replace(/\*\/|\/\*/g, "").replace(/:/g, ": ").replace(/: \//g, ":/").replace(/--/g, " --").replace(/\( --/g, "(--").trim(); + this.props.generatorValues[varName] = {value: oldValue, oldValue}; + inputRefs.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { + dividerBottom: vars[vars.length-1] != varStr, + type: "TextInput", + childProps: { + type: childType, + mode: childMode, + filter: childType == "file" && "image" + }, + label: varName.split("-").map(BDFDB.StringUtils.upperCaseFirstChar).join(" "), + note: varDescription && varDescription.indexOf("*") == 0 ? varDescription.slice(1) : varDescription, + basis: "70%", + value: oldValue, + placeholder: oldValue, + onChange: value => { + BDFDB.TimeUtils.clear(updateGeneratorTimeout); + updateGeneratorTimeout = BDFDB.TimeUtils.timeout(_ => this.props.generatorValues[varName] = {value, oldValue}, 1000); + } + })); + } + } + return inputRefs; + })() + ]) + ].flat(10).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: "Shows following Themes", + 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 ThemeCard 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, + 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, + name: BDFDB.LibraryComponents.SvgIcon.Names.HEART + }), + this.props.data.likes + ] + }) + ] + }), + BDFDB.ReactUtils.createElement(RepoCardDownloadButtonComponent, { + ...buttonData[(Object.entries(themeStates).find(n => n[1] == this.props.data.state) || [])[0]], + installed: this.props.data.state == themeStates.INSTALLED, + outdated: this.props.data.state == themeStates.OUTDATED, + onDownload: _ => { + if (!list || 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 == themeStates ? "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", `Theme "${this.props.data.name}"`), {type: "danger"}); + } + else list.createThemeFile(this.props.data.name, this.props.data.rawSourceUrl.split("/").pop(), body, autoloadKey).then(error2 => { + delete this.props.downloading; + loadingToast.close(); + if (!error2) { + this.props.data.state = themeStates.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.getThemesFolder(), this.props.data.rawSourceUrl.split("/").pop())); + delete this.props.deleting; + BDFDB.NotificationUtils.toast(BDFDB.LanguageUtils.LibraryStringsFormat("delete_success", `Theme "${this.props.data.name}"`)); + this.props.data.state = themeStates.DOWNLOADABLE; + BDFDB.ReactUtils.forceUpdate(this); + } + }) + ] + }) + ] + }) + ] + }) + ] + }); + } + }; + + const RepoCardDownloadButtonComponent = class ThemeCardDownloadButton 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 ThemeRepo extends Plugin { + onLoad () { + _this = this; + + loading = {is: false, timeout: null, amount: 0}; + + cachedThemes = []; + grabbedThemes = []; + searchString = ""; + + this.defaults = { + general: { + notifyOutdated: {value: true, autoload: false, description: "Shows a Notification when one of your Themes 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 Themes after Download"}, + startUpdated: {value: false, autoload: true, description: "Starts updated Themes 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 () { + generatorThemes = []; + + this.forceUpdateAll(); + + this.loadThemes(); + + updateInterval = BDFDB.TimeUtils.interval(_ => this.checkForNewThemes(), 1000*60*30); + } + + onStop () { + BDFDB.TimeUtils.clear(updateInterval); + BDFDB.TimeUtils.clear(loading.timeout); + + this.forceUpdateAll(); + + BDFDB.DOMUtils.remove(BDFDB.dotCN._themereponotice, BDFDB.dotCN._themerepoloadingicon); + } + + 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)) children[index].props.children.push(BDFDB.ContextMenuUtils.createItem(BDFDB.LibraryComponents.MenuItems.MenuItem, { + label: "Theme Repo", + id: BDFDB.ContextMenuUtils.createItemId(this.name, "repo"), + action: _ => BDFDB.LibraryModules.UserSettingsUtils.open("themerepo") + })); + }); + } + + 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 != "themerepo"); + let index = e2.returnValue.indexOf(e2.returnValue.find(n => n.section == "pluginrepo") || 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: "themerepo-tab", + section: "themerepo", + label: "Theme 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]; + options.useLightMode = BDFDB.DiscordUtils.getTheme() == BDFDB.disCN.themelight; + options.useThemeFixer = false; + options.useCustomCSS = false; + + return BDFDB.ReactUtils.createElement(RepoListComponent, options); + } + }); + if (!e2.returnValue.find(n => n.section == "plugins" || n.section == "pluginrepo")) e2.returnValue.splice(index + 1, 0, {section: "DIVIDER"}); + } + } + }}); + } + } + + processStandardSidebarView (e) { + if (e.instance.props && e.instance.props.section == "themerepo") e.instance.props.contentType = "custom"; + } + + generateTheme (fullCSS, generatorValues) { + if (!fullCSS || !BDFDB.ObjectUtils.is(generatorValues)) return ""; + for (let inputId in generatorValues) if (generatorValues[inputId].value && generatorValues[inputId].value.trim() && generatorValues[inputId].value != generatorValues[inputId].oldValue) fullCSS = fullCSS.replace(new RegExp(`--${BDFDB.StringUtils.regEscape(inputId)}(\\s*):(\\s*)${BDFDB.StringUtils.regEscape(generatorValues[inputId].oldValue)}`,"g"),`--${inputId}$1: $2${generatorValues[inputId].value}`); + return fullCSS; + } + + loadThemes (forceBanner) { + BDFDB.DOMUtils.remove(BDFDB.dotCN._themerepoloadingicon); + cachedThemes = BDFDB.DataUtils.load(this, "cached"); + cachedThemes = (typeof cachedThemes == "string" ? cachedThemes.split(" ") : []).map(n => parseInt(n)).filter(n => !isNaN(n)); + + let loadingIcon; + let newEntries = 0, outdatedEntries = 0, checkIndex = 0, checksRunning = 0, callbackCalled = false; + + const checkTheme = _ => { + if (checksRunning > 20) return; + else if (grabbedThemes.every(t => t.loaded || (!t.latestSourceUrl && !t.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._themerepoloadingicon); + loading = {is: false, timeout: null, amount: loading.amount}; + + BDFDB.LogUtils.log("Finished fetching Themes", this); + BDFDB.ReactUtils.forceUpdate(list); + + if ((this.settings.general.notifyOutdated || forceBanner) && outdatedEntries > 0) { + let notice = document.querySelector(BDFDB.dotCN._themerepooutdatednotice); + if (notice) notice.close(); + BDFDB.NotificationUtils.notice(this.labels.notice_outdated_themes.replace("{{var0}}", outdatedEntries), { + type: "danger", + className: BDFDB.disCNS._themereponotice + BDFDB.disCN._themerepooutdatednotice, + customIcon: themeRepoIcon.replace(/COLOR_[0-9]+/gi, "currentColor"), + buttons: [{ + contents: BDFDB.LanguageUtils.LanguageStrings.OPEN, + close: true, + onClick: _ => { + showOnlyOutdated = true; + BDFDB.LibraryModules.UserSettingsUtils.open("themerepo"); + } + }] + }); + } + + if (this.settings.general.notifyNewEntries && newEntries > 0) { + let notice = document.querySelector(BDFDB.dotCN._themereponewentriesnotice); + if (notice) notice.close(); + BDFDB.NotificationUtils.notice(this.labels.notice_new_themes.replace("{{var0}}", newEntries), { + type: "success", + className: BDFDB.disCNS._themereponotice + BDFDB.disCN._themereponewentriesnotice, + customIcon: themeRepoIcon.replace(/COLOR_[0-9]+/gi, "currentColor"), + buttons: [{ + contents: BDFDB.LanguageUtils.LanguageStrings.OPEN, + close: true, + onClick: _ => { + forcedSort = "RELEASEDATE"; + forcedOrder = "ASC"; + BDFDB.LibraryModules.UserSettingsUtils.open("themerepo"); + } + }] + }); + } + + BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/_res/GeneratorList.txt", (error, response, body) => { + if (!error && body) for (let id of body.replace(/[\r\t]/g, "").split(" ").map(n => parseInt(n)).filter(n => n != null)) { + let theme = grabbedThemes.find(t => t.id == id); + if (theme) generatorThemes.push(theme); + } + }); + + BDFDB.LibraryRequires.request(document.querySelector("head link[rel='stylesheet'][href*='assets/app.'], body link[rel='stylesheet'][href*='assets/app.']").href, (error, response, body) => { + let nativeCSS = !error && body; + if (nativeCSS) { + let theme = BDFDB.DiscordUtils.getTheme(); + let vars = (nativeCSS.split(`.${theme}{`)[1] || "").split("}")[0]; + nativeCSSvars = vars ? `.theme-dark, .theme-light {${vars}}` : ""; + } + else nativeCSS = nativeCSSvars = ""; + }); + } + return; + } + else if (checkIndex > grabbedThemes.length) return; + + const theme = grabbedThemes[checkIndex++]; + if (!theme || (!theme.latestSourceUrl && !theme.latest_source_url)) checkTheme(); + else { + checksRunning++; + theme.releasedate = new Date(theme.releaseDate || theme.release_date || 0).getTime(); + theme.latestSourceUrl = theme.latestSourceUrl || theme.latest_source_url; + theme.rawSourceUrl = theme.latestSourceUrl.replace("https://github.com/", "https://raw.githubusercontent.com/").replace(/\/blob\/(.{32,})/i, "/$1"); + theme.thumbnailUrl = theme.thumbnailUrl || theme.thumbnail_url; + theme.thumbnailUrl = theme.thumbnailUrl ? (theme.thumbnailUrl.startsWith("https://") ? theme.thumbnailUrl : `https://betterdiscord.app${theme.thumbnailUrl}`) : ""; + delete theme.release_date; + delete theme.latest_source_url; + delete theme.thumbnail_url; + BDFDB.LibraryRequires.request(theme.rawSourceUrl, (error, response, body) => { + if (error || !body || body.indexOf("404: Not Found") == 0) theme.failed = true; + else { + const META = body.split("*/")[0]; + theme.name = BDFDB.StringUtils.upperCaseFirstChar((/@name\s+([^\t^\r^\n]+)|\/\/\**META.*["']name["']\s*:\s*["'](.+?)["']/i.exec(META) || []).filter(n => n)[1] || theme.name || ""); + theme.authorname = (/@author\s+(.+)|\/\/\**META.*["']author["']\s*:\s*["'](.+?)["']/i.exec(META) || []).filter(n => n)[1] || theme.author.display_name || theme.author; + const version = (/@version\s+(.+)|\/\/\**META.*["']version["']\s*:\s*["'](.+?)["']/i.exec(META) || []).filter(n => n)[1]; + if (version) theme.version = version; + if (theme.version) { + const installedTheme = this.getInstalledTheme(theme); + if (installedTheme && this.compareVersions(version, this.getString(installedTheme.version))) outdatedEntries++; + } + let text = body.trim(); + let hasMETAline = text.replace(/\s/g, "").indexOf("//META{"), newMeta = ""; + if (hasMETAline < 20 && hasMETAline > -1) { + let i = 0, j = 0, metaString = ""; + try { + for (let c of `{${text.split("{").slice(1).join("{")}`) { + metaString += c; + if (c == "{") i++; + else if (c == "}") j++; + if (i > 0 && i == j) break; + } + let metaObj = JSON.parse(metaString); + newMeta = "/**\n"; + for (let key in metaObj) newMeta += ` * @${key} ${metaObj[key]}\n`; + newMeta += "*/"; + } + catch (err) {newMeta = "";} + } + theme.fullCSS = [newMeta, newMeta ? text.split("\n").slice(1).join("\n") : text].filter(n => n).join("\n"); + theme.css = (hasMETAline < 20 && hasMETAline > -1 ? text.split("\n").slice(1).join("\n") : text).replace(/[\r|\n|\t]/g, ""); + if (!cachedThemes.includes(theme.id)) newEntries++; + } + + theme.loaded = true; + + let loadingTooltip = document.querySelector(BDFDB.dotCN._themerepoloadingtooltip); + if (loadingTooltip) loadingTooltip.update(this.getLoadingTooltipText()); + + checksRunning--; + checkTheme(); + }); + } + }; + + BDFDB.TimeUtils.timeout(_ => BDFDB.LibraryRequires.request("https://api.betterdiscord.app/v1/store/themes", {bdVersion: true}, (error, response, body) => { + if (!error && body && response.statusCode == 200) try { + grabbedThemes = BDFDB.ArrayUtils.keySort(JSON.parse(body).filter(n => n), "name"); + + BDFDB.DataUtils.save(BDFDB.ArrayUtils.numSort(grabbedThemes.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.loadThemes(), 10000); + loading = {is: false, timeout: null, amount: loading.amount}; + } + }, 1200000), amount: loading.amount + 1}; + + loadingIcon = BDFDB.DOMUtils.create(themeRepoIcon.replace(/COLOR_1/gi, "var(--bdfdb-blurple)").replace(/COLOR_2/gi, "#72767d")); + BDFDB.DOMUtils.addClass(loadingIcon, BDFDB.disCN._themerepoloadingicon); + loadingIcon.addEventListener("mouseenter", _ => { + BDFDB.TooltipUtils.create(loadingIcon, this.getLoadingTooltipText(), { + type: "left", + className: BDFDB.disCN._themerepoloadingtooltip, + delay: 500, + style: "max-width: unset;" + }); + }); + BDFDB.PluginUtils.addLoadingIcon(loadingIcon); + + BDFDB.ReactUtils.forceUpdate(list); + + for (let i = 0; i <= 20; i++) checkTheme(); + } + catch (err) {BDFDB.NotificationUtils.toast("Failed to load Theme Store", {type: "danger"});} + let status = {}; + if (response && response.statusCode == 403) status = {code: response.statusCode, reason: " due to DDoS Protection"}; + else if (response && response.statusCode == 404) status = {code: response.statusCode, reason: " due to DDoS Protection"}; + else if (response && response.statusCode == 502) status = {code: response.statusCode, reason: ", because the API is down"}; + if (status.code) { + BDFDB.NotificationUtils.toast("Failed to fetch Theme Store from the Website API" + status.reason, {type: "danger"}); + errorState = status.code; + } + else errorState = null; + }), 10000); + } + + getLoadingTooltipText () { + return BDFDB.LanguageUtils.LibraryStringsFormat("loading", `Theme Repo - [${grabbedThemes.filter(n => n.loaded).length}/${grabbedThemes.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)); + } + + getInstalledTheme (theme) { + if (!theme || typeof theme.authorname != "string") return; + const iTheme = BDFDB.BDUtils.getTheme(theme.name, false, true); + if (iTheme && (theme.authorname.toUpperCase().indexOf(this.getString(iTheme.author).toUpperCase()) > -1 || this.getString(iTheme.author).toUpperCase().indexOf(theme.authorname.toUpperCase()) > -1)) return iTheme; + else if (theme.rawSourceUrl && window.BdApi && BdApi.Themes && typeof BdApi.Themes.getAll == "function") { + const filename = theme.rawSourceUrl.split("/").pop(); + for (let t of BdApi.Themes.getAll()) if (t.filename == filename && (theme.authorname.toUpperCase().indexOf(this.getString(t.author).toUpperCase()) > -1 || this.getString(t.author).toUpperCase().indexOf(theme.authorname.toUpperCase()) > -1)) return t; + } + } + + checkForNewThemes () { + BDFDB.LibraryRequires.request("https://api.betterdiscord.app/v1/store/themes", {bdVersion: true}, (error, response, body) => { + if (!error && body) try { + if (JSON.parse(body).filter(n => n).length != grabbedThemes.length) { + loading = {is: false, timeout: null, amount: 0}; + this.loadThemes(); + } + } + catch (err) {BDFDB.NotificationUtils.toast("Failed to load Theme Store", {type: "danger"});} + }); + } + + setLabelsByLanguage () { + switch (BDFDB.LanguageUtils.getLanguage().id) { + case "bg": // Bulgarian + return { + list: "Списък", + notice_failed_themes: "Някои Themes [{{var0}}] не можаха да бъдат заредени", + notice_new_themes: "Новите Themes [{{var0}}] бяха добавени към Theme Repo", + notice_outdated_themes: "Някои Themes [{{var0}}] са остарели" + }; + case "da": // Danish + return { + list: "Liste", + notice_failed_themes: "Nogle Themes [{{var0}}] kunne ikke indlæses", + notice_new_themes: "Nye Themes [{{var0}}] er blevet føjet til Theme Repo", + notice_outdated_themes: "Nogle Themes [{{var0}}] er forældede" + }; + case "de": // German + return { + list: "Liste", + notice_failed_themes: "Einige Themes [{{var0}}] konnten nicht geladen werden", + notice_new_themes: "Neue Themes [{{var0}}] wurden zur Theme Repo hinzugefügt", + notice_outdated_themes: "Einige Themes [{{var0}}] sind veraltet" + }; + case "el": // Greek + return { + list: "Λίστα", + notice_failed_themes: "Ορισμένα Θέματα [{{var0}}] δεν μπορούν να φορτωθούν", + notice_new_themes: "Νέα Θέματα [{{var0}}] προστέθηκαν στο Απωθετήριο Θεμάτων", + notice_outdated_themes: "Ορισμένα Θέματα [{{var0}}] είναι παλαιά" + }; + case "es": // Spanish + return { + list: "Lista", + notice_failed_themes: "Algunos Themes [{{var0}}] no se pudieron cargar", + notice_new_themes: "Se han agregado nuevos Themes [{{var0}}] a Theme Repo", + notice_outdated_themes: "Algunas Themes [{{var0}}] están desactualizadas" + }; + case "fi": // Finnish + return { + list: "Lista", + notice_failed_themes: "Joitain kohdetta Themes [{{var0}}] ei voitu ladata", + notice_new_themes: "Uusi Themes [{{var0}}] on lisätty Theme Repo", + notice_outdated_themes: "Jotkut Themes [{{var0}}] ovat vanhentuneita" + }; + case "fr": // French + return { + list: "Liste", + notice_failed_themes: "Certains Themes [{{var0}}] n'ont pas pu être chargés", + notice_new_themes: "De nouveaux Themes [{{var0}}] ont été ajoutés à Theme Repo", + notice_outdated_themes: "Certains Themes [{{var0}}] sont obsolètes" + }; + case "hr": // Croatian + return { + list: "Popis", + notice_failed_themes: "Neke datoteke Themes [{{var0}}] nije moguće učitati", + notice_new_themes: "Novi Themes [{{var0}}] dodani su u Theme Repo", + notice_outdated_themes: "Neki su Themes [{{var0}}] zastarjeli" + }; + case "hu": // Hungarian + return { + list: "Lista", + notice_failed_themes: "Néhány Themes [{{var0}}] nem sikerült betölteni", + notice_new_themes: "Új Themes [{{var0}}] hozzáadva a következőhöz: Theme Repo", + notice_outdated_themes: "Néhány Themes [{{var0}}] elavult" + }; + case "it": // Italian + return { + list: "Elenco", + notice_failed_themes: "Impossibile caricare alcuni Themes [{{var0}}] ", + notice_new_themes: "Il nuovo Themes [{{var0}}] è stato aggiunto a Theme Repo", + notice_outdated_themes: "Alcuni Themes [{{var0}}] non sono aggiornati" + }; + case "ja": // Japanese + return { + list: "リスト", + notice_failed_themes: "一部の Themes [{{var0}}] を読み込めませんでした", + notice_new_themes: "新しい Themes [{{var0}}] が Theme Repo に追加されました", + notice_outdated_themes: "一部の Themes [{{var0}}] は古くなっています" + }; + case "ko": // Korean + return { + list: "명부", + notice_failed_themes: "일부 Themes [{{var0}}] 을 (를)로드 할 수 없습니다.", + notice_new_themes: "새 Themes [{{var0}}] 이 Theme Repo 에 추가되었습니다.", + notice_outdated_themes: "일부 Themes [{{var0}}] 이 오래되었습니다." + }; + case "lt": // Lithuanian + return { + list: "Sąrašas", + notice_failed_themes: "Kai kurių Themes [{{var0}}] nepavyko įkelti", + notice_new_themes: "Naujas Themes [{{var0}}] pridėtas prie Theme Repo", + notice_outdated_themes: "Kai kurie Themes [{{var0}}] yra pasenę" + }; + case "nl": // Dutch + return { + list: "Lijst", + notice_failed_themes: "Sommige Themes [{{var0}}] konden niet worden geladen", + notice_new_themes: "Nieuwe Themes [{{var0}}] zijn toegevoegd aan de Theme Repo", + notice_outdated_themes: "Sommige Themes [{{var0}}] zijn verouderd" + }; + case "no": // Norwegian + return { + list: "Liste", + notice_failed_themes: "Noen Themes [{{var0}}] kunne ikke lastes inn", + notice_new_themes: "Nye Themes [{{var0}}] er lagt til i Theme Repo", + notice_outdated_themes: "Noen Themes [{{var0}}] er utdaterte" + }; + case "pl": // Polish + return { + list: "Lista", + notice_failed_themes: "Nie można załadować niektórych Themes [{{var0}}] ", + notice_new_themes: "Nowe Themes [{{var0}}] zostały dodane do Theme Repo", + notice_outdated_themes: "Niektóre Themes [{{var0}}] są nieaktualne" + }; + case "pt-BR": // Portuguese (Brazil) + return { + list: "Lista", + notice_failed_themes: "Algum Themes [{{var0}}] não pôde ser carregado", + notice_new_themes: "Novo Themes [{{var0}}] foi adicionado ao Theme Repo", + notice_outdated_themes: "Alguns Themes [{{var0}}] estão desatualizados" + }; + case "ro": // Romanian + return { + list: "Listă", + notice_failed_themes: "Unele Themes [{{var0}}] nu au putut fi încărcate", + notice_new_themes: "Themes [{{var0}}] nou au fost adăugate la Theme Repo", + notice_outdated_themes: "Unele Themes [{{var0}}] sunt învechite" + }; + case "ru": // Russian + return { + list: "Список", + notice_failed_themes: "Не удалось загрузить некоторые Themes [{{var0}}] ", + notice_new_themes: "Новые Themes [{{var0}}] добавлены в Theme Repo", + notice_outdated_themes: "Некоторые Themes [{{var0}}] устарели" + }; + case "sv": // Swedish + return { + list: "Lista", + notice_failed_themes: "Vissa Themes [{{var0}}] kunde inte laddas", + notice_new_themes: "Nya Themes [{{var0}}] har lagts till i Theme Repo", + notice_outdated_themes: "Vissa Themes [{{var0}}] är föråldrade" + }; + case "th": // Thai + return { + list: "รายการ", + notice_failed_themes: "ไม่สามารถโหลด Themes [{{var0}}] บางรายการได้", + notice_new_themes: "เพิ่ม Themes [{{var0}}] ใหม่ใน Theme Repo แล้ว", + notice_outdated_themes: "Themes [{{var0}}] บางรายการล้าสมัย" + }; + case "tr": // Turkish + return { + list: "Liste", + notice_failed_themes: "Bazı Themes [{{var0}}] yüklenemedi", + notice_new_themes: "Yeni Themes [{{var0}}], Theme Repo 'ye eklendi", + notice_outdated_themes: "Bazı Themes [{{var0}}] güncel değil" + }; + case "uk": // Ukrainian + return { + list: "Список", + notice_failed_themes: "Деякі Themes [{{var0}}] не вдалося завантажити", + notice_new_themes: "Нові Themes [{{var0}}] були додані до Theme Repo", + notice_outdated_themes: "Деякі Themes [{{var0}}] застарілі" + }; + case "vi": // Vietnamese + return { + list: "Danh sách", + notice_failed_themes: "Không thể tải một số Themes [{{var0}}] ", + notice_new_themes: "Themes [{{var0}}] mới đã được thêm vào Theme Repo", + notice_outdated_themes: "Một số Themes [{{var0}}] đã lỗi thời" + }; + case "zh-CN": // Chinese (China) + return { + list: "清单", + notice_failed_themes: "某些 Themes [{{var0}}] 无法加载", + notice_new_themes: "新的 Themes [{{var0}}] 已添加到 Theme Repo", + notice_outdated_themes: "一些 Themes [{{var0}}] 已过时" + }; + case "zh-TW": // Chinese (Taiwan) + return { + list: "清單", + notice_failed_themes: "某些 Themes [{{var0}}] 無法加載", + notice_new_themes: "新的 Themes [{{var0}}] 已添加到 Theme Repo", + notice_outdated_themes: "一些 Themes [{{var0}}] 已過時" + }; + default: // English + return { + list: "List", + notice_failed_themes: "Some Themes [{{var0}}] could not be loaded", + notice_new_themes: "New Themes [{{var0}}] have been added to the Theme Repo", + notice_outdated_themes: "Some Themes [{{var0}}] are outdated" + }; + } + } + }; + })(window.BDFDB_Global.PluginUtils.buildPlugin(changeLog)); +})(); \ No newline at end of file