//META{"name":"ThemeRepo","authorId":"278543574059057154","invite":"Jx3TjNS","donate":"https://www.paypal.me/MircoWittrien","patreon":"https://www.patreon.com/MircoWittrien","website":"https://github.com/mwittrien/BetterDiscordAddons/tree/master/Plugins/ThemeRepo","source":"https://raw.githubusercontent.com/mwittrien/BetterDiscordAddons/master/Plugins/ThemeRepo/ThemeRepo.plugin.js"}*// var ThemeRepo = (_ => { var loading, cachedThemes, grabbedThemes, foundThemes, loadedThemes, generatorThemes, updateInterval; const themeStates = { UPDATED: 0, OUTDATED: 1, DOWNLOADABLE: 2 }; const buttonData = { UPDATED: { colorClass: "GREEN", backgroundColor: "STATUS_GREEN", text: "Updated" }, OUTDATED: { colorClass: "RED", backgroundColor: "STATUS_RED", text: "Outdated" }, DOWNLOADABLE: { colorClass: "BRAND", backgroundColor: "BRAND", text: "Download" } }; const favStates = { FAVORIZED: 0, NOT_FAVORIZED: 1 }; const newStates = { NEW: 0, NOT_NEW: 1 }; const sortKeys = { NAME: "Name", AUTHOR: "Author", VERSION: "Version", DESCRIPTION: "Description", STATE: "Update State", FAV: "Favorites", NEW: "New Themes" }; const orderKeys = { ASC: "Ascending", DESC: "Descending" }; const themeRepoIcon = ``; const repoListComponent = class ThemeList extends BdApi.React.Component { render() { let list = BDFDB.ReactUtils.createElement("ul", { className: BDFDB.disCN._repolist, style: { display: "flex", flexDirection: "column", margin: "unset", width: "unset" }, children: [].concat(this.props.entries).filter(n => n).map(entry => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.AddonCard, entry)) }); BDFDB.ReactUtils.forceStyle(list, ["display", "flex-direction", "margin", "width"]); return list; } }; let forceRerenderGenerator; const generatorComponent = class ThemeGenerator extends BdApi.React.Component { render() { if (forceRerenderGenerator) { BDFDB.TimeUtils.timeout(_ => { forceRerenderGenerator = false; BDFDB.ReactUtils.forceUpdate(this); }, 500); } let theme = this.props.options && loadedThemes[this.props.options.currentGenerator]; return !this.props.options || !this.props.options.frame ? null : [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { className: BDFDB.disCN.marginbottom20, type: "Select", label: "Choose a Generator Theme", basis: "70%", value: this.props.options.currentGenerator || "-----", options: ["-----"].concat(generatorThemes).map(url => {return {value:url, label:(loadedThemes[url] || {}).name || "-----"}}), searchable: true, onChange: (value, instance) => { if (loadedThemes[value.value]) { if (this.props.options.currentGenerator) forceRerenderGenerator = true; this.props.options.currentGenerator = value.value; this.props.options.generatorValues = {}; } else { delete this.props.options.currentGenerator; delete this.props.options.generatorValues; } delete this.props.options.currentTheme; this.props.plugin.updateList(instance, this.props.options); this.props.options.frame.contentWindow.postMessage({ origin: "ThemeRepo", reason: "NewTheme", checked: true, css: (loadedThemes[value.value] || {}).fullcss }, "*"); } }), theme && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { className: BDFDB.disCN.marginbottom20, type: "Button", label: "Download generated Theme", children: "Download", onClick: _ => { this.props.plugin.createThemeFile(theme.name + ".theme.css", this.props.plugin.generateTheme(theme, this.props.options)); } }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormDivider, { className: BDFDB.disCN.marginbottom20 }), theme && !forceRerenderGenerator && this.props.plugin.createGeneratorInputs(theme, this.props.options) ].flat(10).filter(n => n) } }; return class ThemeRepo { getName () {return "ThemeRepo";} getVersion () {return "1.9.5";} getAuthor () {return "DevilBro";} getDescription () {return "Allows you to preview all themes from the theme repo and download them on the fly. Repo button is in the theme settings.";} constructor () { this.patchedModules = { after: { V2C_ContentColumn: "render" } }; } initConstructor () { loading = {is:false, timeout:null, amount:0}; cachedThemes = []; grabbedThemes = []; foundThemes = []; loadedThemes = {}; generatorThemes = []; this.defaults = { settings: { useChromium: {value:false, description:"Use an inbuilt browser window instead of opening your default browser"}, notifyOutdated: {value:true, description:"Notifies you when one of your Themes is outdated"}, notifyNewentries: {value:true, description:"Notifies you when there are new entries in the Repo"} }, modalSettings: { updated: {value:true, modify:true, description:"Show updated Themes",}, outdated: {value:true, modify:true, description:"Show outdated Themes"}, downloadable: {value:true, modify:true, description:"Show downloadable Themes"}, rnmStart: {value:true, modify:false, description:"Apply Theme after Download"} } }; this.css = ` iframe.discordPreview { width: 100vw !important; height: 100vh !important; position: absolute !important; z-index: 999 !important; } iframe.discordPreview ~ ${BDFDB.dotCN.appmount} { position: absolute !important; top: 0 !important; } iframe.discordPreview ~ ${BDFDB.dotCNS.appmount + BDFDB.dotCN.titlebar}, iframe.discordPreview ~ ${BDFDB.dotCNS.appmount + BDFDB.dotCN.app} > *:not(.toasts):not(.bd-toasts) { opacity: 0 !important; visibility: hidden !important; } .${this.name}-modal.repo-modal { max-width: 800px; min-height: 90vh; max-height: 90vh; } `; } getSettingsPanel (collapseStates = {}) { if (!window.BDFDB || typeof BDFDB != "object" || !BDFDB.loaded || !this.started) return; let settings = BDFDB.DataUtils.get(this, "settings"); let customList = this.getCustomList(), customUrl = ""; let settingsPanel, settingsItems = []; settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { title: "Settings", collapseStates: collapseStates, children: Object.keys(settings).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { className: BDFDB.disCN.marginbottom8, type: "Switch", plugin: this, keys: ["settings", key], label: this.defaults.settings[key].description, value: settings[key] })) })); settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { title: "Custom Themes", collapseStates: collapseStates, dividertop: true, children: [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FormComponents.FormItem, { title: "Add Theme:", tag: BDFDB.LibraryComponents.FormComponents.FormTitleTags.H3, className: BDFDB.disCNS.margintop4 + BDFDB.disCN.marginbottom8, children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { align: BDFDB.LibraryComponents.Flex.Align.CENTER, children: [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TextInput, { placeholder: "Insert Raw Github Link of Theme (https://raw.githubusercontent.com/...)", onChange: value => {customUrl = value.trim();} }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, { onClick: _ => { if (customUrl) { customList.push(customUrl); BDFDB.DataUtils.save(BDFDB.ArrayUtils.removeCopies(customList), this, "custom"); BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates); } }, children: BDFDB.LanguageUtils.LanguageStrings.ADD }) ] }) }), customList.length ? BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsPanelInner, { title: "Custom Theme List:", className: BDFDB.disCNS.margintop8 + BDFDB.disCN.marginbottom20, first: true, last: true, children: customList.map(url => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Card, { children: url, onRemove: _ => { BDFDB.ArrayUtils.remove(customList, url, true); BDFDB.DataUtils.save(customList, this, "custom"); BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates); } })) }) : null, BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { type: "Button", color: BDFDB.LibraryComponents.Button.Colors.RED, label: "Remove all custom added Themes", onClick: _ => { BDFDB.ModalUtils.confirm(this, "Are you sure you want to remove all added Themes from your own list", _ => { BDFDB.DataUtils.save([], this, "custom"); BDFDB.PluginUtils.refreshSettingsPanel(this, settingsPanel, collapseStates); }); }, children: BDFDB.LanguageUtils.LanguageStrings.REMOVE }) ].flat(10).filter(n => n) })); settingsItems.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.CollapseContainer, { title: "Refetch All", collapseStates: collapseStates, dividertop: true, children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { type: "Button", label: "Force all Themes to be fetched again", onClick: _ => { loading = {is:false, timeout:null, amount:0}; this.loadThemes(); }, children: BDFDB.LanguageUtils.LanguageStrings.ERRORS_RELOAD }) })); return settingsPanel = BDFDB.PluginUtils.createSettingsPanel(this, settingsItems); } //legacy load () {} start () { if (!window.BDFDB) window.BDFDB = {myPlugins:{}}; if (window.BDFDB && window.BDFDB.myPlugins && typeof window.BDFDB.myPlugins == "object") window.BDFDB.myPlugins[this.getName()] = this; let libraryScript = document.querySelector("head script#BDFDBLibraryScript"); if (!libraryScript || (performance.now() - libraryScript.getAttribute("date")) > 600000) { if (libraryScript) libraryScript.remove(); libraryScript = document.createElement("script"); libraryScript.setAttribute("id", "BDFDBLibraryScript"); libraryScript.setAttribute("type", "text/javascript"); libraryScript.setAttribute("src", "https://mwittrien.github.io/BetterDiscordAddons/Plugins/BDFDB.min.js"); libraryScript.setAttribute("date", performance.now()); libraryScript.addEventListener("load", _ => {this.initialize();}); document.head.appendChild(libraryScript); } else if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) this.initialize(); this.startTimeout = setTimeout(_ => { try {return this.initialize();} catch (err) {console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not initiate plugin! " + err);} }, 30000); } initialize () { if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { if (this.started) return; BDFDB.PluginUtils.init(this); // REMOVE 10.02.2020 let olddata = BDFDB.DataUtils.load(this, "ownlist", "ownlist"); if (olddata) { BDFDB.DataUtils.save(olddata, this, "custom"); BDFDB.DataUtils.remove(this, "ownlist"); } this.loadThemes(); updateInterval = BDFDB.TimeUtils.interval(_ => {this.checkForNewThemes();}, 1000*60*30); BDFDB.ModuleUtils.forceAllUpdates(this); } else console.error(`%c[${this.getName()}]%c`, "color: #3a71c1; font-weight: 700;", "", "Fatal Error: Could not load BD functions!"); } stop () { if (window.BDFDB && typeof BDFDB === "object" && BDFDB.loaded) { this.stopping = true; BDFDB.TimeUtils.clear(updateInterval); BDFDB.TimeUtils.clear(loading.timeout); BDFDB.ModuleUtils.forceAllUpdates(this); BDFDB.DOMUtils.remove("iframe.discordPreview", ".bd-themerepobutton", ".themerepo-notice", ".themerepo-loadingicon"); BDFDB.PluginUtils.clear(this); } } // begin of own functions onUserSettingsCogContextMenu (e) { BDFDB.TimeUtils.timeout(_ => { let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {props: [["label", "BandagedBD"]]}); if (index > -1) children[index].props.render.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ContextMenuItems.Item, { label: "Theme Repo", action: _ => { if (!loading.is) BDFDB.ContextMenuUtils.close(e.instance); this.openThemeRepoModal(); } })); }); } processV2CContentColumn (e) { if (typeof e.instance.props.title == "string" && e.instance.props.title.toUpperCase().indexOf("THEMES") == 0) { let [children, index] = BDFDB.ReactUtils.findChildren(e.returnvalue, {key: "folder-button"}); if (index > -1) children.splice(index + 1, 0, BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { text: "Open Theme Repo", children: BDFDB.ReactUtils.createElement("button", { className: `${BDFDB.disCNS._repobutton + BDFDB.disCN._repofolderbutton} bd-themerepobutton`, onClick: _ => {this.openThemeRepoModal();}, children: "ThemeRepo" }) })); } } getCustomList () { let customlist = BDFDB.DataUtils.load(this, "custom"); return BDFDB.ArrayUtils.is(customlist) ? customlist : []; } openThemeRepoModal (options = {}) { if (loading.is) BDFDB.NotificationUtils.toast(`Themes are still being fetched. Try again in some seconds.`, {type:"danger"}); else { let keyPressed, messageReceived; let modalSettings = BDFDB.DataUtils.get(this, "modalSettings"); let searchTimeout, automaticLoading = BDFDB.BDUtils.isAutoLoadEnabled(); options = Object.assign(options, modalSettings); options.updated = options.updated && !options.showOnlyOutdated; options.outdated = options.updated || options.showOnlyOutdated; options.downloadable = options.downloadable && !options.showOnlyOutdated; options.searchString = ""; options.sortKey = options.forcedSort || Object.keys(sortKeys)[0]; options.orderKey = options.forcedOrder || Object.keys(orderKeys)[0]; options.frame = BDFDB.DOMUtils.create(``) let entries = this.createEntries(options); BDFDB.ModalUtils.open(this, { className: "repo-modal", size: "LARGE", header: "Theme Repository", subheader: BDFDB.ReactUtils.createElement(class RepoAmount extends BDFDB.ReactUtils.Component { render () {return `${this.props.entries.length}/${Object.keys(loadedThemes).length} Themes`} }, {entries}), tabBarChildren: [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SearchBar, { autoFocus: true, query: options.searchString, onChange: (value, instance) => { BDFDB.TimeUtils.clear(searchTimeout); searchTimeout = BDFDB.TimeUtils.timeout(_ => { options.searchString = value.replace(/[<|>]/g, "").toUpperCase(); this.updateList(instance, options); }, 1000); }, onClear: instance => { options.searchString = ""; this.updateList(instance, options); } }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.QuickSelect, { label: "Sort by:", value: { label: sortKeys[options.sortKey], value: options.sortKey }, options: Object.keys(sortKeys).filter(n => n != "NEW" || Object.keys(loadedThemes).some(t => !cachedThemes.includes(t))).map(key => ({ label: sortKeys[key], value: key })), onChange: (key, instance) => { options.sortKey = key; this.updateList(instance, options); } }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex.Child, { children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.QuickSelect, { label: "Order:", value: { label: orderKeys[options.orderKey], value: options.orderKey }, options: Object.keys(orderKeys).map(key => ({ label: orderKeys[key], value: key })), onChange: (key, instance) => { options.orderKey = key; this.updateList(instance, options); } }) }) ], children: [ BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, { tab: "Themes", children: BDFDB.ReactUtils.createElement(repoListComponent, { plugin: this, entries: entries }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, { tab: "Generator", children: BDFDB.ReactUtils.createElement(generatorComponent, { plugin: this, options: options }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.ModalComponents.ModalTabContent, { tab: BDFDB.LanguageUtils.LanguageStrings.SETTINGS, children: [ !automaticLoading && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { className: BDFDB.disCN.marginbottom8, children: BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCNS.titledefault + BDFDB.disCN.cursordefault, style: {maxWidth: 760}, children: "To experience ThemeRepo in the best way. I would recommend you to enable BD intern reload function, that way all downloaded files are loaded into Discord without the need to reload." }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Flex, { className: BDFDB.disCN.marginbottom20, children: BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCNS.titledefault + BDFDB.disCN.cursordefault, style: {maxWidth: 760}, children: "You can toggle this menu with the 'Ctrl' key to take a better look at the preview." }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { className: BDFDB.disCN.marginbottom20, type: "Switch", label: "Preview in light mode", value: BDFDB.DiscordUtils.getTheme() == BDFDB.disCN.themelight, onChange: (value, instance) => { options.frame.contentWindow.postMessage({ origin: "ThemeRepo", reason: "DarkLight", checked: value }, "*"); } }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { className: BDFDB.disCN.marginbottom20, type: "Switch", label: "Preview with normalized classes", value: BDFDB.BDUtils.getSettings("fork-ps-4"), onChange: (value, instance) => { options.frame.contentWindow.postMessage({ origin: "ThemeRepo", reason: "Normalize", checked: value }, "*"); } }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { className: BDFDB.disCN.marginbottom20, type: "Switch", label: "Include Custom CSS in Preview", value: false, onChange: (value, instance) => { let customCSS = document.querySelector("style#customcss"); if (customCSS && customCSS.innerText.length > 0) options.frame.contentWindow.postMessage({ origin: "ThemeRepo", reason: "CustomCSS", checked: value, css: customCSS.innerText }, "*"); } }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { className: BDFDB.disCN.marginbottom20, type: "Switch", label: "Include ThemeFixer CSS in Preview", value: false, onChange: (value, instance) => { BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/res/ThemeFixer.css", (error, response, body) => { options.frame.contentWindow.postMessage({ origin: "ThemeRepo", reason: "ThemeFixer", checked: value, css: this.createFixerCSS(body) }, "*"); }); } }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { className: BDFDB.disCN.marginbottom20, type: "Button", label: "Download ThemeFixer", children: "Download", onClick: _ => { BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/res/ThemeFixer.css", (error, response, body) => { this.createThemeFile("ThemeFixer.theme.css", `//META{"name":"ThemeFixer","description":"ThemeFixerCSS for transparent themes","author":"DevilBro","version":"1.0.3","authorId":"278543574059057154","invite":"Jx3TjNS","donate":"https://www.paypal.me/MircoWittrien","patreon":"https://www.patreon.com/MircoWittrien"}*//\n\n` + this.createFixerCSS(body)); }); } }), Object.keys(modalSettings).map(key => BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsSaveItem, { className: BDFDB.disCN.marginbottom20, type: "Switch", plugin: this, keys: ["modalSettings", key], label: this.defaults.modalSettings[key].description, note: key == "rnmStart" && !automaticLoading && "Automatic Loading has to be enabled", disabled: key == "rnmStart" && !automaticLoading, value: options[key], onChange: (value, instance) => { options[key] = value; this.updateList(instance, options); } })) ].flat(10).filter(n => n) }) ], onOpen: (modal, instance) => { let layerContainer = BDFDB.DOMUtils.getParent(BDFDB.dotCN.itemlayercontainer, modal); BDFDB.ListenerUtils.addToChildren(layerContainer, "mouseenter", BDFDB.dotCN.backdropwithlayer, e => { if (!document.querySelector(BDFDB.dotCN.colorpicker)) { for (let child of layerContainer.childNodes) { child.style.setProperty("transition", "opacity .5s ease-in-out", "important"); child.style.setProperty("opacity", "0", "important"); } } }); BDFDB.ListenerUtils.addToChildren(layerContainer, "mouseleave", BDFDB.dotCN.backdropwithlayer, e => { if (!document.querySelector(BDFDB.dotCN.colorpicker)) { layerContainer.childNodes[0].style.setProperty("opacity", "0.85"); layerContainer.childNodes[1].style.setProperty("opacity", "1"); BDFDB.TimeUtils.timeout(_ => {for (let child of layerContainer.childNodes) child.style.removeProperty("transition");}, 500); } }); keyPressed = e => { if (e.which == 17) { if (!Array.from(document.querySelectorAll(BDFDB.dotCNC.input + BDFDB.dotCN.searchbarinput)).some(ele => ele == document.activeElement)) BDFDB.DOMUtils.toggle(layerContainer); } else if (e.which == 27) options.frame.remove(); }; messageReceived = e => { if (!document.contains(options.frame)) { document.removeEventListener("keyup", keyPressed); window.removeEventListener("message", messageReceived); } else if (typeof e.data === "object" && e.data.origin == "DiscordPreview") { switch (e.data.reason) { case "OnLoad": let nativecss = document.querySelector("head link[rel='stylesheet'][integrity]"); let titlebar = document.querySelector(BDFDB.dotCN.titlebar); options.frame.contentWindow.postMessage({ origin: "ThemeRepo", reason: "OnLoad", classes: JSON.stringify(BDFDB.DiscordClasses), classmodules: JSON.stringify(BDFDB.DiscordClassModules), username: BDFDB.UserUtils.me.username, id: BDFDB.UserUtils.me.id, discriminator: BDFDB.UserUtils.me.discriminator, avatar: BDFDB.UserUtils.getAvatar(), nativecss: nativecss && nativecss.href, html: document.documentElement.className, titlebar: titlebar && titlebar.outerHTML }, "*"); options.frame.contentWindow.postMessage({ origin: "ThemeRepo", reason: "DarkLight", checked: BDFDB.DiscordUtils.getTheme() == BDFDB.disCN.themelight }, "*"); break; case "KeyUp": keyPressed(e.data); break; } } }; document.addEventListener("keyup", keyPressed); window.addEventListener("message", messageReceived); document.body.insertBefore(options.frame, document.body.firstElementChild); }, onClose: (modal, instance) => { options.frame.remove(); document.removeEventListener("keyup", keyPressed); window.removeEventListener("message", messageReceived); } }); } } updateList (instance, options = {}) { let modalIns = BDFDB.ReactUtils.findOwner(instance, {name:"BDFDB_Modal", up:true}); if (modalIns) { let amountIns = BDFDB.ReactUtils.findOwner(modalIns, {name:"RepoAmount"}); let listIns = BDFDB.ReactUtils.findOwner(modalIns, {name:"ThemeList"}); let genIns = BDFDB.ReactUtils.findOwner(modalIns, {name:"ThemeGenerator"}); if (amountIns && listIns && genIns) { let entries = this.createEntries(options); amountIns.props.entries = entries; listIns.props.entries = entries; BDFDB.ReactUtils.forceUpdate(amountIns, listIns, genIns); } } } createEntries (options = {}) { let favorites = BDFDB.DataUtils.load(this, "favorites"); let themes = Object.keys(loadedThemes).map(url => { let theme = loadedThemes[url]; let instTheme = BDFDB.BDUtils.getTheme(theme.name); if (instTheme && instTheme.author.toUpperCase() == theme.author.toUpperCase()) theme.state = instTheme.version != theme.version ? themeStates.OUTDATED : themeStates.UPDATED; else theme.state = themeStates.DOWNLOADABLE; return { url: theme.url, requesturl: theme.requesturl, search: (theme.name + " " + theme.version + " " + theme.author + " " + theme.description).toUpperCase(), name: theme.name, version: theme.version, author: theme.author, description: theme.description || "No Description found.", fav: favorites[url] ? favStates.FAVORIZED : favStates.NOT_FAVORIZED, new: !cachedThemes.includes(url) ? newStates.NEW : newStates.NOT_NEW, state: theme.state, css: theme.css, fullcss: theme.fullcss }; }); if (!options.updated) themes = themes.filter(theme => theme.state != themeStates.UPDATED); if (!options.outdated) themes = themes.filter(theme => theme.state != themeStates.OUTDATED); if (!options.downloadable) themes = themes.filter(theme => theme.state != themeStates.DOWNLOADABLE); if (options.searchString) themes = themes.filter(theme => theme.search.indexOf(options.searchString) > -1).map(theme => Object.assign({}, theme, { name: BDFDB.ReactUtils.elementToReact(BDFDB.DOMUtils.create(BDFDB.StringUtils.highlight(theme.name, options.searchString))) || theme.name, version: BDFDB.ReactUtils.elementToReact(BDFDB.DOMUtils.create(BDFDB.StringUtils.highlight(theme.version, options.searchString))) || theme.version, author: BDFDB.ReactUtils.elementToReact(BDFDB.DOMUtils.create(BDFDB.StringUtils.highlight(theme.author, options.searchString))) || theme.author, description: BDFDB.ReactUtils.elementToReact(BDFDB.DOMUtils.create(BDFDB.StringUtils.highlight(theme.description, options.searchString))) || theme.description })); BDFDB.ArrayUtils.keySort(themes, (options.sortKey == "NEW" && !themes.some(theme => theme.new == newStates.NEW) ? Object.keys(sortKeys)[0] : options.sortKey).toLowerCase()); if (options.orderKey == "DESC") themes.reverse(); return themes.map(theme => { let buttonConfig = buttonData[(Object.entries(themeStates).find(n => n[1] == theme.state) || [])[0]] return buttonConfig && { data: theme, controls: [ theme.new == newStates.NEW && BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Badges.TextBadge, { style: { borderRadius: 3, textTransform: "uppercase", background: BDFDB.DiscordConstants.Colors.STATUS_YELLOW }, text: BDFDB.LanguageUtils.LanguageStrings.NEW }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.FavButton, { className: BDFDB.disCNS._repoentryiconwrapper + BDFDB.disCN.cursorpointer, isFavorite: theme.fav == favStates.FAVORIZED, onClick: value => { theme.fav = value ? favStates.FAVORIZED : favStates.NOT_FAVORIZED; if (value) BDFDB.DataUtils.save(true, this, "favorites", theme.url); else BDFDB.DataUtils.remove(this, "favorites", theme.url); } }), BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN._repocontrolsbutton, children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { text: "Go to Source", children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { name: BDFDB.LibraryComponents.SvgIcon.Names.GITHUB, className: BDFDB.disCN._repoicon, onClick: _ => { let giturl = null; if (theme.requesturl.indexOf("https://raw.githubusercontent.com") == 0) { let temp = theme.requesturl.replace("//raw.githubusercontent", "//github").split("/"); temp.splice(5, 0, "blob"); giturl = temp.join("/"); } else if (theme.requesturl.indexOf("https://gist.githubusercontent.com/") == 0) { giturl = theme.requesturl.replace("//gist.githubusercontent", "//gist.github").split("/raw/")[0]; } if (giturl) BDFDB.DiscordUtils.openLink(giturl, BDFDB.DataUtils.get(this, "settings", "useChromium")); } }) }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Switch, { value: options.currentTheme == theme.url, onChange: (value, instance) => { if (value) options.currentTheme = theme.url; else delete options.currentTheme; delete options.currentGenerator; delete options.generatorValues; this.updateList(instance, options); options.frame.contentWindow.postMessage({ origin: "ThemeRepo", reason: "NewTheme", checked: value, css: theme.css }, "*"); } }) ], buttons: [ theme.state != themeStates.DOWNLOADABLE && BDFDB.ReactUtils.createElement("div", { className: BDFDB.disCN._repocontrolsbutton, children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.TooltipContainer, { text: "Delete Themefile", children: BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SvgIcon, { name: BDFDB.LibraryComponents.SvgIcon.Names.NOVA_TRASH, className: BDFDB.disCN._repoicon, onClick: (e, instance) => { this.deleteThemeFile(theme); BDFDB.TimeUtils.timeout(_ => { this.updateList(instance, options); if (!BDFDB.BDUtils.isAutoLoadEnabled()) this.removeTheme(theme); }, 3000); } }) }) }), BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.Button, { size: BDFDB.LibraryComponents.Button.Sizes.MIN, color: BDFDB.LibraryComponents.Button.Colors[buttonConfig.colorClass], style: {backgroundColor: BDFDB.DiscordConstants.Colors[buttonConfig.backgroundColor]}, children: buttonConfig.text, onClick: (e, instance) => { this.downloadTheme(theme); BDFDB.TimeUtils.timeout(_ => { this.updateList(instance, options); if (options.rnmStart) this.applyTheme(theme); }, 3000); } }) ] }; }).filter(n => n); } 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"); } createGeneratorInputs (theme, options = {}) { if (!options.frame || !BDFDB.ObjectUtils.is(options.generatorValues) || !BDFDB.ObjectUtils.is(theme) || !theme.fullcss) return null; let vars = theme.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.slice(2).split(/;--|\*\/--/); let inputRefs = [], updateTimeout; 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(); options.generatorValues[varName] = {value:oldValue, oldValue}; inputRefs.push(BDFDB.ReactUtils.createElement(BDFDB.LibraryComponents.SettingsItem, { className: BDFDB.disCN.marginbottom20, dividerbottom: vars[vars.length-1] != varStr, type: "TextInput", childProps: { type: childType, mode: childMode, filter: childType == "file" && "image" }, label: varName[0].toUpperCase() + varName.slice(1), note: varDescription && varDescription.indexOf("*") == 0 ? varDescription.slice(1) : varDescription, basis: "70%", value: oldValue, placeholder: oldValue, onChange: value => { BDFDB.TimeUtils.clear(updateTimeout); updateTimeout = BDFDB.TimeUtils.timeout(_ => { options.generatorValues[varName] = {value, oldValue}; options.frame.contentWindow.postMessage({ origin: "ThemeRepo", reason: "NewTheme", checked: true, css: this.generateTheme(theme, options) }, "*"); }, 1000); } })); } } return inputRefs; } generateTheme (theme, options = {}) { if (!BDFDB.ObjectUtils.is(theme) || !BDFDB.ObjectUtils.is(options) || !BDFDB.ObjectUtils.is(options.generatorValues)) return ""; let css = theme.fullcss; for (let inputId in options.generatorValues) if (options.generatorValues[inputId].value && options.generatorValues[inputId].value.trim() && options.generatorValues[inputId].value != options.generatorValues[inputId].oldValue) css = css.replace(new RegExp(`--${BDFDB.StringUtils.regEscape(inputId)}(\\s*):(\\s*)${BDFDB.StringUtils.regEscape(options.generatorValues[inputId].oldValue)}`,"g"),`--${inputId}$1:$2${options.generatorValues[inputId].value}`); return css; } loadThemes () { BDFDB.DOMUtils.remove(".themerepo-loadingicon"); let settings = BDFDB.DataUtils.load(this, "settings"); let getThemeInfo, outdated = 0, newentries = 0, i = 0, NFLDreplace = null; let tags = ["name","description","author","version"]; let newentriesdata = BDFDB.DataUtils.load(this, "newentriesdata"), customList = this.getCustomList(); cachedThemes = (newentriesdata.urlbase64 ? atob(newentriesdata.urlbase64).split("\n") : []).concat(customList); BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/res/ThemeList.txt", (error, response, body) => { if (!error && body) { body = body.replace(/[\r\t]/g, ""); BDFDB.DataUtils.save(btoa(body), this, "newentriesdata", "urlbase64"); loadedThemes = {}; grabbedThemes = body.split("\n").filter(n => n); BDFDB.LibraryRequires.request("https://github.com/NFLD99/Better-Discord", (error2, response2, body2) => { if (!error2 && body2) { NFLDreplace = /\/NFLD99\/Better-Discord\/tree\/master\/Themes_[^"]+">([^<]+)/i.exec(body2); NFLDreplace = NFLDreplace && NFLDreplace.length > 1 ? NFLDreplace[1] : null; } foundThemes = grabbedThemes.concat(customList); 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}; let loadingicon = BDFDB.DOMUtils.create(themeRepoIcon); BDFDB.DOMUtils.addClass(loadingicon, "themerepo-loadingicon"); loadingicon.addEventListener("mouseenter", _ => { BDFDB.TooltipUtils.create(loadingicon, this.getLoadingTooltipText(), { type: "left", delay: 500, style: "max-width: unset;", selector: "themerepo-loading-tooltip" }); }); BDFDB.PluginUtils.addLoadingIcon(loadingicon); getThemeInfo(_ => { if (!this.started) { BDFDB.TimeUtils.clear(loading.timeout); return; } BDFDB.TimeUtils.clear(loading.timeout); BDFDB.DOMUtils.remove(loadingicon, ".themerepo-loadingicon"); loading = {is:false, timeout:null, amount:loading.amount}; BDFDB.LogUtils.log("Finished fetching Themes.", this.name); if (document.querySelector(".bd-themerepobutton")) BDFDB.NotificationUtils.toast(`Finished fetching Themes.`, {type:"success"}); if ((settings.notifyOutdated || settings.notifyOutdated == undefined) && outdated > 0) { let oldbarbutton = document.querySelector(".themerepo-outdate-notice " + BDFDB.dotCN.noticedismiss); if (oldbarbutton) oldbarbutton.click(); let bar = BDFDB.NotificationUtils.notice(`${outdated} of your Themes ${outdated == 1 ? "is" : "are"} outdated. Check:`, { type: "danger", btn: "ThemeRepo", selector: "themerepo-notice themerepo-outdate-notice", customicon: themeRepoIcon.replace(/#7289da/gi, "#FFF").replace(/#7f8186/gi, "#B9BBBE") }); bar.querySelector(BDFDB.dotCN.noticebutton).addEventListener("click", _ => { this.openThemeRepoModal({showOnlyOutdated:true}); bar.querySelector(BDFDB.dotCN.noticedismiss).click(); }); } if ((settings.notifyNewentries || settings.notifyNewentries == undefined) && newentries > 0) { let oldbarbutton = document.querySelector(".themerepo-newentries-notice " + BDFDB.dotCN.noticedismiss); if (oldbarbutton) oldbarbutton.click(); let single = newentries == 1; let bar = BDFDB.NotificationUtils.notice(`There ${single ? "is" : "are"} ${newentries} new Theme${single ? "" : "s"} in the Repo. Check:`, { type: "success", btn: "ThemeRepo", selector: "themerepo-notice themerepo-newentries-notice", customicon: themeRepoIcon.replace(/#7289da/gi, "#FFF").replace(/#7f8186/gi, "#B9BBBE") }); bar.querySelector(BDFDB.dotCN.noticebutton).addEventListener("click", _ => { this.openThemeRepoModal({forcedSort:"new",forcedOrder:"asc"}); bar.querySelector(BDFDB.dotCN.noticedismiss).click(); }); } if (BDFDB.UserUtils.me.id == "278543574059057154") { let wrongUrls = []; for (let url of foundThemes) if (url && !loadedThemes[url] && !wrongUrls.includes(url)) wrongUrls.push(url); if (wrongUrls.length) { let bar = BDFDB.NotificationUtils.notice(`ThemeRepo: ${wrongUrls.length} Theme${wrongUrls.length > 1 ? "s" : ""} could not be loaded.`, { type: "danger", btn: "List", selector: "themerepo-notice themerepo-fail-notice", customicon: themeRepoIcon.replace(/#7289da/gi, "#FFF").replace(/#7f8186/gi, "#B9BBBE") }); bar.querySelector(BDFDB.dotCN.noticebutton).addEventListener("click", e => { let toast = BDFDB.NotificationUtils.toast(wrongUrls.join("\n"), {type: "error"}); toast.style.setProperty("overflow", "hidden"); for (let url of wrongUrls) console.log(url); }); } } BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/res/GeneratorList.txt", (error3, response3, body3) => { if (!error3 && body3) for (let url of body3.replace(/[\r\t]/g, "").split("\n").filter(n => n)) if (loadedThemes[url]) generatorThemes.push(url); }); }); }); } }); getThemeInfo = (callback) => { if (i >= foundThemes.length || !this.started || !loading.is) { callback(); return; } let url = foundThemes[i]; let requesturl = NFLDreplace && url.includes("NFLD99/Better-Discord/master/Themes") ? url.replace("master/Themes", "master/" + NFLDreplace) : url; BDFDB.LibraryRequires.request(requesturl, (error, response, body) => { if (!response) { if (url && BDFDB.ArrayUtils.getAllIndexes(foundThemes, url).length < 2) foundThemes.push(url); } else if (body && body.indexOf("404: Not Found") != 0 && response.statusCode == 200) { let theme = {}, text = body; if ((text.split("*//").length > 1 || text.indexOf("/**") == 0) && text.split("\n").length > 1) { let hasMETAline = text.replace(/\s/g, "").indexOf("//META{"); if (hasMETAline < 20 && hasMETAline > -1) { let searchtext = text.replace(/\s*:\s*/g, ":").replace(/\s*}\s*/g, "}"); for (let tag of tags) { let result = searchtext.split('"' + tag + '":"'); result = result.length > 1 ? result[1].split('",')[0].split('"}')[0] : null; result = result && tag != "version" ? result.charAt(0).toUpperCase() + result.slice(1) : result; theme[tag] = result ? result.trim() : result; } } else { let searchtext = text.replace(/[\r\t| ]*\*\s*/g, "*"); for (let tag of tags) { let result = searchtext.split('@' + tag + ' '); result = result.length > 1 ? result[1].split('\n')[0] : null; result = result && tag != "version" ? result.charAt(0).toUpperCase() + result.slice(1) : result; theme[tag] = result ? result.trim() : result; } } let valid = true; for (let tag of tags) if (theme[tag] === null) valid = false; if (valid) { theme.fullcss = text; theme.css = hasMETAline < 20 && hasMETAline > -1 ? text.split("\n").slice(1).join("\n").replace(/[\r|\n|\t]/g, "") : text.replace(/[\r|\n|\t]/g, ""); theme.url = url; theme.requesturl = requesturl; loadedThemes[url] = theme; let instTheme = BDFDB.BDUtils.getTheme(theme.name); if (instTheme && instTheme.author.toUpperCase() == theme.author.toUpperCase() && instTheme.version != theme.version) outdated++; if (!cachedThemes.includes(url)) newentries++; } } } i++; let loadingtooltip = document.querySelector(".themerepo-loading-tooltip"); if (loadingtooltip) { BDFDB.DOMUtils.setText(loadingtooltip, this.getLoadingTooltipText()); BDFDB.TooltipUtils.update(loadingtooltip); } getThemeInfo(callback); }); } } getLoadingTooltipText () { return `Loading ThemeRepo - [${Object.keys(loadedThemes).length}/${Object.keys(grabbedThemes).length}]`; } checkForNewThemes () { BDFDB.LibraryRequires.request("https://mwittrien.github.io/BetterDiscordAddons/Plugins/ThemeRepo/res/ThemeList.txt", (error, response, result) => { if (response && !BDFDB.equals(result.replace(/\t|\r/g, "").split("\n").filter(n => n), grabbedThemes)) { loading = {is:false, timeout:null, amount:0}; this.loadThemes(); } }); } downloadTheme (data) { BDFDB.LibraryRequires.request(data.requesturl, (error, response, body) => { if (error) BDFDB.NotificationUtils.toast(`Unable to download Theme "${data.name}".`, {type:"danger"}); else this.createThemeFile(data.requesturl.split("/").pop(), body); }); } createThemeFile (filename, content) { BDFDB.LibraryRequires.fs.writeFile(BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getThemesFolder(), filename), content, (error) => { if (error) BDFDB.NotificationUtils.toast(`Unable to save Theme "${filename}".`, {type:"danger"}); else BDFDB.NotificationUtils.toast(`Successfully saved Theme "${filename}".`, {type:"success"}); }); } applyTheme (data) { if (BDFDB.BDUtils.isThemeEnabled(data.name) == false) { let id = data.name.replace(/^[^a-z]+|[^\w-]+/gi, "-"); BDFDB.DOMUtils.remove(`style#${id}`); document.head.appendChild(BDFDB.DOMUtils.create(``)); BDFDB.BDUtils.enableTheme(data.name, false); BDFDB.LogUtils.log(`Applied Theme ${data.name}.`, this.name); } } deleteThemeFile (data) { let filename = data.requesturl.split("/").pop(); BDFDB.LibraryRequires.fs.unlink(BDFDB.LibraryRequires.path.join(BDFDB.BDUtils.getThemesFolder(), filename), (error) => { if (error) BDFDB.NotificationUtils.toast(`Unable to delete Theme "${filename}".`, {type:"danger"}); else BDFDB.NotificationUtils.toast(`Successfully deleted Theme "${filename}".`); }); } removeTheme (data) { if (BDFDB.BDUtils.isThemeEnabled(data.name) == true) { let id = data.name.replace(/^[^a-z]+|[^\w-]+/gi, "-"); BDFDB.DOMUtils.remove(`style#${id}`); BDFDB.BDUtils.disableTheme(data.name, false); BDFDB.LogUtils.log(`Removed Theme ${data.name}.`, this.name); } } } })();