Spaces:
Running
Running
Commit
·
29f166e
1
Parent(s):
655b911
improve AiTube algorithm
Browse files- src/app/interface/like-button/generic.tsx +2 -2
- src/app/interface/like-button/index.tsx +20 -9
- src/app/interface/top-header/index.tsx +1 -1
- src/app/interface/track-card/index.tsx +25 -7
- src/app/server/actions/ai-tube-hf/extendVideosWithStats.ts +13 -3
- src/app/server/actions/ai-tube-hf/getVideo.ts +12 -2
- src/app/server/actions/ai-tube-hf/getVideos.ts +20 -9
- src/app/server/actions/stats.ts +23 -25
- src/app/server/actions/utils/isAntisocial.ts +23 -0
- src/app/server/actions/utils/isHighQuality.ts +22 -0
- src/types.ts +1 -0
src/app/interface/like-button/generic.tsx
CHANGED
@@ -56,7 +56,7 @@ export function GenericLikeButton({
|
|
56 |
<div>{
|
57 |
isLikedByUser ? <RiThumbUpFill /> : <RiThumbUpLine />
|
58 |
}</div>
|
59 |
-
<div>{numberOfLikes}</div>
|
60 |
</div>
|
61 |
<div className={cn(
|
62 |
`flex flex-row items-center justify-center`,
|
@@ -74,7 +74,7 @@ export function GenericLikeButton({
|
|
74 |
<div>{
|
75 |
isDislikedByUser ? <RiThumbDownFill /> : <RiThumbDownLine />
|
76 |
}</div>
|
77 |
-
<div>{
|
78 |
</div>
|
79 |
</div>
|
80 |
)
|
|
|
56 |
<div>{
|
57 |
isLikedByUser ? <RiThumbUpFill /> : <RiThumbUpLine />
|
58 |
}</div>
|
59 |
+
<div>{Math.max(0, numberOfLikes)}</div>
|
60 |
</div>
|
61 |
<div className={cn(
|
62 |
`flex flex-row items-center justify-center`,
|
|
|
74 |
<div>{
|
75 |
isDislikedByUser ? <RiThumbDownFill /> : <RiThumbDownLine />
|
76 |
}</div>
|
77 |
+
<div>{Math.max(0, numberOfDislikes0}</div>
|
78 |
</div>
|
79 |
</div>
|
80 |
)
|
src/app/interface/like-button/index.tsx
CHANGED
@@ -41,31 +41,42 @@ export function LikeButton({
|
|
41 |
if (!huggingfaceApiKey) { return null }
|
42 |
|
43 |
const handleLike = huggingfaceApiKey ? () => {
|
44 |
-
// we use
|
|
|
45 |
setRating({
|
46 |
...rating,
|
47 |
isLikedByUser: true,
|
48 |
isDislikedByUser: false,
|
49 |
-
numberOfLikes: rating.numberOfLikes + 1,
|
50 |
-
numberOfDislikes: rating.numberOfDislikes - 1,
|
51 |
})
|
52 |
startTransition(async () => {
|
53 |
-
|
54 |
-
|
|
|
|
|
|
|
|
|
55 |
})
|
56 |
} : undefined
|
57 |
|
58 |
const handleDislike = huggingfaceApiKey ? () => {
|
|
|
|
|
59 |
setRating({
|
60 |
...rating,
|
61 |
isLikedByUser: false,
|
62 |
isDislikedByUser: true,
|
63 |
-
numberOfLikes: rating.numberOfLikes - 1,
|
64 |
-
numberOfDislikes: rating.numberOfDislikes + 1,
|
65 |
})
|
66 |
startTransition(async () => {
|
67 |
-
|
68 |
-
|
|
|
|
|
|
|
|
|
69 |
})
|
70 |
} : undefined
|
71 |
|
|
|
41 |
if (!huggingfaceApiKey) { return null }
|
42 |
|
43 |
const handleLike = huggingfaceApiKey ? () => {
|
44 |
+
// we use optimistic updates
|
45 |
+
const previousRating = { ...rating }
|
46 |
setRating({
|
47 |
...rating,
|
48 |
isLikedByUser: true,
|
49 |
isDislikedByUser: false,
|
50 |
+
numberOfLikes: Math.abs(Math.max(0, rating.numberOfLikes + 1)),
|
51 |
+
numberOfDislikes: Math.abs(Math.max(0, rating.numberOfDislikes - 1)),
|
52 |
})
|
53 |
startTransition(async () => {
|
54 |
+
try {
|
55 |
+
const freshRating = await rateVideo(video.id, true, huggingfaceApiKey)
|
56 |
+
// setRating(freshRating)
|
57 |
+
} catch (err) {
|
58 |
+
setRating(previousRating)
|
59 |
+
}
|
60 |
})
|
61 |
} : undefined
|
62 |
|
63 |
const handleDislike = huggingfaceApiKey ? () => {
|
64 |
+
// we use optimistic updates
|
65 |
+
const previousRating = { ...rating }
|
66 |
setRating({
|
67 |
...rating,
|
68 |
isLikedByUser: false,
|
69 |
isDislikedByUser: true,
|
70 |
+
numberOfLikes: Math.abs(Math.max(0, rating.numberOfLikes - 1)),
|
71 |
+
numberOfDislikes: Math.abs(Math.max(0, rating.numberOfDislikes + 1)),
|
72 |
})
|
73 |
startTransition(async () => {
|
74 |
+
try {
|
75 |
+
const freshRating = await rateVideo(video.id, false, huggingfaceApiKey)
|
76 |
+
// setRating(freshRating)
|
77 |
+
} catch (err) {
|
78 |
+
setRating(previousRating)
|
79 |
+
}
|
80 |
})
|
81 |
} : undefined
|
82 |
|
src/app/interface/top-header/index.tsx
CHANGED
@@ -116,7 +116,7 @@ export function TopHeader() {
|
|
116 |
</div>
|
117 |
</div>
|
118 |
{
|
119 |
-
isNormalSize ?
|
120 |
<div className={cn(
|
121 |
`hidden sm:flex flex-row space-x-3`,
|
122 |
`text-[13px] font-semibold`,
|
|
|
116 |
</div>
|
117 |
</div>
|
118 |
{
|
119 |
+
isNormalSize && view !== "public_music_videos" ?
|
120 |
<div className={cn(
|
121 |
`hidden sm:flex flex-row space-x-3`,
|
122 |
`text-[13px] font-semibold`,
|
src/app/interface/track-card/index.tsx
CHANGED
@@ -36,6 +36,7 @@ export function TrackCard({
|
|
36 |
const [shouldLoadMedia, setShouldLoadMedia] = useState(false)
|
37 |
|
38 |
const isTable = layout === "table"
|
|
|
39 |
const isCompact = layout === "vertical"
|
40 |
|
41 |
const handlePointerEnter = () => {
|
@@ -77,12 +78,14 @@ export function TrackCard({
|
|
77 |
<div
|
78 |
className={cn(
|
79 |
`w-full flex`,
|
80 |
-
isTable ? `flex-row h-
|
81 |
isCompact ? `flex-row h-24 py-1 space-x-2` :
|
82 |
`flex-col space-y-3`,
|
83 |
`bg-line-900`,
|
84 |
`cursor-pointer`,
|
85 |
-
|
|
|
|
|
86 |
className,
|
87 |
)}
|
88 |
onPointerEnter={handlePointerEnter}
|
@@ -94,7 +97,8 @@ export function TrackCard({
|
|
94 |
className={cn(
|
95 |
`flex items-center justify-center`,
|
96 |
`rounded overflow-hidden`,
|
97 |
-
isTable ? `
|
|
|
98 |
isCompact ? ` flex-col w-42 h-42` :
|
99 |
` flex-col aspect-square`
|
100 |
)}
|
@@ -172,6 +176,10 @@ export function TrackCard({
|
|
172 |
{/* TEXT BLOCK */}
|
173 |
<div className={cn(
|
174 |
`flex flex-row`,
|
|
|
|
|
|
|
|
|
175 |
isCompact ? `w-40 lg:w-44 xl:w-51` : `space-x-4`,
|
176 |
)}>
|
177 |
{
|
@@ -192,13 +200,15 @@ export function TrackCard({
|
|
192 |
roundShape
|
193 |
/>}
|
194 |
<div className={cn(
|
195 |
-
`flex
|
196 |
-
|
197 |
-
|
|
|
198 |
)}>
|
199 |
<h3 className={cn(
|
200 |
`text-zinc-100 mb-0 line-clamp-2`,
|
201 |
-
|
|
|
202 |
isCompact ? `font-medium text-2xs md:text-xs lg:text-sm mb-1.5` :
|
203 |
`font-medium text-base`
|
204 |
)}>{media.label}</h3>
|
@@ -222,6 +232,14 @@ export function TrackCard({
|
|
222 |
<div className="font-semibold scale-125">·</div>
|
223 |
<div>{formatTimeAgo(media.updatedAt)}</div>
|
224 |
</div>}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
225 |
</div>
|
226 |
</div>
|
227 |
</div>
|
|
|
36 |
const [shouldLoadMedia, setShouldLoadMedia] = useState(false)
|
37 |
|
38 |
const isTable = layout === "table"
|
39 |
+
const isMicro = layout === "micro"
|
40 |
const isCompact = layout === "vertical"
|
41 |
|
42 |
const handlePointerEnter = () => {
|
|
|
78 |
<div
|
79 |
className={cn(
|
80 |
`w-full flex`,
|
81 |
+
isTable ? `flex-row h-16 space-x-4 px-2 py-2 rounded-lg` :
|
82 |
isCompact ? `flex-row h-24 py-1 space-x-2` :
|
83 |
`flex-col space-y-3`,
|
84 |
`bg-line-900`,
|
85 |
`cursor-pointer`,
|
86 |
+
(isTable || isMicro) ? (
|
87 |
+
(index % 2) ? "bg-neutral-800/40 hover:bg-neutral-800/70" : "hover:bg-neutral-800/70"
|
88 |
+
) : "",
|
89 |
className,
|
90 |
)}
|
91 |
onPointerEnter={handlePointerEnter}
|
|
|
97 |
className={cn(
|
98 |
`flex items-center justify-center`,
|
99 |
`rounded overflow-hidden`,
|
100 |
+
isTable ? `flex-col` :
|
101 |
+
isMicro ? `flex-col` :
|
102 |
isCompact ? ` flex-col w-42 h-42` :
|
103 |
` flex-col aspect-square`
|
104 |
)}
|
|
|
176 |
{/* TEXT BLOCK */}
|
177 |
<div className={cn(
|
178 |
`flex flex-row`,
|
179 |
+
|
180 |
+
|
181 |
+
isTable ? `w-full` :
|
182 |
+
|
183 |
isCompact ? `w-40 lg:w-44 xl:w-51` : `space-x-4`,
|
184 |
)}>
|
185 |
{
|
|
|
200 |
roundShape
|
201 |
/>}
|
202 |
<div className={cn(
|
203 |
+
`flex`,
|
204 |
+
isMicro ? ` flex-col justify-center` :
|
205 |
+
isTable ? `w-full flex-col md:flex-row justify-center md:justify-start items-start md:items-center` :
|
206 |
+
isCompact ? `flex-col` : `flex-col flex-grow`
|
207 |
)}>
|
208 |
<h3 className={cn(
|
209 |
`text-zinc-100 mb-0 line-clamp-2`,
|
210 |
+
isMicro ? `font-normal text-2xs md:text-xs lg:text-sm mb-0.5` :
|
211 |
+
isTable ? `w-[30%] font-normal text-xs md:text-sm lg:text-base mb-0.5` :
|
212 |
isCompact ? `font-medium text-2xs md:text-xs lg:text-sm mb-1.5` :
|
213 |
`font-medium text-base`
|
214 |
)}>{media.label}</h3>
|
|
|
232 |
<div className="font-semibold scale-125">·</div>
|
233 |
<div>{formatTimeAgo(media.updatedAt)}</div>
|
234 |
</div>}
|
235 |
+
|
236 |
+
{/*
|
237 |
+
{isTable ? <div className={cn(
|
238 |
+
`hidden md:flex flex-row flex-grow`,
|
239 |
+
`text-zinc-100 mb-0 line-clamp-2`,
|
240 |
+
`w-[30%] font-normal text-xs md:text-sm lg:text-base mb-0.5`
|
241 |
+
)}>{media.duration}</div> : null}
|
242 |
+
*/}
|
243 |
</div>
|
244 |
</div>
|
245 |
</div>
|
src/app/server/actions/ai-tube-hf/extendVideosWithStats.ts
CHANGED
@@ -1,14 +1,24 @@
|
|
1 |
"use server"
|
2 |
|
3 |
import { VideoInfo } from "@/types"
|
4 |
-
|
|
|
5 |
|
6 |
export async function extendVideosWithStats(videos: VideoInfo[]): Promise<VideoInfo[]> {
|
7 |
|
8 |
-
const
|
9 |
|
10 |
return videos.map(v => {
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
return v
|
13 |
})
|
14 |
}
|
|
|
1 |
"use server"
|
2 |
|
3 |
import { VideoInfo } from "@/types"
|
4 |
+
|
5 |
+
import { getStatsForVideos } from "../stats"
|
6 |
|
7 |
export async function extendVideosWithStats(videos: VideoInfo[]): Promise<VideoInfo[]> {
|
8 |
|
9 |
+
const allStats = await getStatsForVideos(videos.map(v => v.id))
|
10 |
|
11 |
return videos.map(v => {
|
12 |
+
const stats = allStats[v.id] || {
|
13 |
+
numberOfViews: 0,
|
14 |
+
numberOfLikes: 0,
|
15 |
+
numberOfDislikes: 0
|
16 |
+
}
|
17 |
+
|
18 |
+
v.numberOfViews = stats.numberOfViews
|
19 |
+
v.numberOfLikes = stats.numberOfLikes
|
20 |
+
v.numberOfDislikes = stats.numberOfDislikes
|
21 |
+
|
22 |
return v
|
23 |
})
|
24 |
}
|
src/app/server/actions/ai-tube-hf/getVideo.ts
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
import { VideoInfo } from "@/types"
|
4 |
|
5 |
import { getVideoIndex } from "./getVideoIndex"
|
6 |
-
import {
|
7 |
|
8 |
export async function getVideo({
|
9 |
videoId,
|
@@ -26,7 +26,17 @@ export async function getVideo({
|
|
26 |
throw new Error(`cannot get the video, nothing found for id "${id}"`)
|
27 |
}
|
28 |
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
return video
|
32 |
} catch (err) {
|
|
|
3 |
import { VideoInfo } from "@/types"
|
4 |
|
5 |
import { getVideoIndex } from "./getVideoIndex"
|
6 |
+
import { getStatsForVideos } from "../stats"
|
7 |
|
8 |
export async function getVideo({
|
9 |
videoId,
|
|
|
26 |
throw new Error(`cannot get the video, nothing found for id "${id}"`)
|
27 |
}
|
28 |
|
29 |
+
const allStats = await getStatsForVideos([video.id])
|
30 |
+
|
31 |
+
const stats = allStats[video.id] || {
|
32 |
+
numberOfViews: 0,
|
33 |
+
numberOfLikes: 0,
|
34 |
+
numberOfDislikes: 0,
|
35 |
+
}
|
36 |
+
|
37 |
+
video.numberOfViews = stats.numberOfViews
|
38 |
+
video.numberOfLikes = stats.numberOfLikes
|
39 |
+
video.numberOfDislikes = stats.numberOfDislikes
|
40 |
|
41 |
return video
|
42 |
} catch (err) {
|
src/app/server/actions/ai-tube-hf/getVideos.ts
CHANGED
@@ -4,6 +4,8 @@ import { VideoInfo } from "@/types"
|
|
4 |
|
5 |
import { getVideoIndex } from "./getVideoIndex"
|
6 |
import { extendVideosWithStats } from "./extendVideosWithStats"
|
|
|
|
|
7 |
|
8 |
const HARD_LIMIT = 100
|
9 |
|
@@ -41,13 +43,16 @@ export async function getVideos({
|
|
41 |
renewCache: true
|
42 |
})
|
43 |
|
44 |
-
|
45 |
let allPotentiallyValidVideos = Object.values(published)
|
46 |
|
47 |
if (ignoreVideoIds.length) {
|
48 |
allPotentiallyValidVideos = allPotentiallyValidVideos.filter(video => !ignoreVideoIds.includes(video.id))
|
49 |
}
|
50 |
|
|
|
|
|
|
|
|
|
51 |
if (sortBy === "date") {
|
52 |
allPotentiallyValidVideos.sort(((a, b) => b.updatedAt.localeCompare(a.updatedAt)))
|
53 |
} else {
|
@@ -92,16 +97,22 @@ export async function getVideos({
|
|
92 |
]
|
93 |
}
|
94 |
}
|
95 |
-
|
96 |
|
|
|
|
|
97 |
// we enforce the max limit of HARD_LIMIT (eg. 100)
|
98 |
-
const
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
|
|
|
|
|
|
|
|
|
|
105 |
} catch (err) {
|
106 |
if (neverThrow) {
|
107 |
console.error("failed to get videos:", err)
|
|
|
4 |
|
5 |
import { getVideoIndex } from "./getVideoIndex"
|
6 |
import { extendVideosWithStats } from "./extendVideosWithStats"
|
7 |
+
import { isHighQuality } from "../utils/isHighQuality"
|
8 |
+
import { isAntisocial } from "../utils/isAntisocial"
|
9 |
|
10 |
const HARD_LIMIT = 100
|
11 |
|
|
|
43 |
renewCache: true
|
44 |
})
|
45 |
|
|
|
46 |
let allPotentiallyValidVideos = Object.values(published)
|
47 |
|
48 |
if (ignoreVideoIds.length) {
|
49 |
allPotentiallyValidVideos = allPotentiallyValidVideos.filter(video => !ignoreVideoIds.includes(video.id))
|
50 |
}
|
51 |
|
52 |
+
if (ignoreVideoIds.length) {
|
53 |
+
allPotentiallyValidVideos = allPotentiallyValidVideos.filter(video => !ignoreVideoIds.includes(video.id))
|
54 |
+
}
|
55 |
+
|
56 |
if (sortBy === "date") {
|
57 |
allPotentiallyValidVideos.sort(((a, b) => b.updatedAt.localeCompare(a.updatedAt)))
|
58 |
} else {
|
|
|
97 |
]
|
98 |
}
|
99 |
}
|
|
|
100 |
|
101 |
+
const sanitizedVideos = videosMatchingFilters.filter(v => !isAntisocial(v))
|
102 |
+
|
103 |
// we enforce the max limit of HARD_LIMIT (eg. 100)
|
104 |
+
const limitedNumberOfVideos = sanitizedVideos.slice(0, Math.min(HARD_LIMIT, maxVideos))
|
105 |
+
|
106 |
+
// we ask Redis for the freshest stats
|
107 |
+
const videosWithStats = await extendVideosWithStats(limitedNumberOfVideos)
|
108 |
+
|
109 |
+
const highQuality = videosWithStats.filter(v => isHighQuality(v))
|
110 |
+
const lowQuality = videosWithStats.filter(v => !isHighQuality(v))
|
111 |
+
|
112 |
+
return [
|
113 |
+
...highQuality,
|
114 |
+
...lowQuality
|
115 |
+
]
|
116 |
} catch (err) {
|
117 |
if (neverThrow) {
|
118 |
console.error("failed to get videos:", err)
|
src/app/server/actions/stats.ts
CHANGED
@@ -13,29 +13,38 @@ const redis = new Redis({
|
|
13 |
token: redisToken
|
14 |
})
|
15 |
|
16 |
-
export async function
|
17 |
if (!Array.isArray(videoIds)) {
|
18 |
return {}
|
19 |
}
|
20 |
|
21 |
try {
|
22 |
|
23 |
-
const stats: Record<string, number> = {}
|
24 |
|
25 |
-
const
|
26 |
|
27 |
for (const videoId of videoIds) {
|
28 |
-
|
29 |
-
stats
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
}
|
31 |
|
32 |
-
const
|
33 |
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
|
|
|
|
|
|
39 |
|
40 |
return stats
|
41 |
} catch (err) {
|
@@ -43,22 +52,11 @@ export async function getNumberOfViewsForVideos(videoIds: string[]): Promise<Rec
|
|
43 |
}
|
44 |
}
|
45 |
|
46 |
-
export async function getNumberOfViewsForVideo(videoId: string): Promise<number> {
|
47 |
-
try {
|
48 |
-
const key = `videos:${videoId}:stats`
|
49 |
-
|
50 |
-
const result = await redis.get<number>(key) || 0
|
51 |
-
|
52 |
-
return result
|
53 |
-
} catch (err) {
|
54 |
-
return 0
|
55 |
-
}
|
56 |
-
}
|
57 |
-
|
58 |
-
|
59 |
export async function watchVideo(videoId: string): Promise<number> {
|
60 |
if (developerMode) {
|
61 |
-
|
|
|
|
|
62 |
}
|
63 |
|
64 |
try {
|
|
|
13 |
token: redisToken
|
14 |
})
|
15 |
|
16 |
+
export async function getStatsForVideos(videoIds: string[]): Promise<Record<string, { numberOfViews: number; numberOfLikes: number; numberOfDislikes: number}>> {
|
17 |
if (!Array.isArray(videoIds)) {
|
18 |
return {}
|
19 |
}
|
20 |
|
21 |
try {
|
22 |
|
23 |
+
const stats: Record<string, { numberOfViews: number; numberOfLikes: number; numberOfDislikes: number; }> = {}
|
24 |
|
25 |
+
const listOfRedisIDs: string[] = []
|
26 |
|
27 |
for (const videoId of videoIds) {
|
28 |
+
listOfRedisIDs.push(`videos:${videoId}:stats:views`)
|
29 |
+
listOfRedisIDs.push(`videos:${videoId}:stats:likes`)
|
30 |
+
listOfRedisIDs.push(`videos:${videoId}:stats:dislikes`)
|
31 |
+
stats[videoId] = {
|
32 |
+
numberOfViews: 0,
|
33 |
+
numberOfLikes: 0,
|
34 |
+
numberOfDislikes: 0,
|
35 |
+
}
|
36 |
}
|
37 |
|
38 |
+
const listOfRedisValues = await redis.mget<number[]>(...listOfRedisIDs)
|
39 |
|
40 |
+
let v = 0
|
41 |
+
for (let i = 0; i < listOfRedisValues.length; i += 3) {
|
42 |
+
stats[videoIds[v++]] = {
|
43 |
+
numberOfViews: listOfRedisValues[i] || 0,
|
44 |
+
numberOfLikes: listOfRedisValues[i + 1] || 0,
|
45 |
+
numberOfDislikes: listOfRedisValues[i + 2] || 0
|
46 |
+
}
|
47 |
+
}
|
48 |
|
49 |
return stats
|
50 |
} catch (err) {
|
|
|
52 |
}
|
53 |
}
|
54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
export async function watchVideo(videoId: string): Promise<number> {
|
56 |
if (developerMode) {
|
57 |
+
const stats = await getStatsForVideos([videoId])
|
58 |
+
|
59 |
+
return stats[videoId].numberOfViews
|
60 |
}
|
61 |
|
62 |
try {
|
src/app/server/actions/utils/isAntisocial.ts
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { VideoInfo } from "@/types"
|
2 |
+
|
3 |
+
const winners = new Set(`${process.env.WINNERS || ""}`.toLowerCase().split(",").map(x => x.trim()).filter(x => x))
|
4 |
+
|
5 |
+
export function isAntisocial(video: VideoInfo): boolean {
|
6 |
+
|
7 |
+
// some people are reported by the community for their anti-social behavior
|
8 |
+
// this include:
|
9 |
+
// - harassing
|
10 |
+
//
|
11 |
+
// - annoying or not letting people in peace on social networks
|
12 |
+
// (keep trying to reach with multiple user accounts etc)
|
13 |
+
//
|
14 |
+
// - stealing other people content (prompt, identity, images etc)
|
15 |
+
//
|
16 |
+
// -- creating multiple/duplicate accounts in order to foil and get around AiTube bans
|
17 |
+
//
|
18 |
+
// - generating nonsense content (eg. sentences not finished, one letter titles)
|
19 |
+
//
|
20 |
+
// - duplicate many videos with little to no changes
|
21 |
+
// (TV series are of course an exception to this rule - as long as this is original content obviously)
|
22 |
+
return winners.has(video.channel.datasetUser.toLowerCase())
|
23 |
+
}
|
src/app/server/actions/utils/isHighQuality.ts
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { VideoInfo } from "@/types"
|
2 |
+
|
3 |
+
export function isHighQuality(video: VideoInfo) {
|
4 |
+
const numberOfViews = Math.abs(Math.max(0, video.numberOfViews))
|
5 |
+
const numberOfLikes = Math.abs(Math.max(0, video.numberOfLikes))
|
6 |
+
const numberOfDislikes = Math.abs(Math.max(0, video.numberOfDislikes))
|
7 |
+
|
8 |
+
|
9 |
+
// rock star videos will quickly reach high ratings
|
10 |
+
const isVeryPopular = numberOfViews > 100000 || numberOfLikes > 100000
|
11 |
+
|
12 |
+
if (isVeryPopular) { return true }
|
13 |
+
|
14 |
+
const rating = numberOfLikes - numberOfDislikes
|
15 |
+
|
16 |
+
// while the number of dislike should be enough, some content is so bad that
|
17 |
+
// people don't even take the time to watch and dislike it
|
18 |
+
// so we might add other roules
|
19 |
+
const isAppreciatedByPeople = rating > 0
|
20 |
+
|
21 |
+
return isAppreciatedByPeople
|
22 |
+
}
|
src/types.ts
CHANGED
@@ -650,6 +650,7 @@ export type MediaDisplayLayout =
|
|
650 |
| "horizontal" // will be used for a "Netflix" horizontal sliding mode
|
651 |
| "vertical" // used in the right recommendation panel
|
652 |
| "table" // used when shown in a table mode
|
|
|
653 |
|
654 |
export type VideoRating = {
|
655 |
isLikedByUser: boolean
|
|
|
650 |
| "horizontal" // will be used for a "Netflix" horizontal sliding mode
|
651 |
| "vertical" // used in the right recommendation panel
|
652 |
| "table" // used when shown in a table mode
|
653 |
+
| "micro"
|
654 |
|
655 |
export type VideoRating = {
|
656 |
isLikedByUser: boolean
|