Add functionality to import and export subscriptions / history
This commit is contained in:
		
							parent
							
								
									8308734716
								
							
						
					
					
						commit
						d77c9aed49
					
				|  | @ -31,13 +31,13 @@ | |||
|       } | ||||
|     }, | ||||
|     "@babel/core": { | ||||
|       "version": "7.11.5", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.5.tgz", | ||||
|       "integrity": "sha512-fsEANVOcZHzrsV6dMVWqpSeXClq3lNbYrfFGme6DE25FQWe7pyeYpXyx9guqUnpy466JLzZ8z4uwSr2iv60V5Q==", | ||||
|       "version": "7.11.6", | ||||
|       "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", | ||||
|       "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@babel/code-frame": "^7.10.4", | ||||
|         "@babel/generator": "^7.11.5", | ||||
|         "@babel/generator": "^7.11.6", | ||||
|         "@babel/helper-module-transforms": "^7.11.0", | ||||
|         "@babel/helpers": "^7.10.4", | ||||
|         "@babel/parser": "^7.11.5", | ||||
|  | @ -51,7 +51,7 @@ | |||
|         "lodash": "^4.17.19", | ||||
|         "resolve": "^1.3.2", | ||||
|         "semver": "^5.4.1", | ||||
|         "source-map": "^0.6.1" | ||||
|         "source-map": "^0.5.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "@babel/code-frame": { | ||||
|  | @ -64,14 +64,14 @@ | |||
|           } | ||||
|         }, | ||||
|         "@babel/generator": { | ||||
|           "version": "7.11.5", | ||||
|           "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.5.tgz", | ||||
|           "integrity": "sha512-9UqHWJ4IwRTy4l0o8gq2ef8ws8UPzvtMkVKjTLAiRmza9p9V6Z+OfuNd9fB1j5Q67F+dVJtPC2sZXI8NM9br4g==", | ||||
|           "version": "7.11.6", | ||||
|           "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", | ||||
|           "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "@babel/types": "^7.11.5", | ||||
|             "jsesc": "^2.5.1", | ||||
|             "source-map": "^0.6.1" | ||||
|             "source-map": "^0.5.0" | ||||
|           } | ||||
|         }, | ||||
|         "@babel/highlight": { | ||||
|  | @ -133,12 +133,6 @@ | |||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", | ||||
|           "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "source-map": { | ||||
|           "version": "0.6.1", | ||||
|           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", | ||||
|           "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", | ||||
|           "dev": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|  | @ -1602,6 +1596,80 @@ | |||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "@eslint/eslintrc": { | ||||
|       "version": "0.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", | ||||
|       "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "ajv": "^6.12.4", | ||||
|         "debug": "^4.1.1", | ||||
|         "espree": "^7.3.0", | ||||
|         "globals": "^12.1.0", | ||||
|         "ignore": "^4.0.6", | ||||
|         "import-fresh": "^3.2.1", | ||||
|         "js-yaml": "^3.13.1", | ||||
|         "lodash": "^4.17.19", | ||||
|         "minimatch": "^3.0.4", | ||||
|         "strip-json-comments": "^3.1.1" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "ajv": { | ||||
|           "version": "6.12.4", | ||||
|           "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", | ||||
|           "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "fast-deep-equal": "^3.1.1", | ||||
|             "fast-json-stable-stringify": "^2.0.0", | ||||
|             "json-schema-traverse": "^0.4.1", | ||||
|             "uri-js": "^4.2.2" | ||||
|           } | ||||
|         }, | ||||
|         "debug": { | ||||
|           "version": "4.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", | ||||
|           "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "ms": "^2.1.1" | ||||
|           } | ||||
|         }, | ||||
|         "fast-deep-equal": { | ||||
|           "version": "3.1.3", | ||||
|           "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", | ||||
|           "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "globals": { | ||||
|           "version": "12.4.0", | ||||
|           "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", | ||||
|           "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "type-fest": "^0.8.1" | ||||
|           } | ||||
|         }, | ||||
|         "ignore": { | ||||
|           "version": "4.0.6", | ||||
|           "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", | ||||
|           "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "ms": { | ||||
|           "version": "2.1.2", | ||||
|           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", | ||||
|           "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "strip-json-comments": { | ||||
|           "version": "3.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", | ||||
|           "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", | ||||
|           "dev": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "@fortawesome/fontawesome-common-types": { | ||||
|       "version": "0.2.30", | ||||
|       "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz", | ||||
|  | @ -1624,9 +1692,9 @@ | |||
|       } | ||||
|     }, | ||||
|     "@fortawesome/vue-fontawesome": { | ||||
|       "version": "0.1.10", | ||||
|       "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-0.1.10.tgz", | ||||
|       "integrity": "sha512-b2+SLF31h32LSepVcXe+BQ63yvbq5qmTCy4KfFogCYm2bn68H5sDWUnX+U7MBqnM2aeEk9M7xSoqGnu+wSdY6w==" | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.0.tgz", | ||||
|       "integrity": "sha512-N3VKw7KzRfOm8hShUVldpinlm13HpvLBQgT63QS+aCrIRLwjoEUXY5Rcmttbfb6HkzZaeqjLqd/aZCQ53UjQpg==" | ||||
|     }, | ||||
|     "@istanbuljs/load-nyc-config": { | ||||
|       "version": "1.1.0", | ||||
|  | @ -6956,9 +7024,9 @@ | |||
|       } | ||||
|     }, | ||||
|     "electron": { | ||||
|       "version": "10.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-10.1.0.tgz", | ||||
|       "integrity": "sha512-DyS6WhQ59+ZXQsI1EkpsYkOXFt0Xbp+mbxPTJS9A7O21r3JDzaTC+1Jxz7g6J+Sbi9Y7UFdRs0tn/vqhHJx2gA==", | ||||
|       "version": "10.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-10.1.1.tgz", | ||||
|       "integrity": "sha512-ZJtZHMr17AvvBosuA6XUmpehwAlGM4/n46Mw9BcyD8tpgdI6IQd0X5OU9meE3X3M8Y6Ja2Kr2udTMgtjvot2hA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@electron/get": "^1.0.1", | ||||
|  | @ -7853,12 +7921,13 @@ | |||
|       } | ||||
|     }, | ||||
|     "eslint": { | ||||
|       "version": "7.7.0", | ||||
|       "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.7.0.tgz", | ||||
|       "integrity": "sha512-1KUxLzos0ZVsyL81PnRN335nDtQ8/vZUD6uMtWbF+5zDtjKcsklIi78XoE0MVL93QvWTu+E5y44VyyCsOMBrIg==", | ||||
|       "version": "7.8.1", | ||||
|       "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.8.1.tgz", | ||||
|       "integrity": "sha512-/2rX2pfhyUG0y+A123d0ccXtMm7DV7sH1m3lk9nk2DZ2LReq39FXHueR9xZwshE5MdfSf0xunSaMWRqyIA6M1w==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@babel/code-frame": "^7.0.0", | ||||
|         "@eslint/eslintrc": "^0.1.3", | ||||
|         "ajv": "^6.10.0", | ||||
|         "chalk": "^4.0.0", | ||||
|         "cross-spawn": "^7.0.2", | ||||
|  | @ -7868,7 +7937,7 @@ | |||
|         "eslint-scope": "^5.1.0", | ||||
|         "eslint-utils": "^2.1.0", | ||||
|         "eslint-visitor-keys": "^1.3.0", | ||||
|         "espree": "^7.2.0", | ||||
|         "espree": "^7.3.0", | ||||
|         "esquery": "^1.2.0", | ||||
|         "esutils": "^2.0.2", | ||||
|         "file-entry-cache": "^5.0.1", | ||||
|  | @ -8081,9 +8150,9 @@ | |||
|           "dev": true | ||||
|         }, | ||||
|         "supports-color": { | ||||
|           "version": "7.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", | ||||
|           "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", | ||||
|           "version": "7.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", | ||||
|           "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "has-flag": "^4.0.0" | ||||
|  | @ -8319,12 +8388,12 @@ | |||
|       "dev": true | ||||
|     }, | ||||
|     "espree": { | ||||
|       "version": "7.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/espree/-/espree-7.2.0.tgz", | ||||
|       "integrity": "sha512-H+cQ3+3JYRMEIOl87e7QdHX70ocly5iW4+dttuR8iYSPr/hXKFb+7dBsZ7+u1adC4VrnPlTkv0+OwuPnDop19g==", | ||||
|       "version": "7.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", | ||||
|       "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "acorn": "^7.3.1", | ||||
|         "acorn": "^7.4.0", | ||||
|         "acorn-jsx": "^5.2.0", | ||||
|         "eslint-visitor-keys": "^1.3.0" | ||||
|       }, | ||||
|  | @ -16630,18 +16699,24 @@ | |||
|       } | ||||
|     }, | ||||
|     "sass-loader": { | ||||
|       "version": "10.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.0.1.tgz", | ||||
|       "integrity": "sha512-b2PSldKVTS3JcFPHSrEXh3BeAfR7XknGiGCAO5aHruR3Pf3kqLP3Gb2ypXLglRrAzgZkloNxLZ7GXEGDX0hBUQ==", | ||||
|       "version": "10.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.0.2.tgz", | ||||
|       "integrity": "sha512-wV6NDUVB8/iEYMalV/+139+vl2LaRFlZGEd5/xmdcdzQcgmis+npyco6NsDTVOlNA3y2NV9Gcz+vHyFMIT+ffg==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "klona": "^2.0.3", | ||||
|         "loader-utils": "^2.0.0", | ||||
|         "neo-async": "^2.6.2", | ||||
|         "schema-utils": "^2.7.0", | ||||
|         "schema-utils": "^2.7.1", | ||||
|         "semver": "^7.3.2" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "@types/json-schema": { | ||||
|           "version": "7.0.6", | ||||
|           "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", | ||||
|           "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "ajv": { | ||||
|           "version": "6.12.4", | ||||
|           "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", | ||||
|  | @ -16696,14 +16771,14 @@ | |||
|           "dev": true | ||||
|         }, | ||||
|         "schema-utils": { | ||||
|           "version": "2.7.0", | ||||
|           "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", | ||||
|           "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", | ||||
|           "version": "2.7.1", | ||||
|           "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", | ||||
|           "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "@types/json-schema": "^7.0.4", | ||||
|             "ajv": "^6.12.2", | ||||
|             "ajv-keywords": "^3.4.1" | ||||
|             "@types/json-schema": "^7.0.5", | ||||
|             "ajv": "^6.12.4", | ||||
|             "ajv-keywords": "^3.5.2" | ||||
|           } | ||||
|         }, | ||||
|         "semver": { | ||||
|  |  | |||
							
								
								
									
										10
									
								
								package.json
								
								
								
								
							
							
						
						
									
										10
									
								
								package.json
								
								
								
								
							|  | @ -10,7 +10,7 @@ | |||
|   "dependencies": { | ||||
|     "@fortawesome/fontawesome-svg-core": "^1.2.30", | ||||
|     "@fortawesome/free-solid-svg-icons": "^5.14.0", | ||||
|     "@fortawesome/vue-fontawesome": "^0.1.10", | ||||
|     "@fortawesome/vue-fontawesome": "^2.0.0", | ||||
|     "@silvermine/videojs-quality-selector": "^1.2.4", | ||||
|     "autolinker": "^3.14.1", | ||||
|     "bulma-pro": "^0.2.0", | ||||
|  | @ -53,7 +53,7 @@ | |||
|   }, | ||||
|   "description": "A private YouTube client", | ||||
|   "devDependencies": { | ||||
|     "@babel/core": "^7.11.5", | ||||
|     "@babel/core": "^7.11.6", | ||||
|     "@babel/plugin-proposal-class-properties": "^7.10.4", | ||||
|     "@babel/plugin-proposal-object-rest-spread": "^7.11.0", | ||||
|     "@babel/preset-env": "^7.11.5", | ||||
|  | @ -66,12 +66,12 @@ | |||
|     "copy-webpack-plugin": "^6.1.0", | ||||
|     "css-loader": "^4.2.2", | ||||
|     "devtron": "^1.4.0", | ||||
|     "electron": "^10.1.0", | ||||
|     "electron": "^10.1.1", | ||||
|     "electron-builder": "^22.8.0", | ||||
|     "electron-builder-squirrel-windows": "^22.8.1", | ||||
|     "electron-debug": "^3.1.0", | ||||
|     "electron-rebuild": "^2.0.1", | ||||
|     "eslint": "^7.7.0", | ||||
|     "eslint": "^7.8.1", | ||||
|     "eslint-config-prettier": "^6.11.0", | ||||
|     "eslint-config-standard": "^14.1.1", | ||||
|     "eslint-plugin-import": "^2.22.0", | ||||
|  | @ -90,7 +90,7 @@ | |||
|     "npm-run-all": "^4.1.5", | ||||
|     "prettier": "^2.1.1", | ||||
|     "sass": "^1.26.10", | ||||
|     "sass-loader": "^10.0.1", | ||||
|     "sass-loader": "^10.0.2", | ||||
|     "style-loader": "^1.2.1", | ||||
|     "tree-kill": "1.2.2", | ||||
|     "typescript": "^4.0.2", | ||||
|  |  | |||
|  | @ -0,0 +1,782 @@ | |||
| import Vue from 'vue' | ||||
| import { mapActions, mapMutations } from 'vuex' | ||||
| import FtCard from '../ft-card/ft-card.vue' | ||||
| import FtButton from '../ft-button/ft-button.vue' | ||||
| import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue' | ||||
| import FtFlexBox from '../ft-flex-box/ft-flex-box.vue' | ||||
| import FtPrompt from '../ft-prompt/ft-prompt.vue' | ||||
| 
 | ||||
| import { remote } from 'electron' | ||||
| import fs from 'fs' | ||||
| import opmlToJson from 'opml-to-json' | ||||
| import ytch from 'yt-channel-info' | ||||
| 
 | ||||
| const app = remote.app | ||||
| const dialog = remote.dialog | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
|   name: 'DataSettings', | ||||
|   components: { | ||||
|     'ft-card': FtCard, | ||||
|     'ft-button': FtButton, | ||||
|     'ft-toggle-switch': FtToggleSwitch, | ||||
|     'ft-flex-box': FtFlexBox, | ||||
|     'ft-prompt': FtPrompt | ||||
|   }, | ||||
|   data: function () { | ||||
|     return { | ||||
|       showImportSubscriptionsPrompt: false, | ||||
|       showExportSubscriptionsPrompt: false, | ||||
|       subscriptionsPromptValues: [ | ||||
|         'freetube', | ||||
|         'youtube', | ||||
|         'newpipe' | ||||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     rememberHistory: function () { | ||||
|       return this.$store.getters.getRememberHistory | ||||
|     }, | ||||
|     saveWatchedProgress: function () { | ||||
|       return this.$store.getters.getSaveWatchedProgress | ||||
|     }, | ||||
|     backendPreference: function () { | ||||
|       return this.$store.getters.getBackendPreference | ||||
|     }, | ||||
|     backendFallback: function () { | ||||
|       return this.$store.getters.getBackendFallback | ||||
|     }, | ||||
|     invidiousInstance: function () { | ||||
|       return this.$store.getters.getInvidiousInstance | ||||
|     }, | ||||
|     profileList: function () { | ||||
|       return this.$store.getters.getProfileList | ||||
|     }, | ||||
|     importSubscriptionsPromptNames: function () { | ||||
|       const importFreeTube = this.$t('Settings.Data Settings.Import FreeTube') | ||||
|       const importYouTube = this.$t('Settings.Data Settings.Import YouTube') | ||||
|       const importNewPipe = this.$t('Settings.Data Settings.Import NewPipe') | ||||
|       return [ | ||||
|         `${importFreeTube} (.db)`, | ||||
|         `${importYouTube} (.opml)`, | ||||
|         `${importNewPipe} (.json)` | ||||
|       ] | ||||
|     }, | ||||
|     exportSubscriptionsPromptNames: function () { | ||||
|       const exportFreeTube = this.$t('Settings.Data Settings.Export FreeTube') | ||||
|       const exportYouTube = this.$t('Settings.Data Settings.Export YouTube') | ||||
|       const exportNewPipe = this.$t('Settings.Data Settings.Export NewPipe') | ||||
|       return [ | ||||
|         `${exportFreeTube} (.db)`, | ||||
|         `${exportYouTube} (.opml)`, | ||||
|         `${exportNewPipe} (.json)` | ||||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     importSubscriptions: function (option) { | ||||
|       this.showImportSubscriptionsPrompt = false | ||||
| 
 | ||||
|       if (option === null) { | ||||
|         return | ||||
|       } | ||||
| 
 | ||||
|       switch (option) { | ||||
|         case 'freetube': | ||||
|           this.importFreeTubeSubscriptions() | ||||
|           break | ||||
|         case 'youtube': | ||||
|           this.importYouTubeSubscriptions() | ||||
|           break | ||||
|         case 'newpipe': | ||||
|           this.importNewPipeSubscriptions() | ||||
|           break | ||||
|       } | ||||
|     }, | ||||
| 
 | ||||
|     importFreeTubeSubscriptions: function () { | ||||
|       const options = { | ||||
|         properties: ['openFile'], | ||||
|         filters: [ | ||||
|           { | ||||
|             name: 'Database File', | ||||
|             extensions: ['db'] | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
| 
 | ||||
|       dialog.showOpenDialog(options).then((response) => { | ||||
|         if (response.canceled || response.filePaths.length === 0) { | ||||
|           return | ||||
|         } | ||||
| 
 | ||||
|         const filePath = response.filePaths[0] | ||||
| 
 | ||||
|         fs.readFile(filePath, async (err, data) => { | ||||
|           if (err) { | ||||
|             const message = this.$t('Settings.Data Settings.Unable to read file') | ||||
|             this.showToast({ | ||||
|               message: `${message}: ${err}` | ||||
|             }) | ||||
|             return | ||||
|           } | ||||
| 
 | ||||
|           let textDecode = new TextDecoder('utf-8').decode(data) | ||||
|           textDecode = textDecode.split('\n') | ||||
|           textDecode.pop() | ||||
| 
 | ||||
|           textDecode.forEach((data) => { | ||||
|             const profileData = JSON.parse(data) | ||||
|             // We would technically already be done by the time the data is parsed,
 | ||||
|             // however we want to limit the possibility of malicious data being sent
 | ||||
|             // to the app, so we'll only grab the data we need here.
 | ||||
| 
 | ||||
|             const requiredKeys = [ | ||||
|               '_id', | ||||
|               'name', | ||||
|               'bgColor', | ||||
|               'textColor', | ||||
|               'subscriptions' | ||||
|             ] | ||||
| 
 | ||||
|             const profileObject = {} | ||||
| 
 | ||||
|             Object.keys(profileData).forEach((key) => { | ||||
|               if (!requiredKeys.includes(key)) { | ||||
|                 const message = this.$t('Settings.Data Settings.Unknown data key') | ||||
|                 this.showToast({ | ||||
|                   message: `${message}: ${key}` | ||||
|                 }) | ||||
|               } else { | ||||
|                 profileObject[key] = profileData[key] | ||||
|               } | ||||
|             }) | ||||
| 
 | ||||
|             if (Object.keys(profileObject).length < requiredKeys.length) { | ||||
|               const message = this.$t('Settings.Data Settings.Profile object has insufficient data, skipping item') | ||||
|               this.showToast({ | ||||
|                 message: message | ||||
|               }) | ||||
|             } else { | ||||
|               this.updateProfile(profileObject) | ||||
|             } | ||||
|           }) | ||||
| 
 | ||||
|           this.showToast({ | ||||
|             message: this.$t('Settings.Data Settings.All subscriptions and profiles have been successfully imported') | ||||
|           }) | ||||
|         }) | ||||
|       }) | ||||
|     }, | ||||
| 
 | ||||
|     importYouTubeSubscriptions: function () { | ||||
|       const options = { | ||||
|         properties: ['openFile'], | ||||
|         filters: [ | ||||
|           { | ||||
|             name: 'Database File', | ||||
|             extensions: ['*'] | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
| 
 | ||||
|       dialog.showOpenDialog(options).then(async (response) => { | ||||
|         if (response.canceled || response.filePaths.length === 0) { | ||||
|           return | ||||
|         } | ||||
| 
 | ||||
|         const filePath = response.filePaths[0] | ||||
| 
 | ||||
|         fs.readFile(filePath, async (err, data) => { | ||||
|           if (err) { | ||||
|             const message = this.$t('Settings.Data Settings.Unable to read file') | ||||
|             this.showToast({ | ||||
|               message: `${message}: ${err}` | ||||
|             }) | ||||
|             return | ||||
|           } | ||||
| 
 | ||||
|           opmlToJson(data, async (err, json) => { | ||||
|             if (err) { | ||||
|               console.log(err) | ||||
|               const message = this.$t('Settings.Data Settings.Invalid subscriptions file') | ||||
|               this.showToast({ | ||||
|                 message: `${message}: ${err}` | ||||
|               }) | ||||
|               return | ||||
|             } | ||||
| 
 | ||||
|             const feedData = json.children[0].children | ||||
| 
 | ||||
|             if (typeof feedData === 'undefined') { | ||||
|               const message = this.$t('Settings.Data Settings.Invalid subscriptions file') | ||||
|               this.showToast({ | ||||
|                 message: message | ||||
|               }) | ||||
| 
 | ||||
|               return | ||||
|             } | ||||
| 
 | ||||
|             const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0])) | ||||
|             const subscriptions = [] | ||||
| 
 | ||||
|             this.showToast({ | ||||
|               message: this.$t('Settings.Data Settings.This might take a while, please wait') | ||||
|             }) | ||||
| 
 | ||||
|             this.updateShowProgressBar(true) | ||||
|             this.setProgressBarPercentage(0) | ||||
| 
 | ||||
|             feedData.forEach(async (channel, index) => { | ||||
|               const channelId = channel.xmlurl.replace('https://www.youtube.com/feeds/videos.xml?channel_id=', '') | ||||
|               let channelInfo | ||||
|               if (this.backendPreference === 'invidious') { | ||||
|                 channelInfo = await this.getChannelInfoInvidious(channelId) | ||||
|               } else { | ||||
|                 channelInfo = await this.getChannelInfoLocal(channelId) | ||||
|               } | ||||
| 
 | ||||
|               const subscription = { | ||||
|                 id: channelId, | ||||
|                 name: channelInfo.author, | ||||
|                 thumbnail: channelInfo.authorThumbnails[1].url | ||||
|               } | ||||
| 
 | ||||
|               subscriptions.push(subscription) | ||||
| 
 | ||||
|               const progressPercentage = ((subscriptions.length + 1) / feedData.length) * 100 | ||||
|               this.setProgressBarPercentage(progressPercentage) | ||||
| 
 | ||||
|               if (subscriptions.length === feedData.length) { | ||||
|                 primaryProfile.subscriptions = primaryProfile.subscriptions.concat(subscriptions) | ||||
|                 this.updateProfile(primaryProfile) | ||||
| 
 | ||||
|                 this.showToast({ | ||||
|                   message: this.$t('Settings.Data Settings.All subscriptions have been successfully imported') | ||||
|                 }) | ||||
|                 this.updateShowProgressBar(false) | ||||
|               } | ||||
|             }) | ||||
|           }) | ||||
|         }) | ||||
|       }) | ||||
|     }, | ||||
| 
 | ||||
|     importNewPipeSubscriptions: function () { | ||||
|       const options = { | ||||
|         properties: ['openFile'], | ||||
|         filters: [ | ||||
|           { | ||||
|             name: 'Database File', | ||||
|             extensions: ['json'] | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
| 
 | ||||
|       dialog.showOpenDialog(options).then(async (response) => { | ||||
|         if (response.canceled || response.filePaths.length === 0) { | ||||
|           return | ||||
|         } | ||||
| 
 | ||||
|         const filePath = response.filePaths[0] | ||||
| 
 | ||||
|         fs.readFile(filePath, async (err, data) => { | ||||
|           if (err) { | ||||
|             const message = this.$t('Settings.Data Settings.Unable to read file') | ||||
|             this.showToast({ | ||||
|               message: `${message}: ${err}` | ||||
|             }) | ||||
|             return | ||||
|           } | ||||
| 
 | ||||
|           const newPipeData = JSON.parse(data) | ||||
| 
 | ||||
|           if (typeof newPipeData.subscriptions === 'undefined') { | ||||
|             this.showToast({ | ||||
|               message: this.$t('Settings.Data Settings.Invalid subscriptions file') | ||||
|             }) | ||||
| 
 | ||||
|             return | ||||
|           } | ||||
| 
 | ||||
|           const newPipeSubscriptions = newPipeData.subscriptions | ||||
| 
 | ||||
|           const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0])) | ||||
|           const subscriptions = [] | ||||
| 
 | ||||
|           this.showToast({ | ||||
|             message: this.$t('Settings.Data Settings.This might take a while, please wait') | ||||
|           }) | ||||
| 
 | ||||
|           this.updateShowProgressBar(true) | ||||
|           this.setProgressBarPercentage(0) | ||||
| 
 | ||||
|           newPipeSubscriptions.forEach(async (channel, index) => { | ||||
|             const channelId = channel.url.replace(/https:\/\/(www\.)?youtube\.com\/channel\//, '') | ||||
|             let channelInfo | ||||
|             if (this.backendPreference === 'invidious') { | ||||
|               channelInfo = await this.getChannelInfoInvidious(channelId) | ||||
|             } else { | ||||
|               channelInfo = await this.getChannelInfoLocal(channelId) | ||||
|             } | ||||
| 
 | ||||
|             const subscription = { | ||||
|               id: channelId, | ||||
|               name: channelInfo.author, | ||||
|               thumbnail: channelInfo.authorThumbnails[1].url | ||||
|             } | ||||
| 
 | ||||
|             subscriptions.push(subscription) | ||||
| 
 | ||||
|             const progressPercentage = ((subscriptions.length + 1) / newPipeSubscriptions.length) * 100 | ||||
|             this.setProgressBarPercentage(progressPercentage) | ||||
| 
 | ||||
|             if (subscriptions.length === newPipeSubscriptions.length) { | ||||
|               primaryProfile.subscriptions = primaryProfile.subscriptions.concat(subscriptions) | ||||
|               this.updateProfile(primaryProfile) | ||||
| 
 | ||||
|               this.showToast({ | ||||
|                 message: this.$t('Settings.Data Settings.All subscriptions have been successfully imported') | ||||
|               }) | ||||
|               this.updateShowProgressBar(false) | ||||
|             } | ||||
|           }) | ||||
|         }) | ||||
|       }) | ||||
|     }, | ||||
| 
 | ||||
|     exportSubscriptions: function (option) { | ||||
|       this.showExportSubscriptionsPrompt = false | ||||
| 
 | ||||
|       if (option === null) { | ||||
|         return | ||||
|       } | ||||
| 
 | ||||
|       switch (option) { | ||||
|         case 'freetube': | ||||
|           this.exportFreeTubeSubscriptions() | ||||
|           break | ||||
|         case 'youtube': | ||||
|           this.exportYouTubeSubscriptions() | ||||
|           break | ||||
|         case 'newpipe': | ||||
|           this.exportNewPipeSubscriptions() | ||||
|           break | ||||
|       } | ||||
|     }, | ||||
| 
 | ||||
|     exportFreeTubeSubscriptions: function () { | ||||
|       const userData = app.getPath('userData') | ||||
|       const subscriptionsDb = `${userData}/profiles.db` | ||||
|       const date = new Date() | ||||
|       let dateMonth = date.getMonth() + 1 | ||||
| 
 | ||||
|       if (dateMonth < 10) { | ||||
|         dateMonth = '0' + dateMonth | ||||
|       } | ||||
| 
 | ||||
|       let dateDay = date.getDate() | ||||
| 
 | ||||
|       if (dateDay < 10) { | ||||
|         dateDay = '0' + dateDay | ||||
|       } | ||||
| 
 | ||||
|       const dateYear = date.getFullYear() | ||||
|       const exportFileName = 'freetube-subscriptions-' + dateYear + '-' + dateMonth + '-' + dateDay + '.db' | ||||
| 
 | ||||
|       const options = { | ||||
|         defaultPath: exportFileName, | ||||
|         filters: [ | ||||
|           { | ||||
|             name: 'Database File', | ||||
|             extensions: ['db'] | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
| 
 | ||||
|       dialog.showSaveDialog(options).then((response) => { | ||||
|         if (response.canceled || response.filePath === '') { | ||||
|           // User canceled the save dialog
 | ||||
|           return | ||||
|         } | ||||
| 
 | ||||
|         const filePath = response.filePath | ||||
| 
 | ||||
|         fs.readFile(subscriptionsDb, (readErr, data) => { | ||||
|           if (readErr) { | ||||
|             const message = this.$t('Settings.Data Settings.Unable to read file') | ||||
|             this.showToast({ | ||||
|               message: `${message}: ${readErr}` | ||||
|             }) | ||||
|             return | ||||
|           } | ||||
| 
 | ||||
|           fs.writeFile(filePath, data, (writeErr) => { | ||||
|             if (writeErr) { | ||||
|               const message = this.$t('Settings.Data Settings.Unable to write file') | ||||
|               this.showToast({ | ||||
|                 message: `${message}: ${writeErr}` | ||||
|               }) | ||||
|               return | ||||
|             } | ||||
| 
 | ||||
|             this.showToast({ | ||||
|               message: this.$t('Settings.Data Settings.All subscriptions have been successfully exported') | ||||
|             }) | ||||
|           }) | ||||
|         }) | ||||
|       }) | ||||
|     }, | ||||
| 
 | ||||
|     exportYouTubeSubscriptions: async function () { | ||||
|       const date = new Date() | ||||
|       let dateMonth = date.getMonth() + 1 | ||||
| 
 | ||||
|       if (dateMonth < 10) { | ||||
|         dateMonth = '0' + dateMonth | ||||
|       } | ||||
| 
 | ||||
|       let dateDay = date.getDate() | ||||
| 
 | ||||
|       if (dateDay < 10) { | ||||
|         dateDay = '0' + dateDay | ||||
|       } | ||||
| 
 | ||||
|       const dateYear = date.getFullYear() | ||||
|       const exportFileName = 'youtube-subscriptions-' + dateYear + '-' + dateMonth + '-' + dateDay + '.opml' | ||||
| 
 | ||||
|       const options = { | ||||
|         defaultPath: exportFileName, | ||||
|         filters: [ | ||||
|           { | ||||
|             name: 'Database File', | ||||
|             extensions: ['opml'] | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
| 
 | ||||
|       let opmlData = '<opml version="1.1"><body><outline text="YouTube Subscriptions" title="YouTube Subscriptions">' | ||||
|       const endingOpmlString = '</outline></body></opml>' | ||||
| 
 | ||||
|       let count = 0 | ||||
| 
 | ||||
|       this.profileList[0].subscriptions.forEach((channel) => { | ||||
|         const channelOpmlString = `<outline text="${channel.name}" title="${channel.name}" type="rss" xmlUrl="https://www.youtube.com/feeds/videos.xml?channel_id=${channel.id}"/>` | ||||
|         count++ | ||||
|         opmlData += channelOpmlString | ||||
| 
 | ||||
|         if (count === this.profileList[0].subscriptions.length) { | ||||
|           opmlData += endingOpmlString | ||||
|         } | ||||
|       }) | ||||
| 
 | ||||
|       dialog.showSaveDialog(options).then((response) => { | ||||
|         if (response.canceled || response.filePath === '') { | ||||
|           // User canceled the save dialog
 | ||||
|           return | ||||
|         } | ||||
| 
 | ||||
|         const filePath = response.filePath | ||||
| 
 | ||||
|         fs.writeFile(filePath, opmlData, (writeErr) => { | ||||
|           if (writeErr) { | ||||
|             const message = this.$t('Settings.Data Settings.Unable to write file') | ||||
|             this.showToast({ | ||||
|               message: `${message}: ${writeErr}` | ||||
|             }) | ||||
|             return | ||||
|           } | ||||
| 
 | ||||
|           this.showToast({ | ||||
|             message: this.$t('Settings.Data Settings.All subscriptions have been successfully exported') | ||||
|           }) | ||||
|         }) | ||||
|       }) | ||||
|     }, | ||||
| 
 | ||||
|     exportNewPipeSubscriptions: function () { | ||||
|       const date = new Date() | ||||
|       let dateMonth = date.getMonth() + 1 | ||||
| 
 | ||||
|       if (dateMonth < 10) { | ||||
|         dateMonth = '0' + dateMonth | ||||
|       } | ||||
| 
 | ||||
|       let dateDay = date.getDate() | ||||
| 
 | ||||
|       if (dateDay < 10) { | ||||
|         dateDay = '0' + dateDay | ||||
|       } | ||||
| 
 | ||||
|       const dateYear = date.getFullYear() | ||||
|       const exportFileName = 'newpipe-subscriptions-' + dateYear + '-' + dateMonth + '-' + dateDay + '.json' | ||||
| 
 | ||||
|       const options = { | ||||
|         defaultPath: exportFileName, | ||||
|         filters: [ | ||||
|           { | ||||
|             name: 'Database File', | ||||
|             extensions: ['json'] | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
| 
 | ||||
|       const newPipeObject = { | ||||
|         app_version: '0.19.8', | ||||
|         app_version_int: 953, | ||||
|         subscriptions: [] | ||||
|       } | ||||
| 
 | ||||
|       this.profileList[0].subscriptions.forEach((channel) => { | ||||
|         const channelUrl = `https://www.youtube.com/channel/${channel.id}` | ||||
|         const subscription = { | ||||
|           service_id: 0, | ||||
|           url: channelUrl, | ||||
|           name: channel.name | ||||
|         } | ||||
| 
 | ||||
|         newPipeObject.subscriptions.push(subscription) | ||||
|       }) | ||||
| 
 | ||||
|       dialog.showSaveDialog(options).then((response) => { | ||||
|         if (response.canceled || response.filePath === '') { | ||||
|           // User canceled the save dialog
 | ||||
|           return | ||||
|         } | ||||
| 
 | ||||
|         const filePath = response.filePath | ||||
| 
 | ||||
|         fs.writeFile(filePath, JSON.stringify(newPipeObject), (writeErr) => { | ||||
|           if (writeErr) { | ||||
|             const message = this.$t('Settings.Data Settings.Unable to write file') | ||||
|             this.showToast({ | ||||
|               message: `${message}: ${writeErr}` | ||||
|             }) | ||||
|             return | ||||
|           } | ||||
| 
 | ||||
|           this.showToast({ | ||||
|             message: this.$t('Settings.Data Settings.All subscriptions have been successfully exported') | ||||
|           }) | ||||
|         }) | ||||
|       }) | ||||
|     }, | ||||
| 
 | ||||
|     importHistory: function () { | ||||
|       const options = { | ||||
|         properties: ['openFile'], | ||||
|         filters: [ | ||||
|           { | ||||
|             name: 'Database File', | ||||
|             extensions: ['db'] | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
| 
 | ||||
|       dialog.showOpenDialog(options).then((response) => { | ||||
|         if (response.canceled || response.filePaths.length === 0) { | ||||
|           return | ||||
|         } | ||||
| 
 | ||||
|         const filePath = response.filePaths[0] | ||||
| 
 | ||||
|         fs.readFile(filePath, async (err, data) => { | ||||
|           if (err) { | ||||
|             const message = this.$t('Settings.Data Settings.Unable to read file') | ||||
|             this.showToast({ | ||||
|               message: `${message}: ${err}` | ||||
|             }) | ||||
|             return | ||||
|           } | ||||
| 
 | ||||
|           let textDecode = new TextDecoder('utf-8').decode(data) | ||||
|           textDecode = textDecode.split('\n') | ||||
|           textDecode.pop() | ||||
| 
 | ||||
|           textDecode.forEach((history) => { | ||||
|             const historyData = JSON.parse(history) | ||||
|             // We would technically already be done by the time the data is parsed,
 | ||||
|             // however we want to limit the possibility of malicious data being sent
 | ||||
|             // to the app, so we'll only grab the data we need here.
 | ||||
|             const requiredKeys = [ | ||||
|               '_id', | ||||
|               'author', | ||||
|               'authorId', | ||||
|               'description', | ||||
|               'isLive', | ||||
|               'lengthSeconds', | ||||
|               'paid', | ||||
|               'published', | ||||
|               'timeWatched', | ||||
|               'title', | ||||
|               'type', | ||||
|               'videoId', | ||||
|               'viewCount', | ||||
|               'watchProgress' | ||||
|             ] | ||||
| 
 | ||||
|             const historyObject = {} | ||||
| 
 | ||||
|             Object.keys(historyData).forEach((key) => { | ||||
|               if (!requiredKeys.includes(key)) { | ||||
|                 this.showToast({ | ||||
|                   message: `Unknown data key: ${key}` | ||||
|                 }) | ||||
|               } else { | ||||
|                 historyObject[key] = historyData[key] | ||||
|               } | ||||
|             }) | ||||
| 
 | ||||
|             if (Object.keys(historyObject).length < requiredKeys.length) { | ||||
|               this.showToast({ | ||||
|                 message: this.$t('Settings.Data Settings.History object has insufficient data, skipping item') | ||||
|               }) | ||||
|             } else { | ||||
|               this.updateHistory(historyObject) | ||||
|             } | ||||
|           }) | ||||
| 
 | ||||
|           this.showToast({ | ||||
|             message: this.$t('Settings.Data Settings.All watched history has been successfully imported') | ||||
|           }) | ||||
|         }) | ||||
|       }) | ||||
|     }, | ||||
| 
 | ||||
|     exportHistory: function () { | ||||
|       const userData = app.getPath('userData') | ||||
|       const historyDb = `${userData}/history.db` | ||||
|       const date = new Date() | ||||
|       let dateMonth = date.getMonth() + 1 | ||||
| 
 | ||||
|       if (dateMonth < 10) { | ||||
|         dateMonth = '0' + dateMonth | ||||
|       } | ||||
| 
 | ||||
|       let dateDay = date.getDate() | ||||
| 
 | ||||
|       if (dateDay < 10) { | ||||
|         dateDay = '0' + dateDay | ||||
|       } | ||||
| 
 | ||||
|       const dateYear = date.getFullYear() | ||||
|       const exportFileName = 'freetube-history-' + dateYear + '-' + dateMonth + '-' + dateDay + '.db' | ||||
| 
 | ||||
|       const options = { | ||||
|         defaultPath: exportFileName, | ||||
|         filters: [ | ||||
|           { | ||||
|             name: 'Database File', | ||||
|             extensions: ['db'] | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
| 
 | ||||
|       dialog.showSaveDialog(options).then((response) => { | ||||
|         if (response.canceled || response.filePath === '') { | ||||
|           // User canceled the save dialog
 | ||||
|           return | ||||
|         } | ||||
| 
 | ||||
|         const filePath = response.filePath | ||||
| 
 | ||||
|         fs.readFile(historyDb, (readErr, data) => { | ||||
|           if (readErr) { | ||||
|             const message = this.$t('Settings.Data Settings.Unable to read file') | ||||
|             this.showToast({ | ||||
|               message: `${message}: ${readErr}` | ||||
|             }) | ||||
|             return | ||||
|           } | ||||
| 
 | ||||
|           fs.writeFile(filePath, data, (writeErr) => { | ||||
|             if (writeErr) { | ||||
|               const message = this.$t('Settings.Data Settings.Unable to write file') | ||||
|               this.showToast({ | ||||
|                 message: `${message}: ${writeErr}` | ||||
|               }) | ||||
|               return | ||||
|             } | ||||
| 
 | ||||
|             this.showToast({ | ||||
|               message: this.$t('Settings.Data Settings.All watched history has been successfully exported') | ||||
|             }) | ||||
|           }) | ||||
|         }) | ||||
|       }) | ||||
|     }, | ||||
| 
 | ||||
|     getChannelInfoInvidious: function (channelId) { | ||||
|       return new Promise((resolve, reject) => { | ||||
|         const subscriptionsPayload = { | ||||
|           resource: 'channels', | ||||
|           id: channelId, | ||||
|           params: {} | ||||
|         } | ||||
| 
 | ||||
|         this.invidiousAPICall(subscriptionsPayload).then((response) => { | ||||
|           resolve(response) | ||||
|         }).catch((err) => { | ||||
|           console.log(err) | ||||
|           const errorMessage = this.$t('Invidious API Error (Click to copy)') | ||||
|           this.showToast({ | ||||
|             message: `${errorMessage}: ${err.responseText}`, | ||||
|             time: 10000, | ||||
|             action: () => { | ||||
|               navigator.clipboard.writeText(err) | ||||
|             } | ||||
|           }) | ||||
| 
 | ||||
|           if (this.backendFallback) { | ||||
|             this.showToast({ | ||||
|               message: this.$t('Falling back to the local API') | ||||
|             }) | ||||
|             resolve(this.getChannelInfoLocal(channelId)) | ||||
|           } else { | ||||
|             resolve([]) | ||||
|           } | ||||
|         }) | ||||
|       }) | ||||
|     }, | ||||
| 
 | ||||
|     getChannelInfoLocal: function (channelId) { | ||||
|       return new Promise((resolve, reject) => { | ||||
|         ytch.getChannelInfo(channelId, 'latest').then(async (response) => { | ||||
|           resolve(response) | ||||
|         }).catch((err) => { | ||||
|           console.log(err) | ||||
|           const errorMessage = this.$t('Local API Error (Click to copy)') | ||||
|           this.showToast({ | ||||
|             message: `${errorMessage}: ${err}`, | ||||
|             time: 10000, | ||||
|             action: () => { | ||||
|               navigator.clipboard.writeText(err) | ||||
|             } | ||||
|           }) | ||||
| 
 | ||||
|           if (this.backendFallback) { | ||||
|             this.showToast({ | ||||
|               message: this.$t('Falling back to the Invidious API') | ||||
|             }) | ||||
|             resolve(this.getChannelInfoInvidious(channelId)) | ||||
|           } else { | ||||
|             resolve([]) | ||||
|           } | ||||
|         }) | ||||
|       }) | ||||
|     }, | ||||
| 
 | ||||
|     ...mapActions([ | ||||
|       'invidiousAPICall', | ||||
|       'updateProfile', | ||||
|       'updateShowProgressBar', | ||||
|       'updateHistory', | ||||
|       'showToast' | ||||
|     ]), | ||||
| 
 | ||||
|     ...mapMutations([ | ||||
|       'setProgressBarPercentage' | ||||
|     ]) | ||||
|   } | ||||
| }) | ||||
|  | @ -0,0 +1 @@ | |||
| @use "../../sass-partials/settings" | ||||
|  | @ -0,0 +1,44 @@ | |||
| <template> | ||||
|   <ft-card | ||||
|     class="relative card" | ||||
|   > | ||||
|     <h3> | ||||
|       {{ $t("Settings.Data Settings.Data Settings") }} | ||||
|     </h3> | ||||
|     <ft-flex-box> | ||||
|       <ft-button | ||||
|         :label="$t('Settings.Data Settings.Import Subscriptions')" | ||||
|         @click="showImportSubscriptionsPrompt = true" | ||||
|       /> | ||||
|       <ft-button | ||||
|         :label="$t('Settings.Data Settings.Export Subscriptions')" | ||||
|         @click="showExportSubscriptionsPrompt = true" | ||||
|       /> | ||||
|       <ft-button | ||||
|         :label="$t('Settings.Data Settings.Import History')" | ||||
|         @click="importHistory" | ||||
|       /> | ||||
|       <ft-button | ||||
|         :label="$t('Settings.Data Settings.Export Subscriptions')" | ||||
|         @click="exportHistory" | ||||
|       /> | ||||
|     </ft-flex-box> | ||||
|     <ft-prompt | ||||
|       v-if="showImportSubscriptionsPrompt" | ||||
|       :label="$t('Settings.Data Settings.Select Import Type')" | ||||
|       :option-names="importSubscriptionsPromptNames" | ||||
|       :option-values="subscriptionsPromptValues" | ||||
|       @click="importSubscriptions" | ||||
|     /> | ||||
|     <ft-prompt | ||||
|       v-if="showExportSubscriptionsPrompt" | ||||
|       :label="$t('Settings.Data Settings.Select Export Type')" | ||||
|       :option-names="exportSubscriptionsPromptNames" | ||||
|       :option-values="subscriptionsPromptValues" | ||||
|       @click="exportSubscriptions" | ||||
|     /> | ||||
|   </ft-card> | ||||
| </template> | ||||
| 
 | ||||
| <script src="./data-settings.js" /> | ||||
| <style scoped lang="sass" src="./data-settings.sass" /> | ||||
|  | @ -20,7 +20,7 @@ | |||
|   top: 60px; | ||||
|   right: 10px; | ||||
|   min-width: 250px; | ||||
|   height: 300px; | ||||
|   height: 400px; | ||||
|   padding: 5px; | ||||
|   background-color: var(--card-bg-color); | ||||
|   box-shadow: 0 1px 2px rgba(0,0,0,.1); | ||||
|  | @ -33,7 +33,7 @@ | |||
| 
 | ||||
| .profileWrapper { | ||||
|   margin-top: 60px; | ||||
|   height: 240px; | ||||
|   height: 340px; | ||||
|   overflow-y: auto; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ export default Vue.extend({ | |||
|     return { | ||||
|       showSearchCachePrompt: false, | ||||
|       showRemoveHistoryPrompt: false, | ||||
|       showRemoveSubscriptionsPrompt: false, | ||||
|       promptValues: [ | ||||
|         'yes', | ||||
|         'no' | ||||
|  | @ -32,6 +33,12 @@ export default Vue.extend({ | |||
|     saveWatchedProgress: function () { | ||||
|       return this.$store.getters.getSaveWatchedProgress | ||||
|     }, | ||||
|     profileList: function () { | ||||
|       return this.$store.getters.getProfileList | ||||
|     }, | ||||
|     removeSubscriptionsPromptMessage: function () { | ||||
|       return this.$t('Settings.Privacy Settings["Are you sure you want to remove all subscriptions and profiles?  This cannot be undone."]') | ||||
|     }, | ||||
|     promptNames: function () { | ||||
|       return [ | ||||
|         this.$t('Yes'), | ||||
|  | @ -62,11 +69,37 @@ export default Vue.extend({ | |||
|       } | ||||
|     }, | ||||
| 
 | ||||
|     handleRemoveSubscriptions: function (option) { | ||||
|       this.showRemoveSubscriptionsPrompt = false | ||||
| 
 | ||||
|       this.updateActiveProfile(0) | ||||
| 
 | ||||
|       if (option === 'yes') { | ||||
|         this.profileList.forEach((profile) => { | ||||
|           if (profile._id === 'allChannels') { | ||||
|             const newProfile = { | ||||
|               _id: 'allChannels', | ||||
|               name: profile.name, | ||||
|               bgColor: profile.bgColor, | ||||
|               textColor: profile.textColor, | ||||
|               subscriptions: [] | ||||
|             } | ||||
|             this.updateProfile(newProfile) | ||||
|           } else { | ||||
|             this.removeProfile(profile._id) | ||||
|           } | ||||
|         }) | ||||
|       } | ||||
|     }, | ||||
| 
 | ||||
|     ...mapActions([ | ||||
|       'updateRememberHistory', | ||||
|       'removeAllHistory', | ||||
|       'updateSaveWatchedProgress', | ||||
|       'clearSessionSearchHistory', | ||||
|       'updateProfile', | ||||
|       'removeProfile', | ||||
|       'updateActiveProfile', | ||||
|       'showToast' | ||||
|     ]) | ||||
|   } | ||||
|  |  | |||
|  | @ -37,6 +37,12 @@ | |||
|         background-color="var(--primary-color)" | ||||
|         @click="showRemoveHistoryPrompt = true" | ||||
|       /> | ||||
|       <ft-button | ||||
|         :label="$t('Settings.Privacy Settings.Remove All Subscriptions / Profiles')" | ||||
|         text-color="var(--text-with-main-color)" | ||||
|         background-color="var(--primary-color)" | ||||
|         @click="showRemoveSubscriptionsPrompt = true" | ||||
|       /> | ||||
|     </ft-flex-box> | ||||
|     <ft-prompt | ||||
|       v-if="showSearchCachePrompt" | ||||
|  | @ -52,6 +58,13 @@ | |||
|       :option-values="promptValues" | ||||
|       @click="handleRemoveHistory" | ||||
|     /> | ||||
|     <ft-prompt | ||||
|       v-if="showRemoveSubscriptionsPrompt" | ||||
|       :label="removeSubscriptionsPromptMessage" | ||||
|       :option-names="promptNames" | ||||
|       :option-values="promptValues" | ||||
|       @click="handleRemoveSubscriptions" | ||||
|     /> | ||||
|   </ft-card> | ||||
| </template> | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,8 +4,9 @@ import FtElementList from '../../components/ft-element-list/ft-element-list.vue' | |||
| import GeneralSettings from '../../components/general-settings/general-settings.vue' | ||||
| import ThemeSettings from '../../components/theme-settings/theme-settings.vue' | ||||
| import PlayerSettings from '../../components/player-settings/player-settings.vue' | ||||
| import PrivacySettings from '../../components/privacy-settings/privacy-settings.vue' | ||||
| import SubscriptionSettings from '../../components/subscription-settings/subscription-settings.vue' | ||||
| import PrivacySettings from '../../components/privacy-settings/privacy-settings.vue' | ||||
| import DataSettings from '../../components/data-settings/data-settings.vue' | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
|   name: 'Settings', | ||||
|  | @ -15,14 +16,8 @@ export default Vue.extend({ | |||
|     'general-settings': GeneralSettings, | ||||
|     'theme-settings': ThemeSettings, | ||||
|     'player-settings': PlayerSettings, | ||||
|     'subscription-settings': SubscriptionSettings, | ||||
|     'privacy-settings': PrivacySettings, | ||||
|     'subscription-settings': SubscriptionSettings | ||||
|   }, | ||||
|   mounted: function () { | ||||
|   }, | ||||
|   methods: { | ||||
|     handleToggleSwitch: function (event) { | ||||
|       console.log(event) | ||||
|     } | ||||
|     'data-settings': DataSettings | ||||
|   } | ||||
| }) | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
|     <player-settings /> | ||||
|     <subscription-settings /> | ||||
|     <privacy-settings /> | ||||
|     <data-settings /> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import Vue from 'vue' | ||||
| import { mapActions } from 'vuex' | ||||
| import FtCard from '../../components/ft-card/ft-card.vue' | ||||
| import FtLoader from '../../components/ft-loader/ft-loader.vue' | ||||
| import FtElementList from '../../components/ft-element-list/ft-element-list.vue' | ||||
|  | @ -121,7 +122,7 @@ export default Vue.extend({ | |||
|         console.log(err) | ||||
|         const errorMessage = this.$t('Invidious API Error (Click to copy)') | ||||
|         this.showToast({ | ||||
|           message: `${errorMessage}: ${err}`, | ||||
|           message: `${errorMessage}: ${err.responseText}`, | ||||
|           time: 10000, | ||||
|           action: () => { | ||||
|             navigator.clipboard.writeText(err) | ||||
|  | @ -137,6 +138,10 @@ export default Vue.extend({ | |||
|           this.isLoading = false | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|     }, | ||||
| 
 | ||||
|     ...mapActions([ | ||||
|       'showToast' | ||||
|     ]) | ||||
|   } | ||||
| }) | ||||
|  |  | |||
|  | @ -178,18 +178,40 @@ Settings: | |||
|     Are you sure you want to remove your entire watch history?: Are you sure you want | ||||
|       to remove your entire watch history? | ||||
|     Watch history has been cleared: Watch history has been cleared | ||||
|     Remove All Subscriptions / Profiles: Remove All Subscriptions / Profiles | ||||
|     Are you sure you want to remove all subscriptions and profiles?  This cannot be undone.: Are you sure you want to remove all subscriptions and profiles?  This cannot be undone. | ||||
|   Subscription Settings: | ||||
|     Subscription Settings: Subscription Settings | ||||
|     Hide Videos on Watch: Hide Videos on Watch | ||||
|     Fetch Feeds from RSS: Fetch Feeds from RSS | ||||
|     Subscriptions Export Format: | ||||
|       Subscriptions Export Format: Subscriptions Export Format | ||||
|       #& Freetube | ||||
|       Newpipe: Newpipe | ||||
|       OPML: OPML | ||||
|     Manage Subscriptions: Manage Subscriptions | ||||
|   Data Settings: | ||||
|     Data Settings: Data Settings | ||||
|     Select Import Type: Select Import Type | ||||
|     Select Export Type: Select Export Type | ||||
|     Import Subscriptions: Import Subscriptions | ||||
|     Import FreeTube: Import FreeTube | ||||
|     Import YouTube: Import YouTube | ||||
|     Import NewPipe: Import NewPipe | ||||
|     Export Subscriptions: Export Subscriptions | ||||
|     Export FreeTube: Export FreeTube | ||||
|     Export YouTube: Export YouTube | ||||
|     Export NewPipe: Export NewPipe | ||||
|     Import History: Import History | ||||
|     Export History: Export History | ||||
|     Profile object has insufficient data, skipping item: Profile object has insufficient data, skipping item | ||||
|     All subscriptions and profiles have been successfully imported: All subscriptions and profiles have been successfully imported | ||||
|     All subscriptions have been successfully imported: All subscriptions have been successfully imported | ||||
|     Invalid subscriptions file: Invalid subscriptions file | ||||
|     This might take a while, please wait: This might take a while, please wait | ||||
|     Invalid history file: Invalid history file | ||||
|     Subscriptions have been successfully exported: Subscriptions have been successfully exported | ||||
|     History object has insufficient data, skipping item: History object has insufficient data, skipping item | ||||
|     All watched history has been successfully imported: All watched history has been successfully imported | ||||
|     All watched history has been successfully exported: All watched history has been successfully exported | ||||
|     Unable to read file: Unable to read file | ||||
|     Unable to write file: Unable to write file | ||||
|     Unknown data key: Unknown data key | ||||
|     How do I import my subscriptions?: How do I import my subscriptions? | ||||
|   Advanced Settings: | ||||
|     Advanced Settings: Advanced Settings | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue