/* setup player */ let player const titleBar = document.getElementById("title") function onPlayerReady(event) { document.title = titleBar.textContent = player.videoTitle } function onYouTubeIframeAPIReady() { player = new YT.Player("player", { host: "https://www.youtube-nocookie.com", events: { "onReady": onPlayerReady, }, }) } /* helpers */ function getApiReq(path) { return fetch("/api" + path) .then(async res => { if (!res.ok) { const err = await res.text(); throw new Error(`Server error: ${err}`); } return res.json() }) .then(data => { return data }) .catch(error => { console.error("Fetch error:", error) }) } function toggleVis(e) { e.style.display = e.style.display ? null : "none" //window.location.hash = e.id && !e.style.display ? e.id : "" } function createAnchors(text) { const urlRegex = /(https?:\/\/[a-zA-Z0-9#$%&-./=?@_~]+[a-zA-Z0-9/]|@[a-zA-Z0-9.-_]+|[0-9]+:[0-9]+)/gi; return text.replace(urlRegex, url => { let href = url if (url[0] == "@") { href = `https://youtube.com/${url}` } else if (parseInt(url[0]) !== NaN) { const ts = url.split(":").reverse() let t = 0 for (let i = 0; i < ts.length; i++) t += ts[i] * Math.pow(60, i) href = `https://youtube.com/watch?v=${player.playerInfo.videoData.video_id}&t=${t}` } return `${url}` }) } /* setup video details buttons */ const buttonDetails = document.getElementById("toggle-details") const divDetails = document.getElementById("details") let detailsLoaded = false async function toggleDetails() { if (!player) return if (detailsLoaded) { toggleVis(divDetails) return } // elements const channel = document.getElementById("details-channel") const likes = document.getElementById("details-likes") const views = document.getElementById("details-views") const date = document.getElementById("details-date") const desc = document.getElementById("details-desc") const tags = document.getElementById("details-tags") // req const d = (await getApiReq(`/details?id=${player.playerInfo.videoData.video_id}`)).items[0] // populate channel.textContent = d.snippet.channelTitle channel.href = `https://www.youtube.com/channel/${d.snippet.channelId}` views.textContent = new Intl.NumberFormat().format(d.statistics.viewCount) likes.textContent = new Intl.NumberFormat().format(d.statistics.likeCount) date.textContent = d.snippet.publishedAt if (d.snippet.description) desc.innerHTML = createAnchors(d.snippet.description.replaceAll("\n", "
")) tags.textContent = d.snippet.tags.join(", ") // done detailsLoaded = true toggleVis(divDetails) } /* setup video comments buttons */ const buttonComments = document.getElementById("toggle-comments") const divComments = document.getElementById("comments") const commentTemplate = document.getElementById("template-comment") let commentsLoaded = false function genComment(cd, parent, replies) { const c = commentTemplate.content.cloneNode(true) const author = c.querySelector(".author") const date = c.querySelector(".date") const dateM = c.querySelector(".date.modified") const body = c.querySelector(".body") const likes = c.querySelector(".likes") author.textContent = cd.authorDisplayName author.href = cd.authorChannelUrl date.textContent = cd.publishedAt if (cd.publishedAt === cd.updatedAt) dateM.remove() else dateM.textContent = `(Modified ${cd.updatedAt})` body.innerHTML = cd.textDisplay likes.textContent = new Intl.NumberFormat().format(cd.likeCount) if (replies) { const divReplies = c.querySelector(".replies") for (const reply of replies) genComment(reply.snippet, divReplies) } parent.append(c) } async function toggleComments() { if (!player) return if (commentsLoaded) { toggleVis(divComments) return } // req const d = (await getApiReq(`/comments?id=${player.playerInfo.videoData.video_id }`)) // populate for (const cdd of d.items) { const cd = cdd.snippet.topLevelComment.snippet const replies = cdd.replies ? cdd.replies.comments : null genComment(cd, divComments, replies) } // done commentsLoaded = true toggleVis(divComments) } buttonDetails.addEventListener("click", () => { toggleDetails() toggleComments() buttonDetails.remove() }) /* timestamps in comments */ function timestampLinkClick(e) { if (e.target.tagName !== "A") return const url = new URL(e.target.href) if (url.pathname !== "/watch" || url.searchParams.get("v") !== player.playerInfo.videoData.video_id) return e.preventDefault() const t = url.searchParams.get("t") if (!t) return player.seekTo(t) } document.addEventListener("click", timestampLinkClick)