├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── assets
├── banner.svg
└── neptune-screenshot.png
├── flake.lock
├── flake.nix
├── injector
├── index.js
├── package.json
├── preload.js
└── redux-devtools
│ ├── 079db4a1c8da8ec06700.woff2
│ ├── 56f3f8ac2e0a51c02e1c.woff2
│ ├── background.bundle.js
│ ├── background.bundle.js.LICENSE.txt
│ ├── c60b44947671d757833d.woff2
│ ├── content.bundle.js
│ ├── devpanel.bundle.js
│ ├── devpanel.bundle.js.LICENSE.txt
│ ├── devpanel.html
│ ├── devtools.bundle.js
│ ├── devtools.html
│ ├── e46177b21b27cd6643c5.woff2
│ ├── ef865b56e54f6a46f73f.woff2
│ ├── img
│ ├── loading.svg
│ └── logo
│ │ ├── 128x128.png
│ │ ├── 16x16.png
│ │ ├── 38x38.png
│ │ ├── 48x48.png
│ │ ├── error.png
│ │ ├── gray.png
│ │ └── scalable.png
│ ├── manifest.json
│ ├── options.bundle.js
│ ├── options.bundle.js.LICENSE.txt
│ ├── options.html
│ ├── page.bundle.js
│ ├── pagewrap.bundle.js
│ ├── remote.bundle.js
│ ├── remote.bundle.js.LICENSE.txt
│ ├── remote.html
│ ├── window.bundle.js
│ ├── window.bundle.js.LICENSE.txt
│ └── window.html
├── package.json
├── pnpm-lock.yaml
├── rollup.config.js
├── src
├── api
│ ├── hookContextMenu.js
│ ├── intercept.js
│ ├── observe.js
│ ├── plugins.js
│ ├── registerRoute.js
│ ├── registerTab.js
│ ├── showModal.js
│ ├── themes.js
│ └── utils.js
├── handleExfiltrations.js
├── index.js
├── styles.js
├── ui
│ ├── components.js
│ ├── pluginsTab.js
│ ├── settings.js
│ └── themesTab.js
└── windowObject.js
└── types
├── LICENSE
├── api
├── hookContextMenu.d.ts
├── intercept.d.ts
├── observe.d.ts
├── plugins.d.ts
├── registerRoute.d.ts
├── registerTab.d.ts
├── showModal.d.ts
└── utils.d.ts
├── index.d.ts
├── package.json
├── pnpm-lock.yaml
├── tidal
└── index.d.ts
├── tsconfig.base.json
└── ui
└── components.d.ts
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build and push
2 | on:
3 | push:
4 | branches: [master]
5 | paths:
6 | - "src/**"
7 | workflow_dispatch:
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v3
15 | - uses: actions/checkout@v3
16 | with:
17 | repository: "uwu/neptune-builds"
18 | path: "builds"
19 | token: ${{ secrets.LINK_TOKEN }}
20 | - uses: actions/setup-node@v3
21 | with:
22 | node-version: 20
23 |
24 | - name: Install dependencies
25 | run: |
26 | npm i -g pnpm
27 | pnpm i
28 |
29 | - name: Build
30 | run: npm run build
31 |
32 | - name: Push builds
33 | run: |
34 | rm $GITHUB_WORKSPACE/builds/* || true
35 | cp -r dist/* $GITHUB_WORKSPACE/builds || true
36 | cd $GITHUB_WORKSPACE/builds
37 | git config --local user.email "actions@github.com"
38 | git config --local user.name "GitHub Actions"
39 | git add .
40 | git commit -m "Build $GITHUB_SHA" || exit 0
41 | git push
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/macos,node
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,node
3 |
4 | ### macOS ###
5 | # General
6 | .DS_Store
7 | .AppleDouble
8 | .LSOverride
9 |
10 | # Icon must end with two \r
11 | Icon
12 |
13 |
14 | # Thumbnails
15 | ._*
16 |
17 | # Files that might appear in the root of a volume
18 | .DocumentRevisions-V100
19 | .fseventsd
20 | .Spotlight-V100
21 | .TemporaryItems
22 | .Trashes
23 | .VolumeIcon.icns
24 | .com.apple.timemachine.donotpresent
25 |
26 | # Directories potentially created on remote AFP share
27 | .AppleDB
28 | .AppleDesktop
29 | Network Trash Folder
30 | Temporary Items
31 | .apdisk
32 |
33 | ### macOS Patch ###
34 | # iCloud generated files
35 | *.icloud
36 |
37 | ### Node ###
38 | # Logs
39 | logs
40 | *.log
41 | npm-debug.log*
42 | yarn-debug.log*
43 | yarn-error.log*
44 | lerna-debug.log*
45 | .pnpm-debug.log*
46 |
47 | # Diagnostic reports (https://nodejs.org/api/report.html)
48 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
49 |
50 | # Runtime data
51 | pids
52 | *.pid
53 | *.seed
54 | *.pid.lock
55 |
56 | # Directory for instrumented libs generated by jscoverage/JSCover
57 | lib-cov
58 |
59 | # Coverage directory used by tools like istanbul
60 | coverage
61 | *.lcov
62 |
63 | # nyc test coverage
64 | .nyc_output
65 |
66 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
67 | .grunt
68 |
69 | # Bower dependency directory (https://bower.io/)
70 | bower_components
71 |
72 | # node-waf configuration
73 | .lock-wscript
74 |
75 | # Compiled binary addons (https://nodejs.org/api/addons.html)
76 | build/Release
77 |
78 | # Dependency directories
79 | node_modules/
80 | jspm_packages/
81 |
82 | # Snowpack dependency directory (https://snowpack.dev/)
83 | web_modules/
84 |
85 | # TypeScript cache
86 | *.tsbuildinfo
87 |
88 | # Optional npm cache directory
89 | .npm
90 |
91 | # Optional eslint cache
92 | .eslintcache
93 |
94 | # Optional stylelint cache
95 | .stylelintcache
96 |
97 | # Microbundle cache
98 | .rpt2_cache/
99 | .rts2_cache_cjs/
100 | .rts2_cache_es/
101 | .rts2_cache_umd/
102 |
103 | # Optional REPL history
104 | .node_repl_history
105 |
106 | # Output of 'npm pack'
107 | *.tgz
108 |
109 | # Yarn Integrity file
110 | .yarn-integrity
111 |
112 | # dotenv environment variable files
113 | .env
114 | .env.development.local
115 | .env.test.local
116 | .env.production.local
117 | .env.local
118 |
119 | # parcel-bundler cache (https://parceljs.org/)
120 | .cache
121 | .parcel-cache
122 |
123 | # Next.js build output
124 | .next
125 | out
126 |
127 | # Nuxt.js build / generate output
128 | .nuxt
129 | dist
130 |
131 | # Gatsby files
132 | .cache/
133 | # Comment in the public line in if your project uses Gatsby and not Next.js
134 | # https://nextjs.org/blog/next-9-1#public-directory-support
135 | # public
136 |
137 | # vuepress build output
138 | .vuepress/dist
139 |
140 | # vuepress v2.x temp and cache directory
141 | .temp
142 |
143 | # Docusaurus cache and generated files
144 | .docusaurus
145 |
146 | # Serverless directories
147 | .serverless/
148 |
149 | # FuseBox cache
150 | .fusebox/
151 |
152 | # DynamoDB Local files
153 | .dynamodb/
154 |
155 | # TernJS port file
156 | .tern-port
157 |
158 | # Stores VSCode versions used for testing VSCode extensions
159 | .vscode-test
160 |
161 | # yarn v2
162 | .yarn/cache
163 | .yarn/unplugged
164 | .yarn/build-state.yml
165 | .yarn/install-state.gz
166 | .pnp.*
167 |
168 | ### Node Patch ###
169 | # Serverless Webpack directories
170 | .webpack/
171 |
172 | # Optional stylelint cache
173 |
174 | # SvelteKit build / generate output
175 | .svelte-kit
176 | unreleased_code.js
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": false,
7 | "quoteProps": "as-needed",
8 | "jsxSingleQuote": false,
9 | "trailingComma": "all",
10 | "bracketSpacing": true,
11 | "bracketSameLine": true,
12 | "arrowParens": "always",
13 | "proseWrap": "always"
14 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Microsoft Public License (Ms-PL)
2 |
3 | This license governs use of the accompanying software. If you use the
4 | software, you accept this license. If you do not accept the license,
5 | do not use the software.
6 |
7 | 1. Definitions
8 |
9 | The terms "reproduce," "reproduction," "derivative works," and
10 | "distribution" have the same meaning here as under U.S. copyright
11 | law.
12 |
13 | A "contribution" is the original software, or any additions or
14 | changes to the software.
15 |
16 | A "contributor" is any person that distributes its contribution
17 | under this license.
18 |
19 | "Licensed patents" are a contributor's patent claims that read
20 | directly on its contribution.
21 |
22 | 2. Grant of Rights
23 |
24 | (A) Copyright Grant- Subject to the terms of this license,
25 | including the license conditions and limitations in section 3,
26 | each contributor grants you a non-exclusive, worldwide,
27 | royalty-free copyright license to reproduce its contribution,
28 | prepare derivative works of its contribution, and distribute its
29 | contribution or any derivative works that you create.
30 |
31 | (B) Patent Grant- Subject to the terms of this license, including
32 | the license conditions and limitations in section 3, each
33 | contributor grants you a non-exclusive, worldwide, royalty-free
34 | license under its licensed patents to make, have made, use, sell,
35 | offer for sale, import, and/or otherwise dispose of its
36 | contribution in the software or derivative works of the
37 | contribution in the software.
38 |
39 | 3. Conditions and Limitations
40 |
41 | (A) No Trademark License- This license does not grant you rights
42 | to use any contributors' name, logo, or trademarks.
43 |
44 | (B) If you bring a patent claim against any contributor over
45 | patents that you claim are infringed by the software, your patent
46 | license from such contributor to the software ends automatically.
47 |
48 | (C) If you distribute any portion of the software, you must retain
49 | all copyright, patent, trademark, and attribution notices that are
50 | present in the software.
51 |
52 | (D) If you distribute any portion of the software in source code
53 | form, you may do so only under this license by including a
54 | complete copy of this license with your distribution. If you
55 | distribute any portion of the software in compiled or object code
56 | form, you may only do so under a license that complies with this
57 | license.
58 |
59 | (E) The software is licensed "as-is." You bear the risk of using
60 | it. The contributors give no express warranties, guarantees, or
61 | conditions. You may have additional consumer rights under your
62 | local laws which this license cannot change. To the extent
63 | permitted under your local laws, the contributors exclude the
64 | implied warranties of merchantability, fitness for a particular
65 | purpose and non-infringement.
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## what is neptune?
4 | neptune was an experimental client mod for TIDAL that provides a simple plugin and theme system.
5 |
6 | i have no interest in maintaining software that nobody wants to contribute to. if you want to make your own thing, i highly prefer that you commit to it.
7 |
8 | ## screenshot(s)
9 | 
10 |
11 | ## how can i install neptune?
12 | you can download the neptune installer [here](https://github.com/uwu/neptune-installer/releases).
13 |
14 | ### NixOS
15 |
16 | > [!WARNING]
17 | > TIDAL-HIFI right now is colliding with neptune when trying to login
18 | >
19 | > create a nix-shell with tidal-hifi and login once, after that you can use the neptune package
20 |
21 | you install this package as an overlay
22 |
23 | add as an input in your flakes:
24 | ```nix
25 | inputs = {
26 | neptune = {
27 | url = "github.com:uwu/neptune";
28 | inputs.nixpkgs.follows = "nixpkgs";
29 | }
30 | };
31 | ```
32 |
33 | configure your package system to use this overlay:
34 | ```nix
35 | nixpkgs.overlays = [ inputs.neptune.overlays.default ];
36 | ```
37 |
38 | and then just add neptune as a package:
39 | ```nix
40 | enivronment.systemPackages = [ pkgs.neptune ];
41 | ```
42 |
43 | After that you can find TIDAL-HIFI as program in your system
44 |
45 | ## developing plugins for neptune
46 | neptune exfiltrates every single action one can do in TIDAL into an easily accessible API found on `window.neptune.actions`.
47 |
48 | TIDAL is built on [Redux](https://redux.js.org) and neptune's actions are simply exfiltrated Redux actions, which are explained in [this document](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#actions) on Redux's website.
49 |
50 | neptune includes full type definitions for all of TIDAL's actions.
51 |
52 | To get the global Redux store's state, you can use `window.neptune.store.getState()`. The return value of `getState()` will change as a direct result of actions.
53 |
54 | To intercept and subscribe to actions, you can use `window.neptune.intercept("category/ACTION_NAME", ([payload]) => {})`, with the first argument being the name (or an array of names) of the action(s) to subscribe to, and the second argument being a function that gets called upon that action being ran. If you return `true` the action will automatically be cancelled.
55 |
56 | A template for making neptune plugins is available [here](https://github.com/uwu/neptune-template).
57 |
--------------------------------------------------------------------------------
/assets/neptune-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uwu/neptune/724cd20dcf734d34661e9453f696be128508e7e1/assets/neptune-screenshot.png
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "nixpkgs": {
4 | "locked": {
5 | "lastModified": 1733376361,
6 | "narHash": "sha256-aLJxoTDDSqB+/3orsulE6/qdlX6MzDLIITLZqdgMpqo=",
7 | "owner": "nixos",
8 | "repo": "nixpkgs",
9 | "rev": "929116e316068c7318c54eb4d827f7d9756d5e9c",
10 | "type": "github"
11 | },
12 | "original": {
13 | "owner": "nixos",
14 | "ref": "nixpkgs-unstable",
15 | "repo": "nixpkgs",
16 | "type": "github"
17 | }
18 | },
19 | "root": {
20 | "inputs": {
21 | "nixpkgs": "nixpkgs"
22 | }
23 | }
24 | },
25 | "root": "root",
26 | "version": 7
27 | }
28 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs = {
3 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
4 | };
5 |
6 | outputs = inputs:
7 | let
8 | neptuneOverlay = final: prev:
9 | let
10 | neptune-src = prev.fetchzip {
11 | url = "https://github.com/uwu/neptune/archive/548f93b.zip";
12 | sha256 = "sha256-oI/bRjL6zjsaA8p8QTeJEB5k+SXkJqSJ/hEAltDenok=";
13 | };
14 | in
15 | {
16 | # Use the already existing package tidal-hifi and inject neptune in it
17 | tidal-hifi = prev.tidal-hifi.overrideAttrs (old: {
18 |
19 | # Patch neptune into tidal-hifi
20 | # Needing to override the full thing to get everything from the install phase
21 | installPhase = ''
22 | runHook preInstall
23 |
24 | mkdir -p "$out/bin"
25 | cp -R "opt" "$out"
26 | cp -R "usr/share" "$out/share"
27 | chmod -R g-w "$out"
28 |
29 | cp -r ${neptune-src}/injector/ $out/opt/tidal-hifi/resources/app/
30 | mv $out/opt/tidal-hifi/resources/app.asar $out/opt/tidal-hifi/resources/original.asar
31 |
32 | runHook postInstall
33 |
34 | '';
35 | });
36 |
37 | # declare a new package named neptune that uses the new tidal-hifi
38 | neptune = final.tidal-hifi;
39 | };
40 |
41 | system = "x86_64-linux"; # This setting is based on my system, change if needed
42 |
43 | # The Module Configuration
44 | # Disclaimer: There is no module configuration yet, because I dont have a solution myself, to get access to the InnoDB
45 |
46 | in {
47 | # Overlay used by other flakes
48 | overlays.default = neptuneOverlay;
49 |
50 | # Testing the overlay/package
51 | devShells."${system}".default = let
52 | pkgs = import inputs.nixpkgs {
53 | inherit system;
54 | overlays = [ neptuneOverlay ];
55 | };
56 | in pkgs.mkShell {
57 | packages = [ pkgs.neptune ];
58 | shellHook = '' ${pkgs.neptune}/bin/tidal-hifi ''; # Activating the package/overlay
59 | };
60 | };
61 | }
62 |
--------------------------------------------------------------------------------
/injector/index.js:
--------------------------------------------------------------------------------
1 | const electron = require("electron");
2 | const path = require("path");
3 | const Module = require("module");
4 | const fs = require("fs");
5 | const https = require("https");
6 |
7 | const logger = new Proxy(console, {
8 | get: (target, key) =>
9 | function (...args) {
10 | return target[key].apply(console, ["[neptune]", ...args]);
11 | },
12 | });
13 |
14 | logger.log("Loading...");
15 |
16 | // #region Bundle
17 | const remoteUrl =
18 | process.env.NEPTUNE_BUNDLE_URL ||
19 | "https://raw.githubusercontent.com/uwu/neptune-builds/master/neptune.js";
20 | const localBundle = process.env.NEPTUNE_DIST_PATH;
21 |
22 | let fetchPromise; // only fetch once
23 |
24 | if (!localBundle)
25 | fetchPromise = new Promise((resolve, reject) => {
26 | const req = https.get(remoteUrl);
27 |
28 | req.on("response", (res) => {
29 | const chunks = [];
30 |
31 | res.on("data", (chunk) => chunks.push(chunk));
32 | res.on("end", () => {
33 | let data = Buffer.concat(chunks).toString("utf-8");
34 |
35 | if (!data.includes("//# sourceMappingURL="))
36 | data += `\n//# sourceMappingURL=${remoteUrl + ".map"}`;
37 |
38 | resolve(data);
39 | });
40 | });
41 |
42 | req.on("error", reject);
43 |
44 | req.end();
45 | });
46 |
47 | const getNeptuneBundle = () =>
48 | !localBundle
49 | ? fetchPromise
50 | : Promise.resolve(
51 | fs.readFileSync(path.join(localBundle, "neptune.js"), "utf8") +
52 | `\n//# sourceMappingURL=file:////${path.join(
53 | localBundle,
54 | "neptune.js.map"
55 | )}`
56 | );
57 | // #endregion
58 |
59 | // #region IPC
60 | electron.ipcMain.on("NEPTUNE_ORIGINAL_PRELOAD", (event) => {
61 | event.returnValue = event.sender.originalPreload;
62 | });
63 |
64 | electron.ipcMain.handle("NEPTUNE_BUNDLE_FETCH", getNeptuneBundle);
65 | // #endregion
66 |
67 | // #region Redux Devtools
68 | electron.app.whenReady().then(() => {
69 | electron.session.defaultSession.loadExtension(
70 | path.join(process.resourcesPath, "app", "redux-devtools")
71 | );
72 | });
73 | // #endregion
74 |
75 | // #region CSP bypass
76 | electron.app.whenReady().then(() => {
77 | electron.protocol.handle("https", async (req) => {
78 | const url = new URL(req.url);
79 | if (url.pathname === "/" || url.pathname == "/index.html") {
80 | console.log(req.url);
81 | const res = await electron.net.fetch(req, { bypassCustomProtocolHandlers: true })
82 | let body = await res.text();
83 | body = body.replace(
84 | //g, `