2020-07-16 23:17:02 +02:00
import { Config } from "data" ;
2019-05-28 23:27:25 +02:00
import Utilities from "./utilities" ;
2019-06-19 21:24:05 +02:00
import WebpackModules from "./webpackmodules" ;
import DiscordModules from "./discordmodules" ;
2019-05-28 23:27:25 +02:00
import DataStore from "./datastore" ;
2019-06-19 05:09:49 +02:00
import DOMManager from "./dommanager" ;
2019-06-23 06:11:50 +02:00
import Toasts from "../ui/toasts" ;
import Modals from "../ui/modals" ;
2019-09-27 04:50:53 +02:00
import PluginManager from "./pluginmanager" ;
import ThemeManager from "./thememanager" ;
2020-07-16 07:42:56 +02:00
import Settings from "./settingsmanager" ;
2021-03-06 09:30:16 +01:00
import Logger from "common/logger" ;
2020-07-19 01:01:49 +02:00
import Patcher from "./patcher" ;
2020-07-24 10:59:16 +02:00
import Emotes from "../builtins/emotes/emotes" ;
2021-07-09 00:20:28 +02:00
import ipc from "./ipc" ;
2019-05-28 23:27:25 +02:00
const BdApi = {
2020-07-25 10:22:57 +02:00
get React ( ) { return DiscordModules . React ; } ,
get ReactDOM ( ) { return DiscordModules . ReactDOM ; } ,
2020-07-16 23:17:02 +02:00
get WindowConfigFile ( ) { return "" ; } ,
2020-07-16 07:42:56 +02:00
get settings ( ) { return Settings . collections ; } ,
2020-07-25 10:22:57 +02:00
get emotes ( ) {
return new Proxy ( Emotes . Emotes , {
2021-04-08 02:31:02 +02:00
get ( obj , category ) {
2021-04-03 05:16:23 +02:00
if ( category === "blocklist" ) return Emotes . blocklist ;
2021-02-22 23:53:21 +01:00
const group = Emotes . Emotes [ category ] ;
if ( ! group ) return undefined ;
return new Proxy ( group , {
2021-04-08 02:31:02 +02:00
get ( cat , emote ) { return group [ emote ] ; } ,
2021-07-09 08:05:58 +02:00
set ( ) { Logger . warn ( "BdApi.emotes" , "Addon policy for plugins #5 https://github.com/BetterDiscord/BetterDiscord/wiki/Addon-Policies#plugins" ) ; }
2021-02-22 23:53:21 +01:00
} ) ;
} ,
2021-07-09 08:05:58 +02:00
set ( ) { Logger . warn ( "BdApi.emotes" , "Addon policy for plugins #5 https://github.com/BetterDiscord/BetterDiscord/wiki/Addon-Policies#plugins" ) ; }
2020-07-25 10:22:57 +02:00
} ) ;
} ,
2020-07-16 23:17:02 +02:00
get version ( ) { return Config . version ; }
2019-05-28 20:19:48 +02:00
} ;
BdApi . getAllWindowPreferences = function ( ) {
2021-04-12 07:07:00 +02:00
Logger . warn ( "Deprecated" , "BdApi.getAllWindowPreferences() has been deprecated due to the new handling of window transparency." ) ;
2019-05-28 20:19:48 +02:00
} ;
2021-04-12 07:07:00 +02:00
BdApi . getWindowPreference = function ( ) {
Logger . warn ( "Deprecated" , "BdApi.getWindowPreference() has been deprecated due to the new handling of window transparency." ) ;
return null ;
2019-05-28 20:19:48 +02:00
} ;
2021-04-12 07:07:00 +02:00
BdApi . setWindowPreference = function ( ) {
Logger . warn ( "Deprecated" , "BdApi.setWindowPreference() has been deprecated due to the new handling of window transparency." ) ;
2019-05-28 20:19:48 +02:00
} ;
2020-07-25 10:22:57 +02:00
// Inject CSS to document head
// id = id of element
// css = custom css
2019-05-28 20:19:48 +02:00
BdApi . injectCSS = function ( id , css ) {
2019-06-20 04:19:34 +02:00
DOMManager . injectStyle ( id , css ) ;
2019-05-28 20:19:48 +02:00
} ;
2020-07-25 10:22:57 +02:00
// Clear css/remove any element
// id = id of element
2019-05-28 20:19:48 +02:00
BdApi . clearCSS = function ( id ) {
2019-06-20 04:19:34 +02:00
DOMManager . removeStyle ( id ) ;
2019-05-28 20:19:48 +02:00
} ;
2020-07-25 10:22:57 +02:00
// Inject CSS to document head
// id = id of element
// css = custom css
2019-05-28 20:19:48 +02:00
BdApi . linkJS = function ( id , url ) {
2019-06-20 04:19:34 +02:00
return DOMManager . injectScript ( id , url ) ;
2019-05-28 20:19:48 +02:00
} ;
2020-07-25 10:22:57 +02:00
// Clear css/remove any element
// id = id of element
2019-05-28 20:19:48 +02:00
BdApi . unlinkJS = function ( id ) {
2019-06-20 04:19:34 +02:00
DOMManager . removeScript ( id ) ;
2019-05-28 20:19:48 +02:00
} ;
/ * *
* Shows a generic but very customizable modal .
* @ param { string } title - title of the modal
* @ param { string } content - a string of text to display in the modal
* /
BdApi . alert = function ( title , content ) {
2019-05-31 07:53:11 +02:00
Modals . alert ( title , content ) ;
2019-05-28 20:19:48 +02:00
} ;
/ * *
* Shows a generic but very customizable confirmation modal with optional confirm and cancel callbacks .
* @ param { string } title - title of the modal
* @ param { ( string | ReactElement | Array < string | ReactElement > ) } children - a single or mixed array of react elements and strings . Everything is wrapped in Discord ' s ` TextElement ` component so strings will show and render properly .
* @ param { object } [ options ] - options to modify the modal
* @ param { boolean } [ options . danger = false ] - whether the main button should be red or not
* @ param { string } [ options . confirmText = Okay ] - text for the confirmation / submit button
* @ param { string } [ options . cancelText = Cancel ] - text for the cancel button
* @ param { callable } [ options . onConfirm = NOOP ] - callback to occur when clicking the submit button
* @ param { callable } [ options . onCancel = NOOP ] - callback to occur when clicking the cancel button
* /
BdApi . showConfirmationModal = function ( title , content , options = { } ) {
2019-05-31 07:53:11 +02:00
return Modals . showConfirmationModal ( title , content , options ) ;
2019-05-28 20:19:48 +02:00
} ;
2019-06-03 22:25:08 +02:00
/ * *
* This shows a toast similar to android towards the bottom of the screen .
*
* @ param { string } content The string to show in the toast .
* @ param { object } options Options object . Optional parameter .
* @ param { string } [ options . type = "" ] Changes the type of the toast stylistically and semantically . Choices : "" , "info" , "success" , "danger" / "error" , "warning" / "warn" . Default : ""
* @ param { boolean } [ options . icon = true ] Determines whether the icon should show corresponding to the type . A toast without type will always have no icon . Default : true
* @ param { number } [ options . timeout = 3000 ] Adjusts the time ( in ms ) the toast should be shown for before disappearing automatically . Default : 3000
* @ param { boolean } [ options . forceShow = false ] Whether to force showing the toast and ignore the bd setting
* /
2019-05-28 20:19:48 +02:00
BdApi . showToast = function ( content , options = { } ) {
2019-05-31 07:53:11 +02:00
Toasts . show ( content , options ) ;
2019-05-28 20:19:48 +02:00
} ;
// Finds module
BdApi . findModule = function ( filter ) {
2019-05-30 07:06:17 +02:00
return WebpackModules . getModule ( filter ) ;
2019-05-28 20:19:48 +02:00
} ;
// Finds module
BdApi . findAllModules = function ( filter ) {
2019-05-30 07:06:17 +02:00
return WebpackModules . getModule ( filter , false ) ;
2019-05-28 20:19:48 +02:00
} ;
// Finds module
BdApi . findModuleByProps = function ( ... props ) {
2019-05-30 07:06:17 +02:00
return WebpackModules . getByProps ( ... props ) ;
2019-05-28 20:19:48 +02:00
} ;
BdApi . findModuleByPrototypes = function ( ... protos ) {
2019-05-30 07:06:17 +02:00
return WebpackModules . getByPrototypes ( ... protos ) ;
2019-05-28 20:19:48 +02:00
} ;
BdApi . findModuleByDisplayName = function ( name ) {
2019-05-30 07:06:17 +02:00
return WebpackModules . getByDisplayName ( name ) ;
2019-05-28 20:19:48 +02:00
} ;
// Gets react instance
BdApi . getInternalInstance = function ( node ) {
2021-03-01 01:19:25 +01:00
// if (!(node instanceof window.jQuery) && !(node instanceof Element)) return undefined;
// if (node instanceof jQuery) node = node[0];
2019-06-20 04:19:34 +02:00
return Utilities . getReactInstance ( node ) ;
2019-05-28 20:19:48 +02:00
} ;
// Gets data
BdApi . loadData = function ( pluginName , key ) {
return DataStore . getPluginData ( pluginName , key ) ;
} ;
BdApi . getData = BdApi . loadData ;
// Sets data
BdApi . saveData = function ( pluginName , key , data ) {
return DataStore . setPluginData ( pluginName , key , data ) ;
} ;
BdApi . setData = BdApi . saveData ;
// Deletes data
BdApi . deleteData = function ( pluginName , key ) {
return DataStore . deletePluginData ( pluginName , key ) ;
} ;
// Patches other functions
2020-07-19 01:01:49 +02:00
// BdApi.monkeyPatch = function(what, methodName, options) {
// const {before, after, instead, once = false, silent = false, force = false} = options;
// const displayName = options.displayName || what.displayName || what.name || what.constructor.displayName || what.constructor.name;
// if (!silent) console.log("patch", methodName, "of", displayName); // eslint-disable-line no-console
// if (!what[methodName]) {
// if (force) what[methodName] = function() {};
// else return console.error(methodName, "does not exist for", displayName); // eslint-disable-line no-console
// }
// const origMethod = what[methodName];
// const cancel = () => {
// if (!silent) console.log("unpatch", methodName, "of", displayName); // eslint-disable-line no-console
// what[methodName] = origMethod;
// };
// what[methodName] = function() {
// const data = {
// thisObject: this,
// methodArguments: arguments,
// cancelPatch: cancel,
// originalMethod: origMethod,
// callOriginalMethod: () => data.returnValue = data.originalMethod.apply(data.thisObject, data.methodArguments)
// };
// if (instead) {
// const tempRet = Utilities.suppressErrors(instead, "`instead` callback of " + what[methodName].displayName)(data);
// if (tempRet !== undefined) data.returnValue = tempRet;
// }
// else {
// if (before) Utilities.suppressErrors(before, "`before` callback of " + what[methodName].displayName)(data);
// data.callOriginalMethod();
// if (after) Utilities.suppressErrors(after, "`after` callback of " + what[methodName].displayName)(data);
// }
// if (once) cancel();
// return data.returnValue;
// };
// what[methodName].__monkeyPatched = true;
// if (!what[methodName].__originalMethod) what[methodName].__originalMethod = origMethod;
// what[methodName].displayName = "patched " + (what[methodName].displayName || methodName);
// return cancel;
// };
2019-05-28 20:19:48 +02:00
BdApi . monkeyPatch = function ( what , methodName , options ) {
2020-10-06 23:44:10 +02:00
const { before , after , instead , once = false , callerId = "BdApi" } = options ;
2020-07-19 01:01:49 +02:00
const patchType = before ? "before" : after ? "after" : instead ? "instead" : "" ;
if ( ! patchType ) return Logger . err ( "BdApi" , "Must provide one of: after, before, instead" ) ;
const originalMethod = what [ methodName ] ;
const data = {
originalMethod : originalMethod ,
callOriginalMethod : ( ) => data . originalMethod . apply ( data . thisObject , data . methodArguments )
2019-09-27 04:50:53 +02:00
} ;
2020-10-06 23:44:10 +02:00
data . cancelPatch = Patcher [ patchType ] ( callerId , what , methodName , ( thisObject , args , returnValue ) => {
2020-07-19 01:01:49 +02:00
data . thisObject = thisObject ;
data . methodArguments = args ;
data . returnValue = returnValue ;
try {
2021-05-22 07:51:16 +02:00
const patchReturn = Reflect . apply ( options [ patchType ] , null , [ data ] ) ;
2020-07-19 01:01:49 +02:00
if ( once ) data . cancelPatch ( ) ;
2021-05-22 07:51:16 +02:00
return patchReturn ;
2019-09-27 04:50:53 +02:00
}
2020-07-19 01:01:49 +02:00
catch ( err ) {
2020-10-06 23:44:10 +02:00
Logger . err ( ` ${ callerId } :monkeyPatch ` , ` Error in the ${ patchType } of ${ methodName } ` ) ;
2019-09-27 04:50:53 +02:00
}
2020-07-19 01:01:49 +02:00
} ) ;
2020-10-06 23:44:10 +02:00
return data . cancelPatch ;
2019-05-28 20:19:48 +02:00
} ;
// Event when element is removed
BdApi . onRemoved = function ( node , callback ) {
2019-05-28 23:27:25 +02:00
return Utilities . onRemoved ( node , callback ) ;
2019-05-28 20:19:48 +02:00
} ;
// Wraps function in try..catch
BdApi . suppressErrors = function ( method , message ) {
2019-05-28 23:27:25 +02:00
return Utilities . suppressErrors ( method , message ) ;
2019-05-28 20:19:48 +02:00
} ;
// Tests for valid JSON
BdApi . testJSON = function ( data ) {
2019-05-28 23:27:25 +02:00
return Utilities . testJSON ( data ) ;
2019-05-28 20:19:48 +02:00
} ;
2020-07-16 07:42:56 +02:00
BdApi . isSettingEnabled = function ( collection , category , id ) {
return Settings . get ( collection , category , id ) ;
} ;
BdApi . enableSetting = function ( collection , category , id ) {
return Settings . set ( collection , category , id , true ) ;
} ;
BdApi . disableSetting = function ( collection , category , id ) {
return Settings . set ( collection , category , id , false ) ;
} ;
BdApi . toggleSetting = function ( collection , category , id ) {
return Settings . set ( collection , category , id , ! Settings . get ( collection , category , id ) ) ;
2020-04-24 00:12:01 +02:00
} ;
2019-05-28 20:19:48 +02:00
// Gets data
BdApi . getBDData = function ( key ) {
return DataStore . getBDData ( key ) ;
} ;
// Sets data
BdApi . setBDData = function ( key , data ) {
return DataStore . setBDData ( key , data ) ;
2019-05-28 23:27:25 +02:00
} ;
2021-07-09 00:20:28 +02:00
// Opens a filesystem dialog
BdApi . openDialog = async function ( options ) {
const data = await ipc . openDialog ( options ) ;
if ( data . error ) throw new Error ( data . error ) ;
return data ;
} ;
2020-07-16 07:42:56 +02:00
const makeAddonAPI = ( manager ) => new class AddonAPI {
2020-07-18 04:24:20 +02:00
get folder ( ) { return manager . addonFolder ; }
2020-07-16 07:42:56 +02:00
isEnabled ( idOrFile ) { return manager . isEnabled ( idOrFile ) ; }
enable ( idOrAddon ) { return manager . enableAddon ( idOrAddon ) ; }
disable ( idOrAddon ) { return manager . disableAddon ( idOrAddon ) ; }
toggle ( idOrAddon ) { return manager . toggleAddon ( idOrAddon ) ; }
reload ( idOrFileOrAddon ) { return manager . reloadAddon ( idOrFileOrAddon ) ; }
2020-07-19 01:01:49 +02:00
get ( idOrFile ) { return manager . getAddon ( idOrFile ) ; }
getAll ( ) { return manager . addonList . map ( a => manager . getAddon ( a . id ) ) ; }
2020-04-24 00:12:01 +02:00
} ;
2020-07-16 07:42:56 +02:00
BdApi . Plugins = makeAddonAPI ( PluginManager ) ;
BdApi . Themes = makeAddonAPI ( ThemeManager ) ;
2020-07-19 01:01:49 +02:00
BdApi . Patcher = {
patch : ( caller , moduleToPatch , functionName , callback , options = { } ) => {
if ( typeof ( caller ) !== "string" ) return Logger . err ( "BdApi.Patcher" , "Parameter 0 of patch must be a string representing the caller" ) ;
if ( options . type !== "before" && options . type !== "instead" && options . type !== "after" ) return Logger . err ( "BdApi.Patcher" , "options.type must be one of: before, instead, after" ) ;
return Patcher . pushChildPatch ( caller , moduleToPatch , functionName , callback , options ) ;
} ,
before : ( caller , moduleToPatch , functionName , callback , options = { } ) => BdApi . Patcher . patch ( caller , moduleToPatch , functionName , callback , Object . assign ( options , { type : "before" } ) ) ,
instead : ( caller , moduleToPatch , functionName , callback , options = { } ) => BdApi . Patcher . patch ( caller , moduleToPatch , functionName , callback , Object . assign ( options , { type : "instead" } ) ) ,
after : ( caller , moduleToPatch , functionName , callback , options = { } ) => BdApi . Patcher . patch ( caller , moduleToPatch , functionName , callback , Object . assign ( options , { type : "after" } ) ) ,
unpatchAll : ( caller ) => {
if ( typeof ( caller ) !== "string" ) return Logger . err ( "BdApi.Patcher" , "Parameter 0 of unpatchAll must be a string representing the caller" ) ;
return Patcher . unpatchAll ( caller ) ;
}
} ;
2020-07-16 07:42:56 +02:00
2020-07-16 23:17:02 +02:00
Object . freeze ( BdApi ) ;
Object . freeze ( BdApi . Plugins ) ;
Object . freeze ( BdApi . Themes ) ;
2020-07-19 01:01:49 +02:00
Object . freeze ( BdApi . Patcher ) ;
2020-07-16 23:17:02 +02:00
2019-05-28 23:27:25 +02:00
export default BdApi ;