"""广告复核队列的判据回归。 样本全部来自 2026-08-15 的真实误判与用户手工标记的真广告:命中推广词本身 不是证据,要看剥掉推广词后还剩不剩内容。 """ import sqlite3 import tempfile import unittest from contextlib import closing from pathlib import Path from unittest import mock from peach import junk, web_batch from peach.web_contract import ( BUNDLE_DIR_ASSETS, CONTENT_BYTES, PART_MARK, PROMO_CLUSTER_FILES, PROMO_DOMAIN, PROMO_PHRASE, REAL_CODE, WebContract, has_sibling_original, promo_residue, q_ads, q_items, w_batch, ) from support.ledger import fresh_ledger def promo_hit(name: str) -> bool: return bool(PROMO_PHRASE.search(name) or PROMO_DOMAIN.search(name)) class AdJudgementTests(unittest.TestCase): #: 用户手工标记为待删的真广告,名字剥完应当基本不剩内容。 REAL_ADS = ( "点击观看 房间火爆", "国产大片", "女神在线视频www.55h.me", "澳门皇冠赌场~vip0955.com", "tuu26.com", "极欲鬼滅之刃366gm.com", "苍老师强力推荐", "全中文AV在线观看【9se.tv】", "【91狼友之家91home.cc】4", ) #: 真实内容,只是被打了站点水印或剧情里出现了推广同形词。 REAL_CONTENT = ( "1248 - #饭饭吖 NTR剧情_给单男口交深喉,被母狗式爆操,还要微信跟老公汇报战果", "236953.xyz 推特新晋4年绿帽美腿淫妻网黄「一个ren」《榨精美足》" "谁能在这美妙的裸足压榨下保持不败", "No.017 - 서서 펠라_뒤치기【02분 34초】", ) def test_real_ads_have_nothing_left_after_stripping_promo(self): for name in self.REAL_ADS: with self.subTest(name=name): self.assertTrue(promo_hit(name), "应命中推广词或域名") self.assertLess(promo_residue(name), 14, "广告名剥完不应还剩大段实质描述") def test_watermarked_content_keeps_its_description(self): """名字里有域名/推广同形词,但正文是真实描述,必须留下足够残留。""" for name in self.REAL_CONTENT: with self.subTest(name=name): self.assertGreaterEqual( promo_residue(name), 14, "真实内容剥完仍应剩下可观描述,否则会被当成广告") def test_plot_word_is_not_a_contact_method(self): """“微信跟老公汇报战果”是剧情;“加微信”“微信号”才是联系方式。""" self.assertIsNone(PROMO_PHRASE.search("还要微信跟老公汇报战果")) self.assertIsNotNone(PROMO_PHRASE.search("加微信看完整版")) self.assertIsNotNone(PROMO_PHRASE.search("微信号:abc123")) def test_domain_is_detected_when_followed_by_a_word_character(self): """`uuc82.com_2` 用 \\b 收尾会漏:m 与 _ 都是词字符,构不成边界。""" self.assertIsNotNone(PROMO_DOMAIN.search("妹妹在精彩表演 哥哥快来大饱眼福uuc82.com_2")) self.assertIsNotNone(PROMO_DOMAIN.search("星空天使ccc.18my.cc")) self.assertIsNone(PROMO_DOMAIN.search("普通标题 没有站点")) def test_only_real_codes_drive_the_longer_version_rule(self): """RAIKUN325 是被误填进 code 的创作者账号,不能拿来比时长。""" self.assertIsNone(REAL_CODE.match("RAIKUN325")) self.assertIsNotNone(REAL_CODE.match("ABW-153")) self.assertIsNotNone(REAL_CODE.match("259LUXU-1141")) self.assertIsNotNone(REAL_CODE.match("FC2-PPV-1234567")) def test_disposal_marks_mix_two_different_intents(self): """`disposal` 同时承载“这是广告”和“这个我不喜欢”,不能当纯广告标注集。 用户 2026-08-15 澄清:`1726897607_*` 那批 10 个时间戳命名的长录像被标删, 是因为不喜欢该来源,与广告无关。判据不该、也不可能识别出这类。 """ for name in ("1726897607_1726897607_20250831-214903", "1726897607_1726897607_20250823-215124"): with self.subTest(name=name): self.assertFalse(promo_hit(name), "时间戳文件名不含推广特征") def test_part_markers_still_protect_split_volumes(self): for name in ("Movie CD2", "feature part1", "作品 分卷", "title-2", "title (3)"): with self.subTest(name=name): self.assertIsNotNone(PART_MARK.search(name)) def test_game_interstitial_padding_does_not_count_as_content(self): """手游插页的残留全是固定套话,2026-09-03 之前它们把分数压到门槛以下。 `WAAA-415-uncensored-HD` 目录里这批图命中「扫码」,可剥完剩下的是 「免费18禁手游」「安装」——一个内容字都没有,却让残留数越算越高。 """ for name in ("爱姬远征-免费18禁手游-扫码安装", "天下布魔-免费18禁手游-扫码安装", "工口.R18-成人遊戲-免费18禁手游-扫码安装", "工口MH-扫描访问", "QR CODE--扫一扫"): with self.subTest(name=name): self.assertTrue(promo_hit(name), "应命中推广词") self.assertLess(promo_residue(name), 6, "剥完只剩游戏名,不该算作实质描述") def test_filler_words_alone_are_not_evidence(self): """「免费」「手游」「安装」只降低残留,自己不构成命中。 真片名里也有这些词(`免费` 尤其常见),拿它们当命中依据就等于按题材删片。 """ for name in ("免费的午后 上门安装师傅", "手游主播的私拍"): with self.subTest(name=name): self.assertFalse(promo_hit(name)) def test_shot_tail_only_strips_the_thumbnail_suffix(self): """`MIAD573_02_s` 要退回 `MIAD573_02`,不能退到 `MIAD573`:那是另一条正片。""" self.assertTrue(has_sibling_original("MIAD573_02_s", {"miad573_02"})) self.assertTrue(has_sibling_original("BNST-033-cover", {"bnst-033"})) self.assertFalse(has_sibling_original("MIAD573_02_s", {"miad573"})) self.assertFalse(has_sibling_original("别的广告图", {"miad573_02"})) def test_poster_suffix_pairs_with_the_disc_it_belongs_to(self): """`pl`/`ps` 是 JAV 海报的通用写法,`lxvs006pl` 要配上 `LXVS006-BD`。 配对时剥掉的载体尾巴(`-BD`)在索引一侧完成,这里只验证海报尾巴。 """ self.assertTrue(has_sibling_original("lxvs006pl", {"lxvs006"})) self.assertTrue(has_sibling_original("lxvs006ps", {"lxvs006"})) self.assertFalse(has_sibling_original("lxvs006pl", {"cawd241"})) def test_content_threshold_is_far_above_any_promo_image(self): """插页整包加起来不到 10 MB;判据只要不把这类体积算成内容就够。""" self.assertGreaterEqual(CONTENT_BYTES, 512 * 1024**2) class ResourceJunkQueueTests(unittest.TestCase): """物理资源都要经过垃圾判断,类型不能成为免检条件。""" def setUp(self): self.temporary = tempfile.TemporaryDirectory() self.addCleanup(self.temporary.cleanup) self.db_path = fresh_ledger(Path(self.temporary.name).resolve()) self.contract = WebContract(self.db_path) def add(self, asset_id, location, path, medium, size=1000, duration=None, creator=None, disposal=None, code=None): connection = sqlite3.connect(self.db_path) try: connection.execute( "INSERT INTO asset(id,location,path,name,medium,size,duration,creator,disposal,code) " "VALUES(?,?,?,?,?,?,?,?,?,?)", (asset_id, location, path, path.rsplit("\\", 1)[-1], medium, size, duration, creator, disposal, code), ) connection.commit() finally: connection.close() def test_automatic_isolation_requires_a_scope_and_observes_source_and_directory_boundaries(self): for aid, location, folder in ((1, 'local', 'media'), (2, 'local', 'media2'), (3, '115', 'media')): self.add(aid, location, rf'R:\{folder}\点击进入.url', 'other') with self.assertRaises(ValueError): junk.isolate(self.db_path) self.assertEqual(junk.isolate(self.db_path, asset_ids=[])['isolated'], 0) self.assertEqual(junk.isolate(self.db_path, roots=[])['isolated'], 0) checked = junk.isolate(self.db_path, roots=[('local', r'r:\MEDIA')]) self.assertEqual(checked['ids'], [1]) self.assertEqual(junk.isolate(self.db_path, roots=[('local', r'R:\media')])['isolated'], 0) with self.contract.read_connection() as connection: self.assertEqual([tuple(row) for row in connection.execute( 'SELECT id,disposal,disposal_reason FROM asset ORDER BY id')], [(1, 'trash', 'junk'), (2, None, None), (3, None, None)]) def test_automatic_isolation_preserves_user_choices_records_subtitles_and_main_identity(self): for aid in range(1, 8): self.add(aid, 'local', rf'R:\media\{aid}\点击进入.url', 'other') self.add(8, 'local', r'R:\media\123.com-ABC-123.mp4', 'video', 2000, 5) with self.contract.database.write_transaction() as connection: connection.execute("UPDATE asset SET feedback='like' WHERE id=1") connection.execute('UPDATE asset SET play_count=1 WHERE id=2') connection.execute("INSERT INTO watch_queue(profile_id,asset_id,added_at) VALUES('local-default',3,'now')") connection.execute("INSERT INTO asset_subtitle(asset_id,location,path,name,format,pairing,first_seen,last_seen) " "VALUES(4,'local','R:\\media\\sub.srt','sub.srt','srt','exact','now','now')") w_batch(self.contract, {'ids': [5], 'operation': 'dismiss-junk'}) checked = junk.isolate(self.contract.database, asset_ids=list(range(1, 9)), protected_ids=[6]) self.assertEqual(checked['ids'], [7]) w_batch(self.contract, {'ids': [7], 'operation': 'restore'}) w_batch(self.contract, {'ids': [7], 'operation': 'dismiss-junk'}) self.assertEqual(junk.isolate(self.db_path, asset_ids=[7])['isolated'], 0) def test_isolation_rechecks_evidence_after_scoring(self): self.add(1, 'local', r'R:\media\点击进入.url', 'other') original = junk.scored_candidates def score_then_change(*args, **options): result = original(*args, **options) with self.contract.database.write_transaction() as connection: connection.execute("UPDATE asset SET name='notes.txt' WHERE id=1") return result with mock.patch.object(junk, 'scored_candidates', side_effect=score_then_change): self.assertEqual(junk.isolate(self.db_path, asset_ids=[1])['isolated'], 0) def test_unmeasured_and_failed_videos_await_review(self): for aid, duration in enumerate((None, 0, -1), 1): self.add(aid, 'local', rf'R:\media\{aid}\点击进入.mp4', 'video', 1000, duration) result = junk.isolate(self.db_path, asset_ids=[1, 2, 3]) self.assertEqual((result['isolated'], result['review']), (0, 3)) def test_short_attachment_structure_stays_in_review_during_automatic_processing(self): self.release_with_shorts(r'B:\发行物\正片标题') checked = junk.isolate(self.db_path, roots=[('115', r'B:\发行物')]) self.assertEqual((checked['isolated'], checked['review']), (0, 3)) def test_video_image_audio_archive_and_url_shortcut_are_all_judged(self): self.add(1, "115", r"B:\广告\tuu26.com.mp4", "video", 10 * 1024**2, 60) self.add(2, "115", r"B:\番号\CAWD-241\亚博体育hayob9.com.jpg", "image", 28725) self.add(3, "pikpak", r"A:\资源\点击这里.url", "other", 128) self.add(4, "local", r"R:\media\福利群vip0955.com.zip", "archive", 2048) self.add(5, "local", r"R:\media\苍老师强力推荐.mp3", "audio", 4096) # 域名属于作品出处且仍有长内容描述,不是推广垃圾。 self.add( 6, "115", r"B:\创作者\Dakota Doll - [LegalPorno.com] - [2024] - Breakfast Sex.mp4.jpg", "image", 280000, creator="Dakota Doll", ) # online 的 path 本来就是 URL,不是等待清理的物理快捷方式。 self.add(7, "online", "https://example.com/post/1.url", "other", 0) result = q_ads(self.contract, limit=200) self.assertEqual(result["total"], 5) self.assertEqual( {item["medium"] for item in result["items"]}, {"video", "image", "audio", "archive", "other"}, ) by_id = {item["id"]: item for item in result["items"]} self.assertIn("网址快捷方式", by_id[3]["why"]) self.assertEqual(by_id[3]["junk_kind"], "url") self.assertIn("整个名字都是推广语", by_id[2]["why"]) self.assertNotIn(6, by_id) self.assertNotIn(7, by_id) self.assertTrue(all("path" not in item for item in result["items"])) def test_game_download_promo_name_enters_review_without_automatic_trash(self): name = "18+游戏大全(996gg.cc)-七龍珠H版-三國志H版-三國群淫傳等.mp4" self.add(80, "115", "B:\\云下载\\MNGS-071\\" + name, "video", 2001226, 11.378005) self.add(81, "115", "B:\\云下载\\ROYD-358\\" + name, "video", 2001226) self.add(82, "115", "B:\\游戏录像\\" + name, "video", 5 * 1024**2, 11) self.add(83, "115", "B:\\游戏录像\\七龍珠H版游戏实况.mp4", "video", 2001226, 11) self.add(84, "115", "B:\\完整录像\\" + name, "video", 2001226, 3600) candidates = {row["id"]: row for row in q_ads(self.contract)["items"]} self.assertEqual(set(candidates), {80, 81}) for row in candidates.values(): self.assertIn("游戏下载站推广视频", row["why"]) self.assertGreaterEqual(row["score"], web_batch.JUNK_REVIEW_SCORE) self.assertLess(row["score"], web_batch.AUTO_TRASH_SCORE) self.assertEqual(junk.isolate(self.db_path, asset_ids=list(candidates))['isolated'], 0) def release_with_shorts(self, base, first_id=300, names=None, **short_values): self.add(first_id, "115", base + "\\" + base.rsplit("\\", 1)[-1] + ".mp4", "video", 1103584330, 3264.96) for offset, name in enumerate(names or ("附件甲", "附件乙", "附件丙"), 1): values = dict(size=24880490, duration=19.319002) values.update(short_values) self.add(first_id + offset, "115", base + "\\" + name + ".mp4", "video", **values) def test_short_attachment_cluster_is_reviewed_without_promo_words(self): self.release_with_shorts(r"B:\发行物\正片标题", names=( "海贼王3D版 成人欲海狂潮 黄 游", "附件甲", "附件乙", "附件丙", "附件丁", "附件戊")) items = q_ads(self.contract)["items"] self.assertEqual({row["id"] for row in items}, set(range(301, 307))) self.assertTrue(all("6 条极短小附件" in row["why"] for row in items)) self.assertTrue(all(web_batch.JUNK_REVIEW_SCORE <= row["score"] < web_batch.AUTO_TRASH_SCORE for row in items)) self.assertEqual(junk.isolate(self.db_path, asset_ids=[row['id'] for row in items])['isolated'], 0) def test_a_pair_of_shorts_and_standalone_animation_loops_stay_out(self): self.release_with_shorts(r"B:\发行物\正片标题", names=("附件甲", "附件乙")) for asset_id in range(310, 316): self.add(asset_id, "115", rf"B:\画师作品集\动画{asset_id}.mp4", "video", 24880490, 19.319002) self.assertEqual(q_ads(self.contract)["items"], []) def test_short_attachment_identity_and_measurements_must_be_eligible(self): cases = ( ({"duration": None}, None), ({"duration": 0}, None), ({"duration": -1}, None), ({"duration": 31}, None), ({"size": 0}, None), ({"size": 33 * 1024**2}, None), ({"creator": "示例画师"}, None), ({}, ("作品 CD1", "作品 part2", "作品 分卷")), ({}, ("ABW-153", "ABW-154", "ABW-155")), ) for index, (values, names) in enumerate(cases): self.release_with_shorts(rf"B:\发行物\正片{index}", 400 + index * 10, names=names, **values) self.assertEqual(q_ads(self.contract)["items"], []) def test_short_attachments_can_have_inherited_directory_codes(self): self.release_with_shorts(r"B:\发行物\ABW-153", code="ABW-153") self.assertEqual({row["id"] for row in q_ads(self.contract)["items"]}, {301, 302, 303}) def test_mixed_folder_or_inactive_main_cannot_support_attachment_evidence(self): for index, main_values in enumerate(( {"path": r"B:\发行物\正片0\别的标题.mp4"}, {"disposal": "trash"}, {"location": "online"}, {"duration": 1100}, {"size": 40 * 1024**2})): main_id = 500 + index * 10 self.release_with_shorts(rf"B:\发行物\正片{index}", main_id) with closing(sqlite3.connect(self.db_path)) as connection: for column, value in main_values.items(): connection.execute(f"UPDATE asset SET {column}=? WHERE id=?", (value, main_id)) connection.commit() self.assertEqual(q_ads(self.contract)["items"], []) def test_cross_release_spread_uses_measurements_with_different_names(self): for index in range(3): self.release_with_shorts(rf"B:\发行物\正片{index}", 600 + index * 10, names=(f"无推广字样的名字{index}",)) with closing(sqlite3.connect(self.db_path)) as connection: connection.execute("UPDATE asset SET width=1920,height=1080") connection.commit() items = q_ads(self.contract)["items"] self.assertEqual({row["id"] for row in items}, {601, 611, 621}) self.assertTrue(all("3 个发行目录" in row["why"] for row in items)) self.assertEqual(junk.isolate(self.db_path, asset_ids=[row['id'] for row in items])['isolated'], 0) def test_cross_release_spread_requires_full_matching_measurements(self): for index in range(3): self.release_with_shorts(rf"B:\发行物\正片{index}", 600 + index * 10, names=("附件",)) self.assertEqual(q_ads(self.contract)["items"], []) with closing(sqlite3.connect(self.db_path)) as connection: connection.execute("UPDATE asset SET width=1920,height=1080") connection.execute("UPDATE asset SET duration=20 WHERE id=621") connection.commit() self.assertEqual(q_ads(self.contract)["items"], []) def test_dismissed_short_attachment_can_be_reconsidered(self): self.release_with_shorts(r"B:\发行物\正片标题") w_batch(self.contract, {"ids": [301], "operation": "dismiss-junk"}) self.assertEqual({row["id"] for row in q_ads(self.contract)["items"]}, {302, 303}) w_batch(self.contract, {"ids": [301], "operation": "reconsider-junk"}) self.assertEqual({row["id"] for row in q_ads(self.contract)["items"]}, {301, 302, 303}) def test_domain_watermarked_release_volumes_are_not_junk(self): names = ("www.98T.la@FC2-1529680-CD2.mp4", "www.example.com_ABW-153-part1.mp4", "FC2-PPV-1234567-2.mp4", "ABW-153 (3).mp4") for asset_id, name in enumerate(names, 700): self.add(asset_id, "115", "B:\\发行物\\" + name, "video", 10 * 1024**2, 400) self.assertEqual(q_ads(self.contract)["items"], []) self.assertEqual(junk.isolate(self.db_path, asset_ids=range(700, 704))['isolated'], 0) def test_numbered_source_watermarks_only_enter_review(self): for asset_id, name in enumerate(("【来源站example.com】1", "[example.com]84 (2)"), 710): self.add(asset_id, "115", "B:\\短片\\" + name + ".mp4", "video", 5 * 1024**2, 10) items = q_ads(self.contract)["items"] self.assertEqual({row["id"] for row in items}, {710, 711}) self.assertTrue(all(row["score"] < web_batch.AUTO_TRASH_SCORE for row in items)) self.assertEqual(junk.isolate(self.db_path, asset_ids=[row['id'] for row in items])['isolated'], 0) def test_non_video_trash_items_remain_visible_and_recoverable(self): self.add(10, "115", r"B:\番号\广告图.jpg", "image", disposal="trash") self.add(11, "115", r"B:\番号\备用网址.url", "other", disposal="trash") self.add(12, "115", r"B:\番号\正片.mp4", "video", 1024, 60) result = q_items(self.contract, { "state": "trash", "limit": "60", "thumb": "1", "exclude_vertical": "1", "dur_min": "3600", "jav": "1", }) self.assertEqual(result["total"], 2) self.assertEqual( {(item["id"], item["medium"]) for item in result["items"]}, {(10, "image"), (11, "other")}, ) def test_nfo_and_large_same_code_clips_never_enter_the_junk_queue(self): """NFO 是资料边车;达到 120 MB 的同番号短版本归重复清理,不归垃圾。""" self.add(13, "local", r"R:\番号\点击观看.nfo", "other", 4096) self.add(14, "local", r"R:\番号\FC2-PPV-1110408.mp4", "video", 4 * 1024**3, 50 * 60, code="FC2-PPV-1110408") self.add(15, "local", r"R:\番号\FC2-PPV-1110408-2-4K修复.mp4", "video", 488 * 1024**2, 3 * 60, code="FC2-PPV-1110408") self.assertEqual(q_ads(self.contract, limit=200)["items"], []) def test_mib_archives_can_be_excluded_and_reconsidered_per_asset(self): """域名文件名可能是真资源;“不是垃圾”必须可持久排除且能撤销。""" self.add(20, "115", r"B:\\MIB\\Mib19.com.zip", "archive", 14 * 1024**3) self.add(21, "115", r"B:\\MIB\\Mib19.com(2).zip", "archive", 15 * 1024**3) self.add(22, "115", r"B:\\广告\\hayob9.com.jpg", "image", 28000) pending = q_ads(self.contract, limit=200) self.assertEqual(pending["counts"]["archive"], 2) self.assertEqual([item["id"] for item in q_ads( self.contract, limit=200, kind="archive")["items"]], [21, 20]) changed = w_batch(self.contract, {"ids": [20], "operation": "dismiss-junk"}) self.assertEqual(changed["changed"], 1) self.assertEqual(q_ads(self.contract, limit=200)["pending_total"], 2) dismissed = q_ads(self.contract, limit=200, status="dismissed") self.assertEqual([item["id"] for item in dismissed["items"]], [20]) self.assertEqual(dismissed["counts"]["archive"], 1) w_batch(self.contract, {"ids": [20], "operation": "reconsider-junk"}) self.assertEqual(q_ads(self.contract, limit=200)["pending_total"], 3) def test_one_scoring_serves_every_kind_status_and_page(self): """打分与类型、状态、分页无关:算一次,各种切法共用;一页里的条目改不到下一页。""" self.add(30, "115", r"B:\\广告\\hayob9.com.jpg", "image", 28000) self.add(31, "115", r"B:\\MIB\\Mib19.com.zip", "archive", 14 * 1024**3) with mock.patch.object(web_batch, "_scored_junk", wraps=web_batch._scored_junk) as scored: first = q_ads(self.contract, limit=1) archives = q_ads(self.contract, limit=200, kind="archive") dismissed = q_ads(self.contract, limit=200, status="dismissed") self.assertEqual(scored.call_count, 1) self.assertEqual(first["total"], 2) self.assertEqual([item["id"] for item in archives["items"]], [31]) self.assertEqual(dismissed["items"], []) first["items"][0]["why"] = ["被改过"] self.assertNotEqual(q_ads(self.contract, limit=1)["items"][0]["why"], ["被改过"]) self.assertEqual(q_ads( self.contract, limit=200, status="dismissed")["items"], []) def test_game_interstitials_in_a_release_directory_all_enter_the_queue(self): """`WAAA-415-uncensored-HD` 那批插页此前一张都没到门槛,只有正片自己入队。""" release = r"B:\番号\Wanz Factory\WAAA-415\WAAA-415-uncensored-HD" titles = ("爱姬远征", "天下布魔", "奇迹少女", "星隕計畫", "樱境物语", "欲神幻想", "腥空幻想", "工口.R18-成人遊戲", "三国志侵略版") for offset, title in enumerate(titles): self.add(30 + offset, "115", rf"{release}\{title}-免费18禁手游-扫码安装.jpg", "image", 1024**2) self.add(50, "115", rf"{release}\工口MH-扫描访问.png", "image", 966 * 1024) # 正片本体:体积与时长都在候选范围外,进不了队列,只作为目录里的邻居存在。 self.add(51, "115", rf"{release}\WAAA-415-UNCENSORED.mp4", "video", 4 * 1024**3, 8888) result = q_ads(self.contract, limit=200) self.assertEqual(result["total"], 10) self.assertNotIn(51, {item["id"] for item in result["items"]}) by_id = {item["id"]: item for item in result["items"]} # 游戏名有六个字,单看名字只够中间档;同目录另外九个同类推广名补齐证据。 self.assertIn("同目录另有 9 个同类推广名", by_id[38]["why"]) self.assertIn("整个名字都是推广语", by_id[30]["why"]) def test_bundled_photo_set_is_a_resource_not_an_ad(self): """域名+序号是打包渠道给整包起的名;几十上百张成套的图是资源。""" gallery = r"A:\创作者\森萝财团 整合用\森萝财团 X-019 肉丝换白丝 [103P1V-1.39GB]" for offset in range(BUNDLE_DIR_ASSETS): self.add(60 + offset, "local", rf"{gallery}\jitumi.pw({offset}).gif", "image", 5 * 1024**2) # 同样的名字只有一两张挤在别人的番号目录里,那就是插页。 self.add(90, "115", r"B:\番号\CAWD-241\jitumi.pw(1).gif", "image", 5 * 1024**2) result = q_ads(self.contract, limit=200) self.assertEqual([item["id"] for item in result["items"]], [90]) def test_cover_and_screenshot_outlive_their_promo_directory(self): """目录名带域名只说明从哪个站下的,说明不了目录里的图是广告。""" # 正片本体的体积在候选范围外,这里只作为截图的对照存在。 self.add(70, "115", r"B:\云下载\Jav.li_MIAD573_HD\MIAD573_02.wmv", "video", 800 * 1024**2, 3600, creator="Jav.li_MIAD573_HD") self.add(71, "115", r"B:\云下载\Jav.li_MIAD573_HD\MIAD573_02_s.jpg", "image", 40000, creator="Jav.li_MIAD573_HD") self.add(72, "115", r"B:\创作者\BNST033\aaxv.xyz-BNST033\BNST-033(1).jpg", "image", 40000, code="BNST-033") # 同一个广告包目录里,与番号无关的推广图仍然是广告。 self.add(73, "115", r"B:\创作者\BNST033\aaxv.xyz-BNST033\点击进入.jpg", "image", 40000) result = q_ads(self.contract, limit=200) self.assertEqual([item["id"] for item in result["items"]], [73]) def test_bluray_disc_image_and_its_poster_are_not_ads(self): """19.8 GB 的原盘和它的海报都是资源;同目录那个推广 rar 仍然是广告。 样本是 `B:\\云下载\\javme.me_LXVS-006-BD` 的实际三项。原盘在账本里是 `other`、没有 code、也没有时长,光看名字和目录只剩「域名+番号」这一条证据。 """ pack = r"B:\云下载\javme.me_LXVS-006-BD" self.add(91, "115", rf"{pack}\LXVS006-BD.iso", "other", 19778371584) self.add(92, "115", rf"{pack}\lxvs006pl.jpg", "image", 134351) self.add(93, "115", rf"{pack}\九色腾免费高清五码自拍在线观看不卡。.rar", "archive", 1362754) result = q_ads(self.contract, limit=200) self.assertEqual([item["id"] for item in result["items"]], [93]) def test_promo_cluster_needs_more_than_a_couple_of_neighbours(self): """两张同类插页不构成成群;`PROMO_CLUSTER_FILES` 是这条判据的下限。""" self.assertGreaterEqual(PROMO_CLUSTER_FILES, 3) release = r"B:\番号\Faleno\FSDSS-258\FSDSS-258.HD" for offset in range(PROMO_CLUSTER_FILES - 1): self.add(80 + offset, "115", rf"{release}\逆王传说 - 入侵女兒國{offset}-免费18禁手游-扫码安装.jpg", "image", 1024**2) result = q_ads(self.contract, limit=200) self.assertEqual(result["items"], []) def test_ad_flooded_folder_keeps_video_and_its_cover_only(self): """用户实例 `B:\\xxr\\0208 (23)`:整包只有正片和它的同名 PNG 是资源。 26 条广告各有各的自曝形态:装饰符小图、品牌推广卡、`.mhtml` 网页存档、 目录名自曝(`-APP`、`一键约炮`、`論壇文宣`)里的推广图。 """ base = r"B:\xxr\0208 (23)" self.add(100, "115", rf"{base}\梓怡-背著老公.mp4", "video", 985 * 1024**2, 1647) self.add(101, "115", rf"{base}\梓怡-背著老公.png", "image", 2379 * 1024) self.add(102, "115", rf"{base}\❤91短视频❤.jpg", "image", 600 * 1024) self.add(103, "115", rf"{base}\全国外围-一键约炮.jpg", "image", 606 * 1024) self.add(104, "115", rf"{base}\❤成人游戏-导航-.mhtml", "other", 6615 * 1024) self.add(105, "115", rf"{base}\_含羞草APP\❤含 羞 草❤.png", "image", 8 * 1024) self.add(106, "115", rf"{base}\論壇文宣\(_0高清国产精品资源获取.png", "image", 18 * 1024) result = q_ads(self.contract, limit=200) self.assertEqual( {item["id"] for item in result["items"]}, {102, 103, 104, 105, 106}, ) by_id = {item["id"]: item for item in result["items"]} self.assertIn("装饰符小图", by_id[102]["why"]) self.assertIn("推广品牌名", by_id[103]["why"]) self.assertIn("网页存档", by_id[104]["why"]) def test_brand_word_in_a_video_title_is_not_promo_by_itself(self): """`麻豆传媒` 一类品牌词也可以是正片标题的一部分,视频单独命中不算证据。""" self.add(110, "115", r"B:\番号\麻豆传媒 背著老公的妻子.mp4", "video", 400 * 1024**2, 900) result = q_ads(self.contract, limit=200) self.assertEqual(result["items"], []) def test_invalid_junk_filters_are_rejected(self): with self.assertRaisesRegex(ValueError, "invalid junk kind"): q_ads(self.contract, kind="document") with self.assertRaisesRegex(ValueError, "invalid junk status"): q_ads(self.contract, status="deleted") def test_small_installer_archive_name_enters_review_with_release_code(self): path = r'B:\MVP\Tokyo-Hot n1025-FHD\1024核工厂.rar' self.add(211, '115', path, 'archive', 4111) with closing(sqlite3.connect(self.db_path)) as db: db.execute('UPDATE asset SET code=? WHERE id=?', ('n1025', 211)) db.commit() items = q_ads(self.contract, limit=200)['items'] self.assertEqual([item['id'] for item in items], [211]) self.assertIn('推广安装包', items[0]['why']) self.assertEqual(junk._promo_name_reason('.rar', path, 128 * 1024), '') self.assertEqual(junk._promo_name_reason('.rar', r'B:\MVP\n1025.rar', 4111), '') def test_promotional_page_navigation_images_with_inherited_release_code(self): base = r'B:\MVP\1pon-092415_001-fhd' page = base + r'\花一分钟记住我们的网址 - sex5.xxx_files' self.add(201, '115', page + r'\banner_b.jpg', 'image', 312739) self.add(202, '115', base + r'\img\001.jpg', 'image', 46234) self.add(203, '115', page + r'\001.jpg', 'image', 46234) self.add(204, '115', base + r'\movie_files\logo.gif', 'image', 3439) with closing(sqlite3.connect(self.db_path)) as db: db.execute('UPDATE asset SET code=? WHERE id IN (201,202,203,204)', ('092415-001',)) db.commit() items = q_ads(self.contract, limit=200)['items'] self.assertEqual([item['id'] for item in items], [201]) self.assertIn('导航图片', items[0]['why']) self.assertEqual(junk._attachment_junk_reason('.gif', page + r'\1.gif', 974), '推广网页存档的横幅、标志或导航图片') self.assertEqual(junk._attachment_junk_reason('.gif', page + r'\1.gif', 6000), '') self.assertTrue(junk._attachment_junk_reason('.gif', page + r'\1000x90yunding.gif', 97843)) def add_download(self, task_id, code, folder, main): with self.contract.database.write_transaction() as connection: connection.execute( "INSERT INTO download_task(id,provider,source_uri,target,code,state,ledger_path,asset_id,updated_at) " "VALUES(?,'pikpak','magnet:?xt=urn:btih:fixture',?,?,'ingested',?,?,'now')", (task_id, f'115-{task_id}', code, folder, main)) def test_download_package_attachments_are_isolated_while_release_files_stay(self): # 样本取自 2026-11-03 的只读盘点:整理把正片移走后,推广附件留在落地目录。 self.add(1, '115', r'B:\S1\SSIS-839\SSIS-839 标题.mp4', 'video', 5 * 1024**3, 7200) self.add_download(1, 'SSIS-839', r'B:\云下载\SSIS-839', 1) base = r'B:\云下载\SSIS-839' + '\\' self.add(2, '115', base + '人间尤物.mp4', 'video', 78893257, 61) self.add(3, '115', base + '好站全在这.txt', 'other', 470) self.add(4, '115', base + r'其他作品合集\kpkp3.com_259LUXU-1555.jpg', 'image', 258032) self.add(5, '115', base + 'hjd2048.com-0907ssis839pl.jpg', 'image', 176840) self.add(6, '115', base + 'poster.jpg', 'image', 180000) self.add(7, '115', base + '回家指南综合包.zip', 'archive', 3165927) self.add(8, '115', base + '特典映像.mp4', 'video', 90 * 1024**2, 600) self.add(9, '115', base + '未探测.mp4', 'video', 4650760) # 落地目录就是下载根时,不能把根下所有文件都当成这个种子的附件。 self.add(10, '115', r'B:\云下载\notes.txt', 'other', 470) self.add_download(2, 'ABP-616', r'B:\云下载', 1) items = {item['id']: item for item in q_ads(self.contract, limit=200)['items']} self.assertEqual(set(items), {2, 3, 4, 7}) self.assertIn('SSIS-839 下载包里的附带短视频,正片长 120 分', items[2]['why']) self.assertIn('附带压缩包', items[7]['why']) self.assertLess(items[7]['score'], web_batch.AUTO_TRASH_SCORE) checked = junk.isolate(self.contract.database, asset_ids=list(range(1, 11)), protected_ids=[1]) self.assertEqual(sorted(checked['ids']), [2, 3, 4]) def test_untracked_folders_under_the_download_root_use_their_own_release_video(self): # 下载根由已有任务确定;没有任务行的旧下载按目录名里的番号认正片。 self.add(1, '115', r'B:\S1\SSIS-839\SSIS-839.mp4', 'video', 5 * 1024**3, 7200) self.add_download(1, 'SSIS-839', r'B:\云下载\SSIS-839', 1) base = r'B:\云下载\第一會所新片@SIS001@DDH-454' + '\\' self.add(10, '115', base + 'DDH-454.mp4', 'video', 3271300936, 4221.9) self.add(11, '115', base + '[资源推荐]!下载地址.txt', 'other', 154647) self.add(12, '115', base + '推特新晋约啪大神原创约炮自拍.jpg', 'image', 1410048) self.add(13, '115', base + 'cap_e_3_ddh-454.jpg', 'image', 275947) # 目录名带番号却找不到同番号正片的不算下载包;下载根以外的目录也不算。 self.add(20, '115', r'B:\云下载\ABC-123 合集\片段.mp4', 'video', 3 * 1024**3, 7200) self.add(21, '115', r'B:\云下载\ABC-123 合集\说明.txt', 'other', 400) self.add(30, '115', r'B:\日本\DDH-455\DDH-455.mp4', 'video', 3 * 1024**3, 4000) self.add(31, '115', r'B:\日本\DDH-455\推特新晋约啪大神原创约炮自拍.jpg', 'image', 1410048) items = {item['id']: item for item in q_ads(self.contract, limit=200)['items']} self.assertEqual(set(items), {11, 12}) self.assertIn('DDH-454 下载包里的附带文件', items[12]['why']) checked = junk.isolate(self.contract.database, asset_ids=[10, 11, 12, 13, 20, 21, 30, 31]) self.assertEqual(sorted(checked['ids']), [11, 12]) def test_download_attachment_rules_need_a_bound_main_and_a_long_enough_release(self): self.add(1, '115', r'B:\云下载\IPX-867\IPX-867.mp4', 'video', 300 * 1024**2, 1200) self.add_download(1, 'IPX-867', r'B:\云下载\IPX-867', 1) self.add(2, '115', r'B:\云下载\IPX-867\片头.mp4', 'video', 20 * 1024**2, 90) self.add(3, '115', r'B:\云下载\SONE-136\好站全在这.txt', 'other', 470) with self.contract.database.write_transaction() as connection: connection.execute( "INSERT INTO download_task(id,provider,source_uri,target,code,state,ledger_path,updated_at) " "VALUES(2,'pikpak','magnet:?xt=urn:btih:unbound','115-2','SONE-136','ingested',?,'now')", (r'B:\云下载\SONE-136',)) self.assertEqual(junk.isolate(self.contract.database, asset_ids=[1, 2, 3], protected_ids=[1])['ids'], []) def test_release_names_match_compact_padded_and_store_prefixed_spellings(self): for stem, code in (('hjd2048.com-0907mide580-h264', 'MIDE-580'), ('tkmj001jp', 'TKMJ-001'), ('hhd800.com@FC2-PPV-2753986_2', 'FC2-PPV-2753986'), ('STZY-019', '107STZY-019')): with self.subTest(stem=stem): self.assertTrue(junk.names_release(stem, code)) for stem, code in (('offkab@sukebei', 'FC2-PPV-4330669'), ('kpkp3.com_259LUXU-1555', 'SGKX-016'), ('MIDE5800', 'MIDE-580')): with self.subTest(stem=stem): self.assertFalse(junk.names_release(stem, code)) def test_identical_small_copies_across_release_folders_are_junk_evidence(self): for offset, folder in enumerate(('MIDA-117-UC', 'MIDA-154-UC', 'MIDA-234-UC')): self.add(20 + offset, '115', rf'B:\云下载\{folder}\情報.mp4', 'video', 11057812, 157.28) self.add(30 + offset, '115', rf'B:\云下载\{folder}\说明.txt', 'other', 136) self.add(40 + offset, '115', rf'B:\云下载\{folder}\ABC-123.jpg', 'image', 52000) self.add(50, '115', r'B:\云下载\MIDA-117-UC\片段.mp4', 'video', 2001226, 11) self.add(51, '115', r'B:\云下载\MIDA-154-UC\片段.mp4', 'video', 2001226, 11) self.add(52, '115', r'B:\云下载\MIDA-234-UC\片段.mp4', 'video', 2001226, 3600) # 创作者目录里每期都带的同一段片头不算:目录名不是发行包。 for offset, folder in enumerate(('第一期', '第二期', '第三期')): self.add(70 + offset, '115', rf'B:\创作者\{folder}\片头.mp4', 'video', 3000000, 8) items = {item['id']: item for item in q_ads(self.contract, limit=200)['items']} self.assertEqual(set(items), {20, 21, 22, 30, 31, 32}) self.assertIn('同名同体积的副本散布在 3 个目录', items[20]['why']) self.assertGreaterEqual(items[20]['score'], web_batch.AUTO_TRASH_SCORE) self.assertLess(items[30]['score'], web_batch.AUTO_TRASH_SCORE) def test_letter_spaced_promo_names_are_read_as_one_word(self): self.assertEqual(junk.despaced('x u u 6 2 . c o m'), 'xuu62.com') self.assertEqual(junk.despaced('最 新 位 址 獲 取'), '最新位址獲取') self.assertEqual(junk.despaced('No.017 - 서서 펠라_뒤치기'), 'No.017 - 서서 펠라_뒤치기') self.add(60, '115', r'B:\资源\x u u 9 2 .c o m.mp4', 'video', 23141112, 88.7) self.add(61, '115', r'B:\资源\最 新 位 址 獲 取:489155.com 收藏不迷路.txt', 'other', 136) self.add(62, '115', r'B:\资源\manko.fun.mp4', 'video', 11057812, 157.28) items = {item['id']: item for item in q_ads(self.contract, limit=200)['items']} self.assertEqual(set(items), {60, 61, 62}) self.assertTrue(all(item['score'] >= web_batch.AUTO_TRASH_SCORE for item in items.values())) if __name__ == "__main__": unittest.main()