├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── INCOMPATIBILITIES.md
├── README.md
├── TODO.md
├── components
└── BDPluginManager.js
├── index.js
├── libraries
├── BDApi.js
├── BDContentManager.js
└── BDV2.js
├── manifest.json
├── reactcomponents
├── DeleteConfirm.jsx
├── Icons.jsx
├── Plugin.jsx
├── PluginList.jsx
├── PluginSettings.jsx
└── Settings.jsx
└── 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 |
--------------------------------------------------------------------------------
/INCOMPATIBILITIES.md:
--------------------------------------------------------------------------------
1 | # Notes on incompatibiltiies
2 |
3 | - Regarding monkey patching
4 | - Some plugins, especially those that uses their own plugin library, patches Discord's functions, and if it's not done through BD's API for monkey patching then it can lead to breakages.
5 | - One notable note is with [DevilBro's plugins](https://github.com/mwittrien/BetterDiscordAddons), the library used in his plugins tries to patch method responsible for handling the guild header, and thus it breaks some plugins like Powercord's Badges plugin (`pc-badges`)
6 |
7 | - Plugins that enhances BetterDiscord's emotes feature
8 | - Affects plugins like EmoteSearch
9 | - No, they don't work. Why? Go figure.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pc-bdCompat
2 |
3 | Compatibility layer for running BetterDiscord plugins in Powercord
4 |
5 | [](https://imgur.com/a/2gWgY7q)
6 |
7 | ## Installation
8 |
9 | Clone this repository to your Powercord install's plugins folder
10 |
11 | ```
12 | git clone https://github.com/intrnl/pc-bdCompat
13 | ```
14 |
15 | ## Installing BD plugins
16 |
17 | Before you download and install any BD plugins, please take a look at the incompatibilites note on `INCOMPATIBILITIES.md` file
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 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # To do
2 |
3 | - 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/BDPluginManager.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const electron = require('electron')
4 | const process = require('process')
5 | const path = require('path')
6 | const fs = require('fs')
7 | const { Module } = require('module')
8 |
9 | Module.globalPaths.push(path.resolve(electron.remote.app.getAppPath(), 'node_modules'))
10 |
11 |
12 | class BDPluginManager {
13 | constructor () {
14 | this.currentWindow = electron.remote.getCurrentWindow()
15 | this.currentWindow.webContents.on('did-navigate-in-page', () => this.onSwitchListener())
16 |
17 | // DevilBro's plugins checks whether or not it's running on ED
18 | // This isn't BetterDiscord, so we'd be better off doing this.
19 | // eslint-disable-next-line no-process-env
20 | process.env.injDir = __dirname
21 |
22 | // Wait for jQuery, then load the plugins
23 | window.BdApi.linkJS('jquery', '//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js')
24 | .then(() => {
25 | this.__log('Loaded jQuery')
26 | this.loadAllPlugins()
27 | this.startAllEnabledPlugins()
28 | })
29 | }
30 |
31 | get pluginDirectory () {
32 | const pluginDir = path.join(__dirname, '..', 'plugins/')
33 |
34 | if (!fs.existsSync(pluginDir)) fs.mkdirSync(pluginDir)
35 |
36 | return pluginDir
37 | }
38 |
39 | destroy () {
40 | window.BdApi.unlinkJS('jquery')
41 | this.currentWindow.webContents.off('did-navigate-in-page', () => this.onSwitchListener())
42 |
43 | this.stopAllPlugins()
44 |
45 | // eslint-disable-next-line no-process-env
46 | process.env.injDir = ''
47 | }
48 |
49 |
50 | startAllEnabledPlugins () {
51 | const plugins = Object.keys(window.bdplugins)
52 |
53 | plugins.forEach((pluginName) => {
54 | if (window.BdApi.loadData('BDCompat-EnabledPlugins', pluginName) === true) this.startPlugin(pluginName)
55 | })
56 | }
57 |
58 | stopAllPlugins () {
59 | const plugins = Object.keys(window.bdplugins)
60 |
61 | plugins.forEach((pluginName) => {
62 | this.stopPlugin(pluginName)
63 | })
64 | }
65 |
66 |
67 | isEnabled (pluginName) {
68 | const plugin = window.bdplugins[pluginName]
69 | if (!plugin) return this.__error(null, `Tried to access a missing plugin: ${pluginName}`)
70 |
71 | return plugin.__started
72 | }
73 |
74 | startPlugin (pluginName) {
75 | const plugin = window.bdplugins[pluginName]
76 | if (!plugin) return this.__error(null, `Tried to start a missing plugin: ${pluginName}`)
77 |
78 | if (plugin.__started) return
79 |
80 | try {
81 | plugin.start()
82 | plugin.__started = true
83 | this.__log(`Started plugin ${plugin.getName()}`)
84 | } catch (err) {
85 | this.__error(err, `Could not start ${plugin.getName()}`)
86 | window.BdApi.saveData('BDCompat-EnabledPlugins', plugin.getName(), false)
87 | }
88 | }
89 | stopPlugin (pluginName) {
90 | const plugin = window.bdplugins[pluginName]
91 | if (!plugin) return this.__error(null, `Tried to stop a missing plugin: ${pluginName}`)
92 |
93 | if (!plugin.__started) return
94 |
95 | try {
96 | plugin.stop()
97 | plugin.__started = false
98 | this.__log(`Stopped plugin ${plugin.getName()}`)
99 | } catch (err) {
100 | this.__error(err, `Could not stop ${plugin.getName()}`)
101 | window.BdApi.saveData('BDCompat-EnabledPlugins', plugin.getName(), false)
102 | }
103 | }
104 |
105 | enablePlugin (pluginName) {
106 | const plugin = window.bdplugins[pluginName]
107 | if (!plugin) return this.__error(null, `Tried to enable a missing plugin: ${pluginName}`)
108 |
109 | window.BdApi.saveData('BDCompat-EnabledPlugins', plugin.getName(), true)
110 | this.startPlugin(pluginName)
111 | }
112 | disablePlugin (pluginName) {
113 | const plugin = window.bdplugins[pluginName]
114 | if (!plugin) return this.__error(null, `Tried to disable a missing plugin: ${pluginName}`)
115 |
116 | window.BdApi.saveData('BDCompat-EnabledPlugins', plugin.getName(), false)
117 | this.stopPlugin(pluginName)
118 | }
119 |
120 | loadAllPlugins () {
121 | const plugins = fs.readdirSync(this.pluginDirectory)
122 | .filter((pluginFile) => pluginFile.endsWith('.plugin.js'))
123 | .map((pluginFile) => pluginFile.slice(0, -('.plugin.js'.length)))
124 |
125 | plugins.forEach((pluginName) => this.loadPlugin(pluginName))
126 | }
127 | loadPlugin (pluginName) {
128 | const pluginPath = path.join(this.pluginDirectory, `${pluginName}.plugin.js`)
129 | if (!fs.existsSync(pluginPath)) return this.__error(null, `Tried to load a nonexistant plugin: ${pluginName}`)
130 |
131 | let content = fs.readFileSync(pluginPath, 'utf8')
132 | if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1)
133 |
134 | const meta = this.extractMeta(content)
135 | content += `\nmodule.exports = ${meta.name};`
136 |
137 | const tempPluginPath = path.join(this.pluginDirectory, `__${pluginName}.plugin.js`)
138 | fs.writeFileSync(tempPluginPath, content)
139 |
140 | // eslint-disable-next-line global-require
141 | const Plugin = require(tempPluginPath)
142 | const plugin = new Plugin
143 | plugin.__meta = meta
144 | plugin.__filePath = pluginPath
145 |
146 | if (window.bdplugins[plugin.getName()]) window.bdplugins[plugin.getName()].stop()
147 | delete window.bdplugins[plugin.getName()]
148 | window.bdplugins[plugin.getName()] = plugin
149 |
150 | if (plugin.load && typeof plugin.load === 'function')
151 | try {
152 | plugin.load()
153 | } catch (err) {
154 | this.__error(err, `Failed to preload ${plugin.getName()}`)
155 | }
156 |
157 |
158 | this.__log(`Loaded ${plugin.getName()} v${plugin.getVersion()} by ${plugin.getAuthor()}`)
159 | fs.unlinkSync(tempPluginPath)
160 | delete require.cache[require.resolve(tempPluginPath)]
161 | }
162 |
163 | extractMeta (content) {
164 | const metaLine = content.split('\n')[0]
165 | const rawMeta = metaLine.substring(metaLine.lastIndexOf('//META') + 6, metaLine.lastIndexOf('*//'))
166 |
167 | if (metaLine.indexOf('META') < 0) throw new Error('META was not found.')
168 | if (!window.BdApi.testJSON(rawMeta)) throw new Error('META could not be parsed')
169 |
170 | const parsed = JSON.parse(rawMeta)
171 | if (!parsed.name) throw new Error('META missing name data')
172 |
173 | return parsed
174 | }
175 |
176 | deletePlugin (pluginName) {
177 | const plugin = window.bdplugins[pluginName]
178 | if (!plugin) return this.__error(null, `Tried to delete a missing plugin: ${pluginName}`)
179 |
180 | this.disablePlugin(pluginName)
181 | if (typeof plugin.unload === 'function') plugin.unload()
182 | delete window.bdplugins[pluginName]
183 |
184 | fs.unlinkSync(plugin.__filePath)
185 | }
186 |
187 |
188 | fireEvent (event, ...args) {
189 | for (const plug in window.bdplugins) {
190 | const plugin = window.bdplugins[plug]
191 | if (!plugin[event] || typeof plugin[event] !== 'function') continue
192 |
193 | try {
194 | plugin[event](...args)
195 | } catch (err) {
196 | this.__error(err, `Could not fire ${event} event for ${plugin.name}`)
197 | }
198 | }
199 | }
200 |
201 | onSwitchListener () {
202 | this.fireEvent('onSwitch')
203 | }
204 |
205 |
206 | __log (...message) {
207 | console.log('%c[BDCompat:BDPluginManager]', 'color: #3a71c1;', ...message)
208 | }
209 |
210 | __warn (...message) {
211 | console.log('%c[BDCompat:BDPluginManager]', 'color: #e8a400;', ...message)
212 | }
213 |
214 | __error (error, ...message) {
215 | console.log('%c[BDCompat:BDPluginManager]', 'color: red;', ...message)
216 |
217 | if (error) {
218 | console.groupCollapsed(`%cError: ${error.message}`, 'color: red;')
219 | console.error(error.stack)
220 | console.groupEnd()
221 | }
222 | }
223 | }
224 |
225 | module.exports = BDPluginManager
226 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { Plugin } = require('powercord/entities')
4 | const { React } = require('powercord/webpack')
5 | const path = require('path')
6 |
7 | const BDApi = require('./libraries/BDApi.js')
8 | const BDV2 = require('./libraries/BDV2.js')
9 | const BDContentManager = require('./libraries/BDContentManager.js')
10 |
11 | const BDPluginManager = require('./components/BDPluginManager.js')
12 | const Settings = require('./reactcomponents/Settings.jsx')
13 |
14 |
15 | class BDCompat extends Plugin {
16 | startPlugin () {
17 | this.loadCSS(path.join(__dirname, 'style.css'))
18 | this.defineGlobals()
19 |
20 | this.PluginManager = new BDPluginManager
21 |
22 | this.registerSettings(
23 | 'pc-bdCompat',
24 | 'BetterDiscord Plugins',
25 | () => React.createElement(Settings, { settings: this.settings })
26 | )
27 | }
28 |
29 | pluginWillUnload () {
30 | this.PluginManager.destroy()
31 |
32 | this.unloadCSS()
33 | this.destroyGlobals()
34 |
35 | powercord.pluginManager
36 | .get('pc-settings')
37 | .unregister('pc-bdCompat')
38 | }
39 |
40 | defineGlobals () {
41 | window.bdConfig = { dataPath: __dirname }
42 | window.settingsCookie = {}
43 |
44 | window.bdplugins = {}
45 | window.pluginCookie = {}
46 | window.bdpluginErrors = []
47 |
48 | window.bdthemes = {}
49 | window.themeCookie = {}
50 | window.bdthemeErrors = []
51 |
52 | window.BdApi = BDApi
53 | window.bdPluginStorage = { get: BDApi.getData, set: BDApi.setData }
54 | window.Utils = { monkeyPatch: BDApi.monkeyPatch, suppressErrors: BDApi.suppressErrors, escapeID: BDApi.escapeID }
55 |
56 | window.BDV2 = BDV2
57 | window.ContentManager = BDContentManager
58 |
59 | this.log('Defined BetterDiscord globals')
60 | }
61 |
62 | destroyGlobals () {
63 | delete window.bdConfig
64 | delete window.settingsCookie
65 | delete window.bdplugins
66 | delete window.pluginCookie
67 | delete window.bdpluginErrors
68 | delete window.bdthemes
69 | delete window.themeCookie
70 | delete window.bdthemeErrors
71 | delete window.BdApi
72 | delete window.bdPluginStorage
73 | delete window.Utils
74 | delete window.BDV2
75 |
76 | this.log('Destroyed BetterDiscord globals')
77 | }
78 | }
79 |
80 | module.exports = BDCompat
81 |
--------------------------------------------------------------------------------
/libraries/BDApi.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const fs = require('fs')
5 | const crypto = require('crypto')
6 |
7 | const { React, ReactDOM } = require('powercord/webpack')
8 | const { getModule, getAllModules } = require('powercord/webpack')
9 | const { getOwnerInstance } = require('powercord/util')
10 | const { inject, uninject } = require('powercord/injector')
11 |
12 | const PluginData = {}
13 |
14 |
15 | // __ is not part of BdApi entirely
16 | // _ is part of BD but not exactly in BdApi, but kept here anyway for easier maintain
17 |
18 | class BdApi {
19 | // React
20 | static get React () {
21 | return React
22 | }
23 | static get ReactDOM () {
24 | return ReactDOM
25 | }
26 |
27 |
28 | // General
29 | static getCore () {
30 | return null
31 | }
32 | static escapeID (id) {
33 | return id.replace(/^[^a-z]+|[^\w-]+/giu, '')
34 | }
35 |
36 | static suppressErrors (method, message = '') {
37 | return (...params) => {
38 | try {
39 | return method(...params)
40 | } catch (err) {
41 | BdApi.__error(err, `Error occured in ${message}`)
42 | }
43 | }
44 | }
45 |
46 | static testJSON (data) {
47 | try {
48 | JSON.parse(data)
49 |
50 | return true
51 | } catch (err) {
52 | return false
53 | }
54 | }
55 |
56 |
57 | // Style tag
58 | static get __styleParent () {
59 | return BdApi.__elemParent('style')
60 | }
61 |
62 | static injectCSS (id, css) {
63 | const style = document.createElement('style')
64 |
65 | style.id = `bd-style-${BdApi.escapeID(id)}`
66 | style.innerHTML = css
67 |
68 | BdApi.__styleParent.append(style)
69 | }
70 |
71 | static clearCSS (id) {
72 | const elem = document.getElementById(`bd-style-${BdApi.escapeID(id)}`)
73 | if (elem) elem.remove()
74 | }
75 |
76 |
77 | // Script tag
78 | static get __scriptParent () {
79 | return BdApi.__elemParent('script')
80 | }
81 |
82 | static linkJS (id, url) {
83 | return new Promise((resolve) => {
84 | const script = document.createElement('script')
85 |
86 | script.id = `bd-script-${BdApi.escapeID(id)}`
87 | script.src = url
88 | script.type = 'text/javascript'
89 | script.onload = resolve
90 |
91 | BdApi.__scriptParent.append(script)
92 | })
93 | }
94 |
95 | static unlinkJS (id) {
96 | const elem = document.getElementById(`bd-script-${BdApi.escapeID(id)}`)
97 | if (elem) elem.remove()
98 | }
99 |
100 |
101 | // Plugin data
102 | static get __pluginData () {
103 | return PluginData
104 | }
105 |
106 | static __getPluginConfigPath (pluginName) {
107 | return path.join(__dirname, '..', 'config', pluginName + '.json')
108 | }
109 |
110 | static __getPluginConfig (pluginName) {
111 | const configPath = BdApi.__getPluginConfigPath(pluginName)
112 |
113 | if (typeof BdApi.__pluginData[pluginName] === 'undefined')
114 | if (!fs.existsSync(configPath)) {
115 | BdApi.__pluginData[pluginName] = {}
116 | } else {
117 | BdApi.__pluginData[pluginName] = JSON.parse(fs.readFileSync(configPath))
118 | }
119 |
120 |
121 | return BdApi.__pluginData[pluginName]
122 | }
123 |
124 | static __savePluginConfig (pluginName) {
125 | const configPath = BdApi.__getPluginConfigPath(pluginName)
126 | const configFolder = path.join(__dirname, '..', 'config/')
127 |
128 | if (!fs.existsSync(configFolder)) fs.mkdirSync(configFolder)
129 | fs.writeFileSync(configPath, JSON.stringify(BdApi.__pluginData[pluginName], null, 2))
130 | }
131 |
132 |
133 | static loadData (pluginName, key) {
134 | const config = BdApi.__getPluginConfig(pluginName)
135 |
136 | return config[key]
137 | }
138 |
139 | static get getData () {
140 | return BdApi.loadData
141 | }
142 |
143 |
144 | static saveData (pluginName, key, value) {
145 | if (typeof value === 'undefined') return
146 |
147 | const config = BdApi.__getPluginConfig(pluginName)
148 |
149 | config[key] = value
150 |
151 | BdApi.__savePluginConfig(pluginName)
152 | }
153 |
154 | static get setData () {
155 | return BdApi.saveData
156 | }
157 |
158 | static deleteData (pluginName, key) {
159 | const config = BdApi.__getPluginConfig(pluginName)
160 |
161 | if (typeof config[key] === 'undefined') return
162 | delete config[key]
163 |
164 | BdApi.__savePluginConfig(pluginName)
165 | }
166 |
167 |
168 | // Plugin communication
169 | static getPlugin (name) {
170 | if (window.bdplugins[name]) return window.bdplugins[name].plugin
171 | }
172 |
173 |
174 | // Alerts and toasts
175 | static alert (title, body) {
176 | const ModalStack = getModule(['push', 'update', 'pop', 'popWithKey'])
177 | const AlertModal = getModule((module) => module.prototype &&
178 | module.prototype.handleCancel && module.prototype.handleSubmit && module.prototype.handleMinorConfirm)
179 |
180 | ModalStack.push((props) => BdApi.React.createElement(AlertModal, { title, body, ...props }))
181 | }
182 |
183 | static showToast (content, options = {}) {
184 | const { type = '', icon = true, timeout = 3000 } = options
185 |
186 | const toastElem = document.createElement('div')
187 | toastElem.classList.add('bd-toast')
188 | toastElem.innerText = content
189 |
190 | if (type) toastElem.classList.add(`toast-${type}`)
191 | if (type && icon) toastElem.classList.add('icon')
192 |
193 | const toastWrapper = BdApi.__createToastWrapper()
194 | toastWrapper.appendChild(toastElem)
195 |
196 | setTimeout(() => {
197 | toastElem.classList.add('closing')
198 |
199 | setTimeout(() => {
200 | toastElem.remove()
201 | if (!document.querySelectorAll('.bd-toasts .bd-toast').length) toastWrapper.remove()
202 | }, 300)
203 | }, timeout)
204 | }
205 |
206 | static __createToastWrapper () {
207 | const toastWrapperElem = document.querySelector('.bd-toasts')
208 |
209 | if (!toastWrapperElem) {
210 | const DiscordElements = {
211 | settings: '.contentColumn-2hrIYH, .customColumn-Rb6toI',
212 | chat: '.chat-3bRxxu form',
213 | friends: '.container-3gCOGc',
214 | serverDiscovery: '.pageWrapper-1PgVDX',
215 | applicationStore: '.applicationStore-1pNvnv',
216 | gameLibrary: '.gameLibrary-TTDw4Y',
217 | activityFeed: '.activityFeed-28jde9',
218 | }
219 |
220 | const boundingElement = document.querySelector(Object.keys(DiscordElements).map((component) => DiscordElements[component]).join(', '))
221 |
222 | const toastWrapper = document.createElement('div')
223 | toastWrapper.classList.add('bd-toasts')
224 | toastWrapper.style.setProperty('width', boundingElement ? boundingElement.offsetWidth + 'px' : '100%')
225 | toastWrapper.style.setProperty('left', boundingElement ? boundingElement.getBoundingClientRect().left + 'px' : '0px')
226 | toastWrapper.style.setProperty(
227 | 'bottom',
228 | (document.querySelector(DiscordElements.chat) ? document.querySelector(DiscordElements.chat).offsetHeight + 20 : 80) + 'px'
229 | )
230 |
231 | document.querySelector('#app-mount > div[class^="app-"]').appendChild(toastWrapper)
232 |
233 | return toastWrapper
234 | }
235 |
236 | return toastWrapperElem
237 | }
238 |
239 |
240 | // Discord's internals manipulation and such
241 | static onRemoved (node, callback) {
242 | const observer = new MutationObserver((mutations) => {
243 | for (const mut in mutations) {
244 | const mutation = mutations[mut]
245 | const nodes = Array.from(mutation.removedNodes)
246 |
247 | const directMatch = nodes.indexOf(node) > -1
248 | const parentMatch = nodes.some((parent) => parent.contains(node))
249 |
250 | if (directMatch || parentMatch) {
251 | observer.disconnect()
252 |
253 | return callback()
254 | }
255 | }
256 | })
257 |
258 | observer.observe(document.body, { subtree: true, childList: true })
259 | }
260 |
261 | static getInternalInstance (node) {
262 | if (!(node instanceof window.jQuery) && !(node instanceof Element)) return undefined // eslint-disable-line no-undefined
263 | if (node instanceof window.jQuery) node = node[0] // eslint-disable-line no-param-reassign
264 |
265 | return getOwnerInstance(node)
266 | }
267 |
268 | static findModule (filter) {
269 | return getModule(filter)
270 | }
271 |
272 | static findAllModules (filter) {
273 | return getAllModules(filter)
274 | }
275 |
276 | static findModuleByProps (...props) {
277 | return BdApi.findModule((module) => props.every((prop) => typeof module[prop] !== 'undefined'))
278 | }
279 |
280 | static _findModuleByDisplayName (displayName) {
281 | return BdApi.findModule((module) => module.displayName === displayName)
282 | }
283 |
284 | static monkeyPatch (what, methodName, options = {}) {
285 | const displayName = options.displayName ||
286 | what.displayName || what.name || what.constructor.displayName || what.constructor.name ||
287 | 'MissingName'
288 |
289 | if (options.instead) return BdApi.__warn('Powercord API currently does not support replacing the entire method!')
290 |
291 | if (!what[methodName])
292 | if (options.force) {
293 | // eslint-disable-next-line no-empty-function
294 | what[methodName] = function forcedFunction () {}
295 | } else {
296 | return BdApi.__error(null, `${methodName} doesn't exist in ${displayName}!`)
297 | }
298 |
299 |
300 | if (!options.silent)
301 | BdApi.__log(`Patching ${displayName}'s ${methodName} method`)
302 |
303 |
304 | const patches = []
305 | if (options.before) patches.push(BdApi.__injectBefore({ what, methodName, options, displayName }))
306 | if (options.after) patches.push(BdApi.__injectAfter({ what, methodName, options, displayName }))
307 |
308 | const finalCancelPatch = () => patches.forEach((patch) => patch())
309 |
310 | return finalCancelPatch
311 | }
312 |
313 | static __injectBefore (data) {
314 | const patchID = `bd-patch-before-${data.displayName.toLowerCase()}-${crypto.randomBytes(4).toString('hex')}`
315 |
316 | const cancelPatch = () => {
317 | if (!data.options.silent) BdApi.__log(`Unpatching before of ${data.displayName} ${data.methodName}`)
318 | uninject(patchID)
319 | }
320 |
321 | inject(patchID, data.what, data.methodName, function beforePatch (args, res) {
322 | const patchData = {
323 | // eslint-disable-next-line no-invalid-this
324 | thisObject: this,
325 | methodArguments: args,
326 | returnValue: res,
327 | cancelPatch: cancelPatch,
328 | // originalMethod,
329 | // callOriginalMethod,
330 | }
331 |
332 | try {
333 | data.options.before(patchData)
334 | } catch (err) {
335 | BdApi.__error(err, `Error in before callback of ${data.displayName} ${data.methodName}`)
336 | }
337 |
338 | if (data.options.once) cancelPatch()
339 |
340 | return patchData.returnValue
341 | }, true)
342 |
343 | return cancelPatch
344 | }
345 |
346 | static __injectAfter (data) {
347 | const patchID = `bd-patch-after-${data.displayName.toLowerCase()}-${crypto.randomBytes(4).toString('hex')}`
348 |
349 | const cancelPatch = () => {
350 | if (!data.options.silent) BdApi.__log(`Unpatching after of ${data.displayName} ${data.methodName}`)
351 | uninject(patchID)
352 | }
353 |
354 | inject(patchID, data.what, data.methodName, function afterPatch (args, res) {
355 | const patchData = {
356 | // eslint-disable-next-line no-invalid-this
357 | thisObject: this,
358 | methodArguments: args,
359 | returnValue: res,
360 | cancelPatch: cancelPatch,
361 | // originalMethod,
362 | // callOriginalMethod,
363 | }
364 |
365 | try {
366 | data.options.after(patchData)
367 | } catch (err) {
368 | BdApi.__error(err, `Error in after callback of ${data.displayName} ${data.methodName}`)
369 | }
370 |
371 | if (data.options.once) cancelPatch()
372 |
373 | return patchData.returnValue
374 | }, false)
375 |
376 | return cancelPatch
377 | }
378 |
379 |
380 | // Miscellaneous, things that aren't part of BD
381 | static __elemParent (id) {
382 | const elem = document.getElementsByTagName(`bd-${id}`)[0]
383 | if (elem) return elem
384 |
385 | const newElem = document.createElement(`bd-${id}`)
386 | document.head.append(newElem)
387 |
388 | return newElem
389 | }
390 |
391 | static __log (...message) {
392 | console.log('%c[BDCompat:BdApi]', 'color: #3a71c1;', ...message)
393 | }
394 |
395 | static __warn (...message) {
396 | console.log('%c[BDCompat:BdApi]', 'color: #e8a400;', ...message)
397 | }
398 |
399 | static __error (error, ...message) {
400 | console.log('%c[BDCompat:BdApi]', 'color: red;', ...message)
401 |
402 | if (error) {
403 | console.groupCollapsed(`%cError: ${error.message}`, 'color: red;')
404 | console.error(error.stack)
405 | console.groupEnd()
406 | }
407 | }
408 | }
409 |
410 | module.exports = BdApi
411 |
--------------------------------------------------------------------------------
/libraries/BDContentManager.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | // Purposefully incomplete ContentManager
4 | class ContentManager {
5 | static get pluginsFolder () {
6 | return window.powercord.pluginManager.plugins.get('pc-bdCompat').PluginManager.pluginDirectory
7 | }
8 | static get themesFolder () {
9 | // We'll just pretend it exists.
10 | return path.join(ContentManager.pluginsFolder, '..', 'themes')
11 | }
12 |
13 | static get extractMeta () {
14 | return window.powercord.pluginManager.plugins.get('pc-bdCompat').PluginManager.extractMeta
15 | }
16 | }
17 |
18 | module.exports = ContentManager
--------------------------------------------------------------------------------
/libraries/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 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "BetterDiscord plugin compatibility",
3 | "version": "0.2.0",
4 | "description": "Adds support for running BetterDiscord plugins",
5 | "author": "intrnl",
6 | "license": "MIT",
7 | "repo": "https://github.com/intrnl/pc-bdCompat"
8 | }
9 |
--------------------------------------------------------------------------------
/reactcomponents/DeleteConfirm.jsx:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { React, getModule } = require('powercord/webpack')
4 | const { Confirm: ConfirmModal } = require('powercord/components/modal')
5 | const { close: closeModal } = require('powercord/modal')
6 |
7 | const Text = getModule(['Sizes', 'Weights'])
8 |
9 | class DeleteConfirm extends React.Component {
10 | constructor (props) {
11 | super(props)
12 | }
13 |
14 | render () {
15 | return (
16 |