diff --git a/bun.lockb b/bun.lockb
index 68ff44b..ff75593 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/src/router/websocket.ts b/src/router/websocket.ts
index c7e96e8..8e018f8 100644
--- a/src/router/websocket.ts
+++ b/src/router/websocket.ts
@@ -134,11 +134,11 @@ app.ws('/save', {
return sendError(ws, 'Unable to retrieve video info from YouTube. Please try again later.')
}
- const slopScore = await parseSlop(videoId, data.videoDetails.title,
+ const isSlop = await parseSlop(videoId, data.videoDetails.title,
(data.microformat.playerMicroformatRenderer.description?.simpleText || '').replaceAll('\n', '
'),
data.videoDetails.channelId)
- if (slopScore >= 4) {
+ if (isSlop) {
sendError(ws, 'Filters can always be wrong. Is the rating wrong? Email me at admin@preservetube.com', false);
return sendError(ws, 'Your download has been rejected by our slop filter.');
}
@@ -216,10 +216,10 @@ app.ws('/savechannel', {
break;
}
- const slopScore = await parseSlop(video.video_id, video.title.text,
+ const isSlop = await parseSlop(video.video_id, video.title.text,
video.description_snippet?.text || '', channelId)
- if (slopScore >= 4) {
+ if (isSlop) {
sendError(ws, 'Filters can always be wrong. Is the rating wrong? Email me at admin@preservetube.com', false);
sendError(ws, 'Your download has been rejected by our slop filter.');
continue;
diff --git a/src/utils/slop.ts b/src/utils/slop.ts
index 717064d..df2d59b 100644
--- a/src/utils/slop.ts
+++ b/src/utils/slop.ts
@@ -14,7 +14,8 @@ const channelBlacklist = [
'UCUQmW2YLzhEmMTqO0RAPlOw',
'UC4Anp9y2TU-d5blFUf7wCqw',
'UC-TaLb_bURExFdLFXkJ3Adg',
- 'UC1XgosSxwK9xtrMLsRzCbHw'
+ 'UC1XgosSxwK9xtrMLsRzCbHw',
+ 'UCInXezQxpWgrd7uPKeA6gPQ',
]
async function analyseSlop(id: string, title: string, description: string) {
@@ -25,52 +26,55 @@ async function analyseSlop(id: string, title: string, description: string) {
'Content-Type': 'application/json'
},
body: JSON.stringify({
- model: 'gemini-2.5-flash-lite',
+ model: 'deepseek/deepseek-v3.2',
messages: [
- { role: 'user', content: `Role: You are "The Slop Detector." Analyze video titles and rate them 0-5 on the "Slop Score." Slop is derivative content using popular movie/TV footage + trendy audio + basic editing.
+ { role: 'user', content: `You are "The Slop Detector." Analyze video titles and determine if the content is SLOP or NOT SLOP.
-### SLOP SCORE (0-5)
-- **0:** Original, complex, artistic. No slop signals.
-- **1:** Fan edit with unique perspective, non-obvious choices.
-- **2:** Well-made but predictable, adds nothing new.
-- **3:** Some slop signals present, minimal effort visible.
-- **4:** Textbook slop β popular character + overused/slowed song + 4K tag + "Edit."
-- **5:** Maximum slop density β every possible signal stacked.
-
-### SLOP SIGNALS
-Use these as intuition guides, not a checklist. Weight them by how many stack together.
-
-- **Emoji in title** β strong signal, especially ππ€£πππ₯Ί. Multiple emoji = very strong.
-- **Title is entirely hashtags** β near-instant slop.
-- **Unicode styled text** (ππ’π€π ππ‘π’π¬) β common in character edits.
-- **Pipe separators** (|) splitting title into the classic pattern of caption | source | song β only a signal when used to stack multiple slop elements (e.g. character | show | slowed song). A single pipe for emphasis or listing does not count.
-- **4K / [4K] tag** β almost always slop when paired with anything else.
-- **"Edit" / "OneShot" / "Morphosis"** suffix or delimiter usage (β Edit β, γEditγ).
-- **Slowed / Reverb / Slowed+Reverb / MONTAGEM** β only counts when stacked with others
-- **Known slop franchises:** This list is definitive, not illustrative. Only the following qualify: Johnny English, Mr. Bean, Breaking Bad, Peaky Blinders, American Psycho, Patrick Bateman, The Boys, Homelander, Dexter, Joe Goldberg, Squid Game, Rick Grimes, Thomas Shelby, John Wick, Kingsman. They must be explicitly named, no interpretation from your side.
-- **Song name explicitly in title** β especially if slowed/remixed.
-- **Description** β if it contains hashtag spam, or music credits it reinforces slop signals from the title.
-- **Clickbait** is its own category and does not affect the slop score.
+### DECISION RULES
+**Score SLOP if the video matches either of these categories:**
+**1. Slop Edit** β a formulaic fan/character edit. Look for a combination of these signals:
+- **Emoji in title** β strong signal, especially ππ€£πππ₯Ί. Multiple emoji = very strong. (optional)
+- **Title is entirely hashtags, or mostly hastags** β near-instant slop
+- **Hashtag spam** β title consists of a short hook followed by 3+ hashtags, or the description is dominated by hashtags. Strong signal on its own.
+- **Unicode styled text** (ππ’π€π ππ‘π’π¬) β common in character edits (optional)
+- **Pipe separators** (|) are only a signal when the segments themselves are slop elements (character name | franchise | song title). Pipes used for general formatting, listing topics, or separating unrelated phrases do not count under any circumstances.
+- **4K / [4K] tag** β strong signal when paired with anything else (optional)
+- **"Edit" / "OneShot" / "Morphosis"** as suffix or with delimiters (β Edit β, γEditγ)
+- **Slowed / Reverb / Slowed+Reverb / MONTAGEM** β only counts when stacked with other signals
+- **Known slop franchises** β this list is exhaustive, not illustrative. Only these qualify: Johnny English, Mr. Bean, Breaking Bad, Peaky Blinders, American Psycho, Patrick Bateman, The Boys, Homelander, Dexter, Joe Goldberg, Squid Game, Rick Grimes, Thomas Shelby, John Wick, Kingsman. Must be explicitly named.
+- **Song name explicitly in title** β especially if slowed or remixed
+- **Description** β hashtag spam or music credits reinforce title signals
+- **Title mirrors description exactly** β reinforces slop when combined with other signals (optional)
+**2. Raw clip** β an unedited or minimally edited fragment of a TV show or movie with no transformative content.
+**3. AI-generated compilation** β footage generated by AI tools. Score SLOP if:
+- Title or description explicitly mentions "Sora"
### EXCEPTIONS
-- Tutorials, news, commentary, politics β score 0 automatically
-- Lost/archived content β score 0 automatically
-- Fan animations/original franchise-inspired art β do not trigger slop
-- Direct movie/TV clips or scenes uploaded without transformative editing β score 4 automatically
+Always score NOT SLOP:
+- News, commentary, politics
+- Lost or archived content
+
+### NOTES
+- Emoji alone does not make something slop
+- A character reference alone does not make something slop
+- Pipes and emoji together are not sufficient for a SLOP ruling without additional corroborating signals
+- ASMR, art, cooking, and similar creator content is NOT slop even if it uses emoji or pipes
+- Do not infer or assume anything about creators, channels, or franchises not explicitly listed in the known slop franchises. If it's not on the list, it's not a signal.
+- A channel with social links, Discord, Patreon, and editor credits in the description is a legitimate creator, not a slop channel
### OUTPUT
-Valid JSON only. No other text. One sentence reasoning max, be brief. Use your judgment β if multiple signals stack logically toward slop, score accordingly. Do not assume content type beyond what title explicitly states.
+Valid JSON only. No other text. One sentence reasoning max, be brief.
-{"score": 0, "reasoning": "..."}
+{"is_slop": true/false, "reasoning": "..."}
User Title: ${title}
-User Description: ${description.slice(0,100)}` }
+User Description: ${description.slice(0,500)}` }
]
})
})).json()
- let parsedResponse: {score: number, reasoning: string}
- = { score: 0, reasoning: 'failed to parse' }
+ let parsedResponse: {is_slop: boolean, reasoning: string}
+ = { is_slop: false, reasoning: 'failed to parse' }
try {
parsedResponse = JSON.parse(llmResponse.choices[0].message.content.replace(/```json|```/g, '').trim())
@@ -82,16 +86,16 @@ User Description: ${description.slice(0,100)}` }
return parsedResponse
}
-async function parseSlop(id: string, title: string, description: string, channelId: string): Promise {
- if (channelBlacklist.includes(channelId)) return 5;
+async function parseSlop(id: string, title: string, description: string, channelId: string): Promise {
+ if (channelBlacklist.includes(channelId)) return true;
const cachedSlop = await redis.get(`slop:${id}`)
- if (cachedSlop) return parseInt(cachedSlop)
+ if (cachedSlop) return Boolean(cachedSlop)
- const { score, reasoning } = await analyseSlop(id , title, description)
- if (reasoning != 'failed to parse') await redis.set(`slop:${id}`, score, 'EX', 60 * 60 * 24 * 7)
+ const { is_slop, reasoning } = await analyseSlop(id , title, description)
+ if (reasoning != 'failed to parse') await redis.set(`slop:${id}`, is_slop.toString(), 'EX', 60 * 60 * 24 * 7)
- return score
+ return is_slop
}
export { parseSlop }
\ No newline at end of file