Skip to content

Commit f9d7716

Browse files
committed
i
1 parent 92d6005 commit f9d7716

10 files changed

Lines changed: 161 additions & 158 deletions

File tree

.github/workflows/static.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# This is just the example GitHub workflow (https://github.com/actions/starter-workflows/blob/main/pages/static.yml) edited to serve only the /dist folder to GitHub Pages. The "static" limitation is overcome with the script in 404.html (https://github.com/i1li/i/blob/main/src/404.html)
1+
# This is just the example GitHub workflow (https://github.com/actions/starter-workflows/blob/main/pages/static.yml) edited to serve only the /dist folder to GitHub Pages. The "static" limitation is overcome by simply copying index.html to 404.html during the build process.
22

33
name: Deploy static content to Pages from /dist
44
on:

dist/404.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/bundle-6pixvs.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/bundle-nbkm4g.js

Lines changed: 0 additions & 2 deletions
This file was deleted.

dist/index.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/index.html

Lines changed: 100 additions & 96 deletions
Large diffs are not rendered by default.

src/yt/youtube-embed.css

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
.yt-wrapper {
22
display: none;
33
position: relative;
4-
margin: auto;
5-
max-width: 720px;
6-
padding-bottom: 56.25%;
4+
padding: 56.25% 0 0 0;
75
}
86
.yt {
97
position: absolute;

src/yt/youtube-embed.js

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,11 @@ class YTEmbed extends HTMLElement {
1616
attributeChangedCallback(name, oldValue, newValue) {
1717
if (name === 'v' && oldValue !== newValue) {this.initFromV();}
1818
}
19-
isPlaylistID(id) {
20-
return id.length > 11 && (id.startsWith('PL') || id.startsWith('TL') || id.startsWith('OL') || id.startsWith('FL') || id.startsWith('UU'));
21-
}
2219
initFromV() {
23-
const v = this.getAttribute('v');
24-
if (!v) return;
25-
const [id, params] = v.split('?');
26-
this.id = id;
27-
this.params = params;
28-
const videoIds = this.id.split(',');
29-
this.videoIds = videoIds;
20+
const v = this.getAttribute('v'); if (!v) return;
21+
const rawIds = v.split(','); this.rawIds = rawIds;
22+
const {id,params,videoIds,playlistIds} = (()=>{const parts=this.rawIds.map(r=>r.split('?'));const list=parts.map(([a,b])=>({id:a,params:b}));const videoIds=list.filter(x=>x.id.length===11).map(x=>x.id);const playlistIds=list.filter(x=>x.id.length>11).map(x=>x.id);const pick=playlistIds[0]? list.find(x=>x.id===playlistIds[0]) : list.find(x=>x.id.length===11)|| list[0];return {id:pick.id,params:pick.params,videoIds,playlistIds};})();
23+
Object.assign(this, { id, params, videoIds, playlistIds });
3024
let linkUrl , embedUrl;
3125
const embedProxyUrl = 'https://embed-proxy.github.io/';
3226
if (this.classList.contains('no-embed')) {
@@ -39,7 +33,7 @@ class YTEmbed extends HTMLElement {
3933
linkUrl = `https://www.youtube.com/watch_videos?video_ids=${this.videoIds.join(',')}&autoplay=1`;
4034
embedUrl = null;
4135
break;
42-
case this.isPlaylistID(this.id):
36+
case this.playlistIds.length > 0:
4337
linkUrl = `https://www.youtube.com/playlist?list=${this.id}&autoplay=1`;
4438
embedUrl = null;
4539
break;
@@ -54,7 +48,7 @@ class YTEmbed extends HTMLElement {
5448
embedUrl = `https://www.youtube-nocookie.com/embed/?playlist=${this.videoIds.join(',')}&autoplay=1`;
5549
linkUrl = embedProxyUrl + embedUrl;
5650
break;
57-
case this.isPlaylistID(this.id):
51+
case this.playlistIds.length > 0:
5852
embedUrl = `https://www.youtube-nocookie.com/embed/videoseries?list=${this.id}&autoplay=1`;
5953
linkUrl = embedProxyUrl + embedUrl;
6054
break;

src/yt/yt-ids.js

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,35 @@ const { readFileSync, writeFileSync } = require('fs');
44
const { join } = require('path');
55
const https = require('https');
66
if (!KEY) throw new Error('API key missing');
7-
const excludedIds = new Set(
8-
readFileSync(join(__dirname, '..', 'yt', 'exclude.txt'), 'utf8')
9-
.split(',').map(id => id.trim()).filter(Boolean)
10-
);
11-
const htmlContent = readFileSync(join(__dirname, '..', 'index.html'), 'utf8');
12-
const searchDiv = htmlContent.match(/id="shuffle">([\s\S]*?)<\/div>/)?.[1] || '';
13-
const cleanId = id => id.split('?')[0];
14-
const isVideoId = id => cleanId(id).length === 11;
15-
const isPlaylistId = id => cleanId(id).length > 11;
167
const fetchUrl = url => new Promise((resolve, reject) => {
178
https.get(url.toString(), res => {
189
let data = '';
1910
res.on('data', c => data += c);
2011
res.on('end', () => resolve(JSON.parse(data)));
2112
}).on('error', reject);
2213
});
23-
const buildUrl = (base, params) => {
24-
const u = new URL(base);
25-
u.search = new URLSearchParams(params);
26-
return u;
27-
};
14+
const buildUrl = (base, params) => {const u = new URL(base);
15+
u.search = new URLSearchParams(params); return u; };
2816
const extractIds = (regex, src) => {
2917
const out = [];
3018
let m;
3119
while ((m = regex.exec(src))) {
3220
if (m[1]) out.push(...m[1].split(',').map(s => s.trim()));
33-
if (m[2]) out.push(...m[2].split(',').map(s => s.trim()));
3421
}
3522
return out;
3623
};
37-
const allIds = extractIds(/(?:p="([^"]+)"|v="([^"]+)")/g, searchDiv);
24+
const htmlContent = readFileSync(join(__dirname, '..', 'index.html'), 'utf8');
25+
const searchDiv = htmlContent.match(/id="shuffle">([\s\S]*?)<\/div>/)?.[1] || '';
26+
const allIds = extractIds(/(?:p|v)="([^"]+)"/g, searchDiv);
27+
const cleanId = id => id.split('?')[0];
28+
const isVideoId = id => cleanId(id).length === 11;
29+
const isPlaylistId = id => cleanId(id).length > 11;
3830
const playlistIds = allIds.filter(isPlaylistId);
39-
const videoIds = allIds.filter(isVideoId);
31+
const videoIds = allIds.filter(isVideoId);
32+
const excludedIds = new Set(
33+
readFileSync(join(__dirname, '..', 'yt', 'exclude.txt'), 'utf8')
34+
.split(',').map(id => id.trim()).filter(Boolean)
35+
);
4036
const writeOutput = async () => {
4137
const playlistVideos = {};
4238
let playlistErrors = 0;
@@ -76,49 +72,60 @@ const writeOutput = async () => {
7672
...videoIds.map(cleanId),
7773
...Object.values(playlistVideos).flat()
7874
])].filter(id => !excludedIds.has(id));
79-
const embeddableMap = {};
75+
const infoMap = {};
8076
let videoErrors = 0;
8177
for (let i = 0; i < allCleanIds.length; i += 50) {
8278
const batch = allCleanIds.slice(i, i + 50);
8379
try {
8480
const data = await fetchUrl(buildUrl('https://www.googleapis.com/youtube/v3/videos', {
85-
part: 'status',
81+
part: 'status,contentDetails',
8682
id: batch.join(','),
8783
key: KEY
8884
}));
8985
if (data.error) throw new Error(data.error.message);
9086
for (const item of data.items || []) {
9187
if (item.status?.privacyStatus === 'private') continue;
92-
embeddableMap[item.id] = item.status.embeddable ?? null;
88+
const durationSeconds = (()=>{const m=(item.contentDetails?.duration||'PT0S').match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/); return ((+m?.[1]||0)*3600)+((+m?.[2]||0)*60)+(+m?.[3]||0);})();
89+
infoMap[item.id] = {
90+
embeddable: item.status.embeddable ?? null, durationSeconds
91+
};
9392
}
94-
} catch (e) {
95-
videoErrors++;
96-
console.error(`Videos batch error: ${e.message}`);
97-
}
93+
} catch (e) {videoErrors++;console.error(`Videos batch error: ${e.message}`);}
9894
}
9995
const originalMap = new Map(allIds.map(id => [cleanId(id), id]));
10096
let total = 0;
10197
const updated = searchDiv.replace(/<y-t([^>]*)>/g, (_, attrs) => {
102-
const p = (attrs.match(/p="([^"]*)"/)?.[1] || '').split(',').map(s => s.trim()).filter(isPlaylistId);
103-
const v = (attrs.match(/v="([^"]*)"/)?.[1] || '').split(',').map(s => s.trim());
104-
const u = (attrs.match(/u="([^"]*)"/)?.[1] || '').split(',').map(s => s.trim()).filter(Boolean);
105-
const fromPlaylists = p.flatMap(pid => playlistVideos[cleanId(pid)] || []);
106-
const combinedClean = [...new Set([...fromPlaylists, ...v.map(cleanId)])]
107-
.filter(id => !excludedIds.has(id));
108-
const embeddableClean = combinedClean.filter(id => embeddableMap[id] === true);
109-
const nonEmbeddableClean = combinedClean.filter(id => embeddableMap[id] === false || embeddableMap[id] === null);
98+
const p_match = attrs.match(/p="([^"]*)"/);
99+
const v_match = attrs.match(/v="([^"]*)"/);
100+
const u_match = attrs.match(/u="([^"]*)"/);
101+
const p_split = p_match ? p_match[1].split(',').map(s => s.trim()).filter(Boolean) : [];
102+
const v_split = v_match ? v_match[1].split(',').map(s => s.trim()).filter(Boolean) : [];
103+
const u_split = u_match ? u_match[1].split(',').map(s => s.trim()).filter(Boolean) : [];
104+
const all_attr_ids = [...p_split, ...v_split];
105+
const playlist_full = all_attr_ids.filter(isPlaylistId);
106+
const video_full = all_attr_ids.filter(isVideoId);
107+
const fromPlaylists = playlist_full.map(full => playlistVideos[cleanId(full)] || []).flat();
108+
const v_clean = video_full.map(cleanId);
109+
const combinedClean = [...new Set([...fromPlaylists, ...v_clean])].filter(id => !excludedIds.has(id));
110+
const infoFilter = (id, condition) => {
111+
const info = infoMap[id];
112+
return info && condition(info.embeddable) && info.durationSeconds >= 150;
113+
};
114+
const embeddableClean = combinedClean.filter(id => infoFilter(id, emb => emb === true));
115+
const nonEmbeddableClean = combinedClean.filter(id => infoFilter(id, emb => emb === false || emb === null));
110116
total += embeddableClean.length + nonEmbeddableClean.length;
111117
const embeddableFull = embeddableClean.map(id => originalMap.get(id) || id);
112118
const nonEmbeddableFull = [...new Set([
113119
...nonEmbeddableClean.map(id => originalMap.get(id) || id),
114-
...u
115-
])].filter(Boolean);
116-
let newAttrs = attrs
117-
.replace(/p="[^"]*"/g, p.length ? `p="${p.join(',')}"` : 'p=""')
118-
.replace(/v="[^"]*"/g, `v="${embeddableFull.join(',')}"`)
119-
.replace(/u="[^"]*"/g, '');
120-
if (nonEmbeddableFull.length>0) newAttrs += ` u="${nonEmbeddableFull.join(',')}"`;
121-
return `<y-t ${newAttrs.trim().replace(/\s+/g, ' ')}>`;
120+
...u_split
121+
])].filter(Boolean);
122+
const otherAttrs = attrs.replace(/[\s]*(p|v|u)="[^"]*"/g, '').trim();
123+
const attrParts = [];
124+
if (playlist_full.length > 0) attrParts.push(`p="${playlist_full.join(',')}"`);
125+
attrParts.push(`v="${embeddableFull.join(',')}"`);
126+
if (nonEmbeddableFull.length > 0) attrParts.push(`u="${nonEmbeddableFull.join(',')}"`);
127+
const newAttrs = [otherAttrs, ...attrParts].filter(Boolean).join(' ');
128+
return `<y-t${newAttrs ? ' ' + newAttrs.replace(/\s+/g, ' ') : ''}>`;
122129
});
123130
writeFileSync(join(__dirname, '..', 'index.html'),
124131
htmlContent
@@ -129,4 +136,4 @@ const writeOutput = async () => {
129136
console.log(`\nTotal videos processed: ${total.toLocaleString()}`);
130137
console.log(`Errors — playlists: ${playlistErrors}, videos: ${videoErrors}`);
131138
};
132-
writeOutput().catch(console.error);
139+
writeOutput().catch(console.error);

0 commit comments

Comments
 (0)