aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
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
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')
-rw-r--r--server/initializers/constants.ts2
-rw-r--r--server/models/video/video-query-builder.ts33
-rw-r--r--server/models/video/video.ts6
-rw-r--r--server/tests/api/check-params/config.ts2
-rw-r--r--server/tests/api/server/config.ts2
-rw-r--r--server/tests/api/videos/single-server.ts8
6 files changed, 37 insertions, 16 deletions
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 0fab872a9..9d9b3966c 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', 'hot' ], 75 VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'trending', 'hot', 'best' ],
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/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
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts
index e6309b5f7..d6c20f7af 100644
--- a/server/tests/api/check-params/config.ts
+++ b/server/tests/api/check-params/config.ts
@@ -141,7 +141,7 @@ describe('Test config API validators', function () {
141 trending: { 141 trending: {
142 videos: { 142 videos: {
143 algorithms: { 143 algorithms: {
144 enabled: [ 'hot', 'most-viewed', 'most-liked' ], 144 enabled: [ 'best', 'hot', 'most-viewed', 'most-liked' ],
145 default: 'most-viewed' 145 default: 'most-viewed'
146 } 146 }
147 } 147 }
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts
index e5bab0b77..26df8373e 100644
--- a/server/tests/api/server/config.ts
+++ b/server/tests/api/server/config.ts
@@ -375,7 +375,7 @@ describe('Test config', function () {
375 trending: { 375 trending: {
376 videos: { 376 videos: {
377 algorithms: { 377 algorithms: {
378 enabled: [ 'hot', 'most-viewed', 'most-liked' ], 378 enabled: [ 'best', 'hot', 'most-viewed', 'most-liked' ],
379 default: 'hot' 379 default: 'hot'
380 } 380 }
381 } 381 }
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts
index 52c6800c1..da90223b8 100644
--- a/server/tests/api/videos/single-server.ts
+++ b/server/tests/api/videos/single-server.ts
@@ -363,6 +363,14 @@ describe('Test a single server', function () {
363 expect(videos.length).to.equal(2) 363 expect(videos.length).to.equal(2)
364 }) 364 })
365 365
366 it('Should list and sort by best in descending order', async function () {
367 const res = await getVideosListPagination(server.url, 0, 2, '-best')
368
369 const videos = res.body.data
370 expect(res.body.total).to.equal(6)
371 expect(videos.length).to.equal(2)
372 })
373
366 it('Should update a video', async function () { 374 it('Should update a video', async function () {
367 const attributes = { 375 const attributes = {
368 name: 'my super video updated', 376 name: 'my super video updated',