aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorRigel Kent <sendmemail@rigelk.eu>2021-01-22 00:12:44 +0100
committerChocobozzz <chocobozzz@cpy.re>2021-01-28 15:55:34 +0100
commit5bcbcbe338ef5a1ed14f084311d013fbb25dabcf (patch)
treeb0f6382b30b67f1f7adddaf7d12af9adae0c9f5d /server
parent7a4994873c0b3394d04e16e877fc7418bc8b146a (diff)
downloadPeerTube-5bcbcbe338ef5a1ed14f084311d013fbb25dabcf.tar.gz
PeerTube-5bcbcbe338ef5a1ed14f084311d013fbb25dabcf.tar.zst
PeerTube-5bcbcbe338ef5a1ed14f084311d013fbb25dabcf.zip
modularize abstract video list header and implement video hotness recommendation variant
Diffstat (limited to 'server')
-rw-r--r--server/initializers/constants.ts2
-rw-r--r--server/middlewares/sort.ts2
-rw-r--r--server/models/video/video-query-builder.ts42
-rw-r--r--server/models/video/video.ts2
4 files changed, 42 insertions, 6 deletions
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 84a515857..89491708e 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -72,7 +72,7 @@ const SORTABLE_COLUMNS = {
72 FOLLOWERS: [ 'createdAt', 'state', 'score' ], 72 FOLLOWERS: [ 'createdAt', 'state', 'score' ],
73 FOLLOWING: [ 'createdAt', 'redundancyAllowed', 'state' ], 73 FOLLOWING: [ 'createdAt', 'redundancyAllowed', 'state' ],
74 74
75 VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'trending' ], 75 VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'trending', 'hot' ],
76 76
77 // Don't forget to update peertube-search-index with the same values 77 // Don't forget to update peertube-search-index with the same values
78 VIDEOS_SEARCH: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'match' ], 78 VIDEOS_SEARCH: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'match' ],
diff --git a/server/middlewares/sort.ts b/server/middlewares/sort.ts
index 609046a46..0600ccd15 100644
--- a/server/middlewares/sort.ts
+++ b/server/middlewares/sort.ts
@@ -16,7 +16,7 @@ function setBlacklistSort (req: express.Request, res: express.Response, next: ex
16 // Set model we want to sort onto 16 // Set model we want to sort onto
17 if (req.query.sort === '-createdAt' || req.query.sort === 'createdAt' || 17 if (req.query.sort === '-createdAt' || req.query.sort === 'createdAt' ||
18 req.query.sort === '-id' || req.query.sort === 'id') { 18 req.query.sort === '-id' || req.query.sort === 'id') {
19 // If we want to sort onto the BlacklistedVideos relation, we won't specify it in the query parameter ... 19 // If we want to sort onto the BlacklistedVideos relation, we won't specify it in the query parameter...
20 newSort.sortModel = undefined 20 newSort.sortModel = undefined
21 } else { 21 } else {
22 newSort.sortModel = 'Video' 22 newSort.sortModel = 'Video'
diff --git a/server/models/video/video-query-builder.ts b/server/models/video/video-query-builder.ts
index 9e5b6febb..65b72fe1c 100644
--- a/server/models/video/video-query-builder.ts
+++ b/server/models/video/video-query-builder.ts
@@ -32,6 +32,8 @@ export type BuildVideosQueryOptions = {
32 videoPlaylistId?: number 32 videoPlaylistId?: number
33 33
34 trendingDays?: number 34 trendingDays?: number
35 hot?: boolean
36
35 user?: MUserAccountId 37 user?: MUserAccountId
36 historyOfUser?: MUserId 38 historyOfUser?: MUserId
37 39
@@ -239,14 +241,46 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions)
239 } 241 }
240 } 242 }
241 243
242 // We don't exclude results in this if so if we do a count we don't need to add this complex clauses 244 // We don't exclude results in this so if we do a count we don't need to add this complex clause
243 if (options.trendingDays && options.isCount !== true) { 245 if (options.trendingDays && options.isCount !== true) {
244 const viewsGteDate = new Date(new Date().getTime() - (24 * 3600 * 1000) * options.trendingDays) 246 const viewsGteDate = new Date(new Date().getTime() - (24 * 3600 * 1000) * options.trendingDays)
245 247
246 joins.push('LEFT JOIN "videoView" ON "video"."id" = "videoView"."videoId" AND "videoView"."startDate" >= :viewsGteDate') 248 joins.push('LEFT JOIN "videoView" ON "video"."id" = "videoView"."videoId" AND "videoView"."startDate" >= :viewsGteDate')
247 replacements.viewsGteDate = viewsGteDate 249 replacements.viewsGteDate = viewsGteDate
248 250
249 attributes.push('COALESCE(SUM("videoView"."views"), 0) AS "videoViewsSum"') 251 attributes.push('COALESCE(SUM("videoView"."views"), 0) AS "score"')
252
253 group = 'GROUP BY "video"."id"'
254 } else if (options.hot && options.isCount !== true) {
255 /**
256 * "Hotness" is a measure based on absolute view/comment/like/dislike numbers,
257 * with fixed weights only applied to their log values.
258 *
259 * This algorithm gives little chance for an old video to have a good score,
260 * for which recent spikes in interactions could be a sign of "hotness" and
261 * justify a better score. However there are multiple ways to achieve that
262 * goal, which is left for later. Yes, this is a TODO :)
263 *
264 * note: weights and base score are in number of half-days.
265 * see https://github.com/reddit-archive/reddit/blob/master/r2/r2/lib/db/_sorts.pyx#L47-L58
266 */
267 const weights = {
268 like: 3,
269 dislike: 3,
270 view: 1 / 12,
271 comment: 6
272 }
273
274 joins.push('LEFT JOIN "videoComment" ON "video"."id" = "videoComment"."videoId"')
275
276 attributes.push(
277 `LOG(GREATEST(1, "video"."likes" - 1)) * ${weights.like} ` + // likes (+)
278 `- LOG(GREATEST(1, "video"."dislikes" - 1)) * ${weights.dislike} ` + // dislikes (-)
279 `+ LOG("video"."views" + 1) * ${weights.view} ` + // views (+)
280 `+ LOG(GREATEST(1, COUNT(DISTINCT "videoComment"."id") - 1)) * ${weights.comment} ` + // comments (+)
281 '+ (SELECT EXTRACT(epoch FROM "video"."publishedAt") / 47000) ' + // base score (in number of half-days)
282 'AS "score"'
283 )
250 284
251 group = 'GROUP BY "video"."id"' 285 group = 'GROUP BY "video"."id"'
252 } 286 }
@@ -372,8 +406,8 @@ function buildOrder (value: string) {
372 406
373 if (field.toLowerCase() === 'random') return 'ORDER BY RANDOM()' 407 if (field.toLowerCase() === 'random') return 'ORDER BY RANDOM()'
374 408
375 if (field.toLowerCase() === 'trending') { // Sort by aggregation 409 if ([ 'trending', 'hot' ].includes(field.toLowerCase())) { // Sort by aggregation
376 return `ORDER BY "videoViewsSum" ${direction}, "video"."views" ${direction}` 410 return `ORDER BY "score" ${direction}, "video"."views" ${direction}`
377 } 411 }
378 412
379 let firstSort: string 413 let firstSort: string
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 9b0aa809e..c56fbfbf2 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1090,6 +1090,7 @@ export class VideoModel extends Model {
1090 const trendingDays = options.sort.endsWith('trending') 1090 const trendingDays = options.sort.endsWith('trending')
1091 ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS 1091 ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
1092 : undefined 1092 : undefined
1093 const hot = options.sort.endsWith('hot')
1093 1094
1094 const serverActor = await getServerActor() 1095 const serverActor = await getServerActor()
1095 1096
@@ -1119,6 +1120,7 @@ export class VideoModel extends Model {
1119 user: options.user, 1120 user: options.user,
1120 historyOfUser: options.historyOfUser, 1121 historyOfUser: options.historyOfUser,
1121 trendingDays, 1122 trendingDays,
1123 hot,
1122 search: options.search 1124 search: options.search
1123 } 1125 }
1124 1126