├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── README.md
├── TODO.md
├── components
├── Plugin.jsx
├── PluginList.jsx
├── PluginSettings.jsx
└── Settings.jsx
├── index.js
├── manifest.json
├── modules
├── AddonAPI.js
├── BDApi.js
├── BDV2.js
├── ContentManager.js
├── Patcher.js
├── PluginManager.js
└── index.js
└── style.css
/.eslintignore:
--------------------------------------------------------------------------------
1 | /config
2 | /plugins
3 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['eslint:recommended', 'intrnl'],
3 | env: { node: true },
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /config
2 | /plugins
3 | plugin.js
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bdCompat
2 |
3 | Compatibility layer for running BetterDiscord plugins in Powercord
4 |
5 | [](https://i.imgur.com/xaAOdSE.png)
6 |
7 | ## Installation
8 |
9 | Clone this repository to your Powercord install's plugins folder
10 |
11 | ```
12 | git clone https://github.com/Juby210/bdCompat
13 | ```
14 |
15 | ## Installing BD plugins
16 |
17 |
18 |
19 | - Put the plugin in the `plugins` folder, if it doesn't exist then create one.
20 | - Reload your Discord.
21 | - Go to User Settings and head to the `BetterDiscord Plugins` section
22 | - Enable the said plugin
23 |
24 | #### edCompat? ED plugins support?
25 | If you want EnhancedDiscord plugins support, you can use [EDPluginsLoader](https://github.com/Juby210/EDPluginsLoader) for BD.
26 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # To do
2 |
3 | - [x] Revisit the idea of replacing the require function to allow directly requiring the plugin files without creating temporary files
4 | - [ ] Hot reloading of plugins
5 | - Should already be possible, just needs a function that unloads and loads, and a watcher.
6 |
--------------------------------------------------------------------------------
/components/Plugin.jsx:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { shell: { openExternal } } = require('electron')
4 |
5 | const { React, i18n: { Messages } } = require('powercord/webpack')
6 | const { Tooltip, Switch, Button, Card, Divider } = require('powercord/components')
7 | const { open: openModal } = require('powercord/modal')
8 |
9 | const SettingsModal = require('./PluginSettings.jsx')
10 |
11 | let Details = () =>
Failed to load powercord module manager's details component!
12 | try {
13 | Details = require('../../../src/Powercord/coremods/moduleManager/components/parts/Details')
14 | } catch (e) {
15 | console.error('Failed to load powercord module manager\'s details component! Settings won\'t render correctly.', e)
16 | }
17 |
18 | module.exports = class Plugin extends React.Component {
19 | render () {
20 | this.props.enabled = this.props.meta.__started
21 |
22 | // We're reusing Powercord's plugin manager classes
23 | return (
24 |
25 |
26 |
{this.props.plugin.getName()}
27 |
28 |
29 | this.togglePlugin()}/>
30 |
31 |
32 |
33 |
34 |
35 |
41 |
42 |
43 |
44 |
45 |
46 | {this.props.meta.source &&
47 |
55 | }
56 |
57 | {this.props.meta.website &&
58 |
66 | }
67 |
68 |
69 |
70 | {typeof this.props.plugin.getSettingsPanel === 'function' &&
71 |
79 | }
80 | {typeof this.props.plugin.getSettingsPanel === 'function' &&
81 |
82 | }
83 |
84 |
96 |
97 |
98 | )
99 | }
100 |
101 | togglePlugin () {
102 | if (this.props.enabled) {
103 | this.props.onDisable()
104 | } else {
105 | this.props.onEnable()
106 | }
107 |
108 | this.forceUpdate()
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/components/PluginList.jsx:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { shell: { openPath } } = require('electron')
4 |
5 | const { React } = require('powercord/webpack')
6 | const { Button } = require('powercord/components')
7 | const { TextInput } = require('powercord/components/settings')
8 |
9 | const Plugin = require('./Plugin.jsx')
10 |
11 | module.exports = class PluginList extends React.Component {
12 | constructor (props) {
13 | super(props)
14 |
15 | this.state = {
16 | search: '',
17 | }
18 | }
19 | render () {
20 | const plugins = this.__getPlugins()
21 |
22 | return (
23 |
24 |
25 |
33 |
34 |
35 | this.setState({ search: val })}
38 | placeholder='What are you looking for?'
39 | >
40 | Search plugins
41 |
42 |
43 |
44 |
45 | {plugins.map((plugin) =>
46 |
this.props.pluginManager.enablePlugin(plugin.plugin.getName())}
51 | onDisable={() => this.props.pluginManager.disablePlugin(plugin.plugin.getName())}
52 | onDelete={() => this.__deletePlugin(plugin.plugin.getName())}
53 | />
54 | )}
55 |
56 |
57 | )
58 | }
59 |
60 | __getPlugins () {
61 | let plugins = Object.keys(window.bdplugins)
62 | .map((plugin) => window.bdplugins[plugin])
63 |
64 | if (this.state.search !== '') {
65 | const search = this.state.search.toLowerCase()
66 |
67 | plugins = plugins.filter(({ plugin }) =>
68 | plugin.getName().toLowerCase().includes(search) ||
69 | plugin.getAuthor().toLowerCase().includes(search) ||
70 | plugin.getDescription().toLowerCase().includes(search)
71 | )
72 | }
73 |
74 | return plugins.sort((a, b) => {
75 | const nameA = a.plugin.getName().toLowerCase()
76 | const nameB = b.plugin.getName().toLowerCase()
77 |
78 | if (nameA < nameB) return -1
79 | if (nameA > nameB) return 1
80 |
81 | return 0
82 | })
83 | }
84 |
85 | __deletePlugin(pluginName) {
86 | this.props.pluginManager.delete(pluginName)
87 |
88 | this.forceUpdate()
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/components/PluginSettings.jsx:
--------------------------------------------------------------------------------
1 | const { React, getModuleByDisplayName } = require('powercord/webpack')
2 | const { Modal } = require('powercord/components/modal')
3 | const { close: closeModal } = require('powercord/modal')
4 | const { resolve } = require('path')
5 |
6 | const FormTitle = getModuleByDisplayName('FormTitle', false)
7 |
8 | let ErrorBoundary = props => props.children
9 | try {
10 | ErrorBoundary = require('../../../src/Powercord/coremods/settings/components/ErrorBoundary')
11 | } catch (e) {
12 | console.error('Failed to load powercord\'s ErrorBoundary component!', e)
13 | }
14 |
15 | module.exports = class PluginSettings extends React.Component {
16 | renderPluginSettings() {
17 | let panel
18 | try {
19 | panel = this.props.plugin.getSettingsPanel()
20 | } catch (e) {
21 | console.error(e)
22 |
23 | const error = (e.stack || e.toString()).split('\n')
24 | .filter(l => !l.includes('discordapp.com/assets/') && !l.includes('discord.com/assets/'))
25 | .join('\n')
26 | .split(resolve(__dirname, '..', '..')).join('')
27 |
28 | return
29 |
An error occurred while rendering settings panel.
30 |
{error}
31 |
32 | }
33 | if (panel instanceof Node || typeof panel === 'string') {
34 | return el ? panel instanceof Node ? el.append(panel) : el.innerHTML = panel : void 0}>
35 | }
36 | return typeof panel === 'function' ? React.createElement(panel) : panel
37 | }
38 |
39 | render () {
40 | const { plugin } = this.props
41 |
42 | return (
43 |
44 |
45 | {plugin.getName()} Settings
46 |
47 |
48 |
49 |
50 | {this.renderPluginSettings()}
51 |
52 |
53 |
54 | )
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/components/Settings.jsx:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { React } = require('powercord/webpack')
4 | const { SwitchItem } = require('powercord/components/settings')
5 |
6 | const PluginList = require('./PluginList.jsx')
7 |
8 | module.exports = class Settings extends React.Component {
9 | constructor (props) {
10 | super(props)
11 | }
12 |
13 | render () {
14 | return (
15 |
16 |
this.props.toggleSetting('disableWhenStopFailed')}>
18 | Disable plugin when failed to stop
19 |
20 |
21 |
22 | )
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { Plugin } = require('powercord/entities')
4 | const process = require('process')
5 |
6 | const { AddonAPI, BDApi, BDV2, ContentManager, PluginManager } = require('./modules')
7 | const Settings = require('./components/Settings')
8 |
9 | module.exports = class BDCompat extends Plugin {
10 | startPlugin () {
11 | this.loadStylesheet('style.css')
12 | this.defineGlobals()
13 |
14 | powercord.api.settings.registerSettings('bdCompat', {
15 | category: 'bdCompat',
16 | label: 'BetterDiscord Plugins',
17 | render: Settings
18 | });
19 | }
20 |
21 | pluginWillUnload () {
22 | powercord.api.settings.unregisterSettings('bdCompat')
23 | if (window.pluginModule) window.pluginModule.destroy()
24 | if (window.ContentManager) window.ContentManager.destroy()
25 | this.destroyGlobals()
26 | }
27 |
28 | defineGlobals () {
29 | let webReq
30 | window.webpackChunkdiscord_app.push([
31 | ['bdCompat'],
32 | {},
33 | r => webReq = r
34 | ])
35 | window.webpackJsonp = []
36 | window.webpackJsonp.push = ([, mod, [[id]]]) => {
37 | return mod[id]({}, {}, webReq)
38 | }
39 |
40 | window.bdConfig = { dataPath: __dirname }
41 | window.settingsCookie = {}
42 |
43 | window.bdplugins = {}
44 | window.pluginCookie = {}
45 | window.bdpluginErrors = []
46 |
47 | window.bdthemes = {}
48 | window.themeCookie = {}
49 | window.bdthemeErrors = []
50 |
51 | // window.BdApi = BDApi
52 |
53 | // Orignally BdApi is an object, not a class
54 | window.BdApi = {}
55 | Object.getOwnPropertyNames(BDApi).filter(m => typeof BDApi[m] == 'function' || typeof BDApi[m] == 'object').forEach(m => {
56 | window.BdApi[m] = BDApi[m]
57 | })
58 | window.Utils = { monkeyPatch: BDApi.monkeyPatch, suppressErrors: BDApi.suppressErrors, escapeID: BDApi.escapeID }
59 |
60 | window.BDV2 = BDV2
61 | window.ContentManager = new ContentManager
62 | window.pluginModule = new PluginManager(window.ContentManager.pluginsFolder, this.settings)
63 |
64 | // DevilBro's plugins checks whether or not it's running on ED
65 | // This isn't BetterDiscord, so we'd be better off doing this.
66 | // eslint-disable-next-line no-process-env
67 | process.env.injDir = __dirname
68 |
69 | window.BdApi.Plugins = new AddonAPI(window.bdplugins, window.pluginModule)
70 | window.BdApi.Themes = new AddonAPI({}, {})
71 |
72 | this.log('Defined BetterDiscord globals')
73 | }
74 |
75 | destroyGlobals () {
76 | const globals = ['bdConfig', 'settingsCookie', 'bdplugins', 'pluginCookie', 'bdpluginErrors', 'bdthemes',
77 | 'themeCookie', 'bdthemeErrors', 'BdApi', 'Utils', 'BDV2', 'ContentManager', 'pluginModule', 'webpackJsonp']
78 |
79 | globals.forEach(g => {
80 | delete window[g]
81 | })
82 |
83 | // eslint-disable-next-line no-process-env
84 | delete process.env.injDir
85 |
86 | this.log('Destroyed BetterDiscord globals')
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "BetterDiscord plugin compatibility (bdCompat)",
3 | "version": "0.3.12",
4 | "description": "Adds support for running BetterDiscord plugins",
5 | "author": "intrnl & Juby210",
6 | "license": "MIT"
7 | }
8 |
--------------------------------------------------------------------------------
/modules/AddonAPI.js:
--------------------------------------------------------------------------------
1 | module.exports = class AddonAPI {
2 | constructor(list, manager) {
3 | this.list = list
4 | this.manager = manager
5 | this.folder = manager.folder
6 |
7 | this.enable = this.manager.enable?.bind(this.manager) || (() => {})
8 | this.disable = this.manager.disable?.bind(this.manager) || (() => {})
9 | this.reload = this.manager.reload?.bind(this.manager) || (() => {})
10 | }
11 |
12 | isEnabled(name) {
13 | const plugin = this.list[name]
14 | return plugin ? plugin.__started : false
15 | }
16 |
17 | toggle(name) {
18 | if (this.isEnabled(name)) return this.disable(name)
19 | this.enable(name)
20 | }
21 |
22 | get(name) {
23 | if (this.list[name]) {
24 | if (this.list[name].plugin) return this.list[name].plugin
25 | return this.list[name]
26 | }
27 | return null
28 | }
29 |
30 | getAll = () => Object.keys(this.list).map(k => this.get(k)).filter(a => a)
31 | }
32 |
--------------------------------------------------------------------------------
/modules/BDApi.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const fs = require('fs')
5 | const crypto = require('crypto')
6 |
7 | const { getModule, getAllModules, getModuleByDisplayName, React, ReactDOM } = require('powercord/webpack')
8 | const { getOwnerInstance, getReactInstance } = require('powercord/util')
9 | const { inject, uninject } = require('powercord/injector')
10 |
11 | const PluginData = {}
12 |
13 | const Patcher = require('./Patcher')
14 |
15 |
16 | // __ is not part of BdApi entirely
17 | // _ is part of BD but not exactly in BdApi, but kept here anyway for easier maintain
18 |
19 | class BdApi {
20 | // React
21 | static get React () {
22 | return React
23 | }
24 | static get ReactDOM () {
25 | return ReactDOM
26 | }
27 |
28 | // Patcher
29 | static get Patcher() {
30 | return Patcher
31 | }
32 |
33 |
34 | // General
35 | static getCore () {
36 | return null
37 | }
38 | static escapeID (id) {
39 | return id.replace(/^[^a-z]+|[^\w-]+/giu, '')
40 | }
41 |
42 | static suppressErrors (method, message = '') {
43 | return (...params) => {
44 | try {
45 | return method(...params)
46 | } catch (err) {
47 | BdApi.__error(err, `Error occured in ${message}`)
48 | }
49 | }
50 | }
51 |
52 | static testJSON (data) {
53 | try {
54 | JSON.parse(data)
55 |
56 | return true
57 | } catch (err) {
58 | return false
59 | }
60 | }
61 |
62 |
63 | // Style tag
64 | static get __styleParent () {
65 | return BdApi.__elemParent('style')
66 | }
67 |
68 | static injectCSS (id, css) {
69 | const style = document.createElement('style')
70 |
71 | style.id = `bd-style-${BdApi.escapeID(id)}`
72 | style.innerHTML = css
73 |
74 | BdApi.__styleParent.append(style)
75 | }
76 |
77 | static clearCSS (id) {
78 | const elem = document.getElementById(`bd-style-${BdApi.escapeID(id)}`)
79 | if (elem) elem.remove()
80 | }
81 |
82 |
83 | // Script tag
84 | static get __scriptParent () {
85 | return BdApi.__elemParent('script')
86 | }
87 |
88 | static linkJS (id, url) {
89 | return new Promise((resolve) => {
90 | const script = document.createElement('script')
91 |
92 | script.id = `bd-script-${BdApi.escapeID(id)}`
93 | script.src = url
94 | script.type = 'text/javascript'
95 | script.onload = resolve
96 |
97 | BdApi.__scriptParent.append(script)
98 | })
99 | }
100 |
101 | static unlinkJS (id) {
102 | const elem = document.getElementById(`bd-script-${BdApi.escapeID(id)}`)
103 | if (elem) elem.remove()
104 | }
105 |
106 |
107 | // Plugin data
108 | static get __pluginData () {
109 | return PluginData
110 | }
111 |
112 | static __getPluginConfigPath (pluginName) {
113 | return path.join(__dirname, '..', 'config', pluginName + '.config.json')
114 | }
115 |
116 | static __getPluginConfig (pluginName) {
117 | const configPath = BdApi.__getPluginConfigPath(pluginName)
118 |
119 | if (typeof BdApi.__pluginData[pluginName] === 'undefined')
120 | if (!fs.existsSync(configPath)) {
121 | BdApi.__pluginData[pluginName] = {}
122 | } else {
123 | try {
124 | BdApi.__pluginData[pluginName] = JSON.parse(fs.readFileSync(configPath))
125 | } catch (e) {
126 | BdApi.__pluginData[pluginName] = {}
127 | BdApi.__warn(`${pluginName} has corrupted or empty config file, loaded as {}`)
128 | }
129 | }
130 |
131 |
132 | return BdApi.__pluginData[pluginName]
133 | }
134 |
135 | static __savePluginConfig (pluginName) {
136 | const configPath = BdApi.__getPluginConfigPath(pluginName)
137 | const configFolder = path.join(__dirname, '..', 'config/')
138 |
139 | if (!fs.existsSync(configFolder)) fs.mkdirSync(configFolder)
140 | fs.writeFileSync(configPath, JSON.stringify(BdApi.__pluginData[pluginName], null, 2))
141 | }
142 |
143 |
144 | static loadData (pluginName, key) {
145 | const config = BdApi.__getPluginConfig(pluginName)
146 |
147 | return config[key]
148 | }
149 |
150 | static get getData () {
151 | return BdApi.loadData
152 | }
153 |
154 |
155 | static saveData (pluginName, key, value) {
156 | if (typeof value === 'undefined') return
157 |
158 | const config = BdApi.__getPluginConfig(pluginName)
159 |
160 | config[key] = value
161 |
162 | BdApi.__savePluginConfig(pluginName)
163 | }
164 |
165 | static get setData () {
166 | return BdApi.saveData
167 | }
168 |
169 | static deleteData (pluginName, key) {
170 | const config = BdApi.__getPluginConfig(pluginName)
171 |
172 | if (typeof config[key] === 'undefined') return
173 | delete config[key]
174 |
175 | BdApi.__savePluginConfig(pluginName)
176 | }
177 |
178 |
179 | // Plugin communication
180 | static getPlugin (name) {
181 | if (window.bdplugins[name]) return window.bdplugins[name].plugin
182 | }
183 |
184 |
185 | // Alerts and toasts
186 | static async alert(title, children) {
187 | return BdApi.showConfirmationModal(title, children, { cancelText: null })
188 | }
189 |
190 | static async showConfirmationModal(title, children, options = {}) {
191 | const { onConfirm = () => {}, onCancel = () => {}, confirmText = 'Okay', cancelText = 'Cancel', danger = false, key } = options
192 |
193 | const { openModal } = await getModule(['openModal', 'updateModal'])
194 | const Markdown = await getModule(m => m.displayName === 'Markdown' && m.rules)
195 | const ConfirmModal = await getModuleByDisplayName('ConfirmModal')
196 |
197 | if (!Array.isArray(children)) children = [ children ]
198 | children = children.map(c => typeof c == 'string' ? React.createElement(Markdown, null, c) : c)
199 | return openModal(props => React.createElement(ConfirmModal, {
200 | header: title, red: danger, confirmText, cancelText, onConfirm, onCancel, ...props
201 | }, children), { modalKey: key })
202 | }
203 |
204 | static showToast (content, options = {}) {
205 | const { type = '', icon = true, timeout = 3000 } = options
206 |
207 | const toastElem = document.createElement('div')
208 | toastElem.classList.add('bd-toast')
209 | toastElem.innerText = content
210 |
211 | if (type) toastElem.classList.add(`toast-${type}`)
212 | if (type && icon) toastElem.classList.add('icon')
213 |
214 | const toastWrapper = BdApi.__createToastWrapper()
215 | toastWrapper.appendChild(toastElem)
216 |
217 | setTimeout(() => {
218 | toastElem.classList.add('closing')
219 |
220 | setTimeout(() => {
221 | toastElem.remove()
222 | if (!document.querySelectorAll('.bd-toasts .bd-toast').length) toastWrapper.remove()
223 | }, 300)
224 | }, timeout)
225 | }
226 |
227 | static __createToastWrapper () {
228 | const toastWrapperElem = document.querySelector('.bd-toasts')
229 |
230 | if (!toastWrapperElem) {
231 | const DiscordElements = {
232 | settings: '.contentColumn-2hrIYH, .customColumn-Rb6toI',
233 | chat: '.chat-3bRxxu form',
234 | friends: '.container-3gCOGc',
235 | serverDiscovery: '.pageWrapper-1PgVDX',
236 | applicationStore: '.applicationStore-1pNvnv',
237 | gameLibrary: '.gameLibrary-TTDw4Y',
238 | activityFeed: '.activityFeed-28jde9',
239 | }
240 |
241 | const boundingElement = document.querySelector(Object.keys(DiscordElements).map((component) => DiscordElements[component]).join(', '))
242 |
243 | const toastWrapper = document.createElement('div')
244 | toastWrapper.classList.add('bd-toasts')
245 | toastWrapper.style.setProperty('width', boundingElement ? boundingElement.offsetWidth + 'px' : '100%')
246 | toastWrapper.style.setProperty('left', boundingElement ? boundingElement.getBoundingClientRect().left + 'px' : '0px')
247 | toastWrapper.style.setProperty(
248 | 'bottom',
249 | (document.querySelector(DiscordElements.chat) ? document.querySelector(DiscordElements.chat).offsetHeight + 20 : 80) + 'px'
250 | )
251 |
252 | document.querySelector('#app-mount > div[class^="app-"]').appendChild(toastWrapper)
253 |
254 | return toastWrapper
255 | }
256 |
257 | return toastWrapperElem
258 | }
259 |
260 |
261 | // Discord's internals manipulation and such
262 | static onRemoved (node, callback) {
263 | const observer = new MutationObserver((mutations) => {
264 | for (const mut in mutations) {
265 | const mutation = mutations[mut]
266 | const nodes = Array.from(mutation.removedNodes)
267 |
268 | const directMatch = nodes.indexOf(node) > -1
269 | const parentMatch = nodes.some((parent) => parent.contains(node))
270 |
271 | if (directMatch || parentMatch) {
272 | observer.disconnect()
273 |
274 | return callback()
275 | }
276 | }
277 | })
278 |
279 | observer.observe(document.body, { subtree: true, childList: true })
280 | }
281 |
282 | static getInternalInstance (node) {
283 | if (!(node instanceof window.jQuery) && !(node instanceof Element)) return undefined // eslint-disable-line no-undefined
284 | if (node instanceof window.jQuery) node = node[0] // eslint-disable-line no-param-reassign
285 |
286 | return getOwnerInstance(node)
287 | }
288 |
289 | static findModule(filter) {
290 | return getModule(filter, false)
291 | }
292 |
293 | static findAllModules(filter) {
294 | return getAllModules(filter)
295 | }
296 |
297 | static findModuleByProps(...props) {
298 | return BdApi.findModule((module) => props.every((prop) => typeof module[prop] !== 'undefined'))
299 | }
300 |
301 | static findModuleByPrototypes(...protos) {
302 | return getModule(module => module.prototype && protos.every(proto => typeof module.prototype[proto] !== 'undefined'), false)
303 | }
304 |
305 | static findModuleByDisplayName(displayName) {
306 | return getModuleByDisplayName(displayName, false)
307 | }
308 |
309 | static monkeyPatch (what, methodName, options = {}) {
310 | const displayName = options.displayName || what.displayName || what[methodName].displayName
311 | || what.name || what.constructor.displayName || what.constructor.name || 'MissingName'
312 |
313 | // if (options.instead) return BdApi.__warn('Powercord API currently does not support replacing the entire method!')
314 |
315 | if (!what[methodName])
316 | if (options.force) {
317 | // eslint-disable-next-line no-empty-function
318 | what[methodName] = function forcedFunction () {}
319 | } else {
320 | return BdApi.__error(null, `${methodName} doesn't exist in ${displayName}!`)
321 | }
322 |
323 |
324 | if (!options.silent)
325 | BdApi.__log(`Patching ${displayName}'s ${methodName} method`)
326 |
327 | const origMethod = what[methodName]
328 |
329 | if (options.instead) {
330 | const cancel = () => {
331 | if (!options.silent) BdApi.__log(`Unpatching instead of ${displayName} ${methodName}`)
332 | what[methodName] = origMethod
333 | }
334 | what[methodName] = function () {
335 | const data = {
336 | thisObject: this,
337 | methodArguments: arguments,
338 | cancelPatch: cancel,
339 | originalMethod: origMethod,
340 | callOriginalMethod: () => data.returnValue = data.originalMethod.apply(data.thisObject, data.methodArguments)
341 | }
342 | const tempRet = BdApi.suppressErrors(options.instead, "`instead` callback of " + what[methodName].displayName)(data)
343 | if (tempRet !== undefined) data.returnValue = tempRet
344 | return data.returnValue
345 | }
346 | if (displayName != 'MissingName') what[methodName].displayName = displayName
347 | return cancel
348 | }
349 |
350 |
351 | const patches = []
352 | if (options.before) patches.push(BdApi.__injectBefore({ what, methodName, options, displayName }, origMethod))
353 | if (options.after) patches.push(BdApi.__injectAfter({ what, methodName, options, displayName }, origMethod))
354 | if (displayName != 'MissingName') what[methodName].displayName = displayName
355 |
356 | const finalCancelPatch = () => patches.forEach((patch) => patch())
357 |
358 | return finalCancelPatch
359 | }
360 |
361 | static __injectBefore (data, origMethod) {
362 | const patchID = `bd-patch-before-${data.displayName.toLowerCase()}-${crypto.randomBytes(4).toString('hex')}`
363 |
364 | const cancelPatch = () => {
365 | if (!data.options.silent) BdApi.__log(`Unpatching before of ${data.displayName} ${data.methodName}`)
366 | uninject(patchID)
367 | }
368 |
369 | inject(patchID, data.what, data.methodName, function beforePatch (args, res) {
370 | const patchData = {
371 | // eslint-disable-next-line no-invalid-this
372 | thisObject: this,
373 | methodArguments: args,
374 | returnValue: res,
375 | cancelPatch: cancelPatch,
376 | originalMethod: origMethod,
377 | callOriginalMethod: () => patchData.returnValue = patchData.originalMethod.apply(patchData.thisObject, patchData.methodArguments)
378 | }
379 |
380 | try {
381 | data.options.before(patchData)
382 | } catch (err) {
383 | BdApi.__error(err, `Error in before callback of ${data.displayName} ${data.methodName}`)
384 | }
385 |
386 | if (data.options.once) cancelPatch()
387 |
388 | return patchData.methodArguments
389 | }, true)
390 |
391 | return cancelPatch
392 | }
393 |
394 | static __injectAfter (data, origMethod) {
395 | const patchID = `bd-patch-after-${data.displayName.toLowerCase()}-${crypto.randomBytes(4).toString('hex')}`
396 |
397 | const cancelPatch = () => {
398 | if (!data.options.silent) BdApi.__log(`Unpatching after of ${data.displayName} ${data.methodName}`)
399 | uninject(patchID)
400 | }
401 |
402 | inject(patchID, data.what, data.methodName, function afterPatch (args, res) {
403 | const patchData = {
404 | // eslint-disable-next-line no-invalid-this
405 | thisObject: this,
406 | methodArguments: args,
407 | returnValue: res,
408 | cancelPatch: cancelPatch,
409 | originalMethod: origMethod,
410 | callOriginalMethod: () => patchData.returnValue = patchData.originalMethod.apply(patchData.thisObject, patchData.methodArguments)
411 | }
412 |
413 | try {
414 | data.options.after(patchData)
415 | } catch (err) {
416 | BdApi.__error(err, `Error in after callback of ${data.displayName} ${data.methodName}`)
417 | }
418 |
419 | if (data.options.once) cancelPatch()
420 |
421 | return patchData.returnValue
422 | }, false)
423 |
424 | return cancelPatch
425 | }
426 |
427 | static getInternalInstance (node) {
428 | if (!(node instanceof window.jQuery) && !(node instanceof Element)) return undefined
429 | if (node instanceof window.jQuery) node = node[0]
430 | return getReactInstance(node)
431 | }
432 |
433 | static get settings() { // mess
434 | return {"Custom css live update":{id:"bda-css-0"},"Custom css auto udpate":{id:"bda-css-1"},"BetterDiscord Blue":{id:"bda-gs-b"},"Public Servers":{id:"bda-gs-1"},"Minimal Mode":{id:"bda-gs-2"},"Voice Mode":{id:"bda-gs-4"},"Hide Channels":{id:"bda-gs-3"},"Dark Mode":{id:"bda-gs-5"},"Voice Disconnect":{id:"bda-dc-0"},"24 Hour Timestamps":{id:"bda-gs-6"},"Colored Text":{id:"bda-gs-7"},"Normalize Classes":{id:"fork-ps-4"},"Content Error Modal":{id:"fork-ps-1"},"Show Toasts":{id:"fork-ps-2"},"Scroll To Settings":{id:"fork-ps-3"},"Automatic Loading":{id:"fork-ps-5"},"Developer Mode":{id:"bda-gs-8"},"Copy Selector":{id:"fork-dm-1"},"React DevTools":{id:"reactDevTools"},"Enable Transparency":{id:"fork-wp-1"},"Window Frame":{id:"fork-wp-2"},"Download Emotes":{id:"fork-es-3"},"Twitch Emotes":{id:"bda-es-7"},"FrankerFaceZ Emotes":{id:"bda-es-1"},"BetterTTV Emotes":{id:"bda-es-2"},"Emote Menu":{id:"bda-es-0"},"Emoji Menu":{id:"bda-es-9"},"Emote Auto Capitalization":{id:"bda-es-4"},"Show Names":{id:"bda-es-6"},"Show emote modifiers":{id:"bda-es-8"},"Animate On Hover":{id:"fork-es-2"}}
435 | }
436 |
437 | static isSettingEnabled(e) {
438 | return !!settingsCookie[e]
439 | }
440 |
441 | static isPluginEnabled (name) {
442 | const plugin = bdplugins[name]
443 | return plugin ? plugin.__started : false
444 | }
445 |
446 | static isThemeEnabled () {
447 | return false
448 | }
449 |
450 | // Miscellaneous, things that aren't part of BD
451 | static __elemParent (id) {
452 | const elem = document.getElementsByTagName(`bd-${id}`)[0]
453 | if (elem) return elem
454 |
455 | const newElem = document.createElement(`bd-${id}`)
456 | document.head.append(newElem)
457 |
458 | return newElem
459 | }
460 |
461 | static __log (...message) {
462 | console.log('%c[BDCompat:BdApi]', 'color: #3a71c1;', ...message)
463 | }
464 |
465 | static __warn (...message) {
466 | console.log('%c[BDCompat:BdApi]', 'color: #e8a400;', ...message)
467 | }
468 |
469 | static __error (error, ...message) {
470 | console.log('%c[BDCompat:BdApi]', 'color: red;', ...message)
471 |
472 | if (error) {
473 | console.groupCollapsed(`%cError: ${error.message}`, 'color: red;')
474 | console.error(error.stack)
475 | console.groupEnd()
476 | }
477 | }
478 | }
479 |
480 | module.exports = BdApi
481 |
--------------------------------------------------------------------------------
/modules/BDV2.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | // Not to be confused with the actual BDv2's API
4 | class V2 {
5 | static get WebpackModules () {
6 | return {
7 | find: window.BdApi.findModule,
8 | findAll: window.BdApi.findAll,
9 | findByUniqueProperties: window.BdApi.findModuleByProps,
10 | findByDisplayName: window.BdApi.findModuleByDisplayName,
11 | }
12 | }
13 |
14 | static get react () {
15 | return window.BdApi.React
16 | }
17 | static get reactDom () {
18 | return window.BdApi.ReactDOM
19 | }
20 |
21 | static get getInternalInstance () {
22 | return window.BdApi.getInternalInstance
23 | }
24 | }
25 |
26 | module.exports = V2
27 |
--------------------------------------------------------------------------------
/modules/ContentManager.js:
--------------------------------------------------------------------------------
1 | const { existsSync, mkdirSync, readFileSync } = require('fs')
2 | const { join, dirname, resolve, basename } = require('path')
3 | const { Module } = require('module')
4 | const originalRequire = Module._extensions['.js']
5 | let _this
6 |
7 | // Purposefully incomplete ContentManager
8 | module.exports = class ContentManager {
9 | constructor() {
10 | _this = this
11 | Module._extensions['.js'] = this.getContentRequire()
12 | }
13 |
14 | destroy() {
15 | Module._extensions['.js'] = originalRequire
16 | }
17 |
18 | get pluginsFolder() {
19 | const pluginDir = join(__dirname, '..', 'plugins')
20 |
21 | if (!existsSync(pluginDir)) mkdirSync(pluginDir)
22 |
23 | return pluginDir
24 | }
25 |
26 | get themesFolder() {
27 | // We'll just pretend it exists.
28 | return join(ContentManager.pluginsFolder, '..', 'themes')
29 | }
30 |
31 | extractMeta(content) {
32 | const firstLine = content.split("\n")[0]
33 | const hasOldMeta = firstLine.includes("//META")
34 | if (hasOldMeta) return this.parseOldMeta(content)
35 | const hasNewMeta = firstLine.includes("/**")
36 | if (hasNewMeta) return this.parseNewMeta(content)
37 | throw new Error('META was not found.')
38 | }
39 |
40 | parseOldMeta(content) {
41 | const metaLine = content.split('\n')[0]
42 | const rawMeta = metaLine.substring(metaLine.lastIndexOf('//META') + 6, metaLine.lastIndexOf('*//'))
43 |
44 | if (metaLine.indexOf('META') < 0) throw new Error('META was not found.')
45 | if (!window.BdApi.testJSON(rawMeta)) throw new Error('META could not be parsed')
46 |
47 | const parsed = JSON.parse(rawMeta)
48 | if (!parsed.name) throw new Error('META missing name data')
49 | parsed.format = "json"
50 |
51 | return parsed
52 | }
53 |
54 | parseNewMeta(content) {
55 | const block = content.split("/**", 2)[1].split("*/", 1)[0]
56 | const out = {}
57 | let field = "", accum = ""
58 | for (const line of block.split(/[^\S\r\n]*?(?:\r\n|\n)[^\S\r\n]*?\*[^\S\r\n]?/)) {
59 | if (line.length === 0) continue
60 | if (line.charAt(0) === "@" && line.charAt(1) !== " ") {
61 | out[field] = accum
62 | const l = line.indexOf(" ")
63 | field = line.substr(1, l - 1)
64 | accum = line.substr(l + 1)
65 | } else {
66 | accum += " " + line.replace("\\n", "\n").replace(/^\\@/, "@")
67 | }
68 | }
69 | out[field] = accum.trim()
70 | delete out[""]
71 | out.format = "jsdoc"
72 | return out
73 | }
74 |
75 | getContentRequire() {
76 | return function (module, filename) {
77 | if (!filename.endsWith('.plugin.js') ||
78 | dirname(filename) !== resolve(bdConfig.dataPath, 'plugins'))
79 | return originalRequire.apply(this, arguments)
80 |
81 | let content = readFileSync(filename, 'utf8');
82 | if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1) // Strip BOM
83 | const meta = _this.extractMeta(content)
84 | meta.filename = basename(filename)
85 | content += `\nmodule.exports = Object.assign(${JSON.stringify(meta)}, { type: module.exports.__esModule ? module.exports.default : module.exports.prototype ? module.exports : ${meta.exports || meta.name} })`
86 | module._compile(
87 | meta.name === 'ZeresPluginLibrary' ?
88 | content
89 | .replace('.getByProps("getCurrentUser")', '.getByProps("getCurrentUser", "getUser")')
90 | : content,
91 | filename
92 | )
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/modules/Patcher.js:
--------------------------------------------------------------------------------
1 | module.exports = class Patcher {
2 | static get patches() { return this._patches || (this._patches = []) }
3 |
4 | static getPatchesByCaller(name) {
5 | if (!name) return []
6 | const patches = []
7 | for (const patch of this.patches) {
8 | for (const childPatch of patch.children)
9 | if (childPatch.caller === name) patches.push(childPatch)
10 | }
11 | return patches
12 | }
13 |
14 | static unpatchAll(patches) {
15 | if (typeof patches === 'string') patches = this.getPatchesByCaller(patches)
16 | for (const patch of patches) patch.unpatch()
17 | }
18 |
19 | static resolveModule(module) {
20 | if (!module || typeof module === 'function' || (typeof module === 'object' && !Array.isArray(module))) return module
21 | if (typeof module === 'string') return DiscordModules[module]
22 | if (Array.isArray(module)) return BdApi.findModuleByProps(...module)
23 | return null
24 | }
25 |
26 | static makePatch(module, functionName, name) {
27 | const patch = {
28 | name,
29 | module,
30 | functionName,
31 | originalFunction: module[functionName],
32 | revert: () => {
33 | for (const child of patch.children) child.unpatch?.()
34 | patch.children = []
35 | },
36 | counter: 0,
37 | children: []
38 | }
39 |
40 | this.patches.push(patch)
41 | return patch
42 | }
43 |
44 | static before(caller, module, functionName, callback, options = {}) {
45 | return this.pushChildPatch(caller, module, functionName, callback, { ...options, type: 'before' })
46 | }
47 |
48 | static instead(caller, module, functionName, callback, options = {}) {
49 | return this.pushChildPatch(caller, module, functionName, callback, { ...options, type: 'instead' })
50 | }
51 |
52 | static after(caller, module, functionName, callback, options = {}) {
53 | return this.pushChildPatch(caller, module, functionName, callback, { ...options, type: 'after' })
54 | }
55 |
56 | static pushChildPatch(caller, module, functionName, callback, options = {}) {
57 | const { type = 'after', forcePatch = true } = options
58 | const mdl = this.resolveModule(module)
59 | if (!mdl) return null
60 | if (!mdl[functionName] && forcePatch) mdl[functionName] = function () {}
61 | if (typeof mdl[functionName] !== 'function') return null
62 |
63 | const displayName = options.displayName || module.displayName || module.name || module.constructor.displayName || module.constructor.name
64 |
65 | const patchId = `${displayName}.${functionName}`
66 | const patch = this.patches.find(p => p.module == module && p.functionName === functionName) || this.makePatch(module, functionName, patchId)
67 |
68 | const child = {
69 | caller,
70 | type,
71 | id: patch.counter,
72 | callback,
73 | unpatch: BdApi.monkeyPatch(mdl, functionName, { [type]: data => {
74 | const r = callback(data.thisObject, data.methodArguments, data.returnValue)
75 | if (r !== undefined) data.returnValue = r
76 | } })
77 | }
78 | patch.children.push(child)
79 | patch.counter++
80 | return child.unpatch
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/modules/PluginManager.js:
--------------------------------------------------------------------------------
1 | const { webFrame } = require('electron')
2 | const { join } = require('path')
3 | const { Module } = require('module')
4 | const { existsSync, readdirSync, unlinkSync } = require('fs')
5 | const { getModule, FluxDispatcher } = require('powercord/webpack')
6 | const { inject, uninject } = require('powercord/injector')
7 |
8 | // Allow loading from discords node_modules
9 | Module.globalPaths.push(join(process.resourcesPath, 'app.asar/node_modules'))
10 |
11 | module.exports = class BDPluginManager {
12 | constructor(pluginsFolder, settings) {
13 | this.folder = pluginsFolder
14 | this.settings = settings
15 |
16 | FluxDispatcher.subscribe('CHANNEL_SELECT', this.channelSwitch = () => this.fireEvent('onSwitch'))
17 |
18 | this.observer = new MutationObserver((mutations) => {
19 | for (let i = 0, mlen = mutations.length; i < mlen; i++) this.fireEvent('observer', mutations[i])
20 | })
21 | this.observer.observe(document, { childList: true, subtree: true })
22 |
23 | // Wait for jQuery, then load the plugins
24 | window.BdApi.linkJS('jquery', '//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js')
25 | .then(async () => {
26 | this.__log('Loaded jQuery')
27 |
28 | if (!window.jQuery) {
29 | Object.defineProperty(window, 'jQuery', {
30 | get: () => webFrame.top.context.window.jQuery
31 | })
32 | window.$ = window.jQuery
33 | }
34 |
35 | const ConnectionStore = await getModule(['isTryingToConnect', 'isConnected'])
36 | const listener = () => {
37 | if (!ConnectionStore.isConnected()) return
38 | ConnectionStore.removeChangeListener(listener)
39 | this.__log('Loading plugins..')
40 | this.loadAllPlugins()
41 | this.startAllEnabledPlugins()
42 | }
43 | if (ConnectionStore.isConnected()) listener()
44 | else ConnectionStore.addChangeListener(listener)
45 | })
46 | }
47 |
48 | destroy () {
49 | window.BdApi.unlinkJS('jquery')
50 | if (this.channelSwitch) FluxDispatcher.unsubscribe('CHANNEL_SELECT', this.channelSwitch)
51 |
52 | this.observer.disconnect()
53 | this.stopAllPlugins()
54 | }
55 |
56 |
57 | startAllEnabledPlugins () {
58 | const plugins = Object.keys(window.bdplugins)
59 |
60 | plugins.forEach((pluginName) => {
61 | if (window.BdApi.loadData('BDCompat-EnabledPlugins', pluginName) === true) this.startPlugin(pluginName)
62 | })
63 | }
64 |
65 | stopAllPlugins () {
66 | const plugins = Object.keys(window.bdplugins)
67 |
68 | plugins.forEach((pluginName) => {
69 | this.stopPlugin(pluginName)
70 | })
71 | }
72 |
73 |
74 | isEnabled (pluginName) {
75 | const plugin = window.bdplugins[pluginName]
76 | if (!plugin) return this.__error(null, `Tried to access a missing plugin: ${pluginName}`)
77 |
78 | return plugin.__started
79 | }
80 |
81 | startPlugin (pluginName) {
82 | const plugin = window.bdplugins[pluginName]
83 | if (!plugin) return this.__error(null, `Tried to start a missing plugin: ${pluginName}`)
84 |
85 | if (plugin.__started) return
86 |
87 | try {
88 | plugin.plugin.start()
89 | plugin.__started = true
90 | this.__log(`Started plugin ${plugin.plugin.getName()}`)
91 | } catch (err) {
92 | this.__error(err, `Could not start ${plugin.plugin.getName()}`)
93 | window.BdApi.saveData('BDCompat-EnabledPlugins', plugin.plugin.getName(), false)
94 | }
95 | }
96 | stopPlugin (pluginName) {
97 | const plugin = window.bdplugins[pluginName]
98 | if (!plugin) return this.__error(null, `Tried to stop a missing plugin: ${pluginName}`)
99 |
100 | if (!plugin.__started) return
101 |
102 | try {
103 | plugin.plugin.stop()
104 | plugin.__started = false
105 | this.__log(`Stopped plugin ${plugin.plugin.getName()}`)
106 | } catch (err) {
107 | this.__error(err, `Could not stop ${plugin.plugin.getName()}`)
108 | if (this.settings.get('disableWhenStopFailed'))
109 | window.BdApi.saveData('BDCompat-EnabledPlugins', plugin.plugin.getName(), false)
110 | }
111 | }
112 | reloadPlugin (pluginName) {
113 | const plugin = window.bdplugins[pluginName]
114 | if (!plugin) return this.__error(null, `Tried to reload a missing plugin: ${pluginName}`)
115 |
116 | this.stopPlugin(pluginName)
117 |
118 | delete window.bdplugins[pluginName]
119 | delete require.cache[plugin.__filePath]
120 |
121 | this.loadPlugin(pluginName)
122 | this.startPlugin(pluginName)
123 | }
124 |
125 | enablePlugin (pluginName) {
126 | const plugin = window.bdplugins[pluginName]
127 | if (!plugin) return this.__error(null, `Tried to enable a missing plugin: ${pluginName}`)
128 |
129 | window.BdApi.saveData('BDCompat-EnabledPlugins', plugin.plugin.getName(), true)
130 | this.startPlugin(pluginName)
131 | }
132 | disablePlugin (pluginName) {
133 | const plugin = window.bdplugins[pluginName]
134 | if (!plugin) return this.__error(null, `Tried to disable a missing plugin: ${pluginName}`)
135 |
136 | window.BdApi.saveData('BDCompat-EnabledPlugins', plugin.plugin.getName(), false)
137 | this.stopPlugin(pluginName)
138 | }
139 |
140 | loadAllPlugins() {
141 | const plugins = readdirSync(this.folder)
142 | .filter((pluginFile) => pluginFile.endsWith('.plugin.js'))
143 | .map((pluginFile) => pluginFile.slice(0, -('.plugin.js'.length)))
144 |
145 | plugins.forEach((pluginName) => this.loadPlugin(pluginName))
146 | }
147 |
148 | loadPlugin(pluginName) {
149 | const pluginPath = join(this.folder, `${pluginName}.plugin.js`)
150 | if (!existsSync(pluginPath)) return this.__error(null, `Tried to load a nonexistant plugin: ${pluginName}`)
151 |
152 | try {
153 | // eslint-disable-next-line global-require
154 | const meta = require(pluginPath)
155 | try {
156 | const plugin = new meta.type
157 | this.addMissingGetMethods(pluginName, meta, plugin)
158 | if (window.bdplugins[plugin.getName()]) window.bdplugins[plugin.getName()].plugin.stop()
159 | delete window.bdplugins[plugin.getName()]
160 | window.bdplugins[plugin.getName()] = { plugin, __filePath: pluginPath, ...meta }
161 |
162 | if (plugin.load && typeof plugin.load === 'function')
163 | try {
164 | plugin.load()
165 |
166 | if (pluginName === '0PluginLibrary') this.__patchZLibPatcher()
167 | } catch (err) {
168 | this.__error(err, `Failed to preload ${plugin.getName()}`)
169 | }
170 |
171 | this.__log(`Loaded ${plugin.getName()} v${plugin.getVersion()} by ${plugin.getAuthor()}`)
172 | } catch (e) {
173 | this.__error(e, `Failed to load ${pluginName}:`, meta)
174 | }
175 | } catch (e) {
176 | this.__error(`Failed to compile ${pluginName}:`, e)
177 | }
178 | }
179 |
180 | delete(pluginName) {
181 | const plugin = window.bdplugins[pluginName]
182 | if (!plugin) return this.__error(null, `Tried to delete a missing plugin: ${pluginName}`)
183 |
184 | this.disablePlugin(pluginName)
185 | if (typeof plugin.plugin.unload === 'function') plugin.plugin.unload()
186 | delete window.bdplugins[pluginName]
187 |
188 | unlinkSync(plugin.__filePath)
189 | }
190 |
191 | fireEvent (event, ...args) {
192 | for (const plug in window.bdplugins) {
193 | const p = window.bdplugins[plug], { plugin } = p
194 | if (!p.__started || !plugin[event] || typeof plugin[event] !== 'function') continue
195 |
196 | try {
197 | plugin[event](...args)
198 | } catch (err) {
199 | this.__error(err, `Could not fire ${event} event for ${plugin.name}`)
200 | }
201 | }
202 | }
203 |
204 | addMissingGetMethods(pluginName, meta, plugin) {
205 | if (!plugin.getName) plugin.getName = () => meta.name || pluginName
206 | if (!plugin.getDescription) plugin.getDescription = () => meta.description || ''
207 | if (!plugin.getVersion) plugin.getVersion = () => meta.version || '0.0.0'
208 | if (!plugin.getAuthor) plugin.getAuthor = () => meta.author || meta.authorId || 'Unknown'
209 | }
210 |
211 |
212 | // ZLibrary checks for instanceof Function and Function class is different in renderer and preload, so need to fix it in bdCompat
213 | __patchZLibPatcher() {
214 | this.__unpatchZLibPatcher()
215 |
216 | const _window = webFrame.top.context.window
217 | if (!window?.ZLibrary?.Patcher) return this.__error(null, 'Failed to patch ZLibrary Patcher')
218 |
219 | const origFunction = Function
220 | inject('bdCompat-zlib-patcher-pre', window.ZLibrary.Patcher, 'pushChildPatch', args => {
221 | const orig = args[1]?.[args[2]]
222 | if (orig && !(orig instanceof origFunction) && orig instanceof _window.Function) window.Function = _window.Function
223 | return args
224 | }, true)
225 | inject('bdCompat-zlib-patcher', window.ZLibrary.Patcher, 'pushChildPatch', (_, res) => {
226 | window.Function = origFunction
227 | return res
228 | })
229 |
230 | this.__log('Patched ZLibrary Patcher')
231 | }
232 | __unpatchZLibPatcher() {
233 | uninject('bdCompat-zlib-patcher-pre')
234 | uninject('bdCompat-zlib-patcher')
235 | }
236 |
237 |
238 | disable = this.disablePlugin
239 | enable = this.enablePlugin
240 | reload = this.reloadPlugin
241 |
242 |
243 | __log (...message) {
244 | console.log('%c[BDCompat:BDPluginManager]', 'color: #3a71c1;', ...message)
245 | }
246 |
247 | __warn (...message) {
248 | console.warn('%c[BDCompat:BDPluginManager]', 'color: #e8a400;', ...message)
249 | }
250 |
251 | __error (error, ...message) {
252 | console.error('%c[BDCompat:BDPluginManager]', 'color: red;', ...message)
253 |
254 | if (error) {
255 | console.groupCollapsed(`%cError: ${error.message}`, 'color: red;')
256 | console.error(error.stack)
257 | console.groupEnd()
258 | }
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/modules/index.js:
--------------------------------------------------------------------------------
1 | require('fs')
2 | .readdirSync(__dirname)
3 | .filter(file => file != 'index.js')
4 | .forEach(filename => {
5 | const moduleName = filename.split('.')[0]
6 | exports[moduleName] = require(`${__dirname}/${filename}`)
7 | })
8 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | .bdc-spacer {
2 | flex-grow: 1;
3 | flex-shrink: 1;
4 | }
5 | .bdc-margin {
6 | width: 20px;
7 | }
8 | .bdc-justifystart {
9 | display: flex;
10 | justify-content: flex-start;
11 | }
12 | .bdc-plugin .metadata > div {
13 | width: 50% !important;
14 | }
15 | .bdc-plugin .metadata > .license {
16 | display: none;
17 | }
18 |
19 | /* Toast CSS */
20 |
21 | .bd-toasts {
22 | position: fixed;
23 | display: flex;
24 | top: 0;
25 | flex-direction: column;
26 | align-items: center;
27 | justify-content: flex-end;
28 | pointer-events: none;
29 | z-index: 4000;
30 | }
31 |
32 | @keyframes toast-up {
33 | from {
34 | transform: translateY(0);
35 | opacity: 0;
36 | }
37 | }
38 |
39 | .bd-toast {
40 | animation: toast-up 300ms ease;
41 | transform: translateY(-10px);
42 | background: #36393F;
43 | padding: 10px;
44 | border-radius: 5px;
45 | box-shadow: 0 0 0 1px rgba(32, 34, 37, .6), 0 2px 10px 0 rgba(0, 0, 0, .2);
46 | font-weight: 500;
47 | color: #fff;
48 | user-select: text;
49 | font-size: 14px;
50 | opacity: 1;
51 | margin-top: 10px;
52 | pointer-events: none;
53 | user-select: none;
54 | }
55 |
56 | @keyframes toast-down {
57 | to {
58 | transform: translateY(0px);
59 | opacity: 0;
60 | }
61 | }
62 |
63 | .bd-toast.closing {
64 | animation: toast-down 200ms ease;
65 | animation-fill-mode: forwards;
66 | opacity: 1;
67 | transform: translateY(-10px);
68 | }
69 |
70 | .bd-toast.icon {
71 | padding-left: 30px;
72 | background-size: 20px 20px;
73 | background-repeat: no-repeat;
74 | background-position: 6px 50%;
75 | }
76 |
77 | .bd-toast.toast-info {
78 | background-color: #4a90e2;
79 | }
80 |
81 | .bd-toast.toast-info.icon {
82 | background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMSAxNWgtMnYtNmgydjZ6bTAtOGgtMlY3aDJ2MnoiLz48L3N2Zz4=);
83 | }
84 |
85 | .bd-toast.toast-success {
86 | background-color: #43b581;
87 | }
88 |
89 | .bd-toast.toast-success.icon {
90 | background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptLTIgMTVsLTUtNSAxLjQxLTEuNDFMMTAgMTQuMTdsNy41OS03LjU5TDE5IDhsLTkgOXoiLz48L3N2Zz4=);
91 | }
92 |
93 | .bd-toast.toast-danger,
94 | .bd-toast.toast-error {
95 | background-color: #f04747;
96 | }
97 |
98 | .bd-toast.toast-danger.icon,
99 | .bd-toast.toast-error.icon {
100 | background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTEyIDJDNi40NyAyIDIgNi40NyAyIDEyczQuNDcgMTAgMTAgMTAgMTAtNC40NyAxMC0xMFMxNy41MyAyIDEyIDJ6bTUgMTMuNTlMMTUuNTkgMTcgMTIgMTMuNDEgOC40MSAxNyA3IDE1LjU5IDEwLjU5IDEyIDcgOC40MSA4LjQxIDcgMTIgMTAuNTkgMTUuNTkgNyAxNyA4LjQxIDEzLjQxIDEyIDE3IDE1LjU5eiIvPiAgICA8cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PC9zdmc+);
101 | }
102 |
103 | .bd-toast.toast-warning,
104 | .bd-toast.toast-warn {
105 | background-color: #FFA600;
106 | color: white;
107 | }
108 |
109 | .bd-toast.toast-warning.icon,
110 | .bd-toast.toast-warn.icon {
111 | background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPiAgICA8cGF0aCBkPSJNMSAyMWgyMkwxMiAyIDEgMjF6bTEyLTNoLTJ2LTJoMnYyem0wLTRoLTJ2LTRoMnY0eiIvPjwvc3ZnPg==);
112 | }
113 |
--------------------------------------------------------------------------------