Skip to content

Commit 925ef98

Browse files
committed
Plugin that tries to skip live versions of songs
1 parent fa2862c commit 925ef98

File tree

3 files changed

+118
-0
lines changed

3 files changed

+118
-0
lines changed

src/i18n/resources/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,10 @@
802802
"description": "Automatically skip silences sections in songs",
803803
"name": "Skip Silences"
804804
},
805+
"skip-live-songs": {
806+
"description": "Automatically tries to skip renditions of songs",
807+
"name": "Skip Live Songs"
808+
},
805809
"sponsorblock": {
806810
"description": "Automatically Skips non-music parts like intro/outro or parts of music videos where the song isn't playing",
807811
"name": "SponsorBlock"
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { t } from '@/i18n';
2+
import { createPlugin } from '@/utils';
3+
import type { SongInfo } from '@/providers/song-info';
4+
import { nonStudioPatterns } from './patterns';
5+
6+
export default createPlugin({
7+
name: () => t('plugins.skip-live-songs.name'),
8+
description: () => t('plugins.skip-live-songs.description'),
9+
restartNeeded: false,
10+
config: {
11+
enabled: false,
12+
},
13+
renderer: {
14+
lastSkippedVideoId: '',
15+
16+
_skipLiveHandler: undefined as unknown as ((songInfo: SongInfo) => void) | undefined,
17+
18+
start({ ipc }) {
19+
console.debug('[Skip Live Songs] Renderer started');
20+
21+
const SELECTORS = [
22+
'yt-icon-button.next-button',
23+
'.next-button',
24+
'button[aria-label*="Next"]',
25+
'button[aria-label*="next"]',
26+
'#player-bar-next-button',
27+
'ytmusic-player-bar .next-button',
28+
'.player-bar .next-button',
29+
];
30+
31+
const handler = (songInfo: SongInfo) => {
32+
const titleToCheck = songInfo.alternativeTitle || songInfo.title;
33+
if (!titleToCheck) return;
34+
35+
// Skip if we've already attempted this video id
36+
if (songInfo.videoId === this.lastSkippedVideoId) return;
37+
38+
const isNonStudio = nonStudioPatterns.some(pattern => pattern.test(titleToCheck));
39+
40+
if (!isNonStudio) return; // studio version — nothing to do
41+
42+
// Mark as attempted so we don't loop repeatedly
43+
this.lastSkippedVideoId = songInfo.videoId;
44+
console.info(`[Skip Live Songs] Skipping non-studio song: "${titleToCheck}" (id: ${songInfo.videoId})`);
45+
46+
let clicked = false;
47+
for (const sel of SELECTORS) {
48+
const button = document.querySelector<HTMLElement>(sel);
49+
if (button) {
50+
button.click();
51+
console.debug(`[Skip Live Songs] Clicked next button using selector: ${sel}`);
52+
clicked = true;
53+
break;
54+
}
55+
}
56+
57+
if (!clicked) {
58+
console.warn('[Skip Live Songs] Could not find next button with any configured selector');
59+
}
60+
};
61+
62+
this._skipLiveHandler = handler;
63+
ipc.on('peard:update-song-info', handler);
64+
},
65+
66+
// Unregister the ipc handler on plugin stop to avoid duplicate listeners on hot reload
67+
stop({ ipc }) {
68+
if (this._skipLiveHandler) {
69+
ipc.removeAllListeners('peard:update-song-info');
70+
this._skipLiveHandler = undefined;
71+
console.debug('[Skip Live Songs] Renderer stopped and listeners removed');
72+
}
73+
},
74+
},
75+
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Patterns to detect non-studio recordings
3+
*
4+
* Add or modify patterns here to customize what gets skipped.
5+
* All patterns are not case-sensitive.
6+
*/
7+
8+
export const nonStudioPatterns = [
9+
// "Live" in specific contexts (not as part of song title)
10+
/[\(\[]live[\)\]]/i, // "(Live)" or "[Live]" in parentheses/brackets
11+
/live\s+(at|from|in|on|with|@)/i, // "Live at", "Live from", "Live in", etc.
12+
/live\s+with\b/i, // "Live with" (e.g. "Live with the SFSO")
13+
/-\s*live\s*$/i, // "- Live" at the end of title
14+
/:\s*live\s*$/i, // ": Live" at the end of title
15+
16+
// Concert/Performance indicators
17+
/\b(concert|festival|tour)\b/i, // Concert, Festival, Tour
18+
/\(.*?(concert|live performance|live recording).*?(19|20)\d{2}\)/i, // (Live 1985), (Concert 2024)
19+
20+
// Recording types
21+
/\b(acoustic|unplugged|rehearsal|demo)\b/i, // Acoustic, Unplugged, Rehearsal, Demo
22+
23+
// Venues
24+
/\b(arena|stadium|center|centre|hall)\b/i, // Arena, Stadium, Center, Hall
25+
/\bmadison\s+square\s+garden\b/i, // Madison Square Garden
26+
/day\s+on\s+the\s+green/i, // Day on the Green
27+
28+
// Famous venues/festivals
29+
/\b(wembley|glastonbury|woodstock|coachella)\b/i, // Wembley, Glastonbury, Woodstock, Coachella
30+
31+
// Dates and locations
32+
/\b(january|february|march|april|may|june|july|august|september|october|november|december)\s+\d+/i, // "September 22", "August 31"
33+
/\b\d{1,2}[/-]\d{1,2}[/-]\d{2,4}\b/, // Date formats: 09/22/2024, 9-22-24
34+
/\b[A-Z][a-z]+,\s*[A-Z]{2}\b/, // Locations: "Oakland, CA", "London, UK"
35+
/\b[A-Z][a-z]+\s+City\b/i, // Cities: "Mexico City", "New York City"
36+
/\b(tokyo|paris|berlin|sydney)\b/i, // More cities
37+
/\b(bbc|radio|session)\b/i, // Radio sessions
38+
];
39+

0 commit comments

Comments
 (0)