Skip to content

Commit 0e679e2

Browse files
authored
Merge pull request #306 from kunghim/add-weread-skills
add weread domain skill
2 parents 2106221 + 4339d5a commit 0e679e2

1 file changed

Lines changed: 323 additions & 0 deletions

File tree

  • agent-workspace/domain-skills/weread
Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
# WeRead — Auto Reading
2+
3+
Field-tested against weread.qq.com on 2026-05-06.
4+
Requires WeChat login for reading features.
5+
6+
---
7+
8+
## Core Flow
9+
10+
```
11+
Step 1: Check login status
12+
├── Not logged in → Prompt user to scan QR code → Wait for login completion
13+
└── Logged in → Proceed to Step 2
14+
15+
Step 2: Check reading progress
16+
├── No progress or finished → Pick first book from New Book ranking → Record status → Proceed to Step 3
17+
└── Unfinished book → Resume reading → Proceed to Step 3
18+
19+
Step 3: Auto reading (1 minute per session)
20+
├── Every 3-5s scroll down 100px
21+
├── See "Next Chapter" → Click, continue reading
22+
├── See "Book Complete" + "Mark as Finished" → Click mark as finished → Record status as finished
23+
└── Time's up → Save progress, wait for next session
24+
```
25+
26+
---
27+
28+
## URL Patterns
29+
30+
| Page | URL |
31+
|------|-----|
32+
| Home | `https://weread.qq.com/` |
33+
| Rising | `https://weread.qq.com/web/category/rising` |
34+
| Hot Search | `https://weread.qq.com/web/category/hot_search` |
35+
| New Book | `https://weread.qq.com/web/category/newbook` |
36+
| Book Detail | `https://weread.qq.com/web/bookDetail/{BOOK_ID}` |
37+
| Reader | `https://weread.qq.com/web/reader/{BOOK_ID}k{CHAPTER_HASH}` |
38+
39+
### Reader URL Pattern
40+
41+
```
42+
https://weread.qq.com/web/reader/{BOOK_ID}k{CHAPTER_HASH}
43+
```
44+
45+
- `BOOK_ID` — unique book identifier (e.g., `ee0320b053b925ee0519857`)
46+
- `CHAPTER_HASH` — chapter hash value (e.g., `08432c902c4084b6fbb18c9`)
47+
- Directly accessing the URL jumps to the specified chapter
48+
49+
---
50+
51+
## Step 1: Login Flow
52+
53+
### Login Detection
54+
55+
```python
56+
# Check if login is needed
57+
login_needed = js("""
58+
const loginBtn = document.querySelector('[class*=login], [class*=Login]');
59+
const qrCode = document.querySelector('[class*=qrcode], [class*=QRCode]');
60+
return !!(loginBtn || qrCode);
61+
""")
62+
```
63+
64+
### Login Process
65+
66+
1. Navigate to `https://weread.qq.com/`
67+
2. Page shows a QR code login prompt
68+
3. User scans the QR code with the WeChat mobile app
69+
4. Page auto-redirects to the home page after successful scan
70+
5. Login state is persisted — no need to re-login
71+
72+
### Wait For Login Completion
73+
74+
```python
75+
# Wait for login to complete
76+
import time
77+
78+
def wait_for_login(timeout=120):
79+
start = time.time()
80+
while time.time() - start < timeout:
81+
# Check if still on login page
82+
on_login = js("""
83+
const loginBtn = document.querySelector('[class*=login], [class*=Login]');
84+
return !!loginBtn;
85+
""")
86+
if not on_login:
87+
return True
88+
time.sleep(2)
89+
return False
90+
```
91+
92+
---
93+
94+
## Step 2: Reading Progress Management
95+
96+
### progress.json Structure
97+
98+
```json
99+
{
100+
"book": {
101+
"title": "book title",
102+
"author": "author",
103+
"url": "current chapter URL"
104+
},
105+
"progress": {
106+
"status": "reading | finished",
107+
"currentChapter": "chapter name",
108+
"completedChapters": ["list of chapters"],
109+
"lastReadTime": "2026-05-06"
110+
}
111+
}
112+
```
113+
114+
### Load Progress
115+
116+
```python
117+
import json
118+
import os
119+
120+
PROGRESS_FILE = "progress.json"
121+
122+
def load_progress():
123+
if not os.path.exists(PROGRESS_FILE):
124+
return None
125+
with open(PROGRESS_FILE, "r", encoding="utf-8") as f:
126+
return json.load(f)
127+
```
128+
129+
### Save Progress
130+
131+
```python
132+
def save_progress(book_title, author, url, chapter, completed_chapters, status="reading"):
133+
progress = {
134+
"book": {
135+
"title": book_title,
136+
"author": author,
137+
"url": url
138+
},
139+
"progress": {
140+
"status": status,
141+
"currentChapter": chapter,
142+
"completedChapters": completed_chapters,
143+
"lastReadTime": time.strftime("%Y-%m-%d")
144+
}
145+
}
146+
with open(PROGRESS_FILE, "w", encoding="utf-8") as f:
147+
json.dump(progress, f, ensure_ascii=False, indent=2)
148+
```
149+
150+
### Check Reading Status
151+
152+
```python
153+
def should_pick_new_book():
154+
progress = load_progress()
155+
if progress is None:
156+
return True # never read before
157+
if progress["progress"]["status"] == "finished":
158+
return True # already finished
159+
return False # has an unfinished book
160+
```
161+
162+
### Pick Book From New Book Ranking
163+
164+
```python
165+
def pick_book_from_new_ranking():
166+
# Navigate to New Book ranking
167+
new_tab("https://weread.qq.com/web/category/newbook")
168+
wait_for_load()
169+
time.sleep(2)
170+
171+
# Scroll to top
172+
js("window.scrollTo(0, 0)")
173+
time.sleep(1)
174+
175+
# Click the first book
176+
first_book = js("""
177+
const allElements = document.querySelectorAll("[class*=title]");
178+
for (const el of allElements) {
179+
const text = el.textContent.trim();
180+
if (text.length > 3 && text.length < 50 && !text.includes("榜")) {
181+
const rect = el.getBoundingClientRect();
182+
return {
183+
title: text,
184+
x: rect.x + rect.width / 2,
185+
y: rect.y + rect.height / 2
186+
};
187+
}
188+
}
189+
return null;
190+
""")
191+
192+
if first_book:
193+
click_at_xy(first_book["x"], first_book["y"])
194+
wait_for_load()
195+
time.sleep(1)
196+
197+
return first_book["title"] if first_book else None
198+
```
199+
200+
---
201+
202+
## Step 3: Auto Reading
203+
204+
### Scroll Reading
205+
206+
```python
207+
import random
208+
209+
def scroll_reading(duration=360):
210+
start_time = time.time()
211+
chapters_read = []
212+
213+
while time.time() - start_time < duration:
214+
# Scroll down 100px
215+
js("window.scrollBy(0, 100)")
216+
217+
# Random wait 3-5 seconds
218+
wait_time = random.uniform(3, 5)
219+
time.sleep(wait_time)
220+
221+
# Check if "Next Chapter" button is visible
222+
next_chapter = find_next_chapter_button()
223+
if next_chapter and next_chapter["visible"]:
224+
# Record current chapter
225+
current = get_current_chapter()
226+
chapters_read.append(current)
227+
# Click next chapter
228+
click_at_xy(next_chapter["x"], next_chapter["y"])
229+
wait_for_load()
230+
time.sleep(1)
231+
continue
232+
233+
# Check if "Book Complete" and "Mark as Finished"
234+
finished = check_book_finished()
235+
if finished:
236+
click_mark_finished()
237+
return chapters_read, True # True = book finished
238+
239+
return chapters_read, False # False = time's up, book not finished
240+
```
241+
242+
### Find Next Chapter Button
243+
244+
```python
245+
def find_next_chapter_button():
246+
buttons = js("""
247+
const items = [];
248+
const elements = document.querySelectorAll("button, a, [role=button], [class*=next], [class*=Next]");
249+
elements.forEach(el => {
250+
const text = el.textContent.trim();
251+
if (text.includes("下一章")) {
252+
const rect = el.getBoundingClientRect();
253+
items.push({
254+
text: text,
255+
x: rect.x + rect.width/2,
256+
y: rect.y + rect.height/2,
257+
visible: rect.top < window.innerHeight && rect.bottom > 0
258+
});
259+
}
260+
});
261+
return items;
262+
""")
263+
return buttons[0] if buttons else None
264+
```
265+
266+
### Get Current Chapter
267+
268+
```python
269+
def get_current_chapter():
270+
return js("""
271+
const title = document.title;
272+
const parts = title.split(" - ");
273+
return parts.length > 1 ? parts[1] : "未知章节";
274+
""")
275+
```
276+
277+
### Check Book Finished
278+
279+
```python
280+
def check_book_finished():
281+
return js("""
282+
const elements = document.querySelectorAll("[class*=finish], [class*=complete], [class*=end]");
283+
for (const el of elements) {
284+
const text = el.textContent.trim();
285+
if (text.includes("全书完") || text.includes("已读完")) {
286+
return true;
287+
}
288+
}
289+
return false;
290+
""")
291+
```
292+
293+
### Click Mark As Finished
294+
295+
```python
296+
def click_mark_finished():
297+
button = js("""
298+
const elements = document.querySelectorAll("button, [role=button]");
299+
for (const el of elements) {
300+
const text = el.textContent.trim();
301+
if (text.includes("标记读完") || text.includes("标记已读")) {
302+
const rect = el.getBoundingClientRect();
303+
return {x: rect.x + rect.width/2, y: rect.y + rect.height/2};
304+
}
305+
}
306+
return null;
307+
""")
308+
if button:
309+
click_at_xy(button["x"], button["y"])
310+
wait_for_load()
311+
time.sleep(1)
312+
```
313+
314+
---
315+
316+
## Gotchas
317+
318+
- **Persistent login** — No need to re-login after first scan, unless browser data is cleared.
319+
- **"Next Chapter" button position** — The button is at the bottom of the page; scroll it into the viewport before clicking.
320+
- **Scroll interval** — 3-5s random interval simulates real reading; scrolling too fast may trigger detection.
321+
- **Chapter URL changes** — Each chapter has a unique URL hash; saving the full URL allows precise position restoration.
322+
- **Book finished detection** — Some books may lack a "Book Complete" prompt; adjust detection logic as needed.
323+
- **New Book ranking first pick** — Ranking order may change; always fetch the first book in real time.

0 commit comments

Comments
 (0)