Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
323 changes: 323 additions & 0 deletions agent-workspace/domain-skills/weread/read.md
Original file line number Diff line number Diff line change
@@ -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.