From 0bd294480dcd0a29bc3cd53a6934439fbc687ece Mon Sep 17 00:00:00 2001 From: smartfridge <37928912+smartfrigde@users.noreply.github.com> Date: Sat, 19 Nov 2022 20:26:13 +0100 Subject: [PATCH 01/18] Add Windows ARM64 dev build --- .github/workflows/dev.yml | 42 ++++++++++++++++++++++++++++++++++-- .github/workflows/winArm.yml | 38 -------------------------------- 2 files changed, 40 insertions(+), 40 deletions(-) delete mode 100644 .github/workflows/winArm.yml diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 0c540d3..389de9e 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -113,11 +113,44 @@ jobs: with: name: ArmCordWindows.zip path: dist/ArmCord-3.1.0-win.zip - + build-windowsOnARM: + runs-on: windows-latest + + steps: + - uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set architecture + run: set npm_config_arch=arm64 + + - uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json + + - name: Install Node dependencies + run: pnpm install -g cargo-cp-artifact && pnpm install + + - name: Install Electron-Builder + run: pnpm install -g electron-builder + + - name: Replace the version number + run: (Get-Content src/utils.ts) -replace "\d\.\d\.\d", "DEV" | Out-File src/utils.ts + + - name: Build + run: npm run build && electron-builder --windows zip --arm64 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: ArmCordWindowsArm64.zip + path: dist\ArmCord-3.1.0-arm64-win.zip release: runs-on: ubuntu-latest - needs: [build-linux, build-mac, build-windows] + needs: [build-linux, build-mac, build-windows, build-windowsOnARM] steps: - name: Checkout code @@ -142,6 +175,10 @@ jobs: with: name: ArmCordLinuxArm64.zip path: linux + - uses: actions/download-artifact@v2 + with: + name: ArmCordWindowsArm64.zip + path: windows - name: Get some values needed for the release id: vars @@ -171,3 +208,4 @@ jobs: linux/ArmCord-3.1.0-arm64.zip macos/ArmCord-3.1.0-mac.zip windows/ArmCord-3.1.0-win.zip + windows/ArmCord-3.1.0-arm64-win.zip diff --git a/.github/workflows/winArm.yml b/.github/workflows/winArm.yml deleted file mode 100644 index 3dd0dff..0000000 --- a/.github/workflows/winArm.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Windows on ARM -on: - push: - branches: - - dev -jobs: - build-windowsOnARM: - runs-on: windows-latest - - steps: - - uses: actions/setup-node@v3 - with: - node-version: '18' - - - name: Checkout code - uses: actions/checkout@v2 - - - name: Install pnpm - run: npm i -g pnpm && pnpm setup - - name: Set architecture - run: set npm_config_arch=arm64 - - name: Install Node dependencies - run: pnpm install -g cargo-cp-artifact && pnpm install - - - name: Install Electron-Builder - run: pnpm install -g electron-builder - - - name: Build - run: npm run build && electron-builder --windows --arm64 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Delete unpacked builds - run: Remove-Item -LiteralPath ".\dist\win-unpacked" -Force -Recurse - - name: Upload artifact - uses: actions/upload-artifact@v2 - with: - name: ArmCordWindows - path: dist/ \ No newline at end of file From a0e0f6a516b7ba59e79de478489bf04f8464aeaa Mon Sep 17 00:00:00 2001 From: smartfridge <37928912+smartfrigde@users.noreply.github.com> Date: Sat, 19 Nov 2022 23:15:05 +0100 Subject: [PATCH 02/18] Add experimental arrpc --- README.md | 2 +- log.txt | 146 +++++++++++++++ package.json | 1 + pnpm-lock.yaml | 26 +++ src/arrpc/.gitignore | 2 + src/arrpc/LICENSE | 21 +++ src/arrpc/README.md | 44 +++++ src/arrpc/package.json | 22 +++ src/arrpc/simple_mod.js | 31 ++++ src/arrpc/src/bridge.js | 8 + src/arrpc/src/index.js | 11 ++ src/arrpc/src/server.js | 82 +++++++++ src/arrpc/src/transports/ipc.js | 255 ++++++++++++++++++++++++++ src/arrpc/src/transports/websocket.js | 124 +++++++++++++ src/preload/preload.ts | 37 +++- src/window.ts | 4 +- 16 files changed, 813 insertions(+), 3 deletions(-) create mode 100644 log.txt create mode 100644 src/arrpc/.gitignore create mode 100644 src/arrpc/LICENSE create mode 100644 src/arrpc/README.md create mode 100644 src/arrpc/package.json create mode 100644 src/arrpc/simple_mod.js create mode 100644 src/arrpc/src/bridge.js create mode 100644 src/arrpc/src/index.js create mode 100644 src/arrpc/src/server.js create mode 100644 src/arrpc/src/transports/ipc.js create mode 100644 src/arrpc/src/transports/websocket.js diff --git a/README.md b/README.md index c8d7f47..8f01108 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ - **Various mods built in** - Enjoy Cumcord, GooseMod, Flicker, and their many features, or have a more vanilla experience, it's your choice! + Enjoy Vencord, Shelter and their many features, or have a more vanilla experience, it's your choice! - **Made for Privacy™** diff --git a/log.txt b/log.txt new file mode 100644 index 0000000..be8622e --- /dev/null +++ b/log.txt @@ -0,0 +1,146 @@ + +> ArmCord@3.1.0 start +> npm run build && electron ./ts-out/main.js + + +> ArmCord@3.1.0 build +> tsc && copyfiles -u 1 src/**/*.html src/**/**/*.css ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/**/** ts-out/ + +[Config manager] doneSetup: undefined +[Config manager] performanceMode: none +ArmCord has been run before. Skipping setup. +No performance modes set +[Config manager] windowStyle: default +[Config manager] armcordCSP: true +[Config manager] doneSetup: undefined +[Config manager] customIcon: undefined +Setting up CSP unstricter... +[Config manager] trayIcon: default +[Config manager] windowStyle: default +[Config manager] windowStyle: default +[Config manager] ignoreProtocolWarning: undefined +[Config manager] clientName: undefined +[Config manager] 0: undefined +[Config manager] mods: vencord +Downloading mod bundle +[Config manager] mods: vencord +[Config manager] mobileMode: false +[Config manager] trayIcon: default +[Mod loader] Loaded ArmCord Mod Loader made by Vendicated +[Config manager] alternativePaste: false +undefined +[Config manager] inviteWebsocket: true +[Config manager] skipSplash: undefined +[arRPC > ipc] checking /run/user/1000/discord-ipc-0 +Error: connect ECONNREFUSED /run/user/1000/discord-ipc-0 + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { + errno: -111, + code: 'ECONNREFUSED', + syscall: 'connect', + address: '/run/user/1000/discord-ipc-0' +} +[Config manager] channel: stable +[Config manager] mods: vencord +[Config manager] automaticPatches: false +[Config manager] channel: stable +[arRPC > ipc] checked if socket is available: false - reason: timed out +[arRPC > ipc] not available, trying again (attempt 1) +[arRPC > ipc] checking /run/user/1000/discord-ipc-1 +Error: connect ENOENT /run/user/1000/discord-ipc-1 + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { + errno: -2, + code: 'ENOENT', + syscall: 'connect', + address: '/run/user/1000/discord-ipc-1' +} +[arRPC > ipc] checked if socket is available: false - reason: timed out +[arRPC > ipc] not available, trying again (attempt 2) +[arRPC > ipc] checking /run/user/1000/discord-ipc-2 +Error: connect ENOENT /run/user/1000/discord-ipc-2 + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { + errno: -2, + code: 'ENOENT', + syscall: 'connect', + address: '/run/user/1000/discord-ipc-2' +} +[arRPC > ipc] checked if socket is available: false - reason: timed out +[arRPC > ipc] not available, trying again (attempt 3) +[arRPC > ipc] checking /run/user/1000/discord-ipc-3 +Error: connect ENOENT /run/user/1000/discord-ipc-3 + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { + errno: -2, + code: 'ENOENT', + syscall: 'connect', + address: '/run/user/1000/discord-ipc-3' +} +[Window state manager] width: 800 +[Window state manager] height: 600 +[Window state manager] isMaximized: false +[Window state manager] Not maximized. +[Config manager] channel: stable +[Config manager] mods: vencord +[Config manager] automaticPatches: false +[Config manager] channel: stable +[Config manager] mobileMode: false +[arRPC > ipc] checked if socket is available: false - reason: timed out +[arRPC > ipc] not available, trying again (attempt 4) +[arRPC > ipc] checking /run/user/1000/discord-ipc-4 +Error: connect ENOENT /run/user/1000/discord-ipc-4 + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { + errno: -2, + code: 'ENOENT', + syscall: 'connect', + address: '/run/user/1000/discord-ipc-4' +} +[arRPC > ipc] checked if socket is available: false - reason: timed out +[arRPC > ipc] not available, trying again (attempt 5) +[arRPC > ipc] checking /run/user/1000/discord-ipc-5 +Error: connect ENOENT /run/user/1000/discord-ipc-5 + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { + errno: -2, + code: 'ENOENT', + syscall: 'connect', + address: '/run/user/1000/discord-ipc-5' +} +[arRPC > ipc] checked if socket is available: false - reason: timed out +[arRPC > ipc] not available, trying again (attempt 6) +[arRPC > ipc] checking /run/user/1000/discord-ipc-6 +Error: connect ENOENT /run/user/1000/discord-ipc-6 + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { + errno: -2, + code: 'ENOENT', + syscall: 'connect', + address: '/run/user/1000/discord-ipc-6' +} +[arRPC > ipc] checked if socket is available: false - reason: timed out +[arRPC > ipc] not available, trying again (attempt 7) +[arRPC > ipc] checking /run/user/1000/discord-ipc-7 +Error: connect ENOENT /run/user/1000/discord-ipc-7 + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { + errno: -2, + code: 'ENOENT', + syscall: 'connect', + address: '/run/user/1000/discord-ipc-7' +} +[arRPC > ipc] checked if socket is available: false - reason: timed out +[arRPC > ipc] not available, trying again (attempt 8) +[arRPC > ipc] checking /run/user/1000/discord-ipc-8 +Error: connect ENOENT /run/user/1000/discord-ipc-8 + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { + errno: -2, + code: 'ENOENT', + syscall: 'connect', + address: '/run/user/1000/discord-ipc-8' +} +[arRPC > ipc] checked if socket is available: false - reason: timed out +[arRPC > ipc] not available, trying again (attempt 9) +[arRPC > ipc] checking /run/user/1000/discord-ipc-9 +Error: connect ENOENT /run/user/1000/discord-ipc-9 + at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { + errno: -2, + code: 'ENOENT', + syscall: 'connect', + address: '/run/user/1000/discord-ipc-9' +} +[arRPC > ipc] checked if socket is available: false - reason: timed out +[arRPC > ipc] not available, trying again (attempt 10) diff --git a/package.json b/package.json index 98ed1b3..905962c 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "electron-context-menu": "github:ArmCord/electron-context-menu", "extract-zip": "^2.0.1", "node-fetch": "v2", + "arrpc": "file:./src/arrpc", "os-locale": "^6.0.2", "v8-compile-cache": "^2.3.0", "ws": "^8.8.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4dd7f3..671badb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,7 @@ specifiers: '@types/node': ^17.0.42 '@types/node-fetch': ^2.6.2 '@types/ws': ^8.5.3 + arrpc: file:./src/arrpc chalk-cli: ^5.0.0 copyfiles: ^2.4.1 electron: ^20.1.0 @@ -21,6 +22,7 @@ specifiers: dependencies: '@pyke/vibe': github.com/pykeio/vibe/11984868ce9e007859ed91ff159c7f7f0a34e7ae_electron@20.3.1 + arrpc: file:src/arrpc electron-context-menu: github.com/ArmCord/electron-context-menu/280c81398c02a063f46e3285a9708d8db1a7ce32 extract-zip: 2.0.1 node-fetch: 2.6.7 @@ -2188,6 +2190,19 @@ packages: /wrappy/1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + /ws/8.11.0: + resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + /ws/8.9.0: resolution: {integrity: sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==} engines: {node: '>=10.0.0'} @@ -2267,6 +2282,17 @@ packages: engines: {node: '>=10'} dev: true + file:src/arrpc: + resolution: {directory: src/arrpc, type: directory} + name: arrpc + version: 0.1.0 + dependencies: + ws: 8.11.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + github.com/ArmCord/electron-context-menu/280c81398c02a063f46e3285a9708d8db1a7ce32: resolution: {tarball: https://codeload.github.com/ArmCord/electron-context-menu/tar.gz/280c81398c02a063f46e3285a9708d8db1a7ce32} name: electron-context-menu diff --git a/src/arrpc/.gitignore b/src/arrpc/.gitignore new file mode 100644 index 0000000..25c8fdb --- /dev/null +++ b/src/arrpc/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json \ No newline at end of file diff --git a/src/arrpc/LICENSE b/src/arrpc/LICENSE new file mode 100644 index 0000000..f5617f1 --- /dev/null +++ b/src/arrpc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 OpenAsar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/arrpc/README.md b/src/arrpc/README.md new file mode 100644 index 0000000..3106e64 --- /dev/null +++ b/src/arrpc/README.md @@ -0,0 +1,44 @@ +# arRPC + +arRPC is an open source implementation of Discord's half-documented local RPC servers for their desktop client. This open source implementation purely in NodeJS allows it to be used in many places where it is otherwise impossible to do: Discord web and alternative clients like Armcord/etc. It opens a simple bridge WebSocket server which messages the JSON of exactly what to dispatch with in the client with no extra processing needed, allowing small and simple mods or plugins. **It is currently in alpha and is very WIP, expect bugs, etc.** + +### How to try + +1. Clone repo +2. Run server with `node src` (use new Node) +3. Open Discord in browser with CSP disabled (using an extension) +4. Run content of [`simple_mod.js`](simple_mod.js) in console +5. Use an app/thing with RPC +6. Hope it works, if not report bugs :) + +## Supported + +### Transports + +- [x] WebSocket Server + - [x] JSON + - [ ] Erlpack +- [ ] HTTP Server +- [x] IPC + +### Commands + +- [x] DISPATCH +- [ ] AUTHORIZE +- [ ] AUTHENTICATE +- [ ] GET_GUILD +- [ ] GET_GUILDS +- [ ] GET_CHANNEL +- [ ] GET_CHANNELS +- [ ] SUBSCRIBE +- [ ] UNSUBSCRIBE +- [ ] SET_USER_VOICE_SETTINGS +- [ ] SELECT_VOICE_CHANNEL +- [ ] GET_SELECTED_VOICE_CHANNEL +- [ ] SELECT_TEXT_CHANNEL +- [ ] GET_VOICE_SETTINGS +- [ ] SET_VOICE_SETTINGS +- [ ] SET_CERTIFIED_DEVICES +- [x] SET_ACTIVITY +- [ ] SEND_ACTIVITY_JOIN_INVITE +- [ ] CLOSE_ACTIVITY_REQUEST diff --git a/src/arrpc/package.json b/src/arrpc/package.json new file mode 100644 index 0000000..ca74963 --- /dev/null +++ b/src/arrpc/package.json @@ -0,0 +1,22 @@ +{ + "name": "arrpc", + "version": "0.1.0", + "description": "Open Discord RPC server for atypical setups", + "main": "src/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/OpenAsar/arrpc.git" + }, + "author": "OpenAsar", + "license": "MIT", + "bugs": { + "url": "https://github.com/OpenAsar/arrpc/issues" + }, + "homepage": "https://github.com/OpenAsar/arrpc#readme", + "dependencies": { + "ws": "^8.11.0" + } +} diff --git a/src/arrpc/simple_mod.js b/src/arrpc/simple_mod.js new file mode 100644 index 0000000..5b4bc8b --- /dev/null +++ b/src/arrpc/simple_mod.js @@ -0,0 +1,31 @@ +const dispatch = (() => { + let Dispatcher; + + return function (event) { + Dispatcher ??= window.Vencord?.Webpack.Common.FluxDispatcher; + if (!Dispatcher) { + const cache = webpackChunkdiscord_app.push([[Symbol()], {}, (w) => w]).c; + webpackChunkdiscord_app.pop(); + + outer: for (const id in cache) { + const mod = cache[id].exports; + for (const exp in mod) { + if (mod[exp]?.isDispatching) { + Dispatcher = mod[exp]; + break outer; + } + } + } + } + if (!Dispatcher) return; // failed to find, your choice if and how u wanna handle this + + return Dispatcher.dispatch(event); + }; +})(); +const ws = new WebSocket("ws://localhost:1337"); // connect to arRPC bridge +ws.onmessage = (x) => { + msg = JSON.parse(x.data); + console.log(msg); + + dispatch({type: "LOCAL_ACTIVITY_UPDATE", ...msg}); // set RPC status +}; diff --git a/src/arrpc/src/bridge.js b/src/arrpc/src/bridge.js new file mode 100644 index 0000000..2e7d82f --- /dev/null +++ b/src/arrpc/src/bridge.js @@ -0,0 +1,8 @@ +const ws = require("ws"); +const send = (msg) => { + wss.clients.forEach((x) => x.send(JSON.stringify(msg))); +}; + +const wss = new ws.WebSocketServer({port: 1337}); + +module.exports = {send}; diff --git a/src/arrpc/src/index.js b/src/arrpc/src/index.js new file mode 100644 index 0000000..b8d34f8 --- /dev/null +++ b/src/arrpc/src/index.js @@ -0,0 +1,11 @@ +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : {default: mod}; + }; +const server = require("./server.js"); +global.fetch = __importDefault(require("node-fetch")); +async function start() { + const x = await new server.RPCServer(); +} +start(); diff --git a/src/arrpc/src/server.js b/src/arrpc/src/server.js new file mode 100644 index 0000000..4a4a782 --- /dev/null +++ b/src/arrpc/src/server.js @@ -0,0 +1,82 @@ +const rgb = (r, g, b, msg) => `\x1b[38;2;${r};${g};${b}m${msg}\x1b[0m`; +const log = (...args) => console.log(`[${rgb(88, 101, 242, "arRPC")} > ${rgb(87, 242, 135, "bridge")}]`, ...args); + +const IPCServer = require("./transports/ipc.js"); + +const WSServer = require("./transports/websocket.js"); +const Bridge = require("./bridge.js"); + +const lookupAsset = (name, assets) => { + return assets.find((x) => x.name === name)?.id; +}; +class RPCServer { + constructor() { + return (async () => { + this.onConnection = this.onConnection.bind(this); + this.onMessage = this.onMessage.bind(this); + + this.ipc = await new IPCServer.IPCServer(this.onMessage, this.onConnection); + this.ws = await new WSServer.WSServer(this.onMessage, this.onConnection); + })(); + } + + onConnection(socket) { + socket.send({ + cmd: "DISPATCH", + evt: "READY", + data: {} + }); + } + + async onMessage(socket, {cmd, args}) { + switch (cmd) { + case "SET_ACTIVITY": + if (!socket.application) { + socket.application = await ( + await fetch(`https://discord.com/api/v9/oauth2/applications/${socket.clientId}/rpc`) + ).json(); + socket.application.assets = await ( + await fetch(`https://discord.com/api/v9/oauth2/applications/${socket.clientId}/assets`) + ).json(); + log("fetched app info for", socket.clientId, socket.application); + } + + const {activity, pid} = args; + const {buttons, timestamps, instance} = activity; + + const metadata = {}; + const extra = {}; + if (buttons) { + metadata.button_urls = buttons.map((x) => x.url); + extra.buttons = buttons.map((x) => x.label); + } + + if (timestamps) + for (const x in timestamps) { + if (Date.now().toString().length - timestamps[x].toString().length > 2) + timestamps[x] = Math.floor(1e3 * timestamps[x]); + } + + if (activity.assets?.large_image) + activity.assets.large_image = lookupAsset(activity.assets.large_image, socket.application.assets); + if (activity.assets?.small_image) + activity.assets.small_image = lookupAsset(activity.assets.small_image, socket.application.assets); + + Bridge.send({ + activity: { + name: socket.application.name, + application_id: socket.application.id, + type: 0, + metadata, + flags: instance ? 1 << 0 : 0, + ...activity, + ...extra + }, + pid + }); + + break; + } + } +} +module.exports = {RPCServer}; diff --git a/src/arrpc/src/transports/ipc.js b/src/arrpc/src/transports/ipc.js new file mode 100644 index 0000000..3ef8fb4 --- /dev/null +++ b/src/arrpc/src/transports/ipc.js @@ -0,0 +1,255 @@ +const rgb = (r, g, b, msg) => `\x1b[38;2;${r};${g};${b}m${msg}\x1b[0m`; +const log = (...args) => console.log(`[${rgb(88, 101, 242, "arRPC")} > ${rgb(254, 231, 92, "ipc")}]`, ...args); +const path = require("path"); +const {platform, env} = require("process"); +const {unlinkSync} = require("fs"); +const {createServer, createConnection} = require("net"); + +const SOCKET_PATH = + platform === "win32" + ? "\\\\?\\pipe\\discord-ipc" + : path.join(env.XDG_RUNTIME_DIR || env.TMPDIR || env.TMP || env.TEMP || "/tmp", "discord-ipc"); + +const Types = { + HANDSHAKE: 0, + FRAME: 1, + CLOSE: 2, + PING: 3, + PONG: 4 +}; + +const CloseCodes = { + CLOSE_NORMAL: 1000, + CLOSE_UNSUPPORTED: 1003, + CLOSE_ABNORMAL: 1006 +}; + +const ErrorCodes = { + INVALID_CLIENTID: 4000, + INVALID_ORIGIN: 4001, + RATELIMITED: 4002, + TOKEN_REVOKED: 4003, + INVALID_VERSION: 4004, + INVALID_ENCODING: 4005 +}; + +let uniqueId = 0; + +const encode = (type, data) => { + data = JSON.stringify(data); + const dataSize = Buffer.byteLength(data); + + const buf = Buffer.alloc(dataSize + 8); + buf.writeInt32LE(type, 0); // type + buf.writeInt32LE(dataSize, 4); // data size + buf.write(data, 8, dataSize); // data + + return buf; +}; + +const read = (socket) => { + let resp = socket.read(8); + if (!resp) return; + + resp = Buffer.from(resp); + const type = resp.readInt32LE(0); + const dataSize = resp.readInt32LE(4); + + if (type < 0 || type >= Object.keys(Types).length) throw new Error("invalid type"); + + let data = socket.read(dataSize); + if (!data) throw new Error("failed reading data"); + + data = JSON.parse(Buffer.from(data).toString()); + + switch (type) { + case Types.PING: + socket.emit("ping", data); + socket.write(encode(Types.PONG, data)); + break; + + case Types.PONG: + socket.emit("pong", data); + break; + + case Types.HANDSHAKE: + if (socket._handshook) throw new Error("already handshook"); + + socket._handshook = true; + socket.emit("handshake", data); + break; + + case Types.FRAME: + if (!socket._handshook) throw new Error("need to handshake first"); + + socket.emit("request", data); + break; + + case Types.CLOSE: + socket.end(); + socket.destroy(); + break; + } + + read(socket); +}; + +const socketIsAvailable = async (socket) => { + socket.pause(); + socket.on("readable", () => { + try { + read(socket); + } catch (e) { + log("error whilst reading", e); + + socket.end( + encode(Types.CLOSE, { + code: CloseCodes.CLOSE_UNSUPPORTED, + message: e.message + }) + ); + socket.destroy(); + } + }); + + const stop = () => { + try { + socket.end(); + socket.destroy(); + } catch {} + }; + + const possibleOutcomes = Promise.race([ + new Promise((res) => socket.on("error", res)), // errore + new Promise((res, rej) => socket.on("pong", () => rej("socket ponged"))), // ponged + new Promise((res, rej) => setTimeout(() => rej("timed out"), 1000)) // timed out + ]).then( + () => true, + (e) => e + ); + + socket.write(encode(Types.PING, ++uniqueId)); + + const outcome = await possibleOutcomes; + stop(); + log("checked if socket is available:", outcome === true, outcome === true ? "" : `- reason: ${outcome}`); + + return outcome === true; +}; + +const getAvailableSocket = async (tries = 0) => { + if (tries > 9) { + throw new Error("ran out of tries to find socket", tries); + } + + const path = SOCKET_PATH + "-" + tries; + const socket = createConnection(path); + + log("checking", path); + + if (await socketIsAvailable(socket)) { + if (platform !== "win32") + try { + unlinkSync(path); + } catch {} + + return path; + } + + log(`not available, trying again (attempt ${tries + 1})`); + return getAvailableSocket(tries + 1); +}; + +class IPCServer { + constructor(messageHandler, connectionHandler) { + return new Promise(async (res) => { + this.messageHandler = messageHandler; + this.connectionHandler = connectionHandler; + + this.onConnection = this.onConnection.bind(this); + this.onMessage = this.onMessage.bind(this); + + const server = createServer(this.onConnection); + server.on("error", (e) => { + log("server error", e); + }); + + const socketPath = await getAvailableSocket(); + server.listen(socketPath, () => { + log("listening at", socketPath); + this.server = server; + + res(this); + }); + }); + } + + onConnection(socket) { + log("new connection!"); + + socket.pause(); + socket.on("readable", () => { + try { + read(socket); + } catch (e) { + log("error whilst reading", e); + + socket.end( + encode(Types.CLOSE, { + code: CloseCodes.CLOSE_UNSUPPORTED, + message: e.message + }) + ); + socket.destroy(); + } + }); + + socket.once("handshake", (params) => { + log("handshake:", params); + + const ver = params.v ?? 1; + const clientId = params.client_id ?? ""; + + if (ver !== 1) { + log("unsupported version requested", ver); + + socket.close(ErrorCodes.INVALID_VERSION); + return; + } + + if (clientId === "") { + log("client id required"); + + socket.close(ErrorCodes.INVALID_CLIENTID); + return; + } + + socket.on("error", (e) => { + log("socket error", e); + }); + + socket.on("close", (e) => { + log("socket closed", e); + }); + + socket.on("request", this.onMessage.bind(this, socket)); + + socket._send = socket.send; + socket.send = (msg) => { + log("sending", msg); + socket.write(encode(Types.FRAME, msg)); + }; + + socket.clientId = clientId; + + this.connectionHandler(socket); + }); + } + + onMessage(socket, msg) { + log("message", msg); + this.messageHandler(socket, msg); + } +} + +module.exports = {IPCServer}; diff --git a/src/arrpc/src/transports/websocket.js b/src/arrpc/src/transports/websocket.js new file mode 100644 index 0000000..4de9402 --- /dev/null +++ b/src/arrpc/src/transports/websocket.js @@ -0,0 +1,124 @@ +const rgb = (r, g, b, msg) => `\x1b[38;2;${r};${g};${b}m${msg}\x1b[0m`; +const log = (...args) => console.log(`[${rgb(88, 101, 242, "arRPC")} > ${rgb(235, 69, 158, "websocket")}]`, ...args); +const ws = require("ws"); +const {createServer} = require("http"); +const querystring = require("querystring"); + +const portRange = [6463, 6472]; + +class WSServer { + constructor(messageHandler, connectionHandler) { + return new Promise(async (res) => { + this.messageHandler = messageHandler; + this.connectionHandler = connectionHandler; + + this.onConnection = this.onConnection.bind(this); + this.onMessage = this.onMessage.bind(this); + + let port = portRange[0]; + + let http, wss; + while (port <= portRange[1]) { + try { + log("trying port", port); + + http = createServer(); + http.on("error", (e) => { + log("http error", e); + + if (e.code === "EADDRINUSE") { + log(port, "in use!"); + } + }); + + wss = new ws.WebSocketServer({server: http}); + wss.on("error", (e) => { + log("wss error", e); + }); + + wss.on("connection", this.onConnection); + + http.listen(port, "127.0.0.1", () => { + log("listening on", port); + + this.http = http; + this.wss = wss; + + res(this); + }); + } catch (e) { + log("failed to start", e); + } + + break; + } + }); + } + + onConnection(socket, req) { + const params = querystring.parse(req.url.split("?")[1]); + const ver = parseInt(params.v ?? 1); + const encoding = params.encoding ?? "json"; + const clientId = params.client_id ?? ""; + + const origin = req.headers.origin ?? ""; + + log(`new connection! origin:`, origin, JSON.parse(JSON.stringify(params))); + + if (origin !== "") { + log("origin is defined, denying", origin); + + socket.close(); + return; + } + + if (encoding !== "json") { + log("unsupported encoding requested", encoding); + + socket.close(); + return; + } + + if (ver !== 1) { + log("unsupported version requested", ver); + + socket.close(); + return; + } + + if (clientId === "") { + log("client id required"); + + socket.close(); + return; + } + + socket.clientId = clientId; + socket.encoding = encoding; + + socket.on("error", (e) => { + log("socket error", e); + }); + + socket.on("close", (e, r) => { + log("socket closed", e); + }); + + socket.on("message", this.onMessage.bind(this, socket)); + + socket._send = socket.send; + socket.send = (msg) => { + log("sending", msg); + socket._send(JSON.stringify(msg)); + }; + + this.connectionHandler(socket); + } + + onMessage(socket, msg) { + log("message", JSON.parse(msg)); + this.messageHandler(socket, JSON.parse(msg)); + } +} + +module.exports = {WSServer}; diff --git a/src/preload/preload.ts b/src/preload/preload.ts index 8ab16e2..7536f3c 100644 --- a/src/preload/preload.ts +++ b/src/preload/preload.ts @@ -5,7 +5,7 @@ import "./patch"; import * as fs from "fs"; import * as path from "path"; import {injectHummusTitlebar, injectTitlebar} from "./titlebar"; -import {sleep, addStyle} from "../utils"; +import {sleep, addStyle, addScript} from "../utils"; import {injectMobileStuff} from "./mobile"; var version = ipcRenderer.sendSync("displayVersion"); var channel = ipcRenderer.sendSync("channel"); @@ -42,6 +42,41 @@ if (window.location.href.indexOf("splash.html") > -1) { injectMobileStuff(); } sleep(5000).then(async () => { + addScript(` + const dispatch = (() => { + let Dispatcher; + + return function (event) { + Dispatcher ??= window.Vencord?.Webpack.Common.FluxDispatcher + if (!Dispatcher) { + const cache = webpackChunkdiscord_app.push([[Symbol()], {}, w => w]).c; + webpackChunkdiscord_app.pop() + + outer: + for (const id in cache) { + const mod = cache[id].exports; + for (const exp in mod) { + if (mod[exp]?.isDispatching) { + Dispatcher = mod[exp]; + break outer; + } + } + } + } + if (!Dispatcher) + return; // failed to find, your choice if and how u wanna handle this + + return Dispatcher.dispatch(event); + }; + })(); + const ws = new WebSocket('ws://localhost:1337'); // connect to arRPC bridge + ws.onmessage = x => { + msg = JSON.parse(x.data); + console.log(msg); + + dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...msg }); // set RPC status + }; + `); const cssPath = path.join(__dirname, "../", "/content/css/discord.css"); addStyle(fs.readFileSync(cssPath, "utf8")); await updateLang(); diff --git a/src/window.ts b/src/window.ts index e2fc8fe..94f8c8a 100644 --- a/src/window.ts +++ b/src/window.ts @@ -205,7 +205,9 @@ async function doAfterDefiningTheWindow() { }); console.log(contentPath); if ((await getConfig("inviteWebsocket")) == true) { - await startServer(); + //@ts-ignore + import("arrpc") + //await startServer(); } if (firstRun) { await setLang(Intl.DateTimeFormat().resolvedOptions().locale); From 3b571f2322f3704d97206f13e8e9f422a2fcec86 Mon Sep 17 00:00:00 2001 From: smartfridge <37928912+smartfrigde@users.noreply.github.com> Date: Sat, 19 Nov 2022 23:16:47 +0100 Subject: [PATCH 03/18] [skip ci] Remove log.txt --- log.txt | 146 -------------------------------------------------------- 1 file changed, 146 deletions(-) delete mode 100644 log.txt diff --git a/log.txt b/log.txt deleted file mode 100644 index be8622e..0000000 --- a/log.txt +++ /dev/null @@ -1,146 +0,0 @@ - -> ArmCord@3.1.0 start -> npm run build && electron ./ts-out/main.js - - -> ArmCord@3.1.0 build -> tsc && copyfiles -u 1 src/**/*.html src/**/**/*.css ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/**/** ts-out/ - -[Config manager] doneSetup: undefined -[Config manager] performanceMode: none -ArmCord has been run before. Skipping setup. -No performance modes set -[Config manager] windowStyle: default -[Config manager] armcordCSP: true -[Config manager] doneSetup: undefined -[Config manager] customIcon: undefined -Setting up CSP unstricter... -[Config manager] trayIcon: default -[Config manager] windowStyle: default -[Config manager] windowStyle: default -[Config manager] ignoreProtocolWarning: undefined -[Config manager] clientName: undefined -[Config manager] 0: undefined -[Config manager] mods: vencord -Downloading mod bundle -[Config manager] mods: vencord -[Config manager] mobileMode: false -[Config manager] trayIcon: default -[Mod loader] Loaded ArmCord Mod Loader made by Vendicated -[Config manager] alternativePaste: false -undefined -[Config manager] inviteWebsocket: true -[Config manager] skipSplash: undefined -[arRPC > ipc] checking /run/user/1000/discord-ipc-0 -Error: connect ECONNREFUSED /run/user/1000/discord-ipc-0 - at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { - errno: -111, - code: 'ECONNREFUSED', - syscall: 'connect', - address: '/run/user/1000/discord-ipc-0' -} -[Config manager] channel: stable -[Config manager] mods: vencord -[Config manager] automaticPatches: false -[Config manager] channel: stable -[arRPC > ipc] checked if socket is available: false - reason: timed out -[arRPC > ipc] not available, trying again (attempt 1) -[arRPC > ipc] checking /run/user/1000/discord-ipc-1 -Error: connect ENOENT /run/user/1000/discord-ipc-1 - at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { - errno: -2, - code: 'ENOENT', - syscall: 'connect', - address: '/run/user/1000/discord-ipc-1' -} -[arRPC > ipc] checked if socket is available: false - reason: timed out -[arRPC > ipc] not available, trying again (attempt 2) -[arRPC > ipc] checking /run/user/1000/discord-ipc-2 -Error: connect ENOENT /run/user/1000/discord-ipc-2 - at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { - errno: -2, - code: 'ENOENT', - syscall: 'connect', - address: '/run/user/1000/discord-ipc-2' -} -[arRPC > ipc] checked if socket is available: false - reason: timed out -[arRPC > ipc] not available, trying again (attempt 3) -[arRPC > ipc] checking /run/user/1000/discord-ipc-3 -Error: connect ENOENT /run/user/1000/discord-ipc-3 - at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { - errno: -2, - code: 'ENOENT', - syscall: 'connect', - address: '/run/user/1000/discord-ipc-3' -} -[Window state manager] width: 800 -[Window state manager] height: 600 -[Window state manager] isMaximized: false -[Window state manager] Not maximized. -[Config manager] channel: stable -[Config manager] mods: vencord -[Config manager] automaticPatches: false -[Config manager] channel: stable -[Config manager] mobileMode: false -[arRPC > ipc] checked if socket is available: false - reason: timed out -[arRPC > ipc] not available, trying again (attempt 4) -[arRPC > ipc] checking /run/user/1000/discord-ipc-4 -Error: connect ENOENT /run/user/1000/discord-ipc-4 - at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { - errno: -2, - code: 'ENOENT', - syscall: 'connect', - address: '/run/user/1000/discord-ipc-4' -} -[arRPC > ipc] checked if socket is available: false - reason: timed out -[arRPC > ipc] not available, trying again (attempt 5) -[arRPC > ipc] checking /run/user/1000/discord-ipc-5 -Error: connect ENOENT /run/user/1000/discord-ipc-5 - at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { - errno: -2, - code: 'ENOENT', - syscall: 'connect', - address: '/run/user/1000/discord-ipc-5' -} -[arRPC > ipc] checked if socket is available: false - reason: timed out -[arRPC > ipc] not available, trying again (attempt 6) -[arRPC > ipc] checking /run/user/1000/discord-ipc-6 -Error: connect ENOENT /run/user/1000/discord-ipc-6 - at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { - errno: -2, - code: 'ENOENT', - syscall: 'connect', - address: '/run/user/1000/discord-ipc-6' -} -[arRPC > ipc] checked if socket is available: false - reason: timed out -[arRPC > ipc] not available, trying again (attempt 7) -[arRPC > ipc] checking /run/user/1000/discord-ipc-7 -Error: connect ENOENT /run/user/1000/discord-ipc-7 - at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { - errno: -2, - code: 'ENOENT', - syscall: 'connect', - address: '/run/user/1000/discord-ipc-7' -} -[arRPC > ipc] checked if socket is available: false - reason: timed out -[arRPC > ipc] not available, trying again (attempt 8) -[arRPC > ipc] checking /run/user/1000/discord-ipc-8 -Error: connect ENOENT /run/user/1000/discord-ipc-8 - at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { - errno: -2, - code: 'ENOENT', - syscall: 'connect', - address: '/run/user/1000/discord-ipc-8' -} -[arRPC > ipc] checked if socket is available: false - reason: timed out -[arRPC > ipc] not available, trying again (attempt 9) -[arRPC > ipc] checking /run/user/1000/discord-ipc-9 -Error: connect ENOENT /run/user/1000/discord-ipc-9 - at PipeConnectWrap.afterConnect [as oncomplete] (node:net:1187:16) { - errno: -2, - code: 'ENOENT', - syscall: 'connect', - address: '/run/user/1000/discord-ipc-9' -} -[arRPC > ipc] checked if socket is available: false - reason: timed out -[arRPC > ipc] not available, trying again (attempt 10) From ac806fffb75c47d3a9a909ce3205dbd02a395b28 Mon Sep 17 00:00:00 2001 From: smartfridge <37928912+smartfrigde@users.noreply.github.com> Date: Sat, 19 Nov 2022 23:29:12 +0100 Subject: [PATCH 04/18] Dependencies fix --- package.json | 3 +- pnpm-lock.yaml | 107 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 905962c..1d454a3 100644 --- a/package.json +++ b/package.json @@ -40,12 +40,13 @@ }, "dependencies": { "@pyke/vibe": "github:pykeio/vibe#11984868ce9e007859ed91ff159c7f7f0a34e7ae", + "arrpc": "file:./src/arrpc", "electron-context-menu": "github:ArmCord/electron-context-menu", "extract-zip": "^2.0.1", "node-fetch": "v2", - "arrpc": "file:./src/arrpc", "os-locale": "^6.0.2", "v8-compile-cache": "^2.3.0", + "whatwg-url": "^11.0.0", "ws": "^8.8.0" }, "build": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 671badb..bb532c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,11 +13,14 @@ specifiers: electron-context-menu: github:ArmCord/electron-context-menu extract-zip: ^2.0.1 husky: ^8.0.1 + i: ^0.3.7 node-fetch: v2 + npm: ^9.1.2 os-locale: ^6.0.2 prettier: ^2.7.0 typescript: ^4.7.3 v8-compile-cache: ^2.3.0 + whatwg-url: ^11.0.0 ws: ^8.8.0 dependencies: @@ -25,9 +28,12 @@ dependencies: arrpc: file:src/arrpc electron-context-menu: github.com/ArmCord/electron-context-menu/280c81398c02a063f46e3285a9708d8db1a7ce32 extract-zip: 2.0.1 + i: 0.3.7 node-fetch: 2.6.7 + npm: 9.1.2 os-locale: 6.0.2 v8-compile-cache: 2.3.0 + whatwg-url: 11.0.0 ws: 8.9.0 devDependencies: @@ -1235,6 +1241,11 @@ packages: hasBin: true dev: true + /i/0.3.7: + resolution: {integrity: sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==} + engines: {node: '>=0.4'} + dev: false + /iconv-corefoundation/1.1.7: resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==} engines: {node: ^8.11.2 || >=10} @@ -1617,6 +1628,81 @@ packages: pify: 3.0.0 optional: true + /npm/9.1.2: + resolution: {integrity: sha512-qOFg33/5YCHLArtRBep9HJydPZURbCwt8nxwXDRHZO9PZtTUMCo1C4iXBFPW1TxnzAdHscdw76ihbupdmL7cmw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + dev: false + bundledDependencies: + - '@isaacs/string-locale-compare' + - '@npmcli/arborist' + - '@npmcli/config' + - '@npmcli/map-workspaces' + - '@npmcli/package-json' + - '@npmcli/run-script' + - abbrev + - archy + - cacache + - chalk + - ci-info + - cli-columns + - cli-table3 + - columnify + - fastest-levenshtein + - fs-minipass + - glob + - graceful-fs + - hosted-git-info + - ini + - init-package-json + - is-cidr + - json-parse-even-better-errors + - libnpmaccess + - libnpmdiff + - libnpmexec + - libnpmfund + - libnpmhook + - libnpmorg + - libnpmpack + - libnpmpublish + - libnpmsearch + - libnpmteam + - libnpmversion + - make-fetch-happen + - minimatch + - minipass + - minipass-pipeline + - mkdirp + - ms + - node-gyp + - nopt + - npm-audit-report + - npm-install-checks + - npm-package-arg + - npm-pick-manifest + - npm-profile + - npm-registry-fetch + - npm-user-validate + - npmlog + - p-map + - pacote + - parse-conflict-json + - proc-log + - qrcode-terminal + - read + - read-package-json + - read-package-json-fast + - rimraf + - semver + - ssri + - tar + - text-table + - tiny-relative-date + - treeverse + - validate-npm-package-name + - which + - write-file-atomic + /object-keys/1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -1724,7 +1810,6 @@ packages: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} requiresBuild: true - dev: true /pupa/2.1.1: resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} @@ -2062,6 +2147,13 @@ packages: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false + /tr46/3.0.0: + resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} + engines: {node: '>=12'} + dependencies: + punycode: 2.1.1 + dev: false + /trim-newlines/4.0.2: resolution: {integrity: sha512-GJtWyq9InR/2HRiLZgpIKv+ufIKrVrvjQWEj7PxAXNc5dwbNJkqhAUoAGgzRmULAnoOM5EIpveYd3J2VeSAIew==} engines: {node: '>=12'} @@ -2163,6 +2255,19 @@ packages: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false + /webidl-conversions/7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + dev: false + + /whatwg-url/11.0.0: + resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} + engines: {node: '>=12'} + dependencies: + tr46: 3.0.0 + webidl-conversions: 7.0.0 + dev: false + /whatwg-url/5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: From 35b86b92e33fae7626950db1da8b24e99f05e21f Mon Sep 17 00:00:00 2001 From: smartfridge <37928912+smartfrigde@users.noreply.github.com> Date: Sat, 19 Nov 2022 23:30:51 +0100 Subject: [PATCH 05/18] Update pnpm lock --- pnpm-lock.yaml | 84 -------------------------------------------------- 1 file changed, 84 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb532c7..cb77663 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,9 +13,7 @@ specifiers: electron-context-menu: github:ArmCord/electron-context-menu extract-zip: ^2.0.1 husky: ^8.0.1 - i: ^0.3.7 node-fetch: v2 - npm: ^9.1.2 os-locale: ^6.0.2 prettier: ^2.7.0 typescript: ^4.7.3 @@ -28,9 +26,7 @@ dependencies: arrpc: file:src/arrpc electron-context-menu: github.com/ArmCord/electron-context-menu/280c81398c02a063f46e3285a9708d8db1a7ce32 extract-zip: 2.0.1 - i: 0.3.7 node-fetch: 2.6.7 - npm: 9.1.2 os-locale: 6.0.2 v8-compile-cache: 2.3.0 whatwg-url: 11.0.0 @@ -1241,11 +1237,6 @@ packages: hasBin: true dev: true - /i/0.3.7: - resolution: {integrity: sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==} - engines: {node: '>=0.4'} - dev: false - /iconv-corefoundation/1.1.7: resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==} engines: {node: ^8.11.2 || >=10} @@ -1628,81 +1619,6 @@ packages: pify: 3.0.0 optional: true - /npm/9.1.2: - resolution: {integrity: sha512-qOFg33/5YCHLArtRBep9HJydPZURbCwt8nxwXDRHZO9PZtTUMCo1C4iXBFPW1TxnzAdHscdw76ihbupdmL7cmw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hasBin: true - dev: false - bundledDependencies: - - '@isaacs/string-locale-compare' - - '@npmcli/arborist' - - '@npmcli/config' - - '@npmcli/map-workspaces' - - '@npmcli/package-json' - - '@npmcli/run-script' - - abbrev - - archy - - cacache - - chalk - - ci-info - - cli-columns - - cli-table3 - - columnify - - fastest-levenshtein - - fs-minipass - - glob - - graceful-fs - - hosted-git-info - - ini - - init-package-json - - is-cidr - - json-parse-even-better-errors - - libnpmaccess - - libnpmdiff - - libnpmexec - - libnpmfund - - libnpmhook - - libnpmorg - - libnpmpack - - libnpmpublish - - libnpmsearch - - libnpmteam - - libnpmversion - - make-fetch-happen - - minimatch - - minipass - - minipass-pipeline - - mkdirp - - ms - - node-gyp - - nopt - - npm-audit-report - - npm-install-checks - - npm-package-arg - - npm-pick-manifest - - npm-profile - - npm-registry-fetch - - npm-user-validate - - npmlog - - p-map - - pacote - - parse-conflict-json - - proc-log - - qrcode-terminal - - read - - read-package-json - - read-package-json-fast - - rimraf - - semver - - ssri - - tar - - text-table - - tiny-relative-date - - treeverse - - validate-npm-package-name - - which - - write-file-atomic - /object-keys/1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} From 235015bd241b6f9a80b3761ce44d8aea463ef164 Mon Sep 17 00:00:00 2001 From: CanadaHonk <19228318+CanadaHonk@users.noreply.github.com> Date: Sat, 19 Nov 2022 22:53:11 +0000 Subject: [PATCH 06/18] Rename Invite Websocket setting to Rich Presence (#257) --- assets/lang/en-US.json | 4 ++-- src/settings/settings.html | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/assets/lang/en-US.json b/assets/lang/en-US.json index 094cd29..371e7c1 100644 --- a/assets/lang/en-US.json +++ b/assets/lang/en-US.json @@ -32,8 +32,8 @@ "settings-channel-desc3": "this is alpha test release of Discord. By using it you gain access to the newest\n features and fixes.", "settings-channel-desc4": "public test build. Receives features earlier than stable but is a bit older than Canary.", "settings-channel-desc5": "unofficial instance of Discord that takes you back to 2016! Only client mod\n available to run alongside with it is Cordwood. It's run by community, so you take all the risk by\n using it.", - "settings-invitewebsocket": "Invite Websocket", - "settings-invitewebsocket-desc": "When enabled ArmCord will support Discord.gg links which means that if you open an invite link in your\n browser, ArmCord will automatically accept the invite. Can be unresponsive at times.", + "settings-invitewebsocket": "Rich Presence (Experimental)", + "settings-invitewebsocket-desc": "Uses arRPC to support Discord RPC (Rich Presence) with local programs on your machine. Work in progress.", "settings-altPaste": "Alternative Paste", "settings-altPaste-desc": "If you're on Gnome on Linux or just simply can't paste images copied from other messages, then this is\n for you. This enables alternative module for pasting images. Only enable this when you're experiencing\n issues.", "settings-mod": "Client mod", diff --git a/src/settings/settings.html b/src/settings/settings.html index 603d2cd..7e6fd4a 100644 --- a/src/settings/settings.html +++ b/src/settings/settings.html @@ -62,12 +62,11 @@
- +

- When enabled ArmCord will support Discord.gg links which means that if you open an invite link in your - browser, ArmCord will automatically accept the invite. Can be unresponsive at times. + Uses arRPC to support Discord RPC (Rich Presence) with local programs on your machine. Work in progress.


From 598ac2eb2fe6e6474e2f9a93954a8747c84dd28b Mon Sep 17 00:00:00 2001 From: smartfridge <37928912+smartfrigde@users.noreply.github.com> Date: Sun, 20 Nov 2022 00:41:44 +0100 Subject: [PATCH 07/18] Delete .npmrc --- src/.npmrc | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 src/.npmrc diff --git a/src/.npmrc b/src/.npmrc deleted file mode 100644 index 048b75f..0000000 --- a/src/.npmrc +++ /dev/null @@ -1,3 +0,0 @@ -node-linker=hoisted -public-hoist-pattern=* -shamefully-hoist=true \ No newline at end of file From beaa993e39e22968ee1541bd9a59170305627f4e Mon Sep 17 00:00:00 2001 From: smartfridge <37928912+smartfrigde@users.noreply.github.com> Date: Sun, 20 Nov 2022 00:42:07 +0100 Subject: [PATCH 08/18] Create .npmrc --- .npmrc | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..ee8c4d1 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +node-linker=hoisted +public-hoist-pattern=* +shamefully-hoist=true From ddac01ee8f20c1eb6455dee6423e141665c67ef4 Mon Sep 17 00:00:00 2001 From: smartfridge <37928912+smartfrigde@users.noreply.github.com> Date: Sun, 20 Nov 2022 13:48:07 +0100 Subject: [PATCH 09/18] Update arRPC --- package.json | 9 +- pnpm-lock.yaml | 180 +++++++++----------------- src/arrpc/README.md | 30 ++--- src/arrpc/package.json | 2 +- src/arrpc/simple_mod.js | 43 +++--- src/arrpc/src/bridge.js | 18 ++- src/arrpc/src/index.js | 36 ++++-- src/arrpc/src/server.js | 51 ++++++-- src/arrpc/src/transports/ipc.js | 14 +- src/arrpc/src/transports/websocket.js | 92 +++++++------ src/preload/bridge.ts | 7 + src/preload/preload.ts | 11 +- src/settings/settings.html | 3 +- src/utils.ts | 2 +- 14 files changed, 246 insertions(+), 252 deletions(-) diff --git a/package.json b/package.json index 1d454a3..f212c88 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,7 @@ }, "homepage": "https://github.com/armcord/armcord#readme", "devDependencies": { - "@types/node": "^17.0.42", - "@types/node-fetch": "^2.6.2", + "@types/node": "^17.0.45", "@types/ws": "^8.5.3", "chalk-cli": "^5.0.0", "copyfiles": "^2.4.1", @@ -36,17 +35,15 @@ "electron-builder": "^23.6.0", "husky": "^8.0.1", "prettier": "^2.7.0", - "typescript": "^4.7.3" + "typescript": "^4.9.3" }, "dependencies": { "@pyke/vibe": "github:pykeio/vibe#11984868ce9e007859ed91ff159c7f7f0a34e7ae", "arrpc": "file:./src/arrpc", + "cross-fetch": "^3.1.5", "electron-context-menu": "github:ArmCord/electron-context-menu", "extract-zip": "^2.0.1", - "node-fetch": "v2", - "os-locale": "^6.0.2", "v8-compile-cache": "^2.3.0", - "whatwg-url": "^11.0.0", "ws": "^8.8.0" }, "build": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb77663..b761d9b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,47 +2,41 @@ lockfileVersion: 5.4 specifiers: '@pyke/vibe': github:pykeio/vibe#11984868ce9e007859ed91ff159c7f7f0a34e7ae - '@types/node': ^17.0.42 - '@types/node-fetch': ^2.6.2 + '@types/node': ^17.0.45 '@types/ws': ^8.5.3 arrpc: file:./src/arrpc chalk-cli: ^5.0.0 copyfiles: ^2.4.1 + cross-fetch: ^3.1.5 electron: ^20.1.0 electron-builder: ^23.6.0 electron-context-menu: github:ArmCord/electron-context-menu extract-zip: ^2.0.1 husky: ^8.0.1 - node-fetch: v2 - os-locale: ^6.0.2 prettier: ^2.7.0 - typescript: ^4.7.3 + typescript: ^4.9.3 v8-compile-cache: ^2.3.0 - whatwg-url: ^11.0.0 ws: ^8.8.0 dependencies: - '@pyke/vibe': github.com/pykeio/vibe/11984868ce9e007859ed91ff159c7f7f0a34e7ae_electron@20.3.1 + '@pyke/vibe': github.com/pykeio/vibe/11984868ce9e007859ed91ff159c7f7f0a34e7ae_electron@20.3.5 arrpc: file:src/arrpc + cross-fetch: 3.1.5 electron-context-menu: github.com/ArmCord/electron-context-menu/280c81398c02a063f46e3285a9708d8db1a7ce32 extract-zip: 2.0.1 - node-fetch: 2.6.7 - os-locale: 6.0.2 v8-compile-cache: 2.3.0 - whatwg-url: 11.0.0 - ws: 8.9.0 + ws: 8.11.0 devDependencies: '@types/node': 17.0.45 - '@types/node-fetch': 2.6.2 '@types/ws': 8.5.3 chalk-cli: 5.0.0 copyfiles: 2.4.1 - electron: 20.3.1 + electron: 20.3.5 electron-builder: 23.6.0 - husky: 8.0.1 + husky: 8.0.2 prettier: 2.7.1 - typescript: 4.8.4 + typescript: 4.9.3 packages: @@ -184,15 +178,8 @@ packages: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} dev: true - /@types/node-fetch/2.6.2: - resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==} - dependencies: - '@types/node': 17.0.45 - form-data: 3.0.1 - dev: true - - /@types/node/16.11.64: - resolution: {integrity: sha512-z5hPTlVFzNwtJ2LNozTpJcD1Cu44c4LNuzaq1mwxmiHWQh2ULdR6Vjwo1UGldzRpzL0yUEdZddnfqGW2G70z6Q==} + /@types/node/16.18.3: + resolution: {integrity: sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg==} /@types/node/17.0.45: resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} @@ -287,8 +274,8 @@ packages: dependencies: color-convert: 2.0.1 - /ansi-styles/6.1.1: - resolution: {integrity: sha512-qDOv24WjnYuL+wbwHdlsYZFy+cgPtrYw0Tn7GLORicQp9BkQLzrgI3Pm4VyR9ERZ41YTn7KlMPuL1n05WdZvmg==} + /ansi-styles/6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} dev: true @@ -324,7 +311,7 @@ packages: read-config-file: 6.2.0 sanitize-filename: 1.6.3 semver: 7.3.8 - tar: 6.1.11 + tar: 6.1.12 temp-file: 3.4.0 transitivePeerDependencies: - supports-color @@ -515,8 +502,8 @@ packages: engines: {node: '>=10'} dev: true - /cargo-cp-artifact/0.1.6: - resolution: {integrity: sha512-CQw0doK/aaF7j041666XzuilHxqMxaKkn+I5vmBsd8SAwS0cO5CqVEVp0xJwOKstyqWZ6WK4Ww3O6p26x/Goyg==} + /cargo-cp-artifact/0.1.7: + resolution: {integrity: sha512-pxEV9p1on8vu3BOKstVisF9TwMyGKCBRvzaVpQHuU2sLULCKrn3MJWx/4XlNzmG6xNCTPf78DJ7WCGgr2mOzjg==} hasBin: true dev: false @@ -525,7 +512,7 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true dependencies: - ansi-styles: 6.1.1 + ansi-styles: 6.2.1 chalk: 4.1.2 dot-prop: 6.0.1 get-stdin: 9.0.0 @@ -558,8 +545,9 @@ packages: resolution: {integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==} dev: true - /ci-info/3.4.0: - resolution: {integrity: sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==} + /ci-info/3.6.1: + resolution: {integrity: sha512-up5ggbaDqOqJ4UqLKZ2naVkyqSJQgJi5lwD6b6mM748ysrghDBX0bx/qJTUHzw7zu6Mq4gycviSF5hJnwceD8w==} + engines: {node: '>=8'} dev: true /cli-truncate/2.1.0: @@ -681,6 +669,14 @@ packages: dev: true optional: true + /cross-fetch/3.1.5: + resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==} + dependencies: + node-fetch: 2.6.7 + transitivePeerDependencies: + - encoding + dev: false + /cross-spawn/7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -712,8 +708,8 @@ packages: dependencies: ms: 2.1.2 - /decamelize-keys/1.1.0: - resolution: {integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==} + /decamelize-keys/1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} dependencies: decamelize: 1.2.0 @@ -842,13 +838,13 @@ packages: lazy-val: 1.0.5 read-config-file: 6.2.0 simple-update-notifier: 1.0.7 - yargs: 17.6.0 + yargs: 17.6.2 transitivePeerDependencies: - supports-color dev: true - /electron-dl/3.3.1: - resolution: {integrity: sha512-kmcSYZyHVEHHHFKlZWW58GiCmu2NSu3Rdwnl3+/fr/ftQYHJULVf1QkrCBPFE2bp/Ly113Za7c8wJZs1nBy04A==} + /electron-dl/3.5.0: + resolution: {integrity: sha512-Oj+VSuScVx8hEKM2HEvTQswTX6G3MLh7UoAz/oZuvKyNDfudNi1zY6PK/UnFoK1nCl9DF6k+3PFwElKbtZlDig==} dependencies: ext-name: 5.0.0 pupa: 2.1.1 @@ -869,7 +865,7 @@ packages: compare-version: 0.1.2 debug: 2.6.9 isbinaryfile: 3.0.3 - minimist: 1.2.6 + minimist: 1.2.7 plist: 3.0.6 transitivePeerDependencies: - supports-color @@ -889,14 +885,14 @@ packages: - supports-color dev: true - /electron/20.3.1: - resolution: {integrity: sha512-mgFAa79Zj8oCegsluPAo6O1yXd7mVMZ0JZC2Rak9HQUAQ7x9xuQY7yop/+nMbi+bOMWwe5JrtOcvjotBArxioA==} + /electron/20.3.5: + resolution: {integrity: sha512-xTBjdgAZXf6txxfIhv9mZ3yloJZ+KTht7D2X10uHlFnQu4ZmvzqwhGuQPnldVKhRUDvZehIjulmmrFO6Mz6SzQ==} engines: {node: '>= 10.17.0'} hasBin: true requiresBuild: true dependencies: '@electron/get': 1.14.1 - '@types/node': 16.11.64 + '@types/node': 16.18.3 extract-zip: 2.0.1 transitivePeerDependencies: - supports-color @@ -1012,15 +1008,6 @@ packages: path-exists: 4.0.0 dev: true - /form-data/3.0.1: - resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} - engines: {node: '>= 6'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - dev: true - /form-data/4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -1231,8 +1218,8 @@ packages: - supports-color dev: true - /husky/8.0.1: - resolution: {integrity: sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==} + /husky/8.0.2: + resolution: {integrity: sha512-Tkv80jtvbnkK3mYWxPZePGFpQ/tT3HNSs/sasF9P2YfkMezDl3ON37YN6jUUI4eTg5LcyVynlb6r4eyvOmspvg==} engines: {node: '>=14'} hasBin: true dev: true @@ -1281,11 +1268,6 @@ packages: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} optional: true - /invert-kv/3.0.1: - resolution: {integrity: sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw==} - engines: {node: '>=8'} - dev: false - /is-arrayish/0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true @@ -1294,11 +1276,11 @@ packages: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true dependencies: - ci-info: 3.4.0 + ci-info: 3.6.1 dev: true - /is-core-module/2.10.0: - resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} + /is-core-module/2.11.0: + resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} dependencies: has: 1.0.3 dev: true @@ -1411,13 +1393,6 @@ packages: resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} dev: true - /lcid/3.1.1: - resolution: {integrity: sha512-M6T051+5QCGLBQb8id3hdvIW8+zeFV2FyBGFS9IEK5H9Wt4MueD4bW1eWikpHgZp+5xR3l5c8pZUkQsIA0BFZg==} - engines: {node: '>=8'} - dependencies: - invert-kv: 3.0.1 - dev: false - /lines-and-columns/1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true @@ -1470,7 +1445,7 @@ packages: '@types/minimist': 1.2.2 camelcase-keys: 7.0.2 decamelize: 5.0.1 - decamelize-keys: 1.1.0 + decamelize-keys: 1.1.1 hard-rejection: 2.1.0 minimist-options: 4.1.0 normalize-package-data: 3.0.3 @@ -1535,8 +1510,8 @@ packages: kind-of: 6.0.3 dev: true - /minimist/1.2.6: - resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} + /minimist/1.2.7: + resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} dev: true /minipass/3.3.4: @@ -1602,7 +1577,7 @@ packages: engines: {node: '>=10'} dependencies: hosted-git-info: 4.1.0 - is-core-module: 2.10.0 + is-core-module: 2.11.0 semver: 7.3.8 validate-npm-package-license: 3.0.4 dev: true @@ -1629,13 +1604,6 @@ packages: dependencies: wrappy: 1.0.2 - /os-locale/6.0.2: - resolution: {integrity: sha512-qIb8bzRqaN/vVqEYZ7lTAg6PonskO7xOmM7OClD28F6eFa4s5XGe4bGpHUHMoCHbNNuR0pDYFeSLiW5bnjWXIA==} - engines: {node: '>=12.20'} - dependencies: - lcid: 3.1.1 - dev: false - /p-cancelable/1.1.0: resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} engines: {node: '>=6'} @@ -1726,6 +1694,7 @@ packages: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} requiresBuild: true + dev: true /pupa/2.1.1: resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} @@ -2016,9 +1985,9 @@ packages: has-flag: 4.0.0 dev: true - /tar/6.1.11: - resolution: {integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==} - engines: {node: '>= 10'} + /tar/6.1.12: + resolution: {integrity: sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==} + engines: {node: '>=10'} dependencies: chownr: 2.0.0 fs-minipass: 2.1.0 @@ -2063,13 +2032,6 @@ packages: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false - /tr46/3.0.0: - resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} - engines: {node: '>=12'} - dependencies: - punycode: 2.1.1 - dev: false - /trim-newlines/4.0.2: resolution: {integrity: sha512-GJtWyq9InR/2HRiLZgpIKv+ufIKrVrvjQWEj7PxAXNc5dwbNJkqhAUoAGgzRmULAnoOM5EIpveYd3J2VeSAIew==} engines: {node: '>=12'} @@ -2096,8 +2058,8 @@ packages: engines: {node: '>=10'} dev: true - /typescript/4.8.4: - resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==} + /typescript/4.9.3: + resolution: {integrity: sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==} engines: {node: '>=4.2.0'} hasBin: true dev: true @@ -2171,19 +2133,6 @@ packages: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false - /webidl-conversions/7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - dev: false - - /whatwg-url/11.0.0: - resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} - engines: {node: '>=12'} - dependencies: - tr46: 3.0.0 - webidl-conversions: 7.0.0 - dev: false - /whatwg-url/5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: @@ -2224,19 +2173,6 @@ packages: optional: true dev: false - /ws/8.9.0: - resolution: {integrity: sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: false - /xmlbuilder/15.1.1: resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} engines: {node: '>=8.0'} @@ -2279,8 +2215,8 @@ packages: yargs-parser: 20.2.9 dev: true - /yargs/17.6.0: - resolution: {integrity: sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==} + /yargs/17.6.2: + resolution: {integrity: sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==} engines: {node: '>=12'} dependencies: cliui: 8.0.1 @@ -2320,11 +2256,11 @@ packages: version: 3.5.0 dependencies: cli-truncate: 2.1.0 - electron-dl: 3.3.1 + electron-dl: 3.5.0 electron-is-dev: 2.0.0 dev: false - github.com/pykeio/vibe/11984868ce9e007859ed91ff159c7f7f0a34e7ae_electron@20.3.1: + github.com/pykeio/vibe/11984868ce9e007859ed91ff159c7f7f0a34e7ae_electron@20.3.5: resolution: {tarball: https://codeload.github.com/pykeio/vibe/tar.gz/11984868ce9e007859ed91ff159c7f7f0a34e7ae} id: github.com/pykeio/vibe/11984868ce9e007859ed91ff159c7f7f0a34e7ae name: '@pyke/vibe' @@ -2333,6 +2269,6 @@ packages: peerDependencies: electron: '>=11.0' dependencies: - cargo-cp-artifact: 0.1.6 - electron: 20.3.1 + cargo-cp-artifact: 0.1.7 + electron: 20.3.5 dev: false diff --git a/src/arrpc/README.md b/src/arrpc/README.md index 3106e64..4eb5b8b 100644 --- a/src/arrpc/README.md +++ b/src/arrpc/README.md @@ -2,11 +2,21 @@ arRPC is an open source implementation of Discord's half-documented local RPC servers for their desktop client. This open source implementation purely in NodeJS allows it to be used in many places where it is otherwise impossible to do: Discord web and alternative clients like Armcord/etc. It opens a simple bridge WebSocket server which messages the JSON of exactly what to dispatch with in the client with no extra processing needed, allowing small and simple mods or plugins. **It is currently in alpha and is very WIP, expect bugs, etc.** +
+ +Rich Presence (RPC) is the name for how some apps can talk to Discord desktop on your PC via localhost servers to display detailed info about the app's state. This usually works via parts of Discord desktop natively doing things + parts of Discord web interpreting that and setting it as your status. arRPC is an open source implementation of the local RPC servers on your PC, allowing apps to talk to it thinking it was just normal Discord. It can then send that info to apps which usually don't get RPC, like Discord Web, Armcord, etc. which can then set that as your status. This would otherwise not be possible, as web apps/browsers/etc can't just use Discord's already existing code and version. + +- App with Discord RPC +- ~~Discord Desktop's native server~~ arRPC +- ~~Discord Web's setting~~ mod/plugin + +
+ ### How to try 1. Clone repo 2. Run server with `node src` (use new Node) -3. Open Discord in browser with CSP disabled (using an extension) +3. Open Discord in browser 4. Run content of [`simple_mod.js`](simple_mod.js) in console 5. Use an app/thing with RPC 6. Hope it works, if not report bugs :) @@ -24,21 +34,5 @@ arRPC is an open source implementation of Discord's half-documented local RPC se ### Commands - [x] DISPATCH -- [ ] AUTHORIZE -- [ ] AUTHENTICATE -- [ ] GET_GUILD -- [ ] GET_GUILDS -- [ ] GET_CHANNEL -- [ ] GET_CHANNELS -- [ ] SUBSCRIBE -- [ ] UNSUBSCRIBE -- [ ] SET_USER_VOICE_SETTINGS -- [ ] SELECT_VOICE_CHANNEL -- [ ] GET_SELECTED_VOICE_CHANNEL -- [ ] SELECT_TEXT_CHANNEL -- [ ] GET_VOICE_SETTINGS -- [ ] SET_VOICE_SETTINGS -- [ ] SET_CERTIFIED_DEVICES - [x] SET_ACTIVITY -- [ ] SEND_ACTIVITY_JOIN_INVITE -- [ ] CLOSE_ACTIVITY_REQUEST +- [x] INVITE_BROWSER diff --git a/src/arrpc/package.json b/src/arrpc/package.json index ca74963..1de36e1 100644 --- a/src/arrpc/package.json +++ b/src/arrpc/package.json @@ -1,6 +1,6 @@ { "name": "arrpc", - "version": "0.1.0", + "version": "1.1.0", "description": "Open Discord RPC server for atypical setups", "main": "src/index.js", "scripts": { diff --git a/src/arrpc/simple_mod.js b/src/arrpc/simple_mod.js index 5b4bc8b..81bcbe7 100644 --- a/src/arrpc/simple_mod.js +++ b/src/arrpc/simple_mod.js @@ -1,31 +1,24 @@ -const dispatch = (() => { - let Dispatcher; +let Dispatcher; - return function (event) { - Dispatcher ??= window.Vencord?.Webpack.Common.FluxDispatcher; - if (!Dispatcher) { - const cache = webpackChunkdiscord_app.push([[Symbol()], {}, (w) => w]).c; - webpackChunkdiscord_app.pop(); - - outer: for (const id in cache) { - const mod = cache[id].exports; - for (const exp in mod) { - if (mod[exp]?.isDispatching) { - Dispatcher = mod[exp]; - break outer; - } - } - } - } - if (!Dispatcher) return; // failed to find, your choice if and how u wanna handle this - - return Dispatcher.dispatch(event); - }; -})(); -const ws = new WebSocket("ws://localhost:1337"); // connect to arRPC bridge +const ws = new WebSocket("ws://127.0.0.1:1337"); // connect to arRPC bridge ws.onmessage = (x) => { msg = JSON.parse(x.data); console.log(msg); - dispatch({type: "LOCAL_ACTIVITY_UPDATE", ...msg}); // set RPC status + if (!Dispatcher) { + const cache = window.webpackChunkdiscord_app.push([[Symbol()], {}, (x) => x]).c; + window.webpackChunkdiscord_app.pop(); + + for (const id in cache) { + let mod = cache[id].exports; + mod = mod && (mod.Z ?? mod.ZP); + + if (mod && mod.register && mod.wait) { + Dispatcher = mod; + break; + } + } + } + + Dispatcher.dispatch({type: "LOCAL_ACTIVITY_UPDATE", ...msg}); // set RPC status }; diff --git a/src/arrpc/src/bridge.js b/src/arrpc/src/bridge.js index 2e7d82f..d7cffd9 100644 --- a/src/arrpc/src/bridge.js +++ b/src/arrpc/src/bridge.js @@ -1,8 +1,22 @@ -const ws = require("ws"); +const rgb = (r, g, b, msg) => `\x1b[38;2;${r};${g};${b}m${msg}\x1b[0m`; +const log = (...args) => console.log(`[${rgb(88, 101, 242, "arRPC")} > ${rgb(87, 242, 135, "bridge")}]`, ...args); + +const {WebSocketServer} = require("ws"); +// basic bridge to pass info onto webapp const send = (msg) => { wss.clients.forEach((x) => x.send(JSON.stringify(msg))); }; -const wss = new ws.WebSocketServer({port: 1337}); +const port = 1337; +const wss = new WebSocketServer({port}); +wss.on("connection", (socket) => { + log("web connected"); + + socket.on("close", () => { + log("web disconnected"); + }); +}); + +wss.on("listening", () => log("listening on", port)); module.exports = {send}; diff --git a/src/arrpc/src/index.js b/src/arrpc/src/index.js index b8d34f8..3d31a83 100644 --- a/src/arrpc/src/index.js +++ b/src/arrpc/src/index.js @@ -1,11 +1,27 @@ -var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : {default: mod}; - }; -const server = require("./server.js"); -global.fetch = __importDefault(require("node-fetch")); -async function start() { - const x = await new server.RPCServer(); +const rgb = (r, g, b, msg) => `\x1b[38;2;${r};${g};${b}m${msg}\x1b[0m`; +const log = (...args) => console.log(`[${rgb(88, 101, 242, "arRPC")}]`, ...args); + +log("arRPC v1.1.0-beta ArmCord"); + +const Bridge = require("./bridge.js"); +const {RPCServer} = require("./server.js"); +const fetch = require("cross-fetch"); +const {mainWindow} = require("../../../ts-out/window.js"); +const {sleep} = require("../../../ts-out/utils.js"); + +async function run() { + const server = await new RPCServer(); + server.on("activity", (data) => mainWindow.webContents.send("rpc", data)); + server.on("invite", (code) => { + console.log(code); + const {createInviteWindow, inviteWindow} = require("../../../ts-out/window.js"); + const {exportPort} = require("./transports/websocket.js"); + createInviteWindow(); + const win = inviteWindow; + //doesnt work + win.loadURL("https://discord.com/invite/" + code); + win.show(); + }); } -start(); +run(); +//server.on('activity', data => Bridge.send(data)); diff --git a/src/arrpc/src/server.js b/src/arrpc/src/server.js index 4a4a782..75137b8 100644 --- a/src/arrpc/src/server.js +++ b/src/arrpc/src/server.js @@ -1,22 +1,25 @@ const rgb = (r, g, b, msg) => `\x1b[38;2;${r};${g};${b}m${msg}\x1b[0m`; const log = (...args) => console.log(`[${rgb(88, 101, 242, "arRPC")} > ${rgb(87, 242, 135, "bridge")}]`, ...args); -const IPCServer = require("./transports/ipc.js"); - -const WSServer = require("./transports/websocket.js"); +const {IPCServer} = require("./transports/ipc.js"); +const {EventEmitter} = require("events"); +const {WSServer} = require("./transports/websocket.js"); const Bridge = require("./bridge.js"); - +const fetch = require("cross-fetch"); const lookupAsset = (name, assets) => { return assets.find((x) => x.name === name)?.id; }; -class RPCServer { +class RPCServer extends EventEmitter { constructor() { + super(); return (async () => { this.onConnection = this.onConnection.bind(this); this.onMessage = this.onMessage.bind(this); - this.ipc = await new IPCServer.IPCServer(this.onMessage, this.onConnection); - this.ws = await new WSServer.WSServer(this.onMessage, this.onConnection); + this.ipc = await new IPCServer(this.onMessage, this.onConnection); + this.ws = await new WSServer(this.onMessage, this.onConnection); + + return this; })(); } @@ -24,14 +27,22 @@ class RPCServer { socket.send({ cmd: "DISPATCH", evt: "READY", - data: {} + + data: { + v: 1 + } }); + + this.emit("connection", socket); } - async onMessage(socket, {cmd, args}) { + async onMessage(socket, {cmd, args, nonce}) { + this.emit("message", {socket, cmd, args, nonce}); + switch (cmd) { case "SET_ACTIVITY": if (!socket.application) { + // fetch info about application socket.application = await ( await fetch(`https://discord.com/api/v9/oauth2/applications/${socket.clientId}/rpc`) ).json(); @@ -41,28 +52,31 @@ class RPCServer { log("fetched app info for", socket.clientId, socket.application); } - const {activity, pid} = args; + const {activity, pid} = args; // translate given parameters into what discord dispatch expects const {buttons, timestamps, instance} = activity; const metadata = {}; const extra = {}; if (buttons) { + // map buttons into expected metadata metadata.button_urls = buttons.map((x) => x.url); extra.buttons = buttons.map((x) => x.label); } if (timestamps) for (const x in timestamps) { + // translate s -> ms timestamps if (Date.now().toString().length - timestamps[x].toString().length > 2) - timestamps[x] = Math.floor(1e3 * timestamps[x]); + timestamps[x] = Math.floor(1000 * timestamps[x]); } + // lookup assets to ids if (activity.assets?.large_image) activity.assets.large_image = lookupAsset(activity.assets.large_image, socket.application.assets); if (activity.assets?.small_image) activity.assets.small_image = lookupAsset(activity.assets.small_image, socket.application.assets); - Bridge.send({ + this.emit("activity", { activity: { name: socket.application.name, application_id: socket.application.id, @@ -76,6 +90,19 @@ class RPCServer { }); break; + + case "INVITE_BROWSER": + const {code} = args; + socket.send({ + cmd, + data: { + code + }, + nonce + }); + + this.emit("invite", code); + break; } } } diff --git a/src/arrpc/src/transports/ipc.js b/src/arrpc/src/transports/ipc.js index 3ef8fb4..dba70d9 100644 --- a/src/arrpc/src/transports/ipc.js +++ b/src/arrpc/src/transports/ipc.js @@ -1,16 +1,20 @@ const rgb = (r, g, b, msg) => `\x1b[38;2;${r};${g};${b}m${msg}\x1b[0m`; const log = (...args) => console.log(`[${rgb(88, 101, 242, "arRPC")} > ${rgb(254, 231, 92, "ipc")}]`, ...args); -const path = require("path"); + +const {join} = require("path"); const {platform, env} = require("process"); const {unlinkSync} = require("fs"); + const {createServer, createConnection} = require("net"); const SOCKET_PATH = platform === "win32" ? "\\\\?\\pipe\\discord-ipc" - : path.join(env.XDG_RUNTIME_DIR || env.TMPDIR || env.TMP || env.TEMP || "/tmp", "discord-ipc"); + : join(env.XDG_RUNTIME_DIR || env.TMPDIR || env.TMP || env.TEMP || "/tmp", "discord-ipc"); +// enums for various constants const Types = { + // types of packets HANDSHAKE: 0, FRAME: 1, CLOSE: 2, @@ -19,12 +23,14 @@ const Types = { }; const CloseCodes = { + // codes for closures CLOSE_NORMAL: 1000, CLOSE_UNSUPPORTED: 1003, CLOSE_ABNORMAL: 1006 }; const ErrorCodes = { + // codes for errors INVALID_CLIENTID: 4000, INVALID_ORIGIN: 4001, RATELIMITED: 4002, @@ -120,7 +126,7 @@ const socketIsAvailable = async (socket) => { }; const possibleOutcomes = Promise.race([ - new Promise((res) => socket.on("error", res)), // errore + new Promise((res) => socket.on("error", res)), // errored new Promise((res, rej) => socket.on("pong", () => rej("socket ponged"))), // ponged new Promise((res, rej) => setTimeout(() => rej("timed out"), 1000)) // timed out ]).then( @@ -209,6 +215,7 @@ class IPCServer { const ver = params.v ?? 1; const clientId = params.client_id ?? ""; + // encoding is always json for ipc if (ver !== 1) { log("unsupported version requested", ver); @@ -251,5 +258,4 @@ class IPCServer { this.messageHandler(socket, msg); } } - module.exports = {IPCServer}; diff --git a/src/arrpc/src/transports/websocket.js b/src/arrpc/src/transports/websocket.js index 4de9402..cd9c50c 100644 --- a/src/arrpc/src/transports/websocket.js +++ b/src/arrpc/src/transports/websocket.js @@ -1,14 +1,15 @@ const rgb = (r, g, b, msg) => `\x1b[38;2;${r};${g};${b}m${msg}\x1b[0m`; const log = (...args) => console.log(`[${rgb(88, 101, 242, "arRPC")} > ${rgb(235, 69, 158, "websocket")}]`, ...args); -const ws = require("ws"); -const {createServer} = require("http"); -const querystring = require("querystring"); -const portRange = [6463, 6472]; +const {WebSocketServer} = require("ws"); +const {createServer} = require("http"); +const {parse} = require("querystring"); +var exportPort = 0000; +const portRange = [6463, 6472]; // ports available/possible: 6463-6472 class WSServer { constructor(messageHandler, connectionHandler) { - return new Promise(async (res) => { + return (async () => { this.messageHandler = messageHandler; this.connectionHandler = connectionHandler; @@ -19,54 +20,60 @@ class WSServer { let http, wss; while (port <= portRange[1]) { - try { - log("trying port", port); + log("trying port", port); - http = createServer(); - http.on("error", (e) => { - log("http error", e); + if ( + await new Promise((res) => { + http = createServer(); + http.on("error", (e) => { + // log('http error', e); - if (e.code === "EADDRINUSE") { - log(port, "in use!"); - } - }); + if (e.code === "EADDRINUSE") { + log(port, "in use!"); + res(false); + } + }); - wss = new ws.WebSocketServer({server: http}); - wss.on("error", (e) => { - log("wss error", e); - }); + wss = new WebSocketServer({server: http}); + wss.on("error", (e) => { + // log('wss error', e); + }); - wss.on("connection", this.onConnection); + wss.on("connection", this.onConnection); - http.listen(port, "127.0.0.1", () => { - log("listening on", port); + http.listen(port, "127.0.0.1", () => { + log("listening on", port); + exportPort = port; + this.http = http; + this.wss = wss; - this.http = http; - this.wss = wss; - - res(this); - }); - } catch (e) { - log("failed to start", e); - } - - break; + res(true); + }); + }) + ) + break; + port++; } - }); + + return this; + })(); } onConnection(socket, req) { - const params = querystring.parse(req.url.split("?")[1]); + const params = parse(req.url.split("?")[1]); const ver = parseInt(params.v ?? 1); - const encoding = params.encoding ?? "json"; + const encoding = params.encoding ?? "json"; // json | etf (erlpack) const clientId = params.client_id ?? ""; const origin = req.headers.origin ?? ""; log(`new connection! origin:`, origin, JSON.parse(JSON.stringify(params))); - if (origin !== "") { - log("origin is defined, denying", origin); + if ( + origin !== "" && + !["https://discord.com", "https://ptb.discord.com", "https://canary.discord.com/"].includes(origin) + ) { + log("disallowed origin", origin); socket.close(); return; @@ -86,12 +93,12 @@ class WSServer { return; } - if (clientId === "") { - log("client id required"); + /* if (clientId === '') { + log('client id required'); - socket.close(); - return; - } + socket.close(); + return; + } */ socket.clientId = clientId; socket.encoding = encoding; @@ -120,5 +127,4 @@ class WSServer { this.messageHandler(socket, JSON.parse(msg)); } } - -module.exports = {WSServer}; +module.exports = {WSServer, exportPort}; diff --git a/src/preload/bridge.ts b/src/preload/bridge.ts index 1b9f32c..61cae4e 100644 --- a/src/preload/bridge.ts +++ b/src/preload/bridge.ts @@ -28,6 +28,13 @@ contextBridge.exposeInMainWorld("armcord", { splashEnd: () => ipcRenderer.send("splashEnd"), openSettingsWindow: () => ipcRenderer.send("openSettingsWindow") }); +let windowCallback: (arg0: object) => void; +contextBridge.exposeInMainWorld("ArmCordRPC", { + listen: (callback: any) => (windowCallback = callback) +}); +ipcRenderer.on("rpc", (event, data: object) => { + windowCallback(data); +}); //to be only used inside armcord internal setup/splash etc if (window.location.href.indexOf("splash.html") > -1 || window.location.href.indexOf("setup.html") > -1) { contextBridge.exposeInMainWorld("armcordinternal", { diff --git a/src/preload/preload.ts b/src/preload/preload.ts index 7536f3c..8aba86a 100644 --- a/src/preload/preload.ts +++ b/src/preload/preload.ts @@ -69,13 +69,10 @@ if (window.location.href.indexOf("splash.html") > -1) { return Dispatcher.dispatch(event); }; })(); - const ws = new WebSocket('ws://localhost:1337'); // connect to arRPC bridge - ws.onmessage = x => { - msg = JSON.parse(x.data); - console.log(msg); - - dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...msg }); // set RPC status - }; + ArmCordRPC.listen((data) => { + console.log(data) + dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...data }); + }) `); const cssPath = path.join(__dirname, "../", "/content/css/discord.css"); addStyle(fs.readFileSync(cssPath, "utf8")); diff --git a/src/settings/settings.html b/src/settings/settings.html index 7e6fd4a..6f117a1 100644 --- a/src/settings/settings.html +++ b/src/settings/settings.html @@ -66,7 +66,8 @@

- Uses arRPC to support Discord RPC (Rich Presence) with local programs on your machine. Work in progress. + Uses arRPC to support Discord RPC (Rich + Presence) with local programs on your machine. Work in progress.


diff --git a/src/utils.ts b/src/utils.ts index 07b2bd6..33aaebb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import {app, dialog, session} from "electron"; import path from "path"; -import fetch from "node-fetch" +import fetch from "cross-fetch" import extract from "extract-zip" import util from "util" const streamPipeline = util.promisify(require('stream').pipeline) From 17c05fedd3e74bba9113f17dbc980bcedfa4fd00 Mon Sep 17 00:00:00 2001 From: smartfridge <37928912+smartfrigde@users.noreply.github.com> Date: Sun, 20 Nov 2022 19:25:34 +0100 Subject: [PATCH 10/18] Fix dev tools --- src/menu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu.ts b/src/menu.ts index 7cac9d6..bbcb446 100644 --- a/src/menu.ts +++ b/src/menu.ts @@ -49,7 +49,7 @@ export async function setMenu() { label: "Developer tools", accelerator: "CmdOrCtrl+Shift+I", click: function () { - mainWindow.webContents.toggleDevTools(); + BrowserWindow.getFocusedWindow().toggleDevTools() } }, { From fbeffdb166755a5cfa2395d89f498a0eaa1944a9 Mon Sep 17 00:00:00 2001 From: Ven Date: Sun, 20 Nov 2022 20:46:37 +0100 Subject: [PATCH 11/18] Add missing BrowserWindow import; bump deps (#258) --- package.json | 12 ++++++------ pnpm-lock.yaml | 50 +++++++++++++++++++++++++------------------------- src/menu.ts | 4 ++-- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index f212c88..2882ec5 100644 --- a/package.json +++ b/package.json @@ -27,24 +27,24 @@ }, "homepage": "https://github.com/armcord/armcord#readme", "devDependencies": { - "@types/node": "^17.0.45", + "@types/node": "^18.11.9", "@types/ws": "^8.5.3", "chalk-cli": "^5.0.0", "copyfiles": "^2.4.1", - "electron": "^20.1.0", + "electron": "^21.3.0", "electron-builder": "^23.6.0", - "husky": "^8.0.1", - "prettier": "^2.7.0", + "husky": "^8.0.2", + "prettier": "^2.7.1", "typescript": "^4.9.3" }, "dependencies": { "@pyke/vibe": "github:pykeio/vibe#11984868ce9e007859ed91ff159c7f7f0a34e7ae", - "arrpc": "file:./src/arrpc", + "arrpc": "file:src/arrpc", "cross-fetch": "^3.1.5", "electron-context-menu": "github:ArmCord/electron-context-menu", "extract-zip": "^2.0.1", "v8-compile-cache": "^2.3.0", - "ws": "^8.8.0" + "ws": "^8.11.0" }, "build": { "nsis": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b761d9b..5fb39fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,24 +2,24 @@ lockfileVersion: 5.4 specifiers: '@pyke/vibe': github:pykeio/vibe#11984868ce9e007859ed91ff159c7f7f0a34e7ae - '@types/node': ^17.0.45 + '@types/node': ^18.11.9 '@types/ws': ^8.5.3 - arrpc: file:./src/arrpc + arrpc: file:src/arrpc chalk-cli: ^5.0.0 copyfiles: ^2.4.1 cross-fetch: ^3.1.5 - electron: ^20.1.0 + electron: ^21.3.0 electron-builder: ^23.6.0 electron-context-menu: github:ArmCord/electron-context-menu extract-zip: ^2.0.1 - husky: ^8.0.1 - prettier: ^2.7.0 + husky: ^8.0.2 + prettier: ^2.7.1 typescript: ^4.9.3 v8-compile-cache: ^2.3.0 - ws: ^8.8.0 + ws: ^8.11.0 dependencies: - '@pyke/vibe': github.com/pykeio/vibe/11984868ce9e007859ed91ff159c7f7f0a34e7ae_electron@20.3.5 + '@pyke/vibe': github.com/pykeio/vibe/11984868ce9e007859ed91ff159c7f7f0a34e7ae_electron@21.3.0 arrpc: file:src/arrpc cross-fetch: 3.1.5 electron-context-menu: github.com/ArmCord/electron-context-menu/280c81398c02a063f46e3285a9708d8db1a7ce32 @@ -28,11 +28,11 @@ dependencies: ws: 8.11.0 devDependencies: - '@types/node': 17.0.45 + '@types/node': 18.11.9 '@types/ws': 8.5.3 chalk-cli: 5.0.0 copyfiles: 2.4.1 - electron: 20.3.5 + electron: 21.3.0 electron-builder: 23.6.0 husky: 8.0.2 prettier: 2.7.1 @@ -148,7 +148,7 @@ packages: /@types/fs-extra/9.0.13: resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} dependencies: - '@types/node': 17.0.45 + '@types/node': 18.11.9 dev: true /@types/glob/7.2.0: @@ -156,14 +156,14 @@ packages: requiresBuild: true dependencies: '@types/minimatch': 5.1.2 - '@types/node': 17.0.45 + '@types/node': 18.11.9 dev: true optional: true /@types/keyv/3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 17.0.45 + '@types/node': 18.11.9 /@types/minimatch/5.1.2: resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} @@ -181,8 +181,8 @@ packages: /@types/node/16.18.3: resolution: {integrity: sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg==} - /@types/node/17.0.45: - resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} + /@types/node/18.11.9: + resolution: {integrity: sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==} /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -192,7 +192,7 @@ packages: resolution: {integrity: sha512-ULqvZNGMv0zRFvqn8/4LSPtnmN4MfhlPNtJCTpKuIIxGVGZ2rYWzFXrvEBoh9CVyqSE7D6YFRJ1hydLHI6kbWw==} requiresBuild: true dependencies: - '@types/node': 17.0.45 + '@types/node': 18.11.9 xmlbuilder: 15.1.1 dev: true optional: true @@ -200,7 +200,7 @@ packages: /@types/responselike/1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 17.0.45 + '@types/node': 18.11.9 /@types/verror/1.10.6: resolution: {integrity: sha512-NNm+gdePAX1VGvPcGZCDKQZKYSiAWigKhKaz5KF94hG6f2s8de9Ow5+7AbXoeKxL8gavZfk4UquSAygOF2duEQ==} @@ -211,7 +211,7 @@ packages: /@types/ws/8.5.3: resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==} dependencies: - '@types/node': 17.0.45 + '@types/node': 18.11.9 dev: true /@types/yargs-parser/21.0.0: @@ -228,7 +228,7 @@ packages: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true dependencies: - '@types/node': 17.0.45 + '@types/node': 18.11.9 optional: true /agent-base/6.0.2: @@ -599,7 +599,7 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} /colors/1.0.3: - resolution: {integrity: sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==} + resolution: {integrity: sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=} engines: {node: '>=0.1.90'} dev: true @@ -628,7 +628,7 @@ packages: dev: true /concat-map/0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} dev: true /config-chain/1.1.13: @@ -885,8 +885,8 @@ packages: - supports-color dev: true - /electron/20.3.5: - resolution: {integrity: sha512-xTBjdgAZXf6txxfIhv9mZ3yloJZ+KTht7D2X10uHlFnQu4ZmvzqwhGuQPnldVKhRUDvZehIjulmmrFO6Mz6SzQ==} + /electron/21.3.0: + resolution: {integrity: sha512-MGRpshN8fBcx4IRuBABIsGDv0tB/MclIFsyFHFFXsBCUc+vIXaE/E6vuWaniGIFSz5WyeuapfTH5IeRb+7yIfw==} engines: {node: '>= 10.17.0'} hasBin: true requiresBuild: true @@ -2242,7 +2242,7 @@ packages: file:src/arrpc: resolution: {directory: src/arrpc, type: directory} name: arrpc - version: 0.1.0 + version: 1.1.0 dependencies: ws: 8.11.0 transitivePeerDependencies: @@ -2260,7 +2260,7 @@ packages: electron-is-dev: 2.0.0 dev: false - github.com/pykeio/vibe/11984868ce9e007859ed91ff159c7f7f0a34e7ae_electron@20.3.5: + github.com/pykeio/vibe/11984868ce9e007859ed91ff159c7f7f0a34e7ae_electron@21.3.0: resolution: {tarball: https://codeload.github.com/pykeio/vibe/tar.gz/11984868ce9e007859ed91ff159c7f7f0a34e7ae} id: github.com/pykeio/vibe/11984868ce9e007859ed91ff159c7f7f0a34e7ae name: '@pyke/vibe' @@ -2270,5 +2270,5 @@ packages: electron: '>=11.0' dependencies: cargo-cp-artifact: 0.1.7 - electron: 20.3.5 + electron: 21.3.0 dev: false diff --git a/src/menu.ts b/src/menu.ts index bbcb446..6ccae58 100644 --- a/src/menu.ts +++ b/src/menu.ts @@ -1,4 +1,4 @@ -import {Menu, app, clipboard, globalShortcut} from "electron"; +import {BrowserWindow, Menu, app, clipboard, globalShortcut} from "electron"; import {mainWindow} from "./window"; import {getConfig} from "./utils"; import {createSettingsWindow} from "./settings/main"; @@ -49,7 +49,7 @@ export async function setMenu() { label: "Developer tools", accelerator: "CmdOrCtrl+Shift+I", click: function () { - BrowserWindow.getFocusedWindow().toggleDevTools() + BrowserWindow.getFocusedWindow()!.webContents.toggleDevTools(); } }, { From bea251634c80b0782ab9786bd267a9dd2a7e34e1 Mon Sep 17 00:00:00 2001 From: Ven Date: Sun, 20 Nov 2022 21:05:40 +0100 Subject: [PATCH 12/18] yeet husky (#259) --- {.husky => .hooks}/pre-commit | 2 +- .husky/.gitignore | 1 - package.json | 5 +---- pnpm-lock.yaml | 8 -------- 4 files changed, 2 insertions(+), 14 deletions(-) rename {.husky => .hooks}/pre-commit (56%) delete mode 100644 .husky/.gitignore diff --git a/.husky/pre-commit b/.hooks/pre-commit similarity index 56% rename from .husky/pre-commit rename to .hooks/pre-commit index a31ede1..659139f 100755 --- a/.husky/pre-commit +++ b/.hooks/pre-commit @@ -1,5 +1,5 @@ #!/bin/sh -. "$(dirname $0)/_/husky.sh" +set -e npm run format git add -A diff --git a/.husky/.gitignore b/.husky/.gitignore deleted file mode 100644 index 31354ec..0000000 --- a/.husky/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_ diff --git a/package.json b/package.json index 2882ec5..f5f30f0 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,7 @@ "packageQuick": "npm run build && electron-builder --dir", "format": "prettier --write src/**/*", "CIbuild": "npm run build && electron-builder --linux zip && electron-builder --windows zip && electron-builder --macos zip", - "postinstall": "husky install", - "precommit-fix": "husky uninstall && echo - && echo !Make sure to run 'npm run format' before commiting! | chalk --stdin yellow inverse && echo -", - "precommit-fix:format": "husky uninstall && npm run format" + "prepare": "git config --local core.hooksPath .hooks/" }, "repository": { "type": "git", @@ -33,7 +31,6 @@ "copyfiles": "^2.4.1", "electron": "^21.3.0", "electron-builder": "^23.6.0", - "husky": "^8.0.2", "prettier": "^2.7.1", "typescript": "^4.9.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5fb39fd..3222429 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,6 @@ specifiers: electron-builder: ^23.6.0 electron-context-menu: github:ArmCord/electron-context-menu extract-zip: ^2.0.1 - husky: ^8.0.2 prettier: ^2.7.1 typescript: ^4.9.3 v8-compile-cache: ^2.3.0 @@ -34,7 +33,6 @@ devDependencies: copyfiles: 2.4.1 electron: 21.3.0 electron-builder: 23.6.0 - husky: 8.0.2 prettier: 2.7.1 typescript: 4.9.3 @@ -1218,12 +1216,6 @@ packages: - supports-color dev: true - /husky/8.0.2: - resolution: {integrity: sha512-Tkv80jtvbnkK3mYWxPZePGFpQ/tT3HNSs/sasF9P2YfkMezDl3ON37YN6jUUI4eTg5LcyVynlb6r4eyvOmspvg==} - engines: {node: '>=14'} - hasBin: true - dev: true - /iconv-corefoundation/1.1.7: resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==} engines: {node: ^8.11.2 || >=10} From c5ce9109400f8a2effc4763899c42c16389156b8 Mon Sep 17 00:00:00 2001 From: Ven Date: Mon, 21 Nov 2022 06:50:09 +0100 Subject: [PATCH 13/18] Fix csp (Vencord QuickCss & dynamic imports) (#260) --- src/extensions/mods.ts | 51 ++++++++++++------------------------------ 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/src/extensions/mods.ts b/src/extensions/mods.ts index ad3cae2..d3a6748 100644 --- a/src/extensions/mods.ts +++ b/src/extensions/mods.ts @@ -1,52 +1,29 @@ -//https://github.com/GooseMod/GooseMod/wiki/Stuck-Updater-or-Blank-Window-Fix -/* -Copyright 2022 GooseMod - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ import electron from "electron"; import {getConfig} from "../utils"; -const otherMods = { - generic: { - electronProxy: require("util").types.isProxy(electron) // Many modern mods overwrite electron with a proxy with a custom BrowserWindow (copied from PowerCord) - } -}; -const unstrictCSP = () => { +const unstrictCSP = async () => { console.log("Setting up CSP unstricter..."); - const cspAllowAll = ["connect-src", "style-src", "img-src", "font-src"]; + const cspAllowAll = ["style-src", "connect-src", "img-src", "font-src", "media-src"]; - const corsAllowUrls = [ - "https://raw.githubusercontent.com/Cordwood/builds/master/index.js", - "https://github.com/Vendicated/Vencord/releases/download/devbuild/browser.js", - "https://cors.armcord.xyz/raw.githubusercontent.com/uwu/shelter-builds/main/shelter.js" - ]; - - electron.session.defaultSession.webRequest.onHeadersReceived(({responseHeaders, url}, done) => { + const isVencord = await getConfig("mods").then((s) => s.includes("vencord")); + electron.session.defaultSession.webRequest.onHeadersReceived(({responseHeaders}, done) => { let csp = responseHeaders!["content-security-policy"]; - if (otherMods.generic.electronProxy) { - // Since patch v16, override other mod's onHeadersRecieved (Electron only allows 1 listener); because they rely on 0 CSP at all (GM just unrestricts some areas), remove it fully if we detect other mods - delete responseHeaders!["content-security-policy"]; - csp = []; - } - if (csp) { - for (let p of cspAllowAll) { - csp[0] = csp[0].replace(`${p}`, `${p} * blob: data:`); // * does not include data: URIs + for (const directive of cspAllowAll) { + csp[0] = csp[0].replace(new RegExp(`${directive}.+?;`), `${directive} * blob: data: 'unsafe-inline';`); } + if (isVencord) { + // unpkg and cdnjs are used for QuickCss and some plugins' dependencies (e.g. GifEncoder & APNG for FakeNitro) + csp[0] = csp[0].replace( + /script-src.+?(?=;)/, + "$& 'unsafe-eval' https://unpkg.com https://cdnjs.cloudflare.com" + ); + } // Fix Discord's broken CSP which disallows unsafe-inline due to having a nonce (which they don't even use?) - csp[0] = csp[0].replace(/'nonce-.*?' /, ""); - } - - if (corsAllowUrls.some((x) => url.startsWith(x))) { - responseHeaders!["access-control-allow-origin"] = ["*"]; + csp[0] = csp[0].replace(/'nonce-.+?' /, ""); } done({responseHeaders}); From ed775cbf3104dabfd98753ba5730b61a997bb014 Mon Sep 17 00:00:00 2001 From: Ven Date: Mon, 21 Nov 2022 06:50:21 +0100 Subject: [PATCH 14/18] Fix npm format script (#261) --- .vscode/launch.json | 13 ++-- package.json | 144 ++++++++++++++++++++++---------------------- src/ipc.ts | 2 +- src/main.ts | 2 +- src/utils.ts | 85 +++++++++++++------------- src/window.ts | 10 +-- tsconfig.json | 2 +- 7 files changed, 126 insertions(+), 132 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 321b5ee..185ccfc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,14 +9,9 @@ "request": "launch", "name": "Debug ArmCord via NPM", "runtimeExecutable": "npm", - "runtimeArgs": [ - "start", - "debug", - ], + "runtimeArgs": ["start", "debug"], "port": 9229, - "skipFiles": [ - "/**" - ] - }, + "skipFiles": ["/**"] + } ] -} \ No newline at end of file +} diff --git a/package.json b/package.json index f5f30f0..cfb69d4 100644 --- a/package.json +++ b/package.json @@ -1,76 +1,76 @@ { - "name": "ArmCord", - "version": "3.1.0", - "description": "ArmCord is a custom client designed to enhance your Discord experience while keeping everything lightweight.", - "main": "ts-out/main.js", - "scripts": { - "build": "tsc && copyfiles -u 1 src/**/*.html src/**/**/*.css ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/**/** ts-out/", - "watch": "tsc -w", - "start": "npm run build && electron ./ts-out/main.js", - "startNoSandbox": "npm run build && electron ./ts-out/main.js --no-sandbox", - "package": "npm run build && electron-builder", - "packageQuick": "npm run build && electron-builder --dir", - "format": "prettier --write src/**/*", - "CIbuild": "npm run build && electron-builder --linux zip && electron-builder --windows zip && electron-builder --macos zip", - "prepare": "git config --local core.hooksPath .hooks/" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/armcord/armcord.git" - }, - "author": "smartfrigde", - "license": "OSL-3.0", - "bugs": { - "url": "https://github.com/armcord/armcord/issues" - }, - "homepage": "https://github.com/armcord/armcord#readme", - "devDependencies": { - "@types/node": "^18.11.9", - "@types/ws": "^8.5.3", - "chalk-cli": "^5.0.0", - "copyfiles": "^2.4.1", - "electron": "^21.3.0", - "electron-builder": "^23.6.0", - "prettier": "^2.7.1", - "typescript": "^4.9.3" - }, - "dependencies": { - "@pyke/vibe": "github:pykeio/vibe#11984868ce9e007859ed91ff159c7f7f0a34e7ae", - "arrpc": "file:src/arrpc", - "cross-fetch": "^3.1.5", - "electron-context-menu": "github:ArmCord/electron-context-menu", - "extract-zip": "^2.0.1", - "v8-compile-cache": "^2.3.0", - "ws": "^8.11.0" - }, - "build": { - "nsis": { - "include": "build/installer.nsh" + "name": "ArmCord", + "version": "3.1.0", + "description": "ArmCord is a custom client designed to enhance your Discord experience while keeping everything lightweight.", + "main": "ts-out/main.js", + "scripts": { + "build": "tsc && copyfiles -u 1 src/**/*.html src/**/**/*.css ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/**/** ts-out/", + "watch": "tsc -w", + "start": "npm run build && electron ./ts-out/main.js", + "startNoSandbox": "npm run build && electron ./ts-out/main.js --no-sandbox", + "package": "npm run build && electron-builder", + "packageQuick": "npm run build && electron-builder --dir", + "format": "prettier --write src *.json", + "CIbuild": "npm run build && electron-builder --linux zip && electron-builder --windows zip && electron-builder --macos zip", + "prepare": "git config --local core.hooksPath .hooks/" }, - "files": [ - "!*", - "assets", - "node_modules", - "ts-out", - "package.json", - "LICENSE" - ], - "appId": "com.smartfridge.armcord", - "productName": "ArmCord", - "mac": { - "category": "Network" + "repository": { + "type": "git", + "url": "git+https://github.com/armcord/armcord.git" }, - "linux": { - "icon": "build/icon.icns", - "category": "Network", - "maintainer": "smartfridge@vivaldi.net", - "target": [ - "deb", - "tar.gz", - "rpm", - "AppImage" - ] - } - }, - "packageManager": "pnpm@7.13.4" + "author": "smartfrigde", + "license": "OSL-3.0", + "bugs": { + "url": "https://github.com/armcord/armcord/issues" + }, + "homepage": "https://github.com/armcord/armcord#readme", + "devDependencies": { + "@types/node": "^18.11.9", + "@types/ws": "^8.5.3", + "chalk-cli": "^5.0.0", + "copyfiles": "^2.4.1", + "electron": "^21.3.0", + "electron-builder": "^23.6.0", + "prettier": "^2.7.1", + "typescript": "^4.9.3" + }, + "dependencies": { + "@pyke/vibe": "github:pykeio/vibe#11984868ce9e007859ed91ff159c7f7f0a34e7ae", + "arrpc": "file:src/arrpc", + "cross-fetch": "^3.1.5", + "electron-context-menu": "github:ArmCord/electron-context-menu", + "extract-zip": "^2.0.1", + "v8-compile-cache": "^2.3.0", + "ws": "^8.11.0" + }, + "build": { + "nsis": { + "include": "build/installer.nsh" + }, + "files": [ + "!*", + "assets", + "node_modules", + "ts-out", + "package.json", + "LICENSE" + ], + "appId": "com.smartfridge.armcord", + "productName": "ArmCord", + "mac": { + "category": "Network" + }, + "linux": { + "icon": "build/icon.icns", + "category": "Network", + "maintainer": "smartfridge@vivaldi.net", + "target": [ + "deb", + "tar.gz", + "rpm", + "AppImage" + ] + } + }, + "packageManager": "pnpm@7.13.4" } diff --git a/src/ipc.ts b/src/ipc.ts index 1dd9080..536f950 100644 --- a/src/ipc.ts +++ b/src/ipc.ts @@ -16,7 +16,7 @@ import { import {customTitlebar} from "./main"; import {createSettingsWindow} from "./settings/main"; import os from "os"; -import fs from "fs" +import fs from "fs"; import path from "path"; export function registerIpc() { ipcMain.on("get-app-path", (event, arg) => { diff --git a/src/main.ts b/src/main.ts index 6bcd596..db45532 100644 --- a/src/main.ts +++ b/src/main.ts @@ -52,7 +52,7 @@ app.whenReady().then(async () => { } } await init(); - await installModLoader() + await installModLoader(); session.fromPartition("some-partition").setPermissionRequestHandler((webContents, permission, callback) => { if (permission === "notifications") { // Approves the permissions request diff --git a/src/utils.ts b/src/utils.ts index 33aaebb..73203bb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,10 +1,10 @@ import * as fs from "fs"; import {app, dialog, session} from "electron"; import path from "path"; -import fetch from "cross-fetch" -import extract from "extract-zip" -import util from "util" -const streamPipeline = util.promisify(require('stream').pipeline) +import fetch from "cross-fetch"; +import extract from "extract-zip"; +import util from "util"; +const streamPipeline = util.promisify(require("stream").pipeline); export var firstRun: boolean; export var contentPath: string; export var transparency: boolean; @@ -77,7 +77,6 @@ export function getDisplayVersion() { } } export async function injectJS(inject: string) { - const js = await (await fetch(`${inject}`)).text(); const el = document.createElement("script"); @@ -125,7 +124,7 @@ export async function injectElectronFlags() { console.log("No performance modes set"); } if ((await getConfig("windowStyle")) == "transparent" && process.platform === "win32") { - import("@pyke/vibe").then(vibe => { + import("@pyke/vibe").then((vibe) => { console.log("Transparent mode enabled"); vibe.setup(app); transparency = true; @@ -287,65 +286,65 @@ export async function checkIfConfigExists() { // Mods async function updateModBundle() { try { - console.log("Downloading mod bundle") - const distFolder = app.getPath("userData") + "/plugins/loader/dist/"; - while (!fs.existsSync(distFolder)){ - //waiting + console.log("Downloading mod bundle"); + const distFolder = app.getPath("userData") + "/plugins/loader/dist/"; + while (!fs.existsSync(distFolder)) { + //waiting + } + var name: string = await getConfig("mods"); + const clientMods = { + vencord: "https://github.com/Vendicated/Vencord/releases/download/devbuild/browser.js", + cordwood: "https://raw.githubusercontent.com/Cordwood/builds/master/index.js", + shelter: "https://raw.githubusercontent.com/uwu/shelter-builds/main/shelter.js" + }; + var bundle: string = await (await fetch(clientMods[name as keyof typeof clientMods])).text(); + fs.writeFileSync(distFolder + "bundle.js", bundle, "utf-8"); + } catch (e) { + console.log("[Mod loader] Failed to install mods"); + console.error(e); + dialog.showErrorBox( + "Oops, something went wrong.", + "ArmCord couldn't install mods, please check if you have stable internet connection and restart the app. If this issue persists, report it on the support server/Github issues." + ); } - var name: string = await getConfig("mods") - const clientMods = { - vencord: "https://github.com/Vendicated/Vencord/releases/download/devbuild/browser.js", - cordwood: "https://raw.githubusercontent.com/Cordwood/builds/master/index.js", - shelter: "https://raw.githubusercontent.com/uwu/shelter-builds/main/shelter.js" - }; - var bundle: string = await (await fetch(clientMods[name as keyof typeof clientMods])).text() - fs.writeFileSync(distFolder + "bundle.js", bundle, "utf-8"); -} catch (e) { - console.log("[Mod loader] Failed to install mods") - console.error(e) - dialog.showErrorBox( - "Oops, something went wrong.", - "ArmCord couldn't install mods, please check if you have stable internet connection and restart the app. If this issue persists, report it on the support server/Github issues." - ); -} } export var modInstallState: string; export async function installModLoader() { - if (await getConfig("mods") == "none") { - modInstallState = "none" + if ((await getConfig("mods")) == "none") { + modInstallState = "none"; import("./extensions/plugin"); - console.log("[Mod loader] Skipping") + console.log("[Mod loader] Skipping"); } else { const pluginFolder = app.getPath("userData") + "/plugins/"; if (!fs.existsSync(pluginFolder + "loader")) { try { - modInstallState = "installing" + modInstallState = "installing"; var zipPath = app.getPath("temp") + "/" + "loader.zip"; if (!fs.existsSync(pluginFolder)) { fs.mkdirSync(pluginFolder); console.log("[Mod loader] Created missing plugin folder"); } - var loaderZip = await fetch("https://armcord.xyz/loader.zip") - if (!loaderZip.ok) throw new Error(`unexpected response ${loaderZip.statusText}`) - await streamPipeline(loaderZip.body, fs.createWriteStream(zipPath)) - await extract(zipPath, { dir: path.join(app.getPath("userData"), "plugins") }) - modInstallState = "modDownload" - updateModBundle() + var loaderZip = await fetch("https://armcord.xyz/loader.zip"); + if (!loaderZip.ok) throw new Error(`unexpected response ${loaderZip.statusText}`); + await streamPipeline(loaderZip.body, fs.createWriteStream(zipPath)); + await extract(zipPath, {dir: path.join(app.getPath("userData"), "plugins")}); + modInstallState = "modDownload"; + updateModBundle(); import("./extensions/plugin"); - modInstallState = "done" - } catch(e) { - console.log("[Mod loader] Failed to install modloader") - console.error(e) + modInstallState = "done"; + } catch (e) { + console.log("[Mod loader] Failed to install modloader"); + console.error(e); dialog.showErrorBox( "Oops, something went wrong.", "ArmCord couldn't install internal mod loader, please check if you have stable internet connection and restart the app. If this issue persists, report it on the support server/Github issues." ); } } else { - modInstallState = "modDownload" - updateModBundle() + modInstallState = "modDownload"; + updateModBundle(); import("./extensions/plugin"); - modInstallState = "done" + modInstallState = "done"; } } } diff --git a/src/window.ts b/src/window.ts index 94f8c8a..2775611 100644 --- a/src/window.ts +++ b/src/window.ts @@ -12,7 +12,7 @@ import { setConfig, setLang, setWindowState, - transparency, + transparency } from "./utils"; import {registerIpc} from "./ipc"; import {setMenu} from "./menu"; @@ -51,9 +51,9 @@ contextMenu({ }); async function doAfterDefiningTheWindow() { if (transparency && process.platform === "win32") { - import("@pyke/vibe").then(vibe => { + import("@pyke/vibe").then((vibe) => { vibe.applyEffect(mainWindow, "acrylic"); - vibe.forceTheme(mainWindow, 'dark'); + vibe.forceTheme(mainWindow, "dark"); mainWindow.show(); }); } @@ -85,7 +85,7 @@ async function doAfterDefiningTheWindow() { } mainWindow.webContents.setWindowOpenHandler(({url}) => { // Allow about:blank (used by Vencord QuickCss popup) - if (url === "about:blank") return { action: "allow" }; + if (url === "about:blank") return {action: "allow"}; if (url.startsWith("https:" || url.startsWith("http:") || url.startsWith("mailto:"))) { shell.openExternal(url); @@ -206,7 +206,7 @@ async function doAfterDefiningTheWindow() { console.log(contentPath); if ((await getConfig("inviteWebsocket")) == true) { //@ts-ignore - import("arrpc") + import("arrpc"); //await startServer(); } if (firstRun) { diff --git a/tsconfig.json b/tsconfig.json index 1985c03..a54c4c6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,4 +31,4 @@ "downlevelIteration": false, // This flag adds extra support when targeting ES3, but adds extra bloat otherwise. "importHelpers": false // Reduce the amount of bloat that comes from downlevelIteration (when polyfills are redeclared). } -} \ No newline at end of file +} From a4968e75293246bd9c4d5685900fdcc792084614 Mon Sep 17 00:00:00 2001 From: Ven Date: Mon, 21 Nov 2022 06:50:45 +0100 Subject: [PATCH 15/18] Make clicking notifications focus ArmCord (#262) --- src/preload/preload.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/preload/preload.ts b/src/preload/preload.ts index 8aba86a..38ffc25 100644 --- a/src/preload/preload.ts +++ b/src/preload/preload.ts @@ -45,13 +45,13 @@ if (window.location.href.indexOf("splash.html") > -1) { addScript(` const dispatch = (() => { let Dispatcher; - + return function (event) { Dispatcher ??= window.Vencord?.Webpack.Common.FluxDispatcher if (!Dispatcher) { const cache = webpackChunkdiscord_app.push([[Symbol()], {}, w => w]).c; webpackChunkdiscord_app.pop() - + outer: for (const id in cache) { const mod = cache[id].exports; @@ -63,9 +63,9 @@ if (window.location.href.indexOf("splash.html") > -1) { } } } - if (!Dispatcher) + if (!Dispatcher) return; // failed to find, your choice if and how u wanna handle this - + return Dispatcher.dispatch(event); }; })(); @@ -114,3 +114,19 @@ setInterval(() => { el.onclick = () => ipcRenderer.send("openSettingsWindow"); host.append(el); }, 2000); + +// dirty hack to make clicking notifications focus ArmCord +addScript(` +(() => { +const originalSetter = Object.getOwnPropertyDescriptor(Notification.prototype, "onclick").set; +Object.defineProperty(Notification.prototype, "onclick", { + set(onClick) { + originalSetter.call(this, function() { + onClick.apply(this, arguments); + armcord.window.show(); + }) + }, + configurable: true +}); +})(); +`); From afa83c782db413b08c5e0bc29db4139d1bf963a4 Mon Sep 17 00:00:00 2001 From: smartfridge <37928912+smartfrigde@users.noreply.github.com> Date: Mon, 21 Nov 2022 16:25:11 +0100 Subject: [PATCH 16/18] Add mod bundle update skip --- src/utils.ts | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 73203bb..104d3c2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -285,29 +285,34 @@ export async function checkIfConfigExists() { // Mods async function updateModBundle() { - try { - console.log("Downloading mod bundle"); - const distFolder = app.getPath("userData") + "/plugins/loader/dist/"; - while (!fs.existsSync(distFolder)) { - //waiting + if ((await getConfig("noBundleUpdates")) == undefined ?? false) { + try { + console.log("Downloading mod bundle"); + const distFolder = app.getPath("userData") + "/plugins/loader/dist/"; + while (!fs.existsSync(distFolder)) { + //waiting + } + var name: string = await getConfig("mods"); + const clientMods = { + vencord: "https://github.com/Vendicated/Vencord/releases/download/devbuild/browser.js", + cordwood: "https://raw.githubusercontent.com/Cordwood/builds/master/index.js", + shelter: "https://raw.githubusercontent.com/uwu/shelter-builds/main/shelter.js" + }; + var bundle: string = await (await fetch(clientMods[name as keyof typeof clientMods])).text(); + fs.writeFileSync(distFolder + "bundle.js", bundle, "utf-8"); + } catch (e) { + console.log("[Mod loader] Failed to install mods"); + console.error(e); + dialog.showErrorBox( + "Oops, something went wrong.", + "ArmCord couldn't install mods, please check if you have stable internet connection and restart the app. If this issue persists, report it on the support server/Github issues." + ); } - var name: string = await getConfig("mods"); - const clientMods = { - vencord: "https://github.com/Vendicated/Vencord/releases/download/devbuild/browser.js", - cordwood: "https://raw.githubusercontent.com/Cordwood/builds/master/index.js", - shelter: "https://raw.githubusercontent.com/uwu/shelter-builds/main/shelter.js" - }; - var bundle: string = await (await fetch(clientMods[name as keyof typeof clientMods])).text(); - fs.writeFileSync(distFolder + "bundle.js", bundle, "utf-8"); - } catch (e) { - console.log("[Mod loader] Failed to install mods"); - console.error(e); - dialog.showErrorBox( - "Oops, something went wrong.", - "ArmCord couldn't install mods, please check if you have stable internet connection and restart the app. If this issue persists, report it on the support server/Github issues." - ); + } else { + console.log("[Mod loader] Skipping mod bundle update"); } } + export var modInstallState: string; export async function installModLoader() { if ((await getConfig("mods")) == "none") { From d7a318d5c39a5021ce57f27e2633135d87bc50a9 Mon Sep 17 00:00:00 2001 From: smartfridge <37928912+smartfrigde@users.noreply.github.com> Date: Tue, 22 Nov 2022 15:03:54 +0100 Subject: [PATCH 17/18] Update arrpc to 2.2.0 --- log.txt | 572 +++++++++++++++++++++++++ package.json | 2 +- src/arrpc/.gitignore | 2 - src/arrpc/README.md | 65 ++- src/arrpc/changelog.md | 20 + src/arrpc/examples/README.md | 9 + src/arrpc/examples/bridge_mod.js | 78 ++++ src/arrpc/examples/electron/main.js | 9 + src/arrpc/examples/electron/preload.js | 73 ++++ src/arrpc/package.json | 6 +- src/arrpc/simple_mod.js | 24 -- src/arrpc/src/bridge.js | 22 - src/arrpc/src/{index.js => index.cjs} | 6 +- src/arrpc/src/server.js | 65 +-- src/arrpc/src/transports/ipc.js | 12 +- src/arrpc/src/transports/websocket.js | 18 +- src/content/js/rpc.js | 79 ++++ src/preload/preload.ts | 61 +-- src/window.ts | 2 +- 19 files changed, 965 insertions(+), 160 deletions(-) create mode 100644 log.txt delete mode 100644 src/arrpc/.gitignore create mode 100644 src/arrpc/changelog.md create mode 100644 src/arrpc/examples/README.md create mode 100644 src/arrpc/examples/bridge_mod.js create mode 100644 src/arrpc/examples/electron/main.js create mode 100644 src/arrpc/examples/electron/preload.js delete mode 100644 src/arrpc/simple_mod.js delete mode 100644 src/arrpc/src/bridge.js rename src/arrpc/src/{index.js => index.cjs} (78%) create mode 100644 src/content/js/rpc.js diff --git a/log.txt b/log.txt new file mode 100644 index 0000000..7b8b46c --- /dev/null +++ b/log.txt @@ -0,0 +1,572 @@ + +> ArmCord@3.1.0 start +> npm run build && electron ./ts-out/main.js + + +> ArmCord@3.1.0 build +> tsc && copyfiles -u 1 src/**/*.html src/**/**/*.css src/**/**/*.js ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/**/** ts-out/ + +[Config manager] doneSetup: undefined +[Config manager] performanceMode: none +ArmCord has been run before. Skipping setup. +No performance modes set +[Config manager] windowStyle: default +[Config manager] armcordCSP: true +[Config manager] doneSetup: undefined +[Config manager] customIcon: undefined +Setting up CSP unstricter... +[Config manager] mods: vencord +[Config manager] trayIcon: default +[Config manager] windowStyle: default +[Config manager] windowStyle: default +[Config manager] ignoreProtocolWarning: undefined +[Config manager] clientName: undefined +[Config manager] 0: undefined +[Config manager] mods: vencord +[Config manager] noBundleUpdates: undefined +[Config manager] mobileMode: false +Downloading mod bundle +[Config manager] mods: vencord +[Config manager] trayIcon: default +[Mod loader] Loaded ArmCord Mod Loader made by Vendicated +[Config manager] alternativePaste: false +undefined +[Config manager] inviteWebsocket: true +[Config manager] skipSplash: undefined +[arRPC] arRPC v1.1.0-beta ArmCord +[arRPC > ipc] checking /run/user/1000/discord-ipc-0 +[arRPC > bridge] listening on 1337 +[arRPC > ipc] checked if socket is available: true +[arRPC > ipc] listening at /run/user/1000/discord-ipc-0 +[arRPC > websocket] trying port 6463 +[arRPC > websocket] listening on 6463 +[Config manager] channel: stable +[Config manager] mods: vencord +[Config manager] automaticPatches: false +[Config manager] channel: stable +[Window state manager] width: 1920 +[Window state manager] height: 1048 +[Window state manager] isMaximized: true +[Config manager] channel: stable +[Config manager] mods: vencord +[Config manager] automaticPatches: false +[Config manager] channel: stable +[Config manager] mobileMode: false +[arRPC > ipc] new connection! +[arRPC > ipc] handshake: { v: 1, client_id: '383226320970055681' } +[arRPC > ipc] sending { cmd: 'DISPATCH', evt: 'READY', data: { v: 1 } } +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: '4d2717f7-8fae-4339-222a-800a0e43f023' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: 'ed60da61-58bc-4e12-0fc3-c628a710cf7d' +} +[arRPC > bridge] fetched app info for 383226320970055681 { + id: '383226320970055681', + name: 'Visual Studio Code', + icon: 'bc45e1c85351ce0bafcb9245b3762e75', + description: '', + summary: '', + type: null, + cover_image: 'f1794a9f863d86c4a80aa51c3738fe58', + hook: true, + verify_key: 'd9fda69a8c8a9168d7fc41738822738eb31a4975a261d3172835df3595203f99', + flags: 0, + assets: [ + { id: '565944082250334228', type: 1, name: 'applescript' }, + { id: '565944082258722817', type: 1, name: 'arduino' }, + { id: '565944082266849297', type: 1, name: 'cshtml' }, + { id: '565944082271305778', type: 1, name: 'ahk' }, + { id: '565944082317312000', type: 1, name: 'assembly' }, + { id: '565944082321637386', type: 1, name: 'android' }, + { id: '565944082329763843', type: 1, name: 'csharp' }, + { id: '565944082384551955', type: 1, name: 'cmake' }, + { id: '565944082392940584', type: 1, name: 'autoit' }, + { id: '565944082401198090', type: 1, name: 'c' }, + { id: '565944082409455626', type: 1, name: 'angular' }, + { id: '565944082417975296', type: 1, name: 'asp' }, + { id: '565944082501861376', type: 1, name: 'appveyor' }, + { id: '565944082506055681', type: 1, name: 'crystal' }, + { id: '565944082510249984', type: 1, name: 'cpp' }, + { id: '565944082522963968', type: 1, name: 'cargo' }, + { id: '565944082573295616', type: 1, name: 'cssmap' }, + { id: '565944082585878538', type: 1, name: 'css' }, + { id: '565944082816565284', type: 1, name: 'bower' }, + { id: '565944082833342465', type: 1, name: 'clojure' }, + { id: '565944082858377220', type: 1, name: 'circleci' }, + { id: '565944082992726027', type: 1, name: 'as' }, + { id: '565944083021955093', type: 1, name: 'coffee' }, + { id: '565944083088932884', type: 1, name: 'bat' }, + { id: '565944083441516564', type: 1, name: 'brainfuck' }, + { id: '565944476732882948', type: 1, name: 'dart' }, + { id: '565944476829351967', type: 1, name: 'elixir' }, + { id: '565944476879683585', type: 1, name: 'eslint' }, + { id: '565944477068296243', type: 1, name: 'haskell' }, + { id: '565944477076684801', type: 1, name: 'go' }, + { id: '565944477085073418', type: 1, name: 'dm' }, + { id: '565944477122822179', type: 1, name: 'flowconfig' }, + { id: '565944477152444416', type: 1, name: 'fsharp' }, + { id: '565944477202645033', type: 1, name: 'erlang' }, + { id: '565944477215358986', type: 1, name: 'gemfile' }, + { id: '565944477232136192', type: 1, name: 'd' }, + { id: '565944477232136202', type: 1, name: 'git' }, + { id: '565944477278142465', type: 1, name: 'haxe' }, + { id: '565944477286531072', type: 1, name: 'graphql' }, + { id: '565944477328343040', type: 1, name: 'docker' }, + { id: '565944477336993822', type: 1, name: 'firebase' }, + { id: '565944477340926001', type: 1, name: 'editorconfig' }, + { id: '565944477647241228', type: 1, name: 'delphi' }, + { id: '565944477684989977', type: 1, name: 'handlebars' }, + { id: '565944477752098836', type: 1, name: 'gulp' }, + { id: '565944477961814016', type: 1, name: 'cuda' }, + { id: '565944478142169089', type: 1, name: 'gatsbyjs' }, + { id: '565944478418993152', type: 1, name: 'gradle' }, + { id: '565944478549016577', type: 1, name: 'env' }, + { id: '565944478557274142', type: 1, name: 'ejs' }, + { id: '565944478792155136', type: 1, name: 'elm' }, + { id: '565944479194808320', type: 1, name: 'gruntfile' }, + { id: '565944799245369386', type: 1, name: 'makefile' }, + { id: '565944799312740364', type: 1, name: 'less' }, + { id: '565944799446827009', type: 1, name: 'julia' }, + { id: '565944799450890240', type: 1, name: 'jar' }, + { id: '565944799455346703', type: 1, name: 'html' }, + { id: '565944799459278879', type: 1, name: 'jsmap' }, + { id: '565944799492964353', type: 1, name: 'kotlin' }, + { id: '565944799538970634', type: 1, name: 'heroku' }, + { id: '565944799576719366', type: 1, name: 'js' }, + { id: '565944799618662400', type: 1, name: 'livescript' }, + { id: '565944799707004929', type: 1, name: 'log' }, + { id: '565944799761268737', type: 1, name: 'json' }, + { id: '565944799761268766', type: 1, name: 'jest' }, + { id: '565944799996411914', type: 1, name: 'http' }, + { id: '565944800021446707', type: 1, name: 'java' }, + { id: '565944800021577729', type: 1, name: 'lisp' }, + { id: '565944800105332777', type: 1, name: 'lua' }, + { id: '565944802462531603', type: 1, name: 'jsx' }, + { id: '565945077252489228', type: 1, name: 'nim' }, + { id: '565945077260746763', type: 1, name: 'pascal' }, + { id: '565945077277655043', type: 1, name: 'perl' }, + { id: '565945077323792386', type: 1, name: 'postcss' }, + { id: '565945077348958236', type: 1, name: 'marko' }, + { id: '565945077411741717', type: 1, name: 'prisma' }, + { id: '565945077487108099', type: 1, name: 'nodemon' }, + { id: '565945077491433494', type: 1, name: 'markdown' }, + { id: '565945077524987927', type: 1, name: 'markdownx' }, + { id: '565945077554479114', type: 1, name: 'manifest' }, + { id: '565945077575319552', type: 1, name: 'pawn' }, + { id: '565945077583839243', type: 1, name: 'npm' }, + { id: '565945077625651200', type: 1, name: 'pug' }, + { id: '565945077642559498', type: 1, name: 'ocaml' }, + { id: '565945077709406209', type: 1, name: 'prettier' }, + { id: '565945078166716458', type: 1, name: 'powershell' }, + { id: '565945078359785494', type: 1, name: 'objc' }, + { id: '565945078833741834', type: 1, name: 'php' }, + { id: '565945350532104193', type: 1, name: 'tex' }, + { id: '565945350641418270', type: 1, name: 'reasonml' }, + { id: '565945350645481492', type: 1, name: 'python' }, + { id: '565945350670647298', type: 1, name: 'swift' }, + { id: '565945350695813133', type: 1, name: 'scala' }, + { id: '565945350766985216', type: 1, name: 'svg' }, + { id: '565945350771441676', type: 1, name: 'rust' }, + { id: '565945350792151064', type: 1, name: 'toml' }, + { id: '565945350838419490', type: 1, name: 'sql' }, + { id: '565945350846939145', type: 1, name: 'text' }, + { id: '565945350851002389', type: 1, name: 'shell' }, + { id: '565945350897008640', type: 1, name: 'scss' }, + ... 50 more items + ] +} +[arRPC > bridge] fetched app info for 383226320970055681 { + id: '383226320970055681', + name: 'Visual Studio Code', + icon: 'bc45e1c85351ce0bafcb9245b3762e75', + description: '', + summary: '', + type: null, + cover_image: 'f1794a9f863d86c4a80aa51c3738fe58', + hook: true, + verify_key: 'd9fda69a8c8a9168d7fc41738822738eb31a4975a261d3172835df3595203f99', + flags: 0, + assets: [ + { id: '565944082250334228', type: 1, name: 'applescript' }, + { id: '565944082258722817', type: 1, name: 'arduino' }, + { id: '565944082266849297', type: 1, name: 'cshtml' }, + { id: '565944082271305778', type: 1, name: 'ahk' }, + { id: '565944082317312000', type: 1, name: 'assembly' }, + { id: '565944082321637386', type: 1, name: 'android' }, + { id: '565944082329763843', type: 1, name: 'csharp' }, + { id: '565944082384551955', type: 1, name: 'cmake' }, + { id: '565944082392940584', type: 1, name: 'autoit' }, + { id: '565944082401198090', type: 1, name: 'c' }, + { id: '565944082409455626', type: 1, name: 'angular' }, + { id: '565944082417975296', type: 1, name: 'asp' }, + { id: '565944082501861376', type: 1, name: 'appveyor' }, + { id: '565944082506055681', type: 1, name: 'crystal' }, + { id: '565944082510249984', type: 1, name: 'cpp' }, + { id: '565944082522963968', type: 1, name: 'cargo' }, + { id: '565944082573295616', type: 1, name: 'cssmap' }, + { id: '565944082585878538', type: 1, name: 'css' }, + { id: '565944082816565284', type: 1, name: 'bower' }, + { id: '565944082833342465', type: 1, name: 'clojure' }, + { id: '565944082858377220', type: 1, name: 'circleci' }, + { id: '565944082992726027', type: 1, name: 'as' }, + { id: '565944083021955093', type: 1, name: 'coffee' }, + { id: '565944083088932884', type: 1, name: 'bat' }, + { id: '565944083441516564', type: 1, name: 'brainfuck' }, + { id: '565944476732882948', type: 1, name: 'dart' }, + { id: '565944476829351967', type: 1, name: 'elixir' }, + { id: '565944476879683585', type: 1, name: 'eslint' }, + { id: '565944477068296243', type: 1, name: 'haskell' }, + { id: '565944477076684801', type: 1, name: 'go' }, + { id: '565944477085073418', type: 1, name: 'dm' }, + { id: '565944477122822179', type: 1, name: 'flowconfig' }, + { id: '565944477152444416', type: 1, name: 'fsharp' }, + { id: '565944477202645033', type: 1, name: 'erlang' }, + { id: '565944477215358986', type: 1, name: 'gemfile' }, + { id: '565944477232136192', type: 1, name: 'd' }, + { id: '565944477232136202', type: 1, name: 'git' }, + { id: '565944477278142465', type: 1, name: 'haxe' }, + { id: '565944477286531072', type: 1, name: 'graphql' }, + { id: '565944477328343040', type: 1, name: 'docker' }, + { id: '565944477336993822', type: 1, name: 'firebase' }, + { id: '565944477340926001', type: 1, name: 'editorconfig' }, + { id: '565944477647241228', type: 1, name: 'delphi' }, + { id: '565944477684989977', type: 1, name: 'handlebars' }, + { id: '565944477752098836', type: 1, name: 'gulp' }, + { id: '565944477961814016', type: 1, name: 'cuda' }, + { id: '565944478142169089', type: 1, name: 'gatsbyjs' }, + { id: '565944478418993152', type: 1, name: 'gradle' }, + { id: '565944478549016577', type: 1, name: 'env' }, + { id: '565944478557274142', type: 1, name: 'ejs' }, + { id: '565944478792155136', type: 1, name: 'elm' }, + { id: '565944479194808320', type: 1, name: 'gruntfile' }, + { id: '565944799245369386', type: 1, name: 'makefile' }, + { id: '565944799312740364', type: 1, name: 'less' }, + { id: '565944799446827009', type: 1, name: 'julia' }, + { id: '565944799450890240', type: 1, name: 'jar' }, + { id: '565944799455346703', type: 1, name: 'html' }, + { id: '565944799459278879', type: 1, name: 'jsmap' }, + { id: '565944799492964353', type: 1, name: 'kotlin' }, + { id: '565944799538970634', type: 1, name: 'heroku' }, + { id: '565944799576719366', type: 1, name: 'js' }, + { id: '565944799618662400', type: 1, name: 'livescript' }, + { id: '565944799707004929', type: 1, name: 'log' }, + { id: '565944799761268737', type: 1, name: 'json' }, + { id: '565944799761268766', type: 1, name: 'jest' }, + { id: '565944799996411914', type: 1, name: 'http' }, + { id: '565944800021446707', type: 1, name: 'java' }, + { id: '565944800021577729', type: 1, name: 'lisp' }, + { id: '565944800105332777', type: 1, name: 'lua' }, + { id: '565944802462531603', type: 1, name: 'jsx' }, + { id: '565945077252489228', type: 1, name: 'nim' }, + { id: '565945077260746763', type: 1, name: 'pascal' }, + { id: '565945077277655043', type: 1, name: 'perl' }, + { id: '565945077323792386', type: 1, name: 'postcss' }, + { id: '565945077348958236', type: 1, name: 'marko' }, + { id: '565945077411741717', type: 1, name: 'prisma' }, + { id: '565945077487108099', type: 1, name: 'nodemon' }, + { id: '565945077491433494', type: 1, name: 'markdown' }, + { id: '565945077524987927', type: 1, name: 'markdownx' }, + { id: '565945077554479114', type: 1, name: 'manifest' }, + { id: '565945077575319552', type: 1, name: 'pawn' }, + { id: '565945077583839243', type: 1, name: 'npm' }, + { id: '565945077625651200', type: 1, name: 'pug' }, + { id: '565945077642559498', type: 1, name: 'ocaml' }, + { id: '565945077709406209', type: 1, name: 'prettier' }, + { id: '565945078166716458', type: 1, name: 'powershell' }, + { id: '565945078359785494', type: 1, name: 'objc' }, + { id: '565945078833741834', type: 1, name: 'php' }, + { id: '565945350532104193', type: 1, name: 'tex' }, + { id: '565945350641418270', type: 1, name: 'reasonml' }, + { id: '565945350645481492', type: 1, name: 'python' }, + { id: '565945350670647298', type: 1, name: 'swift' }, + { id: '565945350695813133', type: 1, name: 'scala' }, + { id: '565945350766985216', type: 1, name: 'svg' }, + { id: '565945350771441676', type: 1, name: 'rust' }, + { id: '565945350792151064', type: 1, name: 'toml' }, + { id: '565945350838419490', type: 1, name: 'sql' }, + { id: '565945350846939145', type: 1, name: 'text' }, + { id: '565945350851002389', type: 1, name: 'shell' }, + { id: '565945350897008640', type: 1, name: 'scss' }, + ... 50 more items + ] +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: 'b3d5dd03-d425-40ae-132e-8acf53b18497' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: 'a81c11d1-60e8-4c23-2af3-6626c0791f1a' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: '3230ba41-4874-460a-1689-9b9ff1663a7f' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: 'f6d973b1-1e75-4dd6-2e93-b65a9ef3be0b' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: 'b8843cb1-aa6e-437d-3da0-1e08cd44cfb5' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: 'd6a257e0-fa89-449f-38b1-44fa71811fb7' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: 'ac9e04a8-52c3-4614-04b2-1779e10e263f' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + details: 'Idling', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: 'f0e4d6e4-7aa8-4f5b-1264-ac76680cfd24' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing server.js', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: '3cc9be1b-338f-4867-1cf0-cba574084c45' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing server.js', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: 'd4965762-a25b-4b8b-2891-11a1ed78289d' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: '92243961-65fb-48a2-26d1-1d8980a376f3' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: '4d1001fa-6d04-4b0a-0d0d-4ceefd1c79f0' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: '1cb43f39-5cd6-40aa-385b-16fd9fe70d4f' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: '1b139214-f410-48bc-24d4-2835d2d49f02' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: '442a6b6a-126d-435d-2a85-296ec86c36a8' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: 'a0517d56-4927-4761-2642-699fd091dc0c' +} +[arRPC > ipc] message { + cmd: 'SET_ACTIVITY', + args: { + pid: 5707, + activity: { + state: 'Workspace: ArmCord', + details: 'Editing log.txt', + timestamps: [Object], + assets: [Object], + buttons: [Array], + instance: false + } + }, + nonce: '8c63a412-f861-49e9-288e-e31765f7c36b' +} +[Config manager] minimizeToTray: true diff --git a/package.json b/package.json index cfb69d4..bcc8e4b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "ArmCord is a custom client designed to enhance your Discord experience while keeping everything lightweight.", "main": "ts-out/main.js", "scripts": { - "build": "tsc && copyfiles -u 1 src/**/*.html src/**/**/*.css ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/**/** ts-out/", + "build": "tsc && copyfiles -u 1 src/**/*.html src/**/**/*.css src/**/**/*.js ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/**/** ts-out/", "watch": "tsc -w", "start": "npm run build && electron ./ts-out/main.js", "startNoSandbox": "npm run build && electron ./ts-out/main.js --no-sandbox", diff --git a/src/arrpc/.gitignore b/src/arrpc/.gitignore deleted file mode 100644 index 25c8fdb..0000000 --- a/src/arrpc/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -package-lock.json \ No newline at end of file diff --git a/src/arrpc/README.md b/src/arrpc/README.md index 4eb5b8b..8ab45e6 100644 --- a/src/arrpc/README.md +++ b/src/arrpc/README.md @@ -1,10 +1,21 @@ -# arRPC - -arRPC is an open source implementation of Discord's half-documented local RPC servers for their desktop client. This open source implementation purely in NodeJS allows it to be used in many places where it is otherwise impossible to do: Discord web and alternative clients like Armcord/etc. It opens a simple bridge WebSocket server which messages the JSON of exactly what to dispatch with in the client with no extra processing needed, allowing small and simple mods or plugins. **It is currently in alpha and is very WIP, expect bugs, etc.** +
+ + + arRPC +
+ License: MIT + GitHub Sponsors +

An open implementation of Discord's local RPC servers

+

Allowing RPC where it was otherwise impossible, like Discord Web and custom clients

+

-Rich Presence (RPC) is the name for how some apps can talk to Discord desktop on your PC via localhost servers to display detailed info about the app's state. This usually works via parts of Discord desktop natively doing things + parts of Discord web interpreting that and setting it as your status. arRPC is an open source implementation of the local RPC servers on your PC, allowing apps to talk to it thinking it was just normal Discord. It can then send that info to apps which usually don't get RPC, like Discord Web, Armcord, etc. which can then set that as your status. This would otherwise not be possible, as web apps/browsers/etc can't just use Discord's already existing code and version. +arRPC is an open source implementation of Discord's half-documented local RPC servers for their desktop client. This open source implementation purely in NodeJS allows it to be used in many places where it is otherwise impossible to do: Discord web and alternative clients like ArmCord/etc. It opens a simple bridge WebSocket server which messages the JSON of exactly what to dispatch with in the client with no extra processing needed, allowing small and simple mods or plugins. **arRPC is experimental and a work in progress, so expect bugs, etc.** + +
+ +Rich Presence (RPC) is the name for how some apps can talk to Discord desktop on your PC via localhost servers to display detailed info about the app's state. This usually works via parts of Discord desktop natively doing things + parts of Discord web interpreting that and setting it as your status. arRPC is an open source implementation of the local RPC servers on your PC, allowing apps to talk to it thinking it was just normal Discord. It can then send that info to apps which usually don't get RPC, like Discord Web, ArmCord, etc. which can then set that as your status. This would otherwise not be possible, as web apps/browsers/etc can't just use Discord's already existing code and version. - App with Discord RPC - ~~Discord Desktop's native server~~ arRPC @@ -12,14 +23,44 @@ Rich Presence (RPC) is the name for how some apps can talk to Discord desktop on
-### How to try +## Usage -1. Clone repo -2. Run server with `node src` (use new Node) -3. Open Discord in browser -4. Run content of [`simple_mod.js`](simple_mod.js) in console -5. Use an app/thing with RPC -6. Hope it works, if not report bugs :) +### Server (**REQUIRED**) + +1. Have latest (>=18) Node installed +2. Clone GitHub repo +3. `npm install` +4. Run server with `node src` + +### Web + +#### No Mods + +1. Get [the arRPC server running](#server-required) +2. With Discord open, run the content of [`examples/bridge_mod.js`](examples/bridge_mod.js) in Console (Ctrl+Shift+I). + +#### Vencord + +1. Get [the arRPC server running](#server-required) +2. Just enable the `WebRichPresence (arRPC)` Vencord plugin! + +### Custom Clients + +#### ArmCord + +ArmCord has arRPC specially integrated, just enable the option in it's settings (server not required)! + +#### Webcord + +1. Get [the arRPC server running](#server-required) +2. Disable the `Use built-in Content Security Policy` option in Advanced settings: ![image](https://user-images.githubusercontent.com/19228318/202926723-93b772fc-f37d-47d4-81fd-b11c5d4051e8.png) +3. With Webcord open, run the content of [`examples/bridge_mod.js`](examples/bridge_mod.js) in the DevTools Console (Ctrl+Shift+I). + +--- + +Then just use apps with Discord RPC like normal and they _should_ work! + +
## Supported @@ -36,3 +77,5 @@ Rich Presence (RPC) is the name for how some apps can talk to Discord desktop on - [x] DISPATCH - [x] SET_ACTIVITY - [x] INVITE_BROWSER +- [x] GUILD_TEMPLATE_BROWSER +- [x] DEEP_LINK diff --git a/src/arrpc/changelog.md b/src/arrpc/changelog.md new file mode 100644 index 0000000..671fb77 --- /dev/null +++ b/src/arrpc/changelog.md @@ -0,0 +1,20 @@ +# arRPC Changelog + +## v2.2.0 [20-11-2022] + +- Server: Move all looking up/fetching to client + +## v2.1.0 [20-11-2022] + +- Server: Stop activites when app disconnects +- Server: Added support for several apps shown at once (added `socketId`) +- Bridge: Catchup newly connected clients with last message by socket id +- Transports: Rewrote internal API to use handlers object +- API: Added parsing for GUILD_TEMPLATE_BROWSER +- API: Added parsing for DEEP_LINK + +## v2.0.0 [20-11-2022] + +- feat (breaking): moved asset lookup to client +- feat: add examples +- feat: add changelog diff --git a/src/arrpc/examples/README.md b/src/arrpc/examples/README.md new file mode 100644 index 0000000..2ef1a41 --- /dev/null +++ b/src/arrpc/examples/README.md @@ -0,0 +1,9 @@ +# arRPC Examples + +## [Bridge Mod](bridge_mod.js) + +Simple mod for using the arRPC Bridge WebSocket server for setting RPC status (to be used with just Web). + +## [Electron](electron) + +Example usage for within an Electron client. diff --git a/src/arrpc/examples/bridge_mod.js b/src/arrpc/examples/bridge_mod.js new file mode 100644 index 0000000..cd9503e --- /dev/null +++ b/src/arrpc/examples/bridge_mod.js @@ -0,0 +1,78 @@ +(() => { + let Dispatcher, + lookupAsset, + lookupApp, + apps = {}; + + const ws = new WebSocket("ws://127.0.0.1:1337"); // connect to arRPC bridge websocket + ws.onmessage = async (x) => { + msg = JSON.parse(x.data); + console.log(msg); + + if (!Dispatcher) { + const wpRequire = window.webpackChunkdiscord_app.push([[Symbol()], {}, (x) => x]); + const cache = wpRequire.c; + window.webpackChunkdiscord_app.pop(); + + for (const id in cache) { + let mod = cache[id].exports; + mod = mod && (mod.Z ?? mod.ZP); + + if (mod && mod.register && mod.wait) { + Dispatcher = mod; + break; + } + } + + const factories = wpRequire.m; + for (const id in factories) { + if (factories[id].toString().includes("getAssetImage: size must === [number, number] for Twitch")) { + const mod = wpRequire(id); + + const _lookupAsset = Object.values(mod).find( + (e) => typeof e === "function" && e.toString().includes("apply(") + ); + lookupAsset = async (appId, name) => (await _lookupAsset(appId, [name, undefined]))[0]; + + break; + } + } + + for (const id in factories) { + if (factories[id].toString().includes(`e.application={`)) { + const mod = wpRequire(id); + + const _lookupApp = Object.values(mod).find( + (e) => typeof e === "function" && e.toString().includes(`e.application={`) + ); + lookupApp = async (appId) => { + let socket = {}; + await _lookupApp(socket, appId); + return socket.application; + }; + + break; + } + } + } + + if (msg.activity?.assets?.large_image) + msg.activity.assets.large_image = await lookupAsset( + msg.activity.application_id, + msg.activity.assets.large_image + ); + if (msg.activity?.assets?.small_image) + msg.activity.assets.small_image = await lookupAsset( + msg.activity.application_id, + msg.activity.assets.small_image + ); + + const appId = msg.activity.application_id; + if (!apps[appId]) apps[appId] = await lookupApp(appId); + + const app = apps[appId]; + if (!msg.activity.name) msg.activity.name = app.name; + + Dispatcher.dispatch({type: "LOCAL_ACTIVITY_UPDATE", ...msg}); // set RPC status + }; +})(); diff --git a/src/arrpc/examples/electron/main.js b/src/arrpc/examples/electron/main.js new file mode 100644 index 0000000..fb72e3e --- /dev/null +++ b/src/arrpc/examples/electron/main.js @@ -0,0 +1,9 @@ +// myWindow = your discord.com BrowserWindow + +import Server from "./path/to/arrpc/server.js"; + +const arrpc = await new Server(); +arrpc.on("activity", (data) => myWindow.webContents.send("rpc", data)); +arrpc.on("invite", (code) => { + // your invite code handling here +}); diff --git a/src/arrpc/examples/electron/preload.js b/src/arrpc/examples/electron/preload.js new file mode 100644 index 0000000..b9f13d6 --- /dev/null +++ b/src/arrpc/examples/electron/preload.js @@ -0,0 +1,73 @@ +import {ipcRenderer} from "electron"; + +let Dispatcher, + lookupAsset, + lookupApp, + apps = {}; +ipcRenderer.on("rpc", async (event, data) => { + if (!Dispatcher) { + const wpRequire = window.webpackChunkdiscord_app.push([[Symbol()], {}, (x) => x]); + const cache = wpRequire.c; + window.webpackChunkdiscord_app.pop(); + + for (const id in cache) { + let mod = cache[id].exports; + mod = mod && (mod.Z ?? mod.ZP); + + if (mod && mod.register && mod.wait) { + Dispatcher = mod; + break; + } + } + + const factories = wpRequire.m; + for (const id in factories) { + if (factories[id].toString().includes("getAssetImage: size must === [number, number] for Twitch")) { + const mod = wpRequire(id); + + const _lookupAsset = Object.values(mod).find( + (e) => typeof e === "function" && e.toString().includes("apply(") + ); + lookupAsset = async (appId, name) => (await _lookupAsset(appId, [name, undefined]))[0]; + + break; + } + } + + for (const id in factories) { + if (factories[id].toString().includes(`e.application={`)) { + const mod = wpRequire(id); + + const _lookupApp = Object.values(mod).find( + (e) => typeof e === "function" && e.toString().includes(`e.application={`) + ); + lookupApp = async (appId) => { + let socket = {}; + await _lookupApp(socket, appId); + return socket.application; + }; + + break; + } + } + } + + if (data.activity?.assets?.large_image) + data.activity.assets.large_image = await lookupAsset( + data.activity.application_id, + data.activity.assets.large_image + ); + if (data.activity?.assets?.small_image) + data.activity.assets.small_image = await lookupAsset( + data.activity.application_id, + data.activity.assets.small_image + ); + + const appId = data.activity.application_id; + if (!apps[appId]) apps[appId] = await lookupApp(appId); + + const app = apps[appId]; + if (!data.activity.name) data.activity.name = app.name; + + Dispatcher.dispatch({type: "LOCAL_ACTIVITY_UPDATE", ...data}); // set RPC status +}); diff --git a/src/arrpc/package.json b/src/arrpc/package.json index 1de36e1..fa4e473 100644 --- a/src/arrpc/package.json +++ b/src/arrpc/package.json @@ -1,10 +1,10 @@ { "name": "arrpc", - "version": "1.1.0", + "version": "2.2.0", "description": "Open Discord RPC server for atypical setups", - "main": "src/index.js", + "main": "src/index.cjs", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "start": "node src" }, "repository": { "type": "git", diff --git a/src/arrpc/simple_mod.js b/src/arrpc/simple_mod.js deleted file mode 100644 index 81bcbe7..0000000 --- a/src/arrpc/simple_mod.js +++ /dev/null @@ -1,24 +0,0 @@ -let Dispatcher; - -const ws = new WebSocket("ws://127.0.0.1:1337"); // connect to arRPC bridge -ws.onmessage = (x) => { - msg = JSON.parse(x.data); - console.log(msg); - - if (!Dispatcher) { - const cache = window.webpackChunkdiscord_app.push([[Symbol()], {}, (x) => x]).c; - window.webpackChunkdiscord_app.pop(); - - for (const id in cache) { - let mod = cache[id].exports; - mod = mod && (mod.Z ?? mod.ZP); - - if (mod && mod.register && mod.wait) { - Dispatcher = mod; - break; - } - } - } - - Dispatcher.dispatch({type: "LOCAL_ACTIVITY_UPDATE", ...msg}); // set RPC status -}; diff --git a/src/arrpc/src/bridge.js b/src/arrpc/src/bridge.js deleted file mode 100644 index d7cffd9..0000000 --- a/src/arrpc/src/bridge.js +++ /dev/null @@ -1,22 +0,0 @@ -const rgb = (r, g, b, msg) => `\x1b[38;2;${r};${g};${b}m${msg}\x1b[0m`; -const log = (...args) => console.log(`[${rgb(88, 101, 242, "arRPC")} > ${rgb(87, 242, 135, "bridge")}]`, ...args); - -const {WebSocketServer} = require("ws"); -// basic bridge to pass info onto webapp -const send = (msg) => { - wss.clients.forEach((x) => x.send(JSON.stringify(msg))); -}; - -const port = 1337; -const wss = new WebSocketServer({port}); - -wss.on("connection", (socket) => { - log("web connected"); - - socket.on("close", () => { - log("web disconnected"); - }); -}); - -wss.on("listening", () => log("listening on", port)); -module.exports = {send}; diff --git a/src/arrpc/src/index.js b/src/arrpc/src/index.cjs similarity index 78% rename from src/arrpc/src/index.js rename to src/arrpc/src/index.cjs index 3d31a83..8ab51c4 100644 --- a/src/arrpc/src/index.js +++ b/src/arrpc/src/index.cjs @@ -1,13 +1,10 @@ const rgb = (r, g, b, msg) => `\x1b[38;2;${r};${g};${b}m${msg}\x1b[0m`; const log = (...args) => console.log(`[${rgb(88, 101, 242, "arRPC")}]`, ...args); -log("arRPC v1.1.0-beta ArmCord"); +log("arRPC v2.2.0 [ArmCord]"); -const Bridge = require("./bridge.js"); const {RPCServer} = require("./server.js"); -const fetch = require("cross-fetch"); const {mainWindow} = require("../../../ts-out/window.js"); -const {sleep} = require("../../../ts-out/utils.js"); async function run() { const server = await new RPCServer(); @@ -24,4 +21,3 @@ async function run() { }); } run(); -//server.on('activity', data => Bridge.send(data)); diff --git a/src/arrpc/src/server.js b/src/arrpc/src/server.js index 75137b8..0437a88 100644 --- a/src/arrpc/src/server.js +++ b/src/arrpc/src/server.js @@ -1,23 +1,28 @@ const rgb = (r, g, b, msg) => `\x1b[38;2;${r};${g};${b}m${msg}\x1b[0m`; const log = (...args) => console.log(`[${rgb(88, 101, 242, "arRPC")} > ${rgb(87, 242, 135, "bridge")}]`, ...args); -const {IPCServer} = require("./transports/ipc.js"); const {EventEmitter} = require("events"); + +const {IPCServer} = require("./transports/ipc.js"); const {WSServer} = require("./transports/websocket.js"); -const Bridge = require("./bridge.js"); -const fetch = require("cross-fetch"); -const lookupAsset = (name, assets) => { - return assets.find((x) => x.name === name)?.id; -}; + +let socketId = 0; class RPCServer extends EventEmitter { constructor() { super(); return (async () => { this.onConnection = this.onConnection.bind(this); this.onMessage = this.onMessage.bind(this); + this.onClose = this.onClose.bind(this); - this.ipc = await new IPCServer(this.onMessage, this.onConnection); - this.ws = await new WSServer(this.onMessage, this.onConnection); + const handlers = { + connection: this.onConnection, + message: this.onMessage, + close: this.onClose + }; + + this.ipc = await new IPCServer(handlers); + this.ws = await new WSServer(handlers); return this; })(); @@ -33,28 +38,31 @@ class RPCServer extends EventEmitter { } }); + socket.socketId = socketId++; + this.emit("connection", socket); } + onClose(socket) { + this.emit("activity", { + activity: null, + pid: socket.lastPid, + socketId: socket.socketId.toString() + }); + + this.emit("close", socket); + } + async onMessage(socket, {cmd, args, nonce}) { this.emit("message", {socket, cmd, args, nonce}); switch (cmd) { case "SET_ACTIVITY": - if (!socket.application) { - // fetch info about application - socket.application = await ( - await fetch(`https://discord.com/api/v9/oauth2/applications/${socket.clientId}/rpc`) - ).json(); - socket.application.assets = await ( - await fetch(`https://discord.com/api/v9/oauth2/applications/${socket.clientId}/assets`) - ).json(); - log("fetched app info for", socket.clientId, socket.application); - } - const {activity, pid} = args; // translate given parameters into what discord dispatch expects const {buttons, timestamps, instance} = activity; + socket.lastPid = pid ?? socket.lastPid; + const metadata = {}; const extra = {}; if (buttons) { @@ -70,27 +78,22 @@ class RPCServer extends EventEmitter { timestamps[x] = Math.floor(1000 * timestamps[x]); } - // lookup assets to ids - if (activity.assets?.large_image) - activity.assets.large_image = lookupAsset(activity.assets.large_image, socket.application.assets); - if (activity.assets?.small_image) - activity.assets.small_image = lookupAsset(activity.assets.small_image, socket.application.assets); - this.emit("activity", { activity: { - name: socket.application.name, - application_id: socket.application.id, + application_id: socket.clientId, type: 0, metadata, flags: instance ? 1 << 0 : 0, ...activity, ...extra }, - pid + pid, + socketId: socket.socketId.toString() }); break; + case "GUILD_TEMPLATE_BROWSER": case "INVITE_BROWSER": const {code} = args; socket.send({ @@ -101,7 +104,11 @@ class RPCServer extends EventEmitter { nonce }); - this.emit("invite", code); + this.emit(cmd === "INVITE_BROWSER" ? "invite" : "guild_template", code); + break; + + case "DEEP_LINK": + this.emit("link", args.params); break; } } diff --git a/src/arrpc/src/transports/ipc.js b/src/arrpc/src/transports/ipc.js index dba70d9..de0bffa 100644 --- a/src/arrpc/src/transports/ipc.js +++ b/src/arrpc/src/transports/ipc.js @@ -4,9 +4,7 @@ const log = (...args) => console.log(`[${rgb(88, 101, 242, "arRPC")} > ${rgb(254 const {join} = require("path"); const {platform, env} = require("process"); const {unlinkSync} = require("fs"); - const {createServer, createConnection} = require("net"); - const SOCKET_PATH = platform === "win32" ? "\\\\?\\pipe\\discord-ipc" @@ -167,10 +165,9 @@ const getAvailableSocket = async (tries = 0) => { }; class IPCServer { - constructor(messageHandler, connectionHandler) { + constructor(handers) { return new Promise(async (res) => { - this.messageHandler = messageHandler; - this.connectionHandler = connectionHandler; + this.handlers = handers; this.onConnection = this.onConnection.bind(this); this.onMessage = this.onMessage.bind(this); @@ -237,6 +234,7 @@ class IPCServer { socket.on("close", (e) => { log("socket closed", e); + this.handlers.close(socket); }); socket.on("request", this.onMessage.bind(this, socket)); @@ -249,13 +247,13 @@ class IPCServer { socket.clientId = clientId; - this.connectionHandler(socket); + this.handlers.connection(socket); }); } onMessage(socket, msg) { log("message", msg); - this.messageHandler(socket, msg); + this.handlers.message(socket, msg); } } module.exports = {IPCServer}; diff --git a/src/arrpc/src/transports/websocket.js b/src/arrpc/src/transports/websocket.js index cd9c50c..e4983d3 100644 --- a/src/arrpc/src/transports/websocket.js +++ b/src/arrpc/src/transports/websocket.js @@ -4,14 +4,13 @@ const log = (...args) => console.log(`[${rgb(88, 101, 242, "arRPC")} > ${rgb(235 const {WebSocketServer} = require("ws"); const {createServer} = require("http"); const {parse} = require("querystring"); -var exportPort = 0000; + const portRange = [6463, 6472]; // ports available/possible: 6463-6472 class WSServer { - constructor(messageHandler, connectionHandler) { + constructor(handlers) { return (async () => { - this.messageHandler = messageHandler; - this.connectionHandler = connectionHandler; + this.handlers = handlers; this.onConnection = this.onConnection.bind(this); this.onMessage = this.onMessage.bind(this); @@ -43,7 +42,7 @@ class WSServer { http.listen(port, "127.0.0.1", () => { log("listening on", port); - exportPort = port; + this.http = http; this.wss = wss; @@ -108,7 +107,8 @@ class WSServer { }); socket.on("close", (e, r) => { - log("socket closed", e); + log("socket closed", e, r); + this.handlers.close(socket); }); socket.on("message", this.onMessage.bind(this, socket)); @@ -119,12 +119,12 @@ class WSServer { socket._send(JSON.stringify(msg)); }; - this.connectionHandler(socket); + this.handlers.connection(socket); } onMessage(socket, msg) { log("message", JSON.parse(msg)); - this.messageHandler(socket, JSON.parse(msg)); + this.handlers.message(socket, JSON.parse(msg)); } } -module.exports = {WSServer, exportPort}; +module.exports = {WSServer}; diff --git a/src/content/js/rpc.js b/src/content/js/rpc.js new file mode 100644 index 0000000..f14b924 --- /dev/null +++ b/src/content/js/rpc.js @@ -0,0 +1,79 @@ +(() => { + let Dispatcher, + lookupAsset, + lookupApp, + apps = {}; + + ArmCordRPC.listen(async (data) => { + msg = data; //already parsed + console.log(msg); + + if (!Dispatcher) { + const wpRequire = window.webpackChunkdiscord_app.push([[Symbol()], {}, (x) => x]); + const cache = wpRequire.c; + window.webpackChunkdiscord_app.pop(); + + for (const id in cache) { + let mod = cache[id].exports; + mod = mod && (mod.Z ?? mod.ZP); + + if (mod && mod.register && mod.wait) { + Dispatcher = mod; + break; + } + } + + const factories = wpRequire.m; + for (const id in factories) { + if (factories[id].toString().includes("getAssetImage: size must === [number, number] for Twitch")) { + const mod = wpRequire(id); + + const _lookupAsset = Object.values(mod).find( + (e) => typeof e === "function" && e.toString().includes("apply(") + ); + lookupAsset = async (appId, name) => (await _lookupAsset(appId, [name, undefined]))[0]; + + break; + } + } + + for (const id in factories) { + if (factories[id].toString().includes(`e.application={`)) { + const mod = wpRequire(id); + + const _lookupApp = Object.values(mod).find( + (e) => typeof e === "function" && e.toString().includes(`e.application={`) + ); + lookupApp = async (appId) => { + let socket = {}; + await _lookupApp(socket, appId); + return socket.application; + }; + + break; + } + } + } + + if (msg.activity?.assets?.large_image) + msg.activity.assets.large_image = await lookupAsset( + msg.activity.application_id, + msg.activity.assets.large_image + ); + if (msg.activity?.assets?.small_image) + msg.activity.assets.small_image = await lookupAsset( + msg.activity.application_id, + msg.activity.assets.small_image + ); + + if (msg.activity) { + const appId = msg.activity.application_id; + if (!apps[appId]) apps[appId] = await lookupApp(appId); + + const app = apps[appId]; + if (!msg.activity.name) msg.activity.name = app.name; + } + + Dispatcher.dispatch({type: "LOCAL_ACTIVITY_UPDATE", ...msg}); // set RPC status + }); +})(); diff --git a/src/preload/preload.ts b/src/preload/preload.ts index 38ffc25..f5e84f2 100644 --- a/src/preload/preload.ts +++ b/src/preload/preload.ts @@ -42,38 +42,23 @@ if (window.location.href.indexOf("splash.html") > -1) { injectMobileStuff(); } sleep(5000).then(async () => { + // dirty hack to make clicking notifications focus ArmCord addScript(` - const dispatch = (() => { - let Dispatcher; - - return function (event) { - Dispatcher ??= window.Vencord?.Webpack.Common.FluxDispatcher - if (!Dispatcher) { - const cache = webpackChunkdiscord_app.push([[Symbol()], {}, w => w]).c; - webpackChunkdiscord_app.pop() - - outer: - for (const id in cache) { - const mod = cache[id].exports; - for (const exp in mod) { - if (mod[exp]?.isDispatching) { - Dispatcher = mod[exp]; - break outer; - } - } - } - } - if (!Dispatcher) - return; // failed to find, your choice if and how u wanna handle this - - return Dispatcher.dispatch(event); - }; - })(); - ArmCordRPC.listen((data) => { - console.log(data) - dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...data }); - }) + (() => { + const originalSetter = Object.getOwnPropertyDescriptor(Notification.prototype, "onclick").set; + Object.defineProperty(Notification.prototype, "onclick", { + set(onClick) { + originalSetter.call(this, function() { + onClick.apply(this, arguments); + armcord.window.show(); + }) + }, + configurable: true + }); + })(); `); + + addScript(fs.readFileSync(path.join(__dirname, "../", "/content/js/rpc.js"), "utf8")); const cssPath = path.join(__dirname, "../", "/content/css/discord.css"); addStyle(fs.readFileSync(cssPath, "utf8")); await updateLang(); @@ -114,19 +99,3 @@ setInterval(() => { el.onclick = () => ipcRenderer.send("openSettingsWindow"); host.append(el); }, 2000); - -// dirty hack to make clicking notifications focus ArmCord -addScript(` -(() => { -const originalSetter = Object.getOwnPropertyDescriptor(Notification.prototype, "onclick").set; -Object.defineProperty(Notification.prototype, "onclick", { - set(onClick) { - originalSetter.call(this, function() { - onClick.apply(this, arguments); - armcord.window.show(); - }) - }, - configurable: true -}); -})(); -`); diff --git a/src/window.ts b/src/window.ts index 2775611..13056ab 100644 --- a/src/window.ts +++ b/src/window.ts @@ -206,7 +206,7 @@ async function doAfterDefiningTheWindow() { console.log(contentPath); if ((await getConfig("inviteWebsocket")) == true) { //@ts-ignore - import("arrpc"); + require("arrpc"); //await startServer(); } if (firstRun) { From e6df2b0ecf020a17055edc6820d9f133c263a94c Mon Sep 17 00:00:00 2001 From: smartfridge <37928912+smartfrigde@users.noreply.github.com> Date: Tue, 22 Nov 2022 15:34:24 +0100 Subject: [PATCH 18/18] Make invite socket use arrpc --- src/arrpc/src/index.cjs | 9 +- src/socket.ts | 186 ---------------------------------------- src/tray.ts | 5 +- src/window.ts | 16 +++- 4 files changed, 17 insertions(+), 199 deletions(-) delete mode 100644 src/socket.ts diff --git a/src/arrpc/src/index.cjs b/src/arrpc/src/index.cjs index 8ab51c4..93d7943 100644 --- a/src/arrpc/src/index.cjs +++ b/src/arrpc/src/index.cjs @@ -11,13 +11,8 @@ async function run() { server.on("activity", (data) => mainWindow.webContents.send("rpc", data)); server.on("invite", (code) => { console.log(code); - const {createInviteWindow, inviteWindow} = require("../../../ts-out/window.js"); - const {exportPort} = require("./transports/websocket.js"); - createInviteWindow(); - const win = inviteWindow; - //doesnt work - win.loadURL("https://discord.com/invite/" + code); - win.show(); + const {createInviteWindow} = require("../../../ts-out/window.js"); + createInviteWindow(code); }); } run(); diff --git a/src/socket.ts b/src/socket.ts deleted file mode 100644 index 4b6565b..0000000 --- a/src/socket.ts +++ /dev/null @@ -1,186 +0,0 @@ -import type {Server, WebSocket} from "ws"; -import {inviteWindow, createInviteWindow} from "./window"; -/* - MIT License - - Copyright (c) 2020-2022 Dawid Papiewski "SpacingBat3" - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */ -async function wsLog(message: string, ...args: unknown[]) { - console.log("[WebSocket] " + message, ...args); -} - -/** Generates an inclusive range (as `Array`) from `start` to `end`. */ -function range(start: number, end: number) { - return Array.from({length: end - start + 1}, (_v, k) => start + k); -} - -interface InviteResponse { - /** Response type/command. */ - cmd: "INVITE_BROWSER"; - /** Response arguments. */ - args: { - /** An invitation code. */ - code: string; - }; - /** Nonce indentifying the communication. */ - nonce: string; -} - -function isInviteResponse(data: unknown): data is InviteResponse { - if (!(data instanceof Object)) return false; - if ((data as Partial)?.cmd !== "INVITE_BROWSER") return false; - if (typeof (data as Partial)?.args?.code !== "string") return false; - if (typeof (data as Partial)?.nonce !== "string") return false; - return true; -} - -const messages = { - /** - * A fake, hard-coded Discord command to spoof the presence of - * official Discord client (which makes browser to actually start a - * communication with the ArmCord). - */ - handShake: { - /** Message command. */ - cmd: "DISPATCH", - /** Message data. */ - data: { - /** Message scheme version. */ - v: 1, - /** Client properties. */ - config: { - /** Discord CDN host (hard-coded for `dicscord.com` instance). */ - cdn_host: "cdn.discordapp.com", - /** API endpoint (hard-coded for `dicscord.com` instance). */ - api_endpoint: "//discord.com/api", - /** Client type. Can be (probably) `production` or `canary`. */ - environment: "production" - } - }, - evt: "READY", - nonce: null - } -}; - -/** - * Tries to reserve the server at given port. - * - * @returns `Promise`, which always resolves (either to `Server` on - * success or `null` on failure). - */ -async function getServer(port: number) { - const {WebSocketServer} = await import("ws"); - return new Promise | null>((resolve) => { - const wss = new WebSocketServer({host: "127.0.0.1", port}); - wss.once("listening", () => resolve(wss)); - wss.once("error", () => resolve(null)); - }); -} - -/** - * Tries to start a WebSocket server at given port range. If it suceed, it will - * listen to the browser requests which are meant to be sent to official - * Discord client. - * - * Currently it supports only the invitation link requests. - * - */ -export default async function startServer() { - function isJsonSyntaxCorrect(string: string) { - try { - JSON.parse(string); - } catch { - return false; - } - return true; - } - /** Known Discord instances, including the official ones. */ - const knownInstancesList = [ - ["Discord", new URL("https://discord.com/app")], - ["Discord Canary", new URL("https://canary.discord.com/app")], - ["Discord PTB", new URL("https://ptb.discord.com/app")], - ["Fosscord", new URL("https://dev.fosscord.com/app")] - ] as const; - - let wss = null, - wsPort = 6463; - for (const port of range(6463, 6472)) { - wss = await getServer(port); - if (wss !== null) { - void wsLog("ArmCord is listening at " + port.toString()); - wsPort = port; - break; - } - } - if (wss === null) return; - let lock = false; - wss.on("connection", (wss, request) => { - const origin = request.headers.origin ?? "https://discord.com"; - let known = false; - for (const instance of knownInstancesList) { - if (instance[1].origin === origin) known = true; - } - if (!known) return; - wss.send(JSON.stringify(messages.handShake)); - wss.once("message", (data, isBinary) => { - if (lock) return; - lock = true; - let parsedData: unknown = data; - if (!isBinary) parsedData = data.toString(); - if (isJsonSyntaxCorrect(parsedData as string)) parsedData = JSON.parse(parsedData as string); - if (isInviteResponse(parsedData)) { - // Replies to browser, so it finds the communication successful. - wss.send( - JSON.stringify({ - cmd: parsedData.cmd, - data: { - invite: null, - code: parsedData.args.code - }, - evt: null, - nonce: parsedData.nonce - }) - ); - createInviteWindow(); - const child = inviteWindow; - if (child === undefined) return; - void child.loadURL(origin + "/invite/" + parsedData.args.code); - child.webContents.once("did-finish-load", () => { - child.show(); - }); - child.webContents.once("will-navigate", () => { - lock = false; - child.close(); - }); - child.on("close", (e) => { - lock = false; - }); - // Blocks requests to ArmCord's WS, to prevent loops. - child.webContents.session.webRequest.onBeforeRequest( - { - urls: ["ws://127.0.0.1:" + wsPort.toString() + "/*"] - }, - (_details, callback) => callback({cancel: true}) - ); - } - }); - }); -} diff --git a/src/tray.ts b/src/tray.ts index 22fa797..464697c 100644 --- a/src/tray.ts +++ b/src/tray.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; import {app, Menu, Tray, nativeImage} from "electron"; -import {mainWindow} from "./window"; +import {createInviteWindow, mainWindow} from "./window"; import {getConfig, getConfigLocation, setWindowState, getDisplayVersion} from "./utils"; import * as path from "path"; import {createSettingsWindow} from "./settings/main"; @@ -113,8 +113,7 @@ app.whenReady().then(async () => { { label: "Support Discord Server", click: function () { - mainWindow.show(); - mainWindow.loadURL("https://discord.gg/TnhxcqynZ2"); + createInviteWindow("TnhxcqynZ2"); } }, { diff --git a/src/window.ts b/src/window.ts index 13056ab..372c8ad 100644 --- a/src/window.ts +++ b/src/window.ts @@ -17,7 +17,6 @@ import { import {registerIpc} from "./ipc"; import {setMenu} from "./menu"; import * as fs from "fs"; -import startServer from "./socket"; import contextMenu from "electron-context-menu"; import os from "os"; import {tray} from "./tray"; @@ -292,7 +291,7 @@ export function createTransparentWindow() { }); doAfterDefiningTheWindow(); } -export function createInviteWindow() { +export function createInviteWindow(code: string) { inviteWindow = new BrowserWindow({ width: 800, height: 600, @@ -306,5 +305,16 @@ export function createInviteWindow() { spellcheck: true } }); - inviteWindow.hide(); + var formInviteURL = `https://discord.com/invite/${code}`; + inviteWindow.webContents.session.webRequest.onBeforeRequest((details, callback) => { + if (details.url.includes("ws://")) return callback({cancel: true}); + return callback({}); + }); + inviteWindow.loadURL(formInviteURL); + inviteWindow.webContents.once("did-finish-load", () => { + inviteWindow.show(); + inviteWindow.webContents.once("will-navigate", () => { + inviteWindow.close(); + }); + }); }