├── .eslintignore ├── .github ├── FUNDING.yml ├── images │ ├── logo │ │ ├── 128x128.png │ │ └── 256x256.png │ ├── sentry │ │ ├── sentry_01.png │ │ └── sentry_02.png │ ├── platform │ │ ├── apple_16x16.png │ │ ├── apple_32x32.png │ │ ├── linux_16x16.png │ │ ├── linux_32x32.png │ │ ├── windows_16x16.png │ │ └── windows_32x32.png │ ├── screenshots │ │ ├── ui_0.1.0.png │ │ ├── ui_0.10.0.png │ │ ├── ui_0.2.0.png │ │ ├── ui_0.3.0.png │ │ ├── ui_0.4.0.png │ │ ├── ui_0.5.0.png │ │ ├── ui_0.6.0.png │ │ ├── ui_0.7.0.png │ │ ├── ui_0.8.0.png │ │ ├── ui_0.9.0.png │ │ └── ui_latest.png │ └── social_preview_github │ │ ├── social_preview.png │ │ └── social_preview.xcf ├── ISSUE_TEMPLATE │ ├── other.md │ ├── question.md │ ├── feature_request.md │ └── bug_report.md ├── workflows │ ├── av_scan.yml │ ├── greetings.yml │ ├── npm_audit.yml │ ├── mark_stale.yml │ ├── jsdoc.yml │ └── electron_builder.yml ├── .pre-commit-config.yaml ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md └── CODE_OF_CONDUCT.md ├── app ├── img │ ├── dummy │ │ └── dummy.png │ ├── icon │ │ └── icon.png │ ├── about │ │ ├── icon_about.png │ │ └── old │ │ │ └── icon_about_.png │ ├── menu │ │ ├── edit │ │ │ ├── copy_red.png │ │ │ ├── cut_red.png │ │ │ ├── redo_red.png │ │ │ ├── undo_red.png │ │ │ ├── paste_red.png │ │ │ ├── redo.svg │ │ │ ├── copy.svg │ │ │ ├── paste.svg │ │ │ ├── undo.svg │ │ │ └── cut.svg │ │ ├── file │ │ │ ├── cog_red.png │ │ │ ├── power-off_red.png │ │ │ ├── power-off.svg │ │ │ └── cog.svg │ │ ├── help │ │ │ ├── github_red.png │ │ │ ├── youtube_red.png │ │ │ ├── terminal_red.png │ │ │ ├── info-circle_red.png │ │ │ ├── cloud-download-alt_red.png │ │ │ ├── info-circle.svg │ │ │ ├── terminal.svg │ │ │ ├── cloud-download-alt.svg │ │ │ ├── youtube.svg │ │ │ └── github.svg │ │ └── window │ │ │ ├── window-maximize_red.png │ │ │ ├── window-minimize_red.png │ │ │ ├── window-minimize.svg │ │ │ └── window-maximize.svg │ ├── notification │ │ ├── icon.png │ │ └── old │ │ │ └── icon_.png │ └── background │ │ └── icon_textarea_log_64x64.png ├── fonts │ ├── Arial │ │ └── Arial.ttf │ └── ArialMono │ │ └── Arial_Monospaced_MT.ttf ├── css │ ├── distraction.css │ └── core.css ├── js │ └── modules │ │ ├── ffmpeg.js │ │ ├── crashReporter.js │ │ ├── unhandled.js │ │ ├── sentry.js │ │ ├── urls.js │ │ ├── settings.js │ │ ├── youtubeDl.js │ │ └── utils.js ├── distraction.html ├── settings.html └── index.html ├── resources └── installer │ ├── icon.ico │ ├── icon.icns │ ├── icons │ ├── 16x16.png │ ├── 24x24.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 64x64.png │ ├── 128x128.png │ ├── 256x256.png │ ├── 512x512.png │ └── 1024x1024.png │ ├── dmg_background.png │ ├── nsis │ └── installer.nsh │ └── base_material │ └── app_icon │ ├── File_Circle-icons-download_svg_Wikipedia.desktop │ └── Circle-icons-download.svg ├── .gitignore ├── test ├── README.md └── spec.js ├── .codacy.yml ├── docs ├── CONTRIBUTORS.md ├── SECURITY.md ├── BUILD.md ├── VERBOSE.md ├── NOTES.md ├── UI-HISTORY.md ├── INSTALL.md ├── SENTRY.md ├── CONTRIBUTING.md └── CHANGELOG.md ├── .editorconfig ├── .eslintrc.json ├── jsdoc.json ├── README.md └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [yafp] 2 | patreon: yafp 3 | -------------------------------------------------------------------------------- /app/img/dummy/dummy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/dummy/dummy.png -------------------------------------------------------------------------------- /app/img/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/icon/icon.png -------------------------------------------------------------------------------- /app/fonts/Arial/Arial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/fonts/Arial/Arial.ttf -------------------------------------------------------------------------------- /app/img/about/icon_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/about/icon_about.png -------------------------------------------------------------------------------- /resources/installer/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/resources/installer/icon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #directories 2 | # 3 | node_modules 4 | dist 5 | 6 | #files 7 | # 8 | #*.tgz 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/img/menu/edit/copy_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/edit/copy_red.png -------------------------------------------------------------------------------- /app/img/menu/edit/cut_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/edit/cut_red.png -------------------------------------------------------------------------------- /app/img/menu/edit/redo_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/edit/redo_red.png -------------------------------------------------------------------------------- /app/img/menu/edit/undo_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/edit/undo_red.png -------------------------------------------------------------------------------- /app/img/menu/file/cog_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/file/cog_red.png -------------------------------------------------------------------------------- /app/img/notification/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/notification/icon.png -------------------------------------------------------------------------------- /resources/installer/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/resources/installer/icon.icns -------------------------------------------------------------------------------- /.github/images/logo/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/logo/128x128.png -------------------------------------------------------------------------------- /.github/images/logo/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/logo/256x256.png -------------------------------------------------------------------------------- /app/img/about/old/icon_about_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/about/old/icon_about_.png -------------------------------------------------------------------------------- /app/img/menu/edit/paste_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/edit/paste_red.png -------------------------------------------------------------------------------- /app/img/menu/help/github_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/help/github_red.png -------------------------------------------------------------------------------- /app/img/menu/help/youtube_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/help/youtube_red.png -------------------------------------------------------------------------------- /.github/images/sentry/sentry_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/sentry/sentry_01.png -------------------------------------------------------------------------------- /.github/images/sentry/sentry_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/sentry/sentry_02.png -------------------------------------------------------------------------------- /app/img/menu/file/power-off_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/file/power-off_red.png -------------------------------------------------------------------------------- /app/img/menu/help/terminal_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/help/terminal_red.png -------------------------------------------------------------------------------- /app/img/notification/old/icon_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/notification/old/icon_.png -------------------------------------------------------------------------------- /resources/installer/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/resources/installer/icons/16x16.png -------------------------------------------------------------------------------- /resources/installer/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/resources/installer/icons/24x24.png -------------------------------------------------------------------------------- /resources/installer/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/resources/installer/icons/32x32.png -------------------------------------------------------------------------------- /resources/installer/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/resources/installer/icons/48x48.png -------------------------------------------------------------------------------- /resources/installer/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/resources/installer/icons/64x64.png -------------------------------------------------------------------------------- /app/img/menu/help/info-circle_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/help/info-circle_red.png -------------------------------------------------------------------------------- /resources/installer/dmg_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/resources/installer/dmg_background.png -------------------------------------------------------------------------------- /resources/installer/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/resources/installer/icons/128x128.png -------------------------------------------------------------------------------- /resources/installer/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/resources/installer/icons/256x256.png -------------------------------------------------------------------------------- /resources/installer/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/resources/installer/icons/512x512.png -------------------------------------------------------------------------------- /.github/images/platform/apple_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/platform/apple_16x16.png -------------------------------------------------------------------------------- /.github/images/platform/apple_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/platform/apple_32x32.png -------------------------------------------------------------------------------- /.github/images/platform/linux_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/platform/linux_16x16.png -------------------------------------------------------------------------------- /.github/images/platform/linux_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/platform/linux_32x32.png -------------------------------------------------------------------------------- /.github/images/screenshots/ui_0.1.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/screenshots/ui_0.1.0.png -------------------------------------------------------------------------------- /.github/images/screenshots/ui_0.10.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/screenshots/ui_0.10.0.png -------------------------------------------------------------------------------- /.github/images/screenshots/ui_0.2.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/screenshots/ui_0.2.0.png -------------------------------------------------------------------------------- /.github/images/screenshots/ui_0.3.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/screenshots/ui_0.3.0.png -------------------------------------------------------------------------------- /.github/images/screenshots/ui_0.4.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/screenshots/ui_0.4.0.png -------------------------------------------------------------------------------- /.github/images/screenshots/ui_0.5.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/screenshots/ui_0.5.0.png -------------------------------------------------------------------------------- /.github/images/screenshots/ui_0.6.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/screenshots/ui_0.6.0.png -------------------------------------------------------------------------------- /.github/images/screenshots/ui_0.7.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/screenshots/ui_0.7.0.png -------------------------------------------------------------------------------- /.github/images/screenshots/ui_0.8.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/screenshots/ui_0.8.0.png -------------------------------------------------------------------------------- /.github/images/screenshots/ui_0.9.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/screenshots/ui_0.9.0.png -------------------------------------------------------------------------------- /.github/images/screenshots/ui_latest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/screenshots/ui_latest.png -------------------------------------------------------------------------------- /resources/installer/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/resources/installer/icons/1024x1024.png -------------------------------------------------------------------------------- /resources/installer/nsis/installer.nsh: -------------------------------------------------------------------------------- 1 | !macro customHeader 2 | ShowInstDetails show 3 | ShowUninstDetails show 4 | !macroend 5 | -------------------------------------------------------------------------------- /.github/images/platform/windows_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/platform/windows_16x16.png -------------------------------------------------------------------------------- /.github/images/platform/windows_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/platform/windows_32x32.png -------------------------------------------------------------------------------- /app/fonts/ArialMono/Arial_Monospaced_MT.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/fonts/ArialMono/Arial_Monospaced_MT.ttf -------------------------------------------------------------------------------- /app/img/menu/window/window-maximize_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/window/window-maximize_red.png -------------------------------------------------------------------------------- /app/img/menu/window/window-minimize_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/window/window-minimize_red.png -------------------------------------------------------------------------------- /app/img/menu/help/cloud-download-alt_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/menu/help/cloud-download-alt_red.png -------------------------------------------------------------------------------- /app/img/background/icon_textarea_log_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/app/img/background/icon_textarea_log_64x64.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other issue template 3 | about: Anything else 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /.github/images/social_preview_github/social_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/social_preview_github/social_preview.png -------------------------------------------------------------------------------- /.github/images/social_preview_github/social_preview.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yafp/media-dupes/HEAD/.github/images/social_preview_github/social_preview.xcf -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/logo/128x128.png) 2 | 3 | # media-dupes 4 | 5 | ## /test/ 6 | 7 | This folder contains test specifications. 8 | -------------------------------------------------------------------------------- /app/img/menu/window/window-minimize.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: You have a question - ask it right away 4 | title: '[Question]' 5 | labels: 'question' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Question** 11 | ... 12 | -------------------------------------------------------------------------------- /app/css/distraction.css: -------------------------------------------------------------------------------- 1 | /* ########################################################################## */ 2 | /* BODY */ 3 | /* ########################################################################## */ 4 | body{ 5 | background-color: black 6 | } 7 | -------------------------------------------------------------------------------- /.codacy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | exclude_paths: 3 | - 'app/lib/**' 4 | - 'docs/**' 5 | - 'docs/**/*' 6 | - 'docs/jsdocs/**/*' 7 | - '.github/**' 8 | - '.github/**/*' 9 | - 'test/*' 10 | - 'LICENSE' 11 | - 'CODE_OF_CONDUCT.md' 12 | - 'README.md' 13 | - 'jsdoc.json' 14 | -------------------------------------------------------------------------------- /app/img/menu/window/window-maximize.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/logo/128x128.png) 2 | 3 | # media-dupes 4 | 5 | ## contributors 6 | 7 | ### code 8 | #### core 9 | * [yafp](https://github.com/yafp/) 10 | 11 | 12 | #### testing 13 | * G 14 | -------------------------------------------------------------------------------- /docs/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only the latest version of media-dupes is supported at all. 6 | 7 | ## Reporting a Vulnerability 8 | Feel free to open an issue and report the vulnerability you have found. In best case featuring a small demo. 9 | -------------------------------------------------------------------------------- /resources/installer/base_material/app_icon/File_Circle-icons-download_svg_Wikipedia.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Link 4 | Name=File:Circle-icons-download.svg - Wikipedia 5 | Comment= 6 | Icon=user-bookmarks 7 | URL=https://en.m.wikipedia.org/wiki/File:Circle-icons-download.svg 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | # top-most EditorConfig file 3 | root = true 4 | 5 | # valid for all files 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | # valid only for markdown files 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /app/img/menu/edit/redo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/menu/edit/copy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/menu/help/info-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/menu/help/terminal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/menu/help/cloud-download-alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "commonjs": true, 6 | "jquery": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "globals": { 10 | "Atomics": "readonly", 11 | "SharedArrayBuffer": "readonly" 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 2018, 15 | "sourceType": "module" 16 | }, 17 | "rules": { 18 | "no-console": 0, 19 | "no-unused-vars": 0, 20 | "indent": ["error", 4] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/img/menu/file/power-off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/menu/help/youtube.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/menu/edit/paste.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/img/menu/edit/undo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/av_scan.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # This action / workflow: 3 | # - does a simple av scan 4 | # - on each push to the master branch 5 | # - using clamAV 6 | # 7 | # Using: https://github.com/marketplace/actions/git-anti-virus-scan 8 | # ---------------------------------------------------------------------------- 9 | 10 | name: av_scan 11 | 12 | on: [push] 13 | 14 | jobs: 15 | gitavscan: 16 | runs-on: ubuntu-latest 17 | name: AV scan 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Git AV Scan 21 | uses: djdefi/gitavscan@v1 22 | -------------------------------------------------------------------------------- /app/img/menu/edit/cut.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '[Feature request]' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # PRE-COMMIT-HOOK 2 | # 3 | # See: https://github.com/pre-commit/pre-commit-hooks 4 | # 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v2.5.0 # Use the ref you want to point at 7 | hooks: 8 | - id: check-case-conflict # Check for files with names that would conflict on a case-insensitive filesystem like MacOS HFS+ or Windows FAT. 9 | - id: check-docstring-first # Checks for a common error of placing code before the docstring. 10 | - id: check-json # Attempts to load all json files to verify syntax. 11 | - id: check-symlinks # Checks for symlinks which do not point to anything. 12 | - id: check-yaml # Attempts to load all yaml files to verify syntax. 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '[Bug]' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. Windows 10] 28 | - media-dupes version [e.g. 0.1.0] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # This action / workflow: 3 | # - greets first time issuer 4 | # - greets first time PR 5 | # 6 | # Using: https://github.com/marketplace/actions/first-interaction 7 | # ---------------------------------------------------------------------------- 8 | 9 | name: greetings 10 | 11 | on: [pull_request, issues] 12 | 13 | jobs: 14 | greeting: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/first-interaction@v1 18 | with: 19 | repo-token: ${{ secrets.GITHUB_TOKEN }} 20 | issue-message: 'Thank you very much for your first issue to this project. It is much appreciated.' 21 | pr-message: 'Thank you very much for your first pull request to this project. It is much appreciated.' 22 | -------------------------------------------------------------------------------- /.github/workflows/npm_audit.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # This action / workflow: 3 | # - executes npm audit 4 | # - on each push to the master branch 5 | # 6 | # Using: https://github.com/marketplace/actions/npm-audit-action 7 | # ---------------------------------------------------------------------------- 8 | 9 | name: npm_audit 10 | 11 | on: 12 | pull_request: 13 | push: 14 | branches: 15 | - master 16 | jobs: 17 | scan: 18 | name: npm audit 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v1 22 | - name: install dependencies 23 | run: npm ci 24 | - uses: oke-py/npm-audit-action@v1.1.0 25 | with: 26 | github_token: ${{ secrets.GITHUB_TOKEN }} 27 | issue_assignees: oke-py 28 | issue_labels: vulnerability,test 29 | -------------------------------------------------------------------------------- /app/img/menu/file/cog.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/BUILD.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/logo/128x128.png) 2 | 3 | # media-dupes 4 | ## Build 5 | 6 | ### Warning 7 | Be aware that you have to **package** and **build** on the platform you want to build for. 8 | Bulding windows on linux works in theory, but this is causing trouble with the ```youtube-dl``` binary. 9 | See [#47](https://github.com/yafp/media-dupes/issues/47) for more details. 10 | 11 | ### Pre 12 | ``` 13 | # Cleaning up node_modules 14 | npm run reinstall 15 | 16 | # Check synthax of .js files 17 | npm run standardx 18 | ``` 19 | 20 | ### Packaging 21 | ``` 22 | # linux 23 | npm run pack-linux 24 | 25 | # mac 26 | npm run pack-mac 27 | 28 | # windows 29 | npm run pack-win 30 | ``` 31 | 32 | 33 | 34 | ### Building 35 | 36 | ``` 37 | # linux 38 | npm run build-linux 39 | 40 | # mac 41 | npm run build-mac 42 | 43 | # windows 44 | npm run build-win 45 | ``` 46 | -------------------------------------------------------------------------------- /app/js/modules/ffmpeg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Contains all ffmpeg functions 3 | * @author yafp 4 | * @module ffmpeg 5 | */ 6 | 'use strict' 7 | 8 | // ---------------------------------------------------------------------------- 9 | // REQUIRE MODULES 10 | // ---------------------------------------------------------------------------- 11 | // 12 | 13 | /** 14 | * @function ffmpegGetBinaryPath 15 | * @summary Get the binary path of the ffmpeg binary 16 | * @description Get the binary path of the ffmpeg binary 17 | * @return {string} ffmpeg.path - The path to the ffmpeg binary 18 | */ 19 | function ffmpegGetBinaryPath () { 20 | var ffmpeg = require('ffmpeg-static-electron') 21 | return (ffmpeg.path) 22 | } 23 | 24 | // ---------------------------------------------------------------------------- 25 | // EXPORT THE MODULE FUNCTIONS 26 | // ---------------------------------------------------------------------------- 27 | // 28 | module.exports.ffmpegGetBinaryPath = ffmpegGetBinaryPath 29 | -------------------------------------------------------------------------------- /.github/workflows/mark_stale.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # This action / workflow: 3 | # - checks for stale issues 4 | # - and pull requests 5 | # - labels them stale after x days 6 | # - closed them after Y days 7 | # 8 | # Using: https://github.com/marketplace/actions/close-stale-issues 9 | # ---------------------------------------------------------------------------- 10 | 11 | name: mark_stale 12 | 13 | on: 14 | schedule: 15 | - cron: "0 0 * * *" 16 | 17 | jobs: 18 | stale: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/stale@v1 24 | with: 25 | repo-token: ${{ secrets.GITHUB_TOKEN }} 26 | stale-issue-message: 'This issue seems to be stale' 27 | stale-pr-message: 'This pull request seems to be stale' 28 | stale-issue-label: 'no-issue-activity' 29 | stale-pr-label: 'no-pr-activity' 30 | days-before-stale: 90 31 | days-before-close: 365 32 | -------------------------------------------------------------------------------- /docs/VERBOSE.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/logo/128x128.png) 2 | 3 | # media-dupes 4 | ## Verbose 5 | 6 | There are several ways to get verbose informations about what **media-dupes** is doing. 7 | 8 | * Log files 9 | * Developer console 10 | * Commandline output 11 | 12 | ### Log files 13 | **media-dupes** is writing log files. You will find them here: 14 | 15 | * Linux: 16 | * `~/.config/media-dupes/logs/main.log` 17 | * `~/.config/media-dupes/logs/renderer.log` 18 | * macOS: 19 | * `~/Library/Logs/media-dupes/main.log` 20 | * `~/Library/Logs/media-dupes/renderer.log` 21 | * Windows: 22 | * `%USERPROFILE%\AppData\Roaming\media-dupes\logs\main.log` 23 | * `%USERPROFILE%\AppData\Roaming\media-dupes\logs\renderer.log` 24 | 25 | ### Developer console 26 | Press F12 within the app or navigate via the `Help` menu and select `Console (F12)` to access the Developer Console. 27 | 28 | ### Commandline output 29 | * Start **media-dupes** with `--verbose` 30 | -------------------------------------------------------------------------------- /docs/NOTES.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/logo/128x128.png) 2 | 3 | # media-dupes 4 | ## developer notes / reminder 5 | 6 | ### todo 7 | * re-enable ASAR 8 | * Is currently disabled cause otherwise in packaged mode the code does not find the youtube-dl executable 9 | * Current output: asar using is disabled — it is strongly not recommended solution=enable asar and use asarUnpack to unpack files that must be externally available 10 | 11 | ### ideas / brainstorming 12 | * Dependencies: 13 | * youtube-dl- node: exec has a callback. https://github.com/przemyslawpluta/node-youtube-dl/issues/216 14 | * ffmpeg: make pull request to https://github.com/pietrop/ffmpeg-static-electron to update from ffmpeg 3.x to 4.x 15 | * 20191210 - Created pull request for ffmpeg 4.2.1 16 | * Progress for stream (non exec use): https://www.npmjs.com/package/progress-stream 17 | * seperate electron-builder.conf from package-json 18 | 19 | ### urls for screenshots: 20 | * https://www.youtube.com/watch?v=WY0Sap4Q0sg 21 | * https://soundcloud.com/jdilla/thank-you-jay-dee-act-3 22 | -------------------------------------------------------------------------------- /.github/workflows/jsdoc.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # This action / workflow: 3 | # - generates a jsdoc based documentation 4 | # - on each commit to the master branch 5 | # - and uploads it to the project github-page 6 | # 7 | # Using: https://github.com/marketplace/actions/jsdoc-action 8 | # 9 | # See: https://yafp.github.io/media-dupes/ 10 | # ---------------------------------------------------------------------------- 11 | 12 | name: jsdoc 13 | 14 | on: 15 | push: 16 | branches: 17 | - master 18 | 19 | jobs: 20 | deploy: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v2 25 | 26 | - name: Build 27 | uses: andstor/jsdoc-action@v1 28 | with: 29 | output_dir: ./out 30 | config_file: jsdoc.json 31 | template_name: docdash 32 | front_page: README.md 33 | 34 | - name: Deploy 35 | uses: peaceiris/actions-gh-pages@v3 36 | with: 37 | deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} 38 | publish_dir: ./out 39 | -------------------------------------------------------------------------------- /app/js/modules/crashReporter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file crashReporter.js 3 | * @fileOverview This module contains all crashReporter functions, See: https://electronjs.org/docs/api/crash-reporter 4 | * @author yafp 5 | * @module crashReporter 6 | */ 7 | 8 | 'use strict' 9 | 10 | /** 11 | * @function initCrashReporter 12 | * @summary Initialized the crashReporter 13 | * @description Initialized the electron crashReporter. See: https://electronjs.org/docs/api/crash-reporter 14 | */ 15 | function initCrashReporter () { 16 | const { crashReporter } = require('electron') 17 | crashReporter.start({ 18 | productName: 'media-dupes', 19 | companyName: 'yafp', 20 | submitURL: 'https://sentry.io/api/1757940/minidump/?sentry_key=bbaa8fa09ca84a8da6a545c04d086859', 21 | uploadToServer: false 22 | }) 23 | 24 | // To simulate a crash - execute: process.crash(); 25 | } 26 | 27 | // ---------------------------------------------------------------------------- 28 | // EXPORT THE MODULE FUNCTIONS 29 | // ---------------------------------------------------------------------------- 30 | // 31 | module.exports.initCrashReporter = initCrashReporter 32 | -------------------------------------------------------------------------------- /app/img/menu/help/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 4 | 5 | Fixes # (issue) 6 | 7 | ## Type of change 8 | 9 | Please delete options that are not relevant. 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 14 | - [ ] This change requires a documentation update 15 | 16 | # How Has This Been Tested? 17 | 18 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration 19 | 20 | - [ ] Test A 21 | - [ ] Test B 22 | 23 | **Test Configuration**: 24 | 25 | 26 | # Checklist: 27 | 28 | - [ ] My code follows the style guidelines of this project 29 | - [ ] I have performed a self-review of my own code 30 | - [ ] I have commented my code, particularly in hard-to-understand areas 31 | - [ ] I have made corresponding changes to the documentation 32 | - [ ] My changes generate no new warnings 33 | - [ ] I have added tests that prove my fix is effective or that my feature works 34 | - [ ] New and existing unit tests pass locally with my changes 35 | - [ ] Any dependent changes have been merged and published in downstream modules 36 | -------------------------------------------------------------------------------- /docs/UI-HISTORY.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/logo/128x128.png) 2 | 3 | # media-dupes 4 | 5 | ## user interface 6 | This page shows how the UI of **media-dupes** changed over the time 7 | 8 | ### 0.1.0 9 | ![ui](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/screenshots/ui_0.1.0.png) 10 | 11 | ### 0.2.0 12 | ![ui](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/screenshots/ui_0.2.0.png) 13 | 14 | ### 0.3.0 15 | ![ui](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/screenshots/ui_0.3.0.png) 16 | 17 | ### 0.4.0 18 | ![ui](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/screenshots/ui_0.4.0.png) 19 | 20 | ### 0.5.0 21 | ![ui](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/screenshots/ui_0.5.0.png) 22 | 23 | ### 0.6.0 24 | ![ui](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/screenshots/ui_0.6.0.png) 25 | 26 | ### 0.7.0 27 | ![ui](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/screenshots/ui_0.7.0.png) 28 | 29 | ### 0.8.0 30 | ![ui](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/screenshots/ui_0.8.0.png) 31 | 32 | ### 0.9.0 33 | ![ui](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/screenshots/ui_0.9.0.png) 34 | 35 | ### 0.10.x 36 | ![ui](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/screenshots/ui_0.10.0.png) 37 | -------------------------------------------------------------------------------- /app/js/modules/unhandled.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file unhandled.js 3 | * @fileOverview This module contains all unhandled functions, See: https://github.com/sindresorhus/electron-unhandled 4 | * @author yafp 5 | * @module unhandled 6 | */ 7 | 8 | 'use strict' 9 | 10 | // ---------------------------------------------------------------------------- 11 | // REQUIRE MODULES 12 | // ---------------------------------------------------------------------------- 13 | // 14 | const { openNewGitHubIssue, debugInfo } = require('electron-util') 15 | const unhandled = require('electron-unhandled') 16 | unhandled({ 17 | showDialog: true, 18 | reportButton: error => { 19 | openNewGitHubIssue({ 20 | user: 'yafp', 21 | repo: 'media-dupes', 22 | body: `\`\`\`\n${error.stack}\n\`\`\`\n\n---\n\n${debugInfo()}` 23 | }) 24 | } 25 | }) 26 | 27 | /** 28 | * @function initUnhandled 29 | * @summary Initializes electron-unhandled 30 | * @description Initializes electron-unhandled. Is used in main and renderer 31 | */ 32 | function initUnhandled () { 33 | const unhandled = require('electron-unhandled') 34 | const { openNewGitHubIssue, debugInfo } = require('electron-util') 35 | 36 | unhandled({ 37 | showDialog: true, 38 | reportButton: error => { 39 | openNewGitHubIssue({ 40 | user: 'yafp', 41 | repo: 'media-dupes', 42 | body: `\`\`\`\n${error.stack}\n\`\`\`\n\n---\n\n${debugInfo()}` 43 | }) 44 | } 45 | }) 46 | } 47 | 48 | // ---------------------------------------------------------------------------- 49 | // EXPORT THE MODULE FUNCTIONS 50 | // ---------------------------------------------------------------------------- 51 | // 52 | module.exports.initUnhandled = initUnhandled 53 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "plugins/markdown", 4 | "plugins/summarize" 5 | ], 6 | "recurseDepth": 10, 7 | "source": { 8 | "include": [ "app/js/", "main.js" ], 9 | "exclude": [ ], 10 | "includePattern": ".+\\.js(doc|x)?$", 11 | "excludePattern": "(^|\\/|\\\\)_" 12 | }, 13 | "sourceType": "module", 14 | "tags": { 15 | "allowUnknownTags": true, 16 | "dictionaries": ["jsdoc","closure"] 17 | }, 18 | "templates": { 19 | "cleverLinks": false, 20 | "monospaceLinks": true 21 | }, 22 | "opts": { 23 | "template": "node_modules/docdash", 24 | "encoding": "utf8", 25 | "destination": "./docs/jsdocs", 26 | "recurse": true, 27 | "verbose": true 28 | }, 29 | "docdash": { 30 | "static": [true], 31 | "sort": [true], 32 | "sectionOrder": [ 33 | "Classes", 34 | "Modules", 35 | "Externals", 36 | "Events", 37 | "Namespaces", 38 | "Mixins", 39 | "Tutorials", 40 | "Interfaces" 41 | ], 42 | "meta": { 43 | "title": "media-dupes jsdoc documentation", 44 | "description": "a simple JSDoc based documentation for media-dupes", 45 | "keyword": "media-dupes" 46 | }, 47 | "search": [true], 48 | "collapse": [false], 49 | "wrap": [true], 50 | "typedefs": [true], 51 | "navLevel": [10], 52 | "private": [true], 53 | "menu": { 54 | "Github": { 55 | "href":"https://github.com/yafp/media-dupes", 56 | "target":"_blank", 57 | "class":"menu-item", 58 | "id":"website_link" 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /docs/INSTALL.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/logo/128x128.png) 2 | 3 | # media-dupes 4 | 5 | ## installation instructions 6 | 7 | ### ![linux](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/platform/linux_32x32.png) linux 8 | 9 | #### .AppImage 10 | * Download the latest .AppImage 11 | * Copy the AppImage to the desired location 12 | * Make it executable: `chmod +x /path/to/local/media-dupes-file.AppImage` 13 | * Execute it 14 | 15 | #### .deb 16 | * Download the latest .deb 17 | * execute: `sudo dpkg -i /path/to/local/media-dupes-file.deb` 18 | 19 | #### .pacman 20 | * Download the latest .pacman 21 | * execute: `sudo pacman -U /path/to/local/media-dupes-file.pacman` 22 | 23 | #### .rpm 24 | * Download the latest .rpm 25 | * execute: 26 | * `sudo dnf localinstall /path/to/local/media-dupes-file.rpm` or 27 | * `sudo yum localinstall /path/to/local/media-dupes-file.rpm` 28 | 29 | #### .snap 30 | * Download the latest .snap 31 | * execute: `sudo snap install /path/to/local/media-dupes-file.snap` 32 | 33 | ### ![apple](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/platform/apple_32x32.png) macOS 34 | #### .dmg 35 | * Download the latest `.dmg` 36 | * Mount the .dmg 37 | * Drag the .app to `/Applications` 38 | 39 | Informations about how to install non-app-store applications on macOS can be found [here](https://apple.stackexchange.com/questions/294013/how-to-allow-install-of-non-app-store-or-identified-developers-on-macos-sierra). 40 | 41 | ### ![windows](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/platform/windows_32x32.png) windows 42 | 43 | It's recommended to use the installer and use it just for the current user. This ensures that updating the bundles youtube-dl works out of the box. 44 | 45 | #### installer 46 | * Download the latest `win-installer.exe` 47 | * Execute the `win-installer.exe` 48 | -------------------------------------------------------------------------------- /resources/installer/base_material/app_icon/Circle-icons-download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.github/workflows/electron_builder.yml: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # This action / workflow: 3 | # - executed on: macOS, linux and windows 4 | # - checksout the source 5 | # - installs some requirements 6 | # - installs some more requirements on linux 7 | # - builds using electron-builder 8 | # - releases if properly tagged 9 | # 10 | # Using: https://github.com/marketplace/actions/electron-builder-action 11 | # ---------------------------------------------------------------------------- 12 | 13 | name: electron_builder 14 | 15 | on: push 16 | 17 | jobs: 18 | release: 19 | runs-on: ${{ matrix.os }} 20 | 21 | strategy: 22 | matrix: 23 | os: [macos-latest, ubuntu-latest, windows-latest] 24 | 25 | steps: 26 | - name: Check out Git repository 27 | uses: actions/checkout@v1 28 | 29 | - name: Install Node.js, NPM and Yarn 30 | uses: actions/setup-node@v1 31 | with: 32 | node-version: 10 33 | 34 | - name: apt-update 35 | if: startsWith(matrix.os, 'ubuntu-latest') 36 | run: sudo apt-get update 37 | 38 | - name: autoremove 39 | if: startsWith(matrix.os, 'ubuntu-latest') 40 | run: sudo apt autoremove 41 | 42 | - name: Install bsdtar rpm on Linux 43 | if: startsWith(matrix.os, 'ubuntu-latest') 44 | run: sudo apt-get install bsdtar rpm 45 | 46 | - name: Install snapcraft 47 | if: startsWith(matrix.os, 'ubuntu-latest') 48 | run: sudo snap install snapcraft --classic 49 | 50 | - name: Build/release Electron app 51 | uses: samuelmeuli/action-electron-builder@v1 52 | with: 53 | # GitHub token, automatically provided to the action 54 | # (No need to define this secret in the repo settings) 55 | github_token: ${{ secrets.github_token }} 56 | 57 | # If the commit is tagged with a version (e.g. "v1.0.0"), 58 | # release the app after building 59 | release: ${{ startsWith(github.ref, 'refs/tags/v') }} 60 | -------------------------------------------------------------------------------- /app/js/modules/sentry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Contains all sentry functions 3 | * @author yafp 4 | * @module sentry 5 | */ 6 | 'use strict' 7 | 8 | const utils = require('./utils.js') 9 | 10 | // ---------------------------------------------------------------------------- 11 | // Error Handling using: sentry 12 | // ---------------------------------------------------------------------------- 13 | // 14 | // https://sentry.io/organizations/yafp/ 15 | // https://docs.sentry.io/platforms/javascript/electron/ 16 | // https://docs.sentry.io/error-reporting/configuration/?platform=electron 17 | // 18 | const Sentry = require('@sentry/electron') 19 | Sentry.init({ 20 | dsn: 'https://4bd3f512a1e34e24ab9838b00f57d131@sentry.io/1847606' 21 | // debug: true 22 | }) 23 | // 24 | // simple way to force a crash: 25 | // myUndefinedFunctionInSentryModul(); 26 | 27 | /** 28 | * @function enableSentry 29 | * @summary Enables sentry 30 | * @description Enabled sentry to log errors 31 | */ 32 | function enableSentry () { 33 | Sentry.getCurrentHub().getClient().getOptions().enabled = true 34 | // utils.writeConsoleMsg('info', 'enableSentry ::: Enabled sentry') 35 | } 36 | 37 | /** 38 | * @function disableSentry 39 | * @summary Disables sentry 40 | * @description Disables sentry to log errors 41 | */ 42 | function disableSentry () { 43 | Sentry.getCurrentHub().getClient().getOptions().enabled = false 44 | // utils.writeConsoleMsg('warn', 'disableSentry ::: Disabled sentry') 45 | } 46 | 47 | /** 48 | * @function countEvent 49 | * @summary Captures a message or event 50 | * @description Captures a message or event 51 | */ 52 | function countEvent (eventName) { 53 | var isReportingEnabled = utils.globalObjectGet('enableErrorReporting') 54 | if (isReportingEnabled === true) { 55 | Sentry.captureMessage(eventName) 56 | } 57 | } 58 | 59 | // ---------------------------------------------------------------------------- 60 | // EXPORT THE MODULE FUNCTIONS 61 | // ---------------------------------------------------------------------------- 62 | // 63 | module.exports.enableSentry = enableSentry 64 | module.exports.disableSentry = disableSentry 65 | module.exports.countEvent = countEvent 66 | -------------------------------------------------------------------------------- /test/spec.js: -------------------------------------------------------------------------------- 1 | console.log('Executing: test/spec.js') 2 | 3 | const Application = require('spectron').Application 4 | const assert = require('assert') 5 | const electronPath = require('electron') // Require Electron from the binaries included in node_modules. 6 | const path = require('path') 7 | 8 | // Load chai assertions 9 | const chaiAsPromised = require('chai-as-promised') 10 | const chai = require('chai') 11 | chai.should() 12 | chai.use(chaiAsPromised) 13 | 14 | var expect = chai.expect 15 | 16 | describe('Application Window', function () { 17 | this.timeout(20000) 18 | 19 | after(function () { 20 | if (this.app && this.app.isRunning()) { 21 | return this.app.stop() 22 | } 23 | }) 24 | 25 | beforeEach(function () { 26 | this.app = new Application({ 27 | // Your electron path can be any binary 28 | // i.e for OSX an example path could be '/Applications/MyApp.app/Contents/MacOS/MyApp' 29 | // But for the sake of the example we fetch it from our node_modules. 30 | path: electronPath, 31 | 32 | // The following line tells spectron to look and use the main.js file 33 | // and the package.json located 1 level above. 34 | args: [path.join(__dirname, '..')] 35 | }) 36 | return this.app.start() 37 | }) 38 | 39 | afterEach(function () { 40 | if (this.app && this.app.isRunning()) { 41 | return this.app.stop() 42 | } 43 | }) 44 | 45 | // TEST: Check launching the app window 46 | // 47 | /* 48 | it('Open application window', function () { 49 | // this.timeout(10000); 50 | wrapper.find('input').simulate('keypress', {key: 'Enter'}) 51 | 52 | // at least 1 window should be counted 53 | return this.app.client.getWindowCount().should.not.equal(0) 54 | }) 55 | */ 56 | 57 | // TEST: Check the window title 58 | // 59 | /* 60 | it('Check window title', function () { 61 | return this.app.client.browserWindow.getTitle().then(function (title) { 62 | expect(title).to.contain('media-dupes') 63 | return Promise.resolve() 64 | }) 65 | }) 66 | */ 67 | }) 68 | -------------------------------------------------------------------------------- /app/distraction.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 37 | 38 | 39 | 40 | 41 |
42 |
43 | 44 |
45 | 46 | 47 | 48 | 49 |
50 |
51 | 52 | Drop = Space
53 | Pause = ESC or P or Return
54 | Movement = Arrow Keys or Mouse or MouseWheel 55 |
56 |
57 |
58 |
59 | 60 | -------------------------------------------------------------------------------- /docs/SENTRY.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/logo/128x128.png) 2 | 3 | # media-dupes 4 | 5 | ## General 6 | **media-dupes** is using [Sentry](https://sentry.io/for/javascript/) by default to: 7 | 8 | * count events 9 | * track errors 10 | 11 | This page tries to explain as detailed as possible what is tracked and why. 12 | 13 | **Please keep in mind:** *Error reporting and click counting* can be disabled in the application settings at any time. 14 | 15 | ## Use cases 16 | ### Event counting 17 | **media-dupes** is simply counting how often specific events happen. Those counts are overall counters for one particular events and counts for all users and therefor not user-specific. 18 | 19 | For example how often the Intro button was pressed. 20 | 21 | The following screenshot shows the related informations: 22 | 23 | ![logo](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/sentry/sentry_02.png) 24 | 25 | The following event get counted: 26 | 27 | * Main User Interface 28 | * Video button pressed 29 | * Audio button pressed 30 | * Overall application starts 31 | * Overall urls handled 32 | * Overall urls failed 33 | * Settings User Interface 34 | * Disabling error reporting in the settings UI 35 | 36 | ### Error reporting 37 | *Error reporting* heavily helps finding bugs in the source code. Right now **media-dupes** is developed only by a single person on a single linux computer. I am not able to run **media-dupes** on Windows or MacOS myself and even on Linux i will never cause and find all bugs myself. Error reports allow me to see what errors happen on might give me a starting point to try to fix them. 38 | 39 | 40 | ## Data 41 | ### What data is stored? 42 | Whenever either an *error* or one of the *event-count* actions is triggered sentry gets informations about 43 | 44 | * `browser.name` 45 | * `chrome` 46 | * `chrome.name` 47 | * `device.family` 48 | * `environment` 49 | * `event_type` 50 | * `level` 51 | * `node` 52 | * `node.name` 53 | * `os_name` 54 | * `release` 55 | * `runtime` 56 | * `runtime.name` 57 | 58 | The following screenshot shows those informations: 59 | 60 | ![logo](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/sentry/sentry_01.png) 61 | 62 | ### What data is not stored? 63 | 64 | * no ip addresses 65 | 66 | ## Questions 67 | Still got questions - feel free to contact me. 68 | -------------------------------------------------------------------------------- /app/js/modules/urls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Contains all urls 3 | * @author yafp 4 | * @module urls 5 | */ 6 | 'use strict' 7 | 8 | // ---------------------------------------------------------------------------- 9 | // DEFINE URL CONSTANTS 10 | // ---------------------------------------------------------------------------- 11 | // 12 | 13 | /** 14 | * @constant 15 | * @type {string} 16 | * @default 17 | */ 18 | const urlGitHubGeneral = 'https://github.com/yafp/media-dupes' // repo url 19 | 20 | /** 21 | * @constant 22 | * @type {string} 23 | * @default 24 | */ 25 | const urlGitHubIssues = 'https://github.com/yafp/media-dupes/issues' // issue url 26 | 27 | /** 28 | * @constant 29 | * @type {string} 30 | * @default 31 | */ 32 | const urlGitHubChangelog = 'https://github.com/yafp/media-dupes/blob/master/docs/CHANGELOG.md' // changelog url 33 | 34 | /** 35 | * @constant 36 | * @type {string} 37 | * @default 38 | */ 39 | const urlGitHubReleases = 'https://github.com/yafp/media-dupes/releases' // releases url 40 | 41 | /** 42 | * @constant 43 | * @type {string} 44 | * @default 45 | */ 46 | const urlGitHubRepoTags = 'https://api.github.com/repos/yafp/media-dupes/tags' // tags api url 47 | 48 | /** 49 | * @constant 50 | * @type {string} 51 | * @default 52 | */ 53 | const urlGithubApiReleases = 'https://api.github.com/repos/yafp/media-dupes/releases' // release api 54 | 55 | /** 56 | * @constant 57 | * @type {string} 58 | * @default 59 | */ 60 | const urlGithubSentryUsage = 'https://github.com/yafp/media-dupes/blob/master/docs/SENTRY.md' // sentry usage 61 | 62 | /** 63 | * @constant 64 | * @type {string} 65 | * @default 66 | */ 67 | const urlYoutubeDlSupportedSites = 'https://ytdl-org.github.io/youtube-dl/supportedsites.html' // supported sites by youtube-dl 68 | 69 | // ---------------------------------------------------------------------------- 70 | // EXPORT THE MODULE FUNCTIONS 71 | // ---------------------------------------------------------------------------- 72 | // 73 | module.exports.urlGitHubGeneral = urlGitHubGeneral 74 | module.exports.urlGitHubIssues = urlGitHubIssues 75 | module.exports.urlGitHubChangelog = urlGitHubChangelog 76 | module.exports.urlGitHubReleases = urlGitHubReleases 77 | module.exports.urlGitHubRepoTags = urlGitHubRepoTags 78 | module.exports.urlGithubApiReleases = urlGithubApiReleases 79 | module.exports.urlGithubSentryUsage = urlGithubSentryUsage 80 | module.exports.urlYoutubeDlSupportedSites = urlYoutubeDlSupportedSites 81 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/logo/128x128.png) 2 | 3 | 4 | # media-dupes 5 | ## Contributing 6 | 7 | ### general 8 | When contributing to this repository, please first discuss the change you wish to make via issue, 9 | email, or any other method with the owners of this repository before making a change. 10 | 11 | ### jsdoc 12 | The current jsdoc documentation can be found on [https://yafp.github.io/media-dupes/](https://yafp.github.io/media-dupes/). It is auto-generated (using [JsDoc Action](https://github.com/marketplace/actions/jsdoc-action)) on each commit of this project. 13 | 14 | 15 | ### Building media-dupes 16 | Building **media-dupes** yourself is pretty easy. Please see the [building instructions](BUILD.md) for more details. 17 | 18 | 19 | ### Sentry (Crashreports) 20 | Crashreport informations are located [here](https://sentry.io/organizations/yafp/issues/?project=1847606). 21 | 22 | 23 | 24 | ### Getting started 25 | 26 | #### 1. First steps 27 | 28 | ##### Clone repo 29 | * Clone the repository: `git clone https://github.com/yafp/media-dupes` 30 | 31 | ##### Install dependencies 32 | * Go into the repository: `cd media-dupes` 33 | * Install dependencies: `npm install` 34 | 35 | ##### Run the code 36 | * Execute: `npm start` 37 | 38 | ##### Run the code with debug logging 39 | * Execute `npm run start-debug` 40 | 41 | ##### Run some basic test 42 | * Execute: `npm test` 43 | 44 | 45 | 46 | #### 2. Misc howto's 47 | 48 | ##### Auditing 49 | 50 | ###### npm auditing (scan for vulnerabilities) 51 | * `npm audit` 52 | 53 | ##### Install packages 54 | 55 | ###### install single package 56 | * `npm install PACKAGENAME --save` 57 | 58 | ###### npm: install single package in specific version 59 | * `npm install PACKAGENAME@1.2.3` 60 | 61 | 62 | ##### Outdated packages 63 | 64 | ###### check for outdated npm packages 65 | * `npm outdated` 66 | 67 | or using `npm-check`: 68 | 69 | * Install requirements: `npm install -g npm-check` 70 | * Run: 71 | * `npm-check` or 72 | * `npm-check -s` (-s = Skip check for unused packages) 73 | 74 | 75 | ##### Updating 76 | 77 | ###### check dependencies 78 | * Install requirements: `npm install depcheck` 79 | * Run check: `depcheck` 80 | 81 | ###### update single package 82 | * `npm install PACKAGENAME --save` 83 | 84 | ###### update all packages 85 | * `npm update` 86 | 87 | 88 | ##### Others 89 | ###### List all package.json scripts 90 | * `npm run` 91 | 92 | or a dynamic solution 93 | 94 | * Install requirements: `npm i -g ntl` 95 | * Execute `ntl` 96 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at fidel@yafp.de. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |
6 |

media-dupes

7 | 8 | a minimal content duplicator for common media services like youtube 9 | 10 | available for: 11 | 12 | ![linux](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/platform/linux_32x32.png) 13 | ![apple](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/platform/apple_32x32.png) 14 | ![windows](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/platform/windows_32x32.png) 15 | 16 | [![DeepScan grade](https://deepscan.io/api/teams/8831/projects/11041/branches/160247/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=8831&pid=11041&bid=160247) 17 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/0c30508f8add43ee8fbb62c2a669e76b)](https://www.codacy.com/manual/yafp/media-dupes?utm_source=github.com&utm_medium=referral&utm_content=yafp/media-dupes&utm_campaign=Badge_Grade) 18 | ![GitHub Actions - electron_builder](https://github.com/yafp/media-dupes/workflows/electron_builder/badge.svg) 19 | ![GitHub Actions - npm audit](https://github.com/yafp/media-dupes/workflows/npm%20audit/badge.svg) 20 | ![GitHub Current Release](https://img.shields.io/github/release/yafp/media-dupes.svg?style=flat) 21 | ![GitHub Release Date](https://img.shields.io/github/release-date/yafp/media-dupes.svg?style=flat) 22 | ![GitHub Download All releases](https://img.shields.io/github/downloads/yafp/media-dupes/total.svg) 23 | ![GitHub Last Commit](https://img.shields.io/github/last-commit/yafp/media-dupes.svg?style=flat) 24 | ![GitHub Issues Open](https://img.shields.io/github/issues-raw/yafp/media-dupes.svg?style=flat) 25 | [![GitHub contributors](https://img.shields.io/github/contributors/yafp/media-dupes.svg)](https://github.com/yafp/media-dupes/graphs/contributors/) 26 | [![Merged PRs](https://img.shields.io/github/issues-pr-closed-raw/yafp/media-dupes.svg?label=merged+PRs)](https://github.com/yafp/media-dupes/pulls?q=is:pr+is:merged) 27 | [![GitHub stars](https://img.shields.io/github/stars/yafp/media-dupes)](https://github.com/yafp/media-dupes/stargazers) 28 | ![GitHub License](https://img.shields.io/github/license/yafp/media-dupes.svg) 29 | [![jsdoc](https://github.com/yafp/media-dupes/workflows/jsdoc/badge.svg)](https://yafp.github.io/media-dupes/) 30 | ![GitHub AV Scan](https://github.com/yafp/media-dupes/workflows/av_scan/badge.svg) 31 | 32 | 33 | ![ui](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/screenshots/ui_latest.png) 34 | 35 |
36 | 37 | # project is discontinued 38 | 39 | # about 40 | **media-dupes** can[1](#footnote1): 41 | 42 | * download video 43 | * download audio 44 | 45 | **media-dupes** is: 46 | 47 | * based on `electron` 48 | * using bundled versions of `youtube-dl` and `ffmpeg` to do it's magic 49 | * available for *freebsd*, *linux*, *macOS* and *windows*. 50 | * free and open source 51 | 52 | # getting started 53 | 54 | ### download 55 | You'll find the latest releases [here](https://github.com/yafp/media-dupes/releases). 56 | 57 | ### install 58 | Please see the [installation instructions](docs/INSTALL.md) for more details. 59 | 60 | ### updates 61 | **media-dupes** checks on application launch if there is a newer `media-dupes` version available. 62 | Updates must be installed manually as automatic updating of electron applications requires that the builds are code-signed, which i can't provide so far. 63 | 64 | **media-dupes** checks as well on application launch if there are updates available for the bundled `youtube-dl` tool. 65 | 66 | **Be aware:** 67 | 68 | Updating the bundled `youtube-dl` package is only possible if the user has write access to the `media-dupes` installation path (which does not work for all available packages/installation types). 69 | 70 | ### changelog 71 | Please see the [changelog](docs/CHANGELOG.md) for more details. 72 | 73 | ### license 74 | Please see the [LICENSE](LICENSE) for more details. 75 | 76 | ### debugging / verbose / logging 77 | Please see the [verbose](docs/VERBOSE.md) informations for more details. 78 | 79 | ### privacy 80 | * **media-dupes** is using sentry to collect error reports and do some anonymous usage stats. This helps heavily finding bugs which might occur only in some specific use-cases. 81 | * reporting is enabled by default, but can be disabled in the application settings UI. 82 | * media-dupes is not tracking it's users (i.e. using Google Analytics or similar) 83 | * no ip addresses are stored 84 | 85 | Please check [here](docs/SENTRY.md) how **media-dupes** is using sentry and why. 86 | 87 | ### discussion 88 | If you have question regarding **media-dupes** click [here](https://github.com/yafp/media-dupes/issues) to create an issue 89 | 90 | ### support / fund 91 | If you want to support the development of **media-dupes** you can fund me on: 92 | 93 | * [github](https://github.com/sponsors/yafp) 94 | * [patreon](https://www.patreon.com/yafp) 95 | 96 | ### disclosure 97 | **media-dupes** is not affiliated with any of the supported apps/services. 98 | 99 | 100 | # developers 101 | You are always welcome to check and even improve the code. 102 | 103 | * Please see the [contributing informations](docs/CONTRIBUTING.md) for more details. 104 | * A list of all contributors can be found [here](docs/CONTRIBUTORS.md). 105 | 106 | 107 | *** 108 | 109 | ###### footnotes 110 | 1: Assuming the provided url is supported by [youtube-dl](https://ytdl-org.github.io/youtube-dl/supportedsites.html). 111 | -------------------------------------------------------------------------------- /app/css/core.css: -------------------------------------------------------------------------------- 1 | /* 2 | bootstrap color codes: https://www.bitdegree.org/learn/bootstrap-colors 3 | primary: #007bff 4 | info: #17a2b8 5 | success: #28a745 6 | warning: #ffc107 7 | danger: #dc3545 8 | dark: #343a40 9 | secondary #6c757d 10 | light #f8f9fa 11 | white #ffffff 12 | */ 13 | 14 | /* ########################################################################## */ 15 | /* FONT */ 16 | /* ########################################################################## */ 17 | @font-face { 18 | font-family: 'Arial'; 19 | src: url('../fonts/Arial/Arial.ttf') format('opentype'); 20 | font-family: 'ArialMono'; 21 | src: url('../fonts/ArialMono/Arial_Monospaced_MT.ttf') format('opentype'); 22 | } 23 | 24 | /* ########################################################################## */ 25 | /* BODY */ 26 | /* ########################################################################## */ 27 | body { 28 | font-family: 'Arial'; 29 | } 30 | 31 | /* ########################################################################## */ 32 | /* BOOTSTRAP MODAL - used as SPLASH */ 33 | /* ########################################################################## */ 34 | /* background color of the modal */ 35 | .modal-backdrop { 36 | opacity: 0.0 !important; 37 | } 38 | 39 | /* removing border from splash */ 40 | .no-border { 41 | border: none !important; 42 | } 43 | 44 | /* ########################################################################## */ 45 | /* THE MAIN UI WINDOW CONTAINER */ 46 | /* ########################################################################## */ 47 | #mainContainer { 48 | animation-delay: 0.5s; /* custom animation delay */ 49 | } 50 | 51 | /* ########################################################################## */ 52 | /* WINDOW BORDER */ 53 | /* ########################################################################## */ 54 | .container-after-titlebar { 55 | border-left: 1px solid #343a40; 56 | border-right: 1px solid #343a40; 57 | border-bottom: 1px solid #343a40; 58 | } 59 | 60 | /* ########################################################################## */ 61 | /* UI TEXT - MAKE THEM NOT SELECTABLE */ 62 | /* ########################################################################## */ 63 | .mediaDupes_nonSelectableText { 64 | -webkit-user-select:none; 65 | -moz-user-select: none; 66 | } 67 | 68 | /* ########################################################################## */ 69 | /* CUSTOM-TITLEBAR */ 70 | /* ########################################################################## */ 71 | .window-title { 72 | font-size: 13px !important; 73 | } 74 | 75 | /* ########################################################################## */ 76 | /* HEADINGS */ 77 | /* ########################################################################## */ 78 | h2 { 79 | text-align: right; 80 | color: gray; 81 | font-size: 24px; 82 | } 83 | 84 | h3 { 85 | text-align: right; 86 | color: gray; 87 | font-size: 14px; 88 | } 89 | 90 | h4 { 91 | color: #c0392b; 92 | } 93 | 94 | h5 { 95 | color: gray; 96 | font-size: 18px; 97 | } 98 | 99 | h6 { 100 | font-size: 14px; 101 | color: gray; 102 | } 103 | 104 | /* ########################################################################## */ 105 | /* LOG TEXTAREA */ 106 | /* ########################################################################## */ 107 | textarea { 108 | font-size: 12px !important; 109 | font-weight: bold; 110 | font-family: 'ArialMono'; 111 | 112 | background-color: #ecf0f1; 113 | background-repeat: no-repeat; 114 | background-position: center; 115 | width: 32px; 116 | height:32px; 117 | } 118 | 119 | #textareaLogOutput { 120 | background-image: url("../img/background/icon_textarea_log_64x64.png"); 121 | min-width: 100%; 122 | width: 100%; 123 | min-height: 200px; 124 | } 125 | 126 | /* ########################################################################## */ 127 | /* DATATABLES */ 128 | /* ########################################################################## */ 129 | /* table head */ 130 | th { 131 | font-size: 12px; 132 | color: gray; 133 | } 134 | 135 | /* table rows */ 136 | td { 137 | font-size: 11px; 138 | color: gray; 139 | } 140 | 141 | /* hover color for rows */ 142 | table#example.dataTable tbody tr:hover { 143 | background-color: #ffe5e5; 144 | } 145 | 146 | table#example.dataTable tbody tr:hover > .sorting_1 { 147 | background-color: #ffe5e5; 148 | } 149 | 150 | .zoomSmall:hover { 151 | transform: scale(1.5); 152 | } 153 | 154 | .zoomBig:hover { 155 | transform: scale(5.0); 156 | } 157 | 158 | #todoList { 159 | height: 200px; 160 | } 161 | 162 | /* ########################################################################## */ 163 | /* LINKs */ 164 | /* ########################################################################## */ 165 | /* unvisited link */ 166 | .classicLink { 167 | color: #c0392b; 168 | } 169 | 170 | .classicLink:hover { 171 | color: #c0392b; 172 | } 173 | 174 | /* ########################################################################## */ 175 | /* ROW EMPTY */ 176 | /* ########################################################################## */ 177 | .row-empty { 178 | height: 15px; 179 | } 180 | 181 | /* ########################################################################## */ 182 | /* BUTTONS */ 183 | /* ########################################################################## */ 184 | .mediaDupes_btnDefaultWidth { 185 | min-width: 100px; 186 | } 187 | 188 | .mediaDupes_btnSmallWidth { 189 | min-width: 50px; 190 | } 191 | 192 | .mediaDupes_btnDownloadActionWidth { 193 | min-width: 160px; 194 | } 195 | 196 | /* ########################################################################## */ 197 | /* APPLICATION STATE */ 198 | /* ########################################################################## */ 199 | #applicationState { 200 | color: gray; 201 | margin: 0 auto; 202 | width: 400px; 203 | min-width: 400px; 204 | text-align: center; 205 | font-size: 12px; 206 | } 207 | 208 | /* ########################################################################## */ 209 | /* BOTTOM */ 210 | /* ########################################################################## */ 211 | .applicationWindowBottom { 212 | margin-left: 15px; 213 | margin-right: 15px; 214 | } 215 | 216 | /* ########################################################################## */ 217 | /* FADEIN THE WINDOW */ 218 | /* ########################################################################## */ 219 | @keyframes fadein { 220 | from { 221 | opacity: 0; 222 | } 223 | to { 224 | opacity: 1; 225 | } 226 | } 227 | 228 | .app { 229 | /* Disable text selection, or your app will feel like a web page */ 230 | -webkit-user-select: none; 231 | -webkit-app-region: drag; 232 | 233 | /* Cover the whole window */ 234 | /* height: 100%; */ 235 | 236 | /* Make sure this matches the native window background color that you pass to 237 | * electron.BrowserWindow({...}), otherwise your app startup will look janky. */ 238 | background: rgb(255, 255, 255); 239 | 240 | /* Smoother startup */ 241 | animation: fadein 0.5s; 242 | } 243 | 244 | /* ########################################################################## */ 245 | /* INTRO.JS - Fixing issues where the UI element was just white - not visible */ 246 | /* see: https://github.com/usablica/intro.js/issues/840 */ 247 | /* ########################################################################## */ 248 | .introjs-helperLayer { 249 | background: transparent !important; 250 | box-shadow: 0 0 0 9999px 251 | rgba(1, 1, 1, 0.8); 252 | } 253 | 254 | .introjs-overlay { 255 | background: transparent !important; 256 | } 257 | 258 | /* ########################################################################## */ 259 | /* FIXING THE HORIZONTAL SCROLLBAR WHICH APPEARS ON i.e. ARCH LINUX on first load */ 260 | /* ########################################################################## */ 261 | ::-webkit-scrollbar { 262 | display: none; 263 | } 264 | 265 | /* ########################################################################## */ 266 | /* SETTINGS: NAV COLOR */ 267 | /* ########################################################################## */ 268 | .nav-link{ 269 | color: gray; 270 | } 271 | 272 | .nav-link:hover { 273 | color: #c0392b; 274 | } 275 | 276 | .nav-item.nav-link.active { 277 | color: #c0392b; 278 | } 279 | 280 | /* ########################################################################## */ 281 | /* PLAYGROUND: Background colors for development */ 282 | /* ########################################################################## */ 283 | .bg_yellow { 284 | background-color: #F0E68C; 285 | } 286 | 287 | .bg_red { 288 | background-color: #FA8072; 289 | } 290 | 291 | .bg_blue { 292 | background-color: #87CEEB; 293 | } 294 | 295 | .bg_green { 296 | background-color: #90EE90; 297 | } 298 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "media-dupes", 3 | "productName": "media-dupes", 4 | "desktopName": "media-dupes.desktop", 5 | "version": "0.10.3", 6 | "description": "a minimal content duplicator for common media services like youtube", 7 | "main": "main.js", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/yafp/media-dupes" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/yafp/media-dupes/issues" 14 | }, 15 | "contributors": [ 16 | { 17 | "name": "Florian Pöck", 18 | "email": "info@yafp.de", 19 | "url": "https://yafp.de" 20 | } 21 | ], 22 | "homepage": "https://github.com/yafp/media-dupes", 23 | "keywords": [ 24 | "aac", 25 | "audio", 26 | "download", 27 | "downloader", 28 | "dupes", 29 | "duplicator", 30 | "extractor", 31 | "ffmpeg", 32 | "flac", 33 | "m4a", 34 | "media", 35 | "media-dupes", 36 | "mediathek", 37 | "mixcloud.com", 38 | "mp3", 39 | "mirror", 40 | "ogg", 41 | "opus", 42 | "soundcloud", 43 | "stream", 44 | "video", 45 | "vorbis", 46 | "wav", 47 | "youtube", 48 | "youtube-dl" 49 | ], 50 | "author": "yafp ", 51 | "license": "GPL-3.0", 52 | "standardx": { 53 | "ignore": [ 54 | "/dist", 55 | "/docs/jsdocs", 56 | "/test/spec.js" 57 | ] 58 | }, 59 | "scripts": { 60 | "start": "electron .", 61 | "start-debug": "electron . --enable-logging", 62 | "start-verbose": "electron . --verbose", 63 | "show-help": "electron . --help", 64 | "show-version": "electron . --version", 65 | "clean": "rm -rf node_modules", 66 | "clean-dist": "rimraf dist/*", 67 | "clean-modules": "rimraf 'node_modules/!(rimraf|.bin)'", 68 | "reinstall": "npm run clean && npm install", 69 | "jsdocs-delete": "rimraf docs/jsdocs/*", 70 | "jsdocs-create": "./node_modules/jsdoc/jsdoc.js --configure jsdoc.json --readme README.md", 71 | "jsdocs-update": "npm run jsdocs-delete && npm run jsdocs-create", 72 | "standardx": "standardx --verbose | snazzy", 73 | "standardx-fix": "standardx --fix --verbose | snazzy", 74 | "test": "mocha", 75 | "lint": "node_modules/eslint/bin/eslint.js app/js --ignore-path=.eslintignore", 76 | "pack-mac": "electron-packager . --overwrite --platform=darwin --arch=x64 --icon=resources/installer/icon.icns --prune=true --out=dist", 77 | "pack-win": "npm run pack-win-32 && npm run pack-win-64", 78 | "pack-win-32": "electron-packager . media-dupes --overwrite --platform=win32 --arch=ia32 --icon=resources/installer/icon.ico --prune=true --out=dist --version-string.CompanyName=yafp --version-string.FileDescription=${productName} --version-string.ProductName=\"media-dupes\"", 79 | "pack-win-64": "electron-packager . media-dupes --overwrite --platform=win32 --arch=x64 --icon=resources/installer/icon.ico --prune=true --out=dist --version-string.CompanyName=yafp --version-string.FileDescription=${productName} --version-string.ProductName=\"media-dupes\"", 80 | "pack-linux": "electron-packager . media-dupes --overwrite --platform=linux --arch=x64 --icon=resources/installer/icons/1024x1024.png --prune=true --out=dist", 81 | "build-mac": "electron-builder --mac --publish=onTagOrDraft", 82 | "build-win": "electron-builder --win --ia32 --x64 --publish=onTagOrDraft", 83 | "build-linux": "electron-builder --linux --publish=onTagOrDraft" 84 | }, 85 | "build": { 86 | "productName": "media-dupes", 87 | "appId": "de.yafp.media-dupes", 88 | "extraFiles": [ 89 | "LICENSE", 90 | "README.md", 91 | "docs/CHANGELOG.md" 92 | ], 93 | "asar": false, 94 | "mac": { 95 | "category": "public.app-category.productivity", 96 | "files": [ 97 | "!node_modules/ffmpeg-static-electron/bin/linux/*", 98 | "!node_modules/ffmpeg-static-electron/bin/win/*" 99 | ], 100 | "artifactName": "${productName}-${version}-mac.${ext}", 101 | "target": [ 102 | "dmg" 103 | ] 104 | }, 105 | "dmg": { 106 | "title": "media-dupes", 107 | "background": "resources/installer/dmg_background.png", 108 | "iconSize": 80, 109 | "iconTextSize": 12, 110 | "contents": [ 111 | { 112 | "x": 300, 113 | "y": 250, 114 | "type": "link", 115 | "path": "/Applications" 116 | }, 117 | { 118 | "x": 300, 119 | "y": 25, 120 | "type": "file" 121 | } 122 | ] 123 | }, 124 | "win": { 125 | "icon": "resources/installer/icon.ico", 126 | "publisherName": "yafp", 127 | "files": [ 128 | "!node_modules/ffmpeg-static-electron/bin/linux/*", 129 | "!node_modules/ffmpeg-static-electron/bin/mac/*" 130 | ], 131 | "artifactName": "${productName}-${version}-win-${arch}.${ext}", 132 | "target": [ 133 | "zip", 134 | "nsis", 135 | "portable" 136 | ] 137 | }, 138 | "portable": { 139 | "unicode": false, 140 | "artifactName": "${productName}-${version}-win-portable-${arch}.${ext}" 141 | }, 142 | "nsis": { 143 | "include": "resources/installer/nsis/installer.nsh", 144 | "deleteAppDataOnUninstall": true, 145 | "unicode": false, 146 | "artifactName": "${productName}-${version}-win-installer-${arch}.${ext}", 147 | "createDesktopShortcut": true, 148 | "createStartMenuShortcut": true, 149 | "oneClick": false, 150 | "allowElevation": true, 151 | "allowToChangeInstallationDirectory": true, 152 | "runAfterFinish": false, 153 | "uninstallDisplayName": "${productName}-${version}" 154 | }, 155 | "snap": { 156 | "publish": [ 157 | { 158 | "provider": "github" 159 | } 160 | ] 161 | }, 162 | "linux": { 163 | "executableName": "media-dupes", 164 | "files": [ 165 | "!node_modules/ffmpeg-static-electron/bin/win/*", 166 | "!node_modules/ffmpeg-static-electron/bin/mac/*" 167 | ], 168 | "artifactName": "${productName}-${version}-linux-${arch}.${ext}", 169 | "description": "a minimal content duplicator for common media services like youtube", 170 | "category": "Network", 171 | "desktop": { 172 | "Terminal": "false", 173 | "Type": "Application", 174 | "Categories": "GTK;GNOME;Network;Utility;AudioVideo;" 175 | }, 176 | "target": [ 177 | { 178 | "target": "deb", 179 | "arch": [ 180 | "x64" 181 | ] 182 | }, 183 | { 184 | "target": "pacman", 185 | "arch": [ 186 | "x64" 187 | ] 188 | }, 189 | { 190 | "target": "zip", 191 | "arch": [ 192 | "x64" 193 | ] 194 | }, 195 | { 196 | "target": "AppImage", 197 | "arch": [ 198 | "x64" 199 | ] 200 | }, 201 | { 202 | "target": "snap", 203 | "arch": [ 204 | "x64" 205 | ] 206 | }, 207 | { 208 | "target": "freebsd", 209 | "arch": [ 210 | "x64" 211 | ] 212 | }, 213 | { 214 | "target": "rpm", 215 | "arch": [ 216 | "x64" 217 | ] 218 | } 219 | ] 220 | }, 221 | "directories": { 222 | "buildResources": "resources/installer/", 223 | "output": "dist/" 224 | }, 225 | "publish": [ 226 | { 227 | "provider": "github", 228 | "owner": "yafp", 229 | "repo": "media-dupes", 230 | "vPrefixedTagName": false 231 | } 232 | ] 233 | }, 234 | "devDependencies": { 235 | "docdash": "^1.2.0", 236 | "electron": "^9.1.2", 237 | "electron-builder": "^22.8.0", 238 | "electron-packager": "^15.0.0", 239 | "eslint": "^7.6.0", 240 | "jsdoc": "^3.6.5", 241 | "ntl": "^5.1.0", 242 | "rimraf": "^3.0.2", 243 | "snazzy": "^8.0.0", 244 | "standardx": "^5.0.0" 245 | }, 246 | "dependencies": { 247 | "bootstrap": "^4.4.1", 248 | "@fortawesome/fontawesome-free": "^5.14.0", 249 | "@sentry/electron": "^1.5.2", 250 | "about-window": "^1.13.4", 251 | "animate.css": "^3.7.2", 252 | "classic-tetris-js": "^1.0.2", 253 | "custom-electron-titlebar": "^3.2.3", 254 | "datatables.net-dt": "^1.10.21", 255 | "datatables.net-scroller-dt": "^2.0.2", 256 | "electron-json-storage": "^4.2.0", 257 | "electron-log": "^4.2.2", 258 | "electron-prompt": "^1.6.0", 259 | "electron-unhandled": "^3.0.2", 260 | "electron-util": "^0.14.2", 261 | "ffmpeg-static-electron": "^2.0.1", 262 | "got": "^11.5.1", 263 | "intro.js": "^2.9.3", 264 | "jquery": "^3.5.1", 265 | "md5": "^2.2.1", 266 | "metascraper": "^5.13.1", 267 | "metascraper-audio": "^5.13.1", 268 | "metascraper-description": "^5.13.1", 269 | "metascraper-image": "^5.13.1", 270 | "metascraper-logo": "^5.13.1", 271 | "metascraper-logo-favicon": "^5.13.1", 272 | "metascraper-media-provider": "^5.13.1", 273 | "metascraper-soundcloud": "^5.13.1", 274 | "metascraper-title": "^5.13.1", 275 | "metascraper-video": "^5.13.1", 276 | "metascraper-youtube": "^5.13.1", 277 | "noty": "^3.2.0-beta", 278 | "popper.js": "^1.16.1", 279 | "semver": "^7.3.2", 280 | "time-stamp": "^2.2.0", 281 | "v8-compile-cache": "^2.1.1", 282 | "yargs": "^15.4.1", 283 | "youtube-dl": "^3.0.2", 284 | "youtube-suggest": "^1.1.0" 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /app/js/modules/settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Contains all settings functions 3 | * @author yafp 4 | * @module settings 5 | */ 6 | 'use strict' 7 | 8 | // ---------------------------------------------------------------------------- 9 | // REQUIRE MODULES 10 | // ---------------------------------------------------------------------------- 11 | // 12 | const utils = require('./utils.js') 13 | const sentry = require('./sentry.js') 14 | const youtubeDl = require('./youtubeDl.js') 15 | 16 | /** 17 | * @function settingsFolderOpen 18 | * @summary Gets triggered from button on settings.html. Triggers code in main.js which opens the directory which contains possible user-settings-files 19 | * @description Gets triggered from button on settings.html. Triggers code in main.js which opens the directory which contains possible user-settings-files 20 | * @memberof renderer 21 | */ 22 | function settingsFolderOpen () { 23 | utils.writeConsoleMsg('info', 'settingsFolderOpen ::: User wants to open the folder with user config files.') 24 | const { ipcRenderer } = require('electron') 25 | ipcRenderer.send('settingsFolderOpen') 26 | } 27 | 28 | /** 29 | * @function settingsOpenDevTools 30 | * @summary Tells the main process to open devTools for settings UI 31 | * @description Tells the main process to open devTools for settings UI 32 | */ 33 | function settingsOpenDevTools () { 34 | const { ipcRenderer } = require('electron') 35 | ipcRenderer.send('settingsToggleDevTools') 36 | } 37 | 38 | /** 39 | * @function settingsSelectDownloadDir 40 | * @summary Let the user choose a custom download target directory 41 | * @description Is triggered via button on settings.html. 42 | */ 43 | function settingsSelectDownloadDir () { 44 | const options = { properties: ['openDirectory'] } 45 | const { dialog } = require('electron').remote 46 | 47 | utils.writeConsoleMsg('info', 'settingsSelectDownloadDir ::: User wants to set a custom download directory. Now opening dialog to select a new download target') 48 | dialog.showOpenDialog(options).then(res => { 49 | utils.writeConsoleMsg('warn', '_' + res.filePaths + '_') 50 | 51 | if (res.filePaths.length === 0) { 52 | utils.writeConsoleMsg('warn', 'settingsSelectDownloadDir ::: User aborted selecting a custom download directory path in settings') 53 | utils.showNoty('info', 'No changes applied.') 54 | } else { 55 | var newDownloadDirectory = res.filePaths.toString() 56 | utils.userSettingWrite('downloadDir', newDownloadDirectory) // save the value to user-config 57 | $('#inputCustomTargetDir').val(newDownloadDirectory) // show it in the UI 58 | utils.writeConsoleMsg('info', 'settingsSelectDownloadDir ::: User selected the following directory: _' + newDownloadDirectory + '_ as download target.') 59 | 60 | // FIXME - is this needed? 61 | // utils.globalObjectSet('downloadDir', newDownloadDirectory) 62 | } 63 | }) 64 | } 65 | 66 | /** 67 | * @function settingsToggleVerboseMode 68 | * @summary Enables or disabled the verbose mode 69 | * @description Enables or disabled the verbode mode 70 | */ 71 | function settingsToggleVerboseMode () { 72 | if ($('#checkboxEnableVerbose').is(':checked')) { 73 | utils.writeConsoleMsg('info', 'settingsToggleVerboseMode ::: Verbose Mode is now enabled') 74 | utils.userSettingWrite('enableVerboseMode', true) 75 | } else { 76 | utils.writeConsoleMsg('info', 'settingsToggleVerboseMode ::: Verbose Mode is now disabled') 77 | utils.userSettingWrite('enableVerboseMode', false) 78 | } 79 | } 80 | 81 | /** 82 | * @function settingsToggleAdditionalParameter 83 | * @summary Enables or disabled the verbose mode 84 | * @description It is disabled by default. Enables or disabled the verbode mode 85 | */ 86 | function settingsToggleAdditionalParameter () { 87 | if ($('#checkboxEnableAdditionalParameter').is(':checked')) { 88 | utils.writeConsoleMsg('info', 'settingsToggleAdditionalParameter ::: Additional parameter is now enabled') 89 | utils.userSettingWrite('enableAdditionalParameter', true) 90 | } else { 91 | utils.writeConsoleMsg('info', 'settingsToggleAdditionalParameter ::: Additional parameter is now disabled') 92 | utils.userSettingWrite('enableAdditionalParameter', false) 93 | } 94 | } 95 | 96 | /** 97 | * @function settingsSaveAdditionalParameter 98 | * @summary Saves the content of the input field for additional parameters 99 | * @description Saves the content of the input field for additional parameters 100 | */ 101 | function settingsSaveAdditionalParameter () { 102 | const youtubeDlUnsupportedParameter = youtubeDl.blacklistedParameter // get the blacklisted parameters 103 | 104 | var inputContainsErrors = false // used to remember if an error occured 105 | 106 | var newAdditionalUserParameters = $('#textInputAdditionalParameter').val() // get value from UI 107 | var splittedParameters = newAdditionalUserParameters.split(' ') // split the input string into single elements 108 | if (splittedParameters.length > 0) { // If there are elements 109 | for (var j = 0; j < splittedParameters.length; j++) { // check for each element 110 | if ($.inArray(splittedParameters[j], youtubeDlUnsupportedParameter) > -1) { // if it is blacklistet 111 | utils.writeConsoleMsg('warn', 'settingsSaveAdditionalParameter ::: The additional parameter: _' + splittedParameters[j] + '_ is blacklisted.') 112 | utils.showNoty('error', 'The additional parameter ' + splittedParameters[j] + ' is blacklisted. Please remove it and try it again.', 0) 113 | inputContainsErrors = true 114 | } else { // or not 115 | utils.writeConsoleMsg('info', 'settingsSaveAdditionalParameter ::: The additional parameter: _' + splittedParameters[j] + '_ is not blacklisted.') 116 | } 117 | } 118 | } 119 | 120 | if (inputContainsErrors === false) { 121 | utils.writeConsoleMsg('info', 'settingsSaveAdditionalParameter ::: Saving additional parameters - new value is: _' + newAdditionalUserParameters + '_.') 122 | utils.userSettingWrite('additionalYoutubeDlParameter', newAdditionalUserParameters) 123 | } 124 | } 125 | 126 | /** 127 | * @function settingsTogglePrereleases 128 | * @summary Enables or disables the support for pre-releases 129 | * @description Enables or disables the setting to include pre-releases into the update -search 130 | */ 131 | function settingsTogglePrereleases () { 132 | if ($('#checkboxEnablePreReleases').is(':checked')) { 133 | utils.writeConsoleMsg('info', 'settingsTogglePrereleases ::: Update-Search will now include pre-releases') 134 | utils.userSettingWrite('enablePrereleases', true) 135 | } else { 136 | utils.writeConsoleMsg('info', 'settingsTogglePrereleases ::: Update-Search will ignore pre-releases') 137 | utils.userSettingWrite('enablePrereleases', false) 138 | } 139 | } 140 | 141 | /** 142 | * @function settingsToggleErrorReporting 143 | * @summary Enables or disabled the error reporting function 144 | * @description Enables or disabled the error reporting function 145 | */ 146 | function settingsToggleErrorReporting () { 147 | if ($('#checkboxEnableErrorReporting').is(':checked')) { 148 | utils.writeConsoleMsg('info', 'settingsToggleErrorReporting ::: Error reporting is now enabled') 149 | utils.userSettingWrite('enableErrorReporting', true) 150 | sentry.enableSentry() 151 | } else { 152 | // ask if user really wants to disable error-reporting (using a confirm dialog) 153 | const Noty = require('noty') 154 | var n = new Noty( 155 | { 156 | theme: 'bootstrap-v4', 157 | layout: 'bottom', 158 | type: 'info', 159 | closeWith: [''], // to prevent closing the confirm-dialog by clicking something other then a confirm-dialog-button 160 | text: 'Do you really want to disable reporting?

* We don\'t track users
* We don\'t store any IP informations
* We only collect error reports and event counts

This helps improving media-dupes', 161 | buttons: [ 162 | Noty.button('Yes', 'btn btn-danger mediaDupes_btnDownloadActionWidth', function () { 163 | n.close() 164 | utils.writeConsoleMsg('info', 'settingsToggleErrorReporting ::: Error reporting is now disabled') 165 | utils.userSettingWrite('enableErrorReporting', false) 166 | sentry.disableSentry() 167 | sentry.countEvent('usageSettingsErrorReportingDisabled') 168 | // myUndefinedFunctionFromRendererAfterDisable() 169 | }, 170 | { 171 | id: 'button1', 'data-status': 'ok' 172 | }), 173 | 174 | Noty.button('No', 'btn btn-success mediaDupes_btnDownloadActionWidth float-right', function () { 175 | n.close() 176 | $('#checkboxEnableErrorReporting').prop('checked', true) // revert state of checkbox 177 | utils.showNoty('success', 'Thanks for supporting media-dupes development with your error reports.') 178 | utils.writeConsoleMsg('info', 'settingsToggleErrorReporting ::: User cancelled disabling of error-reporting') 179 | }) 180 | ] 181 | }) 182 | 183 | n.show() // show the noty dialog 184 | } 185 | } 186 | 187 | /** 188 | * @function settingsAudioFormatSave 189 | * @summary Fetches the value from the audio-format select in the settings UI and triggers the update of the related user-settings-file 190 | * @description Fetches the value from the audio-format select in the settings UI and triggers the update of the related user-settings-file 191 | */ 192 | function settingsAudioFormatSave () { 193 | var userSelectedAudioFormat = $('#inputGroupSelectAudio').val() // get value from UI select inputGroupSelectAudio 194 | utils.writeConsoleMsg('info', 'settingsAudioFormatSave ::: User selected the audio format: _' + userSelectedAudioFormat + '_.') 195 | utils.userSettingWrite('audioFormat', userSelectedAudioFormat) // store this value in a json file 196 | } 197 | 198 | /** 199 | * @function settingsOpenExternal 200 | * @summary Gets an url from the settings ui and forwards this to the openURL function 201 | * @description Gets an url from the settings ui and forwards this to the openURL function 202 | * @param {string} url - the actual url 203 | */ 204 | function settingsOpenExternal (url) { 205 | utils.openURL(url) 206 | } 207 | 208 | function settingsEnableOrDisableYoutubeDLUpdateButton () { 209 | // check if youtube details file is writeable 210 | // if not - disable thje update button 211 | var youtubeDlBinaryDetailsPath = youtubeDl.binaryDetailsPathGet() 212 | utils.canWriteFileOrFolder(youtubeDlBinaryDetailsPath, function (error, isWritable) { 213 | if (error) { 214 | utils.writeConsoleMsg('error', 'settingsEnableOrDisableYoutubeDLUpdateButton ::: Error while trying to read the youtube-dl details file. Error: ' + error) 215 | throw error 216 | } 217 | 218 | if (isWritable === true) { 219 | // technically we could execute an update if there is one - so lets search for updates 220 | utils.writeConsoleMsg('info', 'settingsEnableOrDisableYoutubeDLUpdateButton ::: Updating youtube-dl binary is technically possible. Button can stay enabled') 221 | $('#buttonSettingsYoutubeDLStartUpdate').prop('title', 'Updating youtube-dl is supported based on your installation permissions.') // set a title to the button 222 | } else { 223 | // details file cant be resetted due to permission issues 224 | utils.writeConsoleMsg('warn', 'settingsEnableOrDisableYoutubeDLUpdateButton ::: Updating youtube-dl binary is not possible on this setup due to permission issues.') 225 | $('#buttonSettingsYoutubeDLStartUpdate').prop('disabled', true) // disable the button 226 | $('#buttonSettingsYoutubeDLStartUpdate').prop('title', 'Updating youtube-dl is disabled based on your installation permissions. The needed file is not writeable') // set a title to the button 227 | } 228 | }) 229 | } 230 | 231 | // ---------------------------------------------------------------------------- 232 | // EXPORT THE MODULE FUNCTIONS 233 | // ---------------------------------------------------------------------------- 234 | // 235 | module.exports.settingsFolderOpen = settingsFolderOpen 236 | module.exports.settingsOpenDevTools = settingsOpenDevTools 237 | module.exports.settingsSelectDownloadDir = settingsSelectDownloadDir 238 | module.exports.settingsToggleVerboseMode = settingsToggleVerboseMode 239 | module.exports.settingsToggleAdditionalParameter = settingsToggleAdditionalParameter 240 | module.exports.settingsSaveAdditionalParameter = settingsSaveAdditionalParameter 241 | module.exports.settingsTogglePrereleases = settingsTogglePrereleases 242 | module.exports.settingsToggleErrorReporting = settingsToggleErrorReporting 243 | module.exports.settingsAudioFormatSave = settingsAudioFormatSave 244 | module.exports.settingsOpenExternal = settingsOpenExternal 245 | module.exports.settingsEnableOrDisableYoutubeDLUpdateButton = settingsEnableOrDisableYoutubeDLUpdateButton 246 | -------------------------------------------------------------------------------- /app/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | media-dupes - settings 30 | 31 | 37 | 38 | 39 | 40 |
41 | 42 | 43 |
44 |   45 |
46 |
47 | 48 | 49 |
50 |

Settings

51 | 52 |
53 | 54 | 55 |
56 |   57 |
58 | 59 | 60 |
61 | 69 |
70 | 71 | 72 | 73 |
74 |   75 |
76 | 77 | 78 |
79 | 80 | 81 |
82 | 83 | 84 |
85 |
Download directory
86 |
87 |
88 |
89 | 90 |
91 | 92 |
93 |
94 |
95 | 96 | 97 |
98 |
Verbose mode
99 |
100 |
101 |
102 |
103 |
104 | 105 |
106 |
107 | 108 |
109 |
110 |
111 | 112 | 113 | 114 | 115 |
116 | 117 |
118 |
Update policy
119 |
120 |
121 |
122 |
123 | 124 |
125 |
126 | 127 |
128 |
129 | 130 | 131 | 132 | 133 |
134 | 135 |
136 |
Audio format
137 |
138 |
139 |
140 | 150 |
151 |
152 |
153 | 154 | 155 | 156 | 157 |
158 | 159 | 160 |
161 |
Installation
162 |
163 |
164 |
165 | 166 |
167 | 168 | 169 |
170 |
171 |
172 | 173 | 174 |
175 |
Additional youtube-dl parameter
176 |
177 |
178 |
179 |
180 |
181 | 182 |
183 |
184 | 185 | 186 |
187 |
188 | 189 |
190 | 191 | 192 | 193 | 194 |
195 | 196 |
197 |
Installation
198 |
199 |
200 |
201 | 202 |
203 | 204 |
205 |
206 |
207 |
208 | 209 | 210 | 211 | 212 |
213 | 214 |
215 |
Error Reporting & Event Counting
216 |
217 |
218 |
219 |
220 | 221 |
222 |
223 | 224 |
225 |
226 |
Additional informations about media-dupes error reporting and event counting can be found here.
227 |
228 |
229 | 230 | 231 |
232 | 233 | 234 |
235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /app/js/modules/youtubeDl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Contains all youtubeDl functions 3 | * @author yafp 4 | * @module youtubeDl 5 | */ 6 | 'use strict' 7 | 8 | // ---------------------------------------------------------------------------- 9 | // REQUIRE MODULES 10 | // ---------------------------------------------------------------------------- 11 | // 12 | const utils = require('./utils.js') 13 | const ui = require('./ui.js') 14 | 15 | // ---------------------------------------------------------------------------- 16 | // VARIABLES 17 | // ---------------------------------------------------------------------------- 18 | // 19 | var youtubeDlBinaryDetailsVersion 20 | var youtubeDlBinaryDetailsPath 21 | var youtubeDLBinaryDetailsExec 22 | 23 | // ---------------------------------------------------------------------------- 24 | // YOUTUBE-DL: Arguments which should be filtered out if the user sets them. 25 | // Idea from: https://github.com/mayeaux/videodownloader/blob/master/util.js 26 | // ---------------------------------------------------------------------------- 27 | /** 28 | * @constant 29 | * @type {array} 30 | * @default 31 | */ 32 | const blacklistedParameter = [ 33 | '-h', 34 | '--help', 35 | '-v', 36 | '--version', 37 | '-U', 38 | '--update', 39 | '-q', 40 | '--quiet', 41 | '-s', 42 | '--simulate', 43 | '-g', 44 | '--get-url', 45 | '-e', 46 | '--get-title', 47 | '--get-id', 48 | '--get-thumbnail', 49 | '--get-description', 50 | '--get-duration', 51 | '--get-filename', 52 | '--get-format', 53 | '-j', 54 | '--dump-json', 55 | '--newline', 56 | '--no-progress', 57 | '--console-title', 58 | '--dump-intermediate-pages', 59 | '--write-pages' 60 | ] 61 | 62 | // ---------------------------------------------------------------------------- 63 | // YOUTUBE-DL: Binary Details 64 | // ---------------------------------------------------------------------------- 65 | 66 | /** 67 | * @function binaryDetailsPathGet 68 | * @summary Gets the path to the youtube-dl binary details file 69 | * @description Gets the path to the youtube-dl binary details file 70 | * @return {string} youtubeDlBinaryDetailsPath - The actual path to the youtube-dl details file 71 | */ 72 | function binaryDetailsPathGet () { 73 | const path = require('path') 74 | const remote = require('electron').remote 75 | const app = remote.app 76 | 77 | var youtubeDlBinaryDetailsPath = path.join(app.getAppPath(), 'node_modules', 'youtube-dl', 'bin', 'details') 78 | return (youtubeDlBinaryDetailsPath) 79 | } 80 | 81 | /** 82 | * @function binaryDetailsValueGet 83 | * @summary Gets all values from the youtube-dl binary details file 84 | * @description Gets all values from the youtube-dl binary details file 85 | */ 86 | function binaryDetailsValueGet (_callback) { 87 | const fs = require('fs') 88 | youtubeDlBinaryDetailsPath = binaryDetailsPathGet() // get the path to the details file 89 | 90 | fs.readFile(youtubeDlBinaryDetailsPath, 'utf8', function (error, contents) { 91 | if (error) { 92 | utils.writeConsoleMsg('error', 'binaryDetailsValueGet ::: Unable to read youtube-dl binary details values. Error: ' + error + '.') 93 | throw error 94 | } else { 95 | const data = JSON.parse(contents) 96 | 97 | youtubeDlBinaryDetailsVersion = data.version 98 | youtubeDlBinaryDetailsPath = data.path 99 | youtubeDLBinaryDetailsExec = data.exec 100 | 101 | utils.writeConsoleMsg('info', 'binaryDetailsValueGet ::: Version: ' + youtubeDlBinaryDetailsVersion + '.') 102 | utils.writeConsoleMsg('info', 'binaryDetailsValueGet ::: Path: ' + youtubeDlBinaryDetailsPath + '.') 103 | utils.writeConsoleMsg('info', 'binaryDetailsValueGet ::: Exec: ' + youtubeDLBinaryDetailsExec + '.') 104 | 105 | // console.info(data[value]) 106 | // var currentDetailsValue = data[value] 107 | 108 | // utils.writeConsoleMsg('warn', 'binaryDetailsValueGet ::: youtube-dl binary details value is version: _' + currentDetailsValue + '_.') 109 | _callback() 110 | } 111 | }) 112 | } 113 | 114 | // ---------------------------------------------------------------------------- 115 | // YOUTUBE-DL: Binary Path 116 | // ---------------------------------------------------------------------------- 117 | 118 | /** 119 | * @function binaryPathGet 120 | * @summary Gets the path to the youtube-dl binary file 121 | * @description Gets the path to the youtube-dl binary file using getYtdlBinary() 122 | * @return {string} youtubeDlBinaryPath - The actual path to the youtube-dl binary 123 | */ 124 | function binaryPathGet () { 125 | const youtubedl = require('youtube-dl') 126 | var youtubeDlBinaryPath 127 | youtubeDlBinaryPath = youtubedl.getYtdlBinary() // get the path of the binary 128 | // utils.writeConsoleMsg('info', 'binaryPathGet ::: youtube-dl binary path is: _' + youtubeDlBinaryPath + '_.') 129 | return (youtubeDlBinaryPath) 130 | } 131 | 132 | /** 133 | * @function binaryPathReset 134 | * @summary Resets the youtube-dl binary path in details 135 | * @description Resets the youtube-dl binary path in details 136 | * @param {string} path - The path to the youtube-dl details file 137 | */ 138 | function binaryPathReset (path) { 139 | const fs = require('fs') 140 | 141 | fs.readFile(path, 'utf8', function (error, contents) { 142 | if (error) { 143 | utils.writeConsoleMsg('error', 'binaryPathReset ::: Error while trying to read the youtube-dl path. Error: ' + error) 144 | utils.showNoty('error', 'Unable to read the youtube-dl binary details file. Error: ' + error) 145 | throw error 146 | } else { 147 | const data = JSON.parse(contents) 148 | var youtubeDlBinaryPath = data.path 149 | utils.writeConsoleMsg('info', 'binaryPathReset ::: youtube-dl binary path was: _' + youtubeDlBinaryPath + '_ before reset.') 150 | 151 | // now update it 152 | if (youtubeDlBinaryPath !== null) { 153 | // update it back to default 154 | data.path = null 155 | fs.writeFileSync(path, JSON.stringify(data)) 156 | utils.writeConsoleMsg('info', 'binaryPathReset ::: Did reset the youtube-dl binary path back to default.') 157 | utils.showNoty('success', 'Did reset the youtube-dl binary path back to default') 158 | } else { 159 | // nothing to do 160 | utils.writeConsoleMsg('info', 'binaryPathReset ::: youtube-dl binary path is: _' + youtubeDlBinaryPath + '_. This is the default') 161 | utils.showNoty('info', 'The youtube-dl binary path was already on its default value. Did no changes.') 162 | } 163 | } 164 | }) 165 | } 166 | 167 | // ---------------------------------------------------------------------------- 168 | // YOUTUBE-DL: Binary Update 169 | // ---------------------------------------------------------------------------- 170 | 171 | /** 172 | * @function binaryUpdateCheck 173 | * @summary Checks if the yoututbe-dl setup allows updating or not 174 | * @description Checks if the yoututbe-dl setup allows updating or not 175 | * @param {boolean} [silent] - Boolean with default value. Shows a feedback in case of no available updates If 'silent' = false. Special handling for manually triggered update search 176 | * @param {boolean} [force] - If progressing is forced or not 177 | */ 178 | function binaryUpdateCheck (silent = true, force = false) { 179 | ui.windowMainLoadingAnimationShow() 180 | ui.windowMainApplicationStateSet('Searching updates for youtube-dl binary') 181 | 182 | // check if we could update in general = is details file writeable? 183 | // if not - we can cancel right away 184 | var youtubeDlBinaryDetailsPath = binaryDetailsPathGet() 185 | utils.canWriteFileOrFolder(youtubeDlBinaryDetailsPath, function (error, isWritable) { 186 | if (error) { 187 | utils.writeConsoleMsg('error', 'binaryUpdateCheck ::: Error while trying to read the youtube-dl details file. Error: ' + error) 188 | throw error 189 | } 190 | 191 | if (isWritable === true) { 192 | // technically we could execute an update if there is one - so lets search for updates 193 | utils.writeConsoleMsg('info', 'binaryUpdateCheck ::: Updating youtube-dl binary is technically possible - so start searching for available youtube-dl updates. Silent: _' + silent + '_ and Force: _' + force + '_.') 194 | // var isYoutubeBinaryUpdateAvailable = binaryUpdateSearch(silent, force) 195 | binaryUpdateSearch(silent, force) 196 | } else { 197 | // details file cant be resetted due to permission issues 198 | utils.writeConsoleMsg('warn', 'binaryUpdateCheck ::: Updating youtube-dl binary is not possible on this setup due to permission issues.') 199 | 200 | if (silent === false) { 201 | utils.showNoty('error', 'Unable to update youtube-dl as ' + youtubeDlBinaryDetailsPath + ' is not writeable. This depends most likely on the package/installation type you selected') 202 | ui.windowMainLoadingAnimationHide() 203 | ui.windowMainApplicationStateSet() 204 | } 205 | } 206 | }) 207 | } 208 | 209 | /** 210 | * @function binaryUpdateSearch 211 | * @summary Searches for youtube-dl binary updates 212 | * @description Searches for youtube-dl binary updates 213 | * @param {boolean} silent - Defaults to true. If true, the progress is silent, if false there is info-feedback even if there is no update available 214 | * @param {boolean} force - Defaults to false. If enabled the update is forced even if there isa no update available (replacing the existing) 215 | */ 216 | function binaryUpdateSearch (silent = true, force = false) { 217 | var remoteAppVersionLatest = '0.0.0' 218 | var localAppVersion = '0.0.0' 219 | var versions 220 | 221 | const urlYTDLGitHubRepoTags = 'https://api.github.com/repos/ytdl-org/youtube-dl/tags' // set youtube-dl API url 222 | utils.writeConsoleMsg('info', 'binaryUpdateSearch ::: Start checking _' + urlYTDLGitHubRepoTags + '_ for available youtube-dl releases. Parameters are silent: _' + silent + '_ and force: _' + force + '_.') 223 | 224 | // var updateStatus = $.get(urlYTDLGitHubRepoTags, function (data, status) { 225 | $.get(urlYTDLGitHubRepoTags, function (data, status) { 226 | // 3000 // in milliseconds 227 | 228 | utils.writeConsoleMsg('info', 'binaryUpdateSearch ::: Accessing _' + urlYTDLGitHubRepoTags + '_ ended with: _' + status + '_') 229 | 230 | // success 231 | versions = data.sort(function (v1, v2) { 232 | // return semver.compare(v2.name, v1.name); 233 | }) 234 | 235 | // get remote version 236 | // 237 | remoteAppVersionLatest = versions[0].name 238 | utils.writeConsoleMsg('info', 'binaryUpdateSearch ::: Latest Remote version is: ' + remoteAppVersionLatest) 239 | // remoteAppVersionLatest = '66.6.6'; // overwrite variable to simulate available updates 240 | 241 | // get local version 242 | binaryDetailsValueGet(function () { 243 | utils.writeConsoleMsg('info', 'binaryUpdateSearch ::: Fetched all values from details file') 244 | localAppVersion = youtubeDlBinaryDetailsVersion 245 | 246 | utils.writeConsoleMsg('info', 'binaryUpdateSearch ::: Local youtube-dl binary version: ' + localAppVersion) 247 | utils.writeConsoleMsg('info', 'binaryUpdateSearch ::: Latest youtube-dl binary version: ' + remoteAppVersionLatest) 248 | 249 | if (force === true) { 250 | binaryUpdateExecute() // we are updating - ignoring what is currently installed 251 | } else { 252 | if (localAppVersion < remoteAppVersionLatest) { 253 | utils.writeConsoleMsg('info', 'binaryUpdateSearch ::: Found update for youtube-dl binary. Ask the user if he wants to execute the update now') 254 | 255 | // ask user if he wants to open all those urls 256 | const Noty = require('noty') 257 | var n = new Noty( 258 | { 259 | theme: 'bootstrap-v4', 260 | layout: 'bottom', 261 | type: 'info', 262 | closeWith: [''], // to prevent closing the confirm-dialog by clicking something other then a confirm-dialog-button 263 | text: 'Do you want to update youtube-dl now from ' + localAppVersion + ' to ' + remoteAppVersionLatest + '?', 264 | buttons: [ 265 | Noty.button('Yes', 'btn btn-success mediaDupes_btnDefaultWidth', function () { 266 | n.close() 267 | binaryUpdateExecute() 268 | }, 269 | { 270 | id: 'button1', 'data-status': 'ok' 271 | }), 272 | Noty.button('No', 'btn btn-secondary mediaDupes_btnDefaultWidth float-right', function () { 273 | n.close() 274 | }) 275 | ] 276 | }) 277 | 278 | n.show() // show the noty dialog 279 | } else { 280 | if (silent === false) { 281 | utils.showNoty('info', 'No youtube-dl binary update available
You are already using the latest binary version of youtube-dl') 282 | } 283 | } 284 | } 285 | }) 286 | 287 | utils.writeConsoleMsg('info', 'binaryUpdateSearch ::: Successfully checked ' + urlYTDLGitHubRepoTags + ' for available releases') 288 | }) 289 | .done(function () { 290 | // utils.writeConsoleMsg('info', 'binaryUpdateSearch ::: Successfully checked ' + urlGitHubRepoTags + ' for available releases'); 291 | }) 292 | 293 | .fail(function () { 294 | utils.writeConsoleMsg('info', 'binaryUpdateSearch ::: Checking ' + urlYTDLGitHubRepoTags + ' for available releases failed.') 295 | utils.showNoty('error', 'Checking ' + urlYTDLGitHubRepoTags + ' for available releases failed. Please troubleshoot your network connection.', 0) 296 | }) 297 | 298 | .always(function () { 299 | utils.writeConsoleMsg('info', 'binaryUpdateSearch ::: Finished checking ' + urlYTDLGitHubRepoTags + ' for available releases') 300 | ui.windowMainLoadingAnimationHide() 301 | ui.windowMainButtonsOthersEnable() 302 | ui.windowMainApplicationStateSet() 303 | }) 304 | } 305 | 306 | /** 307 | * @function binaryUpdateExecute 308 | * @summary Updates the youtube-dl binary 309 | * @description Updates the youtube-dl binary 310 | */ 311 | function binaryUpdateExecute () { 312 | // const youtubedl = require('youtube-dl') 313 | const downloader = require('youtube-dl/lib/downloader') 314 | 315 | const remote = require('electron').remote 316 | const app = remote.app 317 | const path = require('path') 318 | const targetPath = path.join(app.getPath('userData'), 'youtube-dl') // set targetPath 319 | 320 | // start downloading latest youtube-dl binary to custom path 321 | downloader(targetPath, function error (error, done) { 322 | 'use strict' 323 | if (error) { 324 | utils.writeConsoleMsg('error', 'binaryUpdateExecute ::: Error while trying to update the youtube-dl binary at: _' + targetPath + '_. Error: ' + error) 325 | utils.showNoty('error', 'Unable to update youtube-dl binary. ' + error, 0) 326 | throw error 327 | } 328 | utils.writeConsoleMsg('info', 'binaryUpdateExecute ::: Updated youtube-dl binary at: _' + targetPath + '_.') 329 | console.log(done) 330 | utils.showNoty('success', done) 331 | }) 332 | } 333 | 334 | // ---------------------------------------------------------------------------- 335 | // EXPORT THE MODULE FUNCTIONS 336 | // ---------------------------------------------------------------------------- 337 | // 338 | module.exports.blacklistedParameter = blacklistedParameter 339 | module.exports.binaryDetailsPathGet = binaryDetailsPathGet 340 | module.exports.binaryDetailsValueGet = binaryDetailsValueGet 341 | module.exports.binaryPathGet = binaryPathGet 342 | module.exports.binaryPathReset = binaryPathReset 343 | module.exports.binaryUpdateCheck = binaryUpdateCheck 344 | module.exports.binaryUpdateSearch = binaryUpdateSearch 345 | module.exports.binaryUpdateExecute = binaryUpdateExecute 346 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | media-dupes 51 | 52 | 53 | 54 | 55 | 56 | 57 | 66 | 67 | 68 | 69 | 70 | 89 | 90 | 91 | 92 |
93 | 94 | 95 |
 
96 | 97 | 98 |
99 | 100 | 101 | 104 | 105 | 106 |

media-dupes

107 | 108 | 109 | 112 | 113 | 114 |

a minimal content duplicator for common media services like youtube

115 |
116 | 117 | 118 |
 
119 | 120 | 121 |
122 | 123 |
124 | 125 |
126 |
127 | 128 | 129 |
130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
ID Url (long)
144 |
145 | 146 | 147 |
 
148 | 149 | 150 |
151 | 152 | 164 | 165 | 166 | 179 | 180 | 181 | 182 | 194 |
195 | 196 | 197 |
 
198 | 199 | 200 | 201 |
202 | 210 |
211 | 212 | 213 |
214 | 215 | 216 |
217 | 218 | 227 | 228 | 237 | 238 | 247 | 248 | 258 | 263 | 264 |
265 | 266 | 267 | 268 |
 
269 | 270 | 271 |
 
272 | 273 |
274 | 275 | 276 |
277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 393 | 394 | 395 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ![logo](https://raw.githubusercontent.com/yafp/media-dupes/master/.github/images/logo/128x128.png) 2 | 3 | # media-dupes 4 | ## changelog 5 | 6 | This project is using [Semantic Versioning](https://semver.org/). 7 | 8 | ` 9 | MAJOR.MINOR.PATCH 10 | ` 11 | 12 | * `MAJOR` version (incompatible API changes etc) 13 | * `MINOR` version (adding functionality) 14 | * `PATCH` version (bug fixes) 15 | 16 | 17 | The following categories are used: 18 | 19 | * `Added`: for new features 20 | * `Changed`: for changes in existing functionality. 21 | * `Deprecated`: for soon-to-be removed features. 22 | * `Removed`: for now removed features. 23 | * `Fixed`: for any bug fixes. 24 | * `Security`: in case of vulnerabilities. 25 | 26 | 27 | *** 28 | 29 | ### media-dupes 0.10.3 (20200801) 30 | #### `Changed` 31 | * Updated dependencies: 32 | * Updated `eslint` from `7.5.0` to `7.6.0`. 33 | 34 | #### `Fixed` 35 | * Fixed a dependency error. See [#140](https://github.com/yafp/media-dupes/issues/140) 36 | 37 | 38 | *** 39 | 40 | ### media-dupes 0.10.2 (20200730) 41 | #### `Changed` 42 | * Updated dependencies: 43 | * Updated `custom-electron-titlebar` from `3.2.2` to `3.2.3`. 44 | * Updated `fontawesome-free` from `5.13.0` to `5.14.0`. 45 | * Updated `electron` from `9.0.3` to `9.1.2`. 46 | * Updated `electron-builder` from `22.7.0` to `22.8.0` 47 | * Updated `electron-log` from `4.2.1` to `4.2.2`. 48 | * Updated `electron-packager` from `14.2.1` to `15.0.0`. 49 | * Updated `electron-util` from `0.14.1` to `0.14.2`. 50 | * Updated `electron-json-storage` from `4.1.8` to `4.2.0`. 51 | * Updated `electron-prompt` from `1.5.0` to `1.6.0`. 52 | * Updated `eslint` from `7.2.0` to `7.5.0`. 53 | * Updated `got` from `11.3.0` to `11.5.1` 54 | * Updated `jsdoc` from `3.6.4` to `3.6.5`. 55 | * Updated `metascraper-media-provider` from `5.12.0` to `5.13.0` 56 | * Updated `metascraper` from `5.11.21` to `5.13.1`. 57 | * Updated `metascraper-audio` from `5.11.21` to `5.13.1`. 58 | * Updated `metascraper-description` from `5.11.21` to `5.13.1`. 59 | * Updated `metascraper-image` from `5.11.21` to `5.13.1`. 60 | * Updated `metascraper-logo` from `5.11.21` to `5.13.1`. 61 | * Updated `metascraper-logo-favicon` from `5.11.21` to `5.13.1`. 62 | * Updated `metascraper-media-provider` from `5.11.22` to `5.13.1`. 63 | * Updated `metascraper-soundcloud` from `5.11.21` to `5.13.1`. 64 | * Updated `metascraper-title` from `5.11.21` to `5.13.1`. 65 | * Updated `metascraper-video` from `5.11.21` to `5.13.1`. 66 | * Updated `metascraper-youtube` from `5.11.21` to `5.13.1`. 67 | * Updated `ntl` from `5.0.0` to `5.1.0`. 68 | * Updated `sentry/electron` from `1.3.0` to `1.5.2`. 69 | * Updated `yargs` from `15.3.1` to `15.4.1`. 70 | 71 | #### `Fixed` 72 | * Fixed a TypeError. See [#133](https://github.com/yafp/media-dupes/issues/133) 73 | 74 | 75 | *** 76 | 77 | ### media-dupes 0.10.1 (20200609) 78 | #### `Changed` 79 | * Updated dependencies: 80 | * Updated `electron` from `9.0.2` to `9.0.3`. 81 | 82 | #### `Fixed` 83 | * Fixed a dependency error. See [#130](https://github.com/yafp/media-dupes/issues/130) 84 | 85 | 86 | *** 87 | 88 | ### media-dupes 0.10.0 (20200607) 89 | #### `Added` 90 | * Added todo-list-protection to ensure items of the list arent removed while media-dupes is processing the list. See [#127](https://github.com/yafp/media-dupes/issues/127) 91 | * Added a warning dialog about consequences of not updating media-dupes. See [#129](https://github.com/yafp/media-dupes/issues/129) 92 | 93 | #### `Changed` 94 | * Improved update check - using semver for version comparison. 95 | * Updated dependencies: 96 | * Updated `about-window` from `1.13.2` to `1.13.4` 97 | * Updated `datatables.net-dt` from `1.10.20` to `1.10.21` 98 | * Updated `datatables.net-scroller-dt` from `2.0.1` to `2.0.2` 99 | * Updated `electron` from `8.2.5` to `9.0.2`. See [#128](https://github.com/yafp/media-dupes/issues/128) 100 | * Updated `electron-builder` from `22.6.0` to `22.7.0` 101 | * Updated `electron-log` from `4.1.2` to `4.2.1` 102 | * Updated `eslint` from `6.8.0` to `7.2.0` 103 | * Updated `got` from `10.7.0` to `11.3.0` 104 | * Updated `jquery` from `3.5.0` to `3.5.1` 105 | * Updated `metascraper` from `5.11.10` to `5.11.21` 106 | * Updated `metascraper-audio` from `5.11.10` to `5.11.21` 107 | * Updated `metascraper-description` from `5.11.10` to `5.11.21` 108 | * Updated `metascraper-image` from `5.11.10` to `5.11.21` 109 | * Updated `metascraper-logo` from `5.11.10` to `5.11.21` 110 | * Updated `metascraper-logo-favicon` from `5.11.11` to `5.11.21` 111 | * Updated `metascraper-media-provider` from `5.11.11` to `5.11.22` 112 | * Updated `metascraper-soundcloud` from `5.11.11` to `5.11.21` 113 | * Updated `metascraper-title` from `5.11.10` to `5.11.21` 114 | * Updated `metascraper-video` from `5.11.10` to `5.11.21` 115 | * Updated `metascraper-youtube` from `5.11.10` to `5.11.21` 116 | * Updated `v8-compile-cache` from `2.1.0` to `2.1.1` 117 | 118 | *** 119 | 120 | ### media-dupes 0.9.0 (20200502) 121 | #### `Added` 122 | * Added prelisten function to todo-list. See [#119](https://github.com/yafp/media-dupes/issues/119) 123 | * Added 'show supported sites' menu entry to the help -> youtube-dl menu. See [#113](https://github.com/yafp/media-dupes/issues/113) 124 | * Added support for cli parameters. See [#115](https://github.com/yafp/media-dupes/issues/115) 125 | * Added icons to menu. See [#118](https://github.com/yafp/media-dupes/issues/118) 126 | 127 | #### `Changed` 128 | * UI: 129 | * Reduced minimal window height from 830px to 730 px. See [#111](https://github.com/yafp/media-dupes/issues/111) 130 | * Settings icon on settings window now shows its function using a title. 131 | * ToDoList: Content of column url is now truncated to ensure the table does not break. See [#120](https://github.com/yafp/media-dupes/issues/120) 132 | * Disclaimer: Added blur and unblur. Changed timing for disclaimer-check call (moved from ready-to-show to show). 133 | * Sentry: Removed several event count events to reduce overall amount of generated reports. 134 | * New default value for `app.allowRendererProcessReuse` is now `true`. 135 | * Updated dependencies: 136 | * Updated `electron` from `8.2.1` to `8.2.5` 137 | * Updated `electron-builder` from `22.4.1` to `22.6.0` 138 | * Updated `electron-log` from `4.1.1` to `4.1.2` 139 | * Updated `electron-util` from `0.14.0` to `0.14.1` 140 | * Updated `metascraper` from `5.11.8` to `5.11.10` 141 | * Updated `metascraper-audio` from `5.11.8` to `5.11.10` 142 | * Updated `metascraper-description` from `5.11.8` to `5.11.10` 143 | * Updated `metascraper-image` from `5.11.8` to `5.11.10` 144 | * Updated `metascraper-logo` from `5.11.8` to `5.11.10` 145 | * Updated `metascraper-logo-favicon` from `5.11.8` to `5.11.11` 146 | * Updated `metascraper-media-provider` from `5.11.8` to `5.11.11` 147 | * Updated `metascraper-soundcloud` from `5.11.8` to `5.11.10` 148 | * Updated `metascraper-title` from `5.11.8` to `5.11.10` 149 | * Updated `metascraper-video` from `5.11.8` to `5.11.10` 150 | * Updated `metascraper-youtube` from `5.11.8` to `5.11.10` 151 | 152 | #### `Removed` 153 | * Removed the show extractors function from the main UI. Users can still see a list of supported youtube-dl sites via the help menu. See [#121](https://github.com/yafp/media-dupes/issues/121) 154 | 155 | #### `Fixed` 156 | * Fixed a minor issue in todo-list if metascraper got null values. See [#114](https://github.com/yafp/media-dupes/issues/114) 157 | 158 | *** 159 | 160 | ### media-dupes 0.8.0 (20200411) 161 | #### `Added` 162 | * ToDo list 163 | * Single urls can now be removed from list. See [#104](https://github.com/yafp/media-dupes/issues/104) 164 | * Now shows the url favicon. See [#107](https://github.com/yafp/media-dupes/issues/107) 165 | * Now shows the preview image. See [#108](https://github.com/yafp/media-dupes/issues/108) 166 | * New Dependencies 167 | * Added `datatables.net-dt` version `1.10.20`. See [#102](https://github.com/yafp/media-dupes/issues/102) 168 | * Added `datatables.net-scroller-dt` version `2.0.1`. See [#102](https://github.com/yafp/media-dupes/issues/102) 169 | * Added `md5` version `2.2.1`. 170 | * Added `got` version `10.7.0`. 171 | * Added `metascraper` version `5.11.8`. 172 | * Added `metascraper-audio` version `5.11.8`. 173 | * Added `metascraper-description` version `5.11.8`. 174 | * Added `metascraper-image` version `5.11.8`. 175 | * Added `metascraper-logo` version `5.11.8`. 176 | * Added `metascraper-logo-favicon` version `5.11.8`. 177 | * Added `metascraper-media-provider` version `5.11.8`. 178 | * Added `metascraper-soundcloud` version `5.11.8`. 179 | * Added `metascraper-title` version `5.11.8`. 180 | * Added `metascraper-video` version `5.11.8`. 181 | * Added `metascraper-youtube` version `5.11.8`. 182 | 183 | #### `Changed` 184 | * ToDo list is now a table. See [#102](https://github.com/yafp/media-dupes/issues/102) 185 | * Reset UI is now reloading the UI. 186 | * Changed notifcation-display-time on end of download queue. Info hides itself after a while. Warning and error stay until confirmed. 187 | * Dependencies 188 | * Updated `electron` from `8.2.0` to `8.2.1` 189 | * Updated `jsdoc` from `3.6.3` to `3.6.4` 190 | * Updated `jquery` from `3.4.1` to `3.5.0` 191 | 192 | #### `Removed` 193 | * Removed youtube-dl setting 'fetch url informations'. See [#109](https://github.com/yafp/media-dupes/issues/109) 194 | * Removed is-reachable test for urls as it was not reliable. See [#106](https://github.com/yafp/media-dupes/issues/106) 195 | * CI 196 | * Removing travis and appveyor - as building is now realized using GitHub actions. 197 | 198 | #### `Fixed` 199 | * Fixed an error in todo-list saving and restoring. See [#105](https://github.com/yafp/media-dupes/issues/105) 200 | * Fixed an error in todo-list cleanup. See [#110](https://github.com/yafp/media-dupes/issues/110) 201 | 202 | 203 | *** 204 | 205 | ### media-dupes 0.7.0 (20200403) 206 | #### `Added` 207 | * Energy-management 208 | * Added a power-save-blocker. Now trying to prevent powerSave while downloads are in progress. See [#97](https://github.com/yafp/media-dupes/issues/97) 209 | 210 | #### `Changed` 211 | * Audio-Mode: Added tracknumer as first parameter for the naming pattern of audio files. 212 | * Windows nsis installer. Show install and uninstall details. See [#96](https://github.com/yafp/media-dupes/issues/96) 213 | * Removed most of the event count functions. Only core functionality is counted from now. See [#103](https://github.com/yafp/media-dupes/issues/103) 214 | * Settings 215 | * youtube-dl: Update button is now disabled if update is technically not possible. 216 | * Dependencies 217 | * Updated `electron` from `8.1.0` to `8.2.0` 218 | * Updated `electron-builder` from `22.4.0` to `22.4.1` 219 | * Updated `electron-log` from `4.0.7` to `4.1.1` 220 | * Updated `fontaswesome-free` from `5.12.1` to `5.13.0` 221 | * Updated `sentry` from `1.2.1` to `1.3.0` 222 | * Updated `mocha` from `7.1.0` to `7.1.1` 223 | 224 | #### `Fixed` 225 | * youtube-dl update routine 226 | * Improved handling of 'Force updating youtube-dl binary ' function via menu. 227 | * Added error handling to 'reset youtube-dl binary path' function via menu. 228 | * Fixed update issues due to missing permissions. See [#98](https://github.com/yafp/media-dupes/issues/98) 229 | * Fixed horizontal scrollbar bug. See [#100](https://github.com/yafp/media-dupes/issues/100) 230 | * Fixed some vulnerabilities in dependencies. 231 | * Fixed an issue when the configured download target no longer exists. Now fallbacks to default 232 | 233 | *** 234 | 235 | ### media-dupes 0.6.0 (20200306) 236 | #### `Added` 237 | * URL input: Url-input-field is now color-coded (red = unreachable, yellow = unchecked, green = reachable). See [#82](https://github.com/yafp/media-dupes/issues/82) 238 | * Added splash screen. See [#78](https://github.com/yafp/media-dupes/issues/83) 239 | * Energy-management 240 | * Added todoList save function on sleep event. See [#79](https://github.com/yafp/media-dupes/issues/79) 241 | * Added todoList restore function on resume event. See [#79](https://github.com/yafp/media-dupes/issues/79) 242 | * Audio: Added thumbnail-embedding support for .m4a 243 | * Log: Added support for leading timestamps in the log. See [#84](https://github.com/yafp/media-dupes/issues/84) 244 | * Added youtube suggestion function. See [#86](https://github.com/yafp/media-dupes/issues/86) 245 | * Added getInfo routine for urls, executed when Url is added to queue. See [#87](https://github.com/yafp/media-dupes/issues/87) 246 | * Added url thumbnail preview (after adding an url to queue). See [#89](https://github.com/yafp/media-dupes/issues/89) 247 | * Added support for additional youtube-dl flags/parameter by using the settings UI. See [#88](https://github.com/yafp/media-dupes/issues/88) 248 | * Added new setting: Fetch url informations. See [#95](https://github.com/yafp/media-dupes/issues/95) 249 | * Added `v8-compile-cache` to the project. 250 | 251 | #### `Changed` 252 | * Changed application icon 253 | * Improved download resume for user. See [#78](https://github.com/yafp/media-dupes/issues/78) 254 | * Changed the configurations options for the Windows Installer (NSIS). See [#72](https://github.com/yafp/media-dupes/issues/72) 255 | * Moved settings code to module. See [#77](https://github.com/yafp/media-dupes/issues/77) 256 | * Changed textarea font to monospace family to ensure correct indenting in the log 257 | * Added youtube-dl flag `--restrict-filenames` to the parameter list. 258 | * URL Restore: Added makeUrgent notification to the end of a successful executed URL restore. 259 | * Settings: Initial settings creation with default values is now silent. See [#80](https://github.com/yafp/media-dupes/issues/80) 260 | * Removed success notification on 'Loading supported extractors'. 261 | * Standardx: Added `snazzy` to `npm run standardx` and `npm run standardx-fix` scripts to enable funky output for developers. 262 | * OS notifications: Clicking the notification is now raising the application UI. See [#85](https://github.com/yafp/media-dupes/issues/85) 263 | * UI 264 | * Fonts: Added Arial and ArialMono to the project to ensure the same font is used on all installations. See [#94](https://github.com/yafp/media-dupes/issues/94) 265 | * mainWindow: Changed the layout. Using the entire window height now - Part 1. See [#42](https://github.com/yafp/media-dupes/issues/42) 266 | * mainWindow: added fadeIn effect to entire body 267 | * mainWindow: Blur effect now affects as well the titlebar. See [#91](https://github.com/yafp/media-dupes/issues/91) 268 | * Settings: added fadeIn effect to entire body 269 | * Settings: now using tabs. See [#93](https://github.com/yafp/media-dupes/issues/93) 270 | * Update checks 271 | * Search fopr media-dupes updates is now triggered once 5 second after app start. Might speed up the start 272 | * Search for youtube-dl updates is now triggered once 5 seconds after app start. Might speed up the start 273 | * Dependencies 274 | * Updated `custom-electron-titlebar` from `3.2.1` to `3.2.2` 275 | * Updated `electron` from `7.1.10` to `8.1.0` 276 | * Updated `electron-log` from `4.0.4` to `4.0.7` 277 | * Updated `electron-packager` from `14.2.0` to `14.2.1` 278 | * Updated `electron-builder` from `22.3.2` to `22.4.0` 279 | * Updated `electron-util` from `0.13.1` to `0.14.0` 280 | * Updated `fontawesome` from `5.12.0` to `5.12.1` 281 | * Updated `mocha` from `7.0.1` to `7.1.0` 282 | * Updated `rimraf` from `3.0.1` to `3.0.2` 283 | * Updated `spectron` from `10.0.0` to `10.0.1` 284 | * Updated `sentry` from `1.2.0` to `1.2.1` 285 | * Updated `youtube-dl` from `3.0.1` to `3.0.2` 286 | 287 | #### `Removed` 288 | * Removed `npx` from project. See [#41](https://github.com/yafp/media-dupes/issues/41) 289 | 290 | #### `Fixed` 291 | * Fixed undefined version in sentry error events. See [#75](https://github.com/yafp/media-dupes/issues/75) 292 | * Fixed issues with the windows installer (NSIS). See [#76](https://github.com/yafp/media-dupes/issues/76) 293 | * Fixed a bug with the Intro. See [#92](https://github.com/yafp/media-dupes/issues/92) 294 | 295 | *** 296 | 297 | ### media-dupes 0.5.0 (20200129) 298 | #### `Added` 299 | * Added a disclaimer which must be confirmed once per user. See [#52](https://github.com/yafp/media-dupes/issues/52) 300 | * Added support for saving and restoring urls. See [#66](https://github.com/yafp/media-dupes/issues/66) 301 | * Added support for applicationState. Ask user if he really wants to quit when downloads are in progress. See [#59](https://github.com/yafp/media-dupes/issues/59) 302 | * Added some youtube-dl maintenance function to the menu. See [#57](https://github.com/yafp/media-dupes/issues/57) 303 | * Reset youtube-dl binary path (to revert back to bundled youtube-dl binary) 304 | * Force updating youtube-dl binary (to redownload the latest stable binary) 305 | * Added general support for UI animations/effects using animate.js. See [#69](https://github.com/yafp/media-dupes/issues/69) 306 | * Adding support for new audio formats. See [#65](https://github.com/yafp/media-dupes/issues/65) 307 | * Added support for `.aac` 308 | * Added support for `.flac` 309 | * Added support for `.opus` 310 | * Added support for `.ogg/vorbis` 311 | * Added support for the option `best` 312 | * Added basic support for powerMonitoring (suspend and resume). See [#67](https://github.com/yafp/media-dupes/issues/67) 313 | * Added new user setting `verbose mode`. See [#70](https://github.com/yafp/media-dupes/issues/70) 314 | 315 | #### `Changed` 316 | * Update search: 317 | * Is now ignoring pre-releases. See [#73](https://github.com/yafp/media-dupes/issues/73) 318 | * Added new setting to configure search for pre-releases. See [#74](https://github.com/yafp/media-dupes/issues/74) 319 | * Setting UI: 320 | * is now a child window of the main UI. See [#58](https://github.com/yafp/media-dupes/issues/58) 321 | * is now a modal window. See [#63](https://github.com/yafp/media-dupes/issues/63) 322 | * while setting UI is open the main UI gets blur'ed. See [#64](https://github.com/yafp/media-dupes/issues/64) 323 | * Improved validation of youtube-dl setup. See [#56](https://github.com/yafp/media-dupes/issues/56) 324 | * Show extractors function not longer resets the log. It appends now the new data. 325 | * Downloading audio: 326 | * Improved filename pattern for audio downloads. See [#61](https://github.com/yafp/media-dupes/issues/61) 327 | * Added `--add-metadata` flag. 328 | * Added `--ignore-errors` flag. 329 | * Download video: 330 | * Added `--ignore-errors` flag. 331 | * Improved audio quality setting by using `--audio-quality 0` (was set to 5 before). 332 | * Moved functions from renderer to new modules 333 | * ffmpeg 334 | * youtubeDl 335 | * ui 336 | * sentry 337 | * Dependencies 338 | * Updated `youtube-dl` from 3.0.0 to 3.0.1 339 | * Updated `popper.js` from 1.16.0 to 1.16.1 340 | * Updated `electron` from 7.1.9 to 7.1.10 341 | * Updated `electron-log` from 4.0.3 to 4.0.4 342 | * Updated `spectron` from 9.0.0 to 10.0.0 343 | * Updated `mocha` from 7.0.0 to 7.0.1 344 | * Updated `docdash` from 1.1.0 to 1.2.0 345 | * Updated `electron-builder` from 22.2.0 to 22.3.2 346 | * Updated `rimraf` from 3.0.0 to 3.0.1 347 | * Documentation: Improved jsdoc documentation. Adding namespaces and some other changes 348 | * Builds: Improved the macOS .dmg style. New background and icon positions 349 | 350 | #### `Fixed` 351 | * Fixed several errors in application log showing wrong urls and progress-state informations. See [#60](https://github.com/yafp/media-dupes/issues/60) 352 | * Fixed error handling when downloading a single url failed. See [#71](https://github.com/yafp/media-dupes/issues/71) 353 | * Fixed a vertical scrollbar bug 354 | 355 | *** 356 | 357 | ### media-dupes 0.4.2 (20200116) 358 | 359 | #### `Fixed` 360 | * Fixed the broken .icns app icon. See [#54](https://github.com/yafp/media-dupes/issues/54) 361 | * Fixed wrong path information in youtube-dl's detail file. See [#55](https://github.com/yafp/media-dupes/issues/55) 362 | 363 | 364 | *** 365 | 366 | ### media-dupes 0.4.1 (20200115) 367 | 368 | #### `Changed` 369 | * Added `rimraf` to project for package.json scripts to improve clean scripts. See [#48](https://github.com/yafp/media-dupes/issues/48) 370 | * Simplified requirements check on startup. See [#49](https://github.com/yafp/media-dupes/issues/49) 371 | * Dependencies 372 | * Updated `electron-builder` from 21.2.0 to 22.2.0 373 | 374 | #### `Fixed` 375 | * Fixed a bug in youtube-dl binary update routine. See [#50](https://github.com/yafp/media-dupes/issues/50) 376 | 377 | *** 378 | 379 | ### media-dupes 0.4.0 (20200114) 380 | #### `Added` 381 | * Error reporting using sentry is now optional via application settings. See [#31](https://github.com/yafp/media-dupes/issues/31) 382 | * Added background images to textareas (todo-list and log). See [#35](https://github.com/yafp/media-dupes/issues/35) 383 | * Added confirm dialog to UI reset function. See [#37](https://github.com/yafp/media-dupes/issues/37) 384 | * Added update check for youtube-dl binary. See [#40](https://github.com/yafp/media-dupes/issues/40) 385 | * Added update function for youtube-dl binary. See [#34](https://github.com/yafp/media-dupes/issues/34) 386 | * Added update check for youtube-dl binary on app startup. See [#40](https://github.com/yafp/media-dupes/issues/40) 387 | 388 | #### `Changed` 389 | * Improving error handling 390 | * By adding `unhandled` with dialogs & report issue to github function. See [#46](https://github.com/yafp/media-dupes/issues/46) 391 | * Sentry: Enabled `sentry` debug mode. See [#36](https://github.com/yafp/media-dupes/issues/36) 392 | * UI 393 | * General: Disabling most UI buttons while execution of some functions (searching for updates, loading extractors) to prevent race-conditions. See [#33](https://github.com/yafp/media-dupes/issues/33) 394 | * General: Reduced minimal window height about 60px. See [#38](https://github.com/yafp/media-dupes/issues/38) 395 | * Settings: reduced ui-element size on settings page from default to small. See [#38](https://github.com/yafp/media-dupes/issues/38) 396 | * Settings: show `youtube-dl` binary version. See [#39](https://github.com/yafp/media-dupes/issues/39) 397 | * Improved handling if user tries to add un-useable urls (focus to input field & selecting the content if possible). 398 | * Moved some helper functions to `app/js/modules/mdUtils.js` 399 | * Dependencies 400 | * Updated `electron` from 7.1.6 to 7.1.9 401 | * Updated `electron-log` from 4.0.0 to 4.0.3 402 | * Updated `electron-packager` from 14.1.1 to 14.2.0 403 | * Updated `eslint` from 6.7.2 to 6.8.0 404 | * Updated `mocha` from 6.2.2 to 7.0.0 405 | * Updated `sentry` from 1.1.0 to 1.2.0 406 | * Updated `youtube-dl` from 2.3.0 to 3.0.0 407 | * Switching back from `pj-custom-electron-titlebar` to `custom-electron-titlebar` (3.2.1) 408 | * Added missing timeout = 0 to several noty error dialogs (ensure the error must be confirmed) 409 | 410 | #### `Removed` 411 | * Removed any `sentry` usage which was not error-focused (no user tracking). See [#31](https://github.com/yafp/media-dupes/issues/31) 412 | 413 | #### `Fixed` 414 | * Fixed error with non-defined array. See [#30](https://github.com/yafp/media-dupes/issues/30) 415 | * Fixed error URIError: URI malformed. See [#25](https://github.com/yafp/media-dupes/issues/25) 416 | * Fixed error where detecting `youtube-dl` binary was not working on packaged-builds. See [#44](https://github.com/yafp/media-dupes/issues/44) 417 | * Fixed a bug affecting all windows build containing the wrong yotube-dl binary. See [#47](https://github.com/yafp/media-dupes/issues/47) 418 | 419 | *** 420 | 421 | ### media-dupes 0.3.0 (20191219) 422 | #### `Added` 423 | * Added an error dialog to show issues with the spawned download process. See [#25](https://github.com/yafp/media-dupes/issues/25) 424 | * Settings: Added buttons to visit `youtube-dl` and `ffmpeg` project pages. See [#29](https://github.com/yafp/media-dupes/issues/29) 425 | 426 | #### `Changed` 427 | * Reduced build size by only adding ffmpeg for the actual platform. See [#22](https://github.com/yafp/media-dupes/issues/22) 428 | * Improved url detection from clipboard (trim leading and trailing blanks). See [#28](https://github.com/yafp/media-dupes/issues/28) 429 | * Downloading: Added decode function for user urls to avoid the risk of malformed urls. See [#25](https://github.com/yafp/media-dupes/issues/25) 430 | * Added fade-in effect to load process of the .html files (index.html and settings.html). 431 | * Extractors: Show extractors list now shows an error notification if fetching them fails. See [#27](https://github.com/yafp/media-dupes/issues/27) 432 | * Improved adding urls (trim leading and trailing blanks). See [#28](https://github.com/yafp/media-dupes/issues/28) 433 | * Using intro.js now via npm. See [#21](https://github.com/yafp/media-dupes/issues/21) 434 | * UI: Added a left/right/bottom border for the UI (css) 435 | * Dependencies 436 | * Updated `electron` from 7.1.4 to 7.1.6 437 | * Updated `youtube-dl` from 2.2.0 to 2.3.0 438 | 439 | #### `Fixed` 440 | * Fixed issue where the search for software updates was launched twice on application start. See [#26](https://github.com/yafp/media-dupes/issues/26) 441 | * Fixed an issue where the Loading-animation might be hidden, while it should be still displayed. 442 | 443 | *** 444 | 445 | ### media-dupes 0.2.0 (20191213) 446 | #### `Added` 447 | * Added icon to os-notifications. See [#5](https://github.com/yafp/media-dupes/issues/5) 448 | * Added custom-titlebar (merging menu and titlebar to one line). See [#6](https://github.com/yafp/media-dupes/issues/6) 449 | * Added support for urgent window after finishing all download jobs. See [#7](https://github.com/yafp/media-dupes/issues/7) 450 | * Added loading/spinner to show ongoing download process. See [#8](https://github.com/yafp/media-dupes/issues/8) 451 | * Added an intro to the UI using introJs. See [#14](https://github.com/yafp/media-dupes/issues/14) 452 | * Added settings UI. See [#19](https://github.com/yafp/media-dupes/issues/19) 453 | * Added support for user specific target audio formats (mp3, m4a, wav) 454 | * Added support for custom download dir 455 | * Added settings entry to the 'File' menu. See [#19](https://github.com/yafp/media-dupes/issues/19) 456 | * Added support for save and restore window position and size. See [#20](https://github.com/yafp/media-dupes/issues/20) 457 | * Added auto-paste of urls on focus. See [#24](https://github.com/yafp/media-dupes/issues/24) 458 | 459 | 460 | #### `Changed` 461 | * Download directory / handling 462 | * Audio: downloads are now located in a sub-directory 'Audio' inside the target download dir 463 | * Audio: media-dupes tries to create download specific directories inside Audio to improve handling of albums. If that fails the download lands in a subfolder called NA-NA 464 | * Dependencies check on application launch is now searching for youtube-dl as well. 465 | * Dependencies 466 | * Updated `electron` from 7.1.3 to 7.1.4 467 | * Updated `youtube-dl` from 2.1.0 to 2.2.0 468 | * Updated `fontawesome` from 5.11.2 to 5.12.0 469 | * Updated `sentry` from 1.1.0-beta to 1.1.0 470 | * Normalized code style using standardx 471 | * Improved youtube-dl flag usage 472 | * added: `--add-metadata` for video. See [#10](https://github.com/yafp/media-dupes/issues/10) 473 | * added: `--embed-thumbnail` for audio/mp3. See [#11](https://github.com/yafp/media-dupes/issues/11) 474 | * added: `--ignore-errors` for audio & video. See [#12](https://github.com/yafp/media-dupes/issues/12) 475 | * Log section now auto-scrolls to bottom of log while downloading content. See [#13](https://github.com/yafp/media-dupes/issues/13) 476 | * Log section is now showing more informations as we are now using the youtube-dl flag `--verbose` 477 | 478 | #### `Removed` 479 | * Menu 480 | * Removed the option 'View' - 'Hide'. As there was no option to access the hidden window anymore. 481 | * Builds 482 | * No more .zip Builds for macOS 483 | 484 | *** 485 | 486 | ### media-dupes 0.1.0 (20191209) 487 | #### `Added` 488 | * Initial version of media-dupes 489 | * Core functions 490 | * Download video. See [#1](https://github.com/yafp/media-dupes/issues/1) 491 | * Downloading/extracting audio. See [#2](https://github.com/yafp/media-dupes/issues/2) 492 | * Minor functions 493 | * Show extractor list.See [#4](https://github.com/yafp/media-dupes/issues/4) 494 | * Open local download folder 495 | * Application menu 496 | * about window 497 | * check for software updates. See [#3](https://github.com/yafp/media-dupes/issues/3) 498 | * Basic support for in-app notification using noty 499 | * Basic support for os notifications 500 | -------------------------------------------------------------------------------- /app/js/modules/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Contains all helper and utility functions 3 | * @author yafp 4 | * @module utils 5 | */ 6 | 'use strict' 7 | 8 | // ---------------------------------------------------------------------------- 9 | // REQUIRE MODULES 10 | // ---------------------------------------------------------------------------- 11 | // 12 | const sentry = require('./sentry.js') 13 | const ui = require('./ui.js') 14 | 15 | /** 16 | * @function writeConsoleMsg 17 | * @summary Writes console output for the renderer process 18 | * @description Writes console output for the renderer process 19 | * @param {string} type - String which defines the log type 20 | * @param {string} message - String which defines the log message 21 | * @param {string} optionalObject - An optional object which might contain additional informations 22 | */ 23 | function writeConsoleMsg (type, message, optionalObject = '') { 24 | const logR = require('electron-log') 25 | const prefix = '[ Renderer ] ' 26 | 27 | // electron-log can: error, warn, info, verbose, debug, silly 28 | switch (type) { 29 | case 'info': 30 | logR.info(prefix + message, optionalObject) 31 | break 32 | 33 | case 'warn': 34 | logR.warn(prefix + message, optionalObject) 35 | break 36 | 37 | case 'error': 38 | logR.error(prefix + message, optionalObject) 39 | break 40 | 41 | default: 42 | logR.silly(prefix + message, optionalObject) 43 | break 44 | } 45 | } 46 | 47 | /** 48 | * @function showNoty 49 | * @summary Shows a noty notification 50 | * @description Creates an in-app notification using the noty framework 51 | * @param {string} type - Options: alert, success, warning, error, info/information 52 | * @param {string} message - notification text 53 | * @param {number} [timeout] - Timevalue, defines how long the message should be displayed. Use 0 for no-timeout 54 | */ 55 | function showNoty (type, message, timeout = 3000) { 56 | const Noty = require('noty') 57 | new Noty({ 58 | type: type, 59 | timeout: timeout, 60 | theme: 'bootstrap-v4', 61 | layout: 'bottom', 62 | text: message, 63 | animation: { 64 | open: 'animated fadeIn', // Animate.css class names: default: bounceInRight 65 | close: 'animated fadeOut' // Animate.css class names: default:bounceOutRight 66 | } 67 | }).show() 68 | } 69 | 70 | /** 71 | * @function showNotification 72 | * @summary Shows a desktop notification 73 | * @description Shows a desktop notification 74 | * @param {string} message - The notification message text 75 | * @param {string} [title] - The title of the desktop notification 76 | */ 77 | function showNotification (message, title = 'media-dupes') { 78 | const myNotification = new Notification(title, { 79 | body: message, 80 | icon: 'img/notification/icon.png' 81 | }) 82 | 83 | myNotification.onclick = () => { 84 | writeConsoleMsg('info', 'showNotification ::: Notification clicked') 85 | 86 | const { ipcRenderer } = require('electron') 87 | ipcRenderer.send('showAndFocusMainUI') // tell main.js to show the main UI 88 | } 89 | } 90 | 91 | /** 92 | * @function openURL 93 | * @summary Opens an url in browser 94 | * @description Opens a given url in default browser. This is pretty slow, but got no better solution so far. 95 | * @param {string} url - URL string which contains the target url 96 | */ 97 | function openURL (url) { 98 | const { shell } = require('electron') 99 | writeConsoleMsg('info', 'openURL ::: Trying to open the url: _' + url + '_.') 100 | shell.openExternal(url) 101 | } 102 | 103 | /** 104 | * @function validURL 105 | * @summary checks if a given string is a valid url 106 | * @description checks if a given string is a valid url 107 | * @param {string} -str - Given url 108 | * @return {boolean} 109 | */ 110 | function validURL (str) { 111 | var pattern = new RegExp('^(https?:\\/\\/)?' + // protocol 112 | '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name 113 | '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address 114 | '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path 115 | '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string 116 | '(\\#[-a-z\\d_]*)?$', 'i') // fragment locator 117 | return !!pattern.test(str) 118 | } 119 | 120 | /** 121 | * @function formatBytes 122 | * @summary Calculate bytes to... 123 | * @description Calculate bytes to... 124 | * @param bytes - Incoming bytes value 125 | * @param decimals (optimal, defaults to 2) 126 | * @return Human readable value 127 | */ 128 | function formatBytes (bytes, decimals = 2) { 129 | if (bytes === 0) return '0 Bytes' 130 | 131 | const k = 1024 132 | const dm = decimals < 0 ? 0 : decimals 133 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] 134 | const i = Math.floor(Math.log(bytes) / Math.log(k)) 135 | 136 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] 137 | } 138 | 139 | /** 140 | * @function isEncoded 141 | * @summary Helper method for fullyDecodeURI 142 | * @description Helper method for fullyDecodeURI 143 | * @param {string} uri - the uri to check 144 | * @return {string} uri - the decoded uri 145 | */ 146 | function isEncoded (uri) { 147 | uri = uri || '' 148 | return uri !== decodeURIComponent(uri) 149 | } 150 | 151 | /** 152 | * @function fullyDecodeURI 153 | * @summary Used to decode URLs 154 | * @description Used to decode URLs 155 | * param {string} uri - The incoming uri 156 | * @return {string} uri - a decoded url 157 | */ 158 | function fullyDecodeURI (uri) { 159 | while (isEncoded(uri)) { 160 | uri = decodeURIComponent(uri) 161 | } 162 | return uri 163 | } 164 | 165 | /** 166 | * @function pathExists 167 | * @summary Checks if a given filepath exists 168 | * @description Checks if a given filepath exists using fs. Returns a boolean 169 | * param {string} path - The path which should be checked for existance 170 | * @return {boolean} -If path exists or not 171 | */ 172 | function pathExists (path) { 173 | const fs = require('fs') 174 | if (fs.existsSync(path)) { 175 | return true // path exists 176 | } else { 177 | return false // path does not exists 178 | } 179 | } 180 | 181 | /** 182 | * @function globalObjectGet 183 | * @summary Gets a value of a single property from the global object in main.js 184 | * @description Gets a value of a single property from the global object in main.js 185 | * @param {String} property - Name of the property 186 | * @return {string} value - Value of the property 187 | */ 188 | function globalObjectGet (property) { 189 | const { remote } = require('electron') 190 | var value = remote.getGlobal('sharedObj')[property] 191 | // writeConsoleMsg('info', 'globalObjectGet ::: Property: _' + property + '_ has the value: _' + value + '_.') 192 | return value 193 | } 194 | 195 | /** 196 | * @function globalObjectSet 197 | * @summary Updates the value of a single property from the global object in main.js 198 | * @description Updates the value of a single property from the global object in main.js 199 | * @param {String} property - Name of the property 200 | * @param {String} value - The new value of the property 201 | */ 202 | function globalObjectSet (property, value) { 203 | const { ipcRenderer } = require('electron') 204 | ipcRenderer.send('globalObjectSet', property, value) 205 | } 206 | 207 | /** 208 | * @function isDirectoryAvailable 209 | * @summary Checks if a given directory exists 210 | * @description Checks if a given directory exists and returns a boolean 211 | * @param {string} dirPath - The directory path which should be checked 212 | * @return {boolean} 213 | */ 214 | function isDirectoryAvailable (dirPath) { 215 | if (dirPath !== '') { 216 | const fs = require('fs') 217 | if (fs.existsSync(dirPath)) { 218 | writeConsoleMsg('info', 'isDirectoryAvailable ::: The directory _' + dirPath + '_ exists') 219 | return true 220 | } else { 221 | writeConsoleMsg('error', 'isDirectoryAvailable ::: The directory _' + dirPath + '_ does not exist') 222 | return false 223 | } 224 | } else { 225 | writeConsoleMsg('error', 'isDirectoryAvailable ::: Should check if a directory exists but the supplied parameter _' + dirPath + '_ was empty') 226 | } 227 | } 228 | 229 | /** 230 | * @function isDirectoryWriteable 231 | * @summary Checks if a given directory is writeable 232 | * @description Checks if a given directory is writeable and returns a boolean 233 | * @param {string} dirPath - The directory path which should be checked 234 | * @return {boolean} 235 | */ 236 | function isDirectoryWriteable (dirPath) { 237 | if (dirPath !== '') { 238 | const fs = require('fs') 239 | 240 | // sync: check if folder is writeable 241 | try { 242 | fs.accessSync(dirPath, fs.constants.W_OK) 243 | writeConsoleMsg('info', 'isDirectoryWriteable ::: Directory _' + dirPath + '_ is writeable') 244 | return true 245 | } catch (err) { 246 | writeConsoleMsg('error', 'isDirectoryWriteable ::: Directory _' + dirPath + '_ is not writeable. Error: _' + err + '_.') 247 | return false 248 | } 249 | } else { 250 | writeConsoleMsg('error', 'isDirectoryWriteable ::: Should check if a directory is writeable but the supplied parameter _' + dirPath + '_ was empty.') 251 | } 252 | } 253 | 254 | function isFileWriteable (filePath) { 255 | if (filePath !== '') { 256 | const fs = require('fs') 257 | 258 | // sync: check if folder is writeable 259 | try { 260 | fs.accessSync(filePath, fs.constants.W_OK) 261 | writeConsoleMsg('info', 'isFileWriteable ::: File _' + filePath + '_ is writeable') 262 | return true 263 | } catch (err) { 264 | writeConsoleMsg('error', 'isFileWriteable ::: File _' + filePath + '_ is not writeable. Error: _' + err + '_.') 265 | return false 266 | } 267 | } else { 268 | writeConsoleMsg('error', 'isFileWriteable ::: Should check if a file is writeable but the supplied parameter _' + filePath + '_ was empty.') 269 | } 270 | } 271 | 272 | /** 273 | * @function userSettingWrite 274 | * @summary Write a user setting to file 275 | * @description Writes a value for a given key to electron-json-storage 276 | * @param {String} key - Name of storage key 277 | * @param {String} value - New value 278 | * @param {boolean} [silent] - If true - no notification is displayed on initial settingscreation 279 | * @throws Exception when writing a file failed 280 | */ 281 | function userSettingWrite (key, value, silent = false) { 282 | const storage = require('electron-json-storage') 283 | const remote = require('electron').remote 284 | const app = remote.app 285 | const path = require('path') 286 | 287 | // set new path for userUsettings 288 | const userSettingsPath = path.join(app.getPath('userData'), 'UserSettings') 289 | storage.setDataPath(userSettingsPath) 290 | 291 | // write the user setting 292 | storage.set(key, { setting: value }, function (error) { 293 | if (error) { 294 | writeConsoleMsg('error', 'userSettingWrite ::: Unable to write setting _' + key + '_ = _' + value + '_. Error: ' + error) 295 | throw error 296 | } 297 | writeConsoleMsg('info', 'userSettingWrite ::: _' + key + '_ = _' + value + '_') 298 | globalObjectSet(key, value) 299 | 300 | if (silent === false) { 301 | showNoty('success', 'Set ' + key + ' to ' + value + '.') 302 | } 303 | }) 304 | } 305 | 306 | /** 307 | * @function userSettingRead 308 | * @summary Read a user setting from file 309 | * @description Reads a value stored in local storage (for a given key) 310 | * @param {String} key - Name of local storage key 311 | * @param {Boolean} [optionalUpdateSettingUI] Boolean used for an ugly hack 312 | */ 313 | function userSettingRead (key, optionalUpdateSettingUI = false) { 314 | const storage = require('electron-json-storage') 315 | const remote = require('electron').remote 316 | const app = remote.app 317 | const path = require('path') 318 | 319 | // writeConsoleMsg('info', 'userSettingRead ::: Trying to read value of key: _' + key + '_.') 320 | 321 | // change path for userSettings 322 | const userSettingsPath = path.join(app.getPath('userData'), 'UserSettings') 323 | storage.setDataPath(userSettingsPath) 324 | 325 | // read the json file 326 | storage.get(key, function (error, data) { 327 | if (error) { 328 | writeConsoleMsg('error', 'userSettingRead ::: Unable to read user setting. Error: ' + error) 329 | throw error 330 | } 331 | var value = data.setting 332 | // writeConsoleMsg('info', 'userSettingRead ::: _' + key + '_ = _' + value + '_.') 333 | 334 | // Setting: enableVerboseMode 335 | // 336 | if (key === 'enableVerboseMode') { 337 | var settingVerboseMode 338 | 339 | // if it is not yet configured 340 | if ((value === null) || (value === undefined)) { 341 | settingVerboseMode = false // set the default default 342 | writeConsoleMsg('warn', 'userSettingRead ::: No user setting found for: _' + key + '_. Initializing it now with the default value: _' + settingVerboseMode + '_.') 343 | userSettingWrite('enableVerboseMode', settingVerboseMode, true) // write the setting 344 | } else { 345 | settingVerboseMode = value // update global var 346 | writeConsoleMsg('info', 'userSettingRead ::: Found configured _' + key + '_ with value: _' + settingVerboseMode + '_.') 347 | } 348 | globalObjectSet('enableVerboseMode', settingVerboseMode) // update the global object 349 | 350 | // Optional: update the settings UI 351 | if (optionalUpdateSettingUI === true) { 352 | if (settingVerboseMode === true) { 353 | $('#checkboxEnableVerbose').prop('checked', true) 354 | } else { 355 | $('#checkboxEnableVerbose').prop('checked', false) 356 | } 357 | } 358 | } 359 | // end: enableVerboseMode 360 | 361 | // Setting: enableAdditionalParameter 362 | // 363 | if (key === 'enableAdditionalParameter') { 364 | var settingAdditionalParameter 365 | 366 | // if it is not yet configured 367 | if ((value === null) || (value === undefined)) { 368 | settingAdditionalParameter = false // set the default 369 | writeConsoleMsg('warn', 'userSettingRead ::: No user setting found for: _' + key + '_. Initializing it now with the default value: _' + settingAdditionalParameter + '_.') 370 | userSettingWrite('enableAdditionalParameter', settingAdditionalParameter, true) // write the setting 371 | } else { 372 | settingAdditionalParameter = value // update global var 373 | writeConsoleMsg('info', 'userSettingRead ::: Found configured _' + key + '_ with value: _' + settingVerboseMode + '_.') 374 | } 375 | globalObjectSet('enableAdditionalParameter', settingAdditionalParameter) // update the global object 376 | 377 | // Optional: update the settings UI 378 | if (optionalUpdateSettingUI === true) { 379 | if (settingAdditionalParameter === true) { 380 | $('#checkboxEnableAdditionalParameter').prop('checked', true) 381 | } else { 382 | $('#checkboxEnableAdditionalParameter').prop('checked', false) 383 | } 384 | } 385 | } 386 | // end: enableAdditionalParameter 387 | 388 | // Setting: additonalYoutubeDlParameter 389 | // 390 | if (key === 'additionalYoutubeDlParameter') { 391 | var settingAdditionalDefinedParameter 392 | 393 | // if it is not yet configured 394 | if ((value === null) || (value === undefined)) { 395 | settingAdditionalDefinedParameter = '' // set the default 396 | writeConsoleMsg('warn', 'userSettingRead ::: No user setting found for: _' + key + '_. Initializing it now with the default value: _' + settingAdditionalParameter + '_.') 397 | userSettingWrite('additionalYoutubeDlParameter', settingAdditionalDefinedParameter, true) // write the setting 398 | } else { 399 | settingAdditionalDefinedParameter = value // update global var 400 | writeConsoleMsg('info', 'userSettingRead ::: Found configured _' + key + '_ with value: _' + settingAdditionalDefinedParameter + '_.') 401 | } 402 | globalObjectSet('additionalYoutubeDlParameter', settingAdditionalDefinedParameter) // update the global object 403 | 404 | // Optional: update the settings UI 405 | if (optionalUpdateSettingUI === true) { 406 | $('#textInputAdditionalParameter').val(settingAdditionalDefinedParameter) 407 | } 408 | } 409 | // end: enableAdditionalParameter 410 | 411 | // Setting: enablePrereleases 412 | // 413 | if (key === 'enablePrereleases') { 414 | var settingPrereleases 415 | 416 | // if it is not yet configured 417 | if ((value === null) || (value === undefined)) { 418 | settingPrereleases = false // set the default 419 | writeConsoleMsg('warn', 'userSettingRead ::: No user setting found for: _' + key + '_. Initializing it now with the default value: _' + settingPrereleases + '_.') 420 | userSettingWrite('enablePrereleases', settingPrereleases, true) // write the setting 421 | } else { 422 | settingPrereleases = value // update global var 423 | writeConsoleMsg('info', 'userSettingRead ::: Found configured _' + key + '_ with value: _' + settingPrereleases + '_.') 424 | } 425 | globalObjectSet('enablePrereleases', settingPrereleases) // update the global object 426 | 427 | // Optional: update the settings UI 428 | if (optionalUpdateSettingUI === true) { 429 | if (settingPrereleases === true) { 430 | $('#checkboxEnablePreReleases').prop('checked', true) 431 | } else { 432 | $('#checkboxEnablePreReleases').prop('checked', false) 433 | } 434 | } 435 | } 436 | // end: enablePrereleases 437 | 438 | // Setting: enableErrorReporting 439 | // 440 | if (key === 'enableErrorReporting') { 441 | var settingEnableErrorReporting 442 | 443 | // not configured 444 | if ((value === null) || (value === undefined)) { 445 | settingEnableErrorReporting = true 446 | writeConsoleMsg('warn', 'userSettingRead ::: No user setting found for: _' + key + '_. Initializing it now with the default value: _' + settingEnableErrorReporting + '_.') 447 | userSettingWrite('enableErrorReporting', true, true) // write the setting 448 | sentry.enableSentry() 449 | } else { 450 | settingEnableErrorReporting = value 451 | writeConsoleMsg('info', 'userSettingRead ::: Found configured _' + key + '_ with value: _' + settingEnableErrorReporting + '_.') 452 | 453 | if (settingEnableErrorReporting === true) { 454 | sentry.enableSentry() 455 | } else { 456 | sentry.disableSentry() 457 | } 458 | } 459 | globalObjectSet('enableErrorReporting', settingEnableErrorReporting) // update the global object 460 | 461 | // Optional: update the settings UI 462 | if (optionalUpdateSettingUI === true) { 463 | if (settingEnableErrorReporting === true) { 464 | $('#checkboxEnableErrorReporting').prop('checked', true) 465 | } else { 466 | $('#checkboxEnableErrorReporting').prop('checked', false) 467 | } 468 | } 469 | } 470 | // end: enableErrorReporting 471 | 472 | // Setting: downloadDir 473 | // 474 | if (key === 'downloadDir') { 475 | // const { remote } = require('electron') 476 | var settingDownloadDir 477 | 478 | // not yet set - seems like initial run 479 | if ((value === null) || (value === undefined)) { 480 | writeConsoleMsg('warn', 'userSettingRead ::: No user setting found for: _' + key + '_. Initial run - lets set the defaut dir.') 481 | var detectedDefaultDownloadDir = defaultDownloadFolderGet() // lets set it do the users default folder dir 482 | if (detectedDefaultDownloadDir[0]) { 483 | settingDownloadDir = detectedDefaultDownloadDir[1] 484 | userSettingWrite('downloadDir', settingDownloadDir, true) 485 | writeConsoleMsg('info', 'userSettingRead ::: _' + key + '_ got initial value: _' + settingDownloadDir + '_.') 486 | } 487 | } else { 488 | // there is a setting 489 | settingDownloadDir = value 490 | writeConsoleMsg('info', 'userSettingRead ::: Found configured _' + key + '_ with value: _' + settingDownloadDir + '_.') 491 | 492 | // check if directory exists 493 | if (isDirectoryAvailable(settingDownloadDir)) { 494 | // check if directory is writeable 495 | if (isDirectoryWriteable(settingDownloadDir)) { 496 | // dir is available and writeable - seems like everything is ok 497 | globalObjectSet('downloadDir', settingDownloadDir) 498 | } else { 499 | writeConsoleMsg('error', 'userSettingRead ::: Configured download dir _' + settingDownloadDir + '_ exists BUT is not writeable. Gonna reset the user-setting.') 500 | settingDownloadDir = '' 501 | globalObjectSet('downloadDir', settingDownloadDir) 502 | 503 | // delete the config 504 | storage.remove('downloadDir', function (error) { 505 | if (error) { 506 | writeConsoleMsg('error', 'userSettingRead ::: Unable to delete config. Error: ' + error) 507 | throw error 508 | } 509 | }) 510 | } 511 | } else { 512 | // dir does not exists 513 | settingDownloadDir = '' 514 | writeConsoleMsg('error', 'userSettingRead ::: Configured download dir _' + settingDownloadDir + '_ does not exists. Gonna reset the user-setting.') 515 | 516 | settingDownloadDir = defaultDownloadFolderGet() // get the default download target 517 | globalObjectSet('downloadDir', settingDownloadDir) // update the object 518 | userSettingWrite('downloadDir', settingDownloadDir, true) // update the config 519 | } 520 | 521 | // Update UI select 522 | if (optionalUpdateSettingUI === true) { 523 | $('#inputCustomTargetDir').val(settingDownloadDir) 524 | } 525 | } 526 | writeConsoleMsg('info', 'userSettingRead ::: _' + key + '_ = _' + settingDownloadDir + '_.') 527 | } 528 | // end: downloadDir 529 | 530 | // Setting: audioFormat 531 | // 532 | if (key === 'audioFormat') { 533 | var settingAudioFormat 534 | // not configured 535 | if ((value === null) || (value === undefined)) { 536 | settingAudioFormat = 'mp3' 537 | writeConsoleMsg('warn', 'userSettingRead ::: No user setting found for: _' + key + '_. Initializing it now with the default value: _' + settingAudioFormat + '_.') 538 | userSettingWrite('audioFormat', settingAudioFormat, true) // write the setting 539 | } else { 540 | settingAudioFormat = value 541 | writeConsoleMsg('info', 'userSettingRead ::: Found configured _' + key + '_ with value: _' + settingAudioFormat + '_.') 542 | globalObjectSet('audioFormat', settingAudioFormat) 543 | } 544 | 545 | // optional: Adjust the UI 546 | if (optionalUpdateSettingUI === true) { 547 | $('#inputGroupSelectAudio').val(settingAudioFormat) // Update UI select 548 | } 549 | } 550 | // end: audioFormat 551 | 552 | // Setting: confirmedDisclaimer 553 | // 554 | if (key === 'confirmedDisclaimer') { 555 | // special case - first check the global object - to make the -s startup parameter possible 556 | var skipDisclaimer = globalObjectGet('confirmedDisclaimer') 557 | if (skipDisclaimer === true) { 558 | // nothing to do here - we are skipping the disclaimer handling 559 | } else { 560 | var settingConfirmedDisclaimer 561 | // not configured 562 | if ((value === null) || (value === undefined)) { 563 | writeConsoleMsg('warn', 'userSettingRead ::: No user setting found for: _' + key + '_. Gonna show the disclaimer now') 564 | disclaimerShow() 565 | } else { 566 | settingConfirmedDisclaimer = true 567 | globalObjectSet('confirmedDisclaimer', settingConfirmedDisclaimer) 568 | writeConsoleMsg('info', 'userSettingRead ::: Found configured _' + key + '_ with value: _' + settingConfirmedDisclaimer + '_.') 569 | ui.windowMainBlurSet(false) // unblur the main UI 570 | } 571 | } 572 | } 573 | // end: confirmedDisclaimer 574 | }) 575 | } 576 | 577 | /** 578 | * @function defaultDownloadFolderGet 579 | * @summary Validates if the default download directory of the user is useable. 580 | * @description Validates if the default download directory of the user is useable. 581 | * @retun {boolean} boolean - Is the folder useable 582 | * @return {String} defaultTargetPath - The path to the folder 583 | */ 584 | function defaultDownloadFolderGet () { 585 | var defaultTargetPath = globalObjectGet('downloadDir') // use the default download target - which was configured in main.js 586 | writeConsoleMsg('info', 'defaultDownloadFolderGet ::: Searching the default download directory for this user ....') 587 | writeConsoleMsg('info', 'defaultDownloadFolderGet ::: Got' + defaultTargetPath + ' from global object') 588 | 589 | // check if that directory still exists 590 | if (isDirectoryAvailable(defaultTargetPath)) { 591 | writeConsoleMsg('info', 'defaultDownloadFolderGet ::: The default download location _' + defaultTargetPath + '_ exists') // the default download folder exists 592 | 593 | // check if it is writeable 594 | if (isDirectoryWriteable(defaultTargetPath)) { 595 | writeConsoleMsg('info', 'defaultDownloadFolderGet ::: The default download location _' + defaultTargetPath + '_ exists and is writeable. We are all good and gonna use it now') 596 | return [true, defaultTargetPath] 597 | } else { 598 | // folder exists but is not writeable 599 | writeConsoleMsg('error', 'defaultDownloadFolderGet ::: The default download location _' + defaultTargetPath + '_ exists but is not writeable. This is a major problem') 600 | showNoty('error', 'Your configured custom download directory ' + defaultTargetPath + ' exists but is not writeable. Gonna reset the custom setting now back to default', 0) 601 | return [false, ''] 602 | } 603 | } else { 604 | // was unable to detect a download folder. Should force the user to set a custom one 605 | writeConsoleMsg('error', 'defaultDownloadFolderGet ::: Was unable to detect an existing default download location') 606 | showNoty('error', 'Unable to detect an existing default download location. Please configure a download directory in the application settings', 0) 607 | return [false, ''] 608 | } 609 | } 610 | 611 | /** 612 | * @function disclaimerCheck 613 | * @summary Checks if the disclaimer should be shown or not 614 | * @description Is using userSettingRead() to read the user setting confirmedDisclaimer.json. If it exists the user previously confirmed it. 615 | */ 616 | function disclaimerCheck () { 617 | userSettingRead('confirmedDisclaimer') 618 | } 619 | 620 | /** 621 | * @function disclaimerShow 622 | * @summary Opens the disclaimer as dialog 623 | * @description Displays a disclaimer regarding app usage. User should confirm it once. Setting is saved in UserSettings (.json file) 624 | */ 625 | function disclaimerShow () { 626 | const dialog = require('electron').remote.dialog 627 | writeConsoleMsg('warn', 'disclaimerShow ::: Showing the disclaimer now.') 628 | var disclaimerTitle = 'media-dupes disclaimer' 629 | var disclaimerText = 'THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.' 630 | 631 | var choice = dialog.showMessageBoxSync(this, 632 | { 633 | type: 'info', 634 | buttons: ['Confirm'], 635 | title: disclaimerTitle, 636 | message: disclaimerText 637 | }) 638 | if (choice === 0) { 639 | writeConsoleMsg('info', 'disclaimerShow ::: User confirmed the disclaimer.') 640 | userSettingWrite('confirmedDisclaimer', true) 641 | ui.windowMainBlurSet(false) // unblur the main UI 642 | } 643 | } 644 | 645 | /** 646 | * @function downloadStatusCheck 647 | * @summary Checks if all downloads finished 648 | * @description Checks if all downloads finished and creates a final status report using noty 649 | * @param {number} - overall - The amount of overall urls 650 | * @param {number} - succeeded - The amount of succeeded urls 651 | * @param {number} - failed - The amount of failed urls 652 | */ 653 | function downloadStatusCheck (overall = 0, succeeded = 0, failed = 0) { 654 | var statusReport 655 | var notificationType 656 | var notificationTime 657 | 658 | writeConsoleMsg('info', 'downloadStatusCheck ::: Overall: ' + overall) 659 | writeConsoleMsg('info', 'downloadStatusCheck ::: Succeeded: ' + succeeded) 660 | writeConsoleMsg('warn', 'downloadStatusCheck ::: Failed: ' + failed) 661 | 662 | if (overall === succeeded + failed) { 663 | writeConsoleMsg('info', 'downloadStatusCheck ::: All download tasks are finished') 664 | 665 | // everything is fine 666 | if (overall === succeeded) { 667 | writeConsoleMsg('info', 'downloadStatusCheck ::: All downloads successfully') 668 | statusReport = 'Finished entire download queue (' + overall + ') successfully' 669 | notificationType = 'success' 670 | notificationTime = 3000 671 | } 672 | 673 | // some errors 674 | if ((overall === succeeded + failed) && (succeeded > 0) && (failed > 0)) { 675 | writeConsoleMsg('warn', 'downloadStatusCheck ::: Some downloads failed') 676 | statusReport = 'Finished entire download queue (' + overall + ') - ' + succeeded + ' succeeded and ' + failed + ' failed with errors.' 677 | notificationType = 'warning' 678 | notificationTime = 0 679 | } 680 | 681 | // all failed 682 | if ((overall === failed) && (succeeded === 0)) { 683 | writeConsoleMsg('error', 'downloadStatusCheck ::: All downloads failed') 684 | statusReport = 'Finished entire download queue (' + overall + ') - but all downloads failed with errors.' 685 | notificationType = 'error' 686 | notificationTime = 0 687 | } 688 | 689 | showNotification(statusReport) // show an OS notification 690 | showNoty(notificationType, statusReport, notificationTime) // show an in-app notification 691 | ui.windowMainLogAppend(statusReport + '', true) // append to log 692 | ui.windowMainLogAppend('### QUEUE ENDED ###\n\n', true) // Show mode in log 693 | ui.windowMainDownloadQueueFinished() 694 | 695 | // reset the datatable 696 | ui.dataTablesReset() 697 | } else { 698 | writeConsoleMsg('info', 'downloadStatusCheck ::: Some download tasks are not yet finished') 699 | } 700 | } 701 | 702 | /** 703 | * @function generateTimestamp 704 | * @summary Generates a timestamp 705 | * @description Generates a timestamp using time-stamp 706 | * @return {string} - timestamp - The generates timestamp 707 | */ 708 | function generateTimestamp () { 709 | const timestamp = require('time-stamp') 710 | var currentTimestamp = timestamp('HH:mm:ss') // hours : minutes : seconds 711 | return currentTimestamp 712 | } 713 | 714 | /** 715 | * @function canWriteFileOrFolder 716 | * @summary Checks if a file or folder is writeable 717 | * @description Checks if a file or folder is writeable 718 | * @param {String} path - Path which should be checked 719 | */ 720 | function canWriteFileOrFolder (path, callback) { 721 | const fs = require('fs') 722 | fs.access(path, fs.W_OK, function (err) { 723 | callback(null, !err) 724 | }) 725 | } 726 | 727 | /** 728 | * @function generateUrlId 729 | * @summary generates an id based on a given url 730 | * @description generates an id based on a given url 731 | * @param {String} url - url 732 | * @return {string} urlId - the generated md5 733 | */ 734 | function generateUrlId (url) { 735 | var md5 = require('md5') 736 | var urlId = md5(url) 737 | return urlId 738 | } 739 | 740 | // ---------------------------------------------------------------------------- 741 | // EXPORT THE MODULE FUNCTIONS 742 | // ---------------------------------------------------------------------------- 743 | // 744 | module.exports.writeConsoleMsg = writeConsoleMsg 745 | module.exports.showNoty = showNoty 746 | module.exports.showNotification = showNotification 747 | module.exports.openURL = openURL 748 | module.exports.validURL = validURL 749 | module.exports.formatBytes = formatBytes 750 | module.exports.isEncoded = isEncoded 751 | module.exports.fullyDecodeURI = fullyDecodeURI 752 | module.exports.pathExists = pathExists 753 | module.exports.globalObjectGet = globalObjectGet 754 | module.exports.globalObjectSet = globalObjectSet 755 | module.exports.isDirectoryAvailable = isDirectoryAvailable 756 | module.exports.isDirectoryWriteable = isDirectoryWriteable 757 | module.exports.isFileWriteable = isFileWriteable 758 | module.exports.userSettingWrite = userSettingWrite 759 | module.exports.userSettingRead = userSettingRead 760 | module.exports.defaultDownloadFolderGet = defaultDownloadFolderGet 761 | module.exports.disclaimerCheck = disclaimerCheck 762 | module.exports.disclaimerShow = disclaimerShow 763 | module.exports.downloadStatusCheck = downloadStatusCheck 764 | module.exports.generateTimestamp = generateTimestamp 765 | module.exports.canWriteFileOrFolder = canWriteFileOrFolder 766 | module.exports.generateUrlId = generateUrlId 767 | --------------------------------------------------------------------------------