Compare commits

...

33 Commits

Author SHA1 Message Date
c25def8901 Bump version to 2.1.2 2023-10-19 22:39:14 +09:00
284a59b721 fix: fix unresponsive (fix #1325) 2023-10-19 22:35:32 +09:00
5fcba8619a feat(in-app-menu): add an option to hide the window controls (#1335) 2023-10-19 22:34:18 +09:00
f3cd759276 fix: fixed an issue where the album name was missing (#1334) 2023-10-19 21:45:46 +09:00
9d3981e361 chore: Update build.yml 2023-10-19 21:33:54 +09:00
787326948b chore: making actions more efficient 2023-10-19 09:51:31 +09:00
779251933c chore(deps): update dependency electron to v27.0.1 (#1331) 2023-10-19 09:42:41 +09:00
1efe835c69 fix: fixed an issue where only the first 100 songs in a playlist were downloaded (#1329) 2023-10-19 05:40:05 +09:00
5702978227 chore(deps): update dependencies 2023-10-18 18:18:28 +09:00
fa3d742838 chore(deps): update dependency @typescript-eslint/eslint-plugin to v6.8.0 2023-10-18 02:21:25 +09:00
c460cc2296 Updated readme plugins list (#1326) 2023-10-17 20:04:23 +09:00
4e4af5e830 fix(actions): fix if statement 2023-10-16 22:55:04 +09:00
9a4e98063b chore(actions): disable pnpm cache for macOS 2023-10-16 22:54:11 +09:00
8bfe04bb50 chore: update issue template
Thanks to Alipoodle for writing this question.
2023-10-16 22:42:02 +09:00
6774d54f5e chore(deps): update dependency rollup to v4.1.4 2023-10-16 16:41:36 +09:00
9705f8489d chore(deps): Bump @rollup/plugin-commonjs, pnpm version, Remove ytpl 2023-10-16 16:24:16 +09:00
a7229cbe14 Bump @rollup/plugin-commonjs, pnpm version 2023-10-16 03:08:04 +09:00
7577aba45e feat: use test:debug for CI 2023-10-16 02:08:25 +09:00
d78fbe476e hotfix: fix Cannot read properties of undefined (reading 'removeChild') 2023-10-16 01:09:24 +09:00
bfe4b2bba7 fix(actions): use GabrielBB/xvfb-action instead of coactions/setup-xvfb 2023-10-16 00:58:02 +09:00
7625a3aa52 QOL: Move source code under the src directory. (#1318) 2023-10-15 21:52:48 +09:00
30c8dcf730 fix: release action 2023-10-15 18:54:25 +09:00
00a3e8d35e chore(deps): Bump rollup, @xhayper/discord-rpc version 2023-10-15 18:35:57 +09:00
4d01cdfa6c fix(blocker): remove the app.isPackaged check (fix #1315) 2023-10-15 18:33:14 +09:00
f924b6c8e3 fix(actions): install pnpm before call setup-node 2023-10-15 18:28:09 +09:00
926d98174c fix: fix build actions 2023-10-15 18:22:19 +09:00
41b3972f54 chore(README): add pnpm install guide 2023-10-15 18:20:49 +09:00
467f29e363 feat: migrate from npm to pnpm (#1316) 2023-10-15 18:18:20 +09:00
9cc13c3757 Merge pull request #1317 from foonathan/fix-loop-status 2023-10-15 04:23:33 +09:00
f8ccb86156 Fix mpris player.loopStatus 2023-10-14 21:03:06 +02:00
b316aa2301 chore(deps): update dependency rollup to v4.1.0 2023-10-14 22:33:09 +09:00
5c49b28664 fix(discord): Discord RPC fails if a song's title is only one character (fix #1314) 2023-10-14 20:27:58 +09:00
dedf96afd3 Update changelog for v2.1.1 2023-10-14 05:49:56 +00:00
168 changed files with 6201 additions and 10351 deletions

View File

@ -12,11 +12,13 @@ body:
required: true required: true
- label: I have searched the [issue tracker](https://github.com/th-ch/youtube-music/issues) for a bug report that matches the one I want to file, without success. - label: I have searched the [issue tracker](https://github.com/th-ch/youtube-music/issues) for a bug report that matches the one I want to file, without success.
required: true required: true
- label: I understand that **th-ch/youtube-music has NO affiliation with Google or YouTube**
required: true
- type: input - type: input
attributes: attributes:
label: YouTube Music (Application) Version label: YouTube Music (Application) Version
description: | description: |
What version of YouTube Music Application are you using? What version of the YouTube Music Application are you using?
Note: Please check if this issue is reproducible with the latest stable release. Note: Please check if this issue is reproducible with the latest stable release.
placeholder: 2.0.0 placeholder: 2.0.0
@ -36,7 +38,7 @@ body:
- type: input - type: input
attributes: attributes:
label: Operating System Version label: Operating System Version
description: What operating system version are you using? On Windows, click Start button > Settings > System > About. On macOS, click the Apple Menu > About This Mac. On Linux, use lsb_release or uname -a. description: What operating system version are you using? On Windows, click the Start button > Settings > System > About. On macOS, click the Apple Menu > About This Mac. On Linux, use lsb_release or uname -a.
placeholder: "e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04" placeholder: "e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04"
validations: validations:
required: true required: true

View File

@ -20,59 +20,63 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false
- name: Setup NodeJS - name: Setup NodeJS
if: startsWith(matrix.os, 'macOS') != true
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Setup NodeJS for macOS
if: startsWith(matrix.os, 'macOS')
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies - name: Install dependencies
run: npm ci run: pnpm install --frozen-lockfile
# Only rollup build without release if it is a fork
- name: Rollup Build
if: github.repository == 'th-ch/youtube-music' && github.event_name == 'pull_request'
run: |
pnpm build
# Build and release if it's the main repository and is not pull-request
- name: Build and release on Mac
if: startsWith(matrix.os, 'macOS') && (github.repository == 'th-ch/youtube-music' && github.event_name != 'pull_request')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pnpm release:mac
- name: Build and release on Linux
if: startsWith(matrix.os, 'ubuntu') && (github.repository == 'th-ch/youtube-music' && github.event_name != 'pull_request')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pnpm release:linux
- name: Build and release on Windows
if: startsWith(matrix.os, 'windows') && (github.repository == 'th-ch/youtube-music' && github.event_name != 'pull_request')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pnpm release:win
- name: Test - name: Test
uses: GabrielBB/xvfb-action@v1 uses: coactions/setup-xvfb@v1
env: env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
with: with:
run: npm run test run: pnpm test:debug
# Build and release if it's the main repository
- name: Build and release on Mac
if: startsWith(matrix.os, 'macOS') && github.repository == 'th-ch/youtube-music'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm run release:mac
- name: Build and release on Linux
if: startsWith(matrix.os, 'ubuntu') && github.repository == 'th-ch/youtube-music'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm run release:linux
- name: Build and release on Windows
if: startsWith(matrix.os, 'windows') && github.repository == 'th-ch/youtube-music'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm run release:win
# Only build without release if it is a fork
- name: Build on Mac
if: startsWith(matrix.os, 'macOS') && github.repository != 'th-ch/youtube-music'
run: |
npm run build:mac
- name: Build on Linux
if: startsWith(matrix.os, 'ubuntu') && github.repository != 'th-ch/youtube-music'
run: |
npm run build:linux
- name: Build on Windows
if: startsWith(matrix.os, 'windows') && github.repository != 'th-ch/youtube-music'
run: |
npm run build:win
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -84,14 +88,27 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
run_install: false
- name: Setup NodeJS - name: Setup NodeJS
if: startsWith(matrix.os, 'macOS') != true
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Setup NodeJS for macOS
if: startsWith(matrix.os, 'macOS')
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies - name: Install dependencies
run: npm ci run: pnpm install --frozen-lockfile
- name: Get version - name: Get version
run: | run: |
@ -132,7 +149,7 @@ jobs:
- name: Update changelog - name: Update changelog
if: ${{ env.VERSION_HASH == '' }} if: ${{ env.VERSION_HASH == '' }}
run: | run: |
npm run changelog pnpm changelog
- name: Commit changelog - name: Commit changelog
if: ${{ env.VERSION_HASH == '' }} if: ${{ env.VERSION_HASH == '' }}

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"tabWidth": 2,
"useTabs": false,
"singleQuote": true
}

View File

@ -2,8 +2,17 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [v2.1.1](https://github.com/th-ch/youtube-music/compare/v2.1.0...v2.1.1)
- hotfix(downloader): can't get an album title (fix #1313) [`#1313`](https://github.com/th-ch/youtube-music/issues/1313)
- Update changelog for v2.1.0 [`92cab89`](https://github.com/th-ch/youtube-music/commit/92cab89d17175741e60e65ea61633e23ebdc1f45)
- Bump version to 2.1.1 [`3bb5bc2`](https://github.com/th-ch/youtube-music/commit/3bb5bc2ca1856f4e222ee1e01e865f1ab804fdba)
- Add "about" menu to show app version [`21c45fa`](https://github.com/th-ch/youtube-music/commit/21c45faf2043cf72a7c14d5cf6c8d848d0448528)
#### [v2.1.0](https://github.com/th-ch/youtube-music/compare/v2.0.4...v2.1.0) #### [v2.1.0](https://github.com/th-ch/youtube-music/compare/v2.0.4...v2.1.0)
> 14 October 2023
- feat(downloader): Added support for audio format auto-detection [`#1310`](https://github.com/th-ch/youtube-music/pull/1310) - feat(downloader): Added support for audio format auto-detection [`#1310`](https://github.com/th-ch/youtube-music/pull/1310)
- feat(in-app-menu): enable in-app-menu by default (in Windows) [`#1311`](https://github.com/th-ch/youtube-music/pull/1311) - feat(in-app-menu): enable in-app-menu by default (in Windows) [`#1311`](https://github.com/th-ch/youtube-music/pull/1311)
- fix: winget publish [`#1307`](https://github.com/th-ch/youtube-music/pull/1307) - fix: winget publish [`#1307`](https://github.com/th-ch/youtube-music/pull/1307)

10137
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"name": "youtube-music", "name": "youtube-music",
"productName": "YouTube Music", "productName": "YouTube Music",
"version": "2.1.1", "version": "2.1.2",
"description": "YouTube Music Desktop App - including custom plugins", "description": "YouTube Music Desktop App - including custom plugins",
"main": "./dist/index.js", "main": "./dist/index.js",
"license": "MIT", "license": "MIT",
@ -87,39 +87,58 @@
} }
}, },
"scripts": { "scripts": {
"test": "npm run build && playwright test", "test": "playwright test",
"test:debug": "DEBUG=pw:browser* npm run build && playwright test", "test:debug": "cross-env DEBUG=pw:*,-pw:test:protocol playwright test",
"rollup:preload": "rollup -c rollup.preload.config.ts --configPlugin @rollup/plugin-typescript --bundleConfigAsCjs", "rollup:preload": "rollup -c rollup.preload.config.ts --configPlugin @rollup/plugin-typescript --bundleConfigAsCjs",
"rollup:main": "rollup -c rollup.main.config.ts --configPlugin @rollup/plugin-typescript --bundleConfigAsCjs", "rollup:main": "rollup -c rollup.main.config.ts --configPlugin @rollup/plugin-typescript --bundleConfigAsCjs",
"build": "npm run rollup:preload && npm run rollup:main", "build": "yarpm-pnpm run rollup:preload && yarpm-pnpm run rollup:main",
"start": "npm run build && electron ./dist/index.js", "start": "yarpm-pnpm run build && electron ./dist/index.js",
"start:debug": "ELECTRON_ENABLE_LOGGING=1 npm run start", "start:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 yarpm-pnpm run start",
"postinstall": "patch-package", "postinstall": "patch-package",
"clean": "del-cli dist && del-cli pack", "clean": "del-cli dist && del-cli pack",
"dist": "npm run clean && npm run build && electron-builder --win --mac --linux -p never", "dist": "yarpm-pnpm run clean && yarpm-pnpm run build && electron-builder --win --mac --linux -p never",
"dist:linux": "npm run clean && npm run build && electron-builder --linux -p never", "dist:linux": "yarpm-pnpm run clean && yarpm-pnpm run build && electron-builder --linux -p never",
"dist:mac": "npm run clean && npm run build && electron-builder --mac dmg:x64 -p never", "dist:mac": "yarpm-pnpm run clean && yarpm-pnpm run build && electron-builder --mac dmg:x64 -p never",
"dist:mac:arm64": "npm run clean && npm run build && electron-builder --mac dmg:arm64 -p never", "dist:mac:arm64": "yarpm-pnpm run clean && yarpm-pnpm run build && electron-builder --mac dmg:arm64 -p never",
"dist:win": "npm run clean && npm run build && electron-builder --win -p never", "dist:win": "yarpm-pnpm run clean && yarpm-pnpm run build && electron-builder --win -p never",
"dist:win:x64": "npm run clean && npm run build && electron-builder --win nsis-web:x64 -p never", "dist:win:x64": "yarpm-pnpm run clean && yarpm-pnpm run build && electron-builder --win nsis-web:x64 -p never",
"lint": "eslint .", "lint": "eslint .",
"changelog": "auto-changelog", "changelog": "auto-changelog",
"release:linux": "npm run clean && npm run build && electron-builder --linux -p always -c.snap.publish=github", "release:linux": "yarpm-pnpm run clean && yarpm-pnpm run build && electron-builder --linux -p always -c.snap.publish=github",
"release:mac": "npm run clean && npm run build && electron-builder --mac -p always", "release:mac": "yarpm-pnpm run clean && yarpm-pnpm run build && electron-builder --mac -p always",
"release:win": "npm run clean && npm run build && electron-builder --win -p always", "release:win": "yarpm-pnpm run clean && yarpm-pnpm run build && electron-builder --win -p always",
"typecheck": "tsc -p tsconfig.json --noEmit" "typecheck": "tsc -p tsconfig.json --noEmit"
}, },
"engines": { "engines": {
"node": ">=16.0.0" "node": ">=16.0.0"
}, },
"pnpm": {
"overrides": {
"rollup": "4.1.4",
"node-gyp": "9.4.0",
"xml2js": "0.6.2",
"node-fetch": "2.7.0",
"@electron/universal": "1.4.2",
"@babel/runtime": "7.23.2"
}
},
"overrides": {
"rollup": "4.1.4",
"node-gyp": "9.4.0",
"xml2js": "0.6.2",
"node-fetch": "2.7.0",
"@electron/universal": "1.4.2",
"@babel/runtime": "7.23.2"
},
"dependencies": { "dependencies": {
"@cliqz/adblocker-electron": "1.26.8", "@cliqz/adblocker-electron": "1.26.8",
"@cliqz/adblocker-electron-preload": "1.26.8",
"@ffmpeg.wasm/core-mt": "0.12.0", "@ffmpeg.wasm/core-mt": "0.12.0",
"@ffmpeg.wasm/main": "0.12.0", "@ffmpeg.wasm/main": "0.12.0",
"@foobar404/wave": "2.0.4", "@foobar404/wave": "2.0.4",
"@jellybrick/electron-better-web-request": "1.0.4", "@jellybrick/electron-better-web-request": "1.0.4",
"@jellybrick/mpris-service": "2.1.4", "@jellybrick/mpris-service": "2.1.4",
"@xhayper/discord-rpc": "1.0.23", "@xhayper/discord-rpc": "1.0.24",
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"butterchurn": "3.0.0-beta.4", "butterchurn": "3.0.0-beta.4",
"butterchurn-presets": "3.0.0-beta.4", "butterchurn-presets": "3.0.0-beta.4",
@ -141,20 +160,12 @@
"simple-youtube-age-restriction-bypass": "git+https://github.com/organization/Simple-YouTube-Age-Restriction-Bypass.git#v2.5.8", "simple-youtube-age-restriction-bypass": "git+https://github.com/organization/Simple-YouTube-Age-Restriction-Bypass.git#v2.5.8",
"vudio": "2.1.1", "vudio": "2.1.1",
"x11": "2.3.0", "x11": "2.3.0",
"youtubei.js": "6.4.1", "youtubei.js": "6.4.1"
"ytpl": "2.3.0"
},
"overrides": {
"rollup": "4.0.2",
"node-gyp": "9.4.0",
"xml2js": "0.6.2",
"node-fetch": "2.7.0",
"@electron/universal": "1.4.2",
"@babel/runtime": "7.23.2"
}, },
"devDependencies": { "devDependencies": {
"@milahu/patch-package": "6.4.14",
"@playwright/test": "1.39.0", "@playwright/test": "1.39.0",
"@rollup/plugin-commonjs": "25.0.5", "@rollup/plugin-commonjs": "25.0.7",
"@rollup/plugin-image": "3.0.3", "@rollup/plugin-image": "3.0.3",
"@rollup/plugin-json": "6.0.1", "@rollup/plugin-json": "6.0.1",
"@rollup/plugin-node-resolve": "15.2.3", "@rollup/plugin-node-resolve": "15.2.3",
@ -162,31 +173,34 @@
"@rollup/plugin-typescript": "11.1.5", "@rollup/plugin-typescript": "11.1.5",
"@rollup/plugin-wasm": "6.2.2", "@rollup/plugin-wasm": "6.2.2",
"@total-typescript/ts-reset": "0.5.1", "@total-typescript/ts-reset": "0.5.1",
"@types/electron-localshortcut": "3.1.1", "@types/electron-localshortcut": "3.1.2",
"@types/howler": "2.2.9", "@types/howler": "2.2.10",
"@types/html-to-text": "9.0.2", "@types/html-to-text": "9.0.3",
"@typescript-eslint/eslint-plugin": "6.7.5", "@typescript-eslint/eslint-plugin": "6.8.0",
"auto-changelog": "2.4.0", "auto-changelog": "2.4.0",
"builtin-modules": "^3.3.0",
"cross-env": "7.0.3",
"del-cli": "5.1.0", "del-cli": "5.1.0",
"electron": "27.0.0", "electron": "27.0.1",
"electron-builder": "24.6.4", "electron-builder": "24.6.4",
"electron-devtools-installer": "3.2.0", "electron-devtools-installer": "3.2.0",
"eslint": "8.51.0", "eslint": "8.51.0",
"eslint-plugin-import": "2.28.1", "eslint-plugin-import": "2.28.1",
"eslint-plugin-prettier": "5.0.1", "eslint-plugin-prettier": "5.0.1",
"node-gyp": "9.4.0", "node-gyp": "9.4.0",
"patch-package": "8.0.0",
"playwright": "1.39.0", "playwright": "1.39.0",
"rollup": "4.0.2", "rollup": "4.1.4",
"rollup-plugin-copy": "3.5.0", "rollup-plugin-copy": "3.5.0",
"rollup-plugin-import-css": "3.3.5", "rollup-plugin-import-css": "3.3.5",
"rollup-plugin-string": "3.0.0", "rollup-plugin-string": "3.0.0",
"typescript": "5.2.2" "typescript": "5.2.2",
"yarpm": "1.2.0"
}, },
"auto-changelog": { "auto-changelog": {
"hideCredit": true, "hideCredit": true,
"package": true, "package": true,
"unreleased": true, "unreleased": true,
"output": "changelog.md" "output": "changelog.md"
} },
"packageManager": "pnpm@8.9.2"
} }

5618
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@
<div align="center"> <div align="center">
<a href="https://github.com/th-ch/youtube-music/releases/latest"> <a href="https://github.com/th-ch/youtube-music/releases/latest">
<img src="web/youtube-music.svg" width="400" height="100"> <img src="web/youtube-music.svg" width="400" height="100" alt="YouTube Music SVG">
</a> </a>
</div> </div>
@ -79,6 +79,10 @@ winget install th-ch.YouTubeMusic
- **Ad Blocker**: Block all ads and tracking out of the box - **Ad Blocker**: Block all ads and tracking out of the box
- **Album Color Theme**: Applies a dynamic theme and visual effects based on the album color palette
- **Ambient Mode**: Applies a lighting effect by casting gentle colors from the video, into your screens background.
- **Audio Compressor**: Apply compression to audio (lowers the volume of the loudest parts of the signal and raises the - **Audio Compressor**: Apply compression to audio (lowers the volume of the loudest parts of the signal and raises the
volume of the softest parts) volume of the softest parts)
@ -111,6 +115,8 @@ winget install th-ch.YouTubeMusic
- [**Last.fm**](https://www.last.fm/): Scrobbles support - [**Last.fm**](https://www.last.fm/): Scrobbles support
- **Lumia Stream**: Adds [Lumia Stream](https://lumiastream.com/) support
- **Lyrics Genius**: Adds lyrics support for most songs - **Lyrics Genius**: Adds lyrics support for most songs
- **Navigation**: Next/Back navigation arrows directly integrated in the interface, like in your favorite browser - **Navigation**: Next/Back navigation arrows directly integrated in the interface, like in your favorite browser
@ -178,8 +184,8 @@ Some predefined themes are available in https://github.com/kerichdev/themes-for-
```bash ```bash
git clone https://github.com/th-ch/youtube-music git clone https://github.com/th-ch/youtube-music
cd youtube-music cd youtube-music
npm ci pnpm install --frozen-lockfile
npm run start pnpm start
``` ```
## Build your own plugins ## Build your own plugins
@ -266,12 +272,13 @@ export default () => {
## Build ## Build
1. Clone the repo 1. Clone the repo
2. Run `npm i` to install dependencies 2. Follow [this guide](https://pnpm.io/installation) to install `pnpm`
3. Run `npm run build:OS` 3. Run `pnpm install --frozen-lockfile` to install dependencies
4. Run `pnpm build:OS`
- `npm run dist:win` - Windows - `pnpm dist:win` - Windows
- `npm run dist:linux` - Linux - `pnpm dist:linux` - Linux
- `npm run dist:mac` - MacOS - `pnpm dist:mac` - MacOS
Builds the app for macOS, Linux, and Windows, Builds the app for macOS, Linux, and Windows,
using [electron-builder](https://github.com/electron-userland/electron-builder). using [electron-builder](https://github.com/electron-userland/electron-builder).
@ -279,7 +286,7 @@ using [electron-builder](https://github.com/electron-userland/electron-builder).
## Tests ## Tests
```bash ```bash
npm run test pnpm test
``` ```
Uses [Playwright](https://playwright.dev/) to test the app. Uses [Playwright](https://playwright.dev/) to test the app.

View File

@ -18,7 +18,7 @@ export default defineConfig({
nodeResolvePlugin({ nodeResolvePlugin({
browser: false, browser: false,
preferBuiltins: true, preferBuiltins: true,
exportConditions: ['node', 'default', 'module', 'import'] , exportConditions: ['node', 'default', 'module', 'import'],
}), }),
commonjs({ commonjs({
ignoreDynamicRequires: true, ignoreDynamicRequires: true,
@ -34,7 +34,7 @@ export default defineConfig({
css(), css(),
copy({ copy({
targets: [ targets: [
{ src: 'error.html', dest: 'dist/' }, { src: 'src/error.html', dest: 'dist/' },
{ src: 'assets', dest: 'dist/' }, { src: 'assets', dest: 'dist/' },
], ],
}), }),
@ -47,18 +47,14 @@ export default defineConfig({
setTimeout(() => process.exit(0)); setTimeout(() => process.exit(0));
} }
}, },
name: 'force-close' name: 'force-close',
}, },
], ],
input: './index.ts', input: './src/index.ts',
output: { output: {
format: 'cjs', format: 'cjs',
name: '[name].js', name: '[name].js',
dir: './dist', dir: './dist',
}, },
external: [ external: ['electron', 'custom-electron-prompt', ...builtinModules],
'electron',
'custom-electron-prompt',
...builtinModules,
],
}); });

View File

@ -41,18 +41,14 @@ export default defineConfig({
setTimeout(() => process.exit(0)); setTimeout(() => process.exit(0));
} }
}, },
name: 'force-close' name: 'force-close',
}, },
], ],
input: './preload.ts', input: './src/preload.ts',
output: { output: {
format: 'cjs', format: 'cjs',
name: '[name].js', name: '[name].js',
dir: './dist', dir: './dist',
}, },
external: [ external: ['electron', 'custom-electron-prompt', ...builtinModules],
'electron',
'custom-electron-prompt',
...builtinModules,
],
}); });

View File

@ -76,7 +76,7 @@ const defaultConfig = {
'adblocker': { 'adblocker': {
enabled: true, enabled: true,
cache: true, cache: true,
blocker: blockers.WithBlocklists as string, blocker: blockers.InPlayer as string,
additionalBlockLists: [], // Additional list of filters, e.g "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt" additionalBlockLists: [], // Additional list of filters, e.g "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt"
disableDefaultLists: false, disableDefaultLists: false,
}, },
@ -125,6 +125,7 @@ const defaultConfig = {
* true in Windows, false in Linux and macOS (see youtube-music/config/store.ts) * true in Windows, false in Linux and macOS (see youtube-music/config/store.ts)
*/ */
enabled: false, enabled: false,
hideDOMWindowControls: false,
}, },
'last-fm': { 'last-fm': {
enabled: false, enabled: false,

View File

@ -13,6 +13,7 @@ import crossfadeMenu from './plugins/crossfade/menu';
import disableAutoplayMenu from './plugins/disable-autoplay/menu'; import disableAutoplayMenu from './plugins/disable-autoplay/menu';
import discordMenu from './plugins/discord/menu'; import discordMenu from './plugins/discord/menu';
import downloaderMenu from './plugins/downloader/menu'; import downloaderMenu from './plugins/downloader/menu';
import inAppMenuTitlebarMenu from './plugins/in-app-menu/menu';
import lyricsGeniusMenu from './plugins/lyrics-genius/menu'; import lyricsGeniusMenu from './plugins/lyrics-genius/menu';
import notificationsMenu from './plugins/notifications/menu'; import notificationsMenu from './plugins/notifications/menu';
import pictureInPictureMenu from './plugins/picture-in-picture/menu'; import pictureInPictureMenu from './plugins/picture-in-picture/menu';
@ -36,6 +37,7 @@ const pluginMenus = {
'crossfade': crossfadeMenu, 'crossfade': crossfadeMenu,
'discord': discordMenu, 'discord': discordMenu,
'downloader': downloaderMenu, 'downloader': downloaderMenu,
'in-app-menu': inAppMenuTitlebarMenu,
'lyrics-genius': lyricsGeniusMenu, 'lyrics-genius': lyricsGeniusMenu,
'notifications': notificationsMenu, 'notifications': notificationsMenu,
'picture-in-picture': pictureInPictureMenu, 'picture-in-picture': pictureInPictureMenu,

View File

@ -24,12 +24,7 @@ export const loadAdBlockerEngine = async (
disableDefaultLists: boolean | unknown[] = false, disableDefaultLists: boolean | unknown[] = false,
) => { ) => {
// Only use cache if no additional blocklists are passed // Only use cache if no additional blocklists are passed
let cacheDirectory: string; const cacheDirectory = path.join(app.getPath('userData'), 'adblock_cache');
if (app.isPackaged) {
cacheDirectory = path.join(app.getPath('userData'), 'adblock_cache');
} else {
cacheDirectory = path.resolve(__dirname, 'adblock_cache');
}
if (!fs.existsSync(cacheDirectory)) { if (!fs.existsSync(cacheDirectory)) {
fs.mkdirSync(cacheDirectory); fs.mkdirSync(cacheDirectory);
} }

View File

@ -156,6 +156,14 @@ export default (
// Song information changed, so lets update the rich presence // Song information changed, so lets update the rich presence
// @see https://discord.com/developers/docs/topics/gateway#activity-object // @see https://discord.com/developers/docs/topics/gateway#activity-object
// not all options are transfered through https://github.com/discordjs/RPC/blob/6f83d8d812c87cb7ae22064acd132600407d7d05/src/client.js#L518-530 // not all options are transfered through https://github.com/discordjs/RPC/blob/6f83d8d812c87cb7ae22064acd132600407d7d05/src/client.js#L518-530
const hangulFillerUnicodeCharacter = '\u3164'; // This is an empty character
if (songInfo.title.length < 2) {
songInfo.title += hangulFillerUnicodeCharacter.repeat(2 - songInfo.title.length);
}
if (songInfo.artist.length < 2) {
songInfo.artist += hangulFillerUnicodeCharacter.repeat(2 - songInfo.title.length);
}
const activityInfo: SetActivity = { const activityInfo: SetActivity = {
details: songInfo.title, details: songInfo.title,
state: songInfo.artist, state: songInfo.artist,

View File

@ -1,16 +1,32 @@
import { createWriteStream, existsSync, mkdirSync, writeFileSync, } from 'node:fs'; import {
createWriteStream,
existsSync,
mkdirSync,
writeFileSync,
} from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import { randomBytes } from 'node:crypto'; import { randomBytes } from 'node:crypto';
import { app, BrowserWindow, dialog, ipcMain, net } from 'electron'; import { app, BrowserWindow, dialog, ipcMain, net } from 'electron';
import { ClientType, Innertube, UniversalCache, Utils, YTNodes } from 'youtubei.js'; import {
ClientType,
Innertube,
UniversalCache,
Utils,
YTNodes,
} from 'youtubei.js';
import is from 'electron-is'; import is from 'electron-is';
import filenamify from 'filenamify'; import filenamify from 'filenamify';
import { Mutex } from 'async-mutex'; import { Mutex } from 'async-mutex';
import { createFFmpeg } from '@ffmpeg.wasm/main'; import { createFFmpeg } from '@ffmpeg.wasm/main';
import NodeID3, { TagConstants } from 'node-id3'; import NodeID3, { TagConstants } from 'node-id3';
import { cropMaxWidth, getFolder, sendFeedback as sendFeedback_, setBadge } from './utils'; import {
cropMaxWidth,
getFolder,
sendFeedback as sendFeedback_,
setBadge,
} from './utils';
import config from './config'; import config from './config';
import { YoutubeFormatList, type Preset, DefaultPresetList } from './types'; import { YoutubeFormatList, type Preset, DefaultPresetList } from './types';
@ -34,10 +50,8 @@ type CustomSongInfo = SongInfo & { trackId?: string };
const ffmpeg = createFFmpeg({ const ffmpeg = createFFmpeg({
log: false, log: false,
logger() { logger() {}, // Console.log,
}, // Console.log, progress() {}, // Console.log,
progress() {
}, // Console.log,
}); });
const ffmpegMutex = new Mutex(); const ffmpegMutex = new Mutex();
@ -65,9 +79,13 @@ const sendError = (error: Error, source?: string) => {
}; };
export const getCookieFromWindow = async (win: BrowserWindow) => { export const getCookieFromWindow = async (win: BrowserWindow) => {
return (await win.webContents.session.cookies.get({ url: 'https://music.youtube.com' })).map((it) => return (
it.name + '=' + it.value + ';' await win.webContents.session.cookies.get({
).join(''); url: 'https://music.youtube.com',
})
)
.map((it) => it.name + '=' + it.value + ';')
.join('');
}; };
export default async (win_: BrowserWindow) => { export default async (win_: BrowserWindow) => {
@ -78,12 +96,13 @@ export default async (win_: BrowserWindow) => {
cache: new UniversalCache(false), cache: new UniversalCache(false),
cookie: await getCookieFromWindow(win), cookie: await getCookieFromWindow(win),
generate_session_locally: true, generate_session_locally: true,
fetch: async (input: RequestInfo | URL, init?: RequestInit) => { fetch: (async (input: RequestInfo | URL, init?: RequestInit) => {
const url = const url =
typeof input === 'string' ? typeof input === 'string'
new URL(input) : ? new URL(input)
input instanceof URL ? : input instanceof URL
input : new URL(input.url); ? input
: new URL(input.url);
if (init?.body && !init.method) { if (init?.body && !init.method) {
init.method = 'POST'; init.method = 'POST';
@ -95,7 +114,7 @@ export default async (win_: BrowserWindow) => {
); );
return net.fetch(request, init); return net.fetch(request, init);
} }) as typeof fetch,
}); });
ipcMain.on('download-song', (_, url: string) => downloadSong(url)); ipcMain.on('download-song', (_, url: string) => downloadSong(url));
ipcMain.on('video-src-changed', (_, data: GetPlayerResponse) => { ipcMain.on('video-src-changed', (_, data: GetPlayerResponse) => {
@ -110,15 +129,14 @@ export async function downloadSong(
url: string, url: string,
playlistFolder: string | undefined = undefined, playlistFolder: string | undefined = undefined,
trackId: string | undefined = undefined, trackId: string | undefined = undefined,
increasePlaylistProgress: (value: number) => void = () => { increasePlaylistProgress: (value: number) => void = () => {},
},
) { ) {
let resolvedName; let resolvedName;
try { try {
await downloadSongUnsafe( await downloadSongUnsafe(
false, false,
url, url,
(name: string) => resolvedName = name, (name: string) => (resolvedName = name),
playlistFolder, playlistFolder,
trackId, trackId,
increasePlaylistProgress, increasePlaylistProgress,
@ -132,15 +150,14 @@ export async function downloadSongFromId(
id: string, id: string,
playlistFolder: string | undefined = undefined, playlistFolder: string | undefined = undefined,
trackId: string | undefined = undefined, trackId: string | undefined = undefined,
increasePlaylistProgress: (value: number) => void = () => { increasePlaylistProgress: (value: number) => void = () => {},
},
) { ) {
let resolvedName; let resolvedName;
try { try {
await downloadSongUnsafe( await downloadSongUnsafe(
true, true,
id, id,
(name: string) => resolvedName = name, (name: string) => (resolvedName = name),
playlistFolder, playlistFolder,
trackId, trackId,
increasePlaylistProgress, increasePlaylistProgress,
@ -190,8 +207,8 @@ async function downloadSongUnsafe(
metadata.trackId = trackId; metadata.trackId = trackId;
const dir const dir =
= playlistFolder || config.get('downloadFolder') || app.getPath('downloads'); playlistFolder || config.get('downloadFolder') || app.getPath('downloads');
const name = `${metadata.artist ? `${metadata.artist} - ` : ''}${ const name = `${metadata.artist ? `${metadata.artist} - ` : ''}${
metadata.title metadata.title
}`; }`;
@ -214,7 +231,8 @@ async function downloadSongUnsafe(
} }
if (playabilityStatus.status === 'UNPLAYABLE') { if (playabilityStatus.status === 'UNPLAYABLE') {
const errorScreen = playabilityStatus.error_screen as PlayerErrorMessage | null; const errorScreen =
playabilityStatus.error_screen as PlayerErrorMessage | null;
throw new Error( throw new Error(
`[${playabilityStatus.status}] ${errorScreen?.reason.text}: ${errorScreen?.subreason.text}`, `[${playabilityStatus.status}] ${errorScreen?.reason.text}: ${errorScreen?.subreason.text}`,
); );
@ -223,7 +241,8 @@ async function downloadSongUnsafe(
const selectedPreset = config.get('selectedPreset') ?? 'mp3 (256kbps)'; const selectedPreset = config.get('selectedPreset') ?? 'mp3 (256kbps)';
let presetSetting: Preset; let presetSetting: Preset;
if (selectedPreset === 'Custom') { if (selectedPreset === 'Custom') {
presetSetting = config.get('customPresetSetting') ?? DefaultPresetList['Custom']; presetSetting =
config.get('customPresetSetting') ?? DefaultPresetList['Custom'];
} else if (selectedPreset === 'Source') { } else if (selectedPreset === 'Source') {
presetSetting = DefaultPresetList['Source']; presetSetting = DefaultPresetList['Source'];
} else { } else {
@ -240,7 +259,9 @@ async function downloadSongUnsafe(
let targetFileExtension: string; let targetFileExtension: string;
if (!presetSetting?.extension) { if (!presetSetting?.extension) {
targetFileExtension = YoutubeFormatList.find((it) => it.itag === format.itag)?.container ?? 'mp3'; targetFileExtension =
YoutubeFormatList.find((it) => it.itag === format.itag)?.container ??
'mp3';
} else { } else {
targetFileExtension = presetSetting?.extension ?? 'mp3'; targetFileExtension = presetSetting?.extension ?? 'mp3';
} }
@ -285,7 +306,11 @@ async function downloadSongUnsafe(
if (targetFileExtension !== 'mp3') { if (targetFileExtension !== 'mp3') {
createWriteStream(filePath).write(fileBuffer); createWriteStream(filePath).write(fileBuffer);
} else { } else {
const buffer = await writeID3(Buffer.from(fileBuffer), metadata, sendFeedback); const buffer = await writeID3(
Buffer.from(fileBuffer),
metadata,
sendFeedback,
);
if (buffer) { if (buffer) {
writeFileSync(filePath, buffer); writeFileSync(filePath, buffer);
} }
@ -303,8 +328,7 @@ async function iterableStreamToTargetFile(
presetFfmpegArgs: string[], presetFfmpegArgs: string[],
contentLength: number, contentLength: number,
sendFeedback: (str: string, value?: number) => void, sendFeedback: (str: string, value?: number) => void,
increasePlaylistProgress: (value: number) => void = () => { increasePlaylistProgress: (value: number) => void = () => {},
},
) { ) {
const chunks = []; const chunks = [];
let downloaded = 0; let downloaded = 0;
@ -372,7 +396,11 @@ const getCoverBuffer = cache(async (url: string) => {
return nativeImage && !nativeImage.isEmpty() ? nativeImage.toPNG() : null; return nativeImage && !nativeImage.isEmpty() ? nativeImage.toPNG() : null;
}); });
async function writeID3(buffer: Buffer, metadata: CustomSongInfo, sendFeedback: (str: string, value?: number) => void) { async function writeID3(
buffer: Buffer,
metadata: CustomSongInfo,
sendFeedback: (str: string, value?: number) => void,
) {
try { try {
sendFeedback('Writing ID3 tags...'); sendFeedback('Writing ID3 tags...');
const tags: NodeID3.Tags = {}; const tags: NodeID3.Tags = {};
@ -425,10 +453,10 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
return; return;
} }
const playlistId const playlistId =
= getPlaylistID(givenUrl) getPlaylistID(givenUrl) ||
|| getPlaylistID(new URL(win.webContents.getURL())) getPlaylistID(new URL(win.webContents.getURL())) ||
|| getPlaylistID(new URL(playingUrl)); getPlaylistID(new URL(playingUrl));
if (!playlistId) { if (!playlistId) {
sendError(new Error('No playlist ID found')); sendError(new Error('No playlist ID found'));
@ -440,11 +468,19 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
console.log(`trying to get playlist ID: '${playlistId}'`); console.log(`trying to get playlist ID: '${playlistId}'`);
sendFeedback('Getting playlist info…'); sendFeedback('Getting playlist info…');
let playlist: Playlist; let playlist: Playlist;
const items: YTNodes.MusicResponsiveListItem[] = [];
try { try {
playlist = await yt.music.getPlaylist(playlistId); playlist = await yt.music.getPlaylist(playlistId);
if (playlist?.items) {
items.push(...playlist.items.as(YTNodes.MusicResponsiveListItem));
}
} catch (error: unknown) { } catch (error: unknown) {
sendError( sendError(
Error(`Error getting playlist info: make sure it isn't a private or "Mixed for you" playlist\n\n${String(error)}`), Error(
`Error getting playlist info: make sure it isn't a private or "Mixed for you" playlist\n\n${String(
error,
)}`,
),
); );
return; return;
} }
@ -453,26 +489,29 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
sendError(new Error('Playlist is empty')); sendError(new Error('Playlist is empty'));
} }
const items = playlist.items!.as(YTNodes.MusicResponsiveListItem); const normalPlaylistTitle = playlist.header?.title?.text;
const playlistTitle =
normalPlaylistTitle ??
playlist.page.contents_memo
?.get('MusicResponsiveListItemFlexColumn')
?.at(2)
?.as(YTNodes.MusicResponsiveListItemFlexColumn)?.title?.text ??
'NO_TITLE';
const isAlbum = !normalPlaylistTitle;
while (playlist.has_continuation) {
playlist = await playlist.getContinuation();
if (playlist?.items) {
items.push(...playlist.items.as(YTNodes.MusicResponsiveListItem));
}
}
if (items.length === 1) { if (items.length === 1) {
sendFeedback('Playlist has only one item, downloading it directly'); sendFeedback('Playlist has only one item, downloading it directly');
await downloadSongFromId(items.at(0)!.id!); await downloadSongFromId(items.at(0)!.id!);
return; return;
} }
const normalPlaylistTitle = playlist.header?.title?.text;
const playlistTitle = normalPlaylistTitle ??
playlist
.page
.contents_memo
?.get('MusicResponsiveListItemFlexColumn')
?.at(2)
?.as(YTNodes.MusicResponsiveListItemFlexColumn)
?.title
?.text ??
'NO_TITLE';
const isAlbum = !normalPlaylistTitle;
let safePlaylistTitle = filenamify(playlistTitle, { replacement: ' ' }); let safePlaylistTitle = filenamify(playlistTitle, { replacement: ' ' });
if (!is.macOS()) { if (!is.macOS()) {
safePlaylistTitle = safePlaylistTitle.normalize('NFC'); safePlaylistTitle = safePlaylistTitle.normalize('NFC');
@ -528,7 +567,11 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
increaseProgress, increaseProgress,
).catch((error) => ).catch((error) =>
sendError( sendError(
new Error(`Error downloading "${song.author!.name} - ${song.title!}":\n ${error}`) new Error(
`Error downloading "${
song.author!.name
} - ${song.title!}":\n ${error}`,
),
), ),
); );
@ -562,8 +605,8 @@ function getFFmpegMetadataArgs(metadata: CustomSongInfo) {
const INVALID_PLAYLIST_MODIFIER = 'RDAMPL'; const INVALID_PLAYLIST_MODIFIER = 'RDAMPL';
const getPlaylistID = (aURL: URL) => { const getPlaylistID = (aURL: URL) => {
const result const result =
= aURL?.searchParams.get('list') || aURL?.searchParams.get('playlist'); aURL?.searchParams.get('list') || aURL?.searchParams.get('playlist');
if (result?.startsWith(INVALID_PLAYLIST_MODIFIER)) { if (result?.startsWith(INVALID_PLAYLIST_MODIFIER)) {
return result.slice(INVALID_PLAYLIST_MODIFIER.length); return result.slice(INVALID_PLAYLIST_MODIFIER.length);
} }
@ -572,15 +615,18 @@ const getPlaylistID = (aURL: URL) => {
}; };
const getVideoId = (url: URL | string): string | null => { const getVideoId = (url: URL | string): string | null => {
return (new URL(url)).searchParams.get('v'); return new URL(url).searchParams.get('v');
}; };
const getMetadata = (info: TrackInfo): CustomSongInfo => ({ const getMetadata = (info: TrackInfo): CustomSongInfo => ({
videoId: info.basic_info.id!, videoId: info.basic_info.id!,
title: cleanupName(info.basic_info.title!), title: cleanupName(info.basic_info.title!),
artist: cleanupName(info.basic_info.author!), artist: cleanupName(info.basic_info.author!),
album: info.player_overlays?.browser_media_session?.as(YTNodes.BrowserMediaSession).album?.text, album: info.player_overlays?.browser_media_session?.as(
imageSrc: info.basic_info.thumbnail?.find((t) => !t.url.endsWith('.webp'))?.url, YTNodes.BrowserMediaSession,
).album?.text,
imageSrc: info.basic_info.thumbnail?.find((t) => !t.url.endsWith('.webp'))
?.url,
views: info.basic_info.view_count!, views: info.basic_info.view_count!,
songDuration: info.basic_info.duration!, songDuration: info.basic_info.duration!,
}); });

View File

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 392 B

View File

Before

Width:  |  Height:  |  Size: 252 B

After

Width:  |  Height:  |  Size: 252 B

View File

Before

Width:  |  Height:  |  Size: 338 B

After

Width:  |  Height:  |  Size: 338 B

View File

Before

Width:  |  Height:  |  Size: 174 B

After

Width:  |  Height:  |  Size: 174 B

View File

Before

Width:  |  Height:  |  Size: 546 B

After

Width:  |  Height:  |  Size: 546 B

View File

@ -19,6 +19,7 @@ const isMacOS = navigator.userAgent.includes('Macintosh');
const isNotWindowsOrMacOS = !navigator.userAgent.includes('Windows') && !isMacOS; const isNotWindowsOrMacOS = !navigator.userAgent.includes('Windows') && !isMacOS;
export default async () => { export default async () => {
const hideDOMWindowControls = config.get('plugins.in-app-menu.hideDOMWindowControls');
let hideMenu = config.get('options.hideMenu'); let hideMenu = config.get('options.hideMenu');
const titleBar = document.createElement('title-bar'); const titleBar = document.createElement('title-bar');
const navBar = document.querySelector<HTMLDivElement>('#nav-bar-background'); const navBar = document.querySelector<HTMLDivElement>('#nav-bar-background');
@ -98,7 +99,7 @@ export default async () => {
titleBar.appendChild(windowControlsContainer); titleBar.appendChild(windowControlsContainer);
}; };
if (isNotWindowsOrMacOS) await addWindowControls(); if (isNotWindowsOrMacOS && !hideDOMWindowControls) await addWindowControls();
if (navBar) { if (navBar) {
const observer = new MutationObserver((mutations) => { const observer = new MutationObserver((mutations) => {
@ -130,7 +131,7 @@ export default async () => {
menu.style.visibility = 'hidden'; menu.style.visibility = 'hidden';
} }
}); });
if (isNotWindowsOrMacOS) await addWindowControls(); if (isNotWindowsOrMacOS && !hideDOMWindowControls) await addWindowControls();
}; };
await updateMenu(); await updateMenu();
@ -138,12 +139,16 @@ export default async () => {
ipcRenderer.on('refreshMenu', () => updateMenu()); ipcRenderer.on('refreshMenu', () => updateMenu());
ipcRenderer.on('window-maximize', () => { ipcRenderer.on('window-maximize', () => {
maximizeButton.removeChild(maximizeButton.firstChild!); if (isNotWindowsOrMacOS && !hideDOMWindowControls && maximizeButton.firstChild) {
maximizeButton.appendChild(unmaximize); maximizeButton.removeChild(maximizeButton.firstChild);
maximizeButton.appendChild(unmaximize);
}
}); });
ipcRenderer.on('window-unmaximize', () => { ipcRenderer.on('window-unmaximize', () => {
maximizeButton.removeChild(maximizeButton.firstChild!); if (isNotWindowsOrMacOS && !hideDOMWindowControls && maximizeButton.firstChild) {
maximizeButton.appendChild(maximize); maximizeButton.removeChild(maximizeButton.firstChild);
maximizeButton.appendChild(unmaximize);
}
}); });
if (isEnabled('picture-in-picture')) { if (isEnabled('picture-in-picture')) {

View File

@ -0,0 +1,22 @@
import { BrowserWindow } from 'electron';
import is from 'electron-is';
import { setMenuOptions } from '../../config/plugins';
import type { MenuTemplate } from '../../menu';
import type { ConfigType } from '../../config/dynamic';
export default (_: BrowserWindow, config: ConfigType<'in-app-menu'>): MenuTemplate => [
...(is.linux() ? [
{
label: 'Hide DOM Window Controls',
type: 'checkbox',
checked: config.hideDOMWindowControls,
click(item) {
config.hideDOMWindowControls = item.checked;
setMenuOptions('in-app-menu', config);
}
}
] : []) satisfies Electron.MenuItemConstructorOptions[],
];

Some files were not shown because too many files have changed in this diff Show More