mirror of
https://github.com/GetOpenScript/Reddit.openscript.git
synced 2026-09-18 08:05:45 +00:00
Add YouTube.os.js: Reddit Comments on YouTube user script
This commit is contained in:
25
README.md
Normal file
25
README.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Reddit.openscript
|
||||||
|
|
||||||
|
User scripts for Reddit integrations powered by [OpenScript](https://github.com/GetOpenScript/OpenScript).
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
### 1. Reddit Comments on YouTube (`YouTube.os.js`)
|
||||||
|
|
||||||
|
Embeds relevant Reddit discussion threads and comments directly above the native YouTube comment section on any YouTube video page.
|
||||||
|
|
||||||
|
#### Features
|
||||||
|
- **Zero API Keys & Read-Only:** Queries public Reddit JSON endpoints without credentials or cookies. Completely anonymous.
|
||||||
|
- **Dual Comment Display:** YouTube comments remain visible and accessible directly beneath the Reddit discussion section.
|
||||||
|
- **Multiple Threads Support:** Displays clickable subreddit tabs sorted by `top` score.
|
||||||
|
- **Official Subreddit Detection:** Automatically detects subreddit links in video descriptions and badges matching threads as `Official`.
|
||||||
|
- **Collapsible Comment Trees:** Clean nested thread view with `[–]` and `[+]` collapse toggles.
|
||||||
|
- **Native YouTube Theming:** Adapts seamlessly to YouTube's light and dark themes using YouTube CSS color variables.
|
||||||
|
- **Always Fresh:** No caching, so newly submitted threads appear on page navigation.
|
||||||
|
|
||||||
|
#### Installation
|
||||||
|
1. Install [OpenScript](https://chromewebstore.google.com/detail/openscript/dkelmgdchndagjemmodhkphdikhpnfol) (requires version `1.0.4` or newer for `OpenScript.fetch`).
|
||||||
|
2. Open the OpenScript popup and click **New Script**.
|
||||||
|
3. Copy and paste the contents of [`YouTube.os.js`](YouTube.os.js) into the editor.
|
||||||
|
4. Save the script and ensure it is enabled.
|
||||||
|
5. Open any YouTube video with Reddit discussions (e.g. trailers, music videos, podcasts).
|
||||||
526
YouTube.os.js
Normal file
526
YouTube.os.js
Normal file
@@ -0,0 +1,526 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name Reddit Comments on YouTube
|
||||||
|
// @version 1.0.0
|
||||||
|
// @description View Reddit discussion threads and comments directly above YouTube comments.
|
||||||
|
// @match https://www.youtube.com/*
|
||||||
|
// @run-at document_idle
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
const MOUNT_ID = 'os-reddit-mount';
|
||||||
|
const STYLE_ID = 'os-reddit-style';
|
||||||
|
const COMMENTS_SELECTOR = '#comments';
|
||||||
|
const DESC_SELECTOR = '#description, #description-inline-expander, ytd-text-inline-expander';
|
||||||
|
|
||||||
|
let currentVideoId = null;
|
||||||
|
let activePostId = null;
|
||||||
|
let cachedPosts = [];
|
||||||
|
let abortController = null;
|
||||||
|
|
||||||
|
const ensureStyles = () => {
|
||||||
|
if (document.getElementById(STYLE_ID)) return;
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = STYLE_ID;
|
||||||
|
style.textContent = `
|
||||||
|
#${MOUNT_ID} {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
font-family: Roboto, Arial, sans-serif;
|
||||||
|
color: var(--yt-spec-text-primary, #0f0f0f);
|
||||||
|
}
|
||||||
|
.os-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 4px 0 12px;
|
||||||
|
border-bottom: 1px solid var(--yt-spec-outline, rgba(0,0,0,0.1));
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
.os-tab {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid var(--yt-spec-outline, rgba(0,0,0,0.1));
|
||||||
|
background: var(--yt-spec-badge-chip-background, rgba(0,0,0,0.05));
|
||||||
|
color: var(--yt-spec-text-secondary, #606060);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: background 0.2s, border-color 0.2s;
|
||||||
|
}
|
||||||
|
.os-tab:hover {
|
||||||
|
background: var(--yt-spec-button-chip-background-hover, rgba(0,0,0,0.1));
|
||||||
|
}
|
||||||
|
.os-tab.os-active {
|
||||||
|
background: #ff4500;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #ff4500;
|
||||||
|
}
|
||||||
|
.os-tab .os-badge {
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 1px 5px;
|
||||||
|
background: rgba(255,255,255,0.25);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.os-tab:not(.os-active) .os-badge {
|
||||||
|
background: var(--yt-spec-outline, rgba(0,0,0,0.1));
|
||||||
|
color: var(--yt-spec-text-primary, #0f0f0f);
|
||||||
|
}
|
||||||
|
.os-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin: 14px 0 12px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.os-post-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--yt-spec-text-primary, #0f0f0f);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.os-post-title:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.os-post-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--yt-spec-text-secondary, #606060);
|
||||||
|
}
|
||||||
|
.os-official-tag {
|
||||||
|
background: #0079d3;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.os-comment-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.os-comment {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.os-comment-inner {
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
.os-comment-tagline {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--yt-spec-text-secondary, #606060);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.os-collapse {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--yt-spec-text-secondary, #606060);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
.os-author {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--yt-spec-text-primary, #0f0f0f);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.os-author.os-op {
|
||||||
|
color: #0079d3;
|
||||||
|
}
|
||||||
|
.os-score {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.os-body {
|
||||||
|
color: var(--yt-spec-text-primary, #0f0f0f);
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
.os-body p { margin: 4px 0; }
|
||||||
|
.os-body a { color: var(--yt-spec-brand-link-text, #065fd4); text-decoration: none; }
|
||||||
|
.os-body a:hover { text-decoration: underline; }
|
||||||
|
.os-body blockquote {
|
||||||
|
margin: 4px 0;
|
||||||
|
padding-left: 8px;
|
||||||
|
border-left: 3px solid var(--yt-spec-outline, rgba(0,0,0,0.2));
|
||||||
|
color: var(--yt-spec-text-secondary, #606060);
|
||||||
|
}
|
||||||
|
.os-body pre, .os-body code {
|
||||||
|
background: var(--yt-spec-badge-chip-background, rgba(0,0,0,0.05));
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
}
|
||||||
|
.os-replies {
|
||||||
|
margin-left: 12px;
|
||||||
|
padding-left: 12px;
|
||||||
|
border-left: 2px solid var(--yt-spec-outline, rgba(0,0,0,0.1));
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.os-collapsed > .os-body,
|
||||||
|
.os-collapsed > .os-replies,
|
||||||
|
.os-collapsed > .os-load-more {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.os-load-more {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--yt-spec-outline, rgba(0,0,0,0.1));
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
color: var(--yt-spec-brand-link-text, #065fd4);
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 6px;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
.os-load-more:hover {
|
||||||
|
background: var(--yt-spec-badge-chip-background, rgba(0,0,0,0.05));
|
||||||
|
}
|
||||||
|
.os-status {
|
||||||
|
padding: 12px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--yt-spec-text-secondary, #606060);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.append(style);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatScore = n => {
|
||||||
|
if (typeof n !== 'number') return '0';
|
||||||
|
if (Math.abs(n) >= 1e4) return `${(n / 1e3).toFixed(1)}k`;
|
||||||
|
if (Math.abs(n) >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
||||||
|
return String(n);
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeAgo = utc => {
|
||||||
|
const s = Math.max(0, Math.floor(Date.now() / 1e3 - utc));
|
||||||
|
if (s < 60) return `${s}s ago`;
|
||||||
|
const m = Math.floor(s / 60);
|
||||||
|
if (m < 60) return `${m}m ago`;
|
||||||
|
const h = Math.floor(m / 60);
|
||||||
|
if (h < 24) return `${h}h ago`;
|
||||||
|
const d = Math.floor(h / 24);
|
||||||
|
if (d < 30) return `${d}d ago`;
|
||||||
|
const mo = Math.floor(d / 30);
|
||||||
|
if (mo < 12) return `${mo}mo ago`;
|
||||||
|
return `${Math.floor(mo / 12)}y ago`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const decodeHtml = str => {
|
||||||
|
const txt = document.createElement('textarea');
|
||||||
|
txt.innerHTML = str;
|
||||||
|
return txt.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sanitizeHtml = html => {
|
||||||
|
const decoded = decodeHtml(html || '')
|
||||||
|
.replace(/href="\/((r|u)\/[^"]+)"/g, 'href="https://www.reddit.com/$1"')
|
||||||
|
.replace(/<a\b([^>]*)>/gi, '<a target="_blank" rel="noopener noreferrer" $1>');
|
||||||
|
const tpl = document.createElement('template');
|
||||||
|
tpl.innerHTML = decoded;
|
||||||
|
tpl.content.querySelectorAll('script, style, iframe, form, button').forEach(el => el.remove());
|
||||||
|
return tpl.innerHTML;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOfficialSub = () => {
|
||||||
|
const descEl = document.querySelector(DESC_SELECTOR);
|
||||||
|
const match = descEl?.textContent?.match(/reddit\.com\/r\/([^ /\n\r?]+)/i);
|
||||||
|
return match ? match[1].toLowerCase() : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMount = () => {
|
||||||
|
let mount = document.getElementById(MOUNT_ID);
|
||||||
|
if (mount) return mount;
|
||||||
|
const target = document.querySelector(COMMENTS_SELECTOR);
|
||||||
|
if (!target || !target.parentNode) return null;
|
||||||
|
mount = document.createElement('div');
|
||||||
|
mount.id = MOUNT_ID;
|
||||||
|
target.parentNode.insertBefore(mount, target);
|
||||||
|
return mount;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderComments = (children, postAuthor) => {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.className = 'os-comment-list';
|
||||||
|
|
||||||
|
for (const child of children) {
|
||||||
|
if (child.kind === 'more') {
|
||||||
|
const { count, children: moreIds, id, parent_id } = child.data;
|
||||||
|
if (!moreIds?.length) continue;
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'os-load-more';
|
||||||
|
btn.textContent = `Load more comments (${count || moreIds.length})`;
|
||||||
|
btn.onclick = async () => {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Loading...';
|
||||||
|
try {
|
||||||
|
const res = await OpenScript.fetch(
|
||||||
|
`https://www.reddit.com/api/morechildren.json?api_type=json&link_id=${activePostId}&children=${moreIds.slice(0, 20).join(',')}&sort=best`
|
||||||
|
);
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
const json = await res.json();
|
||||||
|
const items = json?.json?.data?.things?.map(t => t.data) || [];
|
||||||
|
const rendered = renderFlatComments(items, postAuthor);
|
||||||
|
btn.replaceWith(rendered);
|
||||||
|
} catch {
|
||||||
|
btn.textContent = 'Failed to load comments';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
container.append(btn);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id, author, score, created_utc, body_html, replies } = child.data;
|
||||||
|
if (!body_html) continue;
|
||||||
|
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'os-comment';
|
||||||
|
el.id = `os-c-${id}`;
|
||||||
|
|
||||||
|
const tagline = document.createElement('div');
|
||||||
|
tagline.className = 'os-comment-tagline';
|
||||||
|
|
||||||
|
const toggle = document.createElement('button');
|
||||||
|
toggle.className = 'os-collapse';
|
||||||
|
toggle.textContent = '[–]';
|
||||||
|
toggle.onclick = () => {
|
||||||
|
const isCol = el.classList.toggle('os-collapsed');
|
||||||
|
toggle.textContent = isCol ? '[+]' : '[–]';
|
||||||
|
};
|
||||||
|
|
||||||
|
const authorLink = document.createElement('a');
|
||||||
|
authorLink.className = `os-author${author === postAuthor ? ' os-op' : ''}`;
|
||||||
|
authorLink.href = `https://www.reddit.com/u/${author}`;
|
||||||
|
authorLink.target = '_blank';
|
||||||
|
authorLink.rel = 'noopener noreferrer';
|
||||||
|
authorLink.textContent = author;
|
||||||
|
|
||||||
|
const metaSpan = document.createElement('span');
|
||||||
|
metaSpan.className = 'os-score';
|
||||||
|
metaSpan.textContent = `• ${formatScore(score)} points • ${timeAgo(created_utc)}`;
|
||||||
|
|
||||||
|
tagline.append(toggle, authorLink, metaSpan);
|
||||||
|
|
||||||
|
const body = document.createElement('div');
|
||||||
|
body.className = 'os-body';
|
||||||
|
body.innerHTML = sanitizeHtml(body_html);
|
||||||
|
|
||||||
|
el.append(tagline, body);
|
||||||
|
|
||||||
|
if (replies?.data?.children?.length) {
|
||||||
|
const sub = renderComments(replies.data.children, postAuthor);
|
||||||
|
sub.className = 'os-replies';
|
||||||
|
el.append(sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
container.append(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderFlatComments = (items, postAuthor) => {
|
||||||
|
const wrap = document.createDocumentFragment();
|
||||||
|
for (const item of items) {
|
||||||
|
if (!item.body_html) continue;
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'os-comment';
|
||||||
|
|
||||||
|
const tagline = document.createElement('div');
|
||||||
|
tagline.className = 'os-comment-tagline';
|
||||||
|
|
||||||
|
const authorLink = document.createElement('a');
|
||||||
|
authorLink.className = `os-author${item.author === postAuthor ? ' os-op' : ''}`;
|
||||||
|
authorLink.href = `https://www.reddit.com/u/${item.author}`;
|
||||||
|
authorLink.target = '_blank';
|
||||||
|
authorLink.rel = 'noopener noreferrer';
|
||||||
|
authorLink.textContent = item.author;
|
||||||
|
|
||||||
|
const meta = document.createElement('span');
|
||||||
|
meta.className = 'os-score';
|
||||||
|
meta.textContent = `• ${formatScore(item.score)} points • ${timeAgo(item.created_utc)}`;
|
||||||
|
|
||||||
|
tagline.append(authorLink, meta);
|
||||||
|
|
||||||
|
const body = document.createElement('div');
|
||||||
|
body.className = 'os-body';
|
||||||
|
body.innerHTML = sanitizeHtml(item.body_html);
|
||||||
|
|
||||||
|
el.append(tagline, body);
|
||||||
|
wrap.append(el);
|
||||||
|
}
|
||||||
|
return wrap;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadPostComments = async (post, postContainer) => {
|
||||||
|
postContainer.innerHTML = '<div class="os-status">Loading Reddit comments...</div>';
|
||||||
|
try {
|
||||||
|
const res = await OpenScript.fetch(`https://www.reddit.com/comments/${post.id}.json?sort=best`);
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
const [, commentsData] = await res.json();
|
||||||
|
const children = commentsData?.data?.children || [];
|
||||||
|
|
||||||
|
postContainer.innerHTML = '';
|
||||||
|
if (!children.length) {
|
||||||
|
postContainer.innerHTML = '<div class="os-status">No comments in this thread yet.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
postContainer.append(renderComments(children, post.author));
|
||||||
|
} catch (err) {
|
||||||
|
postContainer.innerHTML = `<div class="os-status">Unable to load Reddit comments (${err.message}).</div>`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderThread = post => {
|
||||||
|
const mount = getMount();
|
||||||
|
if (!mount) return;
|
||||||
|
|
||||||
|
let contentArea = mount.querySelector('.os-content-area');
|
||||||
|
if (!contentArea) {
|
||||||
|
contentArea = document.createElement('div');
|
||||||
|
contentArea.className = 'os-content-area';
|
||||||
|
mount.append(contentArea);
|
||||||
|
}
|
||||||
|
contentArea.innerHTML = '';
|
||||||
|
|
||||||
|
const header = document.createElement('div');
|
||||||
|
header.className = 'os-header';
|
||||||
|
|
||||||
|
const left = document.createElement('div');
|
||||||
|
const title = document.createElement('a');
|
||||||
|
title.className = 'os-post-title';
|
||||||
|
title.href = `https://www.reddit.com${post.permalink}`;
|
||||||
|
title.target = '_blank';
|
||||||
|
title.rel = 'noopener noreferrer';
|
||||||
|
title.textContent = decodeHtml(post.title);
|
||||||
|
|
||||||
|
const meta = document.createElement('div');
|
||||||
|
meta.className = 'os-post-meta';
|
||||||
|
meta.textContent = `r/${post.subreddit} • Posted by u/${post.author} ${timeAgo(post.created_utc)} • ${formatScore(post.score)} points`;
|
||||||
|
|
||||||
|
left.append(title, meta);
|
||||||
|
header.append(left);
|
||||||
|
contentArea.append(header);
|
||||||
|
|
||||||
|
const commentsArea = document.createElement('div');
|
||||||
|
commentsArea.className = 'os-comments-area';
|
||||||
|
contentArea.append(commentsArea);
|
||||||
|
|
||||||
|
loadPostComments(post, commentsArea);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTabs = posts => {
|
||||||
|
const mount = getMount();
|
||||||
|
if (!mount) return;
|
||||||
|
|
||||||
|
const officialSub = getOfficialSub();
|
||||||
|
if (officialSub) {
|
||||||
|
posts.sort((a, b) => (a.subreddit.toLowerCase() === officialSub ? -1 : b.subreddit.toLowerCase() === officialSub ? 1 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
let tabs = mount.querySelector('.os-tabs');
|
||||||
|
if (!tabs) {
|
||||||
|
tabs = document.createElement('div');
|
||||||
|
tabs.className = 'os-tabs';
|
||||||
|
mount.prepend(tabs);
|
||||||
|
}
|
||||||
|
tabs.innerHTML = '';
|
||||||
|
|
||||||
|
const activePost = posts.find(p => p.name === activePostId) || posts[0];
|
||||||
|
activePostId = activePost.name;
|
||||||
|
|
||||||
|
posts.forEach(post => {
|
||||||
|
const isOfficial = officialSub && post.subreddit.toLowerCase() === officialSub;
|
||||||
|
const tab = document.createElement('div');
|
||||||
|
tab.className = `os-tab${post.name === activePostId ? ' os-active' : ''}`;
|
||||||
|
tab.textContent = `r/${post.subreddit}`;
|
||||||
|
|
||||||
|
if (isOfficial) {
|
||||||
|
const off = document.createElement('span');
|
||||||
|
off.className = 'os-official-tag';
|
||||||
|
off.textContent = 'Official';
|
||||||
|
tab.append(off);
|
||||||
|
}
|
||||||
|
|
||||||
|
const badge = document.createElement('span');
|
||||||
|
badge.className = 'os-badge';
|
||||||
|
badge.textContent = `${formatScore(post.score)} ↑ • ${post.num_comments} 💬`;
|
||||||
|
tab.append(badge);
|
||||||
|
|
||||||
|
tab.onclick = () => {
|
||||||
|
if (activePostId === post.name) return;
|
||||||
|
activePostId = post.name;
|
||||||
|
tabs.querySelectorAll('.os-tab').forEach(t => t.classList.remove('os-active'));
|
||||||
|
tab.classList.add('os-active');
|
||||||
|
renderThread(post);
|
||||||
|
};
|
||||||
|
|
||||||
|
tabs.append(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
renderThread(activePost);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateForVideo = async videoId => {
|
||||||
|
ensureStyles();
|
||||||
|
const mount = getMount();
|
||||||
|
if (!mount) return;
|
||||||
|
|
||||||
|
mount.innerHTML = '<div class="os-status">Searching Reddit for discussion threads...</div>';
|
||||||
|
cachedPosts = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await OpenScript.fetch(
|
||||||
|
`https://www.reddit.com/search.json?q=url:'${encodeURIComponent(videoId)}'&sort=top&type=link`
|
||||||
|
);
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
const data = await res.json();
|
||||||
|
const posts = data?.data?.children?.map(c => c.data) || [];
|
||||||
|
|
||||||
|
if (!posts.length) {
|
||||||
|
mount.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedPosts = posts;
|
||||||
|
renderTabs(posts);
|
||||||
|
} catch (err) {
|
||||||
|
mount.innerHTML = `<div class="os-status">Could not search Reddit (${err.message}).</div>`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const check = () => {
|
||||||
|
const id = new URLSearchParams(location.search).get('v');
|
||||||
|
if (!id) {
|
||||||
|
currentVideoId = null;
|
||||||
|
const mount = document.getElementById(MOUNT_ID);
|
||||||
|
if (mount) mount.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (id === currentVideoId) {
|
||||||
|
if (cachedPosts.length && !document.getElementById(MOUNT_ID)?.hasChildNodes()) {
|
||||||
|
renderTabs(cachedPosts);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentVideoId = id;
|
||||||
|
activePostId = null;
|
||||||
|
updateForVideo(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('yt-navigate-finish', check);
|
||||||
|
setInterval(check, 1200);
|
||||||
|
check();
|
||||||
Reference in New Issue
Block a user