diff --git a/agent-workspace/domain-skills/weread/read.md b/agent-workspace/domain-skills/weread/read.md new file mode 100644 index 00000000..1381576f --- /dev/null +++ b/agent-workspace/domain-skills/weread/read.md @@ -0,0 +1,323 @@ +# WeRead — Auto Reading + +Field-tested against weread.qq.com on 2026-05-06. +Requires WeChat login for reading features. + +--- + +## Core Flow + +``` +Step 1: Check login status + ├── Not logged in → Prompt user to scan QR code → Wait for login completion + └── Logged in → Proceed to Step 2 + +Step 2: Check reading progress + ├── No progress or finished → Pick first book from New Book ranking → Record status → Proceed to Step 3 + └── Unfinished book → Resume reading → Proceed to Step 3 + +Step 3: Auto reading (1 minute per session) + ├── Every 3-5s scroll down 100px + ├── See "Next Chapter" → Click, continue reading + ├── See "Book Complete" + "Mark as Finished" → Click mark as finished → Record status as finished + └── Time's up → Save progress, wait for next session +``` + +--- + +## URL Patterns + +| Page | URL | +|------|-----| +| Home | `https://weread.qq.com/` | +| Rising | `https://weread.qq.com/web/category/rising` | +| Hot Search | `https://weread.qq.com/web/category/hot_search` | +| New Book | `https://weread.qq.com/web/category/newbook` | +| Book Detail | `https://weread.qq.com/web/bookDetail/{BOOK_ID}` | +| Reader | `https://weread.qq.com/web/reader/{BOOK_ID}k{CHAPTER_HASH}` | + +### Reader URL Pattern + +``` +https://weread.qq.com/web/reader/{BOOK_ID}k{CHAPTER_HASH} +``` + +- `BOOK_ID` — unique book identifier (e.g., `ee0320b053b925ee0519857`) +- `CHAPTER_HASH` — chapter hash value (e.g., `08432c902c4084b6fbb18c9`) +- Directly accessing the URL jumps to the specified chapter + +--- + +## Step 1: Login Flow + +### Login Detection + +```python +# Check if login is needed +login_needed = js(""" + const loginBtn = document.querySelector('[class*=login], [class*=Login]'); + const qrCode = document.querySelector('[class*=qrcode], [class*=QRCode]'); + return !!(loginBtn || qrCode); +""") +``` + +### Login Process + +1. Navigate to `https://weread.qq.com/` +2. Page shows a QR code login prompt +3. User scans the QR code with the WeChat mobile app +4. Page auto-redirects to the home page after successful scan +5. Login state is persisted — no need to re-login + +### Wait For Login Completion + +```python +# Wait for login to complete +import time + +def wait_for_login(timeout=120): + start = time.time() + while time.time() - start < timeout: + # Check if still on login page + on_login = js(""" + const loginBtn = document.querySelector('[class*=login], [class*=Login]'); + return !!loginBtn; + """) + if not on_login: + return True + time.sleep(2) + return False +``` + +--- + +## Step 2: Reading Progress Management + +### progress.json Structure + +```json +{ + "book": { + "title": "book title", + "author": "author", + "url": "current chapter URL" + }, + "progress": { + "status": "reading | finished", + "currentChapter": "chapter name", + "completedChapters": ["list of chapters"], + "lastReadTime": "2026-05-06" + } +} +``` + +### Load Progress + +```python +import json +import os + +PROGRESS_FILE = "progress.json" + +def load_progress(): + if not os.path.exists(PROGRESS_FILE): + return None + with open(PROGRESS_FILE, "r", encoding="utf-8") as f: + return json.load(f) +``` + +### Save Progress + +```python +def save_progress(book_title, author, url, chapter, completed_chapters, status="reading"): + progress = { + "book": { + "title": book_title, + "author": author, + "url": url + }, + "progress": { + "status": status, + "currentChapter": chapter, + "completedChapters": completed_chapters, + "lastReadTime": time.strftime("%Y-%m-%d") + } + } + with open(PROGRESS_FILE, "w", encoding="utf-8") as f: + json.dump(progress, f, ensure_ascii=False, indent=2) +``` + +### Check Reading Status + +```python +def should_pick_new_book(): + progress = load_progress() + if progress is None: + return True # never read before + if progress["progress"]["status"] == "finished": + return True # already finished + return False # has an unfinished book +``` + +### Pick Book From New Book Ranking + +```python +def pick_book_from_new_ranking(): + # Navigate to New Book ranking + new_tab("https://weread.qq.com/web/category/newbook") + wait_for_load() + time.sleep(2) + + # Scroll to top + js("window.scrollTo(0, 0)") + time.sleep(1) + + # Click the first book + first_book = js(""" + const allElements = document.querySelectorAll("[class*=title]"); + for (const el of allElements) { + const text = el.textContent.trim(); + if (text.length > 3 && text.length < 50 && !text.includes("榜")) { + const rect = el.getBoundingClientRect(); + return { + title: text, + x: rect.x + rect.width / 2, + y: rect.y + rect.height / 2 + }; + } + } + return null; + """) + + if first_book: + click_at_xy(first_book["x"], first_book["y"]) + wait_for_load() + time.sleep(1) + + return first_book["title"] if first_book else None +``` + +--- + +## Step 3: Auto Reading + +### Scroll Reading + +```python +import random + +def scroll_reading(duration=360): + start_time = time.time() + chapters_read = [] + + while time.time() - start_time < duration: + # Scroll down 100px + js("window.scrollBy(0, 100)") + + # Random wait 3-5 seconds + wait_time = random.uniform(3, 5) + time.sleep(wait_time) + + # Check if "Next Chapter" button is visible + next_chapter = find_next_chapter_button() + if next_chapter and next_chapter["visible"]: + # Record current chapter + current = get_current_chapter() + chapters_read.append(current) + # Click next chapter + click_at_xy(next_chapter["x"], next_chapter["y"]) + wait_for_load() + time.sleep(1) + continue + + # Check if "Book Complete" and "Mark as Finished" + finished = check_book_finished() + if finished: + click_mark_finished() + return chapters_read, True # True = book finished + + return chapters_read, False # False = time's up, book not finished +``` + +### Find Next Chapter Button + +```python +def find_next_chapter_button(): + buttons = js(""" + const items = []; + const elements = document.querySelectorAll("button, a, [role=button], [class*=next], [class*=Next]"); + elements.forEach(el => { + const text = el.textContent.trim(); + if (text.includes("下一章")) { + const rect = el.getBoundingClientRect(); + items.push({ + text: text, + x: rect.x + rect.width/2, + y: rect.y + rect.height/2, + visible: rect.top < window.innerHeight && rect.bottom > 0 + }); + } + }); + return items; + """) + return buttons[0] if buttons else None +``` + +### Get Current Chapter + +```python +def get_current_chapter(): + return js(""" + const title = document.title; + const parts = title.split(" - "); + return parts.length > 1 ? parts[1] : "未知章节"; + """) +``` + +### Check Book Finished + +```python +def check_book_finished(): + return js(""" + const elements = document.querySelectorAll("[class*=finish], [class*=complete], [class*=end]"); + for (const el of elements) { + const text = el.textContent.trim(); + if (text.includes("全书完") || text.includes("已读完")) { + return true; + } + } + return false; + """) +``` + +### Click Mark As Finished + +```python +def click_mark_finished(): + button = js(""" + const elements = document.querySelectorAll("button, [role=button]"); + for (const el of elements) { + const text = el.textContent.trim(); + if (text.includes("标记读完") || text.includes("标记已读")) { + const rect = el.getBoundingClientRect(); + return {x: rect.x + rect.width/2, y: rect.y + rect.height/2}; + } + } + return null; + """) + if button: + click_at_xy(button["x"], button["y"]) + wait_for_load() + time.sleep(1) +``` + +--- + +## Gotchas + +- **Persistent login** — No need to re-login after first scan, unless browser data is cleared. +- **"Next Chapter" button position** — The button is at the bottom of the page; scroll it into the viewport before clicking. +- **Scroll interval** — 3-5s random interval simulates real reading; scrolling too fast may trigger detection. +- **Chapter URL changes** — Each chapter has a unique URL hash; saving the full URL allows precise position restoration. +- **Book finished detection** — Some books may lack a "Book Complete" prompt; adjust detection logic as needed. +- **New Book ranking first pick** — Ranking order may change; always fetch the first book in real time. \ No newline at end of file