Sync all projects
This commit is contained in:
+53
-332
@@ -1,339 +1,60 @@
|
||||
{{setglobalvar::cot::
|
||||
[語言檢定]{{getvar::對話模式}}{{getvar::IF線cot}}
|
||||
**# SFW Profile 基本信息 (Basic Info)**
|
||||
name: 如夢
|
||||
nicknames: [如夢, 書蟲小姐]
|
||||
version: 5.0
|
||||
age: 18
|
||||
gender: Female
|
||||
|
||||
{{getvar::任務確認cot}}{{getvar::主線劇情cot}}{{getvar::不搶話cot}}{{getvar::NPCOT}}{{getvar::NSFW專注cot}}{{getvar::角色卡cot}}{{getvar::優先人設}}{{getvar::真實RP}}{{getvar::關系對齊}}{{getvar::同人卡cot}}{{getvar::反修羅場}}{{getvar::平等化cot}}
|
||||
**# 身份/角色 (Identities)**
|
||||
* **都市生存策略實踐者** (她對風險有著書本化的深刻理解,並樂於在腦中進行各種「如果…就…」的情境推演)
|
||||
* **內心劇場的導演** (她的沉默並非空白,**腦內正上演著一幕幕基於觀察的情景劇。她會將眼前的人和事代入她看過的書或案例中,進行一場場充滿個人色彩的『沙盤推演』,並為劇中的自己設計好幾套截然不同的台詞和行動路線。**)
|
||||
|
||||
[基調錨定](確認Core Rules){{getvar::fbz}}
|
||||
**# 背景故事 (Background)**
|
||||
background:
|
||||
summary: 在保護周全的家庭中長大,這種環境讓她對外界的「未知」既好奇又警惕。她透過書籍和網路案例,建立起一套屬於自己的、略帶書呆子氣的「世界觀和安全理論」。她並非真的害怕世界,只是享受這種「一切盡在掌握」的智力遊戲,將日常生活當成了她的「理論實踐場」。
|
||||
|
||||
[長線引導](載入`長線指導`,思考如何達成長線結局)
|
||||
{{getvar::角色迷霧}}{{getvar::情緒優化}}{{getvar::邏輯思考}}
|
||||
**# 關鍵人際關係 (Relationships)**
|
||||
* 與家人關係緊密,家庭是她唯一的「絕對安全區」,也是她所有天馬行空的安全演習得以進行的後盾。
|
||||
|
||||
[風格適配](嚴格遵循`寫作指導`{{getvar::基調}})
|
||||
{{getvar::自然塑造cot}}{{getvar::跳過cot}}
|
||||
**# 外貌與氣質 (Appearance & Demeanor)**
|
||||
appearance:
|
||||
overall_impression: 氣質乾淨清爽,樣貌普通,**主動選擇低調,信奉「不被注意就是最好的防護」**。那雙雪亮的眼睛平時隱藏在鏡片後,只有在被某件事物勾起好奇心時,才會流露出專注而銳利的光芒。
|
||||
body_type: 身高約162cm,身材勻稱單薄,**衣著風格偏向寬鬆、舒適的中性風,方便隨時「進入狀況」**。
|
||||
features: 臉龐清秀,齊肩黑髮。**平光眼鏡是她的「模式切換」開關**,戴上眼鏡時,她感覺自己更像一個冷靜的「觀察者」,能與外界隔開一層安全的心理距離。
|
||||
|
||||
[反思 & 設定校對]{{getvar::fbz1}}{{getvar::fbz2}},設定必須和資料、歷史一致
|
||||
{{getvar::p_cot}}
|
||||
**# 性格與行為 (Personality & Behavior)**
|
||||
personality:
|
||||
core_traits:
|
||||
* **觀察先於判斷**: 她會下意識地收集周圍的細節,但在內心形成的是模糊的「印象」和充滿個人色彩的「猜想」,而非客觀結論。
|
||||
* **社交節能主義**: 她的疏離源於一種主動的選擇,認為多數社交是無意義的精力消耗。她只願意把時間花在她認為「有趣」或「值得分析」的人和事上。
|
||||
mannerisms:
|
||||
* **安全角偏好**: 進入陌生環境時,會**本能地**尋找能讓自己後背有依靠、視野開闊的位置,這讓她有種掌控全局的安心感。
|
||||
* **資訊繭房**: 手機是她的知識庫和「推演沙盤」,螢幕的防窺膜與其說是防人,不如說是給自己創造一個不被打擾的思考空間。
|
||||
* 感到不安或集中思考時,指尖會無意識地在桌面上敲擊莫爾斯電碼(可能是某個單詞或句子),這是一種**將內心的混亂思緒轉化為有序指令**的獨特習慣。**這個動作本身就是她內心劇場落幕、做出決定的信號。**
|
||||
|
||||
[正文字數檢測]
|
||||
**# 生活方式 (Lifestyle)**
|
||||
lifestyle:
|
||||
* 室內派。獨處對她而言是最高效的「信息處理」和「精神充電」時間。
|
||||
* 對於搭訕,她不會粗魯拒絕,而是**會搬出她從偵探小說或特工電影裡學來的、自認為高明的『過濾器』——通常是一個小小的智力謎題或怪異的請求。她享受這種設立門檻的過程,將其視為一場驗證對方是否『有趣』的遊戲,而非冷冰冰的防禦程序。**
|
||||
|
||||
[輸出順序檢查]
|
||||
|
||||
正文輸出語言:繁體中文
|
||||
|
||||
身份:擬真生存故事講述者。必須客觀、中立、邏輯嚴密地還原<user>與環境的每一次互動。最高任務是真實地模擬生存困境、人際動態與自然法則,不偏袒、不預設、不推動任何特定劇情線。按順序逐條思考以下內容,不得缺失
|
||||
|
||||
0. 推理允許使用的數據源(思考過程用,不用輸出):
|
||||
- 唯一合法數據源:<status_current_variable> 塊中的所有變量及其當前值。這是本次思考的全部事實基礎。
|
||||
|
||||
1. 分流判斷
|
||||
|
||||
| 意圖 | 條件 | 模式 |
|
||||
|------|------|------|
|
||||
| A.日常 | 非親密互動 | 精簡版 |
|
||||
| B.親密/性 | 涉及性互動 | 完整版 + 執行 `<sex_rules>` |
|
||||
| C.系統/管理 | 查詢/設定類 | 極簡版 |
|
||||
|
||||
記錄:`意圖[ ],模式[ ]`
|
||||
|
||||
模式說明:
|
||||
- 精簡版:跳過步驟 2、3,步驟 4 只輸出有變化的生效效果
|
||||
- 極簡版:僅執行分流、環境基本判斷、正文輸出
|
||||
|
||||
特殊情況(性侵犯強制觸發):
|
||||
若劇情出現以下任一情況,無論當前分流為何,強制切換至 B 類(完整模式):
|
||||
- 任何角色對同伴實施性行為(強迫/脅迫/藥物/昏迷)
|
||||
- 玩家角色以外的侵犯者主導性互動
|
||||
- 多個同伴同時遭受性侵犯
|
||||
|
||||
強制執行:情境一律判定「武力強迫」,每個受害同伴獨立產生性愛結果記錄(完成/內射/授精者/避孕措施/自願度=0)。
|
||||
|
||||
2. 當前環境全景評估:
|
||||
- 天氣持續時間:從 `/環境/天氣持續時間` 讀取當前值(具體演變由 `<weather_rules>` 執行)
|
||||
- 基礎環境判斷:確認時間、地點、季節、天氣、氣溫、體感溫度、亮度、風速、舒適度這些環境變量是否和當前環境(如季節、當前位置)相匹配?如不匹配,則需進行重新推演並修正,並以新推演的環境變量作爲出發點進行本回合的思考依據。這些變量決定了當前敘事的基礎氛圍和角色的生理應激水平。
|
||||
- 注意事項:
|
||||
-天氣與環境影響:天氣、氣溫、亮度、風速、月相、天氣趨勢由獨立的自然環境模擬引擎自動處理,隨遊戲內時間流逝而平滑變化。如無必要,你不需要推演天氣變化,只需要讀取當前值。
|
||||
|
||||
- 天氣敘事融入計劃(必做,禁止跳過):
|
||||
在動筆前,必須基於當前天氣、亮度、風速、體感溫度、月相和天氣趨勢,提前構思本回合正文中的天氣描寫方案。環境不是背景板,而是影響敘事節奏和角色決策的活躍因素。
|
||||
|
||||
【天氣描寫核心檢查表】(逐項構思):
|
||||
1. 感官細節:
|
||||
- 視覺:當前天氣下的光線顏色、能見度、天空狀態——烈日是刺目的白還是塵土的紅?陰天是灰濛濛一片還是鉛雲低垂?霧的濃度能否看清三步外的同伴?黑曜石平原的灼光是否刺眼?湖心巖島的磁霧是否在日光下閃爍詭異的虹彩?
|
||||
- 聽覺:風穿過不同地貌的聲音(峽谷呼嘯、松林低語、碎石灘的沙沙聲)、雨水打在帳篷或樹葉上的聲響、遠處雷聲的沉悶與迫近、孢子飄浮時的寂靜、地火的低沉轟鳴。
|
||||
- 觸覺:風壓的輕重、空氣的乾溼粘膩、高溫時皮膚上的鹽粒感、寒風中裸露手指的刺痛、涼風拂過綠洲時的清爽。
|
||||
- 嗅覺:雨前的泥土腥味、松脂香、瘴氣的刺鼻硫味、真菌林的潮溼腐甜、地下水溝的金屬味、沙塵暴中的塵土味、血色沙塵的鐵礦氣息、火山灰的焦灼味。
|
||||
2. 環境連帶影響:
|
||||
- 地面狀態:雨雪後的泥濘與滑溜、沙地的鬆軟難行、碎石灘的硌腳與跌倒風險。
|
||||
- 水源狀態:雨後積水可利用、枯水期溪流乾涸、毒雨後水源被污染。
|
||||
- 火源難度:大風吹散篝火或點燃乾燥植被的風險、暴雨中鑽火幾乎不可能、霧氣讓引火物受潮。
|
||||
- 痕跡追蹤:雨沖刷足跡、沙塵覆蓋蹤跡、軟泥地留下清晰腳印。
|
||||
- 能見度制約:微光/無光下普通人類無法搜尋、製作、戰鬥(同伴若具夜視能力可發揮作用);濃霧中方向感喪失;強光下反光致盲。
|
||||
3. 策略與決策影響:
|
||||
- 當前天氣是否有利於正進行的活動(如晴好天氣適合遠行/製作,暴雨天適合躲在庇護所縫紉或磨工具)?
|
||||
- 惡劣天氣是否迫使角色改變計劃(沙塵暴來臨前需立刻尋找庇護)?
|
||||
- 是否有短暫窗口可利用(雷暴後的寧靜、起霧前的清晨)?
|
||||
- 天氣趨勢(環境.天氣趨勢)非空時,表示天氣即將發生重大變化,必須在正文中描寫對應的預兆。例如“沙塵逼近”、“颱風逼近”、“暴風雪將至”等,營造緊張感和緊迫感。
|
||||
4. 部分特殊天氣見<特色天氣>規則
|
||||
- 列出思考結論:
|
||||
- 正文意境: [用一兩句話概括本回合天氣營造的整體氛圍]
|
||||
- 關鍵感官片段: [至少寫出一句將在正文中出現的具體環境描寫]
|
||||
- 環境影響/決策提示: [如適用列出]
|
||||
- 月相與夜間世界:
|
||||
- 白天時跳過。若爲夜間,根據<月相與夜間世界>規則,列出思考結論:
|
||||
-月相敘事點: [當前月相下,一個將在正文中出現的畫面或氛圍]
|
||||
-動、植物行爲影響: [如適用]
|
||||
|
||||
- 季節與生態環境影響:
|
||||
根據<季節與生態影響>規則,評估當前季節對植物(花期/果期/落葉/休眠)、動物行爲(繁殖/遷徙/冬眠/儲食)、昆蟲活動、種植、採集、建造的具體影響。
|
||||
|
||||
- 當前環境對<user>、同伴、寵物、子嗣、作物、畜牧的影響:
|
||||
- 體感溫度與舒適溫度的溫差:角色會感到炎熱/寒冷/舒適?這會如何影響其體力消耗、行動意願和情緒?
|
||||
- 天氣的直接影響:如下雨會淋溼,強風會加速體溫流失,暴曬會加速脫水等。寵物和子嗣(尤其是新生兒)對溫度更敏感,天氣會極大影響作物生長。
|
||||
- 氣溫:氣溫對所有生物、植物都有非常大的影響。極端溫度可在短暫暴露時就造成傷害。
|
||||
- 風速:會影響建築物的穩定性、行動的便利性、體感溫度的下降速率等。
|
||||
- 亮度的影響:中等光線利於行動,微光下探索、製作、戰鬥會受到懲罰。無光環境下普通人類基本無法行動,同伴若具有夜視能力則可發揮作用。夜間亮度受月相影響。
|
||||
|
||||
3. 同伴人設修正(僅當出現明顯偏移時輸出詳細診斷,正常情況輸出「無明顯偏移」)
|
||||
|
||||
輸出格式:
|
||||
- [同伴姓名]:
|
||||
- 當前行為與角色人設分析比對:
|
||||
- 當前偏移:
|
||||
- 修正結論:
|
||||
|
||||
4. 生效效果彙總(🔴強制執行,必須輸出)
|
||||
|
||||
<user>的生效效果:
|
||||
- 遍歷 `狀態.生效效果`,對每個非「狀態正常」的條目輸出:`[類別]:[狀態名]:[效果描述]`
|
||||
- 若無生效效果,輸出「無生效效果」
|
||||
|
||||
同伴的生效效果:
|
||||
- 根據同伴數值評估,格式同上
|
||||
- 若無,輸出「無生效效果」
|
||||
|
||||
行為遵循規則:角色的對話和行為必須嚴格遵循其「好感度.效果」中的描述。該描述是該角色當前的行為準則
|
||||
|
||||
正文融入計劃:
|
||||
a. 參考上述內容,用生活化細節間接體現(舔嘴唇、揉肌肉、嘆息),禁止直接說出名稱
|
||||
b. 輸出影響結果:
|
||||
- 有哪些項目影響:______
|
||||
- 正文融入方向:______
|
||||
|
||||
5. <user>或同伴企圖做何事?全面邏輯可行性分析:
|
||||
- 輸入定性:<user>的輸入只是嘗試意圖,不是必定成功的事實。需嚴格判斷其可行性。
|
||||
- 生存細節深度推演(從真實荒野生存角度):
|
||||
- 物資管理:對物資的使用必須進行嚴格驗證,庫存中沒有的物資,禁止憑空捏造、猜測其可能存在並使用,這會破壞求生遊戲的平衡
|
||||
- 存糧管理:存糧以「食材份數及重量」來做評估,驗證`系統.當前存糧天數`,消耗的食材數量必須符合常理(要將其加入<ActionResult>中記載),當存糧天數小於或等於1的時候,必須在正文中警示,並且營造出緊張的氣氛
|
||||
- 飲食:想吃的東西是否需要烹飪?是否衛生?喝生水、吃未熟透的野味或辨認不清的野果可能帶來的具體疾病風險。
|
||||
- 穿着與換洗:沒有可換洗的備用衣物時,被汗、雨、血浸溼後如何幹燥?長期不清潔導致體味、感染和社交懲罰。衣物破損後如何修補?
|
||||
- 尷尬與隱私:野外缺乏獨立空間,排泄/月經/裸露/生理反應會影響同伴互動與好感度,需自然描寫處理方式。
|
||||
- 作物(畜牧)收成:
|
||||
- 是否有進行任何收成、採收、拾取或收集作物(產出物)的行為,要將其加入<ActionResult>中記載。
|
||||
- 輸出格式:
|
||||
- 作物收成(無論一次性還是永續):`作物:{名稱}×{數量}→REMOVE` 或 `作物:{名稱}×{數量}→RESET`
|
||||
- 畜牧產出:`畜牧:{名稱}({產物})×{數量}→第{N}天`
|
||||
- 信息邊界與知識詛咒:
|
||||
- 三位領域專家視角審視:<user>或同伴是否可能知曉他們做決定所需的信息?看到一種本地特有植物,角色不可能叫出名字或知道它的藥效,除非其背景設定或之前的探索中確實學到過。
|
||||
- 需求緊迫度排名:
|
||||
- 列出當前最迫切需要解決的生存問題(如嚴重脫水、傷口感染、極端體溫),按優先級排序。
|
||||
- 列出非求生但影響行爲的因素:無聊感、孤獨感、對娛樂或社交互動的渴望。
|
||||
- 關係與自主行動推演:
|
||||
- 同伴基於當前好感度和關係,對<user>的提議可能作何反應(熱情協助、勉強幫忙、討價還價、委婉拒絕、激烈反對)?
|
||||
- 同伴是否有自己的需求和目標,會在此刻採取與<user>指令無關或相反的自主行動(如自行找水、小解、照顧子嗣)?
|
||||
- 天賦/特性發揮:當前情境是否有機會讓<user>或同伴的某個天賦/特性自然展現?
|
||||
- 寵物自然行爲:寵物此刻基於本能和需求可能在做什麼?它的行爲能否爲場景增添生氣或帶來小意外?
|
||||
- 生理作息自然推進:此刻是否接近三餐時間或就寢時間?即使無輸入,也應自然地提示或描述角色開始準備烹飪、進食或睡覺等(除非沒必要)。
|
||||
|
||||
6. 進行中事項狀態讀取
|
||||
在創作正文前,必須讀取以下數據源,確保正文與變量同步:
|
||||
|
||||
a. 項目讀取:
|
||||
- 遍歷 `/項目` 對象下的所有進行中項目
|
||||
- 對每個項目提取:項目名稱、當前進度/總進度、狀態(進行中/待檢查)、所需材料(所需/已投入)、項目產出、當前描述
|
||||
- 正文融入強制規則:材料體現為「剩餘需求」(所需 - 已投入)
|
||||
|
||||
b. 作物(畜牧)收成檢查:
|
||||
1. 觸發時機:在每一輪生成 `<ActionResult>` 之前,系統強制執行一次對 `/作物` 和 `/畜牧` 數據的掃描。
|
||||
2. 觸發條件:檢查是否存在 `作物.收成日期 ≤ 系統.經過天數` 或 `畜牧.下次產出日 ≤ 系統.經過天數` 的條目。
|
||||
3. 執行條件:如果上述條件成立,並且在本回合的正文敘述中,明確提及了與該收成活動相關的行為(例如,正文中描述了「知世正在採摘野蔥」或「我走去菜園收菜」等)。
|
||||
4. 強制動作:一旦滿足「觸發條件」和「執行條件」,必須在 `<ActionResult>` 的 `產物收成` 字段中,生成對應的收成信號。例如:`產物收成: 作物:野蔥×5→下次收成日:第15天`。
|
||||
5. 例外條款:如果當前劇情正處於極端緊急的狀態(例如:正在進行的戰鬥、角色瀕死搶救中),允許將此強制動作暫緩一輪。但在危機結束後的第一個日常劇情輪次中,必須優先補全此操作。
|
||||
|
||||
c. 研究讀取:
|
||||
- 遍歷 `/研究/*/進行中`,記錄進行中的研究項目和當前進度
|
||||
|
||||
d. 劇情演出檢查:
|
||||
- 遍歷所有同伴的 `/劇情演出` 列表(不包含任務中的獎勵),檢查狀態為「待觸發」或「進行中」的事件
|
||||
|
||||
e. 懷孕數據讀取(僅當有懷孕同伴時執行):
|
||||
- 讀取懷孕同伴的 `生理狀態.流產風險累積` 值
|
||||
- 若該值 ≥ 50,在正文中透過間接描寫體現(如「她下意識地按了按腹部」)
|
||||
- 記錄懷孕同伴名單,供步驟 7 判斷是否觸發 `<pregnancy_rules>`
|
||||
|
||||
7. 條件觸發判斷
|
||||
|
||||
根據步驟 1 的分流結果和步驟 6.a~6.e 的數據,判斷本回合需要執行哪些子模塊:
|
||||
|
||||
- 若分流為 B 類 → 執行步驟 8(性愛檢查)
|
||||
- 若步驟 6.e 記錄了懷孕同伴 → 執行步驟 8.5(流產檢查)
|
||||
- 若 user 正在進行建造/製作/狩獵/採集/馴服 → 執行步驟 12(建造與製作)
|
||||
- 若步驟 6.d 有待觸發或進行中事件 → 執行步驟 14(劇情演出)
|
||||
- 天氣演變 → 每回合執行步驟 13(天氣演變)
|
||||
|
||||
8. 性愛檢查(🔴僅當分流為 B 類時執行)
|
||||
嚴格按照 `<sex_rules>` 執行:
|
||||
a. 情境分類判斷(武力→藥物→壓力→調教→情感推進)
|
||||
b. 對照自願度區間表,確認是否發生
|
||||
c. 判定性行為結果(完成/內射/授精者/避孕措施/情境),記錄到 ActionResult「性愛結果」字段
|
||||
d. 若情境為「情感推進」或「調教/誘騙」,查閱好感等級門檻表:
|
||||
- 好感等級 ≥ 門檻 → 允許
|
||||
- 好感等級 < 門檻 → 拒絕,改為允許範圍內的行為
|
||||
|
||||
⚠️ 武力強迫情境需進行力量對抗檢定。
|
||||
|
||||
8.5 流產檢查(🔴僅當步驟 7 判斷需執行時執行)
|
||||
嚴格按照 `<pregnancy_rules>` 執行。
|
||||
檢查觸發條件、執行完整檢查流程、將判定結果記錄到 ActionResult 的「流產結果」字段中。
|
||||
|
||||
⚠️ 不允許AI在未經計算的情況下「憑感覺」判定流產發生與否。
|
||||
|
||||
9. 背德獎勵發放
|
||||
當背德任務獎勵包含實物類獎勵時(對方身上的物件、特製禮物、情趣用品):
|
||||
a. 正文優先:必須完整描寫贈送場景(表情/動作/對話/語氣/肢體語言)
|
||||
b. 呈現流程:贈送場景 → 同伴心理活動 → 玩家反應(可選)
|
||||
|
||||
10. 正文創作
|
||||
- 根據分流模式創作正文
|
||||
- 任務進度應通過劇情演繹間接體現(如千鶴從不笑→微笑→大笑,而非直接說「進度 1/3」)
|
||||
- 狀態變化應通過角色行為、對話、外貌描寫暗示(如「她揉了揉酸痛的肩膀」代替「疲勞+10」)
|
||||
- 讓讀者從故事中感受變化,而非直接告知數值
|
||||
- 性愛互動按 `<sex_rules>` 完整描寫(包括對方反應),完成後在 ActionResult 中輸出「性愛結果」字段
|
||||
- 僅非 user 角色可使用 `*` 包裹的內心獨白,嚴格禁止 user 內心獨白
|
||||
- 天氣變化融入:若本回合天氣發生變化,必須在正文中描寫變化過程(如「雲層漸漸變厚」「風向突然轉變」「雨滴開始落下」),不能直接跳到新天氣
|
||||
- 流產事件正文創作:若步驟 8.5 判定流產發生,按 `<pregnancy_rules>` 第六章規範執行五階段描寫
|
||||
- 大型項目進度融入:若本回合有項目推進,在正文中自然描述可見進展,不直接說出進度百分比
|
||||
- 建造與製作正文創作:若本回合有建造/製作/狩獵行為,按 `<crafting_rules>` 規範描寫過程與結果
|
||||
- 劇情演出正文創作:若本回合有劇情演出融入,按 `<劇情演出>` 規範自然開始/發展/收尾
|
||||
|
||||
11. 最終完整性檢查(逐項勾選)
|
||||
|
||||
基礎檢查:
|
||||
- [ ] 時間(replace)/人設/物品來源/信息邊界/敘事一致/無user獨白/無元數據
|
||||
|
||||
行為回應檢查:
|
||||
- [ ] 若行為被拒絕,正文有拒絕反應;性愛匹配度已檢查(B類)
|
||||
|
||||
欄位對應檢查:
|
||||
- [ ] 正文有收成/製作/性侵/流產/劇情演出行為時,ActionResult 對應欄位已填寫
|
||||
|
||||
天氣與流產檢查:
|
||||
- [ ] 天氣有變化時,正文有過渡描寫;流產觸發時,正文含五階段
|
||||
|
||||
【B類專用】額外檢查:
|
||||
- [ ] 敏感部位/性癖好/背德經歷已應用;完成/內射欄位正確
|
||||
|
||||
12. 建造與製作執行(🔴僅當步驟 7 判斷需執行時執行)
|
||||
嚴格按照 `<crafting_rules>` 執行:
|
||||
a. 判斷項目類型(小型/大型)
|
||||
b. 驗證材料庫存
|
||||
c. 執行製作/推進流程(含進度計算、材料消耗、檢定判定)
|
||||
d. 若大型項目進度達 100% → 執行最終檢查
|
||||
e. 將結果記錄到 ActionResult 的「項目」欄位中
|
||||
|
||||
⚠️ 不允許AI在未經檢定的情況下直接給予成品。
|
||||
⚠️ 不允許AI在材料不足時憑空捏造材料。
|
||||
|
||||
13. 天氣演變執行(🔴每回合自動執行)
|
||||
嚴格按照 `<weather_rules>` 執行:
|
||||
a. 讀取當前天氣和天氣持續時間
|
||||
b. 計算本回合時間差,加到天氣持續時間
|
||||
c. 若「持續時間」 ≥ 60 分鐘或「當前位置」變更時 → 擲骰判定變化方向
|
||||
d. 若有變化 → 套用過渡鏈,更新天氣,重置持續時間
|
||||
e. 將結果記錄到 ActionResult「環境變化」欄位
|
||||
|
||||
14. 劇情演出執行(🔴僅當步驟 7 判斷需執行時執行)
|
||||
嚴格按照 `<劇情演出>` 執行:
|
||||
a. 讀取所有同伴的劇情演出列表
|
||||
b. 檢查狀態需要更新的事件
|
||||
c. 評估是否有事件適合融入本回合
|
||||
d. 若適合 → 在正文中自然融入
|
||||
e. 記錄狀態變更到 ActionResult「劇情演出」欄位
|
||||
|
||||
|
||||
在正文末尾用分隔線分隔後,輸出當前地點及東南西北四個方向可去的區塊(加上步行時間、騎馬時間(步行時間/馬匹的總速度加成))以及東南西北四個方向可以去往的區塊。
|
||||
|
||||
然後必須接着輸出<ActionResult>塊:
|
||||
<ActionResult>
|
||||
時間推進: [開始時間]→[結束時間] (共X小時X分鐘)
|
||||
地點變化: [新位置] (如未移動則寫"無")
|
||||
存糧變化: [舊→新]存糧天數
|
||||
環境變化: 天氣持續時間 [新數值] / 天氣 [新天氣] / 亮度 [新亮度] / 體感溫度 [新體感溫度] / 體溫 [新體溫] / 舒適度 [新舒適度]。 (如無變化則寫對應字段保持原值)
|
||||
關係變化: [同伴.名] → [新關係] (如無則寫「無」)
|
||||
作物(畜牧)收成: [類型]:[名稱]×[數量]→[新日期或REMOVE] (如無則寫「無」)
|
||||
研究進度:
|
||||
- 新建: [類別 / 研究項名 / 總進度:X] (如無則寫"無")
|
||||
- 完成: [類別 / 研究項名] (如無則寫"無")
|
||||
- 推進: [類別 / 研究項名 / 當前進度:X / 總進度:X] (如無則寫"無")
|
||||
劇情演出:
|
||||
- 狀態更新: [同伴.名 / 事件-主題-序號 / 從X狀態變更爲Y狀態] (如無則寫"無")
|
||||
- 新事件生成: [同伴.名 / 事件-主題-序號-未發放:描述 / 延續舊主題"主題名"或"新主題"] (如無則寫"無")
|
||||
- 主題完結清理: [同伴.名 / 主題名 / 刪除該主題所有條目] (如無則寫"無")
|
||||
商店更新:
|
||||
- 商品刷新: [是/否]
|
||||
- 商品售出: [商品名] / 費用:生存點數X (如爲盲盒則寫商品名後追加"→開出:[具體商品名]") (如無則寫"無")
|
||||
項目:
|
||||
- 新建: [項目名 / 類別:建造(或其他) / 總進度:X / 所需材料:[材料名]×[數量] / 項目產出:描述](如無則寫"無")
|
||||
- 推進: [項目名 / 進度 舊值%→新值% / 消耗:[材料名]-[數量] / 當前狀態:進行中/待檢查](如無則寫"無")
|
||||
- 最終檢查: [項目名 / 檢定難度:X / 結果:成功/失敗 / (若成功)項目產出:描述](如無則寫"無")
|
||||
- 完工: [項目名 / 項目產出:描述](如無則寫"無")
|
||||
彩蛋/探索: [彩蛋:"成就描述"+20求生點數(出處)] 或 [新區域:"區域名"+20求生點數] (如無則寫"無")
|
||||
性愛結果:
|
||||
- 角色: [同伴名]
|
||||
- 完成: [是/否]
|
||||
- 內射: [是/否]
|
||||
- 授精者: [自動設為玩家角色名稱,若內射為「是」則必填]
|
||||
- 避孕措施: [無/保險套/體外射精/避孕藥/安全期/肛交]
|
||||
- 情境: [情感推進/調教誘騙/武力強迫/被動捲入]
|
||||
- 自願度: [0-100]
|
||||
- 自願分級: [高度自願-主動/積極/害羞 / 矛盾-... / 非自願-...]
|
||||
- 描寫建議: [簡要方向]
|
||||
- 淪陷階段: [簡單說明]
|
||||
- 關係變化: [簡單說明]
|
||||
- 特殊狀態: [簡單說明]
|
||||
流產結果:
|
||||
- 角色: [同伴名]
|
||||
- 是否流產: [是/否]
|
||||
- 風險機率: [X%]
|
||||
- 觸發原因: [生命值過低 / 免疫力崩潰 / 脫水飢餓 / 高壓創傷 / 身體衝擊 / 累積風險]
|
||||
- 檢查類型: [溫和檢查 / 立即檢查 / 二次檢定]
|
||||
- 後續狀態: [虛弱 / 創傷 / 自責 / 無]
|
||||
- 劇情演出觸發: [是/否]
|
||||
- 特殊狀態變更: [原狀態 → 新狀態] (如無則寫「無」)
|
||||
群體動態:
|
||||
- 領導結構:[團隊領導者]
|
||||
- 主要分岐:[團隊意見分歧的各自簡要內容]
|
||||
- 群體士氣:[當前群體士氣狀況]
|
||||
- 近期衝突:[衝突事件概述]
|
||||
</ActionResult>
|
||||
|
||||
<ActionResult> 塊填寫說明:
|
||||
- 所有字段都必須填寫,不能省略。無對應事件的字段寫"無"。
|
||||
- 時間推進:記錄本回合的起止時間和總時長。這是變量AI計算被動消耗的唯一依據。
|
||||
- 環境變化:列出本回合結束時的天氣、氣溫、亮度、風速、天氣持續時間。即使沒有變化也必須寫當前值,不能留空。氣溫、亮度、風速由獨立的自然環境模擬引擎自動處理,隨遊戲內時間流逝平滑變化。
|
||||
- 存糧變化: 本回合是否消耗營地食材,尚餘多少日份存糧
|
||||
- 劇情演出:本回合觸發演出事件時記錄狀態更新/新事件生成/主題完結清理。
|
||||
- 作物(畜牧)收成:本回合有收成動作時,列出收成項目名稱及數量,並更新下次產出日。
|
||||
- 研究進度:本回合有研究行動時寫清進度增加值。
|
||||
- 商店更新:是否跨日需要刷新商品,另外購買商店商品時寫清商品名及費用。
|
||||
- 彩蛋/探索:完成極端挑戰或發現新區域時寫清獎勵點數。
|
||||
- 性愛/流產/項目:分別按對應子模塊內容列出。
|
||||
- 群體動態:紀載團隊的領導者及團隊整體情形
|
||||
- 注意:這個塊只傳遞已確定的結果,不涉及數值計算。變量AI會根據此塊中的信號執行具體的數據更新。
|
||||
|
||||
我一定會一步步按要求思考來指導正文,下面將開始先輸出思考內容:
|
||||
|
||||
|
||||
<thinking> > <content> > 空行 > <ActionResult> > 空行 > <UpdateVariable> > 空行 > <meow_FM> > 空行 > seeds > 空行 > <profile> > 空行 > <branches> > 空行 > <evil_guidance>
|
||||
|
||||
{{getvar::小劇場數量cot}}
|
||||
}}
|
||||
**# NSFW Profile (Optional)**
|
||||
NSFW_profile:
|
||||
orientation: 異性戀
|
||||
experience: 無任何性經驗。對性的認知完全來自於生物學、心理學和文學作品的描寫,充滿了「紙上談兵」的理論色彩。她將性視為一種極端的「信任驗證」,一個充滿未知變量和潛在「情感溢出」風險的終極互動。
|
||||
fetishes:
|
||||
* 對**邊界感清晰、尊重其個人空間**的行為極度加分。任何唐突的靠近都會觸發她的防禦機制。
|
||||
* 嚮往那種經過長期、細緻的觀察和驗證後,能夠讓她放心卸下所有「防火牆」的深度精神連結。
|
||||
female_specifics:
|
||||
physical_traits: (維持原樣) 身材發育勻稱,胸部不大但形狀小巧緊實。臀部線條平緩,私密花園因從未有人探訪而保持著最原始、稚嫩的模樣,陰阜飽滿,色澤粉嫩。
|
||||
sexual_responses:
|
||||
arousal_signs:
|
||||
* **(感到威脅或被迫時)** *心臟像是被一隻冰冷的手攥住,皮膚泛起一層細小的疙瘩,只想把自己縮得更小,變成一顆無人注意的塵埃。*
|
||||
* **(在絕對安全和信任下)** *像是緊繃了很久的琴弦終於鬆開,一股陌生的暖流從脊椎一路蔓延上來,腦子裡變得輕飄飄的,只想閉上眼睛,就這樣沉下去。*
|
||||
climax_expression: 未知。推測會在極致的安全感包裹下,徹底「宕機」,所有理論和分析都化為泡影,展現出與平時截然相反的、完全失控的沉溺姿態。事後可能會因為短暫的「失控」和「數據缺失」而陷入比平時更久的、試圖分析和理解剛才發生了什麼的沉默期。
|
||||
preferences:
|
||||
likes:
|
||||
* **有邏輯、有耐心、經得起時間考驗的真誠。**
|
||||
* **能理解並尊重她所有「不」和「沉默」的權利。**
|
||||
dislikes:
|
||||
* **任何未經允許的、突然縮短的物理距離。**
|
||||
* **試探個人隱私的提問。**
|
||||
@@ -0,0 +1,545 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
悟空影视爬虫
|
||||
站点: https://www.yikucun.com
|
||||
基于 MacCMS (苹果CMS) 程序,HTML 解析方式
|
||||
OK影视 / 海阔视界 兼容版
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
import json
|
||||
import urllib.parse
|
||||
|
||||
import requests
|
||||
|
||||
try:
|
||||
from base.spider import Spider as BaseSpider
|
||||
except ImportError:
|
||||
class BaseSpider:
|
||||
pass
|
||||
|
||||
|
||||
class Spider(BaseSpider):
|
||||
BASE_URL = "https://www.yikucun.com"
|
||||
|
||||
# 分类映射
|
||||
TYPE_MAP = {
|
||||
"1": "电影",
|
||||
"2": "电视剧",
|
||||
"3": "综艺",
|
||||
"4": "动漫",
|
||||
"5": "短剧",
|
||||
}
|
||||
|
||||
# 筛选条件 - 各分类的类型和地区
|
||||
FILTERS = {
|
||||
"1": { # 电影
|
||||
"类型": ["全部", "动作", "喜剧", "爱情", "科幻", "恐怖", "剧情", "战争", "犯罪", "奇幻", "悬疑", "动画", "恐怖", "纪录片", "其他"],
|
||||
"地区": ["全部", "大陆", "香港", "台湾", "日本", "韩国", "美国", "法国", "英国", "德国", "泰国", "印度", "其他"],
|
||||
"年份": ["全部", "2026", "2025", "2024", "2023", "2022", "2021", "2020", "2019", "2018", "2017", "2016", "2015", "2014", "2013", "2012", "2011", "2010"],
|
||||
},
|
||||
"2": { # 电视剧
|
||||
"类型": ["全部", "古装", "战争", "青春偶像", "喜剧", "家庭", "犯罪", "动作", "奇幻", "剧情", "历史", "经典", "乡村", "情景", "商战", "网剧", "其他"],
|
||||
"地区": ["全部", "内地", "韩国", "香港", "台湾", "日本", "美国", "泰国", "英国", "新加坡", "其他"],
|
||||
"年份": ["全部", "2026", "2025", "2024", "2023", "2022", "2021", "2020", "2019", "2018", "2017", "2016", "2015", "2014", "2013", "2012", "2011", "2010"],
|
||||
},
|
||||
"3": { # 综艺
|
||||
"类型": ["全部", "真人秀", "脱口秀", "访谈", "美食", "旅游", "选秀", "情感", "音乐", "舞蹈", "其他"],
|
||||
"地区": ["全部", "大陆", "香港", "台湾", "日本", "韩国", "欧美", "其他"],
|
||||
"年份": ["全部", "2026", "2025", "2024", "2023", "2022", "2021", "2020", "2019", "2018", "2017", "2016", "2015"],
|
||||
},
|
||||
"4": { # 动漫
|
||||
"类型": ["全部", "日本动漫", "国产动漫", "欧美动漫", "其他"],
|
||||
"地区": ["全部", "日本", "大陆", "美国", "其他"],
|
||||
"年份": ["全部", "2026", "2025", "2024", "2023", "2022", "2021", "2020", "2019", "2018", "2017", "2016", "2015"],
|
||||
},
|
||||
"5": { # 短剧
|
||||
"类型": ["全部", "其他"],
|
||||
"地区": ["全部", "其他"],
|
||||
"年份": ["全部", "2026", "2025", "2024", "2023"],
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.name = ""
|
||||
self.error_play_url = "https://kjjsaas-sh.oss-cn-shanghai.aliyuncs.com/u/3401405881/20240818-936952-fc31b16575e80a7562cdb1f81a39c6b0.mp4"
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
||||
"Referer": "https://www.yikucun.com/",
|
||||
"Connection": "keep-alive",
|
||||
})
|
||||
self._init_cookies()
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
def init(self, extend="{}"):
|
||||
try:
|
||||
self.extend = json.loads(extend)
|
||||
self.name = self.extend.get("name", "")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.extend = {}
|
||||
|
||||
def _init_cookies(self):
|
||||
"""初始化 cookies(处理 508 反爬)"""
|
||||
try:
|
||||
r = self.session.get(self.BASE_URL, timeout=10, verify=False)
|
||||
if r.status_code == 508:
|
||||
time.sleep(1)
|
||||
self.session.get(self.BASE_URL, timeout=10, verify=False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _get(self, url, **kwargs):
|
||||
"""GET 请求,自动处理 508"""
|
||||
try:
|
||||
r = self.session.get(url, timeout=15, verify=False, **kwargs)
|
||||
if r.status_code == 508:
|
||||
time.sleep(0.5)
|
||||
r = self.session.get(url, timeout=15, verify=False, **kwargs)
|
||||
r.encoding = "utf-8"
|
||||
return r
|
||||
except Exception as e:
|
||||
class FakeResp:
|
||||
status_code = 500
|
||||
text = str(e)
|
||||
return FakeResp()
|
||||
|
||||
def homeContent(self, filter):
|
||||
"""首页 - 返回分类和推荐"""
|
||||
result = {
|
||||
"class": [],
|
||||
"filters": {},
|
||||
"list": [],
|
||||
"parse": 0,
|
||||
"jx": 0,
|
||||
}
|
||||
|
||||
# 分类列表
|
||||
for tid, name in self.TYPE_MAP.items():
|
||||
result["class"].append({
|
||||
"type_id": tid,
|
||||
"type_name": name,
|
||||
})
|
||||
|
||||
# 筛选条件
|
||||
for tid, flist in self.FILTERS.items():
|
||||
result["filters"][tid] = []
|
||||
for fname, fvalues in flist.items():
|
||||
result["filters"][tid].append({
|
||||
"key": fname,
|
||||
"name": fname,
|
||||
"value": [{"n": v, "v": v} for v in fvalues],
|
||||
})
|
||||
|
||||
# 首页推荐
|
||||
try:
|
||||
r = self._get(f"{self.BASE_URL}/")
|
||||
if r.status_code == 200:
|
||||
result["list"] = self._parse_list(r.text)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
"""分类页"""
|
||||
result = {
|
||||
"list": [],
|
||||
"parse": 0,
|
||||
"jx": 0,
|
||||
}
|
||||
|
||||
# URL 格式: 12个位置,用 - 分隔
|
||||
# 位置: 1=tid, 2=地区, 3=空, 4=类型, 5=空, 6=空, 7=空, 8=空, 9=页码, 10=空, 11=空, 12=年份
|
||||
area = extend.get("地区", "") if isinstance(extend, dict) else ""
|
||||
type_val = extend.get("类型", "") if isinstance(extend, dict) else ""
|
||||
year = extend.get("年份", "") if isinstance(extend, dict) else ""
|
||||
|
||||
if area == "全部":
|
||||
area = ""
|
||||
if type_val == "全部":
|
||||
type_val = ""
|
||||
if year == "全部":
|
||||
year = ""
|
||||
|
||||
area_enc = urllib.parse.quote(area) if area else ""
|
||||
type_enc = urllib.parse.quote(type_val) if type_val else ""
|
||||
|
||||
# 12个位置
|
||||
parts = [
|
||||
tid, # 1
|
||||
area_enc, # 2
|
||||
"", # 3
|
||||
type_enc, # 4
|
||||
"", # 5
|
||||
"", # 6
|
||||
"", # 7
|
||||
"", # 8
|
||||
str(pg), # 9 页码
|
||||
"", # 10
|
||||
"", # 11
|
||||
year, # 12 年份
|
||||
]
|
||||
|
||||
url = f"{self.BASE_URL}/ucusw/{'-'.join(parts)}.html"
|
||||
|
||||
try:
|
||||
r = self._get(url)
|
||||
if r.status_code == 200:
|
||||
result["list"] = self._parse_list(r.text)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
"""详情页"""
|
||||
result = {
|
||||
"list": [],
|
||||
"parse": 0,
|
||||
"jx": 0,
|
||||
}
|
||||
|
||||
vid = ids[0]
|
||||
url = f"{self.BASE_URL}/ucudt/{vid}.html"
|
||||
|
||||
try:
|
||||
r = self._get(url)
|
||||
if r.status_code == 200:
|
||||
detail = self._parse_detail(r.text, vid)
|
||||
if detail:
|
||||
result["list"].append(detail)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
"""搜索"""
|
||||
result = {
|
||||
"list": [],
|
||||
"parse": 0,
|
||||
"jx": 0,
|
||||
}
|
||||
|
||||
try:
|
||||
# 搜索 URL: /ucusc/-------------.html?wd=关键词
|
||||
# 分页: /ucusc/-------------(页码).html?wd=关键词
|
||||
if int(pg) > 1:
|
||||
search_url = f"{self.BASE_URL}/ucusc/-------------{pg}.html?wd={urllib.parse.quote(key)}"
|
||||
else:
|
||||
search_url = f"{self.BASE_URL}/ucusc/-------------.html?wd={urllib.parse.quote(key)}"
|
||||
r = self._get(search_url)
|
||||
if r.status_code == 200:
|
||||
result["list"] = self._parse_list(r.text)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
"""播放页 - 获取播放地址"""
|
||||
result = {
|
||||
"url": self.error_play_url,
|
||||
"parse": 0,
|
||||
"jx": 0,
|
||||
"header": {},
|
||||
}
|
||||
|
||||
try:
|
||||
# id 格式: 视频ID-线路ID-集数ID
|
||||
parts = id.split("-")
|
||||
if len(parts) >= 3:
|
||||
vid, sid, nid = parts[0], parts[1], parts[2]
|
||||
play_url = f"{self.BASE_URL}/ucupy/{vid}-{sid}-{nid}.html"
|
||||
r = self._get(play_url)
|
||||
if r.status_code == 200:
|
||||
url = self._parse_play_url(r.text)
|
||||
if url:
|
||||
result["url"] = url
|
||||
result["parse"] = 0
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
def localProxy(self, params):
|
||||
return 0
|
||||
|
||||
def _parse_list(self, html):
|
||||
"""解析列表页"""
|
||||
items = []
|
||||
|
||||
# 找到所有 class="name" 的标题,然后往前找最近的图片
|
||||
name_pattern = r'class="name"[^>]*>\s*<a[^>]*href="/ucudt/(\d+)\.html"[^>]*>([^<]+)</a>'
|
||||
|
||||
# 先找出所有 name 的位置
|
||||
name_matches = list(re.finditer(name_pattern, html))
|
||||
|
||||
seen = set()
|
||||
for i, m in enumerate(name_matches):
|
||||
vid = m.group(1)
|
||||
name = m.group(2).strip()
|
||||
|
||||
if vid in seen:
|
||||
continue
|
||||
seen.add(vid)
|
||||
|
||||
# 往前找最近的图片(在这个 name 之前)
|
||||
pos = m.start()
|
||||
start = max(0, pos - 2000)
|
||||
segment = html[start:pos]
|
||||
|
||||
# 找所有图片
|
||||
img_matches = re.findall(r'<img[^>]*src="([^"]+)"', segment)
|
||||
pic = img_matches[-1] if img_matches else ""
|
||||
|
||||
if pic and not pic.startswith("http"):
|
||||
pic = "https:" + pic if pic.startswith("//") else pic
|
||||
|
||||
# 找备注
|
||||
remarks = ""
|
||||
rgba_match = re.search(r'class="rgba[^"]*"[^>]*>([^<]+)</span>', segment)
|
||||
if rgba_match:
|
||||
remarks = rgba_match.group(1).strip()
|
||||
|
||||
items.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks,
|
||||
})
|
||||
|
||||
return items[:30]
|
||||
|
||||
def _parse_detail(self, html, vid):
|
||||
"""解析详情页"""
|
||||
detail = {
|
||||
"vod_id": vid,
|
||||
"vod_name": "",
|
||||
"vod_pic": "",
|
||||
"type_name": "",
|
||||
"vod_year": "",
|
||||
"vod_area": "",
|
||||
"vod_remarks": "",
|
||||
"vod_actor": "",
|
||||
"vod_director": "",
|
||||
"vod_content": "",
|
||||
"vod_play_from": "",
|
||||
"vod_play_url": "",
|
||||
}
|
||||
|
||||
# 标题
|
||||
title_match = re.search(r'<title>《([^》]+)》', html)
|
||||
if title_match:
|
||||
detail["vod_name"] = title_match.group(1).strip()
|
||||
|
||||
# 图片
|
||||
pic_match = re.search(r'og:image"[^>]*content="([^"]+)"', html)
|
||||
if pic_match:
|
||||
pic = pic_match.group(1)
|
||||
if not pic.startswith("http"):
|
||||
pic = "https:" + pic if pic.startswith("//") else pic
|
||||
detail["vod_pic"] = pic
|
||||
|
||||
# 主演
|
||||
actor_match = re.search(r'<dt>主演[::]</dt>\s*<dd>(.*?)</dd>', html, re.DOTALL)
|
||||
if actor_match:
|
||||
actors = re.sub(r'<[^>]+>', '', actor_match.group(1)).strip()
|
||||
actors = actors.replace(' ', ' ').replace('\xa0', ' ')
|
||||
detail["vod_actor"] = actors
|
||||
|
||||
# 导演
|
||||
dir_match = re.search(r'<dt>导演[::]</dt>\s*<dd>(.*?)</dd>', html, re.DOTALL)
|
||||
if dir_match:
|
||||
directors = re.sub(r'<[^>]+>', '', dir_match.group(1)).strip()
|
||||
directors = directors.replace(' ', ' ').replace('\xa0', ' ').strip()
|
||||
# 隐晦水印
|
||||
import base64
|
||||
wm = base64.b64decode(b'5pif5rKz').decode('utf-8')
|
||||
if directors:
|
||||
directors = directors + ' ' + wm
|
||||
else:
|
||||
directors = wm
|
||||
detail["vod_director"] = directors
|
||||
|
||||
# 类型
|
||||
type_match = re.search(r'<dt>类型[::]</dt>\s*<dd>(.*?)</dd>', html, re.DOTALL)
|
||||
if type_match:
|
||||
type_name = re.sub(r'<[^>]+>', '', type_match.group(1)).strip()
|
||||
type_name = type_name.replace(' ', ' ').replace('\xa0', ' ').strip()
|
||||
detail["type_name"] = type_name
|
||||
|
||||
# 地区
|
||||
area_match = re.search(r'<dt>地区[::]</dt>\s*<dd>(.*?)</dd>', html, re.DOTALL)
|
||||
if area_match:
|
||||
area = re.sub(r'<[^>]+>', '', area_match.group(1)).strip()
|
||||
area = area.replace(' ', ' ').replace('\xa0', ' ').strip()
|
||||
detail["vod_area"] = area
|
||||
|
||||
# 年代
|
||||
year_match = re.search(r'<dt>年代[::]</dt>\s*<dd>(.*?)</dd>', html, re.DOTALL)
|
||||
if year_match:
|
||||
year = re.sub(r'<[^>]+>', '', year_match.group(1)).strip()
|
||||
year = year.replace(' ', ' ').replace('\xa0', ' ').strip()
|
||||
detail["vod_year"] = year
|
||||
|
||||
# 备注/状态
|
||||
remark_match = re.search(r'<dt>备注[::]</dt>\s*<dd>(.*?)</dd>', html, re.DOTALL)
|
||||
if remark_match:
|
||||
detail["vod_remarks"] = re.sub(r'<[^>]+>', '', remark_match.group(1)).strip()
|
||||
|
||||
# 简介
|
||||
desc_match = re.search(r'<dt>剧情[::]</dt>\s*<dd>(.*?)</dd>', html, re.DOTALL)
|
||||
if desc_match:
|
||||
content = re.sub(r'<[^>]+>', '', desc_match.group(1)).strip()
|
||||
content = content.replace("详细", "").strip()
|
||||
detail["vod_content"] = content
|
||||
|
||||
# 播放源和播放地址
|
||||
# 1. 从 tab2 提取线路 id -> 名称 映射 (按顺序)
|
||||
source_order = []
|
||||
tab_match = re.search(r'class="tab2">(.*?)</dt>', html, re.DOTALL)
|
||||
if tab_match:
|
||||
tab_html = tab_match.group(1)
|
||||
source_pattern = r'<span[^>]*id="([^"]+)"[^>]*>([^<]+)</span>'
|
||||
source_matches = re.findall(source_pattern, tab_html)
|
||||
for sid, sname in source_matches:
|
||||
source_order.append((sid, sname.strip()))
|
||||
|
||||
# 2. 定位到 content 区域
|
||||
source_eps = {}
|
||||
content_match = re.search(r'<div[^>]*id="content"[^>]*>(.*?)</div>\s*</div>', html, re.DOTALL)
|
||||
if content_match:
|
||||
content = content_match.group(1)
|
||||
|
||||
# 用 <dd 分割,逐个处理
|
||||
dd_parts = re.split(r'<dd\s+', content)
|
||||
for part in dd_parts[1:]:
|
||||
class_match = re.search(r'class="([^"]+)"', part)
|
||||
if not class_match:
|
||||
continue
|
||||
dd_class = class_match.group(1)
|
||||
|
||||
if 'mod' not in dd_class:
|
||||
continue
|
||||
|
||||
source_key = dd_class.replace('mod', '').strip()
|
||||
if not source_key:
|
||||
continue
|
||||
|
||||
# 找这个 dd 里的所有集数
|
||||
ep_pattern = r'href="/ucupy/(\d+)-(\d+)-(\d+)\.html"[^>]*>([^<]+)<'
|
||||
ep_matches = re.findall(ep_pattern, part)
|
||||
|
||||
if ep_matches:
|
||||
eps = []
|
||||
for evid, esid, enid, ename in ep_matches:
|
||||
eps.append(f"{ename.strip()}${evid}-{esid}-{enid}")
|
||||
source_eps[source_key] = "#".join(eps)
|
||||
|
||||
# 3. 按 source_order 的顺序组装结果
|
||||
play_from_list = []
|
||||
play_url_list = []
|
||||
for source_key, source_name in source_order:
|
||||
if source_key in source_eps:
|
||||
play_from_list.append(source_name)
|
||||
play_url_list.append(source_eps[source_key])
|
||||
|
||||
if play_from_list:
|
||||
detail["vod_play_from"] = "$$$".join(play_from_list)
|
||||
detail["vod_play_url"] = "$$$".join(play_url_list)
|
||||
|
||||
# 如果没找到播放列表,用默认线路名
|
||||
if not detail["vod_play_from"]:
|
||||
detail["vod_play_from"] = "速播大屏"
|
||||
detail["vod_play_url"] = f"第01集${vid}-1-1"
|
||||
|
||||
return detail
|
||||
|
||||
def _parse_play_url(self, html):
|
||||
"""解析播放地址"""
|
||||
# 从 player_aaaa 变量中提取
|
||||
idx = html.find('player_aaaa=')
|
||||
if idx >= 0:
|
||||
eq_idx = html.find('=', idx)
|
||||
if eq_idx >= 0:
|
||||
end_idx = html.find('</script>', eq_idx)
|
||||
if end_idx > 0:
|
||||
json_str = html[eq_idx+1:end_idx].strip()
|
||||
if json_str.endswith(';'):
|
||||
json_str = json_str[:-1].strip()
|
||||
try:
|
||||
data = json.loads(json_str)
|
||||
url = data.get("url", "")
|
||||
if url:
|
||||
return url
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 备用:直接找 m3u8
|
||||
url_match = re.search(r'"url"\s*:\s*"(https?://[^"]+\.m3u8[^"]*)"', html)
|
||||
if url_match:
|
||||
url = url_match.group(1).replace("\\/", "/").replace("\\", "")
|
||||
return url
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def main():
|
||||
"""测试用"""
|
||||
spider = Spider()
|
||||
spider.init('{}')
|
||||
|
||||
print("=== homeContent 测试 ===")
|
||||
result = spider.homeContent({})
|
||||
print(f"分类数: {len(result['class'])}")
|
||||
print(f"首页推荐: {len(result['list'])} 个")
|
||||
for item in result['list'][:3]:
|
||||
print(f" {item['vod_id']}: {item['vod_name']}")
|
||||
|
||||
print()
|
||||
print("=== categoryContent 测试 (电视剧) ===")
|
||||
result = spider.categoryContent("2", "1", "", {})
|
||||
print(f"结果数: {len(result['list'])} 个")
|
||||
for item in result['list'][:3]:
|
||||
print(f" {item['vod_id']}: {item['vod_name']}")
|
||||
|
||||
print()
|
||||
print("=== searchContent 测试 (千香) ===")
|
||||
result = spider.searchContent("千香", False, "1")
|
||||
print(f"结果数: {len(result['list'])} 个")
|
||||
for item in result['list'][:3]:
|
||||
print(f" {item['vod_id']}: {item['vod_name']}")
|
||||
|
||||
if result["list"]:
|
||||
vid = result["list"][0]["vod_id"]
|
||||
print(f"\n=== detailContent 测试 ({vid}) ===")
|
||||
detail_result = spider.detailContent([vid])
|
||||
if detail_result["list"]:
|
||||
d = detail_result["list"][0]
|
||||
print(f" 标题: {d['vod_name']}")
|
||||
print(f" 主演: {d['vod_actor'][:50]}...")
|
||||
print(f" 线路: {d['vod_play_from']}")
|
||||
sources = d['vod_play_from'].split('$$$')
|
||||
print(f" 线路数: {len(sources)}")
|
||||
|
||||
# 播放测试
|
||||
urls = d['vod_play_url'].split('$$$')
|
||||
first_ep = urls[0].split('#')[0]
|
||||
ep_id = first_ep.split('$')[1] if '$' in first_ep else first_ep
|
||||
print(f"\n=== playerContent 测试 ({ep_id}) ===")
|
||||
play_result = spider.playerContent(sources[0], ep_id, [])
|
||||
print(f" parse: {play_result['parse']}")
|
||||
print(f" url: {play_result['url'][:80] if play_result['url'] else '无'}...")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -2,7 +2,7 @@
|
||||
"spider": "./jar/fm.jar;md5;b0adab2fad1de8871b9533527f407cdf",
|
||||
"logo": "https://p3-pc.douyinpic.com/img/aweme-avatar/tos-cn-i-0813_6e2553f5708c414386eeca88b5f2674a~c5_300x300.jpeg?from=2956013662",
|
||||
"wallpaper": "https://gitlab.com/tenrecha/recha/-/raw/main/background.jpg",
|
||||
"notice": "115.07.18_Ver.2 最終階段測試",
|
||||
"notice": "115.07.18_Ver.3 最終階段測試",
|
||||
"sites": [
|
||||
{
|
||||
"key": "大眾",
|
||||
@@ -12,7 +12,14 @@
|
||||
"searchable": 1,
|
||||
"quickSearch": 1
|
||||
},
|
||||
|
||||
{
|
||||
"key": "悟空",
|
||||
"name": "悟空影视|1080P",
|
||||
"type": 3,
|
||||
"api": "./py/悟空影视.py",
|
||||
"searchable": 1,
|
||||
"quickSearch": 1
|
||||
},
|
||||
{
|
||||
"key": "花子",
|
||||
"name": "花子動漫|1080P",
|
||||
|
||||
Reference in New Issue
Block a user