# Beeper Custom Theme Styles
## Table of Contents
- [devalias' Beeper CSS Hacks](#devalias-beeper-css-hacks)
- [See Also](#see-also)
- [My Other Related Deepdive Gist's and Projects](#my-other-related-deepdive-gists-and-projects)
- [Bonus Section](#bonus-section)
- [Beeper DevTools Hacks](#beeper-devtools-hacks)
- [Beeper Desktop v4 Hacks](#beeper-desktop-v4-hacks)
- [Beeper Update Check URL](#beeper-update-check-url)
- [Accessing the Matrix client from within Beeper / Electron's DevTools](#accessing-the-matrix-client-from-within-beeper--electrons-devtools)
- [Enabling 'internal' mode to see extra labs features/etc](#enabling-internal-mode-to-see-extra-labs-featuresetc)
- [Accessing the React internals for the Beeper 'Rooms' component using Beeper / Electron's DevTools](#accessing-the-react-internals-for-the-beeper-rooms-component-using-beeper--electrons-devtools)
- [Some notes on how to achieve custom Beeper / Electron JS hacks/customisations (eg. more customizability than CSS hacks alone)](#some-notes-on-how-to-achieve-custom-beeper--electron-js-hackscustomisations-eg-more-customizability-than-css-hacks-alone)
- [Implement plugin manager implementation (like BetterDiscord / GooseMod / etc have)](#implement-plugin-manager-implementation-like-betterdiscord--goosemod--etc-have)
- [Various Beeper Inbox Selectors (Favourite, Pinned, Not Pinned, Unread, Etc)](#various-beeper-inbox-selectors-favourite-pinned-not-pinned-unread-etc)
- [Counting chats that match the above](#counting-chats-that-match-the-above)
## devalias' Beeper CSS Hacks
For the modern Beeper Desktop v4, see [`devalias-beeper-css-hacks-v4.css`](#file-devalias-beeper-css-hacks-v4-css) for the customisations I am using myself.
For the legacy Beeper Desktop v3, see [`devalias-beeper-css-hacks.css`](#file-devalias-beeper-css-hacks-css) for the customisations I was using myself.
You'll also find a number of hacks/techniques on my theme issue:
- [Improving UI/UX for users with lots of favourites](https://github.com/beeper/themes/issues/6#issue-1687729358) `DES-10976`
- [Improving UI/UX with the Custom CSS TextArea](https://github.com/beeper/themes/issues/6#issue-1687729358) `DES-10977`
- [Add an 'external link' icon next to the 'Chat Networks' 'tab' in the main 'Settings' dialog](https://github.com/beeper/themes/issues/6#issuecomment-1529282836) `DES-10656`
- [Reduce the 'in your face' bright/colourful/'glare' from the network icons on the new 'View Space Bar in side panel' lab](https://github.com/beeper/themes/issues/6#issuecomment-1534018452) `DES-10915`
- [Add the 🔗 emoji after the 'Chat Networks' item in the main settings menu + remove extra noise from settings menu](https://github.com/beeper/themes/issues/6#issuecomment-1534125906) `Community Request` `DES-10656`
- [Remove the 'Archive' button from the new 'beeper spaces sidebar'](https://github.com/beeper/themes/issues/6#issuecomment-1539270705) `Community Request`
- [Hide the floating 'Archived' and 'Sweep' buttons in the new 'beeper space sidebar'](https://github.com/beeper/themes/issues/6#issuecomment-1539293880) `+Community Contribution` `DES-10914`
- [Change the network icons in the new 'beeper space sidebar' to use the old lineart icons](https://github.com/beeper/themes/issues/6#issuecomment-1539306021) `DES-10915`
- [With the 'new' 'Beeper Space Sidebar', hide the floating 'open archive' button, and always show the floating 'Archive All Read Messages' button](https://github.com/beeper/themes/issues/6#issuecomment-1552300789) `DES-11233`
- [Reduce the size of the 'new' 'Beeper Space Sidebar' + it's 'chat network' buttons](https://github.com/beeper/themes/issues/6#issuecomment-1562332770) `Community Request`
- [Hide Left Panel/Sidebar While Chat Is Open](https://github.com/beeper/themes/issues/6#issuecomment-1567620807) `Community Request`
- [Change the `min-width` of the Left 'Inbox' Side Panel](https://github.com/beeper/themes/issues/6#issuecomment-1576040092) `Community Contribution`
- [Ensure full image preview is visible, rather than a zoomed part of it](https://github.com/beeper/themes/issues/6#issuecomment-1576095044)
- [Change the colours of existing `svg` images embedded in the DOM](https://github.com/beeper/themes/issues/6#issuecomment-1584029941) `Community Request`
- [Beeper Spacebar Icon Modifications](https://github.com/beeper/themes/issues/6#issuecomment-1586076070) `Community Contribution`
- [Hide the Pin/Unpin and/or 'Archive' buttons that appear on hover of each chat room in inbox](https://github.com/beeper/themes/issues/6#issuecomment-1617368101) `Community Request`
- [Hide the help question mark above the left hand sidebar](https://github.com/beeper/themes/issues/6#issuecomment-1631946537) `Community Request`
- [Hiding the white border to the right of the left hand sidebar](https://github.com/beeper/themes/issues/6#issuecomment-1633452879) `Community Contribution`
- [Kinda hacky beeper "compact room list" css theme](https://github.com/beeper/themes/issues/6#issuecomment-1633467603) `Community Contribution`
- [Hide the timestamp on messages](https://github.com/beeper/themes/issues/6#issuecomment-1641107348) `Community Request`
- [Hide certain chats, chats by network, or entire networks from the unified inbox (while still showing in their specific network view)](https://github.com/beeper/themes/issues/6#issuecomment-1641275289) `Community Request`
- [Ensure 'Report a Problem' dialog doesn't take over the entire screen](https://github.com/beeper/themes/issues/6#issuecomment-1643052185)
- ...some comments and things that I haven't linked here specifically...
- [Beeper Desktop v4 Beta CSS Hack, making the pinned/favourites section height limited, scrollable, and with smaller avatar bubbles](https://github.com/beeper/themes/issues/6#issuecomment-2692091954)
- [Beeper Desktop V4 CSS Hack to make pinned/favourite threads respect the 'unread' filter](https://github.com/beeper/themes/issues/6#issuecomment-2829377649)
- etc
## See Also
- https://www.beeper.com/
- https://github.com/beeper/themes
- > Community Themes
- https://github.com/beeper/themes#how-to-use-themes
- > Click Themes -> select the theme you would like to try
- > Select the text content and copy. This code is called CSS.
- > Open Beeper Desktop -> Settings -> Appearance
- > Paste what you copied earlier into the box and hit Apply
- https://github.com/beeper/themes/issues/6
- > A few custom theme hacks for improving Inbox Favourites (max-height, scrollable, reduce avatar size, etc)
- https://github.com/beeper/themes/pull/7
- https://github.com/beeper/themes/pull/7/files
- https://github.com/beeper/themes/tree/main/themes/utility
- > Reusable css that could be added onto other themes for additional functionality
- Announcement Tweet: https://twitter.com/_devalias/status/1651766148046921728
- Reddit Thread: https://www.reddit.com/r/beeper/comments/13cezdc/beeper_ui_ux_hacksimprovements_via_custom_themes/
### My Other Related Deepdive Gist's and Projects
- https://gist.github.com/0xdevalias
- https://github.com/0xdevalias/chatgpt-source-watch : Analyzing the evolution of ChatGPT's codebase through time with curated archives and scripts.
- [Reverse engineering ChatGPT's frontend web app + deep dive explorations of the code (0xdevalias' gist)](https://gist.github.com/0xdevalias/4ac297ee3f794c17d0997b4673a2f160#reverse-engineering-chatgpts-frontend-web-app--deep-dive-explorations-of-the-code)
- [Deobfuscating / Unminifying Obfuscated Web App Code (0xdevalias' gist)](https://gist.github.com/0xdevalias/d8b743efb82c0e9406fc69da0d6c6581#deobfuscating--unminifying-obfuscated-web-app-code)
- [Reverse Engineering Webpack Apps (0xdevalias' gist)](https://gist.github.com/0xdevalias/8c621c5d09d780b1d321bfdb86d67cdd#reverse-engineering-webpack-apps)
- [React Server Components, Next.js v13+, and Webpack: Notes on Streaming Wire Format (`__next_f`, etc) (0xdevalias' gist))](https://gist.github.com/0xdevalias/ac465fb2f7e6fded183c2a4273d21e61#react-server-components-nextjs-v13-and-webpack-notes-on-streaming-wire-format-__next_f-etc)
- [Fingerprinting Minified JavaScript Libraries / AST Fingerprinting / Source Code Similarity / Etc (0xdevalias' gist)](https://gist.github.com/0xdevalias/31c6574891db3e36f15069b859065267#fingerprinting-minified-javascript-libraries--ast-fingerprinting--source-code-similarity--etc)
- [JavaScript Web App Reverse Engineering - Module Identification (0xdevalias' gist)](https://gist.github.com/0xdevalias/28c18edfc17606f09cf413f97e404a60#javascript-web-app-reverse-engineering---module-identification)
- [Reverse Engineered Webpack Tailwind-Styled-Component (0xdevalias' gist)](https://gist.github.com/0xdevalias/916e4ababd3cb5e3470b07a024cf3125#reverse-engineered-webpack-tailwind-styled-component)
- [Bypassing Cloudflare, Akamai, etc (0xdevalias' gist)](https://gist.github.com/0xdevalias/b34feb567bd50b37161293694066dd53#bypassing-cloudflare-akamai-etc)
- [Debugging Electron Apps (and related memory issues) (0xdevalias gist)](https://gist.github.com/0xdevalias/428e56a146e3c09ec129ee58584583ba#debugging-electron-apps-and-related-memory-issues)
- [Reverse Engineering Golang (0xdevalias' gist)](https://gist.github.com/0xdevalias/4e430914124c3fd2c51cb7ac2801acba#reverse-engineering-golang)
- [Reverse Engineering on macOS (0xdevalias' gist)](https://gist.github.com/0xdevalias/256a8018473839695e8684e37da92c25#reverse-engineering-on-macos)
## Bonus Section
### Beeper DevTools Hacks
Open devtools with `Command + Shift + I` (on windows) / `Command + Option + I` (on macOS), then run the following (if `Command + Shift + I` doesn't work, then you may need to disable/rebind the Beeper -> Settings -> Keyboard Shortcuts -> 'Toggle Chat Info' shortcut first):
- Set your beeper update channel from `STABLE` to `INTERNAL` to enable various hidden features/settings:
- `rs.beeperClientStore.loggedInUser.channel = "INTERNAL"`
### Beeper Desktop v4 Hacks
- While there doesn't seem to be a menu item for the Electron / Chrome DevTools, we can access it with `Command + Shift + I` (on windows) / `Command + Option + I` (on macOS). If `Command + Shift + I` doesn't work, then you may need to disable/rebind the Beeper -> Settings -> Keyboard Shortcuts -> 'Toggle Chat Info' shortcut first.
- Non-standard globals:
- ```js
[
"desktopAPI",
"PAGE_BOOT_TIME",
"handleFatalError",
"_sentryDebugIds",
"_sentryDebugIdIdentifier",
"SENTRY_RELEASE",
"INITIAL_STATE",
"MACHINE_ID",
"LOG_LEVEL",
"__SENTRY__",
"Sentry",
"logger",
"setImmediate",
"texts",
"__mobxInstanceCount",
"__mobxGlobals",
"rt",
"totalTimes",
"Mousetrap",
"handleDeepLink",
"google",
"__googleMapsCallback__",
"gm_authFailure",
"rs",
"litHtmlVersions",
"litElementVersions",
"reactiveElementVersions",
"module$contents$mapsapi$overlay$overlayView_OverlayView",
"RudderStackGlobals",
"rudderanalytics",
"sentryUser",
"__federation_shared__",
"__isReactDndBackendSetUp"
]
```
- `INITIAL_STATE.userConfPath`: `/Users/REDACTED/Library/Application Support/BeeperTexts/config.json`
- This file doesn't seem to exist by default.
- The electron app source no longer seems to be packed within a `*.asar` file, making it easier to explore/customize:
- `/Applications/Beeper Desktop.app/Contents/Resources/app/`
- `/Applications/Beeper Desktop.app/Contents/Resources/app/build/app-manifest-fallback-dev.json`
- `/Applications/Beeper Desktop.app/Contents/Resources/app/build/app-manifest-fallback-staging.json`
- `/Applications/Beeper Desktop.app/Contents/Resources/app/build/app-manifest-fallback-production.json`
- `/Applications/Beeper Desktop.app/Contents/Resources/app/build-browser/app-manifest-fallback-dev.json`
- `/Applications/Beeper Desktop.app/Contents/Resources/app/build-browser/app-manifest-fallback-staging.json`
- `/Applications/Beeper Desktop.app/Contents/Resources/app/build-browser/app-manifest-fallback-production.json`
- `/Applications/Beeper Desktop.app/Contents/Resources/app/build/main/constants-DaB6VrEL.mjs`
- ```js
const r = process.platform === 'win32',
_ = process.platform === 'linux',
t = process.platform === 'darwin',
E = 'config.json',
// ..snip..
f = '.texts-conf.json';
// ..snip..
function i() {
const e = process.env.BEEPER_PROFILE || void 0,
o = 'BeeperTexts' + (e ? `-${e}` : '');
return t
? s.join(n.homedir(), 'Library', 'Application Support', o)
: r
? process.env.APPDATA
? s.join(process.env.APPDATA, o)
: s.join(n.homedir(), o)
: s.join(
process.env.XDG_CONFIG_HOME ?? s.join(n.homedir(), '.config'),
o
);
}
function A() {
const e = i();
return r && !process.env.APPDATA ? s.join(i(), '..', f) : s.join(e, E);
}
const U = globalThis.INITIAL_STATE?.userConfPath ?? A(),
```
- `/Users/REDACTED/Library/Application Support/BeeperTexts/custom.css`
- CSS Customization is still possible
- `/Users/REDACTED/Library/Application Support/BeeperTexts/account.db`
- The `store` table looks interesting
- Keys generally seem to map to JSON object values
- `ad:com.beeper.desktop.prefs`
- Has entries in the JSON object including:
- `"ai_language": "en"`
- `"ai_engine": "gpt-3.5-turbo"`
- `"ui_experiments:decrease_sidebar_density": true`
- `/Applications/Beeper Desktop.app/Contents/Resources/app/build/renderer/PrefsPanes-DLpM0stC.js`
- ```js
D.show_hidden_features &&
e.jsxs(e.Fragment, {
children: [
e.jsx('h4', { children: 'UI Experiments' }),
e.jsxs('div', {
className: 'prefs-group',
children: [
e.jsx(g, {
id: `ui_experiments:${re.DECREASE_SIDEBAR_DENSITY}`,
displayName: 'Decrease sidebar density',
}),
e.jsx(g, {
id: `ui_experiments:${re.DISTINCT_BUBBLE_COLORS_IN_GROUPS}`,
displayName:
'Distinct bubble color for each participant in groups',
}),
e.jsx(g, {
id: `ui_experiments:${re.VIBRANT_RIGHT_PANE}`,
displayName: 'Vibrant right pane',
}),
],
}),
],
}),
```
- ```js
return e.jsxs('div', {
className: 'subview-prefs subview-prefs-general styled-inputs',
children: [
e.jsxs('div', {
className: 'prefs-group',
children: [
e.jsx(g, {
id: p.GPT3_SLASH_COMMAND,
displayName: 'Show reply suggestions with /a slash command',
featureFlag: r.features[j.AI_COMPOSER_AUTOCOMPLETE],
footer: e.jsxs('span', {
className: 'description-text',
children: [
'Privacy: this will send your last ',
e.jsx('code', { children: 'n' }),
' messages in a conversation to OpenAI via our servers.',
],
}),
}),
e.jsx(g, {
id: p.TRANSCRIBE_WITH_OPENAI,
displayName:
"Use OpenAI's Whisper model for Talk to Type and transcribing voice messages",
featureFlag: r.features[j.TALK_TO_TYPE],
footer: e.jsx('span', {
className: 'description-text',
children:
'Privacy: this will send audio clips to OpenAI via our servers.',
}),
}),
e.jsx(w, {
id: p.AI_ENGINE,
displayName: 'Engine for text-based AI features',
featureFlag: r.features[j.AI_LLM],
options: An,
}),
e.jsx(w, {
id: p.AI_LANGUAGE,
displayName: 'Language for AI responses and translations',
featureFlag: r.features[j.AI_LLM],
options: ws,
footer: e.jsx('span', {
className: 'description-text',
children:
"ChatGPT's response quality may vary for less commonly spoken languages.",
}),
}),
],
}),
```
- `/Applications/Beeper Desktop.app/Contents/Resources/app/build-browser/index-DJ-7DtqJ.js`
- ```js
return D.remoteStore.llmEnabled && D.prefsStore.prefs[Re.GPT3_SLASH_COMMAND]
? { ...f, ...i }
: f;
```
- There seem to be a bunch of references to `__vite__mapDeps` in the `*.js` files
- From ChatGPT 4o:
- > `__vite__mapDeps` is an internal construct used by Vite for module resolution and dependency mapping in bundled web applications. It appears in Vite-processed JavaScript when using **dynamic imports** (e.g., `import()`), and it helps Vite efficiently manage dependencies in optimized builds.
>
> ### Purpose of `__vite__mapDeps`
>
> - **Dependency Mapping:** It is used to keep track of dependencies that are dynamically imported.
> - **Code Splitting & Optimization:** When Vite builds your project, it processes imports to optimize module resolution. `__vite__mapDeps` is part of the mechanism that rewrites dynamic imports to ensure correct execution at runtime.
> - **Preload Handling:** It might be used to preload certain dependencies to improve loading performance.
### Beeper Update Check URL
- eg.
- https://api.beeper.com/desktop/update-feed.json?bundleID=com.automattic.beeper.desktop&version=4.0.551&platform=linux&arch=x64&channel=stable
- https://api.beeper.com/desktop/update-feed.json?bundleID=com.automattic.beeper.desktop&platform=&arch=&channel=nightly
### Accessing the Matrix client from within Beeper / Electron's DevTools
```js
mxMatrixClientPeg.matrixClient
```
eg. to send a message:
```js
const roomId = "!KlacjKWnARbprTLuRM:nova.chat";
mxMatrixClientPeg.matrixClient.sendMessage(roomId, {
msgtype: "m.text",
body: "This is a test message sent using mxMatrixClientPeg.matrixClient.sendMessage"
})
```
### Enabling 'internal' mode to see extra labs features/etc
_(The following tip comes via [`@cameronaaron`](https://github.com/cameronaaron) from the Beeper Community)_
Open DevTools console, and enter the following:
```js
bpWhoamiMonitor.whoami.userInfo.channel = "INTERNAL"
```
Then go to your Beeper Settings -> Labs -> and look at all the new features you can flip (note that you can also probably flip all of these directly via `/devtools` -> Settings Explorer as well)
## Accessing the React internals for the Beeper 'Rooms' component using Beeper / Electron's DevTools
See also:
- https://gist.github.com/0xdevalias/8c621c5d09d780b1d321bfdb86d67cdd#react-internals
- > Reverse Engineering Webpack Apps
- > React Internals
```js
const el = $('#matrixchat > .mx_MatrixChat_wrapper > .mx_MatrixChat > .bp_LeftPanel > .bp_LeftPanel_contentWrapper > .bp_LeftPanel_content > .rooms')
const elProps = Object.getOwnPropertyNames(el);
const elReactFiberKey = elProps.filter(k => k.includes('__reactFiber'))
const elReactPropsKey = elProps.filter(k => k.includes('__reactProps'))
const elReactInternals = {
reactFiber: el[elReactFiberKey],
reactProps: el[elReactPropsKey],
}
//console.log(elReactInternals)
const UnreadList = elReactInternals.reactProps.children[3].props.children[0]
const ReadList = elReactInternals.reactProps.children[3].props.children[1]
console.log('Inbox Chats', UnreadList.props.unreads)
// (303)Â [Room, Room, ...]
console.log('Archived Chats', ReadList.props.rooms)
// (1633)Â [Room, Room, ...]
```
## Some notes on how to achieve custom Beeper / Electron JS hacks/customisations (eg. more customizability than CSS hacks alone)
**Warning:** This section is way more advanced than CSS hacks, and comes with much higher risks if you run arbitrary untrusted code, as using malicious JS code might steal your beeper auth tokens, any of your private messages, etc, etc. You've been warned.
These notes originally come from my [Debugging Electron Apps (and related memory issues) (0xdevalias gist)](https://gist.github.com/0xdevalias/428e56a146e3c09ec129ee58584583ba?permalink_comment_id=4628816#debugging-electron-apps-and-related-memory-issues) gist ([Ref](https://gist.github.com/0xdevalias/428e56a146e3c09ec129ee58584583ba?permalink_comment_id=4628816#gistcomment-4628816)):
> On macOS you can get to the Beeper `*.asar` files by:
>
> ```shell
> ⇒ cd /Applications/Beeper.app/Contents/Resources
>
> ⇒ ls
> af.lproj/ cs.lproj/ fa.lproj/ icon.icns ml.lproj/ ru.lproj/ todesktop-runtime-config.json
> am.lproj/ da.lproj/ fi.lproj/ icons/ mr.lproj/ sk.lproj/ tr.lproj/
> app-update.yml de.lproj/ fil.lproj/ id.lproj/ ms.lproj/ sl.lproj/ uk.lproj/
> app.asar el.lproj/ fr.lproj/ it.lproj/ nb.lproj/ sr.lproj/ ur.lproj/
> app.asar.unpacked/ en.lproj/ gu.lproj/ ja.lproj/ nl.lproj/ sv.lproj/ vi.lproj/
> ar.lproj/ en_GB.lproj/ he.lproj/ kn.lproj/ pl.lproj/ sw.lproj/ webapp.asar
> bg.lproj/ es.lproj/ hi.lproj/ ko.lproj/ pt_BR.lproj/ ta.lproj/ zh_CN.lproj/
> bn.lproj/ es_419.lproj/ hr.lproj/ lt.lproj/ pt_PT.lproj/ te.lproj/ zh_TW.lproj/
> ca.lproj/ et.lproj/ hu.lproj/ lv.lproj/ ro.lproj/ th.lproj/
> ```
>
> Where the most relevant files/folders there are:
>
> - `app.asar`
> - `app.asar.unpacked/`
> - `webapp.asar`
>
> From memory, I believe `app.asar` is more related to the core electron/element type features, and `webapp.asar` was more related to the more custom Beeper features; but I didn't look super deeply into that side of things.
>
> We can then use the node `asar` package via `npx` to inspect the contents of the `*.asar` files:
>
> ```shell
> ⇒ npx asar list --is-pack app.asar | grep -v node_modules
>
> ⇒ npx asar list --is-pack webapp.asar | grep -v node_modules
> ```
>
> We can then run Beeper passing the node remote debugging `--inspect-brk` command to set a breakpoint at the entrypoint of the code:
>
> ```shell
> ⇒ open /Applications/Beeper.app --args --inspect-brk=1337
> ```
>
> Which we can then connect to by opening a Chrome browser, navigating to `chrome://inspect/#devices`, and under 'Remote Target' looking for something like the following:
>
> 
>
> ```
> electron/js2c/browser_init file:///
> ```
>
> Then clicking on 'inspect', which will open the Chrome DevTools 'Sources' tab and show the entrypoint line where the debugger has stopped execution, in this case, in the `file:///Applications/Beeper.app/Contents/Resources/app.asar/lib/electron-main.js` file:
>
> 
>
> We can then skim around the code in this file to understand what it does, and what other options are available.
>
> For example, here are some command line arguments documentation; of which `--devtools` sounds interesting:
>
> ```js
> if (argv["help"]) {
> console.log("Options:");
> console.log(" --profile-dir {path}: Path to where to store the profile.");
> console.log(" --profile {name}: Name of alternate profile to use, allows for running multiple accounts.");
> console.log(" --devtools: Install and use react-devtools and react-perf.");
> console.log(" --no-update: Disable automatic updating.");
> console.log(" --default-frame: Use OS-default window decorations.");
> console.log(" --hidden: Start the application hidden in the system tray.");
> console.log(" --help: Displays this help message.");
> console.log("And more such as --proxy, see:" +
> "https://electronjs.org/docs/api/command-line-switches");
> electron_1.app.exit();
> }
> ```
>
> We can also see some path loading aspects of where the app looks for `webapp.asar`:
>
> ```js
> // Find the webapp resources and set up things that require them
> async function setupGlobals() {
> // find the webapp asar.
> asarPath = await tryPaths("webapp", __dirname, [
> // If run from the source checkout, this will be in the directory above
> '../webapp.asar',
> // but if run from a packaged application, electron-main.js will be in
> // a different asar file so it will be two levels above
> '../../webapp.asar',
> // also try without the 'asar' suffix to allow symlinking in a directory
> '../webapp',
> // from a packaged application
> '../../webapp',
> // Workaround for developing beeper on windows, where symlinks are poorly supported.
> "../../nova-web/webapp",
> ]);
> console.log("Web App Path is", asarPath);
> iconsPath = await tryPaths("icons", __dirname, [
> '../res/icons',
> '../../icons'
> ]);
> console.log("iconsPath path is", iconsPath);
> // eslint-disable-next-line @typescript-eslint/no-var-requires
> vectorConfig = require(asarPath + 'config.json');
> console.log("Loading vector config for brand", vectorConfig.brand);
> try {
> // Load local config and use it to override values from the one baked with the build
> // eslint-disable-next-line @typescript-eslint/no-var-requires
> const localConfig = require(path_1.default.join(electron_1.app.getPath('userData'), 'config.json'));
> ```
>
> There are also some hidden/undocumented CLI arguments, `localdev` / `localapi`:
>
> ```js
> const localdev = Array.isArray(argv._) && argv._.includes("localdev");
> const localapi = Array.isArray(argv._) && argv._.includes("localapi");
> ```
>
> We find the code that processes the `--devtools` arg here:
>
> ```js
> if (argv['devtools']) {
> try {
> const { default: installExt, REACT_DEVELOPER_TOOLS, REACT_PERF, } = require("electron-devtools-installer");
> await installExt([REACT_DEVELOPER_TOOLS, REACT_PERF], { loadExtensionOptions: { allowFileAccess: true } });
> }
> catch (e) {
> console.log(e);
> }
> }
> ```
>
> A little further down we see how `localdev` / `localapi` are handled:
>
> ```js
> if (localdev) {
> // Open dev tools at startup if in dev mode
> mainWindow.webContents.openDevTools();
> electron_1.app.on("certificate-error", (event, webContents, url, error, certificate, callback) => {
> // On certificate error we disable default behaviour (stop loading the page)
> // and we then say "it is all fine - true" to the callback
> event.preventDefault();
> callback(true);
> });
> }
> if (localapi) {
> vectorConfig.novaApiUrl = `https://localhost:4001`;
> }
> mainWindow.loadURL(localdev ? "http://localhost:8080" : 'nova://nova-web/webapp/');
> ```
>
> Then beyond that, you're sort of getting deeper into the internals of Electron apps and how Beeper / Element is built on top of Electron; so really depends what you're wanting to achieve at that point.
>
> ---
>
> While at the initial 'entrypoint' debugger breakpoint (from `--inspect-brk`), we could also choose to manually load/inject some custom code of our own. For example:
>
> With a code file like:
>
> ```js
> // /Users/devalias/Desktop/beeperInjectionHax.js
> console.log("Hello World, is this custom JS in Beeper?");
> ```
>
> We could run the following in the Chrome Devtools console while at the initial app loading debug breakpoint:
>
> ```js
> require('/Users/devalias/Desktop/beeperInjectionHax.js')
> ```
>
> Which would show an output message such as:
> ```
> beeperInjectionHax.js:1 Hello World, is this custom JS in Beeper?
> ```
>
> 
### Implement plugin manager implementation (like BetterDiscord / GooseMod / etc have)
_ponders_ 🤔
Anyone feel like figuring out/hacking out the plugin implementation from BetterDiscord and making it work as a Beeper customisation?
- https://betterdiscord.app/
- > BetterDiscord comes with a builtin plugin loader and plugin API. Plugins can increase the functionality and user experience of the app through JavaScript. Write your own or download plugins made by the community.
- https://betterdiscord.app/plugins
- https://github.com/BetterDiscord/BetterDiscord
- https://github.com/search?q=repo%3ABetterDiscord%2FBetterDiscord%20plugin&type=code
- https://github.com/BetterDiscord/BetterDiscord/blob/main/renderer/src/modules/pluginmanager.js#L28
- https://github.com/BetterDiscord/BetterDiscord/tree/main/injector/src
- https://github.com/BetterDiscord/BetterDiscord/tree/main/injector/src/modules
- https://github.com/BetterDiscord/BetterDiscord/blob/main/injector/src/modules/csp.js
- Strips content-security-policy headers from requests (in a rather 'blunt' way)
- https://github.com/BetterDiscord/BetterDiscord/blob/main/injector/src/modules/reactdevtools.js
- Finds/loads a local copy of ReactDevTools
- https://github.com/BetterDiscord/BetterDiscord/blob/main/injector/src/modules/ipc.js
Another user from the 'Unofficial Beeper Hackers' community suggested the following as a better example for injection/etc concepts:
> `@emma:conduit.rory.gay`: GooseMod may be more interesting to look at (though dead), came with an in-app plugin/theme store
>
> do note that most of the UI leans on pre-existing react modules (that no longer exist)
- https://github.com/GooseMod/GooseMod
- https://github.com/GooseMod/GooseMod/blob/master/src/moduleManager.js
- https://github.com/GooseMod/GooseMod/blob/master/src/index.js#L170
- https://github.com/GooseMod/GooseMod/blob/master/src/util/discord/webpackModules.js
- This module in particular looks interesting; it seems to be a bunch of helpers for injecting into webpack modules at runtime
> `@emma:conduit.rory.gay`: betterdiscord's injectors is litterally just `const BetterDiscord = require("./modules/betterdiscord").default;`
>
> oh, also intercepting all web requests to unset content-security-policy header
> devalias: The other method I was pondering at one point was basically hijacking just enough to load a chrome browser extension, then implement the rest of it via those API's; but didn't explore too deeply into how limited they are within electron
- https://www.electronjs.org/docs/latest/api/extensions
- > Electron supports a subset of the Chrome Extensions API, primarily to support DevTools extensions and Chromium-internal extensions, but it also happens to support some other extension capabilities.
- https://www.electronjs.org/docs/latest/api/extensions#supported-extensions-apis
- A bunch, including the below
- https://www.electronjs.org/docs/latest/api/extensions#chromemanagement
- https://developer.chrome.com/docs/extensions/reference/api/management
- > The `chrome.management` API provides ways to manage the list of extensions/apps that are installed and running.
- https://www.electronjs.org/docs/latest/api/extensions#chromescripting
- > All features of this API are supported.
- https://developer.chrome.com/docs/extensions/reference/api/scripting
- > Use the `chrome.scripting` API to execute script in different contexts.
- > Electron only supports loading unpacked extensions (i.e., .crx files do not work). Extensions are installed per-session. To load an extension, call `ses.loadExtension`
- https://www.electronjs.org/docs/latest/api/session#sesloadextensionpath-options
Some of my notes in gists might be useful too:
- https://gist.github.com/search?q=user:0xdevalias+electron
Most likely these:
- https://gist.github.com/0xdevalias/428e56a146e3c09ec129ee58584583ba#debugging-electron-apps-and-related-memory-issues
- https://gist.github.com/0xdevalias/3d2f5a861335cc1277b21a29d1285cfe#some-notes-on-how-to-achieve-custom-beeper--electron-js-hackscustomisations-eg-more-customizability-than-css-hacks-alone
- > Me: you could inject/hijack/patch webpack/etc modules directly at runtime: https://gist.github.com/0xdevalias/8c621c5d09d780b1d321bfdb86d67cdd#reverse-engineering-webpack-apps
## Various Beeper Inbox Selectors (Favourite, Pinned, Not Pinned, Unread, Etc)
```js
// Inbox - Favourites List
$$('.rooms > .favourites [data-type="bp_RoomTile"]')
// Inbox - Favourites List - Unread
$$('.rooms > .favourites .isUnread[data-type="bp_RoomTile"]')
// Inbox - Favourites List - Muted
$$('.rooms > .favourites .isMuted[data-type="bp_RoomTile"]')
// Inbox Chats
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]')
// Inbox Chats - Favourite
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:has([data-src="img/beeper/heart-filled16.b7ad82d.svg"])')
// Inbox Chats - Pinned
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:has([data-src="img/beeper/pin-filled16.b7cb2af.svg"])')
// Inbox Chats - Not Pinned
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:not(:has([data-src="img/beeper/pin-filled16.b7cb2af.svg"]))')
// Inbox - Unread - Favourite-Avatar
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .favourite-avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon')
// Inbox - Unread - Avatar
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon')
// Inbox - Unread - Combined
$$('.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .favourite-avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon, .rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon')
```
### Counting chats that match the above
This is a bit of a hacky WIP / PoC, but it seems to do the trick:
```css
/*************************/
/* Counting Chat Types */
/************************/
/* Initialize counters */
.rooms {
counter-reset: unread favourite pinned not-pinned;
}
/* Increment favorites */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] [data-src="img/beeper/heart-filled16.b7ad82d.svg"] {
counter-increment: favourite;
}
/* Increment pinned */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] [data-src="img/beeper/pin-filled16.b7cb2af.svg"] {
counter-increment: pinned;
}
/* Increment not pinned */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:not(:has([data-src="img/beeper/pin-filled16.b7cb2af.svg"])) {
counter-increment: not-pinned;
}
/* Increment unread */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .favourite-avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon,
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] > div > .avatar+div > div > div > span:not(.bp_icon)+span > .bp_icon {
counter-increment: unread;
}
/* Show Counters */
/*.rooms::after {
content: "Counters: Unread (" counter(unread) "), Favourite (" counter(favourite) "), Pinned (" counter(pinned) "), Not-Pinned (" counter(not-pinned) ")"
}*/
.rooms::after {
content: "Unread (" counter(unread) "), Not-Pinned (" counter(not-pinned) ")";
font-size: 10px;
position: absolute;
top: calc(31vh - 2px);
left: calc(8vw - 4px);
background-color: dimgrey;
}
/* Set position to relative for the hovered element */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"] {
position: relative;
}
/* Show the not-pinned counter on hover */
.rooms > .rooms_scroll-container [data-type="bp_RoomTile"]:hover::before {
content: "Not-Pinned above: " counter(not-pinned);
font-size: 10px;
white-space: nowrap;
position: absolute;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
border-radius: 3px;
padding: 2px;
background-color: dimgrey;
}
```