diff --git a/modules/persistence/Settings.js b/modules/persistence/Settings.js index 867cc377..f2ef05eb 100644 --- a/modules/persistence/Settings.js +++ b/modules/persistence/Settings.js @@ -5,7 +5,7 @@ const fs = require("fs").promises; class Settings { constructor( paths, env, outputFormat, audioOutputFormat, downloadPath, - proxy, rateLimit, autoFillClipboard, noPlaylist, globalShortcut, spoofUserAgent, + proxy, rateLimit, autoFillClipboard, noPlaylist, globalShortcut, userAgent, validateCertificate, enableEncoding, taskList, nameFormat, nameFormatMode, sizeMode, splitMode, maxConcurrent, updateBinary, downloadType, updateApplication, cookiePath, statSend, downloadMetadata, downloadThumbnail, keepUnmerged, calculateTotalSize, theme @@ -20,7 +20,7 @@ class Settings { this.autoFillClipboard = autoFillClipboard == null ? true : autoFillClipboard; this.noPlaylist = noPlaylist == null ? false : noPlaylist; this.globalShortcut = globalShortcut == null ? true : globalShortcut; - this.spoofUserAgent = spoofUserAgent == null ? true : spoofUserAgent; + this.userAgent = userAgent == null ? "spoof" : userAgent; this.validateCertificate = validateCertificate == null ? false : validateCertificate; this.enableEncoding = enableEncoding == null ? false : enableEncoding; this.taskList = taskList == null ? true : taskList; @@ -57,7 +57,7 @@ class Settings { data.autoFillClipboard, data.noPlaylist, data.globalShortcut, - data.spoofUserAgent, + data.userAgent, data.validateCertificate, data.enableEncoding, data.taskList, @@ -94,7 +94,7 @@ class Settings { this.autoFillClipboard = settings.autoFillClipboard; this.noPlaylist = settings.noPlaylist; this.globalShortcut = settings.globalShortcut; - this.spoofUserAgent = settings.spoofUserAgent; + this.userAgent = settings.userAgent; this.validateCertificate = settings.validateCertificate; this.enableEncoding = settings.enableEncoding; this.taskList = settings.taskList; @@ -131,7 +131,7 @@ class Settings { autoFillClipboard: this.autoFillClipboard, noPlaylist: this.noPlaylist, globalShortcut: this.globalShortcut, - spoofUserAgent: this.spoofUserAgent, + userAgent: this.userAgent, validateCertificate: this.validateCertificate, enableEncoding: this.enableEncoding, taskList: this.taskList, @@ -156,7 +156,6 @@ class Settings { } save() { - console.log(this.serialize()); fs.writeFile(this.paths.settings, JSON.stringify(this.serialize()), "utf8").then(() => { console.log("Saved settings file.") }); diff --git a/modules/types/Query.js b/modules/types/Query.js index 2d56b17a..47aa1778 100644 --- a/modules/types/Query.js +++ b/modules/types/Query.js @@ -22,9 +22,12 @@ class Query { args.push("--no-cache-dir"); args.push("--ignore-config"); - if(this.environment.settings.spoofUserAgent) { + if(this.environment.settings.userAgent === "spoof") { args.push("--user-agent"); //Add random user agent to slow down user agent profiling args.push(new UserAgent({ deviceCategory: 'desktop' }).toString()); + } else if(this.environment.settings.userAgent === "empty") { + args.push("--user-agent"); + args.push("''"); //Add an empty user agent string to workaround VR video issues } if(this.environment.settings.proxy != null && this.environment.settings.proxy.length > 0) { diff --git a/renderer/renderer.html b/renderer/renderer.html index 258b969a..08ef8ddb 100644 --- a/renderer/renderer.html +++ b/renderer/renderer.html @@ -388,8 +388,12 @@

Advanced

- - + +
diff --git a/renderer/renderer.js b/renderer/renderer.js index 35937dc1..4ec8300a 100644 --- a/renderer/renderer.js +++ b/renderer/renderer.js @@ -978,7 +978,7 @@ async function getSettings() { const settings = await window.main.invoke("settingsAction", {action: "get"}); $('#updateBinary').prop('checked', settings.updateBinary); $('#updateApplication').prop('checked', settings.updateApplication); - $('#spoofUserAgent').prop('checked', settings.spoofUserAgent); + $('#userAgent').val(settings.userAgent); $('#validateCertificate').prop('checked', settings.validateCertificate); $('#enableEncoding').prop('checked', settings.enableEncoding); $('#taskList').prop('checked', settings.taskList); @@ -1014,7 +1014,7 @@ function sendSettings() { outputFormat: $('#outputFormat').val(), audioOutputFormat: $('#audioOutputFormat').val(), proxy: $('#proxySetting').val(), - spoofUserAgent: $('#spoofUserAgent').prop('checked'), + userAgent: $('#userAgent').val(), validateCertificate: $('#validateCertificate').prop('checked'), enableEncoding: $('#enableEncoding').prop('checked'), taskList: $('#taskList').prop('checked'), diff --git a/tests/Query.test.js b/tests/Query.test.js index 524f72bd..21113b0b 100644 --- a/tests/Query.test.js +++ b/tests/Query.test.js @@ -25,16 +25,26 @@ describe('ytdl Query', () => { it('adds a random user agent when this setting is enabled', () => { UserAgent.prototype.toString = jest.fn().mockReturnValue("agent"); const errorHandlerMock = jest.fn(); - const instance = instanceBuilder(true, null, errorHandlerMock, "python"); + const instance = instanceBuilder("spoof", null, errorHandlerMock, "python"); return instance.start("https://url.link", [], null).then(() => { expect(UserAgent.prototype.toString).toBeCalledTimes(1); expect(execa.mock.calls[0][1]).toContain("--user-agent"); expect(execa.mock.calls[0][1]).toContain("agent"); }); }); + it('adds an empty user agent when this setting is enabled', () => { + UserAgent.prototype.toString = jest.fn().mockReturnValue("agent"); + const errorHandlerMock = jest.fn(); + const instance = instanceBuilder("empty", null, errorHandlerMock, "python"); + return instance.start("https://url.link", [], null).then(() => { + expect(UserAgent.prototype.toString).toBeCalledTimes(0); + expect(execa.mock.calls[0][1]).toContain("--user-agent"); + expect(execa.mock.calls[0][1]).toContain("''"); + }); + }); it('adds the proxy when one is set', () => { const errorHandlerMock = jest.fn(); - const instance = instanceBuilder(false, "a/path/to/cookies.txt", errorHandlerMock, "python", "https://iama.proxy"); + const instance = instanceBuilder("default", "a/path/to/cookies.txt", errorHandlerMock, "python", "https://iama.proxy"); return instance.start("https://url.link", [], null).then(() => { expect(execa.mock.calls[0][1]).toContain("--proxy"); expect(execa.mock.calls[0][1]).toContain("https://iama.proxy"); @@ -42,14 +52,14 @@ describe('ytdl Query', () => { }) it('does not add a proxy when none are set', () => { const errorHandlerMock = jest.fn(); - const instance = instanceBuilder(false, "a/path/to/cookies.txt", errorHandlerMock, "python", ""); + const instance = instanceBuilder("default", "a/path/to/cookies.txt", errorHandlerMock, "python", ""); return instance.start("https://url.link", [], null).then(() => { expect(execa.mock.calls[0][1]).not.toContain("--proxy"); }); }) it('adds the cookies argument when specified in settings', () => { const errorHandlerMock = jest.fn(); - const instance = instanceBuilder(false, "a/path/to/cookies.txt", errorHandlerMock, "python"); + const instance = instanceBuilder("default", "a/path/to/cookies.txt", errorHandlerMock, "python"); return instance.start("https://url.link", [], null).then(() => { expect(execa.mock.calls[0][1]).toContain("--cookies"); expect(execa.mock.calls[0][1]).toContain("a/path/to/cookies.txt"); @@ -57,7 +67,7 @@ describe('ytdl Query', () => { }); it('uses the detected python command', () => { const errorHandlerMock = jest.fn(); - const instance = instanceBuilder(false, null, errorHandlerMock, "python3"); + const instance = instanceBuilder("default", null, errorHandlerMock, "python3"); return instance.start("https://url.link", [], null).then(() => { expect(execa.mock.calls[0][0]).toEqual("python3"); expect(execa.mock.calls[0][1][0]).toEqual("a/path/to/ytdl"); @@ -65,14 +75,14 @@ describe('ytdl Query', () => { }); it('adds the url as final argument', () => { const errorHandlerMock = jest.fn(); - const instance = instanceBuilder(false, null, errorHandlerMock, "python"); + const instance = instanceBuilder("default", null, errorHandlerMock, "python"); return instance.start("https://url.link", [], null).then(() => { expect(execa.mock.calls[0][1][execa.mock.calls[0][1].length - 1]).toContain("https://url.link"); }); }) it('adds the no-cache-dir as argument', () => { const errorHandlerMock = jest.fn(); - const instance = instanceBuilder(false, null, errorHandlerMock, "python"); + const instance = instanceBuilder("default", null, errorHandlerMock, "python"); return instance.start("https://url.link", [], null).then(() => { expect(execa.mock.calls[0][1]).toContain("--no-cache-dir"); }); @@ -85,7 +95,7 @@ describe('Query with live callback', () => { execa.mockReturnValue(mock) const errorHandlerMock = jest.fn(); const callbackMock = jest.fn(); - const instance = instanceBuilder(false, null, errorHandlerMock, "python"); + const instance = instanceBuilder("default", null, errorHandlerMock, "python"); const result = instance.start("https://url.link", [], callbackMock); setTimeout(() => { instance.stop(); @@ -99,7 +109,7 @@ describe('Query with live callback', () => { console.error = jest.fn(); const errorHandlerMock = jest.fn(); const callbackMock = jest.fn(); - const instance = instanceBuilder(false, null, errorHandlerMock, "python"); + const instance = instanceBuilder("default", null, errorHandlerMock, "python"); const result = instance.start("https://url.link", [], callbackMock); setTimeout(() => { stderr.emit("data", "test-error"); @@ -114,7 +124,7 @@ describe('Query with live callback', () => { const [stdout, stderr, mock] = execaMockBuilder(false); execa.mockReturnValue(mock) const callbackMock = jest.fn(); - const instance = instanceBuilder(false, null, jest.fn(), "python"); + const instance = instanceBuilder("default", null, jest.fn(), "python"); const result = instance.start("https://url.link", [], callbackMock); setTimeout(() => { stdout.emit("close"); @@ -126,7 +136,7 @@ describe('Query with live callback', () => { const [stdout, stderr, mock] = execaMockBuilder(false); execa.mockReturnValue(mock); const callbackMock = jest.fn(); - const instance = instanceBuilder(false, null, jest.fn(), "python"); + const instance = instanceBuilder("default", null, jest.fn(), "python"); const result = instance.start("https://url.link", [], callbackMock); setTimeout(() => { stdout.emit("data", "test-data"); @@ -143,21 +153,21 @@ describe('Query without callback', () => { it('Returns the data from the execa call', async () => { execa.mockResolvedValue({stdout: "fake-data"}); const errorHandlerMock = jest.fn(); - const instance = instanceBuilder(true, null, errorHandlerMock, "python"); + const instance = instanceBuilder("default", null, errorHandlerMock, "python"); const result = instance.start("https://url.link", [], null) await expect(result).resolves.toEqual("fake-data"); }); it('Returns a stringified empty object on error', async () => { execa.mockResolvedValue(null); const errorHandlerMock = jest.fn(); - const instance = instanceBuilder(true, null, errorHandlerMock, "python"); + const instance = instanceBuilder("default", null, errorHandlerMock, "python"); const result = instance.start("https://url.link", [], null) await expect(result).resolves.toEqual("{}"); }); it('Checks the error on error', () => { execa.mockResolvedValue(null); const errorHandlerMock = jest.fn(); - const instance = instanceBuilder(true, null, errorHandlerMock, "python"); + const instance = instanceBuilder("default", null, errorHandlerMock, "python"); return instance.start("https://url.link", [], null).then(() => { expect(errorHandlerMock).toBeCalled(); }); @@ -171,6 +181,6 @@ function execaMockBuilder(killed) { return [stdout, stderr, mock]; } -function instanceBuilder(spoofUserAgent, cookiePath, errorHandlerMock, pythonCommand, proxy) { - return new Query({pythonCommand: pythonCommand, errorHandler: {checkError: errorHandlerMock, raiseUnhandledError: errorHandlerMock}, paths: {ytdl: "a/path/to/ytdl"}, settings: {cookiePath: cookiePath, spoofUserAgent: spoofUserAgent, proxy: proxy}}, "test__id"); +function instanceBuilder(userAgent, cookiePath, errorHandlerMock, pythonCommand, proxy) { + return new Query({pythonCommand: pythonCommand, errorHandler: {checkError: errorHandlerMock, raiseUnhandledError: errorHandlerMock}, paths: {ytdl: "a/path/to/ytdl"}, settings: {cookiePath: cookiePath, userAgent, proxy: proxy}}, "test__id"); } diff --git a/tests/Settings.test.js b/tests/Settings.test.js index 07514a92..4fa5a8b3 100644 --- a/tests/Settings.test.js +++ b/tests/Settings.test.js @@ -2,8 +2,8 @@ const fs = require('fs').promises; const os = require("os"); const Settings = require('../modules/persistence/Settings'); const env = {version: "2.0.0-test1", app: {getPath: jest.fn().mockReturnValue("test/path")}}; -const defaultSettingsInstance = new Settings({settings: "tests/test-settings.json"}, env, "none", "none", "test/path", "", "", true, false, true, true, false, false, true, "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "click", "49", 8, true, "video", true, "C:\\Users\\user\\cookies.txt", false, true, false, false, true, "dark"); -const defaultSettings = "{\"outputFormat\":\"none\",\"audioOutputFormat\":\"none\",\"downloadPath\":\"test/path\",\"proxy\":\"\",\"rateLimit\":\"\",\"autoFillClipboard\":true,\"noPlaylist\":false,\"globalShortcut\":true,\"spoofUserAgent\":true,\"validateCertificate\":false,\"enableEncoding\":false,\"taskList\":true,\"nameFormat\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"nameFormatMode\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"sizeMode\":\"click\",\"splitMode\":\"49\",\"maxConcurrent\":8,\"defaultConcurrent\":8,\"updateBinary\":true,\"downloadType\":\"video\",\"updateApplication\":true,\"statSend\":false,\"downloadMetadata\":true,\"downloadThumbnail\":false,\"keepUnmerged\":false,\"calculateTotalSize\":true,\"theme\":\"dark\",\"version\":\"2.0.0-test1\"}" +const defaultSettingsInstance = new Settings({settings: "tests/test-settings.json"}, env, "none", "none", "test/path", "", "", true, false, true, "spoof", false, false, true, "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "%(title).200s-(%(height)sp%(fps).0d).%(ext)s", "click", "49", 8, true, "video", true, "C:\\Users\\user\\cookies.txt", false, true, false, false, true, "dark"); +const defaultSettings = "{\"outputFormat\":\"none\",\"audioOutputFormat\":\"none\",\"downloadPath\":\"test/path\",\"proxy\":\"\",\"rateLimit\":\"\",\"autoFillClipboard\":true,\"noPlaylist\":false,\"globalShortcut\":true,\"userAgent\":\"spoof\",\"validateCertificate\":false,\"enableEncoding\":false,\"taskList\":true,\"nameFormat\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"nameFormatMode\":\"%(title).200s-(%(height)sp%(fps).0d).%(ext)s\",\"sizeMode\":\"click\",\"splitMode\":\"49\",\"maxConcurrent\":8,\"defaultConcurrent\":8,\"updateBinary\":true,\"downloadType\":\"video\",\"updateApplication\":true,\"statSend\":false,\"downloadMetadata\":true,\"downloadThumbnail\":false,\"keepUnmerged\":false,\"calculateTotalSize\":true,\"theme\":\"dark\",\"version\":\"2.0.0-test1\"}" describe('Load settings from file', () => { beforeEach(() => { diff --git a/tests/test-settings.json b/tests/test-settings.json index 09c39c33..983e39cb 100644 --- a/tests/test-settings.json +++ b/tests/test-settings.json @@ -1 +1 @@ -{"outputFormat":"none","audioOutputFormat":"none","downloadPath": "test/path","proxy": "","rateLimit": "","autoFillClipboard":true,"noPlaylist": false,"globalShortcut":true,"spoofUserAgent":true,"validateCertificate": false,"enableEncoding": false,"taskList":true,"nameFormat":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","nameFormatMode":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","sizeMode":"click","splitMode":"49","maxConcurrent":8,"defaultConcurrent":8,"updateBinary":true,"downloadType":"video","updateApplication":true,"cookiePath":"C:\\Users\\user\\cookies.txt","statSend":false,"downloadMetadata":true,"downloadThumbnail":false,"keepUnmerged":false,"calculateTotalSize":true,"theme": "dark","version":"2.0.0-test1"} +{"outputFormat":"none","audioOutputFormat":"none","downloadPath": "test/path","proxy": "","rateLimit": "","autoFillClipboard":true,"noPlaylist": false,"globalShortcut":true,"userAgent":"spoof","validateCertificate": false,"enableEncoding": false,"taskList":true,"nameFormat":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","nameFormatMode":"%(title).200s-(%(height)sp%(fps).0d).%(ext)s","sizeMode":"click","splitMode":"49","maxConcurrent":8,"defaultConcurrent":8,"updateBinary":true,"downloadType":"video","updateApplication":true,"cookiePath":"C:\\Users\\user\\cookies.txt","statSend":false,"downloadMetadata":true,"downloadThumbnail":false,"keepUnmerged":false,"calculateTotalSize":true,"theme": "dark","version":"2.0.0-test1"}