Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion electron-builder.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
appId: com.github.th-ch.pear-desktop
productName: Pear Desktop
productName: YouTube Music
files:
- '!*'
- dist
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "pear-music",
"desktopName": "com.github.th_ch.pear_music",
"productName": "Pear Desktop",
"productName": "YouTube Music",
"version": "3.11.0",
"description": "Pear Desktop App - including custom plugins",
"description": "YouTube Music Desktop App - including custom plugins",
"main": "./dist/main/index.js",
"type": "module",
"license": "MIT",
Expand Down Expand Up @@ -65,6 +65,7 @@
"dependencies": {
"@dehoist/romanize-thai": "1.0.0",
"@electron-toolkit/tsconfig": "1.0.1",
"@indic-transliteration/sanscript": "1.3.3",
"@electron/remote": "2.1.3",
"@ffmpeg.wasm/core-mt": "0.12.0",
"@ffmpeg.wasm/main": "0.12.0",
Expand Down Expand Up @@ -134,7 +135,7 @@
"virtua": "0.42.3",
"vudio": "2.1.1",
"x11": "2.3.0",
"youtubei.js": "15.0.1",
"youtubei.js": "^16.0.1",
"zod": "4.1.5"
},
"devDependencies": {
Expand Down
47 changes: 33 additions & 14 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/loader/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export const createContext = <Config extends PluginConfig>(
invoke: (event: string, ...args: unknown[]) =>
window.ipcRenderer.invoke(event, ...args),
on: (event: string, listener: CallableFunction) => {
window.ipcRenderer.on(event, (_, ...args: unknown[]) => {
window.ipcRenderer.on(event, (event, ...args: unknown[]) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
listener(...args);
listener(event, ...args);
});
},
removeAllListeners: (event: string) => {
Expand Down
2 changes: 1 addition & 1 deletion src/music-player.css
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,4 @@ tp-yt-paper-item.ytmusic-guide-entry-renderer::before {
tp-yt-iron-dropdown,
tp-yt-paper-dialog {
app-region: no-drag;
}
}
91 changes: 91 additions & 0 deletions src/plugins/ad-speedup/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { createPlugin } from '@/utils';
import { t } from '@/i18n';

let observer: MutationObserver | null = null;
let lastAdCheck = 0;
let checkInterval: NodeJS.Timeout | null = null;

function checkAndSkipAd() {
// Throttle checks to avoid performance issues
const now = Date.now();
if (now - lastAdCheck < 100) return;
lastAdCheck = now;

const video = document.querySelector<HTMLVideoElement>('video');
if (!video) return;

const adContainer = document.querySelector('.ytp-ad-player-overlay, .video-ads, .ytp-ad-module');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Replace '.ytp-ad-player-overlay,·.video-ads,·.ytp-ad-module' with ⏎····'.ytp-ad-player-overlay,·.video-ads,·.ytp-ad-module',⏎··

Suggested change
const adContainer = document.querySelector('.ytp-ad-player-overlay, .video-ads, .ytp-ad-module');
const adContainer = document.querySelector(
'.ytp-ad-player-overlay, .video-ads, .ytp-ad-module',
);

const adText = document.querySelector('.ytp-ad-text, .ytp-ad-preview-text');

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Delete ··

Suggested change

// Check if ad is playing
const isAd = adContainer || adText ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Replace ·adContainer·||·adText·||· with ⏎····adContainer·||⏎····adText·||

Suggested change
const isAd = adContainer || adText ||
const isAd =
adContainer ||
adText ||

document.querySelector('.ad-showing') ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Delete ···········

Suggested change
document.querySelector('.ad-showing') ||
document.querySelector('.ad-showing') ||

document.querySelector('.advertisement');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Delete ···········

Suggested change
document.querySelector('.advertisement');
document.querySelector('.advertisement');


if (isAd) {
// Mute and speed up the video
if (!video.muted) {
video.muted = true;
}
if (video.playbackRate !== 16) {
video.playbackRate = 16;
}

// Try to click skip button if available
const skipButton = document.querySelector<HTMLElement>(
'.ytp-ad-skip-button, .ytp-ad-skip-button-modern, button.ytp-ad-skip-button'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Insert ,

Suggested change
'.ytp-ad-skip-button, .ytp-ad-skip-button-modern, button.ytp-ad-skip-button'
'.ytp-ad-skip-button, .ytp-ad-skip-button-modern, button.ytp-ad-skip-button',

);
if (skipButton && skipButton.offsetParent !== null) {
skipButton.click();
}
} else {
// Restore normal playback when not an ad
if (video.muted) {
video.muted = false;
}
if (video.playbackRate !== 1) {
video.playbackRate = 1;
}
}
}

export default createPlugin({
name: () => t('plugins.ad-speedup.name'),
description: () => t('plugins.ad-speedup.description'),
restartNeeded: false,
config: {
enabled: true,
},
renderer: {
start() {
// Check for ads periodically
checkInterval = setInterval(() => {
checkAndSkipAd();
}, 500);

// Also watch for DOM changes
observer = new MutationObserver(() => {
checkAndSkipAd();
});

const targetNode = document.body;
if (targetNode) {
observer.observe(targetNode, {
childList: true,
subtree: true,
});
}
},

stop() {
if (observer) {
observer.disconnect();
observer = null;
}
if (checkInterval) {
clearInterval(checkInterval);
checkInterval = null;
}
},
},
});
75 changes: 75 additions & 0 deletions src/plugins/adblocker/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ElectronBlocker } from '@ghostery/adblocker-electron';
import { session } from 'electron';

import { createPlugin } from '@/utils';
import { t } from '@/i18n';

import type { BackendContext } from '@/types/contexts';

export type AdBlockerPluginConfig = {
enabled: boolean;
cache: boolean;
additionalBlockLists: string[];
};

let blocker: ElectronBlocker | null = null;

const defaultBlockLists = [
'https://easylist.to/easylist/easylist.txt',
'https://easylist.to/easylist/easyprivacy.txt',
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt',
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/badware.txt',
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/privacy.txt',
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/resource-abuse.txt',
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/unbreak.txt',
'https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/annoyances.txt',
];

export default createPlugin({
name: () => t('plugins.adblocker.name'),
description: () => t('plugins.adblocker.description'),
restartNeeded: false,
config: {
enabled: true,
cache: true,
additionalBlockLists: [],
},
backend: {
async start({ getConfig }: BackendContext<AdBlockerPluginConfig>) {
const config = await getConfig();
const blockLists = [...defaultBlockLists, ...config.additionalBlockLists];

try {
blocker = await ElectronBlocker.fromLists(
fetch,
blockLists,
{
Comment on lines +43 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Replace ⏎··········fetch,⏎··········blockLists,⏎········· with fetch,·blockLists,

Suggested change
blocker = await ElectronBlocker.fromLists(
fetch,
blockLists,
{
blocker = await ElectronBlocker.fromLists(fetch, blockLists, {

enableCompression: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Delete ··

Suggested change
enableCompression: true,
enableCompression: true,

},
);
Comment on lines +48 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Replace ··},⏎········ with }

Suggested change
},
);
});


blocker.enableBlockingInSession(session.defaultSession);

console.log('[AdBlocker] Ad blocker enabled successfully');
} catch (error) {
console.error('[AdBlocker] Failed to initialize ad blocker:', error);
}
},

async onConfigChange(newConfig: AdBlockerPluginConfig) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [eslint] <@typescript-eslint/require-await> reported by reviewdog 🐶
Async method 'onConfigChange' has no 'await' expression.

if (!newConfig.enabled && blocker) {
blocker.disableBlockingInSession(session.defaultSession);
blocker = null;
console.log('[AdBlocker] Ad blocker disabled');
}
},

stop() {
if (blocker) {
blocker.disableBlockingInSession(session.defaultSession);
blocker = null;
console.log('[AdBlocker] Ad blocker stopped');
}
},
},
});
12 changes: 8 additions & 4 deletions src/plugins/custom-output-device/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,14 @@ export const renderer = createRenderer<
navigator.mediaDevices.ondevicechange = async () =>
await updateDeviceList(context);

document.addEventListener('peard:audio-can-play', this.audioCanPlayHandler, {
once: true,
passive: true,
});
document.addEventListener(
'peard:audio-can-play',
this.audioCanPlayHandler,
{
once: true,
passive: true,
},
);
await updateDeviceList(context);
},

Expand Down
Loading
Loading