From e0bc0d2e8109e08793f65cd54fc111c354214cf9 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Tue, 29 Dec 2020 01:42:25 +1300 Subject: [PATCH] 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. --- api/subscriptions.js | 9 +++++-- api/video.js | 15 ++++++++++- pug/channel.pug | 3 +-- pug/includes/video-list-item.pug | 39 ++++++++++++++------------- pug/search.pug | 3 +-- pug/settings.pug | 6 ++--- pug/subscriptions.pug | 8 ++++-- pug/video.pug | 3 +-- sass/includes/base.sass | 1 - sass/includes/subscriptions-page.sass | 32 ++++++++++++++++++++++ utils/getuser.js | 16 +++++++++++ utils/upgradedb.js | 5 ++++ 12 files changed, 106 insertions(+), 34 deletions(-) diff --git a/api/subscriptions.js b/api/subscriptions.js index d199728..a7b4294 100644 --- a/api/subscriptions.js +++ b/api/subscriptions.js @@ -22,6 +22,8 @@ module.exports = [ channels = db.prepare(`SELECT * FROM Channels WHERE ucid IN (${template}) ORDER BY name`).all(subscriptions) // 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) + // get watched videos + const watchedVideos = user.getWatchedVideos() // get videos if (subscriptions.length) { 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) .map(video => { video.publishedText = timeToPastText(video.published * 1000) + console.log(watchedVideos, video.videoId) + video.watched = watchedVideos.includes(video.videoId) return video }) } } - const instanceOrigin = user.getSettingsOrDefaults().instance - return render(200, "pug/subscriptions.pug", {hasSubscriptions, videos, channels, refreshed, timeToPastText, instanceOrigin}) + const settings = user.getSettingsOrDefaults() + const instanceOrigin = settings.instance + return render(200, "pug/subscriptions.pug", {settings, hasSubscriptions, videos, channels, refreshed, timeToPastText, instanceOrigin}) } } ] diff --git a/api/video.js b/api/video.js index 4748fc1..3bb004d 100644 --- a/api/video.js +++ b/api/video.js @@ -37,22 +37,35 @@ function formatOrder(format) { async function renderVideo(videoPromise, {user, id, instanceOrigin}) { try { + // resolve video const video = await videoPromise if (!video) throw new Error("The instance returned null.") 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)) { if (!format.second__height && format.resolution) format.second__height = +format.resolution.slice(0, -1) if (!format.second__order) format.second__order = formatOrder(format) } + // process length text for (const rec of video.recommendedVideos) { if (!rec.second__lengthText && rec.lengthSeconds > 0) { rec.second__lengthText = converters.lengthSecondsToLengthText(rec.lengthSeconds) } } + // get subscription data 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}) } 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()}) if (e instanceof fetch.FetchError) { const template = ` diff --git a/pug/channel.pug b/pug/channel.pug index a82f297..0ab4c34 100644 --- a/pug/channel.pug +++ b/pug/channel.pug @@ -28,5 +28,4 @@ block content .videos each video in data.latestVideos - .channel-video - +video_list_item(video, instanceOrigin) + +video_list_item("channel-video", video, instanceOrigin) diff --git a/pug/includes/video-list-item.pug b/pug/includes/video-list-item.pug index 58e5f31..2d1343a 100644 --- a/pug/includes/video-list-item.pug +++ b/pug/includes/video-list-item.pug @@ -1,19 +1,20 @@ -mixin video_list_item(video, instanceOrigin) - - let link = `/watch?v=${video.videoId}` - a(href=link tabindex="-1").thumbnail - img(src=`/vi/${video.videoId}/mqdefault.jpg` width=320 height=180 alt="").image - if video.second__lengthText != undefined - span.duration= video.second__lengthText - .info - div.title: a(href=link).title-link= video.title - div.author-line - a(href=`/channel/${video.authorId}`).author= video.author - - const views = video.viewCountText || video.second__viewCountText - if views - = ` • ` - span.views= views - if video.publishedText - = ` • ` - span.published= video.publishedText - if video.descriptionHtml - div.description!= video.descriptionHtml +mixin video_list_item(className, video, instanceOrigin) + div(class={[className]: true, "video-list-item--watched": video.watched}) + - let link = `/watch?v=${video.videoId}` + a(href=link tabindex="-1").thumbnail + img(src=`/vi/${video.videoId}/mqdefault.jpg` width=320 height=180 alt="").image + if video.second__lengthText != undefined + span.duration= video.second__lengthText + .info + div.title: a(href=link).title-link= video.title + div.author-line + a(href=`/channel/${video.authorId}`).author= video.author + - const views = video.viewCountText || video.second__viewCountText + if views + = ` • ` + span.views= views + if video.publishedText + = ` • ` + span.published= video.publishedText + if video.descriptionHtml + div.description!= video.descriptionHtml diff --git a/pug/search.pug b/pug/search.pug index 159e91f..cbfa60a 100644 --- a/pug/search.pug +++ b/pug/search.pug @@ -8,5 +8,4 @@ block head block content main.search-page each result in results - .search-result - +video_list_item(result, instanceOrigin) + +video_list_item("search-result", result, instanceOrigin) diff --git a/pug/settings.pug b/pug/settings.pug index 1f09ca4..adec6c8 100644 --- a/pug/settings.pug +++ b/pug/settings.pug @@ -37,9 +37,9 @@ block content "https://invidious.fdn.fr" ]) - +select("save_history", "Watch history", false, [ - {value: "0", text: "Don't save"}, - {value: "1", text: "Save"} + +select("save_history", "Watched videos history", false, [ + {value: "0", text: "Don't store"}, + {value: "1", text: "Store on server"} ]) +select("local", "Fetch videos", false, [ diff --git a/pug/subscriptions.pug b/pug/subscriptions.pug index 5b65e7b..a7a5fdd 100644 --- a/pug/subscriptions.pug +++ b/pug/subscriptions.pug @@ -27,9 +27,13 @@ block content if notLoaded 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 - .subscriptions-video - +video_list_item(video, instanceOrigin) + +video_list_item("subscriptions-video", video, instanceOrigin) else .no-subscriptions h2 You have no subscriptions. diff --git a/pug/video.pug b/pug/video.pug index f6e47fb..74a9049 100644 --- a/pug/video.pug +++ b/pug/video.pug @@ -66,8 +66,7 @@ block content aside.related-videos h2.related-header Related videos each r in video.recommendedVideos - .related-video - +video_list_item(r, instanceOrigin) + +video_list_item("related-video", r, instanceOrigin) else //- error diff --git a/sass/includes/base.sass b/sass/includes/base.sass index 81febd1..9845ecc 100644 --- a/sass/includes/base.sass +++ b/sass/includes/base.sass @@ -54,7 +54,6 @@ details border-radius: 8px summary - text-decoration: underline cursor: pointer line-height: 1 margin-bottom: 0 diff --git a/sass/includes/subscriptions-page.sass b/sass/includes/subscriptions-page.sass index 40d10a9..2947aab 100644 --- a/sass/includes/subscriptions-page.sass +++ b/sass/includes/subscriptions-page.sass @@ -33,3 +33,35 @@ .name font-size: 22px 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 diff --git a/utils/getuser.js b/utils/getuser.js index 33778b1..f358d7d 100644 --- a/utils/getuser.js +++ b/utils/getuser.js @@ -57,6 +57,22 @@ class User { 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]) + } + } } /** diff --git a/utils/upgradedb.js b/utils/upgradedb.js index 6438831..72c256f 100644 --- a/utils/upgradedb.js +++ b/utils/upgradedb.js @@ -28,6 +28,11 @@ const deltas = [ function() { db.prepare("ALTER TABLE Settings ADD COLUMN local INTEGER DEFAULT 0") .run() + }, + // 3: +WatchedVideos + function() { + db.prepare("CREATE TABLE WatchedVideos (token TEXT NOT NULL, videoID TEXT NOT NULL, PRIMARY KEY (token, videoID))") + .run() } ]