2019-12-21 21:16:47 +01:00
//META{"name":"XenoLib","source":"https://github.com/1Lighty/BetterDiscordPlugins/blob/master/Plugins/1XenoLib.plugin.js/"}*//
/ * @ c c _ o n
@ if ( @ _jscript )
// Offer to self-install for clueless users that try to run this directly.
var shell = WScript . CreateObject ( 'WScript.Shell' ) ;
var fs = new ActiveXObject ( 'Scripting.FileSystemObject' ) ;
var pathPlugins = shell . ExpandEnvironmentStrings ( '%APPDATA%\\BetterDiscord\\plugins' ) ;
var pathSelf = WScript . ScriptFullName ;
// Put the user at ease by addressing them in the first person
shell . Popup ( 'It looks like you\'ve mistakenly tried to run me directly. \n(Don\'t do that!)' , 0 , 'I\'m a plugin for BetterDiscord' , 0x30 ) ;
if ( fs . GetParentFolderName ( pathSelf ) === fs . GetAbsolutePathName ( pathPlugins ) ) {
shell . Popup ( 'I\'m in the correct folder already.\nJust reload Discord with Ctrl+R.' , 0 , 'I\'m already installed' , 0x40 ) ;
} else if ( ! fs . FolderExists ( pathPlugins ) ) {
shell . Popup ( 'I can\'t find the BetterDiscord plugins folder.\nAre you sure it\'s even installed?' , 0 , 'Can\'t install myself' , 0x10 ) ;
} else if ( shell . Popup ( 'Should I copy myself to BetterDiscord\'s plugins folder for you?' , 0 , 'Do you need some help?' , 0x34 ) === 6 ) {
fs . CopyFile ( pathSelf , fs . BuildPath ( pathPlugins , fs . GetFileName ( pathSelf ) ) , true ) ;
// Show the user where to put plugins in the future
shell . Exec ( 'explorer ' + pathPlugins ) ;
shell . Popup ( 'I\'m installed!\nJust reload Discord with Ctrl+R.' , 0 , 'Successfully installed' , 0x40 ) ;
}
WScript . Quit ( ) ;
@ else @ * /
/ *
2019-12-24 12:58:53 +01:00
* Copyright © 2019 - 2020 , _Lighty _
2019-12-21 21:16:47 +01:00
* All rights reserved .
* Code may not be redistributed , modified or otherwise taken without explicit permission .
* /
var XenoLib = ( ( ) => {
2019-12-24 12:58:53 +01:00
/* Setup */
const config = {
main : 'index.js' ,
info : {
name : 'XenoLib' ,
authors : [
{
name : 'Lighty' ,
discord _id : '239513071272329217' ,
github _username : 'LightyPon' ,
twitter _username : ''
}
] ,
version : '1.2.3' ,
description : 'Simple library to complement plugins with shared code without lowering performance.' ,
github : 'https://github.com/1Lighty' ,
github _raw : 'https://raw.githubusercontent.com/1Lighty/BetterDiscordPlugins/master/Plugins/1XenoLib.plugin.js'
} ,
changelog : [
{
title : 'Hopefully fixed' ,
type : 'fixed' ,
items : [ 'Fixed BDFDB causing issues with patching context menus' ]
}
]
} ;
/* Build */
const buildPlugin = ( [ Plugin , Api ] ) => {
const { ContextMenu , EmulatedTooltip , Toasts , Settings , Popouts , Modals , Utilities , WebpackModules , Filters , DiscordModules , ColorConverter , DOMTools , DiscordClasses , DiscordSelectors , ReactTools , ReactComponents , DiscordAPI , Logger , Patcher , PluginUpdater , PluginUtilities , DiscordClassModules , Structs } = Api ;
const { React , ModalStack , ContextMenuActions , ContextMenuItem , ContextMenuItemsGroup , ReactDOM , ChannelStore , GuildStore , UserStore , DiscordConstants , Dispatcher , GuildMemberStore , GuildActions , PrivateChannelActions , LayerManager , InviteActions } = DiscordModules ;
const ContextMenuSubMenuItem = WebpackModules . getByDisplayName ( 'FluxContainer(SubMenuItem)' ) ;
if ( global . XenoLib ) global . XenoLib . shutdown ( ) ;
const XenoLib = { } ;
XenoLib . shutdown = ( ) => {
Patcher . unpatchAll ( ) ;
PluginUtilities . removeStyle ( 'XenoLib-CSS' ) ;
if ( global . BDEvents ) BDEvents . off ( 'plugin-unloaded' , listener ) ;
2019-12-21 21:16:47 +01:00
} ;
2019-12-24 12:58:53 +01:00
PluginUtilities . addStyle (
'XenoLib-CSS' ,
`
2019-12-21 21:16:47 +01:00
. xenoLib - color - picker . xenoLib - button {
width : 34 px ;
min - height : 38 px ;
}
. xenoLib - color - picker . xenoLib - button : hover {
width : 128 px ;
}
. xenoLib - color - picker . xenoLib - button . text - 2 sI5Sd {
opacity : 0 ;
transform : translate3d ( 200 % , 0 , 0 ) ;
}
. xenoLib - color - picker . xenoLib - button : hover . text - 2 sI5Sd {
opacity : 1 ;
transform : translateZ ( 0 ) ;
}
. xenoLib - button - icon {
left : 50 % ;
top : 50 % ;
position : absolute ;
margin - left : - 12 px ;
margin - top : - 8 px ;
width : 24 px ;
height : 24 px ;
opacity : 1 ;
transform : translateZ ( 0 ) ;
transition : opacity . 2 s ease - in - out , transform . 2 s ease - in - out , - webkit - transform . 2 s ease - in - out ;
}
. xenoLib - button - icon . xenoLib - revert > svg {
width : 24 px ;
height : 24 px ;
}
. xenoLib - button - icon . xenoLib - revert {
margin - top : - 12 px ;
}
. xenoLib - button : hover . xenoLib - button - icon {
opacity : 0 ;
transform : translate3d ( - 200 % , 0 , 0 ) ;
}
`
2019-12-24 12:58:53 +01:00
) ;
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
XenoLib . getUser = WebpackModules . getByProps ( 'getUser' , 'acceptAgreements' ) . getUser ;
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
XenoLib . getClass = arg => {
const args = arg . split ( ' ' ) ;
return WebpackModules . getByProps ( ... args ) [ args [ args . length - 1 ] ] ;
} ;
XenoLib . getSingleClass = arg => XenoLib . getClass ( arg ) . split ( ' ' ) [ 0 ] ;
XenoLib . joinClassNames = WebpackModules . getModule ( e => e . default && e . default . default ) ;
XenoLib . authorId = '239513071272329217' ;
const requestUser = ( ) =>
XenoLib . getUser ( XenoLib . authorId )
. then ( user => ( XenoLib . author = user ) )
. catch ( ( ) => setTimeout ( requestUser , 1 * 60 * 1000 ) ) ;
if ( UserStore . getUser ( XenoLib . authorId ) ) XenoLib . author = UserStore . getUser ( XenoLib . authorId ) ;
else requestUser ( ) ;
XenoLib . supportServerId = '389049952732446731' ;
try {
if ( V2C _PluginCard && V2C _ThemeCard ) {
const LinkClassname = XenoLib . joinClassNames ( XenoLib . getClass ( 'anchorUnderlineOnHover anchor' ) , XenoLib . getClass ( 'anchor anchorUnderlineOnHover' ) , 'bda-author' ) ;
const handlePatch = ( _this , _ , ret ) => {
const author = Utilities . getNestedProp ( ret , 'props.children.0.props.children.0.props.children.4' ) ;
const footer = Utilities . getNestedProp ( ret , 'props.children.2.props.children.0.props.children' ) ;
if ( ! author || typeof author . props . children !== 'string' || author . props . children . indexOf ( 'Lighty' ) === - 1 ) return ;
const onClick = ( ) => {
if ( DiscordAPI . currentUser . id === XenoLib . authorId ) return ;
PrivateChannelActions . ensurePrivateChannel ( DiscordAPI . currentUser . id , XenoLib . authorId ) . then ( ( ) => {
PrivateChannelActions . openPrivateChannel ( DiscordAPI . currentUser . id , XenoLib . authorId ) ;
LayerManager . popLayer ( ) ;
2019-12-21 21:16:47 +01:00
} ) ;
2019-12-24 12:58:53 +01:00
} ;
if ( author . props . children === 'Lighty' ) {
author . type = 'a' ;
author . props . className = LinkClassname ;
author . props . onClick = onClick ;
} else {
const idx = author . props . children . indexOf ( 'Lighty' ) ;
const pre = author . props . children . slice ( 0 , idx ) ;
const post = author . props . children . slice ( idx + 6 ) ;
author . props . children = [
pre ,
React . createElement (
'a' ,
{
className : LinkClassname ,
onClick
} ,
'Lighty'
) ,
post
] ;
}
if ( footer ) {
footer . push (
' | ' ,
React . createElement ( 'a' , { className : 'bda-link' , href : 'https://paypal.me/lighty13' , target : '_blank' } , 'Paypal' ) ,
' | ' ,
React . createElement ( 'a' , { className : 'bda-link' , href : 'https://ko-fi.com/lighty_' , target : '_blank' } , 'Ko-fi' ) ,
' | ' ,
React . createElement (
'a' ,
{
className : 'bda-link' ,
onClick : ( ) => {
LayerManager . popLayer ( ) ;
if ( GuildStore . getGuild ( XenoLib . supportServerId ) ) GuildActions . transitionToGuildSync ( XenoLib . supportServerId ) ;
else InviteActions . openNativeAppModal ( 'NYvWdN5' ) ;
}
} ,
'Support Server'
)
) ;
if ( _this . props . plugin . showChangelog || _this . props . plugin . getChanges ) {
footer . push (
' | ' ,
React . createElement (
'a' ,
{
className : 'bda-link' ,
onClick : ( ) => {
if ( _this . props . plugin . showChangelog ) _this . props . plugin . showChangelog ( ) ;
else Modals . showChangelogModal ( _this . props . plugin . getName ( ) + ' Changelog' , _this . props . plugin . getVersion ( ) , _this . props . plugin . getChanges ( ) ) ;
}
} ,
'Changelog'
)
) ;
2019-12-21 21:16:47 +01:00
}
2019-12-24 12:58:53 +01:00
}
2019-12-21 21:16:47 +01:00
} ;
2019-12-24 12:58:53 +01:00
Patcher . after ( V2C _PluginCard . prototype , 'render' , handlePatch ) ;
Patcher . after ( V2C _ThemeCard . prototype , 'render' , handlePatch ) ;
}
} catch ( e ) { }
XenoLib . _ _contextPatches = [ ] ;
XenoLib . _ _contextPatches . _ _contextPatched = false ;
const existingContextMenus = [ 'NativeContextMenu' , 'GuildRoleContextMenu' , 'MessageContextMenu' , 'DeveloperContextMenu' , 'ScreenshareContextMenu' ] ;
const ContextMenuClassname = XenoLib . getSingleClass ( 'subMenuContext contextMenu' ) ;
const getContextMenuChild = val => {
if ( ! val ) return ;
const isValid = obj => obj . type === 'div' && obj . props && typeof obj . props . className === 'string' && obj . props . className . indexOf ( ContextMenuClassname ) !== - 1 && Array . isArray ( Utilities . getNestedProp ( obj , 'props.children.props.children' ) ) ;
if ( isValid ( val ) ) return val . props . children ;
const children = Utilities . getNestedProp ( val , 'props.children' ) ;
if ( ! children ) return ;
if ( Array . isArray ( children ) ) {
for ( let i = 0 ; i < children . length ; i ++ ) {
const ret = getContextMenuChild ( children [ i ] ) ;
if ( ret ) return ret . props . children ;
2019-12-21 21:16:47 +01:00
}
2019-12-24 12:58:53 +01:00
} else if ( isValid ( children ) ) return children . props . children ;
} ;
function patchAllContextMenus ( ) {
const handleContextMenu = ( _this , ret ) => {
const menuGroups = getContextMenuChild ( ret ) || ret ;
if ( ! menuGroups ) return Logger . warn ( 'Failed to get context menu groups!' , _this , ret ) ;
XenoLib . _ _contextPatches . forEach ( e => {
try {
e ( _this , menuGroups ) ;
} catch ( e ) {
Logger . stacktrace ( 'Error with patched context menu' , e ) ;
}
} ) ;
} ;
existingContextMenus . forEach ( type => {
const module = WebpackModules . getByDisplayName ( type ) ;
if ( ! module ) return Logger . warn ( ` Failed to find ContextMenu type ` , type ) ;
Patcher . after ( module . prototype , 'render' , ( _this , _ , ret ) => handleContextMenu ( _this , ret ) ) ;
} ) ;
function getModule ( regex ) {
const modules = WebpackModules . getAllModules ( ) ;
for ( const index in modules ) {
if ( ! modules . hasOwnProperty ( index ) ) continue ;
const module = modules [ index ] ;
if ( ! module . exports || ! module . exports . _ _esModule || ! module . exports . default ) continue ;
/* if BDFDB was inited before us, patch the already patched function */
if ( module . exports . default . toString ( ) . search ( regex ) !== - 1 || ( module . exports . default . isBDFDBpatched && module . exports . default . originalsource . toString ( ) . search ( regex ) !== - 1 ) ) return module ;
2019-12-21 21:16:47 +01:00
}
2019-12-24 12:58:53 +01:00
}
const somemoremenus = [ getModule ( /case \w.ContextMenuTypes.CHANNEL_LIST_TEXT/ ) , getModule ( /case \w.ContextMenuTypes.GUILD_CHANNEL_LIST/ ) , getModule ( /case \w.ContextMenuTypes.USER_CHANNEL_MEMBERS/ ) ] ;
somemoremenus . forEach ( menu => {
if ( ! menu ) return Logger . warn ( 'Special context menu is undefined!' ) ;
const origDef = menu . exports . default ;
const originalFunc = Utilities . getNestedProp ( menu , 'exports.BDFDBpatch.default.originalMethod' ) || menu . exports . default ;
Patcher . after ( menu . exports , 'default' , ( _ , [ props ] , ret ) => handleContextMenu ( { props } , ret ) ) ;
/ * m a k e i t f r i e n d l y t o o t h e r p l u g i n s a n d l i b r a r i e s t h a t s e a r c h b y s t r i n g
note : removing this makes BDFDB shit itself
2019-12-21 21:16:47 +01:00
* /
2019-12-24 12:58:53 +01:00
Patcher . instead ( menu . exports . default , 'toString' , ( _ , args , _ _ ) => originalFunc . toString ( ... args ) ) ;
/ * i f B D F D B a l r e a d y p a t c h e d i t , p a t c h t h e f u n c t i o n B D F D B i s s t o r i n g i n c a s e i t d e c i d e s t o u n a p t c h
this is to prevent BDFDB from removing our patch
this function is never called in BDFDB , it ' s only stored for restore
* /
if ( origDef . isBDFDBpatched && menu . exports . BDFDBpatch && typeof menu . exports . BDFDBpatch . default . originalMethod === 'function' ) {
Patcher . after ( menu . exports . BDFDBpatch . default , 'originalMethod' , ( _ , [ props ] , ret ) => handleContextMenu ( { props } , ret ) ) ;
/ * m a k e i t f r i e n d l y t o o t h e r p l u g i n s a n d l i b r a r i e s t h a t s e a r c h b y s t r i n g
note : removing this makes BDFDB shit itself
* /
Patcher . instead ( menu . exports . BDFDBpatch . default . originalMethod , 'toString' , ( _ , args , _ _ ) => originalFunc . toString ( ... args ) ) ;
2019-12-21 21:16:47 +01:00
}
2019-12-24 12:58:53 +01:00
} ) ;
XenoLib . _ _contextPatches . _ _contextPatched = true ;
}
XenoLib . patchContext = callback => {
XenoLib . _ _contextPatches . push ( callback ) ;
} ;
class ContextMenuWrapper extends React . PureComponent {
render ( ) {
return React . createElement ( 'div' , { className : DiscordClasses . ContextMenu . contextMenu } , this . props . menu ) ;
}
}
XenoLib . createSharedContext = ( menuCreation , props , type ) => {
if ( props . _ _XenoLib _ContextMenus ) {
props . _ _XenoLib _ContextMenus . push ( menuCreation ) ;
} else {
props . _ _XenoLib _ContextMenus = [ menuCreation ] ;
const oOnContextMenu = props . onContextMenu ;
props . onContextMenu = e => ( typeof oOnContextMenu === 'function' && oOnContextMenu ( e ) , ContextMenuActions . openContextMenu ( e , e => React . createElement ( ContextMenuWrapper , { menu : props . _ _XenoLib _ContextMenus . map ( m => m ( ) ) , type } ) ) ) ;
}
} ;
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
if ( global . XenoLib ) if ( global . XenoLib . _ _contextPatches && global . XenoLib . _ _contextPatches . length ) XenoLib . _ _contextPatches . push ( ... global . XenoLib . _ _contextPatches ) ;
XenoLib . unpatchContext = callback => XenoLib . _ _contextPatches . splice ( XenoLib . _ _contextPatches . indexOf ( callback ) , 1 ) ;
patchAllContextMenus ( ) ; /* prevent BDFDB from being a gay piece of crap by patching it first */
XenoLib . createContextMenuItem = ( label , action , options = { } ) =>
React . createElement ( ContextMenuItem , {
label ,
action : ( ) => {
ContextMenuActions . closeContextMenu ( ) ;
action ( ) ;
} ,
... options
} ) ;
XenoLib . createContextMenuSubMenu = ( label , items , options = { } ) =>
React . createElement ( ContextMenuSubMenuItem , {
label ,
render : items ,
... options
} ) ;
XenoLib . createContextMenuGroup = ( children , options ) => React . createElement ( ContextMenuItemsGroup , { children , ... options } ) ;
XenoLib . DiscordUtils = WebpackModules . getByProps ( 'bindAll' , 'debounce' ) ;
const dialog = require ( 'electron' ) . remote . dialog ;
const showSaveDialog = dialog . showSaveDialogSync || dialog . showSaveDialog ;
const showOpenDialog = dialog . showOpenDialogSync || dialog . showOpenDialog ;
XenoLib . ReactComponents = { } ;
XenoLib . ReactComponents . ButtonOptions = WebpackModules . getByProps ( 'ButtonLink' ) ;
XenoLib . ReactComponents . Button = XenoLib . ReactComponents . ButtonOptions . default ;
const MultiInputClassname = XenoLib . joinClassNames ( DiscordClasses . BasicInputs . input . value , XenoLib . getClass ( 'multiInput' ) ) ;
const MultiInputFirstClassname = XenoLib . getClass ( 'multiInputFirst' ) ;
const MultiInputFieldClassname = XenoLib . getClass ( 'multiInputField' ) ;
const ErrorMessageClassname = XenoLib . getClass ( 'input errorMessage' ) ;
const ErrorClassname = XenoLib . getClass ( 'input error' ) ;
const DelayedCall = WebpackModules . getByProps ( 'DelayedCall' ) . DelayedCall ;
const FsModule = require ( 'fs' ) ;
/ * *
* @ interface
* @ name module : FilePicker
* @ property { string } path
* @ property { string } placeholder
* @ property { Function } onChange
* @ property { object } properties
* @ property { bool } nullOnInvalid
* /
XenoLib . ReactComponents . FilePicker = class FilePicker extends React . PureComponent {
constructor ( props ) {
super ( props ) ;
this . state = {
multiInputFocused : false ,
path : props . path ,
error : null
2019-12-21 21:16:47 +01:00
} ;
2019-12-24 12:58:53 +01:00
XenoLib . DiscordUtils . bindAll ( this , [ 'handleOnBrowse' , 'handleChange' ] ) ;
this . delayedCallVerifyPath = new DelayedCall ( 500 , ( ) =>
FsModule . access ( this . state . path , FsModule . constants . W _OK , error => {
const invalid = ( error && error . message . match ( /.*: (.*), access '/ ) [ 1 ] ) || null ;
this . setState ( { error : invalid } ) ;
if ( invalid && this . props . nullOnInvalid ) this . props . onChange ( null ) ;
} )
) ;
}
handleOnBrowse ( ) {
const path = showOpenDialog ( { title : this . props . title , properties : this . props . properties } ) ;
if ( Array . isArray ( path ) && path . length ) this . handleChange ( path [ 0 ] ) ;
}
handleChange ( path ) {
this . props . onChange ( path ) ;
this . setState ( { path } ) ;
this . delayedCallVerifyPath . delay ( ) ;
}
render ( ) {
const n = { } ;
n [ DiscordClasses . BasicInputs . focused ] = this . state . multiInputFocused ;
n [ ErrorClassname ] = ! ! this . state . error ;
return React . createElement (
'div' ,
{ className : DiscordClasses . BasicInputs . inputWrapper , style : { width : '100%' } } ,
React . createElement (
'div' ,
{ className : XenoLib . joinClassNames ( MultiInputClassname , n ) } ,
React . createElement ( DiscordModules . Textbox , {
value : this . state . path ,
placeholder : this . props . placeholder ,
onChange : this . handleChange ,
onFocus : ( ) => this . setState ( { multiInputFocused : true } ) ,
onBlur : ( ) => this . setState ( { multiInputFocused : false } ) ,
autoFocus : false ,
className : MultiInputFirstClassname ,
inputClassName : MultiInputFieldClassname
} ) ,
React . createElement ( XenoLib . ReactComponents . Button , { onClick : this . handleOnBrowse , color : ( ! ! this . state . error && XenoLib . ReactComponents . ButtonOptions . ButtonColors . RED ) || XenoLib . ReactComponents . ButtonOptions . ButtonColors . GREY , look : XenoLib . ReactComponents . ButtonOptions . ButtonLooks . GHOST , size : XenoLib . ReactComponents . Button . Sizes . MEDIUM } , 'Browse' )
) ,
! ! this . state . error && React . createElement ( 'div' , { className : ErrorMessageClassname } , 'Error: ' , this . state . error )
) ;
}
} ;
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
/ * *
* @ param { string } name - name label of the setting
* @ param { string } note - help / note to show underneath or above the setting
* @ param { string } value - current hex color
* @ param { callable } onChange - callback to perform on setting change , callback receives hex string
* @ param { object } [ options ] - object of options to give to the setting
* @ param { boolean } [ options . disabled = false ] - should the setting be disabled
* @ param { Array < number > } [ options . colors = presetColors ] - preset list of colors
* @ author Zerebos , from his library ZLibrary
* /
const FormItem = WebpackModules . getByDisplayName ( 'FormItem' ) ;
const DeprecatedModal = WebpackModules . getByDisplayName ( 'DeprecatedModal' ) ;
const ModalContainerClassname = XenoLib . getClass ( 'mobile container' ) ;
const ModalContentClassname = XenoLib . getClass ( 'mobile container content' ) ;
const Icon = WebpackModules . getByDisplayName ( 'Icon' ) ;
class ColorPickerModal extends React . PureComponent {
constructor ( props ) {
super ( props ) ;
this . state = { value : props . value } ;
XenoLib . DiscordUtils . bindAll ( this , [ 'handleChange' ] ) ;
}
handleChange ( value ) {
this . setState ( { value } ) ;
this . props . onChange ( ColorConverter . int2hex ( value ) ) ;
}
render ( ) {
return React . createElement (
DeprecatedModal ,
{ className : ModalContainerClassname , tag : 'form' , onSubmit : this . handleSubmit , size : '' } ,
React . createElement (
DeprecatedModal . Content ,
{ className : ModalContentClassname } ,
React . createElement (
FormItem ,
{ className : DiscordClasses . Margins . marginTop20 } ,
React . createElement ( WebpackModules . getByDisplayName ( 'ColorPicker' ) , {
defaultColor : this . props . defaultColor ,
colors : [ 16711680 , 16746496 , 16763904 , 13434624 , 65314 , 65484 , 61183 , 43775 , 26367 , 8913151 , 16711918 , 16711782 , 11730944 , 11755264 , 11767552 , 9417472 , 45848 , 45967 , 42931 , 30643 , 18355 , 6226099 , 11731111 , 11731015 ] ,
value : this . state . value ,
onChange : this . handleChange
} )
)
)
) ;
}
}
class ColorPicker extends React . PureComponent {
constructor ( props ) {
super ( props ) ;
this . state = {
error : null ,
value : props . value ,
multiInputFocused : false
2019-12-21 21:16:47 +01:00
} ;
2019-12-24 12:58:53 +01:00
XenoLib . DiscordUtils . bindAll ( this , [ 'handleChange' , 'handleColorPicker' , 'handleReset' ] ) ;
}
handleChange ( value ) {
if ( ! value . length ) {
this . state . error = 'You must input a hex string' ;
} else if ( ! ColorConverter . isValidHex ( value ) ) {
this . state . error = 'Invalid hex string' ;
} else {
this . state . error = null ;
}
this . setState ( { value } ) ;
this . props . onChange ( ! value . length || ! ColorConverter . isValidHex ( value ) ? this . props . defaultColor : value ) ;
}
handleColorPicker ( ) {
ModalStack . push ( e => React . createElement ( ColorPickerModal , { ... e , defaultColor : ColorConverter . hex2int ( this . props . defaultColor ) , value : ColorConverter . hex2int ( this . props . value ) , onChange : this . handleChange } ) ) ;
}
handleReset ( ) {
this . handleChange ( this . props . defaultColor ) ;
}
render ( ) {
const n = { } ;
n [ DiscordClasses . BasicInputs . focused ] = this . state . multiInputFocused ;
n [ ErrorClassname ] = ! ! this . state . error ;
return React . createElement (
'div' ,
{ className : XenoLib . joinClassNames ( DiscordClasses . BasicInputs . inputWrapper . value , 'xenoLib-color-picker' ) , style : { width : '100%' } } ,
React . createElement (
'div' ,
{ className : XenoLib . joinClassNames ( MultiInputClassname , n ) } ,
React . createElement ( 'div' , {
className : XenoLib . ReactComponents . Button . Sizes . SMALL ,
style : {
backgroundColor : this . state . value ,
height : 38
}
} ) ,
React . createElement ( DiscordModules . Textbox , {
value : this . state . value ,
placeholder : 'Hex color' ,
onChange : this . handleChange ,
onFocus : ( ) => this . setState ( { multiInputFocused : true } ) ,
onBlur : ( ) => this . setState ( { multiInputFocused : false } ) ,
autoFocus : false ,
className : MultiInputFirstClassname ,
inputClassName : MultiInputFieldClassname
} ) ,
React . createElement (
XenoLib . ReactComponents . Button ,
{
onClick : this . handleColorPicker ,
color : ( ! ! this . state . error && XenoLib . ReactComponents . ButtonOptions . ButtonColors . RED ) || XenoLib . ReactComponents . ButtonOptions . ButtonColors . GREY ,
look : XenoLib . ReactComponents . ButtonOptions . ButtonLooks . GHOST ,
size : XenoLib . ReactComponents . Button . Sizes . MIN ,
className : 'xenoLib-button button-34kXw5 button-3tQuzi'
} ,
React . createElement ( 'span' , { className : 'text-2sI5Sd' } , 'Color picker' ) ,
React . createElement (
'span' ,
{
className : 'xenoLib-button-icon'
} ,
React . createElement ( Icon , {
name : 'Dropper'
} )
)
) ,
React . createElement (
XenoLib . ReactComponents . Button ,
{
onClick : this . handleReset ,
color : ( ! ! this . state . error && XenoLib . ReactComponents . ButtonOptions . ButtonColors . RED ) || XenoLib . ReactComponents . ButtonOptions . ButtonColors . GREY ,
look : XenoLib . ReactComponents . ButtonOptions . ButtonLooks . GHOST ,
size : XenoLib . ReactComponents . Button . Sizes . MIN ,
className : 'xenoLib-button button-34kXw5 button-3tQuzi'
} ,
React . createElement ( 'span' , { className : 'text-2sI5Sd' } , 'Reset' ) ,
React . createElement (
'span' ,
{
className : 'xenoLib-button-icon xenoLib-revert'
} ,
React . createElement ( Icon , {
name : 'ClockReverse'
} )
)
)
) ,
! ! this . state . error && React . createElement ( 'div' , { className : ErrorMessageClassname } , 'Error: ' , this . state . error )
) ;
}
}
XenoLib . Settings = { } ;
XenoLib . Settings . ColorPicker = class ColorPickerSettingField extends Settings . SettingField {
constructor ( name , note , value , onChange , options = { } ) {
super ( name , note , onChange , ColorPicker , {
disabled : options . disabled ? true : false ,
onChange : reactElement => color => {
this . onChange ( color ) ;
} ,
defaultColor : typeof options . defaultColor !== 'undefined' ? options . defaultColor : ColorConverter . int2hex ( DiscordConstants . DEFAULT _ROLE _COLOR ) ,
value
} ) ;
}
} ;
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
XenoLib . loadData = ( name , key , defaultData ) => {
try {
return Object . assign ( defaultData ? Utilities . deepclone ( defaultData ) : { } , BdApi . getData ( name , key ) ) ;
} catch ( err ) {
Logger . err ( name , 'Unable to load data: ' , err ) ;
return Utilities . deepclone ( defaultData ) ;
}
} ;
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
XenoLib . changeName = ( currentName , newName ) => {
const path = require ( 'path' ) ;
const fs = require ( 'fs' ) ;
const pluginsFolder = path . dirname ( currentName ) ;
const pluginName = path . basename ( currentName ) . match ( /^[^\.]+/ ) [ 0 ] ;
if ( pluginName === newName ) return true ;
const wasEnabled = global . pluginCookie && pluginCookie [ pluginName ] ;
try {
fs . accessSync ( currentName , fs . constants . W _OK | fs . constants . R _OK ) ;
const files = fs . readdirSync ( pluginsFolder ) ;
files . forEach ( file => {
if ( ! file . startsWith ( pluginName ) || file . startsWith ( newName ) || file . indexOf ( '.plugin.js' ) !== - 1 ) return ;
fs . renameSync ( path . resolve ( pluginsFolder , file ) , path . resolve ( pluginsFolder , ` ${ newName } ${ file . match ( new RegExp ( ` ^ ${ pluginName } (.*) ` ) ) [ 1 ] } ` ) ) ;
} ) ;
fs . renameSync ( currentName , path . resolve ( pluginsFolder , ` ${ newName } .plugin.js ` ) ) ;
Toasts . success ( ` [XenoLib] ${ pluginName } file has been renamed to ${ newName } ` ) ;
if ( ! global . pluginCookie || ! global . pluginModule ) Modals . showAlertModal ( 'Plugin has been renamed' , 'Plugin has been renamed, but your client mod has a missing feature, as such, the plugin could not be enabled (if it even was enabled).' ) ;
else {
if ( ! wasEnabled ) return ;
const onLoaded = e => {
if ( e !== newName ) return ;
BDEvents . off ( 'plugin-loaded' , onLoaded ) ;
pluginModule . startPlugin ( newName ) ;
} ;
BDEvents . on ( 'plugin-loaded' , onLoaded ) ;
2019-12-21 21:16:47 +01:00
}
2019-12-24 12:58:53 +01:00
} catch ( e ) {
Logger . stacktrace ( 'There has been an issue renaming a plugin' , e ) ;
}
} ;
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
global . XenoLib = XenoLib ;
const listener = e => {
if ( e !== 'XenoLib' ) return ;
XenoLib . shutdown ( ) ;
BDEvents . off ( 'plugin-unloaded' , listener ) ;
} ;
if ( global . BDEvents ) {
BDEvents . dispatch ( 'xenolib-loaded' ) ;
BDEvents . on ( 'plugin-unloaded' , listener ) ;
}
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
XenoLib . changeName ( _ _filename , '1XenoLib' ) ; /* prevent user from changing libs filename */
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
return class XenoLib extends Plugin {
onStart ( ) {
Toasts . info ( 'Starting me does nothing :)' ) ;
}
get name ( ) {
return config . info . name ;
}
get short ( ) {
let string = '' ;
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
for ( let i = 0 , len = config . info . name . length ; i < len ; i ++ ) {
const char = config . info . name [ i ] ;
if ( char === char . toUpperCase ( ) ) string += char ;
}
return string ;
}
get author ( ) {
return config . info . authors . map ( author => author . name ) . join ( ', ' ) ;
}
get version ( ) {
return config . info . version ;
}
get description ( ) {
return config . info . description ;
}
2019-12-21 21:16:47 +01:00
} ;
2019-12-24 12:58:53 +01:00
} ;
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
/* Finalize */
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
return ! global . ZeresPluginLibrary
? class {
getName ( ) {
return this . name . replace ( /\s+/g , '' ) ;
}
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
getAuthor ( ) {
return this . author ;
}
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
getVersion ( ) {
return this . version ;
}
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
getDescription ( ) {
return this . description ;
}
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
stop ( ) { }
load ( ) {
const header = ` Missing Library ` ;
const content = ` The Library ZeresPluginLibrary required for ${ this . name } is missing. ` ;
const ModalStack = BdApi . findModuleByProps ( 'push' , 'update' , 'pop' , 'popWithKey' ) ;
const TextElement = BdApi . findModuleByProps ( 'Sizes' , 'Weights' ) ;
const ConfirmationModal = BdApi . findModule ( m => m . defaultProps && m . key && m . key ( ) === 'confirm-modal' ) ;
const onFail = ( ) => BdApi . getCore ( ) . alert ( header , ` ${ content } <br/>Due to a slight mishap however, you'll have to download the library yourself. After opening the link, do CTRL + S to download the library.<br/><br/><a href="https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js"target="_blank">Click here to download ZeresPluginLibrary</a> ` ) ;
if ( ! ModalStack || ! ConfirmationModal || ! TextElement ) return onFail ( ) ;
ModalStack . push ( props => {
return BdApi . React . createElement (
ConfirmationModal ,
Object . assign (
{
header ,
children : [
TextElement ( {
color : TextElement . Colors . PRIMARY ,
children : [ ` ${ content } Please click Download Now to install it. ` ]
} )
] ,
red : false ,
confirmText : 'Download Now' ,
cancelText : 'Cancel' ,
onConfirm : ( ) => {
const request = require ( 'request' ) ;
const fs = require ( 'fs' ) ;
const path = require ( 'path' ) ;
const onDone = ( ) => {
if ( ! global . pluginModule || ! global . BDEvents ) return ;
const onLoaded = e => {
if ( e !== 'ZeresPluginLibrary' ) return ;
BDEvents . off ( 'plugin-loaded' , onLoaded ) ;
pluginModule . reloadPlugin ( this . name ) ;
} ;
BDEvents . on ( 'plugin-loaded' , onLoaded ) ;
} ;
request ( 'https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js' , ( error , response , body ) => {
if ( error ) return onFail ( ) ;
onDone ( ) ;
fs . writeFile ( path . join ( window . ContentManager . pluginsFolder , '0PluginLibrary.plugin.js' ) , body , ( ) => { } ) ;
} ) ;
}
} ,
props
)
) ;
} ) ;
}
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
start ( ) { }
2019-12-21 21:16:47 +01:00
2019-12-24 12:58:53 +01:00
get name ( ) {
return config . info . name ;
}
get short ( ) {
let string = '' ;
for ( let i = 0 , len = config . info . name . length ; i < len ; i ++ ) {
const char = config . info . name [ i ] ;
if ( char === char . toUpperCase ( ) ) string += char ;
}
return string ;
2019-12-21 21:16:47 +01:00
}
2019-12-24 12:58:53 +01:00
get author ( ) {
return config . info . authors . map ( author => author . name ) . join ( ', ' ) ;
}
get version ( ) {
return config . info . version ;
}
get description ( ) {
return config . info . description ;
}
}
: buildPlugin ( global . ZeresPluginLibrary . buildPlugin ( config ) ) ;
2019-12-21 21:16:47 +01:00
} ) ( ) ;
/*@end@*/