| 86 | } |
| 87 | |
| 88 | export class QidianRadarSource implements RadarSource { |
| 89 | readonly name = "qidian"; |
| 90 | |
| 91 | async fetch(): Promise<PlatformRankings> { |
| 92 | const entries: RankingEntry[] = []; |
| 93 | |
| 94 | try { |
| 95 | const url = "https://www.qidian.com/rank/"; |
| 96 | const res = await globalThis.fetch(url, { |
| 97 | headers: { |
| 98 | "User-Agent": |
| 99 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", |
| 100 | }, |
| 101 | }); |
| 102 | if (!res.ok) return { platform: "起点中文网", entries }; |
| 103 | const html = await res.text(); |
| 104 | |
| 105 | const bookPattern = |
| 106 | /<a[^>]*href="\/\/book\.qidian\.com\/info\/(\d+)"[^>]*>([^<]+)<\/a>/g; |
| 107 | let match: RegExpExecArray | null; |
| 108 | const seen = new Set<string>(); |
| 109 | while ((match = bookPattern.exec(html)) !== null) { |
| 110 | const title = match[2].trim(); |
| 111 | if (title && !seen.has(title) && title.length > 1 && title.length < 30) { |
| 112 | seen.add(title); |
| 113 | entries.push({ title, author: "", category: "", extra: "[起点热榜]" }); |
| 114 | } |
| 115 | if (entries.length >= 20) break; |
| 116 | } |
| 117 | } catch { |
| 118 | // skip on network error |
| 119 | } |
| 120 | |
| 121 | return { platform: "起点中文网", entries }; |
| 122 | } |
| 123 | } |
nothing calls this directly
no outgoing calls
no test coverage detected