mirror of
https://git.sr.ht/~cadence/cloudtube
synced 2024-11-13 20:07:30 +00:00
Implement watched videos
Watched videos on your subscriptions feed will be darkened, and the option to hide all of them has been added. This only takes effect if you have enabled saving watched videos on the server in the settings menu - default is off.
This commit is contained in:
parent
c3afe77e2d
commit
e0bc0d2e81
@ -22,6 +22,8 @@ module.exports = [
|
|||||||
channels = db.prepare(`SELECT * FROM Channels WHERE ucid IN (${template}) ORDER BY name`).all(subscriptions)
|
channels = db.prepare(`SELECT * FROM Channels WHERE ucid IN (${template}) ORDER BY name`).all(subscriptions)
|
||||||
// get refreshed status
|
// get refreshed status
|
||||||
refreshed = db.prepare(`SELECT min(refreshed) as min, max(refreshed) as max, count(refreshed) as count FROM Channels WHERE ucid IN (${template})`).get(subscriptions)
|
refreshed = db.prepare(`SELECT min(refreshed) as min, max(refreshed) as max, count(refreshed) as count FROM Channels WHERE ucid IN (${template})`).get(subscriptions)
|
||||||
|
// get watched videos
|
||||||
|
const watchedVideos = user.getWatchedVideos()
|
||||||
// get videos
|
// get videos
|
||||||
if (subscriptions.length) {
|
if (subscriptions.length) {
|
||||||
hasSubscriptions = true
|
hasSubscriptions = true
|
||||||
@ -29,12 +31,15 @@ module.exports = [
|
|||||||
videos = db.prepare(`SELECT * FROM Videos WHERE authorId IN (${template}) ORDER BY published DESC LIMIT 60`).all(subscriptions)
|
videos = db.prepare(`SELECT * FROM Videos WHERE authorId IN (${template}) ORDER BY published DESC LIMIT 60`).all(subscriptions)
|
||||||
.map(video => {
|
.map(video => {
|
||||||
video.publishedText = timeToPastText(video.published * 1000)
|
video.publishedText = timeToPastText(video.published * 1000)
|
||||||
|
console.log(watchedVideos, video.videoId)
|
||||||
|
video.watched = watchedVideos.includes(video.videoId)
|
||||||
return video
|
return video
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const instanceOrigin = user.getSettingsOrDefaults().instance
|
const settings = user.getSettingsOrDefaults()
|
||||||
return render(200, "pug/subscriptions.pug", {hasSubscriptions, videos, channels, refreshed, timeToPastText, instanceOrigin})
|
const instanceOrigin = settings.instance
|
||||||
|
return render(200, "pug/subscriptions.pug", {settings, hasSubscriptions, videos, channels, refreshed, timeToPastText, instanceOrigin})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
15
api/video.js
15
api/video.js
@ -37,22 +37,35 @@ function formatOrder(format) {
|
|||||||
|
|
||||||
async function renderVideo(videoPromise, {user, id, instanceOrigin}) {
|
async function renderVideo(videoPromise, {user, id, instanceOrigin}) {
|
||||||
try {
|
try {
|
||||||
|
// resolve video
|
||||||
const video = await videoPromise
|
const video = await videoPromise
|
||||||
if (!video) throw new Error("The instance returned null.")
|
if (!video) throw new Error("The instance returned null.")
|
||||||
if (video.error) throw new InstanceError(video.error, video.identifier)
|
if (video.error) throw new InstanceError(video.error, video.identifier)
|
||||||
// video data additional processing
|
// process stream list ordering
|
||||||
for (const format of video.formatStreams.concat(video.adaptiveFormats)) {
|
for (const format of video.formatStreams.concat(video.adaptiveFormats)) {
|
||||||
if (!format.second__height && format.resolution) format.second__height = +format.resolution.slice(0, -1)
|
if (!format.second__height && format.resolution) format.second__height = +format.resolution.slice(0, -1)
|
||||||
if (!format.second__order) format.second__order = formatOrder(format)
|
if (!format.second__order) format.second__order = formatOrder(format)
|
||||||
}
|
}
|
||||||
|
// process length text
|
||||||
for (const rec of video.recommendedVideos) {
|
for (const rec of video.recommendedVideos) {
|
||||||
if (!rec.second__lengthText && rec.lengthSeconds > 0) {
|
if (!rec.second__lengthText && rec.lengthSeconds > 0) {
|
||||||
rec.second__lengthText = converters.lengthSecondsToLengthText(rec.lengthSeconds)
|
rec.second__lengthText = converters.lengthSecondsToLengthText(rec.lengthSeconds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// get subscription data
|
||||||
const subscribed = user.isSubscribed(video.authorId)
|
const subscribed = user.isSubscribed(video.authorId)
|
||||||
|
// process watched videos
|
||||||
|
user.addWatchedVideoMaybe(video.videoId)
|
||||||
|
const watchedVideos = user.getWatchedVideos()
|
||||||
|
if (watchedVideos.length) {
|
||||||
|
for (const rec of video.recommendedVideos) {
|
||||||
|
rec.watched = watchedVideos.includes(rec.videoId)
|
||||||
|
}
|
||||||
|
}
|
||||||
return render(200, "pug/video.pug", {video, subscribed, instanceOrigin})
|
return render(200, "pug/video.pug", {video, subscribed, instanceOrigin})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// show an appropriate error message
|
||||||
|
// these should probably be split out to their own files
|
||||||
let message = pug.render("pre= error", {error: e.stack || e.toString()})
|
let message = pug.render("pre= error", {error: e.stack || e.toString()})
|
||||||
if (e instanceof fetch.FetchError) {
|
if (e instanceof fetch.FetchError) {
|
||||||
const template = `
|
const template = `
|
||||||
|
@ -28,5 +28,4 @@ block content
|
|||||||
|
|
||||||
.videos
|
.videos
|
||||||
each video in data.latestVideos
|
each video in data.latestVideos
|
||||||
.channel-video
|
+video_list_item("channel-video", video, instanceOrigin)
|
||||||
+video_list_item(video, instanceOrigin)
|
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
mixin video_list_item(video, instanceOrigin)
|
mixin video_list_item(className, video, instanceOrigin)
|
||||||
- let link = `/watch?v=${video.videoId}`
|
div(class={[className]: true, "video-list-item--watched": video.watched})
|
||||||
a(href=link tabindex="-1").thumbnail
|
- let link = `/watch?v=${video.videoId}`
|
||||||
img(src=`/vi/${video.videoId}/mqdefault.jpg` width=320 height=180 alt="").image
|
a(href=link tabindex="-1").thumbnail
|
||||||
if video.second__lengthText != undefined
|
img(src=`/vi/${video.videoId}/mqdefault.jpg` width=320 height=180 alt="").image
|
||||||
span.duration= video.second__lengthText
|
if video.second__lengthText != undefined
|
||||||
.info
|
span.duration= video.second__lengthText
|
||||||
div.title: a(href=link).title-link= video.title
|
.info
|
||||||
div.author-line
|
div.title: a(href=link).title-link= video.title
|
||||||
a(href=`/channel/${video.authorId}`).author= video.author
|
div.author-line
|
||||||
- const views = video.viewCountText || video.second__viewCountText
|
a(href=`/channel/${video.authorId}`).author= video.author
|
||||||
if views
|
- const views = video.viewCountText || video.second__viewCountText
|
||||||
= ` • `
|
if views
|
||||||
span.views= views
|
= ` • `
|
||||||
if video.publishedText
|
span.views= views
|
||||||
= ` • `
|
if video.publishedText
|
||||||
span.published= video.publishedText
|
= ` • `
|
||||||
if video.descriptionHtml
|
span.published= video.publishedText
|
||||||
div.description!= video.descriptionHtml
|
if video.descriptionHtml
|
||||||
|
div.description!= video.descriptionHtml
|
||||||
|
@ -8,5 +8,4 @@ block head
|
|||||||
block content
|
block content
|
||||||
main.search-page
|
main.search-page
|
||||||
each result in results
|
each result in results
|
||||||
.search-result
|
+video_list_item("search-result", result, instanceOrigin)
|
||||||
+video_list_item(result, instanceOrigin)
|
|
||||||
|
@ -37,9 +37,9 @@ block content
|
|||||||
"https://invidious.fdn.fr"
|
"https://invidious.fdn.fr"
|
||||||
])
|
])
|
||||||
|
|
||||||
+select("save_history", "Watch history", false, [
|
+select("save_history", "Watched videos history", false, [
|
||||||
{value: "0", text: "Don't save"},
|
{value: "0", text: "Don't store"},
|
||||||
{value: "1", text: "Save"}
|
{value: "1", text: "Store on server"}
|
||||||
])
|
])
|
||||||
|
|
||||||
+select("local", "Fetch videos", false, [
|
+select("local", "Fetch videos", false, [
|
||||||
|
@ -27,9 +27,13 @@ block content
|
|||||||
if notLoaded
|
if notLoaded
|
||||||
div #{notLoaded} subscriptions have not been refreshed at all
|
div #{notLoaded} subscriptions have not been refreshed at all
|
||||||
|
|
||||||
|
if settings.save_history
|
||||||
|
input(type="checkbox" id="watched-videos-display")
|
||||||
|
.watched-videos-display-container
|
||||||
|
label(for="watched-videos-display").watched-videos-display-label Hide watched videos
|
||||||
|
|
||||||
each video in videos
|
each video in videos
|
||||||
.subscriptions-video
|
+video_list_item("subscriptions-video", video, instanceOrigin)
|
||||||
+video_list_item(video, instanceOrigin)
|
|
||||||
else
|
else
|
||||||
.no-subscriptions
|
.no-subscriptions
|
||||||
h2 You have no subscriptions.
|
h2 You have no subscriptions.
|
||||||
|
@ -66,8 +66,7 @@ block content
|
|||||||
aside.related-videos
|
aside.related-videos
|
||||||
h2.related-header Related videos
|
h2.related-header Related videos
|
||||||
each r in video.recommendedVideos
|
each r in video.recommendedVideos
|
||||||
.related-video
|
+video_list_item("related-video", r, instanceOrigin)
|
||||||
+video_list_item(r, instanceOrigin)
|
|
||||||
|
|
||||||
else
|
else
|
||||||
//- error
|
//- error
|
||||||
|
@ -54,7 +54,6 @@ details
|
|||||||
border-radius: 8px
|
border-radius: 8px
|
||||||
|
|
||||||
summary
|
summary
|
||||||
text-decoration: underline
|
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
line-height: 1
|
line-height: 1
|
||||||
margin-bottom: 0
|
margin-bottom: 0
|
||||||
|
@ -33,3 +33,35 @@
|
|||||||
.name
|
.name
|
||||||
font-size: 22px
|
font-size: 22px
|
||||||
color: c.$fg-main
|
color: c.$fg-main
|
||||||
|
|
||||||
|
|
||||||
|
#watched-videos-display
|
||||||
|
position: relative
|
||||||
|
left: 10px
|
||||||
|
display: block
|
||||||
|
z-index: 1
|
||||||
|
height: 42px
|
||||||
|
margin: 0
|
||||||
|
|
||||||
|
.watched-videos-display-container
|
||||||
|
position: relative
|
||||||
|
display: grid // why does the default not work???
|
||||||
|
top: -42px
|
||||||
|
background: c.$bg-accent-x
|
||||||
|
line-height: 1
|
||||||
|
border-radius: 8px
|
||||||
|
margin-bottom: -18px
|
||||||
|
|
||||||
|
.watched-videos-display-label
|
||||||
|
padding: 12px 0px 12px 32px
|
||||||
|
cursor: pointer
|
||||||
|
|
||||||
|
#watched-videos-display:checked ~ .video-list-item--watched
|
||||||
|
display: none
|
||||||
|
|
||||||
|
.video-list-item--watched
|
||||||
|
background: c.$bg-darker
|
||||||
|
padding: 8px 8px 0px
|
||||||
|
|
||||||
|
.thumbnail .image
|
||||||
|
opacity: 0.4
|
||||||
|
@ -57,6 +57,22 @@ class User {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getWatchedVideos() {
|
||||||
|
const settings = this.getSettingsOrDefaults()
|
||||||
|
if (this.token && settings.save_history) {
|
||||||
|
return db.prepare("SELECT videoID FROM WatchedVideos WHERE token = ?").pluck().all(this.token)
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addWatchedVideoMaybe(videoID) {
|
||||||
|
const settings = this.getSettingsOrDefaults()
|
||||||
|
if (videoID && this.token && settings.save_history) {
|
||||||
|
db.prepare("INSERT OR IGNORE INTO WatchedVideos (token, videoID) VALUES (?, ?)").run([this.token, videoID])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,6 +28,11 @@ const deltas = [
|
|||||||
function() {
|
function() {
|
||||||
db.prepare("ALTER TABLE Settings ADD COLUMN local INTEGER DEFAULT 0")
|
db.prepare("ALTER TABLE Settings ADD COLUMN local INTEGER DEFAULT 0")
|
||||||
.run()
|
.run()
|
||||||
|
},
|
||||||
|
// 3: +WatchedVideos
|
||||||
|
function() {
|
||||||
|
db.prepare("CREATE TABLE WatchedVideos (token TEXT NOT NULL, videoID TEXT NOT NULL, PRIMARY KEY (token, videoID))")
|
||||||
|
.run()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user