feedscope 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- feedscope/__init__.py +3 -0
- feedscope/__main__.py +4 -0
- feedscope/cli.py +154 -0
- feedscope/core/__init__.py +1 -0
- feedscope/core/browser.py +367 -0
- feedscope/core/config.py +367 -0
- feedscope/core/const.py +64 -0
- feedscope/core/display.py +62 -0
- feedscope/core/http.py +104 -0
- feedscope/core/i18n/__init__.py +202 -0
- feedscope/core/i18n/en.toml +447 -0
- feedscope/core/i18n/zh-CN.toml +444 -0
- feedscope/core/media.py +150 -0
- feedscope/core/models.py +86 -0
- feedscope/core/registry.py +53 -0
- feedscope/core/samples.py +68 -0
- feedscope/core/source.py +668 -0
- feedscope/core/text.py +54 -0
- feedscope/core/timefmt.py +53 -0
- feedscope/core/tree.py +134 -0
- feedscope/doctor.py +54 -0
- feedscope/login.py +168 -0
- feedscope/probe.py +131 -0
- feedscope/report.py +19 -0
- feedscope/sources/__init__.py +38 -0
- feedscope/sources/hn/__init__.py +66 -0
- feedscope/sources/hn/fetch.py +82 -0
- feedscope/sources/hn/parse.py +154 -0
- feedscope/sources/reddit/__init__.py +178 -0
- feedscope/sources/reddit/fetch.py +181 -0
- feedscope/sources/reddit/parse.py +318 -0
- feedscope/sources/rss/__init__.py +64 -0
- feedscope/sources/rss/fetch.py +94 -0
- feedscope/sources/rss/parse.py +116 -0
- feedscope/sources/telegram/__init__.py +61 -0
- feedscope/sources/telegram/fetch.py +79 -0
- feedscope/sources/telegram/parse.py +182 -0
- feedscope/sources/v2ex/__init__.py +64 -0
- feedscope/sources/v2ex/fetch.py +46 -0
- feedscope/sources/v2ex/parse.py +116 -0
- feedscope/sources/x/__init__.py +68 -0
- feedscope/sources/x/fetch.py +490 -0
- feedscope/sources/x/parse.py +535 -0
- feedscope/sources/zhihu/__init__.py +81 -0
- feedscope/sources/zhihu/fetch.py +402 -0
- feedscope/sources/zhihu/parse.py +235 -0
- feedscope/subscriptions.py +423 -0
- feedscope/ui/__init__.py +10 -0
- feedscope/ui/account.py +125 -0
- feedscope/ui/app.py +105 -0
- feedscope/ui/dialogs.py +127 -0
- feedscope/ui/feedback.py +64 -0
- feedscope/ui/focus_nav.py +113 -0
- feedscope/ui/labels.py +38 -0
- feedscope/ui/layout.py +66 -0
- feedscope/ui/list_render.py +42 -0
- feedscope/ui/screens/__init__.py +1 -0
- feedscope/ui/screens/answer.py +296 -0
- feedscope/ui/screens/detail.py +274 -0
- feedscope/ui/screens/language.py +59 -0
- feedscope/ui/screens/main.py +736 -0
- feedscope/ui/screens/main_setup.py +226 -0
- feedscope/ui/screens/reading.py +384 -0
- feedscope/ui/screens/source_setup.py +217 -0
- feedscope/ui/screens/subscriptions.py +196 -0
- feedscope/ui/theme.py +45 -0
- feedscope/ui/wrap.py +157 -0
- feedscope-0.1.0.dist-info/METADATA +302 -0
- feedscope-0.1.0.dist-info/RECORD +72 -0
- feedscope-0.1.0.dist-info/WHEEL +4 -0
- feedscope-0.1.0.dist-info/entry_points.txt +2 -0
- feedscope-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: feedscope
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FeedScope (观流): a terminal feed reader — Zhihu / X / Reddit / HN / V2EX / RSS / Telegram, all in one terminal
|
|
5
|
+
Project-URL: Homepage, https://github.com/howardli/FeedScope
|
|
6
|
+
Project-URL: Repository, https://github.com/howardli/FeedScope
|
|
7
|
+
Project-URL: Issues, https://github.com/howardli/FeedScope/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/howardli/FeedScope/blob/main/CHANGELOG.md
|
|
9
|
+
Author: howardli
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: cli,reader,rss,terminal,textual,tui,twitter,zhihu
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Terminals
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: feedparser>=6
|
|
24
|
+
Requires-Dist: html2text>=2024.2.26
|
|
25
|
+
Requires-Dist: httpx>=0.27
|
|
26
|
+
Requires-Dist: platformdirs>=4.0
|
|
27
|
+
Requires-Dist: playwright>=1.45
|
|
28
|
+
Requires-Dist: rich>=13
|
|
29
|
+
Requires-Dist: textual>=1.0
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# FeedScope
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/feedscope/)
|
|
35
|
+
[](https://pypi.org/project/feedscope/)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
[](https://github.com/howardli/FeedScope/actions/workflows/ci.yml)
|
|
38
|
+
|
|
39
|
+
> Terminal-first feed reader — Zhihu, X (Twitter), Reddit, Hacker News, V2EX, RSS and Telegram channels, all in one terminal.
|
|
40
|
+
|
|
41
|
+
**Language**: English · [中文](README.zh-CN.md)
|
|
42
|
+
|
|
43
|
+
## Demo
|
|
44
|
+
|
|
45
|
+
Zhihu Hot → X timeline → post detail (auto translation, quoted post) → Reddit → post detail → HN → story detail → reply tree → V2EX → topic detail — everything below is just `Tab` and `Enter`:
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
- **Multi-source aggregation**: Zhihu Hot / Recommend / Following, X For You / Following, Reddit Home / Popular / custom subreddits, HN Front / New / Ask / Show, V2EX Hot / Latest, Telegram channel subscriptions — one UI, `Tab` to switch
|
|
52
|
+
- **RSS subscriptions**: any RSS / Atom feed; the "All" stream merges by publish time (cross-feed dedup, a dead feed never drags others down); read feed-provided full text in-terminal; conditional requests keep refreshes polite to sources
|
|
53
|
+
- **Full in-terminal reading**: feed → question → answers → comments, loaded per layer on demand
|
|
54
|
+
- **HN discussion tree**: top-level comments as floors, reply trees one `Enter` deeper; deeply nested threads are flattened to two levels with `@parent` prefixes preserving who-replies-to-whom
|
|
55
|
+
- **Auto Grok translation for foreign posts on X**; media markers `[图片]` / `[视频]` are clickable links to the original image / video
|
|
56
|
+
- **Login loop**: in-app Chrome QR / password login when a source needs it; `Ctrl+U` to switch accounts
|
|
57
|
+
- **Enable sources on demand**: pick at first launch, `F2` anytime; site order = the order you tick
|
|
58
|
+
- **Bilingual UI (中文 / English)**: pick the language at first launch (pre-selected from your system), change anytime with `Ctrl+L` (applies after restart); message catalogs live in one file per language, and a new language is just one new TOML file
|
|
59
|
+
- **Editor-inspired theme**: VS Code Dark+ oriented dark palette, easy on the eyes for long reads
|
|
60
|
+
- **IME-friendly keys**: every binding is a non-printing key (Tab / arrows / function keys), works while a Chinese IME is active
|
|
61
|
+
- **Layout-change self-check**: 0 items parsed → immediate "site may have changed" error; `feedscope probe` inspects all sources and drops raw samples
|
|
62
|
+
|
|
63
|
+
## Screenshots
|
|
64
|
+
|
|
65
|
+
Zhihu Hot (feed sidebar on the left, meta column right-aligned and adaptive):
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
X For-You timeline (bylines show "name @handle" in full; media markers link to originals):
|
|
70
|
+
|
|
71
|
+

|
|
72
|
+
|
|
73
|
+
X post detail (media links, translation block, quoted post):
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+
|
|
77
|
+
Reddit Popular (meta column "N pts · N comments · r/sub"; add subreddits in `F2`):
|
|
78
|
+
|
|
79
|
+

|
|
80
|
+
|
|
81
|
+
Reddit post detail (markdown self-text + comment floors with scores; moderator stickies flagged `[置顶]`):
|
|
82
|
+
|
|
83
|
+

|
|
84
|
+
|
|
85
|
+
HN Front page (no login needed; "N pts · N comments · author" doubles as a discussion-heat radar):
|
|
86
|
+
|
|
87
|
+

|
|
88
|
+
|
|
89
|
+
HN story detail (one-line article link + top-level comments as floors, each floor headed by "N replies"):
|
|
90
|
+
|
|
91
|
+

|
|
92
|
+
|
|
93
|
+
HN reply tree of one comment (deep threads flattened to two levels, cross-level replies carry an `@parent` prefix):
|
|
94
|
+
|
|
95
|
+

|
|
96
|
+
|
|
97
|
+
V2EX Hot (no login; the "node · N replies · user" meta column scans as board + heat at a glance):
|
|
98
|
+
|
|
99
|
+

|
|
100
|
+
|
|
101
|
+
V2EX topic detail (markdown body + replies as floors; floor numbers match the site's floor culture):
|
|
102
|
+
|
|
103
|
+

|
|
104
|
+
|
|
105
|
+
## Install
|
|
106
|
+
|
|
107
|
+
Requires Python 3.11+ and a local Google Chrome (no `playwright install` needed). macOS and Linux; Windows is not supported yet.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pipx install feedscope # or: uv tool install feedscope
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
From source (development):
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
git clone https://github.com/howardli/FeedScope.git
|
|
117
|
+
cd FeedScope
|
|
118
|
+
uv sync # dev / test environment
|
|
119
|
+
uv tool install --editable . # global `feedscope` command; code edits take effect immediately
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- Run `feedscope doctor` after install to self-check the environment
|
|
123
|
+
- Slow PyPI? Use a mirror: `export UV_DEFAULT_INDEX=https://pypi.tuna.tsinghua.edu.cn/simple`
|
|
124
|
+
- Dev / test: `uv run feedscope` and `uv run pytest` — see [CONTRIBUTING](CONTRIBUTING.md)
|
|
125
|
+
|
|
126
|
+
## Usage
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
feedscope
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Keyboard
|
|
133
|
+
|
|
134
|
+
| Key | Action |
|
|
135
|
+
| --- | --- |
|
|
136
|
+
| `Tab` / `Shift+Tab` | Switch site (afterwards the keyboard lands in the left sidebar to pick a feed; `Enter` returns to the list) |
|
|
137
|
+
| `←` / `→` | Region moves: from the list `←` enters the sidebar (staying on the current feed) and `→` hits the right edge; from the sidebar `→` returns to the list and `←` goes to the source bar; in the source bar switches site in place; prev / next article in details; prev / next floor in reply layers |
|
|
138
|
+
| `↑` / `↓` | Move selection / scroll body; `↑` on the first row of any region climbs to the top source bar |
|
|
139
|
+
| `Enter` | Open detail; inside a detail, open the discussion layer of the floor you are reading; return to the list from the sidebar / source bar |
|
|
140
|
+
| `Ctrl+N` | Load more (list / floors / comments, each layer independently) |
|
|
141
|
+
| `Ctrl+O` | Open current item in browser |
|
|
142
|
+
| `F5` | Refresh |
|
|
143
|
+
| `Ctrl+U` | Account: login / logout |
|
|
144
|
+
| `Ctrl+L` | UI language: pick and save; restart `feedscope` to apply |
|
|
145
|
+
| `F2` | Manage sources: toggle enable / disable, saved on confirm; `→` opens subscription manager (RSS / Reddit / Telegram) |
|
|
146
|
+
| `Esc` | Go back one layer (from the sidebar / source bar, back to the list first) |
|
|
147
|
+
| `Ctrl+Q` | Quit (double `Ctrl+C` as fallback) |
|
|
148
|
+
|
|
149
|
+
All keys are non-printing — they work while a Chinese IME is active; typing printable characters by mistake shows a hint. The top tab bar and the left sidebar are clickable and drive the same code path as the keyboard. Arrow keys follow CLI conventions: `↑`/`↓` move the selection within the current list; `↑` on the first row of any region climbs to the source bar (the top region sits above everything), and `↓` from the source bar descends to the sidebar (inverse of the sidebar's first-feed `↑`); other region moves are horizontal — in the source bar `←`/`→` switches site in place; in the sidebar `↑`/`↓` switches feed in place, `→` returns to the list and `←` goes to the source bar; in the list `←` enters the sidebar (staying on the current feed) and `→` hits the right edge; `Enter` / `Esc` return to the list at any time. Dialogs (source manager / subscriptions / input / confirm / account) support the same arrow-key focus movement: `↓` past the bottom of the list enters the button row, `↑` returns to the list, `←`/`→` move between buttons, and `Enter` presses the focused button.
|
|
150
|
+
|
|
151
|
+
### UI Language
|
|
152
|
+
|
|
153
|
+
The first launch asks for the language (pre-selected from your system locale; `Enter` to confirm, `Esc` keeps the default). Change it anytime with `Ctrl+L`; the choice is saved to the config and **takes effect when you restart `feedscope`** (counts and unit words are baked into fetched data in the process language, so a fixed-per-process language is the most robust model). **English is the default when no language is configured** — 中文用户在首启回车即选简体中文,或 `Ctrl+L` 随时切换。Subcommands (`doctor` / `probe` / `rss` / `login`) follow the same language as the TUI:
|
|
154
|
+
|
|
155
|
+
```toml
|
|
156
|
+
language = "zh-CN" # or "en" (the default); delete the line to fall back to English (only a fresh install asks again)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Adding a language: copy `src/feedscope/core/i18n/en.toml` to `<code>.toml` (e.g. `ja.toml`), set its native name in `[meta]` (`name = "日本語"`) and translate the messages — the language picker discovers it automatically, no code changes. A parity test (`tests/test_i18n.py`) enforces identical key trees across catalogs, and an AST guard (`tests/test_i18n_no_hardcoded.py`) blocks hardcoded copy in `src`, so a missing translation turns the CI red.
|
|
160
|
+
|
|
161
|
+
### Login / Logout
|
|
162
|
+
|
|
163
|
+
Zhihu, X and Reddit require login (no anonymous feeds); HN, V2EX, RSS and Telegram work out of the box. Opening a logged-out source pops an in-app dialog; choosing "login" opens Chrome and auto-refreshes when done — no TUI restart. Dismiss it and you won't be asked again this session (`Ctrl+U` still works manually). `Ctrl+U` also switches accounts. From the CLI: `feedscope login [zhihu|x|reddit]`.
|
|
164
|
+
|
|
165
|
+
A session revoked server-side (password change, logout elsewhere, periodic rotation) never leaves you guessing: X's 401 is unambiguous (stale cookies dropped, re-login guided); Zhihu, Reddit and X all fetch through resident real-browser channel tabs (TLS, header order and cookies fully consistent with the login session, immune to non-browser-fingerprint blocking). Reddit's criterion is a local JWT decode of token_v2 (logged-in `sub="user"`, anonymous `sub="loid"`) — its login cookies are issued to anonymous traffic too (merely opening the login page sets one, so a cookie appearing ≠ login complete), while a local decode is zero-network and unforgeable. Both "login success" and the login gate use it, making a re-login loop structurally impossible.
|
|
166
|
+
|
|
167
|
+
### Sources
|
|
168
|
+
|
|
169
|
+
| Source | Feeds | Login |
|
|
170
|
+
| --- | --- | --- |
|
|
171
|
+
| Zhihu | Hot / Recommend / Following | ✅ |
|
|
172
|
+
| X (Twitter) | For You / Following | ✅ |
|
|
173
|
+
| Reddit | Home / Popular / custom subreddits | ✅ |
|
|
174
|
+
| HN | Front / New / Ask / Show | — |
|
|
175
|
+
| V2EX | Hot / Latest | — |
|
|
176
|
+
| RSS | All / one per subscription | — |
|
|
177
|
+
| Telegram | All / one per channel | — |
|
|
178
|
+
|
|
179
|
+
### Subcommands
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
feedscope [tui] # start the TUI (default)
|
|
183
|
+
feedscope login [source] # browser login (defaults to zhihu; e.g. x, reddit)
|
|
184
|
+
feedscope probe [source…] # inspect all sources; raw samples saved to disk
|
|
185
|
+
feedscope doctor # environment self-check (Chrome / profile / config)
|
|
186
|
+
feedscope rss add <url> [--key k --label name] # add: feed URL / site homepage (auto-discovered) / OPML path
|
|
187
|
+
feedscope rss list | remove <key> # list / remove subscriptions
|
|
188
|
+
feedscope rss import <x.opml> | export [x.opml] # bulk import (idempotent merge) / export
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Source & Feed Config
|
|
192
|
+
|
|
193
|
+
First launch shows a picker; `F2` reopens it anytime. Selections are saved to user config (`~/.config/feedscope/sources.toml` on Linux; `FEEDSCOPE_CONFIG` overrides), which you can also hand-edit; restart to apply:
|
|
194
|
+
|
|
195
|
+
```toml
|
|
196
|
+
language = "zh-CN" # UI language (maintained by first-run / Ctrl+L)
|
|
197
|
+
enabled = ["zhihu", "x", "reddit", "hn", "v2ex", "rss"] # order = site order; remove an id to disable
|
|
198
|
+
|
|
199
|
+
[zhihu]
|
|
200
|
+
hide = ["follow"] # hide built-in feeds you don't want
|
|
201
|
+
|
|
202
|
+
[reddit.feeds] # subreddit table: key = feed name (names colliding with built-in home/popular are rejected)
|
|
203
|
+
rust = { label = "Rust", url = "https://www.reddit.com/r/rust/" }
|
|
204
|
+
|
|
205
|
+
[telegram.feeds] # channel table: key = feed name (avoid the aggregate name all)
|
|
206
|
+
durov = { url = "https://t.me/durov/" }
|
|
207
|
+
|
|
208
|
+
[rss.feeds] # RSS subscription table: key = feed name (avoid the reserved key all)
|
|
209
|
+
ruanyq = { label = "Ruanyifeng Weekly", url = "https://www.ruanyifeng.com/blog/atom.xml" }
|
|
210
|
+
hnrss = { url = "https://hnrss.org/newest?q=rust" } # label defaults to key (keyword alerts still fit hnrss; for HN itself use the built-in source)
|
|
211
|
+
|
|
212
|
+
[rss]
|
|
213
|
+
hide = ["all"] # hide the "All" aggregate, or a subscription key to hide one feed
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Omitting `enabled` = everything enabled; saving from the manager preserves per-source `hide` sections and `feeds` tables (`[rss.feeds]` / `[reddit.feeds]` / `[telegram.feeds]`). To reorder sites: untick then re-tick in `F2` (moves to the end).
|
|
217
|
+
|
|
218
|
+
### RSS Subscriptions
|
|
219
|
+
|
|
220
|
+
Three ways to manage, pick any:
|
|
221
|
+
|
|
222
|
+
- **In the TUI (`F2`)**: on the RSS row press `→` to open the subscription manager — `Insert` add, `Enter` rename, `Delete` remove, `↑↓` select; changes save instantly; RSS re-ticks itself on return
|
|
223
|
+
- **CLI**: `feedscope rss add / list / remove`; migrating from another reader: `feedscope rss import subs.opml` (OPML from Feedly / Inoreader / NetNewsWire etc.; existing keys are skipped); `export` anytime so your subscriptions are never locked in
|
|
224
|
+
- **Hand-edit config**: edit the `[rss.feeds]` table (see above), then tick RSS in `F2`
|
|
225
|
+
|
|
226
|
+
Convenience behaviors of the add dialog (identical to `rss add`):
|
|
227
|
+
|
|
228
|
+
- Paste an RSS/Atom URL, **or a site homepage** — the declared feed is auto-discovered
|
|
229
|
+
- Paste **a local OPML path** = bulk import (drag the file into the terminal from Finder)
|
|
230
|
+
- Label defaults to the feed's own title (fetched once); add / import auto-enables RSS
|
|
231
|
+
|
|
232
|
+
With zero subscriptions RSS doesn't appear in the source list (`F2` can still open its manager to add the first one). Behavior notes:
|
|
233
|
+
|
|
234
|
+
- The "All" stream merges every subscription by publish time (newest first); the same URL across feeds (reposts / aggregators) is deduped; one dead feed only loses its own items, all dead = error
|
|
235
|
+
- Details read feed-provided content (`content` full text first, `summary` fallback); images become direct links; summary-only feeds show short bodies — `Ctrl+O` to read the rest in a browser
|
|
236
|
+
- Refreshes are conditional requests (ETag / Last-Modified) — unchanged feeds don't re-transfer bodies
|
|
237
|
+
- RSS has no login and no comment layer
|
|
238
|
+
|
|
239
|
+
### Hacker News
|
|
240
|
+
|
|
241
|
+
No login, no API key; data comes from the [public Algolia HN index](https://hn.algolia.com/api) (the same engine behind HN's own site search). Behavior notes:
|
|
242
|
+
|
|
243
|
+
- Same three-layer shape as Zhihu: story (link posts: body = one article link; Ask/Show: self-text in full) → top-level comments as floors → `Enter` opens a floor's reply tree
|
|
244
|
+
- Meta column shows "N pts · N comments · author"; deep threads flatten to two levels, cross-level replies carry an `@parent` prefix preserving the conversation thread
|
|
245
|
+
- The index lags a few minutes and lists are sorted by points (not official rank); floors with no replies don't advertise `Enter`
|
|
246
|
+
|
|
247
|
+
### V2EX
|
|
248
|
+
|
|
249
|
+
No login; data comes from official public v1 endpoints (hot / latest / replies, no token needed; [official API reference](https://www.v2ex.com/help/api)). Behavior notes:
|
|
250
|
+
|
|
251
|
+
- Two-layer shape: topic (markdown body; auto-embedded images become `[图片]` markers, `@` mentions become clickable links) → replies flattened as floors; V2EX replies have no nesting ("sub-replies" are just `@` text), so there is no third layer
|
|
252
|
+
- Meta column shows "node · N replies · user"; floor numbers match the site's floor culture, and a floor's permalink is the `#reply` anchor on the topic page
|
|
253
|
+
- The API returns everything in one shot (list fits a page, replies unpaginated) — floor paging costs zero requests; the body rides along with the list, so opening a detail costs exactly 1 request
|
|
254
|
+
|
|
255
|
+
### X (Twitter)
|
|
256
|
+
|
|
257
|
+
Login required; data comes from the web GraphQL endpoints — requests are issued by a resident browser tab's real Chrome (same channel design as Zhihu / Reddit: TLS, header order and cookies fully consistent with the login session; cf_clearance-style fingerprint blocking simply doesn't apply to in-page fetch). The endpoint queryIds rotate with X deploys: this source starts from snapshot constants and **self-heals by harvesting on failure** — on error it reopens the tab, collects the current deployment's JS chunks, greps out fresh queryIds and retries — turning X redesign days from "run a script and hand-copy constants" into "recovers automatically the same day". Behavior notes:
|
|
258
|
+
|
|
259
|
+
- Meta column shows "name @handle"; foreign-language tweets get an auto Grok translation block; media become clickable `[图片]` / `[视频]` markers to originals / direct video links
|
|
260
|
+
- Detail: full focal tweet + expanded quote chain; replies page along the ShowMore cursor; retweets (RT) jump to the original tweet's conversation
|
|
261
|
+
- Search is out: SearchTimeline's transaction-id gating requires a header only X's own JS computes — in-page fetch can't get in either
|
|
262
|
+
|
|
263
|
+
### Reddit
|
|
264
|
+
|
|
265
|
+
Login required; data comes from **the website's own HTML channel** — the legacy `.json` endpoints have been 403-only for third parties since 2026, so this source rides what the new frontend itself uses: listing pages as SSR HTML, pagination following the `faceplate-partial` load-after chain. Requests are issued by a resident browser tab's real Chrome (same channel design as Zhihu: TLS, header order and cookies fully consistent with the login session, immune to non-browser-fingerprint blocking); `x-reddit-client-version` is harvested from page responses automatically and self-heals on 403. Behavior notes:
|
|
266
|
+
|
|
267
|
+
- Same three-layer shape as HN: post (self posts: HTML converted to markdown; link posts: one article link; media posts: `[图片]` / `[视频]` markers to originals) → top-level comments as floors → `Enter` opens a floor's reply tree
|
|
268
|
+
- Login criterion: a local JWT decode of token_v2 (logged-in `sub="user"`, anonymous `sub="loid"`) — the mere "presence" of reddit_session/token_v2 can be produced by anonymous traffic; the decode is the hard proof. Login polling and the gate share this one check: zero network, zero forgery
|
|
269
|
+
- Meta column shows "N pts · N comments · r/sub"; floors carry scores, moderator stickies are flagged `[置顶]`; deep threads flatten to two levels with `@parent` prefixes (same as HN)
|
|
270
|
+
- "Home" aggregates the subreddits your Reddit account follows on the site (empty for a fresh account that follows nothing — expected); subscribe to what you want instead: `F2` → Reddit row → `→`, type a subreddit name or URL (`rust`, `r/rust`, a site link all work) and it becomes its own feed — no need to follow it on reddit.com
|
|
271
|
+
- The comment tree arrives in one request (huge threads are truncated) with "N more collapsed" hints where cut; floor / reply paging is all in-memory, zero extra requests
|
|
272
|
+
|
|
273
|
+
### Telegram Channels
|
|
274
|
+
|
|
275
|
+
No login; data comes from each channel's public web preview (`t.me/s/<channel>`, server-rendered HTML readable as long as the owner keeps preview on). Behavior notes:
|
|
276
|
+
|
|
277
|
+
- Pure subscription source shaped exactly like RSS: `F2` → Telegram row → `→` opens the manager; type a channel name or link (`durov`, `@durov`, `t.me/durov`, even a post link); label defaults to the channel's own name
|
|
278
|
+
- The "All" stream merges every channel by publish time (newest first), one dead channel only loses its own items; each channel is also its own feed (individually hideable)
|
|
279
|
+
- Meta column "N views" is the channel-post heat radar; the "All" stream prefixes the channel name; inline HTML becomes markdown, images/videos become `[图片]` / `[视频]` markers (videos have no direct link — the marker points to the post page)
|
|
280
|
+
- Details read list-carried content (zero requests); `Ctrl+N` in a single-channel feed pages back through history via the `?before=` cursor; no comment layer (channel discussion groups aren't in the preview)
|
|
281
|
+
|
|
282
|
+
## Community
|
|
283
|
+
|
|
284
|
+
- **Bugs & features** — [issues](https://github.com/howardli/FeedScope/issues): the bug template asks for `feedscope doctor` output, which carries version + environment in a few lines
|
|
285
|
+
- **Questions & chat** — [discussions](https://github.com/howardli/FeedScope/discussions)
|
|
286
|
+
- **Code** — [CONTRIBUTING](CONTRIBUTING.md): adding a new source is designed to be a one-package change, and a new UI language is one TOML file
|
|
287
|
+
- **Security** — [SECURITY.md](SECURITY.md): where your login cookies live and how to report issues privately
|
|
288
|
+
- **Changelog** — [CHANGELOG.md](CHANGELOG.md)
|
|
289
|
+
|
|
290
|
+
Status: **0.x, active development.** Config may gain fields between minors; breaking changes wait for 1.0. A site redesign can break its source at any time — `feedscope probe` pinpoints it, patch releases ship updated parsers.
|
|
291
|
+
|
|
292
|
+
## Disclaimer
|
|
293
|
+
|
|
294
|
+
This project accesses Zhihu, X and Reddit web APIs through your local browser session, for personal learning and research only:
|
|
295
|
+
|
|
296
|
+
- Follow the laws of your region and each site's user agreements; account-risk is your own
|
|
297
|
+
- Ships with no credentials; login state stays in the local browser profile and is never shared
|
|
298
|
+
- Breakage from site redesigns is expected wear (`feedscope probe` helps locate them); do not use for bulk scraping or commercial purposes
|
|
299
|
+
|
|
300
|
+
## License
|
|
301
|
+
|
|
302
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
feedscope/__init__.py,sha256=Smhk5Txm-0CaAjOSBc94_fT-FjSlFucvA5dO3iXD_sY,141
|
|
2
|
+
feedscope/__main__.py,sha256=vhKkcKiOSyZanYu4L3ZvVz7f1znfcLUCx6DWGYeuCrQ,88
|
|
3
|
+
feedscope/cli.py,sha256=TptDJSedyVKQRUD6Fnz6ITh7CbjhvUg-zQ_JqFEKMNo,5698
|
|
4
|
+
feedscope/doctor.py,sha256=bLNpkSCzEGFQPihWGR3AOV-RGMk2jfBX1pvaymQvOgg,1626
|
|
5
|
+
feedscope/login.py,sha256=RaDGB-P_6FkFJg4MPhuML2wlazU4FsEqKywiwI7XPHI,7340
|
|
6
|
+
feedscope/probe.py,sha256=yoR8f7IDv-dgtZEg3LT8j_y1CWc3py1Fv-OceOtL_uM,5195
|
|
7
|
+
feedscope/report.py,sha256=ToA3a3xDw9LME-6-pQcmEw-mimv-0t92Jg2MOaB4xK4,754
|
|
8
|
+
feedscope/subscriptions.py,sha256=ahErqXnflJ2cQIqOLSbsfCPE1O-ampN2RkABORn2NoM,17830
|
|
9
|
+
feedscope/core/__init__.py,sha256=7kGkJR7M3awEQy4nwL-LIE9v5qxmqsLJ6KJ9VMEbP90,86
|
|
10
|
+
feedscope/core/browser.py,sha256=O-n8fT0v5mwMxle32Z5zgsuFtueFJSLFXcidQ25th3E,15618
|
|
11
|
+
feedscope/core/config.py,sha256=Sg6XVDRjo2lga0ai7SOgluqS_wNKPecsGCXfhdz-4vQ,15372
|
|
12
|
+
feedscope/core/const.py,sha256=QiOAoyexYppgL89MN5e6KTzVgaMU9-w2Q9G9AgLONFA,2440
|
|
13
|
+
feedscope/core/display.py,sha256=rjprEUgGnWpM5zxAtFHgt1_s1G3qv02F6Fuh8ZSx6Co,2836
|
|
14
|
+
feedscope/core/http.py,sha256=ejoLr6KSZEbVlEPaSmweHOZ4wEGY03_R1vCIDrhM8Yk,3947
|
|
15
|
+
feedscope/core/media.py,sha256=abgEIkgtlI5a9HSE2Hk5VIkaXzZulGW06v5IsB3OJSk,6396
|
|
16
|
+
feedscope/core/models.py,sha256=d4eJqdvq5HlYN4FB6p6pldHu_b7b74sXwK_ZgePls-Q,3574
|
|
17
|
+
feedscope/core/registry.py,sha256=gThnyDh8PmvlrRkvgWvIYs1wim3qRvwFWLZZnFVFAPI,1826
|
|
18
|
+
feedscope/core/samples.py,sha256=_nqF9N3t47W2pKWH5hK0iZE_zfcoCHrd2Kz10kBgOF4,2586
|
|
19
|
+
feedscope/core/source.py,sha256=rOwhoq2sxwH_Ee8gQ5-EtfgAVhQt8chf6Ci8hjKyum8,28981
|
|
20
|
+
feedscope/core/text.py,sha256=g6oKEl25x6JF7YStWCREeeI8HAAF3qHCsFoexW1sWMs,1923
|
|
21
|
+
feedscope/core/timefmt.py,sha256=bmTasakPmpIeJF8csXQ0mfETtU2VW3l1NstnJHqCouE,1905
|
|
22
|
+
feedscope/core/tree.py,sha256=ogLEG02KuKP7wXD_9X4MQDIKaptwlpYcLJ9krlfKIZo,5142
|
|
23
|
+
feedscope/core/i18n/__init__.py,sha256=sCeOKv7QmUONn_P04uUWwv8d9hkXPXdaQ-TVZjALYPc,7982
|
|
24
|
+
feedscope/core/i18n/en.toml,sha256=fLq_QZkQWRB8AogSTrFmUa27ozxSbl3y38zISzyM-G0,18760
|
|
25
|
+
feedscope/core/i18n/zh-CN.toml,sha256=U-XETEYtERPtds03wI2GhUNXdISAhUMLVn5_wiESgM0,17954
|
|
26
|
+
feedscope/sources/__init__.py,sha256=Mm8pwp6NndYFZXWgmOYRxEo54_DLG3_kIJgxU1i2cKk,2294
|
|
27
|
+
feedscope/sources/hn/__init__.py,sha256=y6Q9v-HnnojHtEORrlQWCjz2_ryh3IBlJ-7CaDjqY5I,2994
|
|
28
|
+
feedscope/sources/hn/fetch.py,sha256=c34KimBhdGPQaMwQWJL-Cjisk7oVqFcr9m2Hj1_ff-I,3418
|
|
29
|
+
feedscope/sources/hn/parse.py,sha256=Dn97R_WQtjozI4hMfkIynuP5pWR-EtbuzwLkf86gtzM,5816
|
|
30
|
+
feedscope/sources/reddit/__init__.py,sha256=YOzvc7hfj3tLOun_ijUKnD5H_JI4rNLNWSlKJLC4w7A,9006
|
|
31
|
+
feedscope/sources/reddit/fetch.py,sha256=gfdhxp6gJk0bBiwR4_9mCpcE5z1Sdh4iNaTLhemdJ_Q,8557
|
|
32
|
+
feedscope/sources/reddit/parse.py,sha256=D4PGyx6TIdwsCiIu6pvrM_64hMtTWD41bYKFCrhSA78,12468
|
|
33
|
+
feedscope/sources/rss/__init__.py,sha256=q3CUJFKt_1J3KdQfB41OMbdx58VrBC38nM4gU1UKymQ,2784
|
|
34
|
+
feedscope/sources/rss/fetch.py,sha256=2Z_q8rl4LrCm-GoRA2tB2CYSzRZb8KtcPXPc8-7yYsA,3583
|
|
35
|
+
feedscope/sources/rss/parse.py,sha256=38MAeLoDvvF0Lc5i2EaY2oEU9cj3g2ITiTOwPpyCurQ,4389
|
|
36
|
+
feedscope/sources/telegram/__init__.py,sha256=mjiXK0ZyLHQQOeMfxhrvuUk0882xg02JSlJwHG_On0A,3016
|
|
37
|
+
feedscope/sources/telegram/fetch.py,sha256=9Ipo5tV02F-9UqPYSBuhzYcICbVCXP26f6Q-mK89MGk,3350
|
|
38
|
+
feedscope/sources/telegram/parse.py,sha256=XcxUm1_TyCY4IaZPHTffIedpCzkj_MnI_0ZHWoroLNs,8252
|
|
39
|
+
feedscope/sources/v2ex/__init__.py,sha256=Q9u9T0joH35JrzYPxgcimnBz3a5671eUJW6uvqkQ7yQ,2963
|
|
40
|
+
feedscope/sources/v2ex/fetch.py,sha256=IVXEma7yFSaZNl6Q4l0te0xSDQaQJrA__tjjLQDOCXM,2032
|
|
41
|
+
feedscope/sources/v2ex/parse.py,sha256=AuILabDTeqe-lDNJsRhQVbMTCsaaCNCzyAv1AO6nDoc,4292
|
|
42
|
+
feedscope/sources/x/__init__.py,sha256=fthdU1ZhSz37mMfEUt_jNBTN2zf_tvPij8y701ottF8,2753
|
|
43
|
+
feedscope/sources/x/fetch.py,sha256=pZuCFSO0piFOumY2c_3ViZx7ua_QuaWX2Z4Ud4vHZY8,22285
|
|
44
|
+
feedscope/sources/x/parse.py,sha256=4b80ppBDHW1oFervQ8VoBTHCfpFLdn1s3IIbBaFCoM8,24247
|
|
45
|
+
feedscope/sources/zhihu/__init__.py,sha256=hSHTdU1OSGMZvIxuDOMohUcxcRLi9AJdiS03yqv229Y,3229
|
|
46
|
+
feedscope/sources/zhihu/fetch.py,sha256=F9t70Ze86x0ChETdvrO2ishbD3lwGFIoO575O9qAinE,18459
|
|
47
|
+
feedscope/sources/zhihu/parse.py,sha256=Sy9kyg_gU5f4aVh0RfbjkCA1LgHgksfEPFtFhQjAgeo,10972
|
|
48
|
+
feedscope/ui/__init__.py,sha256=vgh_xqhGi9Ez0ME3fA95ktwCS4WInGz2wGXbCJ2hGg8,465
|
|
49
|
+
feedscope/ui/account.py,sha256=fN_qzwTwlBcaQB7Vp2QhfMuESCtPdBVoapwQMMoPT_Y,6129
|
|
50
|
+
feedscope/ui/app.py,sha256=oKRmD6FAYGf-IsVGHvv9x_o47A1rmxRR1jhSafBLMjk,4865
|
|
51
|
+
feedscope/ui/dialogs.py,sha256=Yf2VdBXsbynzGMEjbW9B7ABAlyaeMqEjNrQZiawIcYw,5052
|
|
52
|
+
feedscope/ui/feedback.py,sha256=mmZ7O7ZyvVaDz1Qc19vdATjdN2Zu6yW8emaYF0yzbGM,2616
|
|
53
|
+
feedscope/ui/focus_nav.py,sha256=Mygu6ImErnrtlB9H4InttbWBCAFB74bkrMAtaSkRd_c,4809
|
|
54
|
+
feedscope/ui/labels.py,sha256=KTskl88I3ekf6dYOXaOjKjfa6NSKzos6O7dpkhs3b0M,1583
|
|
55
|
+
feedscope/ui/layout.py,sha256=CxzR2gPTtGor7R65tlvDFGoJ2xE6-hw7n_Nn9ChgXLo,3230
|
|
56
|
+
feedscope/ui/list_render.py,sha256=j43TtFdZmWGnkk8bSA_plN96qr2dBx8xW3x4U8CJmDI,1690
|
|
57
|
+
feedscope/ui/theme.py,sha256=bGqKu8ihzBIWLrapSwm-oEH98ahC81JpDpuzrBSzz2M,2208
|
|
58
|
+
feedscope/ui/wrap.py,sha256=weLRh_94aVN1chcdelICg_YJRJ7BOMxmXcAyJ3QTRYE,6112
|
|
59
|
+
feedscope/ui/screens/__init__.py,sha256=wv_lGxH6cAY5g8O7r1I7ibpkhCH7xzkIxUZ99hTJlC8,102
|
|
60
|
+
feedscope/ui/screens/answer.py,sha256=pkAa0Re67_X5A5D7qJJ2lWlaU9ZNyAsxqnsbKa2sVV8,12637
|
|
61
|
+
feedscope/ui/screens/detail.py,sha256=FvXXyFjrusiq9yN2IH-FVx08hKoXwhl8SB-ZrnsOQCY,12380
|
|
62
|
+
feedscope/ui/screens/language.py,sha256=04qq1RwfmwOmzAUN0a_0rEgzSPmRUBFgww_dPdd6sPQ,2480
|
|
63
|
+
feedscope/ui/screens/main.py,sha256=2cZ3lT7-gAqAE03Dpf4MUOSZzIOu49IkoPEHZV4W1hM,34147
|
|
64
|
+
feedscope/ui/screens/main_setup.py,sha256=8vojvk3A4BaQ2A_g3BOfsCk8b_9DhJAw1X63JvyJvFY,10988
|
|
65
|
+
feedscope/ui/screens/reading.py,sha256=ulRt89RXAFwOwZK4wW7mTG0f9ZMiyrvJnLQPgH7DZAw,17503
|
|
66
|
+
feedscope/ui/screens/source_setup.py,sha256=uvYlBO3naL2Hm1oP13GiPMmNTwk7rhexhsAyMW8yU4E,9667
|
|
67
|
+
feedscope/ui/screens/subscriptions.py,sha256=f5EzUC1t-0vBmjTe3Dn4b8lSO955A6s-OYv2fG9xPgs,8011
|
|
68
|
+
feedscope-0.1.0.dist-info/METADATA,sha256=2zo60KgUUw_sEOTPO5jD7NWZnlEZR0qVQ0sL6wZMrx4,21823
|
|
69
|
+
feedscope-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
70
|
+
feedscope-0.1.0.dist-info/entry_points.txt,sha256=aey4GMy_4ZcnjH3AjKZ-ZdsEMLiAHWFLTKQKVlCzzqs,49
|
|
71
|
+
feedscope-0.1.0.dist-info/licenses/LICENSE,sha256=Bt5d7VUv7XERCzikfMR50exYUP82MiX2H94kiICr3eU,1065
|
|
72
|
+
feedscope-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 howardli
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|