add files

This commit is contained in:
uimaxbai 2026-04-18 13:00:33 +01:00
parent 6d79dafadb
commit 1dd7e97f94
15 changed files with 229 additions and 33 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

4
extension/content.js Normal file
View file

@ -0,0 +1,4 @@
const s = document.createElement('script');
s.src = chrome.runtime.getURL('inject.js');
(document.head || document.documentElement).appendChild(s);
s.remove();

BIN
extension/icons/128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
extension/icons/16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
extension/icons/48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

1
extension/inject.js Normal file
View file

@ -0,0 +1 @@
window.__tidalOriginExtension = true;

47
extension/manifest.json Normal file
View file

@ -0,0 +1,47 @@
{
"manifest_version": 3,
"name": "Monochrome Tidal Origin",
"version": "1.0.0",
"description": "Adds Origin: https://listen.tidal.com to Tidal CDN requests so audio plays without a proxy",
"permissions": ["declarativeNetRequest", "scripting", "declarativeNetRequestWithHostAccess"],
"host_permissions": [
"*://*.tidal.com/*",
"*://monochrome.tf/*",
"*://monochrome.samidy.com/*",
"*://lossless.wtf/*",
"*://localhost:*/*"
],
"declarative_net_request": {
"rule_resources": [
{
"id": "rules",
"enabled": true,
"path": "rules.json"
}
]
},
"content_scripts": [
{
"matches": [
"*://monochrome.samidy.com/*",
"*://monochrome.tf/*",
"*://lossless.wtf/*",
"*://localhost:*/*"
],
"js": ["content.js"],
"run_at": "document_start",
"all_frames": true
}
],
"icons": {
"16": "icons/16.png",
"48": "icons/48.png",
"128": "icons/128.png"
},
"web_accessible_resources": [
{
"resources": ["inject.js"],
"matches": ["*://monochrome.samidy.com/*", "*://monochrome.tf/*", "*://lossless.wtf/*", "*://localhost:*/*"]
}
]
}

55
extension/rules.json Normal file
View file

@ -0,0 +1,55 @@
[
{
"id": 1,
"priority": 1,
"action": {
"type": "modifyHeaders",
"responseHeaders": [
{
"header": "Access-Control-Allow-Origin",
"operation": "set",
"value": "*"
},
{
"header": "Access-Control-Allow-Methods",
"operation": "set",
"value": "GET, POST, PUT, DELETE, PATCH, OPTIONS"
},
{
"header": "Access-Control-Allow-Headers",
"operation": "set",
"value": "*"
}
]
},
"condition": {
"urlFilter": "||tidal.com*",
"initiatorDomains": ["monochrome.tf", "monochrome.samidy.com"],
"resourceTypes": ["xmlhttprequest", "media"]
}
},
{
"id": 2,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "Origin",
"operation": "set",
"value": "https://listen.tidal.com"
},
{
"header": "Referer",
"operation": "set",
"value": "https://listen.tidal.com/"
}
]
},
"condition": {
"urlFilter": "||tidal.com*",
"initiatorDomains": ["monochrome.tf", "monochrome.samidy.com"],
"resourceTypes": ["xmlhttprequest", "media"]
}
}
]

65
functions/proxy-audio.js Normal file
View file

@ -0,0 +1,65 @@
export async function onRequest(context) {
const { request } = context;
const url = new URL(request.url);
const targetUrl = url.searchParams.get('url');
if (!targetUrl) {
return new Response('Missing url parameter', { status: 400 });
}
try {
const cacheUrl = new URL(request.url);
try {
const tidalUrl = new URL(targetUrl);
cacheUrl.searchParams.set('cache_key', tidalUrl.pathname);
} catch (e) {}
const cacheKey = new Request(cacheUrl.toString(), request);
const cache = caches.default;
let response = await cache.match(cacheKey);
if (!response) {
const headers = new Headers(request.headers);
headers.delete('host');
headers.delete('referer');
headers.set('Origin', 'https://listen.tidal.com');
headers.set(
'User-Agent',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
);
response = await fetch(targetUrl, {
method: request.method,
headers: headers,
redirect: 'follow',
cf: {
cacheTtl: 2592000,
cacheEverything: true,
},
});
if (request.method === 'GET' && response.ok) {
const cacheResponse = new Response(response.body, response);
cacheResponse.headers.set('Access-Control-Allow-Origin', '*');
cacheResponse.headers.set('Cache-Control', 'public, max-age=2592000');
cacheResponse.headers.delete('Set-Cookie');
context.waitUntil(cache.put(cacheKey, cacheResponse.clone()));
response = cacheResponse;
}
} else {
}
const newResponse = new Response(response.body, response);
newResponse.headers.set('Access-Control-Allow-Origin', '*');
newResponse.headers.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
newResponse.headers.set('Access-Control-Expose-Headers', '*');
newResponse.headers.delete('content-security-policy');
newResponse.headers.delete('x-frame-options');
return newResponse;
} catch (error) {
return new Response('Proxy Error: ' + error.message, { status: 500 });
}
}

53
js/HiFi.ts.rej Normal file
View file

@ -0,0 +1,53 @@
@@ -2097,45 +2185,46 @@
offset,
countryCode: this.#countryCode,
},
signal,
openApiToken
);
- return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data: fallback });
+ return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data: parseOpenApiSearch(fallback) });
}
const openApiToken = await this.getOpenApiToken(signal);
+ const includeAll = 'albums.artists,albums.coverArt,artists.profileArt,playlists.coverArt,tracks.albums,tracks.albums.coverArt,tracks.artists,videos.artists,videos.image';
const mapping: Array<[string | undefined, string, Params]> = [
[
q,
`https://openapi.tidal.com/v2/searchResults/${encodeURIComponent(q || '')}`,
{
limit,
offset,
- include: 'albums,artists,tracks,videos,playlists,topHits',
+ include: includeAll,
countryCode: this.#countryCode,
},
],
[
s,
`https://openapi.tidal.com/v2/searchResults/${encodeURIComponent(s || '')}`,
- { limit, offset, include: 'tracks', countryCode: this.#countryCode },
+ { limit, offset, include: includeAll, countryCode: this.#countryCode },
],
[
a,
`https://openapi.tidal.com/v2/searchResults/${encodeURIComponent(a || '')}`,
- { limit, offset, include: 'artists,tracks', countryCode: this.#countryCode },
+ { limit, offset, include: includeAll, countryCode: this.#countryCode },
],
[
al,
`https://openapi.tidal.com/v2/searchResults/${encodeURIComponent(al || '')}`,
- { limit, offset, include: 'albums', countryCode: this.#countryCode },
+ { limit, offset, include: includeAll, countryCode: this.#countryCode },
],
[
v,
`https://openapi.tidal.com/v2/searchResults/${encodeURIComponent(v || '')}`,
- { limit, offset, include: 'videos', countryCode: this.#countryCode },
+ { limit, offset, include: includeAll, countryCode: this.#countryCode },
],
[
p,
`https://openapi.tidal.com/v2/searchResults/${encodeURIComponent(p || '')}`,

4
js/proxy-utils.js Normal file
View file

@ -0,0 +1,4 @@
export const getProxyUrl = (url) => {
if (window.__tidalOriginExtension) return url;
return `/proxy-audio?url=${encodeURIComponent(url)}`;
};

View file

@ -1,33 +0,0 @@
import { HiFiClient } from './js/HiFi.ts';
import { LosslessAPI } from './js/api.js';
// mock out modules to make LosslessAPI load in bun
import { mock } from 'bun:test';
mock.module('./js/icons.ts', () => ({}));
mock.module('./js/settings.js', () => ({
devModeSettings: { isEnabled: () => false },
syncManager: {},
musicProviderSettings: {},
audioSettings: {},
apiSettings: {},
}));
globalThis.localStorage = { getItem: () => null, setItem: () => {}, removeItem: () => {} };
globalThis.window = { matchMedia: () => ({ matches: false }) };
async function test() {
await HiFiClient.initialize();
const api = new LosslessAPI({ getInstances: () => [] });
// mock cache
api.cache = { get: () => null, set: () => {} };
api.fetchWithRetry = async function (relativePath, options) {
console.log('fetchWithRetry called:', relativePath);
return HiFiClient.instance.query(relativePath);
};
const res = await api.search('coldplay');
console.log('Returned tracks:', res.tracks?.items?.length);
}
test().catch(console.error);