/* 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)