Compare commits

...

301 Commits
ptp ... master

Author SHA1 Message Date
Alexei Stukov 835cc3134c
Merge pull request #318 from JsSucks/2.0.0-beta.6
beta6
2019-03-16 19:22:45 +02:00
Jiiks 02b30153ee beta6 2019-03-16 19:18:28 +02:00
Alexei Stukov d09dc5a2a5
Merge pull request #315 from Mega-Mewthree/patch-3
Fix #303, Try 3
2019-03-16 19:12:08 +02:00
Mega-Mewthree 98d9a30027
Fix #303, Try 3 2019-03-12 22:11:05 -07:00
Alexei Stukov 5cb4bc15bd
Merge pull request #312 from samuelthomas2774/component-patches
React component patches
2019-03-12 22:44:26 +02:00
Alexei Stukov bb2aba04d5
Missing FileUtils import 2019-03-12 22:40:21 +02:00
Samuel Elliott 82e9b0bd6a
Fix packed plugins 2019-03-12 19:47:55 +00:00
Samuel Elliott 47575d3449
Don’t watch packed themes 2019-03-12 19:22:27 +00:00
Samuel Elliott 32e2582ded
Use the system temporary directory for packed content 2019-03-12 19:10:09 +00:00
Samuel Elliott d58dda6f50
Fix E2EE message button patch 2019-03-12 17:23:56 +00:00
Samuel Elliott 648954d533
Rerender messages when disabling coloured text and fix jumbo Discord emoji in spoilers 2019-03-12 15:59:18 +00:00
Samuel Elliott 4aa38f4582
Move E2EE and emote module to their own directories 2019-03-12 15:27:05 +00:00
Samuel Elliott b3ba1aef13
Render emotes in spoilers 2019-03-11 17:56:29 +00:00
Samuel Elliott 2a6cbd39b7
Remove logging 2019-03-11 16:43:18 +00:00
Samuel Elliott ce5bcb9b85
Patch MessageAccessories instead of ImageWrapper for emotes sent as images 2019-03-10 21:29:17 +00:00
Samuel Elliott 5a3821ad3e
Package installer UploadArea patch 2019-03-10 20:29:55 +00:00
Samuel Elliott fd0032b24c
Autocomplete 2019-03-10 18:12:43 +00:00
Samuel Elliott 226719b36e
Rerender messages after loading emotes 2019-03-10 17:45:20 +00:00
Samuel Elliott fcfee53928
Move all component selectors + filters to ReactAutoPatcher 2019-03-10 16:58:24 +00:00
Samuel Elliott 285ae34b50
Fix component patches 2019-03-10 16:30:13 +00:00
Alexei Stukov a170a97688
Merge pull request #306 from samuelthomas2774/keytar-4.4.1
macOS + Linux keytar + node-sass bindings
2019-03-10 15:30:36 +02:00
Samuel Elliott a770f57b28
Linux keytar + node-sass bindings for Discord Canary 2019-03-10 00:04:02 +00:00
Samuel Elliott 5757fc20c9
macOS keytar + node-sass bindings for Discord Canary 2019-03-09 23:57:29 +00:00
Samuel Elliott 421289f63b
Linux keytar binding 2019-03-09 23:36:47 +00:00
Samuel Elliott ac85316578
.. 2019-03-09 22:02:40 +00:00
Samuel Elliott 08af9be061
Fix guilds wrapper position 2019-03-09 21:55:13 +00:00
Samuel Elliott ead0fbbd1e
Fix extra space on macOS in the plugins and themes panel and use BdMenuItems to add the super secret view 2019-03-09 21:43:41 +00:00
Samuel Elliott dc85a808f8
Fix undefined/storage file 2019-03-09 21:24:01 +00:00
Samuel Elliott aecfa814f9
Fix error Cannot find module ‘core/dist/package.json’ 2019-03-09 20:52:04 +00:00
Samuel Elliott 436f3d3c36
macOS keytar binding 2019-03-09 20:41:40 +00:00
Alexei Stukov 68a8187964
Merge pull request #300 from JsSucks/Jiiks-patch-1
Update issue templates
2019-03-08 21:54:49 +02:00
Alexei Stukov f30e4c12fe
Update issue templates 2019-03-08 21:51:44 +02:00
Alexei Stukov 686514ed1d
Merge pull request #299 from JsSucks/Jiiks-patch-1
Update issue templates
2019-03-08 21:43:50 +02:00
Alexei Stukov d795da1750
Update issue templates 2019-03-08 21:38:52 +02:00
Alexei Stukov 3219ff7c6e
Merge pull request #293 from JsSucks/2.0.0-beta.4
beta 4 to master
2019-03-08 14:11:13 +02:00
Alexei Stukov 33567a2cfd
Merge pull request #292 from JsSucks/updater
Updater
2019-03-08 14:08:07 +02:00
Jiiks 85310bfbff Stop updater thread 2019-03-08 14:00:54 +02:00
Jiiks d1d79a37b7 Start updater and set interval to 30 minutes 2019-03-08 13:58:46 +02:00
Jiiks d95592acc9 Styling and debug view 2019-03-08 13:57:02 +02:00
Jiiks f6a3fb65da v69 bindings 2019-03-08 13:33:52 +02:00
Alexei Stukov 6788cca363
Merge pull request #291 from XeonPowder/patch-1
fixed comma
2019-03-08 00:41:41 +02:00
Lars van der Zande 1fb442e096
fixed comma 2019-03-07 16:30:21 -06:00
Jiiks 6e64ff61c5 Copy csp.json 2019-03-07 23:50:17 +02:00
Jiiks dd8fe68a11 Better 2019-03-07 22:57:07 +02:00
Jiiks a3829089f9 Secrets for devs 2019-03-07 22:54:16 +02:00
Jiiks 174c1ee791 add some secrets 2019-03-07 22:44:29 +02:00
Jiiks b8793fd2b6 Package parsing for production 2019-03-07 21:09:48 +02:00
Jiiks 31986ca3a0 Try to rename first. 2019-03-07 20:22:11 +02:00
Jiiks 10ff740f75 Remove asar external from production 2019-03-07 19:34:32 +02:00
Jiiks 83fbab63c0 add release test script for faster release testing. copy extra files 2019-03-07 19:21:27 +02:00
Jiiks c4670946e6 Use replace to set production 2019-03-07 19:10:08 +02:00
Jiiks e5239d952e Send error event 2019-03-07 14:15:24 +02:00
Jiiks a57783a9d8 add updates available notification 2019-03-07 11:41:32 +02:00
Jiiks 001a6e4fda Functional updater 2019-03-07 11:35:24 +02:00
Jiiks dd621038f9 Reload and restart notifications 2019-03-06 12:57:19 +02:00
Jiiks 288c233447 tar.gz download 2019-03-06 12:11:45 +02:00
Jiiks 399c6e792b Updater ui 2019-03-06 09:29:23 +02:00
Jiiks 0be6facba4 assign old state, some updater checks. 2019-03-06 09:04:03 +02:00
Jiiks dc7247a12d Send updates to client 2019-03-06 08:36:54 +02:00
Jiiks 377c4fd104 Send updater events 2019-03-06 08:19:09 +02:00
Jiiks 252d496dc2 Get release from github, todo use common 2019-03-06 02:57:57 +02:00
Jiiks 83e334c3f8 Getters, debug 2019-03-06 02:16:27 +02:00
Jiiks e72ad10dfc add semver 2019-03-06 02:02:02 +02:00
Jiiks 9ef392c575 test updater 2019-03-05 23:46:42 +02:00
Jiiks 76057efbb7 Remove old updater code 2019-03-05 23:35:39 +02:00
Jiiks dcb121750a Use core version as the main version 2019-03-05 23:30:41 +02:00
Jiiks 13fa769e9e add versions to config 2019-03-05 23:26:17 +02:00
Jiiks 3143991239 Nevet test editor in production 2019-03-05 21:31:40 +02:00
Jiiks 1ae0c5aa4d Never ignore externals in production 2019-03-05 21:31:20 +02:00
Jiiks 5ea39f86f7 add axios to vendor 2019-03-05 21:28:32 +02:00
Jiiks d6a946e096 headers 2019-03-05 21:26:36 +02:00
Jiiks b68c1fbd04 Move csp and add some sources. 2019-03-05 21:02:06 +02:00
Jiiks 07d3629622 axios wrapper base 2019-03-05 20:38:21 +02:00
Jiiks 15daa9acef add axios because it's better 2019-03-05 20:22:26 +02:00
Jiiks 817a4a03b6 add versions to test args and version getters to config 2019-03-05 12:56:19 +02:00
Jiiks e63386e9eb Updater base 2019-03-05 12:53:08 +02:00
Jiiks 402acdfea9 Wrong main 2019-03-05 10:11:45 +02:00
Jiiks b440206d07 add webpack parallel build/watch 2019-03-05 10:07:49 +02:00
Jiiks c7bea4a743 remove csseditor 2019-03-05 09:43:25 +02:00
Jiiks 2528d87b8f beta.4 branch. all future pulls should go to the latest dev branch instead of master. 2019-03-05 08:46:27 +02:00
Alexei Stukov 405d74fada
Merge pull request #290 from JsSucks/Jiiks-patch-1
I like badges
2019-03-05 08:41:27 +02:00
Alexei Stukov b311220132
I like badges 2019-03-05 08:39:11 +02:00
Alexei Stukov 66b47457b1
Merge pull request #289 from JsSucks/snyk-badge
Snyk badge
2019-03-05 08:30:49 +02:00
Alexei Stukov 6e9f9f8bf8
put it in correct place 2019-03-05 07:46:02 +02:00
Alexei Stukov 88a113dc8f
snyk badge 2019-03-05 07:44:13 +02:00
Alexei Stukov a61f860466
Merge pull request #288 from JsSucks/update-versions
Update versions
2019-03-05 07:43:53 +02:00
Jiiks 729a4607bd Use mver for release tag instead. 2019-03-05 07:35:59 +02:00
Jiiks d81dcc9aa2 refresh lock, fix editor production config 2019-03-05 07:32:11 +02:00
Jiiks 1e4f3fa82b semver 2019-03-05 07:25:18 +02:00
Alexei Stukov 150a1d63c4
Merge pull request #287 from JsSucks/deps-and-scripts
Deps and scripts
2019-03-04 20:12:14 +02:00
Jiiks 6b481733b9 editor webpack merge 2019-03-04 20:08:48 +02:00
Jiiks e07b9b1550 bindings 2019-03-04 19:50:34 +02:00
Jiiks 99c2b53ec6 depcopy and configs 2019-03-04 17:02:52 +02:00
Jiiks 2a93e5d2a3 lint 2019-03-04 10:25:00 +02:00
Jiiks 3661207602 switch to babel.config.js 2019-03-04 10:12:42 +02:00
Jiiks 92845728cc command line pls 2019-03-04 10:09:58 +02:00
Jiiks c99753fc8c Use webpack merge to simplify configs 2019-03-04 10:07:32 +02:00
Jiiks 688e6022a0 pls 2019-03-04 09:55:34 +02:00
Jiiks 5408d994be babel config for client 2019-03-04 09:52:25 +02:00
Zack Rauen 003c9766bc remove uglifyjs plugin 2019-03-03 14:47:44 -05:00
Zack Rauen 82e9c257ce Update deps 2019-03-02 22:15:24 -05:00
Jiiks 6167cc7c4b remove installer build 2019-03-01 22:02:06 +02:00
Jiiks 1bacecf8d4 vtc 2019-03-01 22:01:37 +02:00
Jiiks d102686379 vue, vue-color, remove cm, remove csseditor scripts 2019-03-01 21:56:35 +02:00
Jiiks 1160955629 add chokidar 2019-03-01 20:15:30 +02:00
Jiiks e60f765a50 Move bdedit to devdeps 2019-03-01 20:14:33 +02:00
Alexei Stukov d3db696616
Merge pull request #284 from JsSucks/installer-compliance
Installer compliance
2019-02-28 19:25:39 +02:00
Jiiks b8d16c6e4d Load correct editor file 2019-02-28 17:07:53 +02:00
Jiiks b22923d12f editor packaging 2019-02-28 17:00:25 +02:00
Jiiks 1ea307efdd Editor release build 2019-02-28 16:51:45 +02:00
Jiiks d98cff878f uglify editor 2019-02-28 16:47:49 +02:00
Jiiks dd11708f9f return null if no path 2019-02-28 15:20:36 +02:00
Jiiks 665a7818c9 Make sure editor path is set 2019-02-28 15:12:17 +02:00
Jiiks d75b907ae4 ensure user.scss exists 2019-02-28 15:10:34 +02:00
Alexei Stukov f051bc4812
Merge pull request #286 from JsSucks/bdedit
Bdedit
2019-02-28 14:46:48 +02:00
Jiiks 9773f78506 Toasts and name conflict handler 2019-02-28 12:37:57 +02:00
Alexei Stukov f4b7c99c31
Merge pull request #283 from JsSucks/bdedit
Bdedit
2019-02-28 09:51:20 +02:00
Jiiks 97519b2307 lint 2019-02-28 09:48:45 +02:00
Jiiks 806ca5028a auto imports 2019-02-28 09:46:24 +02:00
Jiiks db89e3a1a0 sass doesn't like empty 2019-02-28 09:45:06 +02:00
Jiiks 6030a78b91 Delete and rename handlers 2019-02-28 06:40:33 +02:00
Jiiks 99ec82795c reveal, copy and copypath handlers 2019-02-28 06:05:54 +02:00
Jiiks b1e8b591ba Remove todo 2019-02-27 20:34:31 +02:00
Jiiks ac9e16632d Snippet save/load 2019-02-27 20:34:11 +02:00
Jiiks d8f977b57a Not a dir anymore 2019-02-27 16:43:13 +02:00
Jiiks c3283c47f6 Tons of smol things 2019-02-27 08:10:04 +02:00
Jiiks 2b86f2d741 File watchers 2019-02-27 01:33:23 +02:00
Jiiks 9f6389845a add live update 5 seconds after last change 2019-02-26 22:24:05 +02:00
Jiiks 7489fd0aaf Titlebar buttons fix 2019-02-26 20:30:07 +02:00
Jiiks 7e1c379fd3 Set user.scss caption 2019-02-26 20:19:57 +02:00
Jiiks c0765120e8 Juggle paths and keep user.scss in data 2019-02-26 20:18:33 +02:00
Jiiks 7e10de32cb add new paths and enable snippet import 2019-02-26 19:54:13 +02:00
Jiiks 145d61fe5e Enable importing other files 2019-02-26 18:32:06 +02:00
Jiiks 72b278de6e js should be javascript 2019-02-26 16:33:14 +02:00
Jiiks 5e977e8dc4 Hoist user.scss 2019-02-25 18:05:51 +02:00
Jiiks 109eb31aa5 custom css inject and file saved state 2019-02-25 17:56:56 +02:00
Jiiks 1b6f3005f5 No need to compile if not scss 2019-02-25 13:01:46 +02:00
Jiiks d02f894d4a Style inject 2019-02-25 12:41:22 +02:00
Jiiks 20561e2938 Set type 2019-02-25 12:10:00 +02:00
Jiiks a3724d739b Read files on load at least for now 2019-02-24 21:56:46 +02:00
Jiiks cf1da34e16 add editor file writing 2019-02-24 21:38:33 +02:00
Jiiks 5454950838 add writefile 2019-02-24 21:36:04 +02:00
Jiiks ed7abe3571 Don't need all these prefixes since they're automated 2019-02-24 21:29:54 +02:00
Jiiks b14bf93ef9 remove todo 2019-02-24 21:27:05 +02:00
Jiiks 3436bbe52b add editor test flag 2019-02-24 21:26:52 +02:00
Jiiks 1a26e77dd9 Move editor events to editor 2019-02-24 21:24:37 +02:00
Jiiks da1fc0a2f0 Smol things 2019-02-24 19:57:46 +02:00
Jiiks d1fd5ae881 imodule and base for new editor module in client 2019-02-24 19:46:11 +02:00
Jiiks 8c04e7d2d3 File/snippet saving signals 2019-02-24 18:43:58 +02:00
Jiiks 3fc1adc503 get files/snippets from core 2019-02-24 18:39:28 +02:00
Jiiks 691c9f378a new file/snippet 2019-02-24 18:29:00 +02:00
Jiiks a769385219 Run scripts from editor in Discord window 2019-02-24 16:04:25 +02:00
Jiiks f1f23fa220 Initial BDEdit addition. No logic yet. 2019-02-24 13:03:15 +02:00
Jiiks 99ef0d9f81 add bdedit 2019-02-24 12:28:29 +02:00
Jiiks 5597c485d1 oops 2019-02-24 12:12:13 +02:00
Jiiks 7f08ba27e6 Remove my test script 2019-02-24 12:02:30 +02:00
Jiiks 99614aecdc Fix linter warnings 2019-02-24 11:56:07 +02:00
Alexei Stukov 726db7f0c9
Merge pull request #279 from JsSucks/dnd-patch
Add dnd in non-channels and fix upload failover
2019-02-24 11:46:57 +02:00
Zack Rauen 67c39fd9d9 Add dnd in non-channels and fix upload failover 2019-02-23 09:42:53 -05:00
Jiiks 23d4eb77b2 Fix test mode 2019-02-20 20:13:27 +02:00
Jiiks 124ec078aa only warn if tag is not set 2019-02-20 16:12:56 +02:00
Jiiks e6c9bc447e allow tag to be set with args 2019-02-20 16:10:18 +02:00
Jiiks 7bc4b652fa add child process as external instead of asar 2019-02-20 16:03:03 +02:00
Alexei Stukov 6ab1f5c844
Merge pull request #278 from JsSucks/Jiiks-patch-1
Core version
2019-02-18 15:26:32 +02:00
Alexei Stukov 816ff2ed08
Core version 2019-02-18 15:23:05 +02:00
Jiiks 6ca36ebbb5 Should use core package 2019-02-18 15:21:51 +02:00
Jiiks f511e4556e keep stub 2019-02-18 15:20:44 +02:00
Alexei Stukov 36515432c7
Merge pull request #277 from JsSucks/Jiiks-patch-1
stub should be a file
2019-02-18 15:07:54 +02:00
Alexei Stukov 47782c0168
stub should be a file 2019-02-18 15:03:17 +02:00
Jiiks 3a370f2827 higher compression for core 2019-02-18 15:01:30 +02:00
Jiiks 90f5596cb2 files should be under respective dir 2019-02-18 14:57:00 +02:00
Jiiks 75439a600e stub should be a file 2019-02-18 14:54:27 +02:00
Alexei Stukov f46960fdd0
Merge pull request #276 from JsSucks/Jiiks-patch-1
Installer Test 2
2019-02-18 12:29:38 +02:00
Alexei Stukov f684780842
Installer Test 2 2019-02-18 12:26:29 +02:00
Jiiks 6c8631f1cd TODO editor packaging/renaming 2019-02-18 12:14:38 +02:00
Jiiks 0311602539 update scripts 2019-02-18 12:13:51 +02:00
Jiiks b5e8098d20 Not having asar in externals causes some weird error 2019-02-18 12:03:54 +02:00
Jiiks 111be57b59 release task 2019-02-18 11:42:34 +02:00
Jiiks 76e19c8469 wip script updates 2019-02-18 09:21:28 +02:00
Jiiks ccff016820 init comms module duh 2019-02-17 14:14:07 +02:00
Jiiks 021e282b1c add tests flag back 2019-02-17 12:26:07 +02:00
Jiiks 6699fcc1e7 add base for compatibility 2019-02-17 12:22:13 +02:00
Jiiks f3ea192974 Paths compatibility 2019-02-17 12:18:16 +02:00
Jiiks 9c1a93f4c1 Remove installer scripts, correct sparkplug external 2019-02-17 11:58:45 +02:00
Jiiks 2c23f18e89 New installer compliance 2019-02-17 11:12:12 +02:00
Jiiks c88d2cdae9 paths is an object now 2019-02-17 08:48:41 +02:00
Alexei Stukov 8235e55357
Merge pull request #275 from JsSucks/Jiiks-patch-1
Update releaseinfo.json
2019-02-16 03:43:40 +02:00
Alexei Stukov 754e6bbd72
Update releaseinfo.json 2019-02-16 03:40:17 +02:00
Alexei Stukov e27d8874ef
Merge pull request #274 from JsSucks/lodash-vuln
https://nvd.nist.gov/vuln/detail/CVE-2018-16487
2019-02-10 18:49:32 +02:00
Alexei Stukov c310cc52d5
Merge pull request #267 from BeardDesign1/master
Fixed a bunch of transitions and changed the arrow animation.
2019-02-10 18:47:59 +02:00
Jiiks 073cce825d https://nvd.nist.gov/vuln/detail/CVE-2018-16487 2019-02-10 18:45:57 +02:00
Alexei Stukov a04fb59db7
Merge pull request #273 from JsSucks/Jiiks-patch-1
Create README.MD
2019-02-06 01:53:39 +02:00
Alexei Stukov f8ed871f8e
Create README.MD 2019-02-06 01:50:53 +02:00
Alexei Stukov fae97613d1
Merge pull request #272 from JsSucks/installer-stuff
Installer stuff
2019-02-01 15:07:46 +02:00
Jiiks 27df24f369 Update stub 2019-02-01 15:00:03 +02:00
Jiiks 5bb95f59c0 update installer info 2019-02-01 14:58:50 +02:00
Alexei Stukov bcabaab14c
Merge pull request #271 from JsSucks/Jiiks-patch-1
Create stub.js
2019-02-01 09:20:45 +02:00
Alexei Stukov 033f3472cb
Create stub.js 2019-02-01 09:08:21 +02:00
Alexei Stukov 8fbd75ce94
Merge pull request #270 from JsSucks/Jiiks-patch-1
Update installer.json
2019-01-23 11:41:53 +02:00
Alexei Stukov 3a38b59a4d
Update installer.json 2019-01-23 11:38:57 +02:00
Alexei Stukov c29b659afb
Merge pull request #269 from JsSucks/installer-tests
Create installer.json
2019-01-20 21:31:30 +02:00
Alexei Stukov ece161fac0
Create installer.json 2019-01-20 21:28:12 +02:00
Lilian Tedone b57ab92269 Fixed a bunch of transitions and changed the arrow animation. 2019-01-08 08:41:24 +01:00
Alexei Stukov 5a3aa553cd
Merge pull request #266 from JsSucks/content-browser
Content browser
2018-12-06 11:26:26 +02:00
Jiiks a2f48dd007 lint 2018-12-06 11:23:31 +02:00
Jiiks 4f10f38581 Remote tests 2018-12-06 10:02:07 +02:00
Jiiks ea0dc0c6f1 Do the same for plugins 2018-12-05 10:58:43 +02:00
Jiiks 93f536bf21 Make tags clickable 2018-12-05 08:26:01 +02:00
Jiiks 497ab62ee6 Remove hover scaling 2018-12-05 08:09:15 +02:00
Jiiks 784a14f853 More neutral red 2018-12-05 08:08:52 +02:00
Jiiks 95447dd3f0 Array for sort buttons 2018-12-05 08:07:18 +02:00
Jiiks 2cff8edb59 Sorting by name is useless 2018-12-05 07:15:49 +02:00
Jiiks e4c0796e5e add rating sort 2018-12-05 00:43:40 +02:00
Jiiks e3010fcd43 Functional sorting 2018-12-05 00:40:57 +02:00
Jiiks 08b66d5e5a Functional sort buttons 2018-12-04 23:37:39 +02:00
Jiiks c618956639 flipy 2018-12-04 23:30:11 +02:00
Jiiks c6ddb75630 Remove sort by text 2018-12-04 23:25:49 +02:00
Jiiks 1fd7b97ce6 always have spinner container visible 2018-12-04 23:18:13 +02:00
Jiiks fa5be193ad add chevron to sort buttons and styling 2018-12-04 23:15:16 +02:00
Jiiks 63874c8b4e Sort styling 2018-12-04 23:02:36 +02:00
Jiiks b4dbfcd808 Pagination 2018-12-04 10:48:25 +02:00
Jiiks 7a9b72a33a Initial search 2018-12-04 09:30:49 +02:00
Jiiks 6db34b9cee Server emulator thing 2018-12-04 09:22:04 +02:00
Alexei Stukov ead51ae676
Merge pull request #265 from JsSucks/content-browser
Merge for now
2018-12-02 05:06:02 +02:00
Jiiks 9a77aae56b Lint 2018-12-02 04:13:57 +02:00
Jiiks 3182f28359 Layout and initial sort buttons 2018-12-02 02:23:03 +02:00
Jiiks a274b0c839 Styles 2018-12-02 02:20:16 +02:00
Jiiks 199b9249e4 Layout 2018-12-02 02:18:54 +02:00
Jiiks 08ba649a91 Sort box 2018-12-02 00:40:14 +02:00
Jiiks a0eb43188f add search results text 2018-12-02 00:30:20 +02:00
Jiiks b1bd5190c1 Initial loading logic for packed themes 2018-12-01 03:53:49 +02:00
Jiiks 7fb5c8a378 Theme packer 2018-11-30 22:13:36 +02:00
Jiiks 26b1fad5a4 Move packer to contentmanager 2018-11-30 22:08:05 +02:00
Jiiks f028aefab8 Source button 2018-11-30 21:14:52 +02:00
Jiiks f0d66300c4 Styling 2018-11-29 00:22:04 +02:00
Jiiks 12b291cf0b Temp online plugins, local content body scroller 2018-11-28 09:22:29 +02:00
Jiiks a8f686eb61 only scroll body 2018-11-28 08:50:14 +02:00
Jiiks 98bf4d21ac Load more on scroll 2018-11-26 17:15:26 +02:00
Jiiks d5a82ab2b3 Load more spinner 2018-11-26 17:04:20 +02:00
Jiiks d5d7709c9f Scrollend event 2018-11-26 11:32:22 +02:00
Jiiks cfc197cdc0 add spinner 2018-11-26 11:08:27 +02:00
Jiiks e8cfb840fe Styling 2018-11-26 10:56:35 +02:00
Jiiks f01f4926eb Random timestamps 2018-11-26 10:00:57 +02:00
Jiiks 83a4e0a114 Use moment 2018-11-26 09:55:59 +02:00
Jiiks 4b8a905d2a Dummy theme generation 2018-11-26 09:42:52 +02:00
Alexei Stukov 08ae8dceb5
Merge pull request #264 from JsSucks/emotemodule
Emotemodule
2018-11-26 07:31:53 +02:00
Jiiks ad28e7c638 Childpatch 2018-11-26 07:22:15 +02:00
Jiiks 9ba8047066 Fix MessageContent patch 2018-11-26 07:06:21 +02:00
Alexei Stukov 9ba9ec1556
Merge pull request #263 from JsSucks/package-updates
Package updates
2018-11-26 03:02:38 +02:00
Jiiks ce2658b6e2 electron 2018-11-26 02:56:31 +02:00
Jiiks f3cc033182 deepmerge 2018-11-26 02:54:50 +02:00
Jiiks 7b44d163ca node-sass 2018-11-26 02:53:12 +02:00
Alexei Stukov 72d24a1cb0
Merge pull request #262 from JsSucks/injection-patch
Injection patch
2018-11-26 02:50:34 +02:00
Alexei Stukov c2c520fe27
Merge pull request #260 from samuelthomas2774/electron-4.0.0-beta.7
Node Sass and keytar bindings for Electron 4.0.0 beta.7
2018-11-26 02:45:52 +02:00
Samuel Elliott ad55459649
Indentation 2018-11-25 02:19:10 +00:00
Samuel Elliott dfac3f1825
Add keytar and node-sass bindings from Jiiks 2018-11-25 02:13:14 +00:00
Samuel Elliott adecbf8945
Add Linux Node Sass binding 2018-11-25 02:10:19 +00:00
Samuel Elliott a371fabc70
Add keytar build scripts 2018-11-25 02:01:12 +00:00
Samuel Elliott c04ffdfc24
Working keytar binding for Linux 2018-11-25 01:58:35 +00:00
Samuel Elliott 572721c57f
Fix inject script for Canary on Linux 2018-11-25 01:04:07 +00:00
Samuel Elliott 4af09224d7
Update node-sass build script for Windows 2018-11-24 23:22:50 +00:00
Samuel Elliott 7f1ac9bea0
Update keytar and add bindings for Electron 4.0.0-beta.7 2018-11-24 23:20:25 +00:00
Samuel Elliott ce93b2b4c6
Updated node-sass build script for Electron 4.0.0-beta.7 2018-11-24 23:12:15 +00:00
Samuel Elliott d98ed6649e
Updated inject script (works on macOS and Linux) 2018-11-24 23:11:10 +00:00
Zack Rauen 468422084a fix injection for electron 4.0.0-beta.7 2018-11-24 17:44:28 -05:00
Alexei Stukov 395167aafa
Merge pull request #256 from JsSucks/reactcomponents
Fix for new context wrappers in Discord
2018-11-22 21:57:15 +02:00
Alexei Stukov 803cf59f11
Merge pull request #255 from JsSucks/message-content
Fix MessageContent not being found in multiple builtins
2018-11-22 21:57:00 +02:00
Alexei Stukov 133511aeec
Merge pull request #254 from JsSucks/electron-patch
fix for electron 3.x.x (now in discord canary)
2018-11-22 21:56:44 +02:00
Zack Rauen be024c1bbe quickfix array check 2018-10-10 23:58:16 -04:00
Zack Rauen 6f10c71623 add filter for MessageContent 2018-10-10 23:46:34 -04:00
Zack Rauen adb2430d71 fix for electron 3.x.x 2018-10-10 22:13:32 -04:00
Alexei Stukov 990453cdad
Merge pull request #253 from JsSucks/package-installer
Package installer
2018-08-31 08:36:45 +03:00
Jiiks 7cf69f6993 lint 2018-08-31 08:33:23 +03:00
Jiiks 5822110c87 Package updater fix 2018-08-31 05:16:42 +03:00
Jiiks 669a88c1ff Fix package installer and add remote loader 2018-08-31 05:01:05 +03:00
Jiiks 6a8c77b578 add error display 2018-08-31 00:42:05 +03:00
Jiiks a93d12df03 add support for updating unpacked to packed and packed with different package name 2018-08-30 21:24:09 +03:00
Jiiks 80096c5e71 Styling 2018-08-30 18:34:06 +03:00
Jiiks 307b084214 add success circle 2018-08-30 18:32:06 +03:00
Jiiks 1e0f3b69fb Success modal 2018-08-30 18:30:49 +03:00
Jiiks 550d0d3c40 Move uploadarea patcher to packageinstaller 2018-08-30 17:34:18 +03:00
Jiiks 96f8fe680f add verified text 2018-08-30 09:27:16 +03:00
Jiiks b48adc7a62 red outline file svg for unverified 2018-08-30 09:23:27 +03:00
Jiiks 6db016060b Small things 2018-08-30 09:08:52 +03:00
Jiiks ecb0b3e9d0 Verify step 2018-08-30 07:05:14 +03:00
Jiiks 8c060314da Functional installer/updater. Still needs lots of work. 2018-08-30 05:39:23 +03:00
Jiiks a040a605d4 Version is in info not in config 2018-08-29 23:09:30 +03:00
Jiiks 09fb76d148 Versions should be strings 2018-08-29 23:07:32 +03:00
Jiiks ddda5b3cc1 add checks and lint fixes 2018-08-29 23:02:41 +03:00
Jiiks cff425f916 Package installer & install modal 2018-08-29 06:19:18 +03:00
Jiiks 76d54f0a95 Fix plugin loading for packed plugins 2018-08-28 18:08:56 +03:00
Jiiks 2093c472b5 Move unsafe-content under security 2018-08-27 20:17:07 +03:00
Jiiks 1009c62ea0 duplicate asar external 2018-08-27 19:41:52 +03:00
Jiiks 2cd94fcd5f Remove package plugin button if plugin is already packaged 2018-08-27 19:39:29 +03:00
Jiiks cb35b14311 Remove debugs 2018-08-27 19:37:55 +03:00
Jiiks 6b3cb712c7 Block any nonpacked content by default if unsafe is not allowed 2018-08-27 19:36:06 +03:00
Jiiks cfbafffe46 add option for unsafe content 2018-08-27 19:17:33 +03:00
Jiiks 2913a85368 Packed content loading 2018-08-27 19:16:30 +03:00
Jiiks 5c160d75a6 add a packed test plugin 2018-08-27 17:32:21 +03:00
Jiiks f14b1b71e7 Use asar instead 2018-08-27 17:28:42 +03:00
Jiiks 4e3a56e466 Plugin packager 2018-08-26 22:39:04 +03:00
178 changed files with 10650 additions and 10267 deletions

View File

@ -1,9 +0,0 @@
{
"presets": [
["env", {
"targets": {
"node": "6.7.0"
}
}]
]
}

33
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,33 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug]"
labels: bug
assignees: ''
---
- [ ] Used appropriate template for the issue type
- [ ] Searched both open and closed issues for duplicates of this issue
- [ ] Title adequately and _concisely_ reflects the feature or the bug
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
**To Reproduce**
<!-- Steps to reproduce the behavior -->
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Screenshots**
<!-- If applicable, add screenshots to help explain your problem. -->
**System information**
<!-- os, discord branch etc -->
**Additional context**
<!-- Add any other context about the problem here. -->
**Are you willing and able to fix this?**
<!-- "Yes" or, if "no", what can current contributors do to help you create a PR? -->

12
.github/ISSUE_TEMPLATE/custom.md vendored Normal file
View File

@ -0,0 +1,12 @@
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---
- [ ] Used appropriate template for the issue type
- [ ] Searched both open and closed issues for duplicates of this issue
- [ ] Title adequately and _concisely_ reflects the feature or the bug

View File

@ -0,0 +1,27 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature]"
labels: feature
assignees: ''
---
- [ ] Used appropriate template for the issue type
- [ ] Searched both open and closed issues for duplicates of this issue
- [ ] Title adequately and _concisely_ reflects the feature or the bug
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
**Are you willing and able to implement this?**
<!-- "Yes" or, if "no", what can current contributors do to help you create a PR? -->

2
.gitignore vendored
View File

@ -4,6 +4,7 @@ dist
etc
release
tests/tmp
tests/log.txt
# User data
@ -12,3 +13,4 @@ user.config.json
/.vs
/npm-debug.log
/tests/ext/extensions
/tests/userdata

View File

@ -1,4 +1,12 @@
# BetterDiscordApp [![Travis][build-badge]][build]
# BetterDiscordApp [![Travis][build-badge]][build] [![Snyk][snyk-badge]][snyk-url] [![deps][deps-badge]][deps-url] [![devdeps][devdeps-badge]][devdeps-url]
[build-badge]: https://img.shields.io/travis/JsSucks/BetterDiscordApp/master.svg
[build]: https://travis-ci.org/JsSucks/BetterDiscordApp
[snyk-badge]: https://snyk.io/test/github/JsSucks/BetterDiscordApp/badge.svg
[snyk-url]: https://snyk.io/test/github/JsSucks/BetterDiscordApp
[deps-badge]: https://david-dm.org/JsSucks/BetterDiscordApp.svg
[deps-url]: https://david-dm.org/JsSucks/BetterDiscordApp
[devdeps-badge]: https://david-dm.org/JsSucks/BetterDiscordApp/dev-status.svg
[devdeps-url]: https://david-dm.org/JsSucks/BetterDiscordApp?type=dev

17
babel.config.js Normal file
View File

@ -0,0 +1,17 @@
module.exports = function(api) {
api.cache(true);
const presets = [['@babel/env', {
targets: {
'node': '8.6.0'
}
}]];
const plugins = [];
return {
presets,
plugins
}
}

18
client/babel.config.js Normal file
View File

@ -0,0 +1,18 @@
module.exports = function(api) {
api.cache(true);
const presets = [['@babel/env', {
targets: {
'node': '8.6.0',
'chrome': '60'
}
}], '@babel/react'];
const plugins = [];
return {
presets,
plugins
}
}

View File

@ -2,7 +2,7 @@
"name": "bdclient",
"description": "BetterDiscord client package",
"author": "Jiiks",
"version": "2.0.0b",
"version": "2.0.0-beta.6",
"homepage": "https://betterdiscord.net",
"license": "MIT",
"main": "dist/betterdiscord.client.js",

View File

@ -36,5 +36,5 @@ export default new class TwentyFourHour extends BuiltinModule {
if (matched[3] === 'AM') return returnValue.replace(matched[0], `${matched[1] === '12' ? '00' : matched[1].padStart(2, '0')}:${matched[2]}`)
return returnValue.replace(matched[0], `${parseInt(matched[1]) + 12}:${matched[2]}`)
}
}

View File

@ -21,7 +21,7 @@ export default new class BlockedMessages extends BuiltinModule {
async enabled(e) {
const MessageListComponents = Reflection.module.byProps('BlockedMessageGroup');
MessageListComponents.OriginalBlockedMessageGroup = MessageListComponents.BlockedMessageGroup;
MessageListComponents.BlockedMessageGroup = () => { return null; };
MessageListComponents.BlockedMessageGroup = () => null;
this.cancelBlockedMessages = () => {
MessageListComponents.BlockedMessageGroup = MessageListComponents.OriginalBlockedMessageGroup;
delete MessageListComponents.OriginalBlockedMessageGroup;

View File

@ -22,10 +22,10 @@ export default class BuiltinModule {
this.patch = this.patch.bind(this);
}
init() {
async init() {
this.setting.on('setting-updated', this._settingUpdated);
if (this.setting.value) {
if (this.enabled) this.enabled();
if (this.enabled) await this.enabled();
if (this.applyPatches) this.applyPatches();
}
}
@ -38,16 +38,15 @@ export default class BuiltinModule {
return Patcher.getPatchesByCaller(`BD:${this.moduleName}`);
}
_settingUpdated(e) {
const { value } = e;
if (value === true) {
if (this.enabled) this.enabled(e);
if (this.applyPatches) this.applyPatches();
return;
}
if (value === false) {
if (this.disabled) this.disabled(e);
async _settingUpdated(e) {
if (e.value) {
if (this.enabled) await this.enabled(e);
if (this.applyPatches) await this.applyPatches();
if (this.rerenderPatchedComponents) this.rerenderPatchedComponents();
} else {
if (this.disabled) await this.disabled(e);
this.unpatch();
if (this.rerenderPatchedComponents) this.rerenderPatchedComponents();
}
}
@ -75,7 +74,15 @@ export default class BuiltinModule {
*/
patch(module, fnName, cb, when = 'after') {
if (!['before', 'after', 'instead'].includes(when)) when = 'after';
Patch(`BD:${this.moduleName}`, module)[when](fnName, cb.bind(this));
return Patch(`BD:${this.moduleName}`, module)[when](fnName, cb.bind(this));
}
childPatch(module, fnName, child, cb, when = 'after') {
const last = child.pop();
this.patch(module, fnName, (component, args, retVal) => {
const unpatch = this.patch(child.reduce((obj, key) => obj[key], retVal), last, function(...args) {unpatch(); return cb.call(this, component, ...args);}, when);
});
}
/**

View File

@ -42,6 +42,10 @@ export default new class ColoredText extends BuiltinModule {
this.intensitySetting.off('setting-updated', this._intensityUpdated);
}
rerenderPatchedComponents() {
if (this.MessageContent) this.MessageContent.forceUpdateAll();
}
/* Methods */
_intensityUpdated() {
this.MessageContent.forceUpdateAll();
@ -50,18 +54,20 @@ export default new class ColoredText extends BuiltinModule {
/* Patches */
async applyPatches() {
if (this.patches.length) return;
this.MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').selector });
this.MessageContent = await ReactComponents.getComponent('MessageContent');
this.patch(this.MessageContent.component.prototype, 'render', this.injectColoredText);
this.MessageContent.forceUpdateAll();
}
/**
* Set markup text colour to match role colour
*/
injectColoredText(thisObject, args, returnValue) {
const { TinyColor } = Reflection.modules;
const markup = Utils.findInReactTree(returnValue, m => m && m.props && m.props.className && m.props.className.includes('da-markup'));
const roleColor = thisObject.props.message.colorString;
if (markup && roleColor) markup.props.style = {color: TinyColor.mix(roleColor, this.defaultColor, this.intensity)};
injectColoredText(thisObject, args, originalReturn) {
const unpatch = this.patch(originalReturn.props, 'children', (obj, args, returnValue) => {
unpatch();
const { TinyColor } = Reflection.modules;
const markup = Utils.findInReactTree(returnValue, m => m && m.props && m.props.className && m.props.className.includes('da-markup'));
const roleColor = thisObject.props.message.colorString;
if (markup && roleColor) markup.props.style = {color: TinyColor.mix(roleColor, this.defaultColor, this.intensity)};
});
}
}

View File

@ -9,8 +9,8 @@
*/
import { Settings, Cache, Events } from 'modules';
import BuiltinModule from './BuiltinModule';
import { Reflection, ReactComponents, MonkeyPatch, Patcher, DiscordApi, Security } from 'modules';
import BuiltinModule from '../BuiltinModule';
import { Reflection, ReactComponents, DiscordApi, Security } from 'modules';
import { VueInjector, Modals, Toasts } from 'ui';
import { ClientLogger as Logger, ClientIPC } from 'common';
import { request } from 'vendor';
@ -172,7 +172,7 @@ export default new class E2EE extends BuiltinModule {
this.patch(Dispatcher, 'dispatch', this.dispatcherPatch, 'before');
this.patchMessageContent();
const ChannelTextArea = await ReactComponents.getComponent('ChannelTextArea', { selector: Reflection.resolve('channelTextArea', 'emojiButton').selector });
const ChannelTextArea = await ReactComponents.getComponent('ChannelTextArea');
this.patchChannelTextArea(ChannelTextArea);
this.patchChannelTextAreaSubmit(ChannelTextArea);
ChannelTextArea.forceUpdateAll();
@ -236,12 +236,14 @@ export default new class E2EE extends BuiltinModule {
}
async patchMessageContent() {
const MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').selector });
const MessageContent = await ReactComponents.getComponent('MessageContent');
this.patch(MessageContent.component.prototype, 'render', this.beforeRenderMessageContent, 'before');
this.patch(MessageContent.component.prototype, 'render', this.afterRenderMessageContent);
this.childPatch(MessageContent.component.prototype, 'render', ['props', 'children'], this.afterRenderMessageContent);
MessageContent.forceUpdateAll();
const ImageWrapper = await ReactComponents.getComponent('ImageWrapper', { selector: Reflection.resolve('imageWrapper').selector });
const ImageWrapper = await ReactComponents.getComponent('ImageWrapper');
this.patch(ImageWrapper.component.prototype, 'render', this.beforeRenderImageWrapper, 'before');
ImageWrapper.forceUpdateAll();
}
beforeRenderMessageContent(component) {
@ -285,10 +287,16 @@ export default new class E2EE extends BuiltinModule {
component.props.message.contentParsed = create.contentParsed;
}
afterRenderMessageContent(component, args, retVal) {
afterRenderMessageContent(component, _childrenObject, args, retVal) {
if (!component.props.message.bd_encrypted) return;
const buttons = Utils.findInReactTree(retVal, m => Array.isArray(m) && m[1].props && m[1].props.currentUserId);
const { className } = Reflection.resolve('buttonContainer', 'avatar', 'username');
const buttonContainer = Utils.findInReactTree(retVal, m => m && m.className && m.className.indexOf(className) !== -1);
if (!buttonContainer) return;
const buttons = buttonContainer.children.props.children;
if (!buttons) return;
try {
buttons.unshift(VueInjector.createReactElement(E2EEMessageButton));
} catch (err) {

View File

@ -45,7 +45,7 @@
import { E2EE } from 'builtin';
import { Settings, DiscordApi, Reflection } from 'modules';
import { Toasts } from 'ui';
import { MiLock, MiImagePlus, MiIcVpnKey } from '../ui/components/common/MaterialIcon';
import { MiLock, MiImagePlus, MiIcVpnKey } from 'commoncomponents';
export default {
components: {

View File

@ -17,7 +17,7 @@
</template>
<script>
import { MiLock } from '../ui/components/common/MaterialIcon';
import { MiLock } from 'commoncomponents';
export default {
components: {

View File

@ -0,0 +1,3 @@
export { default as default } from './E2EE';
export { default as E2EEComponent } from './E2EEComponent.vue';
export { default as E2EEMessageButton } from './E2EEMessageButton.vue';

View File

@ -9,18 +9,11 @@
*/
import { Settings } from 'modules';
import BuiltinModule from './BuiltinModule';
import EmoteModule from './EmoteModule';
import GlobalAc from '../ui/autocomplete';
import BuiltinModule from '../BuiltinModule';
import EmoteModule, { EMOTE_SOURCES } from './EmoteModule';
import GlobalAc from 'autocomplete';
import { BdContextMenu } from 'ui';
const EMOTE_SOURCES = [
'https://static-cdn.jtvnw.net/emoticons/v1/:id/1.0',
'https://cdn.frankerfacez.com/emoticon/:id/1',
'https://cdn.betterttv.net/emote/:id/1x'
]
export default new class EmoteAc extends BuiltinModule {
/* Getters */

View File

@ -8,15 +8,10 @@
* LICENSE file in the root directory of this source tree.
*/
import VrWrapper from '../ui/vrwrapper';
import VrWrapper from '../../ui/vrwrapper';
import { EMOTE_SOURCES } from '.';
import EmoteComponent from './EmoteComponent.vue';
const EMOTE_SOURCES = [
'https://static-cdn.jtvnw.net/emoticons/v1/:id/1.0',
'https://cdn.frankerfacez.com/emoticon/:id/1',
'https://cdn.betterttv.net/emote/:id/1x'
]
export default class Emote extends VrWrapper {
constructor(type, id, name) {

View File

@ -5,7 +5,7 @@
<script>
import { ClientLogger as Logger } from 'common';
import EmoteModule from './EmoteModule';
import { MiStar } from '../ui/components/common';
import { MiStar } from 'commoncomponents';
export default {
components: {

View File

@ -8,20 +8,17 @@
* LICENSE file in the root directory of this source tree.
*/
import BuiltinModule from './BuiltinModule';
import BuiltinModule from '../BuiltinModule';
import path from 'path';
import { request } from 'vendor';
import { Utils, FileUtils } from 'common';
import { Utils, FileUtils, ClientLogger as Logger } from 'common';
import { DiscordApi, Settings, Globals, Reflection, ReactComponents, Database } from 'modules';
import { DiscordContextMenu } from 'ui';
import Emote from './EmoteComponent.js';
import Autocomplete from '../ui/components/common/Autocomplete.vue';
import GlobalAc from '../ui/autocomplete';
const EMOTE_SOURCES = [
export const EMOTE_SOURCES = [
'https://static-cdn.jtvnw.net/emoticons/v1/:id/1.0',
'https://cdn.frankerfacez.com/emoticon/:id/1',
'https://cdn.betterttv.net/emote/:id/1x'
@ -131,6 +128,8 @@ export default new class EmoteModule extends BuiltinModule {
this.database.set(id, { id: emote.value.id || value, type });
}
Logger.log('EmoteModule', ['Loaded emote database']);
}
async loadUserData() {
@ -218,16 +217,19 @@ export default new class EmoteModule extends BuiltinModule {
async applyPatches() {
this.patchMessageContent();
this.patchSendAndEdit();
const ImageWrapper = await ReactComponents.getComponent('ImageWrapper', { selector: Reflection.resolve('imageWrapper').selector });
this.patch(ImageWrapper.component.prototype, 'render', this.beforeRenderImageWrapper, 'before');
this.patchSpoiler();
const MessageAccessories = await ReactComponents.getComponent('MessageAccessories');
this.patch(MessageAccessories.component.prototype, 'render', this.afterRenderMessageAccessories, 'after');
MessageAccessories.forceUpdateAll();
}
/**
* Patches MessageContent render method
*/
async patchMessageContent() {
const MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').selector });
this.patch(MessageContent.component.prototype, 'render', this.afterRenderMessageContent);
const MessageContent = await ReactComponents.getComponent('MessageContent');
this.childPatch(MessageContent.component.prototype, 'render', ['props', 'children'], this.afterRenderMessageContent);
MessageContent.forceUpdateAll();
}
@ -240,10 +242,26 @@ export default new class EmoteModule extends BuiltinModule {
this.patch(MessageActions, 'editMessage', this.handleEditMessage, 'instead');
}
async patchSpoiler() {
const Spoiler = await ReactComponents.getComponent('Spoiler');
this.childPatch(Spoiler.component.prototype, 'render', ['props', 'children', 'props', 'children'], this.afterRenderSpoiler);
Spoiler.forceUpdateAll();
}
afterRenderSpoiler(component, _childrenObject, args, retVal) {
const markup = Utils.findInReactTree(retVal, filter =>
filter &&
filter.className &&
filter.className.includes('inlineContent'));
if (!markup) return;
markup.children = this.processMarkup(markup.children);
}
/**
* Handle message render
*/
afterRenderMessageContent(component, args, retVal) {
afterRenderMessageContent(component, _childrenObject, args, retVal) {
const markup = Utils.findInReactTree(retVal, filter =>
filter &&
filter.className &&
@ -256,13 +274,15 @@ export default new class EmoteModule extends BuiltinModule {
/**
* Handle send message
*/
async handleSendMessage(component, args, orig) {
async handleSendMessage(MessageActions, args, orig) {
if (!args.length) return orig(...args);
const { content } = args[1];
if (!content) return orig(...args);
Logger.log('EmoteModule', ['Sending message', MessageActions, args, orig]);
const emoteAsImage = Settings.getSetting('emotes', 'default', 'emoteasimage').value &&
(DiscordApi.currentChannel.type === 'DM' || DiscordApi.currentChannel.checkPermissions(DiscordApi.modules.DiscordPermissions.ATTACH_FILES));
(DiscordApi.currentChannel.type === 'DM' || DiscordApi.currentChannel.type === 'GROUP_DM' || DiscordApi.currentChannel.checkPermissions(DiscordApi.modules.DiscordPermissions.ATTACH_FILES));
if (!emoteAsImage || content.split(' ').length > 1) {
args[1].content = args[1].content.split(' ').map(word => {
@ -271,7 +291,7 @@ export default new class EmoteModule extends BuiltinModule {
const emote = this.findByName(isEmote[1], true);
if (!emote) return word;
this.addToMostUsed(emote);
return emote ? `:${isEmote[1]}:` : word;
return emote ? `;${isEmote[1]};` : word;
}
return word;
}).join(' ');
@ -305,23 +325,27 @@ export default new class EmoteModule extends BuiltinModule {
if (!content) return orig(...args);
args[2].content = args[2].content.split(' ').map(word => {
const isEmote = /;(.*?);/g.exec(word);
return isEmote ? `:${isEmote[1]}:` : word;
return isEmote ? `;${isEmote[1]};` : word;
}).join(' ');
return orig(...args);
}
/**
* Handle imagewrapper render
* Handle MessageAccessories render
*/
beforeRenderImageWrapper(component, args, retVal) {
if (!component.props || !component.props.src) return;
afterRenderMessageAccessories(component, args, retVal) {
if (!component.props || !component.props.message) return;
if (!component.props.message.attachments || component.props.message.attachments.length !== 1) return;
const src = component.props.original || component.props.src.split('?')[0];
if (!src || !src.includes('.bdemote.')) return;
const emoteName = src.split('/').pop().split('.')[0];
const emote = this.findByName(emoteName);
const filename = component.props.message.attachments[0].filename;
const match = filename.match(/([^/]*)\.bdemote\.(gif|png)$/i);
if (!match) return;
const emote = this.findByName(match[1]);
if (!emote) return;
retVal.props.children = emote.render();
emote.jumboable = true;
retVal.props.children[2] = emote.render();
}
/**
@ -339,14 +363,14 @@ export default new class EmoteModule extends BuiltinModule {
for (const child of markup) {
if (typeof child !== 'string') {
if (typeof child === 'object') {
const isEmoji = Utils.findInReactTree(child, 'emojiName');
if (isEmoji) child.props.children.props.jumboable = jumboable;
const isEmoji = Utils.findInReactTree(child, filter => filter && filter.emojiName);
if (isEmoji) isEmoji.jumboable = jumboable;
}
newMarkup.push(child);
continue;
}
if (!/:(\w+):/g.test(child)) {
if (!/;(\w+);/g.test(child)) {
newMarkup.push(child);
continue;
}
@ -355,7 +379,7 @@ export default new class EmoteModule extends BuiltinModule {
let s = '';
for (const word of words) {
const isemote = /:(.*?):/g.exec(word);
const isemote = /;(.*?);/g.exec(word);
if (!isemote) {
s += word;
continue;

View File

@ -0,0 +1,4 @@
export { default as EmoteModule, EMOTE_SOURCES } from './EmoteModule';
export { default as Emote } from './EmoteComponent';
export { default as EmoteComponent } from './EmoteComponent.vue';
export { default as EmoteAc } from './EmoteAc';

View File

@ -1,16 +1,19 @@
import { default as EmoteModule } from './EmoteModule';
import { default as ReactDevtoolsModule } from './ReactDevtoolsModule';
import { default as VueDevtoolsModule } from './VueDevToolsModule';
import { default as TrackingProtection } from './TrackingProtection';
import { default as E2EE } from './E2EE';
import { default as ColoredText } from './ColoredText';
import { default as TwentyFourHour } from './24Hour';
import { default as KillClyde } from './KillClyde';
import { default as BlockedMessages } from './BlockedMessages';
import { default as VoiceDisconnect } from './VoiceDisconnect';
import { default as EmoteAc } from './EmoteAc';
import { EmoteModule, EmoteAc } from './Emotes';
import ReactDevtoolsModule from './ReactDevtoolsModule';
import VueDevtoolsModule from './VueDevToolsModule';
import TrackingProtection from './TrackingProtection';
import E2EE from './E2EE';
import ColoredText from './ColoredText';
import TwentyFourHour from './24Hour';
import KillClyde from './KillClyde';
import BlockedMessages from './BlockedMessages';
import VoiceDisconnect from './VoiceDisconnect';
export default class {
static get modules() {
return require('./builtin');
}
static initAll() {
EmoteModule.init();
ReactDevtoolsModule.init();

View File

@ -12,7 +12,7 @@ import BuiltinModule from './BuiltinModule';
import { Reflection } from 'modules';
export default new class E2EE extends BuiltinModule {
export default new class TrackingProtection extends BuiltinModule {
/* Getters */
get moduleName() { return 'TrackingProtection' }

View File

@ -1,4 +1,4 @@
export { default as EmoteModule } from './EmoteModule';
export { EmoteModule, EmoteAc } from './Emotes';
export { default as ReactDevtoolsModule } from './ReactDevtoolsModule';
export { default as VueDevtoolsModule } from './VueDevToolsModule';
export { default as TrackingProtection } from './TrackingProtection';

View File

@ -222,6 +222,13 @@
{
"id": "default",
"settings": [
{
"id": "unsafe-content",
"type": "bool",
"text": "Allow unverified content",
"hint": "Allow loading unverified plugins/themes",
"value": "false"
},
{
"id": "tracking-protection",
"type": "bool",

232
client/src/dev/serveremu.js Normal file
View File

@ -0,0 +1,232 @@
const dummyTags = ['dark', 'light', 'simple', 'minimal', 'extra', 'something', 'tag', 'whatever', 'another', 'transparent'];
export default class ServerEmu {
static async themes(args) {
if (!this._themes) this._themes = this.generateThemes();
await new Promise(r => setTimeout(r, Math.random() * 3000));
let docs = [];
if (args && args.sterm) {
const { sterm } = args;
const reg = new RegExp(sterm, 'gi');
docs = this._themes.filter(doc => doc.tags.includes(sterm) || reg.exec(doc.name) || reg.exec(doc.description));
} else {
docs = this._themes;
}
if (args.sort) {
switch (args.sort) {
case 'updated':
if (args.ascending) docs = docs.sort((docA, docB) => new Date(docA.updated).getTime() - new Date(docB.updated).getTime());
else docs = docs.sort((docA, docB) => new Date(docB.updated).getTime() - new Date(docA.updated).getTime());
break;
case 'installs':
if (args.ascending) docs = docs.sort((docA, docB) => docA.installs - docB.installs);
else docs = docs.sort((docA, docB) => docB.installs - docA.installs);
break;
case 'users':
if (args.ascending) docs = docs.sort((docA, docB) => docA.activeUsers - docB.activeUsers);
else docs = docs.sort((docA, docB) => docB.activeUsers - docA.activeUsers);
break;
case 'rating':
if (args.ascending) docs = docs.sort((docA, docB) => docA.rating - docB.rating);
else docs = docs.sort((docA, docB) => docB.rating - docA.rating);
break;
}
}
const total = docs.length;
const pages = Math.ceil(total / 9);
let page = 1;
if (args && args.page) {
page = args.page;
docs = docs.slice((page - 1) * 9, page * 9);
} else {
docs = docs.slice(0, 9);
}
return {
docs,
filters: {
sterm: args.sterm || '',
ascending: args.ascending || false,
sort: args.sort || 'name'
},
pagination: {
total,
pages,
limit: 9,
page
}
}
}
static async plugins(args) {
if (!this._plugins) this._plugins = this.generatePlugins();
await new Promise(r => setTimeout(r, Math.random() * 3000));
let docs = [];
if (args && args.sterm) {
const { sterm } = args;
const reg = new RegExp(sterm, 'gi');
docs = this._plugins.filter(doc => doc.tags.includes(sterm) || reg.exec(doc.name) || reg.exec(doc.description));
} else {
docs = this._plugins;
}
if (args.sort) {
switch (args.sort) {
case 'updated':
if (args.ascending) docs = docs.sort((docA, docB) => new Date(docA.updated).getTime() - new Date(docB.updated).getTime());
else docs = docs.sort((docA, docB) => new Date(docB.updated).getTime() - new Date(docA.updated).getTime());
break;
case 'installs':
if (args.ascending) docs = docs.sort((docA, docB) => docA.installs - docB.installs);
else docs = docs.sort((docA, docB) => docB.installs - docA.installs);
break;
case 'users':
if (args.ascending) docs = docs.sort((docA, docB) => docA.activeUsers - docB.activeUsers);
else docs = docs.sort((docA, docB) => docB.activeUsers - docA.activeUsers);
break;
case 'rating':
if (args.ascending) docs = docs.sort((docA, docB) => docA.rating - docB.rating);
else docs = docs.sort((docA, docB) => docB.rating - docA.rating);
break;
}
}
const total = docs.length;
const pages = Math.ceil(total / 9);
let page = 1;
if (args && args.page) {
page = args.page;
docs = docs.slice((page - 1) * 9, page * 9);
} else {
docs = docs.slice(0, 9);
}
return {
docs,
filters: {
sterm: args.sterm || '',
ascending: args.ascending || false,
sort: args.sort || 'name'
},
pagination: {
total,
pages,
limit: 9,
page
}
}
}
static generateThemes() {
const docs = [];
const count = Math.floor(Math.random() * 50 + 30);
for (let i = 0; i < count; i++) {
const id = `theme${i}-${this.randomId()}`;
const name = `Dummy Theme ${i}`;
const tags = dummyTags.sort(() => .5 - Math.random()).slice(0, 3);
docs.push({
id,
name,
tags,
installs: Math.floor(Math.random() * 5000) + 5000,
updated: this.randomTimestamp(),
rating: Math.floor(Math.random() * 500) + 500,
activeUsers: Math.floor(Math.random() * 1000) + 1000,
rated: Math.random() > .5,
version: this.randomVersion(),
repository: this.dummyThemeRepo,
files: this.dummyFiles,
author: this.dummyAuthor,
description: ''
});
}
return docs;
}
static generatePlugins() {
const docs = [];
const count = Math.floor(Math.random() * 50 + 30);
for (let i = 0; i < count; i++) {
const id = `plugin${i}-${this.randomId()}`;
const name = `Dummy Plugin ${i}`;
const tags = dummyTags.sort(() => .5 - Math.random()).slice(0, 3);
docs.push({
id,
name,
tags,
installs: Math.floor(Math.random() * 5000) + 5000,
updated: this.randomTimestamp(),
rating: Math.floor(Math.random() * 500) + 500,
activeUsers: Math.floor(Math.random() * 1000) + 1000,
rated: Math.random() > .5,
version: this.randomVersion(),
repository: this.dummyPluginRepo,
files: this.dummyFiles,
author: this.dummyAuthor,
description: ''
});
}
return docs;
}
static get dummyThemeRepo() {
return {
name: 'ExampleRepository',
baseUri: 'https://github.com/Jiiks/ExampleRepository',
rawUri: 'https://github.com/Jiiks/ExampleRepository/raw/master',
assetUri: 'https://api.github.com/repos/Jiiks/ExampleRepository/releases/assets/10023264'
}
}
static get dummyPluginRepo() {
return {
name: 'ExampleRepository',
baseUri: 'https://github.com/Jiiks/ExampleRepository',
rawUri: 'https://github.com/Jiiks/ExampleRepository/raw/master',
assetUri: 'https://api.github.com/repos/Jiiks/ExampleRepository/releases/assets/10023265'
}
}
static get dummyFiles() {
return {
readme: 'Example/readme.md',
previews: [{
large: 'Example/preview1-big.png',
thumb: 'Example/preview1-small.png'
}]
}
}
static get dummyAuthor() {
return 'Someone';
}
static randomId() {
return btoa(Math.random()).substring(3, 9);
}
static randomTimestamp() {
return `2018-${Math.floor((Math.random() * 12) + 1).toString().padStart(2, '0')}-${Math.floor((Math.random() * 30) + 1).toString().padStart(2, '0')}T14:51:32.057Z`;
}
static randomVersion() {
return `${Math.round(Math.random() * 3)}.${Math.round(Math.random() * 10)}.${Math.round(Math.random() * 10)}`;
}
}

View File

@ -8,17 +8,16 @@
* LICENSE file in the root directory of this source tree.
*/
import { DOM, BdUI, BdMenu, Modals, Toasts, Notifications, BdContextMenu, DiscordContextMenu } from 'ui';
import { DOM, BdUI, BdMenu, Modals, Toasts, Notifications, BdContextMenu, DiscordContextMenu, Autocomplete } from 'ui';
import BdCss from './styles/index.scss';
import { Events, CssEditor, Globals, Settings, Database, Updater, ModuleManager, PluginManager, ThemeManager, ExtModuleManager, Vendor, Patcher, MonkeyPatch, ReactComponents, ReactHelpers, ReactAutoPatcher, DiscordApi, BdWebApi, Connectivity, Cache, Reflection } from 'modules';
import { ClientLogger as Logger, ClientIPC, Utils } from 'common';
import { Events, Globals, Settings, Database, Updater, ModuleManager, PluginManager, ThemeManager, ExtModuleManager, Vendor, Patcher, MonkeyPatch, ReactComponents, ReactHelpers, ReactAutoPatcher, DiscordApi, BdWebApi, Connectivity, Cache, Reflection, PackageInstaller } from 'modules';
import { ClientLogger as Logger, ClientIPC, Utils, Axi } from 'common';
import { BuiltinManager, EmoteModule, ReactDevtoolsModule, VueDevtoolsModule, TrackingProtection, E2EE } from 'builtin';
import electron from 'electron';
import path from 'path';
import { setTimeout } from 'timers';
const tests = typeof PRODUCTION === 'undefined';
const ignoreExternal = false;
const ignoreExternal = tests && true;
class BetterDiscord {
@ -28,18 +27,18 @@ class BetterDiscord {
Logger.log('main', 'BetterDiscord starting');
this._bd = {
DOM, BdUI, BdMenu, Modals, Reflection, Toasts, Notifications, BdContextMenu, DiscordContextMenu,
DOM, BdUI, BdMenu, Modals, Reflection, Toasts, Notifications, BdContextMenu, DiscordContextMenu, Autocomplete,
Events, CssEditor, Globals, Settings, Database, Updater,
ModuleManager, PluginManager, ThemeManager, ExtModuleManager,
Events, Globals, Settings, Database, Updater,
ModuleManager, PluginManager, ThemeManager, ExtModuleManager, PackageInstaller,
Vendor,
Patcher, MonkeyPatch, ReactComponents, ReactHelpers, ReactAutoPatcher, DiscordApi,
EmoteModule,
BuiltinManager, EmoteModule,
BdWebApi,
Connectivity,
Cache,
Logger, ClientIPC, Utils,
Logger, ClientIPC, Utils, Axi,
plugins: PluginManager.localContent,
themes: ThemeManager.localContent,

View File

@ -8,6 +8,8 @@
* LICENSE file in the root directory of this source tree.
*/
import ServerEmu from '../dev/serveremu';
import { request } from 'vendor';
const APIBASE = 'ifyouareinwebtestthenyouknowwhatthisshouldbe'; // Do not push
@ -27,6 +29,12 @@ export default class BdWebApi {
};
}
static get plugins() {
return {
get: this.getPlugins
};
}
static get users() {
return {
get: this.getUsers
@ -41,11 +49,19 @@ export default class BdWebApi {
}
static getThemes(args) {
return ServerEmu.themes(args);
// return dummyThemes();
/*
if (!args) return request.get(ENDPOINTS.themes);
const { id } = args;
if (id) return request.get(ENDPOINTS.theme(id));
return request.get(ENDPOINTS.themes);
*/
}
static getPlugins(args) {
return ServerEmu.plugins(args);
}
static getUsers(args) {

View File

@ -65,6 +65,10 @@ export default class Content extends AsyncEventEmitter {
get config() { return this.settings.categories }
get data() { return this.userConfig.data || (this.userConfig.data = {}) }
get packed() { return this.dirName.packed }
get packagePath() { return this.dirName.packagePath }
get packageName() { return this.dirName.pkg }
/**
* Opens a settings modal for this content.
* @return {Modal}

View File

@ -8,14 +8,19 @@
* LICENSE file in the root directory of this source tree.
*/
import asar from 'asar';
import path, { dirname } from 'path';
import rimraf from 'rimraf';
import { remote } from 'electron';
import Content from './content';
import Globals from './globals';
import Database from './database';
import { Utils, FileUtils, ClientLogger as Logger } from 'common';
import { SettingsSet, ErrorEvent } from 'structs';
import { Modals } from 'ui';
import path from 'path';
import Combokeys from 'combokeys';
import Settings from './settings';
/**
* Base class for managing external content
@ -67,6 +72,28 @@ export default class {
return Globals.getPath(this.pathId);
}
static async packContent(path, contentPath) {
return new Promise((resolve, reject) => {
remote.dialog.showSaveDialog({
title: 'Save Package',
defaultPath: path,
filters: [
{
name: 'BetterDiscord Package',
extensions: ['bd']
}
]
}, filepath => {
if (!filepath) return;
asar.uncache(filepath);
asar.createPackage(contentPath, filepath, () => {
resolve(filepath);
});
});
});
}
/**
* Load all locally stored content.
* @param {bool} suppressErrors Suppress any errors that occur during loading of content
@ -77,12 +104,20 @@ export default class {
const directories = await FileUtils.listDirectory(this.contentPath);
for (const dir of directories) {
try {
await FileUtils.directoryExists(path.join(this.contentPath, dir));
} catch (err) { continue; }
const packed = dir.endsWith('.bd');
if (!packed) {
try {
await FileUtils.directoryExists(path.join(this.contentPath, dir));
} catch (err) { continue; }
}
try {
await this.preloadContent(dir);
if (packed) {
await this.preloadPackedContent(dir);
} else {
await this.preloadContent(dir);
}
} catch (err) {
this.errors.push(new ErrorEvent({
module: this.moduleName,
@ -120,6 +155,8 @@ export default class {
const directories = await FileUtils.listDirectory(this.contentPath);
for (const dir of directories) {
const packed = dir.endsWith('.bd');
// If content is already loaded this should resolve
if (this.getContentByDirName(dir)) continue;
@ -173,6 +210,31 @@ export default class {
}
}
static async preloadPackedContent(pkg, reload = false, index) {
try {
const packagePath = path.join(this.contentPath, pkg);
const packageName = pkg.replace('.bd', '');
await FileUtils.fileExists(packagePath);
const config = JSON.parse(asar.extractFile(packagePath, 'config.json').toString());
const unpackedPath = path.join(Globals.getPath('tmp'), packageName);
asar.extractAll(packagePath, unpackedPath);
return this.preloadContent({
config,
contentPath: unpackedPath,
packagePath: packagePath,
pkg,
packageName,
packed: true
}, reload, index);
} catch (err) {
Logger.log('ContentManager', ['Error extracting packed content', err]);
throw err;
}
}
/**
* Common loading procedure for loading content before passing it to the actual loader
* @param {any} dirName Base directory for content
@ -181,7 +243,15 @@ export default class {
*/
static async preloadContent(dirName, reload = false, index) {
try {
const contentPath = path.join(this.contentPath, dirName);
const unsafeAllowed = Settings.getSetting('security', 'default', 'unsafe-content').value;
const packed = typeof dirName === 'object' && dirName.packed;
// Block any unpacked content as they can't be verified
if (!packed && !unsafeAllowed) {
throw 'Blocked unsafe content';
}
const contentPath = packed ? dirName.contentPath : path.join(this.contentPath, dirName);
await FileUtils.directoryExists(contentPath);
@ -189,7 +259,7 @@ export default class {
throw { 'message': `Attempted to load already loaded user content: ${path}` };
const configPath = path.resolve(contentPath, 'config.json');
const readConfig = await FileUtils.readJsonFromFile(configPath);
const readConfig = packed ? dirName.config : await FileUtils.readJsonFromFile(configPath);
const mainPath = path.join(contentPath, readConfig.main || 'index.js');
const defaultConfig = new SettingsSet({
@ -213,7 +283,7 @@ export default class {
}
} catch (err) {
// We don't care if this fails it either means that user config doesn't exist or there's something wrong with it so we revert to default config
Logger.warn(this.moduleName, [`Failed reading config for ${this.contentType} ${readConfig.info.name} in ${dirName}`, err]);
Logger.warn(this.moduleName, [`Failed reading config for ${this.contentType} ${readConfig.info.name} in ${packed ? dirName.pkg : dirName}`, err]);
}
userConfig.config = defaultConfig.clone({ settings: userConfig.config });
@ -243,10 +313,10 @@ export default class {
mainPath
};
const content = await this.loadContent(paths, configs, readConfig.info, readConfig.main, readConfig.dependencies, readConfig.permissions, readConfig.mainExport);
const content = await this.loadContent(paths, configs, readConfig.info, readConfig.main, readConfig.dependencies, readConfig.permissions, readConfig.mainExport, packed ? dirName : false);
if (!content) return undefined;
if (!reload && this.getContentById(content.id))
throw {message: `A ${this.contentType} with the ID ${content.id} already exists.`};
throw { message: `A ${this.contentType} with the ID ${content.id} already exists.` };
if (reload) this.localContent.splice(index, 1, content);
else this.localContent.push(content);
@ -278,6 +348,7 @@ export default class {
await unload;
await FileUtils.recursiveDeleteDirectory(content.paths.contentPath);
if (content.packed) await FileUtils.recursiveDeleteDirectory(content.packagePath);
return true;
} catch (err) {
Logger.err(this.moduleName, err);
@ -309,14 +380,7 @@ export default class {
if (this.unloadContentHook) this.unloadContentHook(content);
if (reload) {
const newcontent = await this.preloadContent(content.dirName, true, index);
if (newcontent.enabled) {
newcontent.userConfig.enabled = false;
newcontent.start(false);
}
return newcontent;
}
if (reload) return content.packed ? this.preloadPackedContent(content.packagePath, true, index) : this.preloadContent(content.dirName, true, index);
this.localContent.splice(index, 1);
} catch (err) {

View File

@ -38,6 +38,14 @@ export default new class {
ClientIPC.on('bd-get-scss', () => this.scss, true);
ClientIPC.on('bd-update-scss', (e, scss) => this.updateScss(scss));
ClientIPC.on('bd-save-csseditor-bounds', (e, bounds) => this.saveEditorBounds(bounds));
ClientIPC.on('bd-editor-runScript', (e, script) => {
try {
new Function(script)();
e.reply('ok');
} catch (err) {
e.reply({ err: err.stack || err });
}
});
ClientIPC.on('bd-save-scss', async (e, scss) => {
await this.updateScss(scss);

View File

@ -126,6 +126,7 @@ export default class DiscordApi {
static get currentGuild() {
const guild = Modules.GuildStore.getGuild(Modules.SelectedGuildStore.getGuildId());
if (guild) return Guild.from(guild);
return null;
}
/**
@ -135,6 +136,7 @@ export default class DiscordApi {
static get currentChannel() {
const channel = Modules.ChannelStore.getChannel(Modules.SelectedChannelStore.getChannelId());
if (channel) return Channel.from(channel);
return null;
}
/**
@ -144,6 +146,7 @@ export default class DiscordApi {
static get currentUser() {
const user = Modules.UserStore.getCurrentUser();
if (user) return User.from(user);
return null;
}
/**

View File

@ -0,0 +1,66 @@
/**
* BetterDiscord Editor Module
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
* All rights reserved.
* https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import Module from './imodule';
import { DOM } from 'ui';
export default new class extends Module {
get name() { return 'Editor' }
get delay() { return false; }
setInitialState(state) {
return {
editorBounds: undefined
};
}
initialize() {
super.initialize();
(async () => {
try {
// TODO this is temporary
const userScss = await this.send('readDataFile', 'user.scss');
const compiled = await this.send('compileSass', { data: userScss });
this.injectStyle('customcss', compiled.css.toString());
} catch (err) {
console.warn('SCSS Compilation error', err);
}
})();
}
events(ipc) {
ipc.on('editor-runScript', (e, script) => {
try {
new Function(script)();
e.reply('ok');
} catch (err) {
e.reply({ err: err.stack || err });
}
});
ipc.on('editor-injectStyle', (e, { id, style }) => {
this.injectStyle(id, style);
e.reply('ok');
});
}
injectStyle(id, style) {
return DOM.injectStyle(style, `userstyle-${id}`);
}
/**
* Show editor, flashes if already visible.
*/
async show() {
await this.send('editor-open', this.state.editorBounds);
}
}

View File

@ -8,6 +8,7 @@
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import sparkplug from 'sparkplug';
import { ClientIPC } from 'common';
import Module from './module';
@ -97,7 +98,7 @@ export default new class extends Module {
}
get version() {
return this.config.version;
return this.config.versions.core;
}
}

View File

@ -0,0 +1,73 @@
/**
* BetterDiscord Module Base
* Copyright (c) 2015-present JsSucks - https://github.com/JsSucks
* All rights reserved.
* https://github.com/JsSucks - https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* Base Module that every non-static module should extend
*/
import { ClientLogger as Logger, ClientIPC } from 'common';
export default class Module {
constructor(args) {
this.__ = {
state: args || {},
args
};
this.setState = this.setState.bind(this);
if (this.delay) { // If delay is set then module is set to load delayed from modulemanager
this.initialize = this.initialize.bind(this);
this.init = this.initialize;
} else {
this.initialize();
this.init = () => { };
}
}
initialize() {
if (this.bindings) this.bindings();
if (this.setInitialState) this.setState(this.setInitialState(this.state));
if (this.events) this.events(ClientIPC);
}
setState(newState) {
const oldState = Object.assign({}, this.state);
Object.assign(this.state, newState);
if (this.stateChanged) this.stateChanged(oldState, newState);
}
set args(t) { }
get args() { return this.__.args; }
set state(state) { return this.__.state = state; }
get state() { return this.__.state; }
async send(channel, message) {
return ClientIPC.send(channel, message);
}
log(msg) {
Logger.log(this.name, msg);
}
warn(msg) {
Logger.log(this.name, msg);
}
err(msg) {
Logger.log(this.name, msg);
}
info(msg) {
Logger.log(this.name, msg);
}
}

View File

@ -9,7 +9,7 @@
*/
import { ClientLogger as Logger } from 'common';
import { SocketProxy, EventHook, CssEditor } from 'modules';
import { SocketProxy, EventHook } from 'modules';
import { ProfileBadges, ClassNormaliser } from 'ui';
import Updater from './updater';
@ -27,7 +27,6 @@ export default class {
new ClassNormaliser(),
new SocketProxy(),
new EventHook(),
CssEditor,
Updater
]);
}

View File

@ -1,5 +1,6 @@
export { default as Events } from './events';
export { default as CssEditor } from './csseditor';
export { default as Editor } from './editor';
export { default as Globals } from './globals';
export { default as Settings } from './settings';
export { default as Database } from './database';
@ -27,3 +28,4 @@ export { default as Connectivity } from './connectivity';
export { default as Security } from './security';
export { default as Cache } from './cache';
export { default as Reflection } from './reflection/index';
export { default as PackageInstaller } from './packageinstaller';

View File

@ -0,0 +1,178 @@
import EventListener from './eventlistener';
import asar from 'asar';
import fs from 'fs';
import path from 'path';
import rimraf from 'rimraf';
import { request } from 'vendor';
import { Modals } from 'ui';
import { Utils, FileUtils } from 'common';
import PluginManager from './pluginmanager';
import Globals from './globals';
import Security from './security';
import Reflection from './reflection';
import DiscordApi from './discordapi';
import ThemeManager from './thememanager';
import { MonkeyPatch } from './patcher';
import { DOM } from 'ui';
export default class PackageInstaller {
/**
* Handler for drag and drop package install
* @param {String} filePath Path to local file
* @param {Boolean} canUpload If the user can upload files in current window
* @returns {Number} returns action code from modal
*/
static async dragAndDropHandler(filePath, canUpload) {
try {
const config = JSON.parse(asar.extractFile(filePath, 'config.json').toString());
const { info, main } = config;
let icon = null;
if (info.icon && info.icon_type) {
const extractIcon = asar.extractFile(filePath, info.icon);
icon = `data:${info.icon_type};base64,${Utils.arrayBufferToBase64(extractIcon)}`;
}
const isPlugin = info.type && info.type === 'plugin' || main.endsWith('.js');
// Show install modal
const modalResult = await Modals.installModal(isPlugin ? 'plugin' : 'theme', config, filePath, icon, canUpload).promise;
return modalResult;
} catch (err) {
console.log(err);
}
}
/**
* Hash and verify a package
* @param {Byte[]|String} bytesOrPath byte array of binary or path to local file
* @param {String} id Package id
*/
static async verifyPackage(bytesOrPath, id) {
const bytes = typeof bytesOrPath === 'string' ? fs.readFileSync(bytesOrPath) : bytesOrPath;
// Temporary hash to simulate response from server
const tempVerified = ['2e3532ee366816adc37b0f478bfef35e03f96e7aeee9b115f5918ef6a4e94de8', '06a2eb4e37b926354ab80cd83207db67e544c932e9beddce545967a21f8db5aa'];
const hashBytes = Security.hash('sha256', bytes, 'hex');
return tempVerified.includes(hashBytes);
}
// TODO lots of stuff
/**
* Installs or updates defined package
* @param {Byte[]|String} bytesOrPath byte array of binary or path to local file
* @param {String} nameOrId Package name
* @param {Boolean} update Does an older version already exist
*/
static async installPackage(bytesOrPath, nameOrId, contentType, update = false) {
let outputPath = null;
try {
const bytes = typeof bytesOrPath === 'string' ? fs.readFileSync(bytesOrPath) : bytesOrPath;
const outputName = `${nameOrId}.bd`;
outputPath = path.join(Globals.getPath(`${contentType}s`), outputName);
fs.writeFileSync(outputPath, bytes);
const manager = contentType === 'plugin' ? PluginManager : ThemeManager;
if (!update) return manager.preloadPackedContent(outputName);
const oldContent = manager.findContent(nameOrId);
await oldContent.unload(true);
if (oldContent.packed && oldContent.packageName !== nameOrId) {
await FileUtils.deleteFile(oldContent.packagePath).catch(err => null);
}
await FileUtils.recursiveDeleteDirectory(oldContent.contentPath).catch(err => null);
return manager.preloadPackedContent(outputName);
} catch (err) {
throw err;
}
}
/**
* Install package from remote location. Only github/bdapi is supported.
* @param {String} remoteLocation Remote resource location
*/
static async installRemotePackage(remoteLocation) {
try {
const { hostname } = Object.assign(document.createElement('a'), { href: remoteLocation });
if (hostname !== 'api.github.com' && hostname !== 'secretbdapi') throw 'Invalid host!';
const options = {
uri: remoteLocation,
encoding: null,
headers: {
'User-Agent': 'BetterDiscordClient',
'Accept': 'application/octet-stream'
}
};
const response = await request.get(options);
const outputPath = path.join(Globals.getPath('tmp'), Security.hash('sha256', response, 'hex'));
fs.writeFileSync(outputPath, response);
console.log('response', response);
console.log('output', outputPath);
await this.dragAndDropHandler(outputPath);
rimraf(outputPath, err => {
if (err) console.log(err);
});
} catch (err) {
throw err;
}
}
static async handleDrop(stateNode, e, original) {
if (!e.dataTransfer.files.length || !e.dataTransfer.files[0].name.endsWith('.bd')) return original && original.call(stateNode, e);
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
if (stateNode) stateNode.clearDragging();
const currentChannel = DiscordApi.currentChannel;
const canUpload = currentChannel ?
currentChannel.checkPermissions(Reflection.modules.DiscordConstants.Permissions.SEND_MESSAGES) &&
currentChannel.checkPermissions(Reflection.modules.DiscordConstants.Permissions.ATTACH_FILES) : false;
const files = Array.from(e.dataTransfer.files).slice(0);
const actionCode = await this.dragAndDropHandler(e.dataTransfer.files[0].path, canUpload);
if (actionCode === 0 && stateNode) stateNode.promptToUpload(files, currentChannel.id, true, !e.shiftKey);
}
/**
* Patches Discord upload area for .bd files
*/
static async uploadAreaPatch(UploadArea) {
// Add a listener to root for when not in a channel
const root = DOM.getElement('#app-mount');
const rootHandleDrop = this.handleDrop.bind(this, undefined);
root.addEventListener('drop', rootHandleDrop);
const unpatchUploadAreaHandleDrop = MonkeyPatch('BD:ReactComponents', UploadArea.component.prototype).instead('handleDrop', (component, [e], original) => this.handleDrop(component, e, original));
this.unpatchUploadArea = () => {
unpatchUploadAreaHandleDrop();
root.removeEventListener('drop', rootHandleDrop);
this.unpatchUploadArea = undefined;
};
for (const element of document.querySelectorAll(UploadArea.important.selector)) {
const stateNode = Reflection.DOM(element).getComponentStateNode(UploadArea);
element.removeEventListener('drop', stateNode.handleDrop);
stateNode.handleDrop = UploadArea.component.prototype.handleDrop.bind(stateNode);
element.addEventListener('drop', stateNode.handleDrop);
stateNode.forceUpdate();
}
}
}

View File

@ -74,7 +74,7 @@ export default class extends ContentManager {
static get refreshPlugins() { return this.refreshContent }
static get loadContent() { return this.loadPlugin }
static async loadPlugin(paths, configs, info, main, dependencies, permissions, mainExport) {
static async loadPlugin(paths, configs, info, main, dependencies, permissions, mainExport, packed = false) {
if (permissions && permissions.length > 0) {
for (const perm of permissions) {
Logger.log(this.moduleName, `Permission: ${Permissions.permissionText(perm).HEADER} - ${Permissions.permissionText(perm).BODY}`);
@ -109,12 +109,7 @@ export default class extends ContentManager {
throw {message: `Plugin ${info.name} did not return a class that extends Plugin.`};
const instance = new plugin({
configs, info, main,
paths: {
contentPath: paths.contentPath,
dirName: paths.dirName,
mainPath: paths.mainPath
}
configs, info, main, paths
});
if (instance.enabled && this.loaded) {

View File

@ -15,6 +15,7 @@ import { Utils, Filters, ClientLogger as Logger } from 'common';
import { MonkeyPatch } from './patcher';
import Reflection from './reflection/index';
import DiscordApi from './discordapi';
import PackageInstaller from './packageinstaller';
class Helpers {
static get plannedActions() {
@ -130,7 +131,8 @@ class Helpers {
static findByProp(obj, what, value) {
if (obj.hasOwnProperty(what) && obj[what] === value) return obj;
if (obj.props && !obj.children) return this.findByProp(obj.props, what, value);
if (!obj.children || !obj.children.length) return null;
if (!obj.children) return null;
if (!(obj.children instanceof Array)) return this.findByProp(obj.children, what, value);
for (const child of obj.children) {
if (!child) continue;
const findInChild = this.findByProp(child, what, value);
@ -171,9 +173,18 @@ class ReactComponent {
this.important = important;
}
get elements() {
if (!this.important || !this.important.selector) return [];
return document.querySelectorAll(this.important.selector);
}
get stateNodes() {
return [...this.elements].map(e => Reflection.DOM(e).getComponentStateNode(this));
}
forceUpdateAll() {
if (!this.important || !this.important.selector) return;
for (const e of document.querySelectorAll(this.important.selector)) {
for (const e of this.elements) {
Reflection.DOM(e).forceUpdate(this);
}
}
@ -184,6 +195,7 @@ export class ReactComponents {
static get unknownComponents() { return this._unknownComponents || (this._unknownComponents = []) }
static get listeners() { return this._listeners || (this._listeners = []) }
static get nameSetters() { return this._nameSetters || (this._nameSetters = []) }
static get componentAliases() { return this._componentAliases || (this._componentAliases = []) }
static get ReactComponent() { return ReactComponent }
@ -220,6 +232,8 @@ export class ReactComponents {
* @return {Promise => ReactComponent}
*/
static async getComponent(name, important, filter) {
name = this.getComponentName(name);
const have = this.components.find(c => c.id === name);
if (have) return have;
@ -237,7 +251,13 @@ export class ReactComponents {
let component, reflect;
for (const element of elements) {
reflect = Reflection.DOM(element);
component = filter ? reflect.components.find(filter) : reflect.component;
component = filter ? reflect.components.find(component => {
try {
return filter.call(undefined, component);
} catch (err) {
return false;
}
}) : reflect.component;
if (component) break;
}
@ -274,6 +294,19 @@ export class ReactComponents {
});
}
static getComponentName(name) {
const resolvedAliases = [];
while (this.componentAliases[name]) {
resolvedAliases.push(name);
name = this.componentAliases[name];
if (resolvedAliases.includes(name)) break;
}
return name;
}
static setName(name, filter) {
const have = this.components.find(c => c.id === name);
if (have) return have;
@ -349,6 +382,21 @@ export class ReactAutoPatcher {
this.Message.forceUpdateAll();
}
static async patchMessageContent() {
const { selector } = Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited');
this.MessageContent = await ReactComponents.getComponent('MessageContent', {selector}, c => c.defaultProps && c.defaultProps.hasOwnProperty('disableButtons'));
}
static async patchSpoiler() {
const { selector } = Reflection.resolve('spoilerText', 'spoilerContainer');
this.Spoiler = await ReactComponents.getComponent('Spoiler', {selector}, c => c.prototype.renderSpoilerText);
}
static async patchMessageAccessories() {
const { selector } = Reflection.resolve('container', 'containerCozy', 'embedWrapper');
this.MessageAccessories = await ReactComponents.getComponent('MessageAccessories', {selector});
}
static async patchMessageGroup() {
const { selector } = Reflection.resolve('container', 'message', 'messageCozy');
this.MessageGroup = await ReactComponents.getComponent('MessageGroup', {selector});
@ -367,7 +415,16 @@ export class ReactAutoPatcher {
this.MessageGroup.forceUpdateAll();
}
static async patchImageWrapper() {
ReactComponents.componentAliases.ImageWrapper = 'Image';
const { selector } = Reflection.resolve('imageWrapper');
this.ImageWrapper = await ReactComponents.getComponent('ImageWrapper', {selector}, c => typeof c.defaultProps.children === 'function');
}
static async patchChannelMember() {
ReactComponents.componentAliases.ChannelMember = 'MemberListItem';
const { selector } = Reflection.resolve('member', 'memberInner', 'activity');
this.ChannelMember = await ReactComponents.getComponent('ChannelMember', {selector}, m => m.prototype.renderActivity);
@ -383,8 +440,13 @@ export class ReactAutoPatcher {
this.ChannelMember.forceUpdateAll();
}
static async patchNameTag() {
const { selector } = Reflection.resolve('nameTag', 'username', 'discriminator', 'ownerIcon');
this.NameTag = await ReactComponents.getComponent('NameTag', {selector});
}
static async patchGuild() {
const selector = `div.${Reflection.resolve('guild', 'guildsWrapper').className}:not(:first-child)`;
const selector = `div.${Reflection.resolve('container', 'guildIcon', 'selected', 'unread').className}:not(:first-child)`;
this.Guild = await ReactComponents.getComponent('Guild', {selector}, m => m.prototype.renderBadge);
this.unpatchGuild = MonkeyPatch('BD:ReactComponents', this.Guild.component.prototype).after('render', (component, args, retVal) => {
@ -401,7 +463,7 @@ export class ReactAutoPatcher {
* The Channel component contains the header, message scroller, message form and member list.
*/
static async patchChannel() {
const selector = '.chat';
const { selector } = Reflection.resolve('chat', 'title', 'channelName');
this.Channel = await ReactComponents.getComponent('Channel', {selector});
this.unpatchChannel = MonkeyPatch('BD:ReactComponents', this.Channel.component.prototype).after('render', (component, args, retVal) => {
@ -417,10 +479,17 @@ export class ReactAutoPatcher {
this.Channel.forceUpdateAll();
}
static async patchChannelTextArea() {
const { selector } = Reflection.resolve('channelTextArea', 'autocomplete');
this.ChannelTextArea = await ReactComponents.getComponent('ChannelTextArea', {selector});
}
/**
* The GuildTextChannel component represents a text channel in the guild channel list.
*/
static async patchGuildTextChannel() {
ReactComponents.componentAliases.GuildTextChannel = 'TextChannel';
const { selector } = Reflection.resolve('containerDefault', 'actionIcon');
this.GuildTextChannel = await ReactComponents.getComponent('GuildTextChannel', {selector}, c => c.prototype.renderMentionBadge);
@ -433,6 +502,8 @@ export class ReactAutoPatcher {
* The GuildVoiceChannel component represents a voice channel in the guild channel list.
*/
static async patchGuildVoiceChannel() {
ReactComponents.componentAliases.GuildVoiceChannel = 'VoiceChannel';
const { selector } = Reflection.resolve('containerDefault', 'actionIcon');
this.GuildVoiceChannel = await ReactComponents.getComponent('GuildVoiceChannel', {selector}, c => c.prototype.handleVoiceConnect);
@ -445,7 +516,9 @@ export class ReactAutoPatcher {
* The DirectMessage component represents a channel in the direct messages list.
*/
static async patchDirectMessage() {
const selector = '.channel.private';
ReactComponents.componentAliases.DirectMessage = 'PrivateChannel';
const { selector } = Reflection.resolve('channel', 'avatar', 'name');
this.DirectMessage = await ReactComponents.getComponent('DirectMessage', {selector}, c => c.prototype.renderAvatar);
this.unpatchDirectMessage = MonkeyPatch('BD:ReactComponents', this.DirectMessage.component.prototype).after('render', this._afterChannelRender);
@ -467,15 +540,18 @@ export class ReactAutoPatcher {
}
static async patchUserProfileModal() {
ReactComponents.componentAliases.UserProfileModal = 'UserProfileBody';
const { selector } = Reflection.resolve('root', 'topSectionNormal');
this.UserProfileModal = await ReactComponents.getComponent('UserProfileModal', {selector}, Filters.byPrototypeFields(['renderHeader', 'renderBadges']));
this.UserProfileModal = await ReactComponents.getComponent('UserProfileModal', {selector}, c => c.prototype.renderHeader && c.prototype.renderBadges);
this.unpatchUserProfileModal = MonkeyPatch('BD:ReactComponents', this.UserProfileModal.component.prototype).after('render', (component, args, retVal) => {
const root = retVal.props.children[0] || retVal.props.children;
const { user } = component.props;
if (!user) return;
retVal.props['data-user-id'] = user.id;
if (user.bot) retVal.props.className += ' bd-isBot';
if (user.id === DiscordApi.currentUser.id) retVal.props.className += ' bd-isCurrentUser';
root.props['data-user-id'] = user.id;
if (user.bot) root.props.className += ' bd-isBot';
if (user.id === DiscordApi.currentUser.id) root.props.className += ' bd-isCurrentUser';
});
this.UserProfileModal.forceUpdateAll();
@ -483,18 +559,19 @@ export class ReactAutoPatcher {
static async patchUserPopout() {
const { selector } = Reflection.resolve('userPopout', 'headerNormal');
this.UserPopout = await ReactComponents.getComponent('UserPopout', {selector});
this.UserPopout = await ReactComponents.getComponent('UserPopout', {selector}, c => c.prototype.renderHeader);
this.unpatchUserPopout = MonkeyPatch('BD:ReactComponents', this.UserPopout.component.prototype).after('render', (component, args, retVal) => {
const root = retVal.props.children[0] || retVal.props.children;
const { user, guild, guildMember } = component.props;
if (!user) return;
retVal.props['data-user-id'] = user.id;
if (user.bot) retVal.props.className += ' bd-isBot';
if (user.id === DiscordApi.currentUser.id) retVal.props.className += ' bd-isCurrentUser';
if (guild) retVal.props['data-guild-id'] = guild.id;
if (guild && user.id === guild.ownerId) retVal.props.className += ' bd-isGuildOwner';
if (guild && guildMember) retVal.props.className += ' bd-isGuildMember';
if (guildMember && guildMember.roles.length) retVal.props.className += ' bd-hasRoles';
root.props['data-user-id'] = user.id;
if (user.bot) root.props.className += ' bd-isBot';
if (user.id === DiscordApi.currentUser.id) root.props.className += ' bd-isCurrentUser';
if (guild) root.props['data-guild-id'] = guild.id;
if (guild && user.id === guild.ownerId) root.props.className += ' bd-isGuildOwner';
if (guild && guildMember) root.props.className += ' bd-isGuildMember';
if (guildMember && guildMember.roles.length) root.props.className += ' bd-hasRoles';
});
this.UserPopout.forceUpdateAll();
@ -504,25 +581,6 @@ export class ReactAutoPatcher {
const { selector } = Reflection.resolve('uploadArea');
this.UploadArea = await ReactComponents.getComponent('UploadArea', {selector});
const reflect = Reflection.DOM(selector);
const stateNode = reflect.getComponentStateNode(this.UploadArea);
const callback = function(e) {
if (!e.dataTransfer.files.length || !e.dataTransfer.files[0].name.endsWith('.bd')) return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
stateNode.clearDragging();
Modals.confirm('Function not ready', `You tried to install "${e.dataTransfer.files[0].path}", but installing .bd files isn't ready yet.`)
// Possibly something like Events.emit('install-file', e.dataTransfer.files[0]);
};
// Remove their handler, add ours, then readd theirs to give ours priority to stop theirs when we get a .bd file.
reflect.element.removeEventListener('drop', stateNode.handleDrop);
reflect.element.addEventListener('drop', callback);
reflect.element.addEventListener('drop', stateNode.handleDrop);
this.unpatchUploadArea = function() {
reflect.element.removeEventListener('drop', callback);
};
PackageInstaller.uploadAreaPatch(this.UploadArea);
}
}

View File

@ -288,7 +288,7 @@ class Module {
if (this._require) return this._require;
const __webpack_require__ = this.getWebpackRequire();
if (!__webpack_require__) return;
if (!__webpack_require__) return null;
this.hookWebpackRequireCache(__webpack_require__);
return this._require = __webpack_require__;

View File

@ -272,7 +272,7 @@ class WebpackModules {
if (this._require) return this._require;
const __webpack_require__ = this.getWebpackRequire();
if (!__webpack_require__) return;
if (!__webpack_require__) return null;
this.hookWebpackRequireCache(__webpack_require__);
return this._require = __webpack_require__;

View File

@ -69,10 +69,11 @@ export default class Theme extends Content {
path: this.paths.mainPath.replace(/\\/g, '/')
});
// Why are these getters?
Logger.log(this.name, ['Finished compiling theme', new class Info {
get SCSS_variables() { console.log(config); }
get Compiled_SCSS() { console.log(result.css.toString()); }
get Result() { console.log(result); }
get SCSS_variables() { console.log(config); return ''; }
get Compiled_SCSS() { console.log(result.css.toString()); return ''; }
get Result() { console.log(result); return ''; }
}]);
return {
@ -146,6 +147,11 @@ export default class Theme extends Content {
* @param {Array} files Files to watch
*/
set watchfiles(files) {
if (this.packed) {
// Don't watch files for packed themes
return;
}
if (!files) files = [];
for (const file of files) {

View File

@ -36,12 +36,7 @@ export default class ThemeManager extends ContentManager {
static async loadTheme(paths, configs, info, main) {
try {
const instance = new Theme({
configs, info, main,
paths: {
contentPath: paths.contentPath,
dirName: paths.dirName,
mainPath: paths.mainPath
}
configs, info, main, paths
});
if (instance.enabled) {
instance.userConfig.enabled = false;

View File

@ -8,81 +8,183 @@
* LICENSE file in the root directory of this source tree.
*/
import Events from './events';
import Globals from './globals';
import { ClientLogger as Logger } from 'common';
import request from 'request-promise-native';
import { Notifications } from 'ui';
import { Reflection, Globals } from 'modules';
export default new class {
import Events from './events';
import Module from './imodule';
export default new class extends Module {
get updates() { return this.state.updates }
get bdUpdates() { return this.state.updates.bd }
get error() { return null; }
get updatesAvailable() { return this.state.updatesAvailable; }
constructor() {
this.updatesAvailable = false;
this.latestVersion = undefined;
this.error = undefined;
this.init = this.init.bind(this);
this.checkForUpdates = this.checkForUpdates.bind(this);
super({
updatesAvailable: false,
error: null,
updates: { bd: [] },
updating: false
});
}
/**
* The interval to wait before checking for updates.
*/
get interval() {
return 60 * 1000 * 30;
bindings() {
this.restartNotif = this.restartNotif.bind(this);
this.reloadNotif = this.reloadNotif.bind(this);
this.startUpdate = this.startUpdate.bind(this);
this.setUpdateStatus = this.setUpdateStatus.bind(this);
this.testUi = this.testUi.bind(this);
}
init() {
this.updateInterval = setInterval(this.checkForUpdates, this.interval);
restartNotif() {
Notifications.add('Updates Finished!', 'Restart required.', [
{
text: 'Restart Later',
onClick: () => { setTimeout(this.restartNotif, 5 * 60000); return true; }
},
{
text: 'Restart Now',
onClick: () => {
try {
const { remote } = Globals.require('electron');
window.close();
Reflection.module.byProps('showToken', 'hideToken').showToken();
remote.app.relaunch();
remote.app.exit(0);
} catch (err) {
console.err(err);
return true;
}
}
}
]);
}
/**
* Installs an update.
* TODO
*/
async update() {
try {
await new Promise(resolve => setTimeout(resolve, 5000));
this.updatesAvailable = false;
this.latestVersion = Globals.version;
Events.emit('update-check-end');
} catch (err) {
this.error = err;
this.checkForUpdates();
throw err;
}
reloadNotif() {
Notifications.add('Updates Finished!', 'Reload required.', [
{
text: 'Reload Later',
onClick: () => { setTimeout(this.reloadNotif, 5 * 60000); return true; }
},
{
text: 'Reload Now',
onClick: () => {
document.location.reload();
}
}
]);
}
/**
* Checks for updates.
* @return {Promise}
*/
async checkForUpdates() {
if (this.updatesAvailable) return true;
Events.emit('update-check-start');
Logger.info('Updater', 'Checking for updates');
events(ipc) {
ipc.on('updater-checkForUpdates', () => {
if (this.state.updating) return; // We're already updating. Updater should be paused anyways at this point.
Events.emit('update-check-start');
});
try {
const response = await request({
uri: 'https://rawgit.com/JsSucks/BetterDiscordApp/master/package.json',
json: true
ipc.on('updater-noUpdates', () => {
if (this.state.updatesAvailable) return; // If for some reason we get this even though we have updates already.
this.setState({
updatesAvailable: false,
updates: {}
});
});
this.latestVersion = response.version;
Events.emit('update-check-end');
Logger.info('Updater', `Latest Version: ${response.version} - Current Version: ${Globals.version}`);
ipc.on('updater-updatesAvailable', (_, updates) => {
console.log(updates);
if (this.state.updating) return; // If for some reason we get more updates when we're already updating
updates.bd = updates.bd.map(update => {
update.text = `${update.id.charAt(0).toUpperCase()}${update.id.slice(1)}`;
update.hint = `Current: ${update.currentVersion} | Latest: ${update.version}`;
update.status = {
update: true,
updating: false,
updated: false,
error: null
};
if (this.latestVersion !== Globals.version) {
this.updatesAvailable = true;
Events.emit('updates-available');
return true;
return update;
});
this.setState({
updates,
updatesAvailable: true
});
});
ipc.on('updater-updated', (_, info) => {
const { reloadRequired, restartRequired } = info;
if (restartRequired) {
this.restartNotif();
return;
}
return false;
} catch (err) {
Events.emit('update-check-fail', err);
throw err;
if (reloadRequired) {
this.reloadNotif();
return;
}
});
ipc.on('updater-updateFinished', (_, update) => {
this.setUpdateStatus(update.id, 'updated', true);
});
ipc.on('updater-updateError', (_, update) => {
this.setUpdateStatus(update.id, 'error', update.error);
});
}
stateChanged(oldState, newState) {
if (!newState.updatesAvailable) return Events.emit('update-check-end');
if (!oldState.updatesAvailable && newState.updatesAvailable) {
Events.emit('updates-available');
Notifications.add('', 'Updates Available!', [
{
text: 'Ignore',
onClick: () => { return true; }
},
{
text: 'Show Updates',
onClick: () => {
Events.emit('bd-open-menu', 'updater');
return true;
}
}
]);
}
}
setUpdateStatus(updateId, statusChild, statusValue) {
for (const u of this.bdUpdates) {
if (u.id === updateId) {
u.status[statusChild] = statusValue;
return;
}
}
}
toggleUpdate(update) {
update.status.update = !update.status.update;
}
async startUpdate() {
console.log('start update');
const updates = { bd: [] };
for (const update of this.bdUpdates) {
if (update.status.update) {
update.status.updating = true;
updates.bd.push(update);
}
}
console.log(updates);
this.send('updater-startUpdate', updates);
}
testUi(updates) {
this.setState({
updates,
updatesAvailable: true
});
}
}

View File

@ -11,6 +11,7 @@
import jQuery from 'jquery';
import lodash from 'lodash';
import Vue from 'vue';
import { Axi } from 'common';
import request from 'request-promise-native';
@ -40,6 +41,8 @@ export default class {
*/
static get Vue() { return Vue }
static get axios() { return Axi.axios }
static get request() { return request }
static get Combokeys() { return Combokeys }

View File

@ -179,7 +179,7 @@ export class PermissionOverwrite {
}
get guild() {
if (this.channel) return this.channel.guild;
return this.channel ? this.channel.guild : null;
}
}
@ -187,7 +187,7 @@ export class RolePermissionOverwrite extends PermissionOverwrite {
get roleId() { return this.discordObject.id }
get role() {
if (this.guild) return this.guild.roles.find(r => r.id === this.roleId);
return this.guild ? this.guild.roles.find(r => r.id === this.roleId) : null;
}
}

View File

@ -166,14 +166,14 @@ export class Guild {
* The guild's AFK channel.
*/
get afkChannel() {
if (this.afkChannelId) return Channel.fromId(this.afkChannelId);
return this.afkChannelId ? Channel.fromId(this.afkChannelId) : null;
}
/**
* The channel system messages are sent to.
*/
get systemChannel() {
if (this.systemChannelId) return Channel.fromId(this.systemChannelId);
return this.systemChannelId ? Channel.fromId(this.systemChannelId) : null;
}
/**

View File

@ -42,11 +42,11 @@ export class Reaction {
}
get message() {
if (this.channel) return this.channel.messages.find(m => m.id === this.messageId);
return this.channel ? this.channel.messages.find(m => m.id === this.messageId) : null;
}
get guild() {
if (this.channel) return this.channel.guild;
return this.channel ? this.channel.guild : null;
}
}
@ -84,11 +84,11 @@ export class Embed {
}
get message() {
if (this.channel) return this.channel.messages.find(m => m.id === this.messageId);
return this.channel ? this.channel.messages.find(m => m.id === this.messageId) : null;
}
get guild() {
if (this.channel) return this.channel.guild;
return this.channel ? this.channel.guild : null;
}
}
@ -143,6 +143,7 @@ export class Message {
get author() {
if (this.discordObject.author && !this.webhookId) return User.from(this.discordObject.author);
return null;
}
get channel() {
@ -150,7 +151,7 @@ export class Message {
}
get guild() {
if (this.channel) return this.channel.guild;
return this.channel ? this.channel.guild : null;
}
/**
@ -202,7 +203,7 @@ export class DefaultMessage extends Message {
get application() { return this.discordObject.application }
get webhook() {
if (this.webhookId) return this.discordObject.author;
return this.webhookId ? this.discordObject.author : null;
}
get mentions() {

View File

@ -77,7 +77,7 @@ export class User {
get note() {
const note = Modules.UserNoteStore.getNote(this.id);
if (note) return note;
return note ? note : null;
}
/**

View File

@ -17,16 +17,45 @@
}
.bd-settingsButtonBtn {
background-image: $logoSmallBw;
background-size: 50% 50%;
background-repeat: no-repeat;
background-position: center;
width: 100%;
height: 100%;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
width: 70px;
height: 48px;
cursor: pointer;
filter: grayscale(100%);
opacity: .5;
transition: all .4s ease-in-out;
position: relative;
transition: all .3s cubic-bezier(.4, 0, 0, 1);
&::before,
&::after {
content: '';
display: block;
position: absolute;
top: 0;
left: 0;
background-repeat: no-repeat;
background-position: center;
}
&::before {
width: 70px;
height: 48px;
background-image: $logoSmallBw;
background-size: 50% 50%;
opacity: 1;
transition: all .3s cubic-bezier(.4, 0, 0, 1), opacity .01s;
}
&::after {
width: 130px;
height: 43px;
background-image: $logoBigBw;
background-size: 100% 100%;
transform: translate(-7px, 2px) scale(.5);
opacity: 0;
transition: all .3s cubic-bezier(.4, 0, 0, 1);
}
&:not(.bd-loading) {
&:hover {
@ -69,14 +98,23 @@
box-shadow: none;
.bd-settingsButtonBtn {
background-image: $logoBigBw;
background-size: 100% 100%;
filter: none;
opacity: 1;
width: 130px;
height: 43px;
margin: 18px 0 17px 25px;
transform: translate(25px, 18px);
cursor: default;
&::before {
opacity: 0;
transform: translate(-16px, -3px) scale(1.9);
transition: all .3s cubic-bezier(.4, 0, 0, 1), opacity .1s .3s;
}
&::after {
opacity: 1;
transform: scale(1);
}
}
}

View File

@ -1,9 +1,97 @@
.bd-pluginsview,
.bd-themesview {
.bd-onlinePh {
.bd-localPh {
.bd-scroller {
padding: 0 20px 0 0;
}
}
.bd-onlinePh,
.bd-localPh {
display: flex;
flex-direction: column;
margin: 10% 0;
margin: 10px 0;
.bd-spinnerContainer {
display: flex;
justify-content: center;
}
.bd-onlinePhHeader {
display: flex;
padding: 0 20px 0 10px;
min-height: 80px;
.bd-flexRow {
min-height: 40px;
}
.bd-searchHint {
flex-grow: 1;
line-height: 40px;
color: #fff;
}
.bd-searchSort {
margin-top: 10px;
justify-content: flex-end;
> span {
color: #fff;
display: flex;
justify-content: flex-end;
font-size: 12px;
height: 14px;
padding: 3px;
}
.bd-sort {
display: flex;
color: $coldimwhite;
font-size: 12px;
padding: 3px;
height: 14px;
cursor: pointer;
transition: color .2s ease-in-out;
font-weight: 700;
&:hover {
color: #fff;
}
&.bd-active {
color: $colbdgreen;
}
.bd-materialDesignIcon {
fill: $colbdgreen;
}
svg {
height: 14px;
}
&.bd-flipY {
.bd-materialDesignIcon {
transform: scaleY(-1);
}
}
}
}
}
.bd-onlinePhBody {
margin-top: 10px;
.bd-spinnerContainer {
min-height: 40px;
padding: 0;
}
.bd-scroller {
padding: 0 20px 0 0;
}
}
h3 {
color: #fff;

View File

@ -0,0 +1,9 @@
.bd-contentColumn .bd-devview {
display: grid;
grid-template-columns: 33% 33% 33%;
.bd-button {
font-size: 10px;
height: 20px;
}
}

View File

@ -10,3 +10,4 @@
@import './kvp';
@import './collection';
@import './e2ee';
@import './devview';

View File

@ -1,22 +1,17 @@
.bd-remoteCard {
display: flex;
flex-direction: column;
margin-top: 10px;
padding: 10px 0;
padding: 10px;
border-radius: 0;
border-bottom: 1px solid rgba(114, 118, 126, .3);
&:hover {
transform: scale(1.005);
}
.bd-remoteCardTitle {
color: #b9bbbe;
font-weight: 700;
}
.bd-remoteCardLikes {
color: #f00;
color: $colerr;
font-size: 12px;
font-weight: 600;
}
@ -52,17 +47,30 @@
.bd-remoteCardTags {
color: #828a97;
font-size: 10px;
line-height: 20px;
display: flex;
align-items: flex-end;
.bd-remoteCardTag {
> div {
display: inline;
cursor: pointer;
margin-left: 2px;
&:hover {
color: #fff;
}
}
}
}
.bd-buttonGroup {
align-self: flex-end;
justify-content: flex-end;
flex-grow: 1;
max-height: 20px;
max-height: 30px;
.bd-button {
font-size: 12px;
font-size: 16px;
padding: 5px 10px;
}
}

View File

@ -1,13 +1,15 @@
.bd-settings {
position: absolute;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
top: 22px;
left: 0;
bottom: 0;
z-index: 1001;
width: 310px;
transform: translateX(-100%) translateY(-100%);
transform: translateX(-310px);
opacity: 0;
transition: all .4s ease-in-out;
transition: transform .3s cubic-bezier(.4, 0, 0, 1), opacity .25s ease;
pointer-events: none;
&.bd-active {
@ -115,7 +117,7 @@
&.bd-active {
.bd-contentRegion {
transition: transform .4s ease-in-out, opacity .2s ease;
transition: all .3s cubic-bezier(.4, 0, 0, 1);
transform: none;
opacity: 1;
}

View File

@ -3,4 +3,22 @@
margin: 0 0 10px;
color: #fff;
}
.bd-settingSwitch {
.bd-spinner7 {
height: 24px;
}
.bd-updaterStatus {
text-align: right;
&.bd-err {
color: $colerr;
}
&.bd-ok {
color: $colok;
}
}
}
}

View File

@ -1,13 +1,13 @@
// sass-lint:disable-all
body:not(.bd-hideButton) {
[class*='guildsWrapper-'] {
[class*='layer-'] > * > [class*='wrapper-'] {
padding-top: 49px !important;
}
.platform-osx [class*='guildsWrapper-'] {
.platform-osx [class*='layer-'] > * > [class*='wrapper-'] {
margin-top: 26px;
}
[class*='guildsWrapper-'] + [class*='flex'] {
[class*='layer-'] > * > [class*='wrapper-'] + [class*='flex'] {
border-radius: 0 0 0 5px;
}
@ -27,3 +27,9 @@ body:not(.bd-hideButton) {
.bd-settingsWrapper.platform-linux {
transform: none;
}
// Remove the margin on message attachments with an emote
.da-containerCozy + .da-containerCozy > * > .bd-emote {
margin-top: -8px;
margin-bottom: -8px;
}

View File

@ -26,14 +26,25 @@
position: relative;
margin-right: 15px;
.bd-chevron1,
.bd-chevron1 {
position: absolute;
svg {
transform: scale(1) translateY(0);
}
}
.bd-chevron2 {
position: absolute;
svg {
visibility: hidden;
transform: scale(1.4, .5) translateY(6px) rotate(180deg);
}
}
svg {
transition: transform .5s ease;
transform: none;
transition: transform .2s cubic-bezier(.2, 0, 0, 1);
}
}
@ -70,16 +81,16 @@
.bd-drawerOpenButton {
.bd-chevron1 {
svg {
transform: rotate(90deg);
transform: scale(1.4, .5) translateY(-6px);
visibility: hidden;
}
}
}
.bd-chevron2 {
margin-left: -4px;
svg {
transform: rotate(270deg);
visibility: visible;
transform: scale(1) translateY(0) rotate(180deg);
}
}
}

View File

@ -47,7 +47,7 @@
bottom: 3px;
background: #f6f6f7;
border-radius: 10px;
transition: all .15s ease;
transition: all .15s cubic-bezier(.2, 0, 0, 1);
box-shadow: 0 3px 1px 0 rgba(0, 0, 0, .05), 0 2px 2px 0 rgba(0, 0, 0, .1), 0 3px 3px 0 rgba(0, 0, 0, .05);
}

View File

@ -164,11 +164,11 @@
.bd-fancySearch {
display: flex;
justify-content: flex-end;
transform: translateY(80px) translateX(-140px);
transition: all .5s ease-in-out;
// transform: translateY(80px) translateX(-140px);
// transition: all .5s ease-in-out;
&::before {
content: 'Search by name, description or tag...';
// content: 'Search by name, description or tag...';
color: #f6f6f7;
position: relative;
top: -20px;
@ -184,6 +184,12 @@
}
}
&.bd-disabled {
.bd-textInput {
opacity: .8;
}
}
.bd-textInput {
padding: 10px;
display: flex;

View File

@ -8,3 +8,4 @@
@import './settings-modal';
@import './permission-modal';
@import './input-modal';
@import './install-modal';

View File

@ -0,0 +1,231 @@
.bd-installModal {
.bd-modalInner {
min-width: 520px;
min-height: 200px;
@at-root .bd-err#{&} {
border: 2px solid $colerr;
}
.bd-modalBody {
flex-grow: 1;
padding: 0;
.bd-installModalBody {
display: flex;
flex-direction: column;
flex-grow: 1;
.bd-installModalTop {
padding: 0 15px;
display: flex;
height: 100px;
span {
font-weight: 700;
}
.bd-installModalIcon {
background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTAwIDEzMCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTAwIDEzMDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+LnN0MHtmaWxsOiNGNEY2RkM7fS5zdDF7ZmlsbDpub25lO3N0cm9rZTojMzYzOTNFO3N0cm9rZS13aWR0aDoyO30uc3Qye29wYWNpdHk6MC42O2ZpbGw6bm9uZTtzdHJva2U6IzM2MzkzRTtzdHJva2Utd2lkdGg6MjtlbmFibGUtYmFja2dyb3VuZDpuZXcgICAgO308L3N0eWxlPjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xNSwxOWMwLTMuMywyLjctNiw2LTZoNDRjNC43LDAsMjAsMTUuMywyMCwyMHY3NGMwLDMuMy0yLjcsNi02LDZIMjFjLTMuMywwLTYtMi43LTYtNlYxOXoiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNMTUsMTljMC0zLjMsMi43LTYsNi02aDQ0YzQuNywwLDIwLDE1LjMsMjAsMjB2NzRjMCwzLjMtMi43LDYtNiw2SDIxYy0zLjMsMC02LTIuNy02LTZWMTl6Ii8+PHBhdGggY2xhc3M9InN0MCIgZD0iTTY2LDE2YzAtMy4zLDEuOS00LjEsNC4yLTEuOGwxMy41LDEzLjVjMi4zLDIuMywxLjYsNC4yLTEuOCw0LjJINjljLTEuNywwLTMtMS4zLTMtM1YxNkw2NiwxNnoiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNNjYsMTZjMC0zLjMsMS45LTQuMSw0LjItMS44bDEzLjUsMTMuNWMyLjMsMi4zLDEuNiw0LjItMS44LDQuMkg2OWMtMS43LDAtMy0xLjMtMy0zVjE2TDY2LDE2eiIvPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik02MSwxN0gyM2MtMi44LDAtNSwyLjItNSw1djciLz48cGF0aCBjbGFzcz0ic3QyIiBkPSJNNjksMTl2Ny41YzAsMS40LDEuMSwyLjUsMi41LDIuNUg3NCIvPjwvc3ZnPg==');
background-size: 100% 100%;
flex-shrink: 0;
width: 100px;
height: 130px;
position: fixed;
border-radius: 3px;
margin-top: -30px;
display: flex;
align-content: center;
justify-content: center;
align-items: center;
justify-items: center;
@at-root .bd-err#{&} {
background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTAwIDEzMCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTAwIDEzMDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+LnN0MHtmaWxsOiNGNEY2RkM7fS5zdDF7ZmlsbDpub25lO3N0cm9rZTojRDg0MDQwO3N0cm9rZS13aWR0aDoyO30uc3Qye29wYWNpdHk6MC42O2ZpbGw6bm9uZTtzdHJva2U6I0Q4NDA0MDtzdHJva2Utd2lkdGg6MjtlbmFibGUtYmFja2dyb3VuZDpuZXcgICAgO308L3N0eWxlPjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xNSwxOWMwLTMuMywyLjctNiw2LTZoNDRjNC43LDAsMjAsMTUuMywyMCwyMHY3NGMwLDMuMy0yLjcsNi02LDZIMjFjLTMuMywwLTYtMi43LTYtNlYxOXoiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNMTUsMTljMC0zLjMsMi43LTYsNi02aDQ0YzQuNywwLDIwLDE1LjMsMjAsMjB2NzRjMCwzLjMtMi43LDYtNiw2SDIxYy0zLjMsMC02LTIuNy02LTZWMTl6Ii8+PHBhdGggY2xhc3M9InN0MCIgZD0iTTY2LDE2YzAtMy4zLDEuOS00LjEsNC4yLTEuOGwxMy41LDEzLjVjMi4zLDIuMywxLjYsNC4yLTEuOCw0LjJINjljLTEuNywwLTMtMS4zLTMtM1YxNkw2NiwxNnoiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNNjYsMTZjMC0zLjMsMS45LTQuMSw0LjItMS44bDEzLjUsMTMuNWMyLjMsMi4zLDEuNiw0LjItMS44LDQuMkg2OWMtMS43LDAtMy0xLjMtMy0zVjE2TDY2LDE2eiIvPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik02MSwxN0gyM2MtMi44LDAtNSwyLjItNSw1djciLz48cGF0aCBjbGFzcz0ic3QyIiBkPSJNNjksMTl2Ny41YzAsMS40LDEuMSwyLjUsMi41LDIuNUg3NCIvPjwvc3ZnPg==');
}
.bd-installModalCi {
width: 40px;
height: 40px;
}
svg {
fill: #36393e;
width: 40px;
height: 40px;
}
}
.bd-installModalInfo {
display: flex;
flex-grow: 1;
flex-direction: column;
padding: 15px 10px;
margin-left: 100px;
}
.bd-installModalDesc {
margin-top: 5px;
}
}
.bd-installModalBottom {
padding: 0 15px;
flex-grow: 1;
.bd-permScope {
&:last-child {
.bd-permInner {
border: 0;
}
}
}
}
}
}
.bd-installModalFooter {
display: flex;
justify-content: flex-end;
padding: 10px;
background: rgba(0, 0, 0, .1);
.bd-installModalStatus {
height: 36px;
line-height: 36px;
flex-grow: 1;
padding: 0 25px;
font-weight: 600;
color: #fff;
&.bd-ok {
color: $colok;
}
&.bd-err {
color: $colerr;
}
}
.bd-button {
font-size: 16px;
padding: 10px;
border-radius: 3px;
min-width: 100px;
transition: opacity .2s ease-in-out;
&.bd-ok {
background: #3ecc9c;
}
&.bd-err {
color: #fff;
}
&:hover {
opacity: .8;
}
&.bd-installModalUpload {
background: transparent;
transition: none;
&:hover {
opacity: 1;
text-decoration: underline;
}
}
}
}
}
&.bd-installModalDone {
.bd-modalInner {
min-width: 400px;
max-width: 400px;
height: 250px;
border: 2px solid $colok;
.bd-installModalBody {
display: flex;
flex-grow: 1;
align-content: center;
justify-content: center;
align-items: center;
h3 {
color: $colok;
font-size: 1.2em;
font-weight: 700;
text-align: center;
padding: 20px;
}
}
.bd-materialDesignIcon {
svg {
fill: $colok;
width: 100px;
height: 100px;
}
}
.bd-installModalFooter {
.bd-button {
margin-left: 5px;
}
}
}
}
&.bd-installModalFail {
.bd-modalInner {
min-width: 400px;
max-width: 400px;
height: 250px;
border: 2px solid $colerr;
.bd-installModalBody {
display: flex;
flex-grow: 1;
align-content: center;
justify-content: center;
align-items: center;
h3 {
color: $colerr;
font-size: 1.2em;
font-weight: 700;
text-align: center;
padding: 20px;
}
}
.bd-materialDesignIcon {
svg {
fill: $colerr;
width: 100px;
height: 100px;
}
}
.bd-installModalFooter {
&.bd-installModalErrMsg {
justify-content: flex-start;
padding: 10px;
background: rgba(0, 0, 0, .2);
font-weight: 700;
span {
color: #fff;
flex-grow: 1;
text-align: right;
}
}
}
}
}
}

View File

@ -22,12 +22,12 @@
> .bd-scroller {
overflow-y: scroll;
}
}
.bd-settings & {
.platform-darwin & { // sass-lint:disable-line class-name-format
padding-top: 22px;
}
}
.bd-settings & {
.platform-darwin & { // sass-lint:disable-line class-name-format
padding-top: 22px;
}
}
@ -120,8 +120,14 @@
}
.bd-settingswrapContents {
padding: 0 20px;
margin-bottom: 84px;
padding: 0 0 0 20px;
}
.bd-scroller {
.bd-settingswrapContents {
margin-bottom: 84px;
padding: 0 20px;
}
}
}
}

View File

@ -37,13 +37,14 @@
white-space: nowrap;
font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif;
&:hover,
&.bd-active {
background: $colbdgreen;
color: #fff;
&:hover {
background-color: rgba(185, 185, 185, .1);
color: #f6f6f6;
}
&.bd-active {
background: $colbdgreen;
color: #fff;
cursor: default;
}
}

View File

@ -11,7 +11,7 @@ export default new class Autocomplete {
}
async init() {
this.cta = await ReactComponents.getComponent('ChannelTextArea', { selector: Reflection.resolve('channelTextArea', 'emojiButton').selector });
this.cta = await ReactComponents.getComponent('ChannelTextArea');
MonkeyPatch('BD:Autocomplete', this.cta.component.prototype).after('render', this.channelTextAreaAfterRender.bind(this));
this.initialized = true;
}

View File

@ -23,7 +23,7 @@
</Sidebar>
<div slot="sidebarfooter" class="bd-info">
<span class="bd-vtext">v2.0.0a by Jiiks/JsSucks</span>
<span class="bd-vtext">{{versionString}}</span>
<div @click="openGithub" v-tooltip="'GitHub'" class="bd-materialButton">
<MiGithubCircle size="16" />
</div>
@ -63,11 +63,11 @@
<script>
// Imports
import { Events, Settings } from 'modules';
import { Events, Settings, Globals, Reflection } from 'modules';
import { BdMenuItems } from 'ui';
import { shell } from 'electron';
import { SidebarView, Sidebar, SidebarItem, ContentColumn } from './sidebar';
import { SettingsWrapper, SettingsPanel, CssEditorView, PluginsView, ThemesView, UpdaterView, ConnectivityView } from './bd';
import { SettingsWrapper, SettingsPanel, CssEditorView, PluginsView, ThemesView, UpdaterView, ConnectivityView, SuperSecretView } from './bd';
import { SvgX, MiGithubCircle, MiWeb, MiClose, MiTwitterCircle } from './common';
export default {
@ -96,6 +96,9 @@
category.push(item);
}
return categories;
},
versionString() {
return Globals.version;
}
},
methods: {
@ -123,6 +126,13 @@
},
created() {
Events.on('bd-open-menu', this.openMenuHandler = item => item && this.itemOnClick(this.items.find(i => i === item || i.id === item || i.contentid === item || i.set === item).id));
try {
const currentUser = Reflection.module.byName('UserStore').getCurrentUser();
if (['81388395867156480', '98003542823944192', '249746236008169473', '284056145272766465', '478559353516064769'].includes(currentUser.id)) {
BdMenuItems.addVueComponent('BD Devs', 'Super Secret', SuperSecretView);
}
} catch (err) {}
},
destroyed() {
if (this.openMenuHandler) Events.off('bd-open-menu', this.openMenuHandler);

View File

@ -8,6 +8,8 @@
* LICENSE file in the root directory of this source tree.
*/
// TODO this should be remade as editor instead of css editor
<template>
<SettingsWrapper headertext="CSS Editor">
<div class="bd-cssEditor">
@ -49,7 +51,7 @@
<script>
// Imports
import { Settings, CssEditor } from 'modules';
import { Settings, Editor } from 'modules';
import { SettingsWrapper } from './';
import { FormButton } from '../common';
import SettingsPanel from './SettingsPanel.vue';
@ -64,18 +66,18 @@
},
data() {
return {
CssEditor
Editor
};
},
computed: {
error() {
return this.CssEditor.error;
return this.Editor.error;
},
compiling() {
return this.CssEditor.compiling;
return this.Editor.compiling;
},
systemEditorPath() {
return this.CssEditor.filePath;
return this.Editor.filePath;
},
liveUpdateSetting() {
return Settings.getSetting('css', 'default', 'live-update');
@ -92,13 +94,13 @@
},
methods: {
openInternalEditor() {
this.CssEditor.show();
this.Editor.show();
},
openSystemEditor() {
this.CssEditor.openSystemEditor();
// this.Editor.openSystemEditor();
},
recompile() {
this.CssEditor.recompile();
// this.Editor.recompile();
}
}
}

View File

@ -12,6 +12,7 @@
<Card :item="plugin">
<SettingSwitch v-if="plugin.type === 'plugin'" slot="toggle" :value="plugin.enabled" @input="$emit('toggle-plugin')" />
<ButtonGroup slot="controls">
<Button v-if="devmode && !plugin.packed" v-tooltip="'Package Plugin'" @click="package"><MiBoxDownload size="18"/></Button>
<Button v-tooltip="'Settings (shift + click to open settings without cloning the set)'" v-if="plugin.hasSettings" @click="$emit('show-settings', $event.shiftKey)"><MiSettings size="18" /></Button>
<Button v-tooltip="'Reload'" @click="$emit('reload-plugin')"><MiRefresh size="18" /></Button>
<Button v-tooltip="'Edit'" @click="editPlugin"><MiPencil size="18" /></Button>
@ -22,18 +23,33 @@
<script>
// Imports
import { Toasts } from 'ui';
import { Settings, PluginManager } from 'modules';
import { ClientLogger as Logger } from 'common';
import { shell } from 'electron';
import Card from './Card.vue';
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension } from '../common';
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload } from '../common';
export default {
data() {
return {
devmode: Settings.getSetting('core', 'advanced', 'developer-mode').value
}
},
props: ['plugin'],
components: {
Card, Button, ButtonGroup, SettingSwitch,
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload
},
methods: {
async package() {
try {
const packagePath = await PluginManager.packContent(this.plugin.name, this.plugin.contentPath);
Toasts.success(`Plugin Packaged: ${packagePath}`);
} catch (err) {
Logger.log('PluginCard', err);
}
},
editPlugin() {
try {
shell.openItem(this.plugin.contentPath);

View File

@ -9,7 +9,7 @@
*/
<template>
<SettingsWrapper headertext="Plugins">
<SettingsWrapper headertext="Plugins" :noscroller="true">
<div class="bd-tabbar" slot="header">
<div class="bd-button" :class="{'bd-active': local}" @click="showLocal">
<h3>Installed</h3>
@ -22,12 +22,39 @@
</div>
<div class="bd-flex bd-flexCol bd-pluginsview">
<div v-if="local" class="bd-flex bd-flexGrow bd-flexCol bd-pluginsContainer bd-localPlugins">
<PluginCard v-for="plugin in localPlugins" :plugin="plugin" :key="plugin.id" :data-plugin-id="plugin.id" @toggle-plugin="togglePlugin(plugin)" @reload-plugin="reloadPlugin(plugin)" @delete-plugin="unload => deletePlugin(plugin, unload)" @show-settings="dont_clone => showSettings(plugin, dont_clone)" />
<div v-if="local" class="bd-flex bd-flexGrow bd-flexCol bd-pluginsContainer bd-localPlugins bd-localPh">
<ScrollerWrap>
<PluginCard v-for="plugin in localPlugins" :plugin="plugin" :key="plugin.id" :data-plugin-id="plugin.id" @toggle-plugin="togglePlugin(plugin)" @reload-plugin="reloadPlugin(plugin)" @delete-plugin="unload => deletePlugin(plugin, unload)" @show-settings="dont_clone => showSettings(plugin, dont_clone)" />
</ScrollerWrap>
</div>
<div v-if="!local" class="bd-onlinePh">
<h3>Coming Soon</h3>
<a href="https://v2.betterdiscord.net/plugins" target="_new">Website Browser</a>
<div v-else class="bd-onlinePh">
<div class="bd-onlinePhHeader bd-flexCol">
<div class="bd-flex bd-flexRow">
<div v-if="loadingOnline" class="bd-spinnerContainer">
<div class="bd-spinner7" />
</div>
<div class="bd-searchHint">{{searchHint}}</div>
<div class="bd-fancySearch" :class="{'bd-disabled': loadingOnline, 'bd-active': loadingOnline || (onlinePlugins && onlinePlugins.docs)}">
<input type="text" class="bd-textInput" placeholder="Search" @keydown.enter="searchInput" @keyup.stop :value="onlinePlugins.filters.sterm" />
</div>
</div>
<div class="bd-flex bd-flexRow" v-if="onlinePlugins && onlinePlugins.docs && onlinePlugins.docs.length">
<div class="bd-searchSort bd-flex bd-flexGrow">
<div v-for="btn in sortBtns"
class="bd-sort"
:class="{'bd-active': onlinePlugins.filters.sort === btn.toLowerCase(), 'bd-flipY': onlinePlugins.filters.ascending}"
@click="sortBy(btn.toLowerCase())">
{{btn}}<MiChevronDown v-if="onlinePlugins.filters.sort === btn.toLowerCase()" size="18" />
</div>
</div>
</div>
</div>
<ScrollerWrap class="bd-onlinePhBody" v-if="!loadingOnline && onlinePlugins" :scrollend="scrollend">
<RemoteCard v-if="onlinePlugins && onlinePlugins.docs" v-for="plugin in onlinePlugins.docs" :key="onlinePlugins.id" :item="plugin" :tagClicked="searchByTag" />
<div class="bd-spinnerContainer">
<div v-if="loadingMore" class="bd-spinner7" />
</div>
</ScrollerWrap>
</div>
</div>
</SettingsWrapper>
@ -35,25 +62,45 @@
<script>
// Imports
import { PluginManager } from 'modules';
import { PluginManager, BdWebApi } from 'modules';
import { Modals } from 'ui';
import { ClientLogger as Logger } from 'common';
import { MiRefresh } from '../common';
import { MiRefresh, ScrollerWrap, MiChevronDown } from '../common';
import SettingsWrapper from './SettingsWrapper.vue';
import PluginCard from './PluginCard.vue';
import RemoteCard from './RemoteCard.vue';
import RefreshBtn from '../common/RefreshBtn.vue';
export default {
data() {
return {
PluginManager,
sortBtns: ['Updated', 'Installs', 'Users', 'Rating'],
local: true,
localPlugins: PluginManager.localPlugins
localPlugins: PluginManager.localPlugins,
onlinePlugins: {
docs: [],
filters: {
sterm: '',
sort: 'installs',
ascending: false
},
pagination: {
total: 0,
pages: 0,
limit: 9,
page: 1
}
},
loadingOnline: false,
loadingMore: false,
searchHint: ''
};
},
components: {
SettingsWrapper, PluginCard,
MiRefresh,
SettingsWrapper, PluginCard, RemoteCard,
MiRefresh, MiChevronDown,
ScrollerWrap,
RefreshBtn
},
methods: {
@ -67,7 +114,19 @@
await this.PluginManager.refreshPlugins();
},
async refreshOnline() {
// TODO
this.searchHint = '';
if (this.loadingOnline || this.loadingMore) return;
this.loadingOnline = true;
try {
const getPlugins = await BdWebApi.plugins.get(this.onlinePlugins.filters);
this.onlinePlugins = getPlugins;
if (!this.onlinePlugins.docs) return;
this.searchHint = `${this.onlinePlugins.pagination.total} Results`;
} catch (err) {
Logger.err('PluginsView', err);
} finally {
this.loadingOnline = false;
}
},
async togglePlugin(plugin) {
// TODO: display error if plugin fails to start/stop
@ -96,6 +155,48 @@
return Modals.contentSettings(plugin, null, {
dont_clone
});
},
searchInput(e) {
if (this.loadingOnline || this.loadingMore) return;
this.onlinePlugins.filters.sterm = e.target.value;
this.refreshOnline();
},
async scrollend(e) {
if (this.onlinePlugins.pagination.page >= this.onlinePlugins.pagination.pages) return;
if (this.loadingOnline || this.loadingMore) return;
this.loadingMore = true;
try {
const getPlugins = await BdWebApi.plugins.get({
sterm: this.onlinePlugins.filters.sterm,
page: this.onlinePlugins.pagination.page + 1,
sort: this.onlinePlugins.filters.sort,
ascending: this.onlinePlugins.filters.ascending
});
this.onlinePlugins.docs = [...this.onlinePlugins.docs, ...getPlugins.docs];
this.onlinePlugins.filters = getPlugins.filters;
this.onlinePlugins.pagination = getPlugins.pagination;
} catch (err) {
Logger.err('PluginsView', err);
} finally {
this.loadingMore = false;
}
},
async sortBy(by) {
if (this.loadingOnline || this.loadingMore) return;
if (this.onlinePlugins.filters.sort === by) {
this.onlinePlugins.filters.ascending = !this.onlinePlugins.filters.ascending;
} else {
this.onlinePlugins.filters.sort = by;
this.onlinePlugins.filters.ascending = false;
}
this.refreshOnline();
},
async searchByTag(tag) {
if (this.loadingOnline || this.loadingMore) return;
this.onlinePlugins.filters.sterm = tag;
this.refreshOnline();
}
}
}

View File

@ -20,30 +20,51 @@
<div class="bd-remoteCardInfoBox bd-flex bd-flexGrow bd-flexCol">
<div class="bd-remoteCardInfo">{{item.installs}} Installs</div>
<div class="bd-remoteCardInfo">{{item.activeUsers}} Active Users</div>
<div class="bd-remoteCardInfo">Updated: Some time ago</div>
<div class="bd-remoteCardInfo">Updated {{fromNow()}}</div>
</div>
</div>
</div>
<div class="bd-flexRow bd-flex bd-flexGrow">
<div class="bd-flexGrow bd-remoteCardTags">{{item.tags.join(', ')}}</div>
<div class="bd-flexGrow bd-remoteCardTags">
<div v-for="(tag, index) in item.tags" class="bd-remoteCardTag">
<div @click="tagClicked(tag)">{{tag}}</div><span v-if="index + 1 < item.tags.length">, </span>
</div>
</div>
<div class="bd-buttonGroup">
<div class="bd-button">Install</div>
<div class="bd-button" @click="install">Install</div>
<div class="bd-button">Preview</div>
<div class="bd-button">Source</div>
<div class="bd-button" @click="openSourceUrl">Source</div>
</div>
</div>
</div>
</template>
<script>
import { Reflection, PackageInstaller } from 'modules';
import { shell } from 'electron';
export default {
props: ['item'],
props: ['item', 'tagClicked'],
data() {
return {}
},
methods: {
resolveThumb() {
return `${this.item.repository.rawUri}/${this.item.files.previews[0].thumb}`;
// TODO
return '';
// return `${this.item.repository.rawUri}/${this.item.files.previews[0].thumb}`;
},
fromNow() {
const { Moment } = Reflection.modules;
return Moment(this.item.updated).fromNow();
},
openSourceUrl() {
if (!this.item.repository || !this.item.repository.baseUri) return;
if (Object.assign(document.createElement('a'), { href: this.item.repository.baseUri }).hostname !== 'github.com') return;
shell.openExternal(this.item.repository.baseUri);
},
async install() {
await PackageInstaller.installRemotePackage(this.item.repository.assetUri);
}
}
}

View File

@ -10,7 +10,16 @@
<template>
<div class="bd-settingswrap">
<ScrollerWrap>
<div v-if="noscroller" class="bd-flex bd-flexCol">
<div class="bd-settingswrapHeader">
<span class="bd-settingswrapHeaderText">{{ headertext }}</span>
<slot name="header" />
</div>
<div class="bd-settingswrapContents bd-flex bd-flexGrow bd-flexCol">
<slot />
</div>
</div>
<ScrollerWrap v-else :scrollend="scrollend">
<div class="bd-settingswrapHeader">
<span class="bd-settingswrapHeaderText">{{ headertext }}</span>
<slot name="header" />
@ -27,7 +36,7 @@
import { ScrollerWrap } from '../common';
export default {
props: ['headertext'],
props: ['headertext', 'scrollend', 'noscroller'],
components: {
ScrollerWrap
}

View File

@ -0,0 +1,105 @@
/**
* BetterDiscord Developer View Component
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
* All rights reserved.
* https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
<template>
<SettingsWrapper headertext="Super Secret">
<div class="bd-flex bd-flexCol bd-devview">
<FormButton @click="forceUpdate">Force Update</FormButton>
<FormButton @click="debugConfig">Config Debug</FormButton>
<FormButton @click="testUpdateUi">Update UI Test</FormButton>
</div>
</SettingsWrapper>
</template>
<script>
import SettingsWrapper from './SettingsWrapper.vue';
import { FormButton } from '../common';
import { Globals, Events, Updater } from 'modules';
import { ClientIPC } from 'common';
export default {
data() {
return {
};
},
components: {
SettingsWrapper,
FormButton
},
methods: {
forceUpdate() {
ClientIPC.send('debug-updater-forceUpdate');
},
debugConfig() {
console.log(Globals);
},
testUpdateUi() {
Updater.testUi({
'bd': [
{
'id': 'update',
'version': '3.0.0',
'currentVersion': '2.0.0',
'text': 'Update test',
'hint': 'Current: 2.0.0 | Latest: 3.0.0',
'status': {
'update': true,
'updating': false,
'updated': false,
'error': null
}
},
{
'id': 'updating',
'version': '3.0.0',
'currentVersion': '2.0.0',
'text': 'Updating test',
'hint': 'Current: 2.0.0 | Latest: 3.0.0',
'status': {
'update': true,
'updating': true,
'updated': false,
'error': null
}
},
{
'id': 'updated',
'version': '3.0.0',
'currentVersion': '2.0.0',
'text': 'Updated test',
'hint': 'Current: 2.0.0 | Latest: 3.0.0',
'status': {
'update': true,
'updating': true,
'updated': true,
'error': null
}
},
{
'id': 'error',
'version': '3.0.0',
'currentVersion': '2.0.0',
'text': 'Error test',
'hint': 'Current: 2.0.0 | Latest: 3.0.0',
'status': {
'update': true,
'updating': true,
'updated': false,
'error': 'Failed to update.'
}
}
],
'haveUpdates': true
});
}
}
}
</script>

View File

@ -12,6 +12,7 @@
<Card :item="theme">
<SettingSwitch slot="toggle" :value="theme.enabled" @input="$emit('toggle-theme')" />
<ButtonGroup slot="controls" v-if="!online">
<Button v-if="devmode && !theme.packed" v-tooltip="'Package Theme'" @click="package"><MiBoxDownload size="18" /></Button>
<Button v-tooltip="'Settings (shift + click to open settings without cloning the set)'" v-if="theme.hasSettings" @click="$emit('show-settings', $event.shiftKey)"><MiSettings size="18" /></Button>
<Button v-tooltip="'Recompile (shift + click to reload)'" @click="$emit('reload-theme', $event.shiftKey)"><MiRefresh size="18" /></Button>
<Button v-tooltip="'Edit'" @click="editTheme"><MiPencil size="18" /></Button>
@ -22,17 +23,33 @@
<script>
// Imports
import { Toasts } from 'ui';
import { Settings, ThemeManager } from 'modules';
import { ClientLogger as Logger } from 'common';
import { shell } from 'electron';
import Card from './Card.vue';
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension } from '../common';
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload } from '../common';
export default {
data() {
return {
devmode: Settings.getSetting('core', 'advanced', 'developer-mode').value
}
},
props: ['theme', 'online'],
components: {
Card, Button, ButtonGroup, SettingSwitch,
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload
},
methods: {
async package() {
try {
const packagePath = await ThemeManager.packContent(this.theme.name, this.theme.contentPath);
Toasts.success(`Theme Packaged: ${packagePath}`);
} catch (err) {
Logger.log('ThemeCard', err);
}
},
editTheme() {
try {
shell.openItem(this.theme.contentPath);

View File

@ -9,7 +9,7 @@
*/
<template>
<SettingsWrapper headertext="Themes">
<SettingsWrapper headertext="Themes" :noscroller="true">
<div class="bd-tabbar" slot="header">
<div class="bd-button" :class="{'bd-active': local}" @click="showLocal">
<h3>Installed</h3>
@ -22,15 +22,38 @@
</div>
<div class="bd-flex bd-flexCol bd-themesview">
<div v-if="local" class="bd-flex bd-flexGrow bd-flexCol bd-themesContainer bd-localThemes">
<ThemeCard v-for="theme in localThemes" :theme="theme" :key="theme.id" :data-theme-id="theme.id" @toggle-theme="toggleTheme(theme)" @reload-theme="reload => reloadTheme(theme, reload)" @show-settings="dont_clone => showSettings(theme, dont_clone)" @delete-theme="unload => deleteTheme(theme, unload)" />
<div v-if="local" class="bd-flex bd-flexGrow bd-flexCol bd-themesContainer bd-localPh">
<ScrollerWrap>
<ThemeCard v-for="theme in localThemes" :theme="theme" :key="theme.id" :data-theme-id="theme.id" @toggle-theme="toggleTheme(theme)" @reload-theme="reload => reloadTheme(theme, reload)" @show-settings="dont_clone => showSettings(theme, dont_clone)" @delete-theme="unload => deleteTheme(theme, unload)" />
</ScrollerWrap>
</div>
<div v-if="!local" class="bd-onlinePh">
<div class="bd-fancySearch" :class="{'bd-active': loadingOnline || (onlineThemes && onlineThemes.docs)}">
<input type="text" class="bd-textInput" @keydown.enter="searchInput" @keyup.stop/>
<div v-else class="bd-onlinePh">
<div class="bd-onlinePhHeader bd-flexCol">
<div class="bd-flex bd-flexRow">
<div v-if="loadingOnline" class="bd-spinnerContainer">
<div class="bd-spinner7" />
</div>
<div class="bd-searchHint">{{searchHint}}</div>
<div class="bd-fancySearch" :class="{'bd-disabled': loadingOnline, 'bd-active': loadingOnline || (onlineThemes && onlineThemes.docs)}">
<input type="text" class="bd-textInput" placeholder="Search" @keydown.enter="searchInput" @keyup.stop :value="onlineThemes.filters.sterm"/>
</div>
</div>
<div class="bd-flex bd-flexRow" v-if="onlineThemes && onlineThemes.docs && onlineThemes.docs.length">
<div class="bd-searchSort bd-flex bd-flexGrow">
<div v-for="btn in sortBtns"
class="bd-sort"
:class="{'bd-active': onlineThemes.filters.sort === btn.toLowerCase(), 'bd-flipY': onlineThemes.filters.ascending}"
@click="sortBy(btn.toLowerCase())">{{btn}}<MiChevronDown v-if="onlineThemes.filters.sort === btn.toLowerCase()" size="18" />
</div>
</div>
</div>
</div>
<h2 v-if="loadingOnline">Loading</h2>
<RemoteCard v-else-if="onlineThemes && onlineThemes.docs" v-for="theme in onlineThemes.docs" :key="theme.id" :item="theme"/>
<ScrollerWrap class="bd-onlinePhBody" v-if="!loadingOnline && onlineThemes" :scrollend="scrollend">
<RemoteCard v-if="onlineThemes && onlineThemes.docs" v-for="theme in onlineThemes.docs" :key="theme.id" :item="theme" :tagClicked="searchByTag"/>
<div class="bd-spinnerContainer">
<div v-if="loadingMore" class="bd-spinner7"/>
</div>
</ScrollerWrap>
</div>
</div>
</SettingsWrapper>
@ -41,7 +64,7 @@
import { ThemeManager, BdWebApi } from 'modules';
import { Modals } from 'ui';
import { ClientLogger as Logger } from 'common';
import { MiRefresh } from '../common';
import { MiRefresh, ScrollerWrap, MiChevronDown } from '../common';
import SettingsWrapper from './SettingsWrapper.vue';
import ThemeCard from './ThemeCard.vue';
import RemoteCard from './RemoteCard.vue';
@ -51,15 +74,32 @@
data() {
return {
ThemeManager,
sortBtns: ['Updated', 'Installs', 'Users', 'Rating'],
local: true,
localThemes: ThemeManager.localThemes,
onlineThemes: null,
loadingOnline: false
onlineThemes: {
docs: [],
filters: {
sterm: '',
sort: 'installs',
ascending: false
},
pagination: {
total: 0,
pages: 0,
limit: 9,
page: 1
}
},
loadingOnline: false,
loadingMore: false,
searchHint: ''
};
},
components: {
SettingsWrapper, ThemeCard, RemoteCard,
MiRefresh,
MiRefresh, MiChevronDown,
ScrollerWrap,
RefreshBtn
},
methods: {
@ -68,44 +108,19 @@
},
async showOnline() {
this.local = false;
if (this.loadingOnline || this.onlineThemes) return;
},
async refreshLocal() {
await this.ThemeManager.refreshThemes();
},
async refreshOnline() {
this.searchHint = '';
if (this.loadingOnline || this.loadingMore) return;
this.loadingOnline = true;
try {
// const getThemes = await BdWebApi.themes.get();
// this.onlineThemes = JSON.parse(getThemes);
const dummies = [];
for (let i = 0; i < 10; i++) {
dummies.push({
id: `theme${i}`,
name: `Dummy ${i}`,
tags: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5'],
installs: Math.floor(Math.random() * 10000),
updated: '2018-07-21T14:51:32.057Z',
rating: Math.floor(Math.random() * 1000),
activeUsers: Math.floor(Math.random() * 1000),
rated: Math.random() > .5,
version: '1.0.0',
repository: {
name: 'ExampleRepository',
baseUri: 'https://github.com/Jiiks/ExampleRepository',
rawUri: 'https://github.com/Jiiks/ExampleRepository/raw/master'
},
files: {
readme: 'Example/readme.md',
previews: [{
large: 'Example/preview1-big.png',
thumb: 'Example/preview1-small.png'
}]
},
author: 'Jiiks'
});
}
this.onlineThemes = { docs: dummies };
const getThemes = await BdWebApi.themes.get(this.onlineThemes.filters);
this.onlineThemes = getThemes;
if (!this.onlineThemes.docs) return;
this.searchHint = `${this.onlineThemes.pagination.total} Results`;
} catch (err) {
Logger.err('ThemesView', err);
} finally {
@ -140,8 +155,46 @@
});
},
searchInput(e) {
this.loadingOnline = true;
setTimeout(this.refreshOnline, 1000);
if (this.loadingOnline || this.loadingMore) return;
this.onlineThemes.filters.sterm = e.target.value;
this.refreshOnline();
},
async scrollend(e) {
if (this.onlineThemes.pagination.page >= this.onlineThemes.pagination.pages) return;
if (this.loadingOnline || this.loadingMore) return;
this.loadingMore = true;
try {
const getThemes = await BdWebApi.themes.get({
sterm: this.onlineThemes.filters.sterm,
page: this.onlineThemes.pagination.page + 1,
sort: this.onlineThemes.filters.sort,
ascending: this.onlineThemes.filters.ascending
});
this.onlineThemes.docs = [...this.onlineThemes.docs, ...getThemes.docs];
this.onlineThemes.filters = getThemes.filters;
this.onlineThemes.pagination = getThemes.pagination;
} catch (err) {
Logger.err('ThemesView', err);
} finally {
this.loadingMore = false;
}
},
async sortBy(by) {
if (this.loadingOnline || this.loadingMore) return;
if (this.onlineThemes.filters.sort === by) {
this.onlineThemes.filters.ascending = !this.onlineThemes.filters.ascending;
} else {
this.onlineThemes.filters.sort = by;
this.onlineThemes.filters.ascending = false;
}
this.refreshOnline();
},
async searchByTag(tag) {
if (this.loadingOnline || this.loadingMore) return;
this.onlineThemes.filters.sterm = tag;
this.refreshOnline();
}
}
}

View File

@ -0,0 +1,28 @@
/**
* BetterDiscord Updater Status Component
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
* All rights reserved.
* https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
<template>
<div class="bd-settingSwitch">
<div class="bd-title">
<h3>{{item.text}}</h3>
<h3 class="bd-updaterStatus bd-err" v-if="item.status.error">Update Failed!</h3>
<h3 class="bd-updaterStatus bd-ok" v-else-if="item.status.updated">Done</h3>
<div class="bd-spinner7" v-else-if="item.status.updating" />
<h3 class="bd-updaterStatus" v-else>Unknown</h3>
</div>
<div class="bd-hint">{{item.hint}}</div>
</div>
</template>
<script>
export default {
props: ['item']
}
</script>

View File

@ -0,0 +1,28 @@
/**
* BetterDiscord Updater Switch Component
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
* All rights reserved.
* https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
<template>
<div class="bd-settingSwitch">
<div class="bd-title">
<h3>{{item.text}}</h3>
<div class="bd-switchWrapper" @click="() => toggle(item)">
<input type="checkbox" class="bd-switchCheckbox" />
<div class="bd-switch" :class="{'bd-checked': item.status.update}" />
</div>
</div>
<div class="bd-hint">{{item.hint}}</div>
</div>
</template>
<script>
export default {
props: ['item', 'toggle']
}
</script>

View File

@ -11,19 +11,22 @@
<template>
<SettingsWrapper headertext="Updates">
<div class="bd-flex bd-flexCol bd-updaterview">
<div v-if="error" class="bd-formItem">
<h5 style="margin-bottom: 10px;">Error installing updates</h5>
<div class="bd-err bd-preWrap"><div class="bd-pre">{{ error.formatted }}</div></div>
<div class="bd-formDivider"></div>
<div class="bd-settingsCategories">
<div class="bd-settingsCategory" v-if="bdUpdates && bdUpdates.length">
<div class="bd-formItem">
<h5>BetterDiscord</h5>
</div>
<div class="bd-formDivider"></div>
<div v-for="update in bdUpdates">
<UpdaterStatus :item="update" v-if="update.status.updating" />
<UpdaterToggle :item="update" :toggle="() => updater.toggleUpdate(update)" v-else />
<div class="bd-formDivider"></div>
</div>
</div>
</div>
<div class="bd-formButton bd-button" @click="update">
Update
</div>
<template v-if="updatesAvailable">
<p>Version {{ newVersion }} is available. You are currently running version {{ currentVersion }}.</p>
<FormButton :onClick="install" :loading="updating">Install</FormButton>
</template>
<template v-else>
<p>You're all up to date!</p>
</template>
</div>
</SettingsWrapper>
</template>
@ -32,7 +35,8 @@
import { Globals, Updater } from 'modules';
import { ClientLogger as Logger } from 'common';
import SettingsWrapper from './SettingsWrapper.vue';
import { FormButton } from '../common';
import UpdaterToggle from './UpdaterToggle.vue';
import UpdaterStatus from './UpdaterStatus.vue';
export default {
data() {
@ -44,26 +48,29 @@
},
components: {
SettingsWrapper,
FormButton
UpdaterToggle,
UpdaterStatus
},
computed: {
updatesAvailable() {
return this.updater.updatesAvailable;
},
newVersion() {
return this.updater.latestVersion;
return '2.0.0-beta.4';
},
error() {
return this.updater.error;
},
updates() {
return this.updater.updates;
},
bdUpdates() {
return this.updater.bdUpdates;
}
},
methods: {
async install() {
this.updating = true;
try {
await this.updater.update();
} catch (err) {}
this.updating = false;
update() {
this.updater.startUpdate();
}
}
}

View File

@ -4,5 +4,8 @@ export { default as CssEditorView } from './CssEditor.vue';
export { default as PluginsView } from './PluginsView.vue';
export { default as ThemesView } from './ThemesView.vue';
export { default as UpdaterView } from './UpdaterView.vue';
export { default as UpdaterStatus } from './UpdaterStatus.vue';
export { default as UpdaterToggle } from './UpdaterToggle.vue';
export { default as BdBadge } from './BdBadge.vue';
export { default as ConnectivityView } from './ConnectivityView.vue';
export { default as SuperSecretView } from './SuperSecretView.vue';

View File

@ -0,0 +1,124 @@
<template>
<Modal class="bd-installModal" :headertext="modal.title" :closing="modal.closing" @close="modal.close" :noheader="true" :class="{'bd-err': !verifying && !verified, 'bd-installModalDone': installed, 'bd-installModalFail': err}">
<template v-if="!installed && !err">
<div slot="body" class="bd-installModalBody">
<div class="bd-installModalTop">
<div class="bd-installModalIcon">
<div v-if="modal.icon" class="bd-installModalCi" :style="{backgroundImage: `url(${modal.icon})`}" />
<MiExtension v-else />
</div>
<div class="bd-installModalInfo">
<span>{{modal.config.info.name}} v{{modal.config.info.version}} by {{modal.config.info.authors.map(a => a.name).join(', ')}}</span>
<div class="bd-installModalDesc">
{{modal.config.info.description}}
</div>
</div>
</div>
<div class="bd-installModalBottom">
<div v-for="(perm, i) in modal.config.permissions" :key="`perm-${i}`" class="bd-permScope">
<div class="bd-permAllow">
<div class="bd-permCheck">
<div class="bd-permCheckInner"></div>
</div>
<div class="bd-permInner">
<div class="bd-permName">{{perm.HEADER}}</div>
<div class="bd-permDesc">{{perm.BODY}}</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="verifying" slot="footer" class="bd-installModalFooter">
<span class="bd-installModalStatus">Verifying {{modal.contentType}}</span>
</div>
<div v-else-if="!verified" slot="footer" class="bd-installModalFooter">
<span class="bd-installModalStatus bd-err">Not verified!</span>
<div class="bd-button bd-installModalUpload" @click="modal.confirm(0); modal.close();" v-if="modal.canUpload">Upload</div>
<div class="bd-button bd-err" @click="install" v-if="allowUnsafe">{{ !alreadyInstalled ? 'Install' : 'Update' }}</div>
</div>
<div v-else-if="alreadyInstalled && upToDate" slot="footer" class="bd-installModalFooter">
<span class="bd-installModalStatus">Up to date version already installed!</span>
<div class="bd-button bd-installModalUpload" @click="modal.confirm(0); modal.close();" v-if="modal.canUpload">Upload</div>
</div>
<div v-else slot="footer" class="bd-installModalFooter">
<span class="bd-installModalStatus bd-ok">Verified!</span>
<div class="bd-button bd-installModalUpload" @click="modal.confirm(0); modal.close();" v-if="modal.canUpload">Upload</div>
<div class="bd-button bd-ok" @click="install">{{ !alreadyInstalled ? 'Install' : 'Update' }}</div>
</div>
</template>
<template v-else-if="err">
<div slot="body" class="bd-installModalBody">
<h3>Something went wrong :(</h3>
<MiError />
</div>
<div slot="footer" class="bd-installModalFooter bd-installModalErrMsg">
{{err.message}}
<span>Ctrl+Shift+I</span>
</div>
</template>
<template v-else>
<div slot="body" class="bd-installModalBody">
<h3>{{alreadyInstalled ? 'Succesfully Updated!' : 'Successfully Installed!'}}</h3>
<MiSuccessCircle/>
</div>
<div slot="footer" class="bd-installModalFooter">
<div class="bd-button bd-ok" v-if="installed.hasSettings" @click="showSettingsModal">Settings</div>
<div class="bd-button bd-ok" @click="modal.confirm(); modal.close();">OK</div>
</div>
</template>
</Modal>
</template>
<script>
// Imports
import { Modal, MiExtension, MiSuccessCircle, MiError } from '../../common';
import { PluginManager, ThemeManager, PackageInstaller, Settings } from 'modules';
export default {
data() {
return {
installing: false,
verifying: true,
alreadyInstalled: false,
upToDate: true,
allowUnsafe: Settings.getSetting('security', 'default', 'unsafe-content').value,
installed: false,
err: null
}
},
props: ['modal'],
components: {
Modal, MiExtension, MiSuccessCircle, MiError
},
mounted() {
const { contentType, config } = this.modal;
const alreadyInstalled = contentType === 'plugin' ? PluginManager.getPluginById(config.info.id) : ThemeManager.getContentById(config.info.id);
if (alreadyInstalled) {
this.alreadyInstalled = true;
if (config.info.version > alreadyInstalled.version) {
this.upToDate = false;
}
}
this.verify();
},
methods: {
async verify() {
const verified = await PackageInstaller.verifyPackage(this.modal.filePath);
this.verified = verified;
this.verifying = false;
},
async install() {
try {
const installed = await PackageInstaller.installPackage(this.modal.filePath, this.modal.config.info.id || this.modal.config.info.name, this.modal.contentType, this.alreadyInstalled);
this.installed = installed;
} catch (err) {
console.log(err);
this.err = err;
}
},
showSettingsModal() {
this.installed.showSettingsModal();
}
}
}
</script>

View File

@ -22,3 +22,5 @@ export { default as MiLock } from './materialicons/Lock.vue';
export { default as MiImagePlus } from './materialicons/ImagePlus.vue';
export { default as MiIcVpnKey } from './materialicons/IcVpnKey.vue';
export { default as MiArrowLeft } from './materialicons/ArrowLeft.vue';
export { default as MiBoxDownload } from './materialicons/BoxDownload.vue';
export { default as MiSuccessCircle } from './materialicons/SuccessCircle.vue';

View File

@ -11,7 +11,7 @@
<template>
<div :class="['bd-modal', {'bd-modalOut': closing, 'bd-modalScrolled': scrolled}]">
<div class="bd-modalInner">
<div class="bd-modalHeader">
<div class="bd-modalHeader" v-if="!noheader">
<slot name="header">
<div v-if="$slots.icon" class="bd-modalIcon">
<slot name="icon" />
@ -41,7 +41,7 @@
import { MiClose } from './MaterialIcon';
export default {
props: ['headertext', 'closing'],
props: ['headertext', 'closing', 'noheader'],
components: {
MiClose
},

View File

@ -10,7 +10,7 @@
<template>
<div class="bd-scrollerWrap" :class="{'bd-dark': dark}">
<div class="bd-scroller">
<div class="bd-scroller" @scroll="onscroll">
<slot/>
</div>
</div>
@ -18,6 +18,13 @@
<script>
export default {
props: ['dark']
props: ['dark', 'scrollend'],
methods: {
onscroll(e) {
if (!this.scrollend) return;
const { offsetHeight, scrollTop, scrollHeight } = e.target;
if (offsetHeight + scrollTop >= scrollHeight) this.scrollend(e);
}
}
}
</script>

View File

@ -0,0 +1,27 @@
/**
* BetterDiscord Material Design Icon
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
* All rights reserved.
* https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* Material Design Icons
* Copyright (c) 2014 Google
* Apache 2.0 LICENSE
* https://www.apache.org/licenses/LICENSE-2.0.txt
*/
<template>
<span class="bd-materialDesignIcon">
<svg :width="size || 24" :height="size || 24" viewBox="0 0 24 24">
<path d="M 4.9956,3L 19.0076,3L 20.7359,5.99339L 20.7304,5.99653C 20.9018,6.29149 21,6.63428 21,7L 21,19C 21,20.1046 20.1046,21 19,21L 5,21C 3.89543,21 3,20.1046 3,19L 3,7C 3,6.638 3.09618,6.29846 3.26437,6.00554L 3.26135,6.0038L 4.9956,3 Z M 5.57294,4.00001L 4.99559,5.00001L 5,5.00001L 19.0076,5.00002L 18.4303,4.00001L 5.57294,4.00001 Z M 7,12L 12,17L 17,12L 14,12L 14,10L 10,10L 10,12L 7,12 Z "/>
</svg>
</span>
</template>
<script>
export default {
props: ['size']
}
</script>

View File

@ -0,0 +1,27 @@
/**
* BetterDiscord Material Design Icon
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
* All rights reserved.
* https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* Material Design Icons
* Copyright (c) 2014 Google
* Apache 2.0 LICENSE
* https://www.apache.org/licenses/LICENSE-2.0.txt
*/
<template>
<span class="bd-materialDesignIcon">
<svg :width="size || 24" :height="size || 24" viewBox="0 0 24 24">
<path d="M 19.9994,11.9981C 19.9994,16.4161 16.4174,19.9981 11.9994,19.9981C 7.58139,19.9981 3.99939,16.4161 3.99939,11.9981C 3.99939,7.58007 7.58139,3.99807 11.9994,3.99807C 12.7634,3.99807 13.5004,4.11207 14.2004,4.31207L 15.7724,2.74007C 14.6074,2.26467 13.3354,1.99807 11.9994,1.99807C 6.47638,1.99807 1.99939,6.47507 1.99939,11.9981C 1.99939,17.5211 6.47638,21.9981 11.9994,21.9981C 17.5224,21.9981 21.9994,17.5211 21.9994,11.9981M 7.91339,10.0841L 6.49939,11.4981L 10.9994,15.9981L 20.9994,5.99807L 19.5854,4.58407L 10.9994,13.1701L 7.91339,10.0841 Z " />
</svg>
</span>
</template>
<script>
export default {
props: ['size']
}
</script>

View File

@ -9,7 +9,7 @@
*/
import { Utils, ClientLogger as Logger } from 'common';
import { ReactComponents, Reflection, MonkeyPatch } from 'modules';
import { Reflection, MonkeyPatch } from 'modules';
import { VueInjector, Toasts } from 'ui';
import CMGroup from './components/contextmenu/Group.vue';

View File

@ -17,6 +17,7 @@ import ErrorModal from './components/bd/modals/ErrorModal.vue';
import SettingsModal from './components/bd/modals/SettingsModal.vue';
import PermissionModal from './components/bd/modals/PermissionModal.vue';
import InputModal from './components/bd/modals/InputModal.vue';
import InstallModal from './components/bd/modals/InstallModal.vue';
let modals = 0;
@ -190,6 +191,19 @@ export default class Modals {
return new Modal(modal, InputModal);
}
static installModal(contentType, config, filePath, icon, canUpload = false) {
return this.add(this.createInstallModal(contentType, config, filePath, icon, canUpload));
}
static createInstallModal(contentType, config, filePath, icon, canUpload = false) {
const modal = { contentType, config, filePath, icon, canUpload };
modal.promise = new Promise((resolve, reject) => {
modal.confirm = value => resolve(value);
modal.beforeClose = () => reject();
});
return new Modal(modal, InstallModal);
}
/**
* Creates a new permissions modal and adds it to the open stack.
* The modal will have a promise property that will be set to a Promise object that is resolved or rejected if the user accepts the permissions or closes the modal.

View File

@ -87,8 +87,7 @@ export default class extends Module {
async patchNameTag() {
if (this.PatchedNameTag) return this.PatchedNameTag;
const selector = Reflection.resolve('nameTag', 'username', 'discriminator', 'ownerIcon').selector;
const NameTag = await ReactComponents.getComponent('NameTag', {selector});
const NameTag = await ReactComponents.getComponent('NameTag');
this.PatchedNameTag = class extends NameTag.component {
render() {

View File

@ -9,6 +9,7 @@ export * from './contextmenus';
export { default as VueInjector } from './vueinjector';
export { default as Reflection } from './reflection';
export { default as Autocomplete } from './autocomplete';
export { default as ProfileBadges } from './profilebadges';
export { default as ClassNormaliser } from './classnormaliser';

View File

@ -71,7 +71,7 @@ export default class {
get vueMount() {
const element = ReactDOM.findDOMNode(this);
if (!element) return;
if (!element) return null;
if (this.props.mountAtTop) return element;
if (element.firstChild) return element.firstChild;
const newElement = document.createElement('span');

View File

@ -0,0 +1,63 @@
const path = require('path');
const webpack = require('webpack');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const jsLoader = {
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
};
const vueLoader = {
test: /\.(vue)$/,
use: 'vue-loader'
};
const scssLoader = {
test: /\.scss$/,
exclude: /node_modules/,
use: ['css-loader', 'sass-loader']
};
module.exports = {
entry: './src/index.js',
module: {
rules: [jsLoader, vueLoader, scssLoader]
},
externals: {
electron: 'require("electron")',
fs: 'require("fs")',
path: 'require("path")',
util: 'require("util")',
process: 'require("process")',
net: 'require("net")',
request: 'require(require("path").join(require("electron").remote.app.getAppPath(), "node_modules", "request"))',
sparkplug: 'require("../../core/dist/sparkplug")',
'node-crypto': 'require("crypto")',
'child_process': 'require("child_process")'
},
resolve: {
alias: {
vue$: path.resolve('..', 'node_modules', 'vue', 'dist', 'vue.esm.js')
},
modules: [
path.resolve('..', 'node_modules'),
path.resolve('..', 'common', 'modules'),
path.resolve('src', 'modules'),
path.resolve('src', 'ui'),
path.resolve('src', 'plugins'),
path.resolve('src', 'structs'),
path.resolve('src', 'builtin')
]
},
node: {
process: false,
__filename: false,
__dirname: false
},
plugins: [
new VueLoaderPlugin()
]
};

View File

@ -1,67 +1,22 @@
const path = require('path');
const baseconfig = require('./webpack.base.config');
const merge = require('webpack-merge');
const webpack = require('webpack');
const jsLoader = {
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['react']
}
};
const vueLoader = {
test: /\.(vue)$/,
loader: 'vue-loader'
};
const scssLoader = {
test: /\.scss$/,
exclude: /node_modules/,
loader: ['css-loader', 'sass-loader']
};
module.exports = {
entry: './src/index.js',
const config = {
mode: 'development',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'betterdiscord.client.js'
},
module: {
loaders: [jsLoader, vueLoader, scssLoader]
},
externals: {
electron: 'require("electron")',
fs: 'require("fs")',
path: 'require("path")',
util: 'require("util")',
process: 'require("process")',
net: 'require("net")',
request: 'require(require("path").join(require("electron").remote.app.getAppPath(), "node_modules", "request"))',
sparkplug: 'require("../../core/dist/sparkplug")',
'node-crypto': 'require("crypto")'
},
resolve: {
alias: {
vue$: path.resolve('..', 'node_modules', 'vue', 'dist', 'vue.esm.js')
},
modules: [
path.resolve('..', 'node_modules'),
path.resolve('..', 'common', 'modules'),
path.resolve('src', 'modules'),
path.resolve('src', 'ui'),
path.resolve('src', 'plugins'),
path.resolve('src', 'structs'),
path.resolve('src', 'builtin')
]
},
node: {
process: false,
__filename: false,
__dirname: false
},
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.EvalSourceMapDevToolPlugin()
]
new webpack.NamedModulesPlugin()
],
externals: {
asar: 'require("asar")'
}
};
module.exports = merge(baseconfig, config);

View File

@ -1,70 +1,24 @@
const path = require('path');
const baseconfig = require('./webpack.base.config');
const merge = require('webpack-merge');
const webpack = require('webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const jsLoader = {
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['react']
}
};
const vueLoader = {
test: /\.(vue)$/,
loader: 'vue-loader'
};
const scssLoader = {
test: /\.scss$/,
exclude: /node_modules/,
loader: ['css-loader', 'sass-loader']
};
module.exports = {
entry: './src/index.js',
const config = {
mode: 'production',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'betterdiscord.client-release.js'
},
module: {
loaders: [jsLoader, vueLoader, scssLoader]
},
externals: {
electron: 'require("electron")',
fs: 'require("fs")',
path: 'require("path")',
util: 'require("util")',
process: 'require("process")',
net: 'require("net")',
request: 'require(require("path").join(require("electron").remote.app.getAppPath(), "node_modules", "request"))',
sparkplug: 'require("./sparkplug")',
'node-crypto': 'require("crypto")'
},
resolve: {
alias: {
vue$: path.resolve('..', 'node_modules', 'vue', 'dist', 'vue.esm.js')
},
modules: [
path.resolve('..', 'node_modules'),
path.resolve('..', 'common', 'modules'),
path.resolve('src', 'modules'),
path.resolve('src', 'ui'),
path.resolve('src', 'plugins'),
path.resolve('src', 'structs'),
path.resolve('src', 'builtin')
]
},
node: {
process: false,
__filename: false,
__dirname: false
},
plugins: [
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true)
}),
new UglifyJsPlugin()
]
})
],
externals: {
sparkplug: 'require("./sparkplug")'
}
};
module.exports = merge(baseconfig, config);

51
common/modules/axi.js Normal file
View File

@ -0,0 +1,51 @@
/**
* BetterDiscord axios wrapper
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
* All rights reserved.
* https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import axios from 'axios';
export default class AxiosWrapper {
static get axios() { return axios; }
static get github() {
return this._github ? this._github : (
this._github = {
main: this.create('https://github.com'),
api: this.create('https://api.github.com')
}
);
}
static get zl() {
return this._zl ? this._zl : (this._zl = {
api: this.create('https://zl', 1000, this.zlHeaders),
cdn: this.create('https://zl', 1000, this.zlHeaders)
});
}
static create(baseUrl, timeout = 1000, headers = null) {
return axios.create({ baseURL: baseUrl, timeout, headers: headers ? headers : this.defaultHeaders });
}
static get defaultHeaders() {
return {
'User-Agent': 'BetterDiscordApp User'
};
}
static get zlHeaders() {
return {
'User-Agent': 'BetterDiscordApp User',
'X-ZL-Apikey': '1a20cce89a2dbd163fc9570f3246c20891e62b2818ada55f82fa3d1d96fa7ef4',
'X-ZL-User': 'anonymous'
}
}
}

View File

@ -3,3 +3,4 @@ export { default as Filters } from './filters';
export { default as Logger, ClientLogger } from './logger';
export { default as ClientIPC } from './bdipc';
export { default as AsyncEventEmitter } from './async-eventemitter';
export { default as Axi } from './axi';

17
core/babel.config.js Normal file
View File

@ -0,0 +1,17 @@
module.exports = function(api) {
api.cache(true);
const presets = [['@babel/env', {
targets: {
'node': '8.6.0'
}
}]];
const plugins = [];
return {
presets,
plugins
}
}

View File

@ -2,7 +2,7 @@
"name": "bdcore",
"description": "BetterDiscord core package",
"author": "Jiiks",
"version": "2.0.0b",
"version": "2.0.0-beta.6",
"homepage": "https://betterdiscord.net",
"license": "MIT",
"main": "dist/main.js",

25
core/src/csp.json Normal file
View File

@ -0,0 +1,25 @@
{
"img-src": [
"https://cdn.betterttv.net",
"https://cdn.frankerfacez.com",
"https://i.imgur.com"
],
"style-src": [
"https://fonts.googleapis.com"
],
"script-src": [
"'sha256-fSHKdpQGCHaIqWP3SpJOuUHrLp49jy4dWHzZ/RBJ/p4='",
"'sha256-VFJcfKY5B3EBkFDgQnv3CozPwBlZcxwssfLVWlPFfZU='",
"'sha256-VzDmLZ4PxPkOS/KY7ITzLQsSWhfCnvUrNculcj8UNgE='",
"'sha256-l6K+77Z1cmldR9gIvaVWlboF/zr5MXCQHcsEHfnr5TU='"
],
"connect-src": [
"https://github.com",
"https://api.github.com",
"https://betterdiscord.net",
"https://api.betterdiscord.net",
"https://cdn.betterdiscord.net",
"https://api.supersecretbdapiandcdn.net",
"https://cdn.supersecretbdapiandcdn.net"
]
}

3
core/src/csp.txt Normal file
View File

@ -0,0 +1,3 @@
React Devtools: sha256-fSHKdpQGCHaIqWP3SpJOuUHrLp49jy4dWHzZ/RBJ/p4=
Vue Devtools: sha256-VFJcfKY5B3EBkFDgQnv3CozPwBlZcxwssfLVWlPFfZU=
Vue Detector: sha256-l6K+77Z1cmldR9gIvaVWlboF/zr5MXCQHcsEHfnr5TU=

Some files were not shown because too many files have changed in this diff Show More