diff --git a/package.json b/package.json index cba61a0e98..6a1f7c3186 100644 --- a/package.json +++ b/package.json @@ -300,6 +300,7 @@ "solid-js": "1.9.7", "solid-styled-components": "0.28.5", "solid-transition-group": "0.3.0", + "ua-parser-js": "2.0.3", "ts-morph": "26.0.0", "vudio": "2.1.1", "x11": "2.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e490fd1f7..82e3ee9416 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -219,6 +219,9 @@ importers: ts-morph: specifier: 26.0.0 version: 26.0.0 + ua-parser-js: + specifier: 2.0.3 + version: 2.0.3 vudio: specifier: 2.1.1 version: 2.1.1(patch_hash=0e06c2ed11c02bdc490c209fa80070e98517c2735c641f5738b6e15d7dc1959d) @@ -1263,6 +1266,9 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node-fetch@2.6.12': + resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} + '@types/node@16.9.1': resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==} @@ -2045,6 +2051,9 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + detect-europe-js@0.1.2: + resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==} + detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -2972,6 +2981,9 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} + is-standalone-pwa@0.1.1: + resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -4336,6 +4348,13 @@ packages: engines: {node: '>=14.17'} hasBin: true + ua-is-frozen@0.1.2: + resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==} + + ua-parser-js@2.0.3: + resolution: {integrity: sha512-LZyXZdNttONW8LjzEH3Z8+6TE7RfrEiJqDKyh0R11p/kxvrV2o9DrT2FGZO+KVNs3k+drcIQ6C3En6wLnzJGpw==} + hasBin: true + uint8array-extras@1.4.0: resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} engines: {node: '>=18'} @@ -5667,6 +5686,11 @@ snapshots: '@types/ms@2.1.0': {} + '@types/node-fetch@2.6.12': + dependencies: + '@types/node': 22.13.5 + form-data: 4.0.2 + '@types/node@16.9.1': {} '@types/node@22.13.5': @@ -6572,6 +6596,8 @@ snapshots: delayed-stream@1.0.0: {} + detect-europe-js@0.1.2: {} + detect-libc@2.0.3: {} detect-node@2.1.0: @@ -7729,6 +7755,8 @@ snapshots: dependencies: call-bound: 1.0.3 + is-standalone-pwa@0.1.1: {} + is-stream@2.0.1: {} is-string@1.1.1: @@ -9164,6 +9192,16 @@ snapshots: typescript@5.8.3: {} + ua-is-frozen@0.1.2: {} + + ua-parser-js@2.0.3: + dependencies: + '@types/node-fetch': 2.6.12 + detect-europe-js: 0.1.2 + is-standalone-pwa: 0.1.1 + node-fetch: 3.3.2 + ua-is-frozen: 0.1.2 + uint8array-extras@1.4.0: {} unbox-primitive@1.1.0: diff --git a/src/config/defaults.ts b/src/config/defaults.ts index dcd56128a7..91d08e6dfd 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -32,6 +32,8 @@ export interface DefaultConfig { proxy: string; startingPage: string; overrideUserAgent: boolean; + overrideUserAgentValue: string; + customUserAgent: string[]; usePodcastParticipantAsArtist: boolean; themes: string[]; }; @@ -67,6 +69,8 @@ const defaultConfig: DefaultConfig = { proxy: '', startingPage: '', overrideUserAgent: false, + overrideUserAgentValue: '', + customUserAgent: [], usePodcastParticipantAsArtist: false, themes: [], }, diff --git a/src/i18n/resources/en.json b/src/i18n/resources/en.json index 41e1242eff..655e968e7d 100644 --- a/src/i18n/resources/en.json +++ b/src/i18n/resources/en.json @@ -100,6 +100,8 @@ "disable-hardware-acceleration": "Disable hardware acceleration", "edit-config-json": "Edit config.json", "override-user-agent": "Override User-Agent", + "add-override-user-agent": "Add user agent", + "reset-override-user-agent": "Reset user agent", "restart-on-config-changes": "Restart on config changes", "set-proxy": { "label": "Set proxy", diff --git a/src/menu.ts b/src/menu.ts index 0f560e71cf..2be16aedd2 100644 --- a/src/menu.ts +++ b/src/menu.ts @@ -7,7 +7,9 @@ import { Menu, MenuItem, shell, + session, } from 'electron'; +import { UAParser } from 'ua-parser-js'; import prompt from 'custom-electron-prompt'; import { satisfies } from 'semver'; @@ -483,11 +485,98 @@ export const mainMenuTemplate = async ( label: t( 'main.menu.options.submenu.advanced-options.submenu.override-user-agent', ), - type: 'checkbox', - checked: config.get('options.overrideUserAgent'), - click(item: MenuItem) { - config.setMenuOption('options.overrideUserAgent', item.checked); - }, + submenu: [ + { + label: t( + 'main.menu.options.submenu.advanced-options.submenu.reset-override-user-agent', + ), + type: 'normal' as const, + click() { + // Clear the override value from config + config.set('options.overrideUserAgentValue', ''); + + // Remove the onBeforeSendHeaders listener + session.defaultSession.webRequest.onBeforeSendHeaders( + { urls: ['*://*/*'] }, + () => {}, + ); + + // Reset to original user agent + const originalUserAgent = win.webContents.userAgent; + win.webContents.userAgent = originalUserAgent; + app.userAgentFallback = originalUserAgent; + }, + } satisfies Electron.MenuItemConstructorOptions, + { + label: t( + 'main.menu.options.submenu.advanced-options.submenu.add-override-user-agent', + ), + type: 'normal', + click() { + prompt( + { + title: t( + 'main.menu.options.submenu.advanced-options.submenu.add-override-user-agent', + ), + label: t( + 'main.menu.options.submenu.advanced-options.submenu.add-override-user-agent', + ), + value: '', + type: 'input', + width: 450, + height: 150, + alwaysOnTop: true, + ...promptOptions, + }, + win, + ) + .then((result) => { + if (result === null) { + return; + } + const existingConfig = + config.get('options.customUserAgent') || []; + config.set('options.customUserAgent', [ + ...existingConfig, + result, + ]); + refreshMenu(win); + }) + .catch(console.error); + }, + }, + ].concat( + config.get('options.customUserAgent').map((userAgent) => { + const parser = new UAParser(userAgent); + const result = parser.getResult(); + return { + label: `${result.os.name} ${result.browser.name} ${result.browser.version}`, + type: 'radio', + checked: + config.get('options.overrideUserAgentValue') === + userAgent, + click() { + session.defaultSession.webRequest.onBeforeSendHeaders( + (details, callback) => { + config.set( + 'options.overrideUserAgentValue', + userAgent, + ); + const overrideUA = config.get( + 'options.overrideUserAgentValue', + ); + if (overrideUA) { + details.requestHeaders['User-Agent'] = overrideUA; + } + callback({ + requestHeaders: details.requestHeaders, + }); + }, + ); + }, + } satisfies Electron.MenuItemConstructorOptions; + }), + ), }, { label: t(