Fast async NZB verification against one or more NNTP servers.
nzbcyclops reads an .nzb, extracts every article Message-ID, and checks article availability with persistent async NNTP connections. The default path is optimized for speed and low bandwidth: it uses STAT only, keeps all configured servers active from the start, and marks an article missing only after every server returns 430.
It also supports an optional deeper validation pass that samples present articles, downloads their BODY, and validates yEnc size, crc32/pcrc32, and multipart =ypart begin/end ranges.
- Async NNTP verification with persistent connection pools
- Active-active load balancing across multiple NNTP servers
STAT-only verification in normal mode- Missing detection only after all configured servers return
430 - Optional sampled deep checks with yEnc validation
- Streaming NZB parsing
- Real-time progress output
- Standard library only
- Python 3.10+
No external Python packages are required.
- Clone the repo:
git clone [email protected]:xbmc4lyfe/cyclops.git
cd cyclops- Create your NNTP config:
cp config.ini.example config.ini-
Edit
config.iniwith your server details. -
Run a verification:
python3 verify_nzb.py path/to/file.nzb --config config.iniNNTP servers are defined in an INI file with one section per server:
[server.primary]
host = news.example.com
port = 563
ssl = true
username = your_username
password = your_password
max_connections = 50
timeout = 10
[server.backup]
host = news2.example.com
port = 563
ssl = true
username = your_username
password = your_password
max_connections = 25
timeout = 10- All configured servers participate immediately.
- Total concurrency is the sum of
max_connectionsacross all servers. max_connectionsmust be at least1.timeoutis per network operation.
usage: verify_nzb.py [-h] --config CONFIG [--retries RETRIES]
[--missing-output MISSING_OUTPUT] [--deep-check]
[--sample-percent SAMPLE_PERCENT]
[--sample-seed SAMPLE_SEED] [--deep-output DEEP_OUTPUT]
nzb_path
Basic verification:
python3 verify_nzb.py nzbs/example.nzb --config config.iniWrite missing or indeterminate articles to a file:
python3 verify_nzb.py nzbs/example.nzb \
--config config.ini \
--missing-output missing.txtRetry transient failures:
python3 verify_nzb.py nzbs/example.nzb \
--config config.ini \
--retries 2Run the optional deep check on a sampled subset of present articles:
python3 verify_nzb.py nzbs/example.nzb \
--config config.ini \
--deep-check \
--sample-percent 2 \
--sample-seed 123 \
--deep-output deep.txt- Parses
Message-IDs from the NZB - Sends
STAT <message-id>over persistent NNTP connections - Marks an article
presentas soon as any server returns223 - Marks an article
missingonly when every configured server returns430 - Marks an article
error/indeterminatewhen all attempts are exhausted and at least one server failed transiently
This is the fastest mode and uses minimal bandwidth.
When --deep-check is enabled:
- Only articles already confirmed present are eligible
- A sample is chosen with
--sample-percent - Sampled articles are fetched with
BODY - The body is validated as yEnc:
=ybegin/=yend- decoded size
crc32orpcrc32- multipart
=ypart begin/endrange consistency
Deep check is useful for spotting corrupted segments, but it is intentionally sample-based unless you set --sample-percent 100.
The script prints a live progress line during execution and a final summary like:
summary: checked=85716 present=85716 missing=0 error/indeterminate=0 stat_requests=85716 elapsed=504.425s
If deep check is enabled, it also prints:
deep: sampled=858 ok=858 corrupt=0 error=0 body_requests=858 elapsed=12.345s
--missing-output: writes missing and indeterminate article IDs--deep-output: writes one line per sampled deep-check result
Run the test suite with:
python3 -B -m unittest -v- Normal mode verifies availability, not payload integrity
- Deep mode validates sampled yEnc articles, but it is not a PAR2 verifier
- Deep corruption checks use
BODY, so they are slower and consume more bandwidth thanSTAT