Skip to content

Commit a3910fd

Browse files
committed
[#60] Implement YT video on m.youtube.com
1 parent 2ab0739 commit a3910fd

2 files changed

Lines changed: 56 additions & 10 deletions

File tree

src/scripts/content/youtube/html.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const goChannel = getBrowserInstance().i18n.getMessage('pageGoChannel');
55
const videoConfidence = getBrowserInstance().i18n.getMessage('pageVideoConfidence');
66
const searchConfidence = getBrowserInstance().i18n.getMessage('pageSearchConfidence');
77

8-
export const constructButton = (vid: nebulavideo) => {
8+
export const constructButton = (vid: nebulavideo, isMobile = false) => {
99
if (!document.querySelector('.watch-on-nebula') || document.querySelector('.watch-on-nebula').children.length === 0) {
1010
Array.from(document.querySelectorAll<HTMLElement>('.watch-on-nebula')).forEach(n => n.remove());
1111
// for some reason youtube custom elements clear their inner html in construct, so we have to do it like this
@@ -24,7 +24,7 @@ export const constructButton = (vid: nebulavideo) => {
2424
bdiv.className = 'cbox yt-spec-button-shape-next--button-text-content';
2525
const bspan = bdiv.appendChild(document.createElement('span'));
2626
bspan.className = 'yt-core-attributed-string yt-core-attributed-string--white-space-no-wrap';
27-
bspan.textContent = watchOnNebula;
27+
bspan.textContent = isMobile ? 'Nebula' : watchOnNebula;
2828
bspan.setAttribute('href', vid.link);
2929
const bfeedback = btn.appendChild(document.createElement('yt-touch-feedback-shape'));
3030
bfeedback.style.borderRadius = 'inherit';

src/scripts/content/youtube/index.ts

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ export const youtube = async () => {
2727
setTimeout(run, 100);
2828
});
2929

30-
if (location.host.startsWith('m.')) return console.error('Enhancer for Nebula: YouTube mobile is currently not supported!');
31-
3230
Array.from(document.querySelectorAll<HTMLElement>('.watch-on-nebula')).forEach(n => n.remove());
3331
setTimeout(run, 0);
3432

@@ -40,6 +38,14 @@ export const youtube = async () => {
4038
console.log('VID id', vidDetails.videoId, 'title', vidDetails.title, 'CHANNEL author', vidDetails.author, 'Id', vidDetails.channelId);
4139
run({ vidID: vidDetails.videoId, videoTitle: vidDetails.title, channelID: vidDetails.channelId, channelNice: vidDetails.author });
4240
});
41+
window.addEventListener('state-navigateend', (e: CustomEvent) => {
42+
console.dev.log('NAVIGATE2', e);
43+
const vidDetails = e.detail?.data?.response?.playerResponse?.videoDetails as YouTubePlayer.VideoDetails;
44+
remove();
45+
if (!vidDetails) return;
46+
console.log('VID id', vidDetails.videoId, 'title', vidDetails.title, 'CHANNEL author', vidDetails.author, 'Id', vidDetails.channelId);
47+
run({ vidID: vidDetails.videoId, videoTitle: vidDetails.title, channelID: vidDetails.channelId, channelNice: vidDetails.author });
48+
});
4349
};
4450

4551
const action = new CancellableRepeatingAction();
@@ -55,8 +61,9 @@ const run = debounce(async ({ vidID, videoTitle, channelID, channelNice }: { vid
5561
if (!options.watchnebula) return Array.from(document.querySelectorAll<HTMLElement>('.watch-on-nebula')).forEach(n => n.remove());
5662
let first = true;
5763
let channelName = channelNice;
64+
const isMobile = location.host.startsWith('m.');
5865

59-
async function* videoDetailsFromDOM() {
66+
async function* videoDetailsFromDOMDesktop() {
6067
if (first && vidID && videoTitle && channelID && channelNice) return;
6168

6269
const channelElement = document.querySelector<HTMLAnchorElement>(
@@ -68,7 +75,7 @@ const run = debounce(async ({ vidID, videoTitle, channelID, channelNice }: { vid
6875

6976
// accessing custom attributes is not possible from content scripts, so inject this helper
7077
const foundChannelID = await injectFunctionWithReturn(document.body, () =>
71-
(document.querySelector('ytd-channel-name .yt-simple-endpoint') as any).data.browseEndpoint.browseId as string);
78+
(document.querySelector('ytd-channel-name .yt-simple-endpoint') as any)?.data?.browseEndpoint?.browseId as string);
7279
channelID ??= foundChannelID;
7380
if (first && channelID !== foundChannelID) {
7481
channelName = channelNice;
@@ -82,8 +89,40 @@ const run = debounce(async ({ vidID, videoTitle, channelID, channelNice }: { vid
8289
vidID ??= idElement.getAttribute('video-id');
8390
}
8491

92+
async function* videoDetailsFromDOMMobile() {
93+
if (first && vidID && videoTitle && channelID && channelNice) return;
94+
95+
const channelElement = document.querySelector<HTMLAnchorElement>('a.slim-owner-icon-and-title[href^="/@"]');
96+
const titleElement = document.querySelector<HTMLSpanElement>('h2.slim-video-information-title span');
97+
const idElement = document.querySelector('ytm-slim-video-metadata-section-renderer');
98+
console.dev.debug('Elements', !!channelElement, !!titleElement, !!idElement);
99+
if (!channelElement || !titleElement || !idElement) yield true; // retry
100+
101+
// accessing custom attributes is not possible from content scripts, so inject this helper
102+
const foundChannelID = await injectFunctionWithReturn(document.body, () =>
103+
(document.querySelector('ytm-slim-owner-renderer') as any)?.data?.navigationEndpoint?.browseEndpoint?.browseId as string);
104+
channelID ??= foundChannelID;
105+
if (first && channelID !== foundChannelID) {
106+
channelName = channelNice;
107+
} else if (!first && channelID !== foundChannelID) {
108+
yield true; // retry
109+
} else {
110+
channelName = channelElement.href.split('/').pop();
111+
}
112+
113+
videoTitle ??= titleElement.textContent;
114+
if (!vidID) {
115+
vidID = await injectFunctionWithReturn(document.body, () =>
116+
(document.querySelector('ytm-slim-video-metadata-section-renderer') as any)?.data?.videoId as string);
117+
}
118+
}
119+
85120
await action.run(async function* () {
86-
yield* videoDetailsFromDOM();
121+
if (isMobile) {
122+
yield* videoDetailsFromDOMMobile();
123+
} else {
124+
yield* videoDetailsFromDOMDesktop();
125+
}
87126
const vid: nebulavideo = await getBrowserInstance().runtime.sendMessage({ type: BrowserMessage.GET_VID, channelID, channelName, channelNice, videoTitle });
88127
console.debug('got video information',
89128
'\nchannelID:', channelID, 'channelName:', channelName, '(', channelNice, ')', 'videoTitle:', videoTitle, 'vidID:', vidID,
@@ -98,11 +137,18 @@ const run = debounce(async ({ vidID, videoTitle, channelID, channelNice }: { vid
98137
console.dev.log('Found video:', vid);
99138
first = false;
100139

101-
const subscribeElement = document.querySelector<HTMLDivElement>('ytd-watch-metadata #subscribe-button, ytd-video-secondary-info-renderer #subscribe-button');
140+
const subscribeElement = !isMobile ?
141+
document.querySelector<HTMLDivElement>('ytd-watch-metadata #subscribe-button, ytd-video-secondary-info-renderer #subscribe-button') :
142+
document.querySelector<HTMLDivElement>('ytm-slim-owner-renderer .slim-owner-subscribe-button');
102143
if (!subscribeElement) yield true; // retry
103144

104-
subscribeElement.before(constructButton(vid));
105-
subscribeElement.closest<HTMLDivElement>('#top-row.ytd-watch-metadata').style.display = 'block'; // not the prettiest, but it works
145+
const button = constructButton(vid, isMobile);
146+
subscribeElement.before(button);
147+
if (!isMobile) {
148+
subscribeElement.closest<HTMLDivElement>('#top-row.ytd-watch-metadata').style.display = 'block'; // not the prettiest, but it works
149+
} else {
150+
button.style.marginRight = '5px';
151+
}
106152
if ((window.history.state || {})['_enhancer_checked'] === true) return console.debug('Ignoring video since already processed');
107153

108154
const { ytOpenTab: doOpenTab, ytMuteOnly: muteOnly, ytReplaceTab: replaceTab } = options;

0 commit comments

Comments
 (0)