aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video
diff options
context:
space:
mode:
authorRigel Kent <sendmemail@rigelk.eu>2021-02-02 12:59:41 +0100
committerChocobozzz <chocobozzz@cpy.re>2021-02-04 09:04:47 +0100
commit3d4e112d16471703f51a542c0cc6e73a6f5db628 (patch)
treecb4a53a50f9bc14a87b62ccfa9d398feb4bbcbc8 /server/models/video
parentf6267b610145033ee26ca8a4a7c2b97eca65072e (diff)
downloadPeerTube-3d4e112d16471703f51a542c0cc6e73a6f5db628.tar.gz
PeerTube-3d4e112d16471703f51a542c0cc6e73a6f5db628.tar.zst
PeerTube-3d4e112d16471703f51a542c0cc6e73a6f5db628.zip
add best trending strategy based on Reddit's best
inspired from https://www.reddit.com/r/changelog/comments/7spgg0/best_is_the_new_hotness/ this implementation only adds freshness, and doesn't personalize based on subscribed communities yet.
Diffstat (limited to 'server/models/video')
-rw-r--r--server/models/video/video-query-builder.ts33
-rw-r--r--server/models/video/video.ts6
2 files changed, 26 insertions, 13 deletions
diff --git a/server/models/video/video-query-builder.ts b/server/models/video/video-query-builder.ts
index e2145fb9a..822d0c89b 100644
--- a/server/models/video/video-query-builder.ts
+++ b/server/models/video/video-query-builder.ts
@@ -31,8 +31,8 @@ export type BuildVideosQueryOptions = {
31 31
32 videoPlaylistId?: number 32 videoPlaylistId?: number
33 33
34 trendingAlgorithm?: string // best, hot, or any other algorithm implemented
34 trendingDays?: number 35 trendingDays?: number
35 hot?: boolean
36 36
37 user?: MUserAccountId 37 user?: MUserAccountId
38 historyOfUser?: MUserId 38 historyOfUser?: MUserId
@@ -252,7 +252,7 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions)
252 attributes.push('COALESCE(SUM("videoView"."views"), 0) AS "score"') 252 attributes.push('COALESCE(SUM("videoView"."views"), 0) AS "score"')
253 253
254 group = 'GROUP BY "video"."id"' 254 group = 'GROUP BY "video"."id"'
255 } else if (options.hot) { 255 } else if ([ 'best', 'hot' ].includes(options.trendingAlgorithm)) {
256 /** 256 /**
257 * "Hotness" is a measure based on absolute view/comment/like/dislike numbers, 257 * "Hotness" is a measure based on absolute view/comment/like/dislike numbers,
258 * with fixed weights only applied to their log values. 258 * with fixed weights only applied to their log values.
@@ -269,28 +269,39 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions)
269 */ 269 */
270 const weights = { 270 const weights = {
271 like: 3, 271 like: 3,
272 dislike: 3, 272 dislike: -3,
273 view: 1 / 12, 273 view: 1 / 12,
274 comment: 2 // a comment takes more time than a like to do, but can be done multiple times 274 comment: 2, // a comment takes more time than a like to do, but can be done multiple times
275 history: -2
275 } 276 }
276 277
277 joins.push('LEFT JOIN "videoComment" ON "video"."id" = "videoComment"."videoId"') 278 joins.push('LEFT JOIN "videoComment" ON "video"."id" = "videoComment"."videoId"')
278 279
279 attributes.push( 280 let attribute =
280 `LOG(GREATEST(1, "video"."likes" - 1)) * ${weights.like} ` + // likes (+) 281 `LOG(GREATEST(1, "video"."likes" - 1)) * ${weights.like} ` + // likes (+)
281 `- LOG(GREATEST(1, "video"."dislikes" - 1)) * ${weights.dislike} ` + // dislikes (-) 282 `+ LOG(GREATEST(1, "video"."dislikes" - 1)) * ${weights.dislike} ` + // dislikes (-)
282 `+ LOG("video"."views" + 1) * ${weights.view} ` + // views (+) 283 `+ LOG("video"."views" + 1) * ${weights.view} ` + // views (+)
283 `+ LOG(GREATEST(1, COUNT(DISTINCT "videoComment"."id"))) * ${weights.comment} ` + // comments (+) 284 `+ LOG(GREATEST(1, COUNT(DISTINCT "videoComment"."id"))) * ${weights.comment} ` + // comments (+)
284 '+ (SELECT EXTRACT(epoch FROM "video"."publishedAt") / 47000) ' + // base score (in number of half-days) 285 '+ (SELECT EXTRACT(epoch FROM "video"."publishedAt") / 47000) ' // base score (in number of half-days)
285 'AS "score"' 286
286 ) 287 if (options.trendingAlgorithm === 'best' && options.user) {
288 joins.push(
289 'LEFT JOIN "userVideoHistory" ON "video"."id" = "userVideoHistory"."videoId" AND "userVideoHistory"."userId" = :bestUser'
290 )
291 replacements.bestUser = options.user.id
292
293 attribute += `+ POWER(COUNT(DISTINCT "userVideoHistory"."id"), 2.0) * ${weights.history} `
294 }
295
296 attribute += 'AS "score"'
297 attributes.push(attribute)
287 298
288 group = 'GROUP BY "video"."id"' 299 group = 'GROUP BY "video"."id"'
289 } 300 }
290 } 301 }
291 302
292 if (options.historyOfUser) { 303 if (options.historyOfUser) {
293 joins.push('INNER JOIN "userVideoHistory" on "video"."id" = "userVideoHistory"."videoId"') 304 joins.push('INNER JOIN "userVideoHistory" ON "video"."id" = "userVideoHistory"."videoId"')
294 305
295 and.push('"userVideoHistory"."userId" = :historyOfUser') 306 and.push('"userVideoHistory"."userId" = :historyOfUser')
296 replacements.historyOfUser = options.historyOfUser.id 307 replacements.historyOfUser = options.historyOfUser.id
@@ -410,7 +421,7 @@ function buildOrder (value: string) {
410 421
411 if (field.toLowerCase() === 'random') return 'ORDER BY RANDOM()' 422 if (field.toLowerCase() === 'random') return 'ORDER BY RANDOM()'
412 423
413 if ([ 'trending', 'hot' ].includes(field.toLowerCase())) { // Sort by aggregation 424 if ([ 'trending', 'hot', 'best' ].includes(field.toLowerCase())) { // Sort by aggregation
414 return `ORDER BY "score" ${direction}, "video"."views" ${direction}` 425 return `ORDER BY "score" ${direction}, "video"."views" ${direction}`
415 } 426 }
416 427
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index ea6c9d44b..0ecb8d600 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1090,7 +1090,9 @@ 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 let trendingAlgorithm
1094 if (options.sort.endsWith('hot')) trendingAlgorithm = 'hot'
1095 if (options.sort.endsWith('best')) trendingAlgorithm = 'best'
1094 1096
1095 const serverActor = await getServerActor() 1097 const serverActor = await getServerActor()
1096 1098
@@ -1120,7 +1122,7 @@ export class VideoModel extends Model {
1120 user: options.user, 1122 user: options.user,
1121 historyOfUser: options.historyOfUser, 1123 historyOfUser: options.historyOfUser,
1122 trendingDays, 1124 trendingDays,
1123 hot, 1125 trendingAlgorithm,
1124 search: options.search 1126 search: options.search
1125 } 1127 }
1126 1128