pyspamcop 0.1.0__tar.gz
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.
- pyspamcop-0.1.0/.claude/settings.local.json +7 -0
- pyspamcop-0.1.0/.gitignore +173 -0
- pyspamcop-0.1.0/.python-version +1 -0
- pyspamcop-0.1.0/CLAUDE.md +73 -0
- pyspamcop-0.1.0/LICENSE +674 -0
- pyspamcop-0.1.0/Makefile +79 -0
- pyspamcop-0.1.0/PKG-INFO +10 -0
- pyspamcop-0.1.0/README.md +174 -0
- pyspamcop-0.1.0/pyproject.toml +85 -0
- pyspamcop-0.1.0/src/pyspamcop/config.py +111 -0
- pyspamcop-0.1.0/src/pyspamcop/db.py +126 -0
- pyspamcop-0.1.0/src/pyspamcop/domain.py +212 -0
- pyspamcop-0.1.0/src/pyspamcop/exception.py +15 -0
- pyspamcop-0.1.0/src/pyspamcop/html.py +388 -0
- pyspamcop-0.1.0/src/pyspamcop/http/client.py +87 -0
- pyspamcop-0.1.0/src/pyspamcop/main.py +63 -0
- pyspamcop-0.1.0/src/pyspamcop/py.typed +0 -0
- pyspamcop-0.1.0/src/pyspamcop/runner.py +126 -0
- pyspamcop-0.1.0/src/pyspamcop/spamcop/client.py +46 -0
- pyspamcop-0.1.0/tests/fixtures/after_login.html +75 -0
- pyspamcop-0.1.0/tests/fixtures/bounce_error.html +70 -0
- pyspamcop-0.1.0/tests/fixtures/boundary.html +208 -0
- pyspamcop-0.1.0/tests/fixtures/failed_load_header.html +59 -0
- pyspamcop-0.1.0/tests/fixtures/login_failed.html +95 -0
- pyspamcop-0.1.0/tests/fixtures/mailhost_problem.html +137 -0
- pyspamcop-0.1.0/tests/fixtures/missing_sendreport_form.html +174 -0
- pyspamcop-0.1.0/tests/fixtures/post_reporting.html +84 -0
- pyspamcop-0.1.0/tests/fixtures/reports_disabled.html +110 -0
- pyspamcop-0.1.0/tests/fixtures/sample_cfg.yaml +15 -0
- pyspamcop-0.1.0/tests/fixtures/sendreport_form_ok.html +209 -0
- pyspamcop-0.1.0/tests/spamcop/test_client.py +27 -0
- pyspamcop-0.1.0/tests/test_config.py +58 -0
- pyspamcop-0.1.0/tests/test_db.py +200 -0
- pyspamcop-0.1.0/tests/test_domain.py +133 -0
- pyspamcop-0.1.0/tests/test_html_parser.py +210 -0
- pyspamcop-0.1.0/tests/test_http_client.py +19 -0
- pyspamcop-0.1.0/uv.lock +500 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
|
|
110
|
+
# pdm
|
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
112
|
+
#pdm.lock
|
|
113
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
114
|
+
# in version control.
|
|
115
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
116
|
+
.pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
121
|
+
__pypackages__/
|
|
122
|
+
|
|
123
|
+
# Celery stuff
|
|
124
|
+
celerybeat-schedule
|
|
125
|
+
celerybeat.pid
|
|
126
|
+
|
|
127
|
+
# SageMath parsed files
|
|
128
|
+
*.sage.py
|
|
129
|
+
|
|
130
|
+
# Environments
|
|
131
|
+
.env
|
|
132
|
+
.venv
|
|
133
|
+
env/
|
|
134
|
+
venv/
|
|
135
|
+
ENV/
|
|
136
|
+
env.bak/
|
|
137
|
+
venv.bak/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# Pyre type checker
|
|
155
|
+
.pyre/
|
|
156
|
+
|
|
157
|
+
# pytype static type analyzer
|
|
158
|
+
.pytype/
|
|
159
|
+
|
|
160
|
+
# Cython debug symbols
|
|
161
|
+
cython_debug/
|
|
162
|
+
|
|
163
|
+
# PyCharm
|
|
164
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
165
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
166
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
167
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
168
|
+
#.idea/
|
|
169
|
+
|
|
170
|
+
# PyPI configuration file
|
|
171
|
+
.pypirc
|
|
172
|
+
*.pm
|
|
173
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project overview
|
|
6
|
+
|
|
7
|
+
pyspamcop is a Python web crawler that automates finishing [SpamCop.net](https://www.spamcop.net) spam reports. It logs into the SpamCop website, fetches pending reports, parses the HTML, and submits confirmations — sequentially, with forced delays to be polite.
|
|
8
|
+
|
|
9
|
+
The project is a rewrite of the Perl-based [App-SpamcupNG](https://github.com/glasswalk3r/App-SpamcupNG). The original Perl code is kept under `legacy/` for reference.
|
|
10
|
+
|
|
11
|
+
## Environment setup
|
|
12
|
+
|
|
13
|
+
Uses `uv` for dependency management and Python 3.12.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
make init # create venv and install all deps (uv venv && uv sync)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
make lint # ruff check --fix && ruff format && mypy
|
|
23
|
+
make unit # pytest (excluding integration tests)
|
|
24
|
+
make integration # pytest -m integration
|
|
25
|
+
make coverage # pytest --cov=pyspamcop tests/
|
|
26
|
+
make clean # remove build/test/cache artifacts
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Run a single test file:
|
|
30
|
+
```bash
|
|
31
|
+
python -m pytest tests/test_html_parser.py -v
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Run a single test by name:
|
|
35
|
+
```bash
|
|
36
|
+
python -m pytest tests/test_html_parser.py -k "test_find_errors" -v
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
src/pyspamcop/
|
|
43
|
+
main.py # CLI entry point (argparse); entrypoint is pyspamcop:main; log_config() sets up logging
|
|
44
|
+
runner.py # main_loop()/run_account(): the login -> analyse -> confirm -> record cycle
|
|
45
|
+
db.py # Recorder: SQLite persistence of Summary objects (see Key design patterns)
|
|
46
|
+
config.py # YAML config loading; Configuration + EmailAccount dataclasses
|
|
47
|
+
domain.py # All domain models: Message hierarchy, Receiver, EmailHeader, MessageAge, Summary
|
|
48
|
+
html.py # BeautifulSoup HTML parsers: find_errors, find_warnings, find_receivers,
|
|
49
|
+
# find_next_id, find_header, find_best_contacts, report_form
|
|
50
|
+
exception.py # BaseExceptionError and UnknownReceiverFormat
|
|
51
|
+
http/client.py # HTTPClient: concrete httpx-based implementation of ClientBase
|
|
52
|
+
spamcop/client.py # ClientBase: abstract base class for the SpamCop HTTP session
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Key design patterns
|
|
56
|
+
|
|
57
|
+
**Message hierarchy** (`domain.py`): `Message` is an abstract base class with two branches — `UnrecoverableSpamReportMessage` (skip this report, move on) and `WarningMessage` (report can still complete). Concrete types include `MailHostMessage`, `EmailAddressBounceMessage`, `SpamHeaderMessage`, `MailhostForgeryMessage`, and `FreshSpamMessage`. Each implements `is_related(text)`, `extract(tag)`, and `complete_message()`.
|
|
58
|
+
|
|
59
|
+
**HTML parsing** (`html.py`): All SpamCop page parsing lives here as standalone functions taking a `BeautifulSoup` object. Tests use HTML fixtures from `tests/fixtures/` rather than live HTTP calls. `find_best_contacts` identifies reporting candidates on the analysis preview page; `find_receivers` parses the post-submission confirmation page.
|
|
60
|
+
|
|
61
|
+
**Client abstraction** (`spamcop/client.py` + `http/client.py`): `ClientBase` defines the abstract interface (`login`, `is_authenticated`, `spam_report`, `confirm_report`, `last_response`). `HTTPClient` extends it using `httpx`. Several methods on `HTTPClient` are still stubs (`spam_report`, `confirm_report`, `last_response`).
|
|
62
|
+
|
|
63
|
+
**Lookup-table registry** (`db.py`): `Recorder` persists a `Summary` to SQLite, normalizing repeated string values (email content type, spam age unit, charset, mailer, receiver address) into small reference tables via a get-or-create pattern (`_upsert_lookup`, using `INSERT OR IGNORE` + `SELECT` against each table's `UNIQUE` column). The table/column pairs are declared exactly once, in the `_LOOKUP_TABLES` tuple of `LookupTable(name, column)` — both the `CREATE TABLE` DDL (`_SCHEMA`) and the runtime column lookup (`_LOOKUP_COLUMN`) are derived from it, so schema and code can't drift apart. Add a new lookup table by adding one `LookupTable` entry, not by editing DDL and code separately.
|
|
64
|
+
|
|
65
|
+
### Configuration file
|
|
66
|
+
|
|
67
|
+
Default path: `~/.pyspamcop.yaml` (override with `--config`). Schema documented in `README.md`. Parsed by `read_config()` in `config.py`.
|
|
68
|
+
|
|
69
|
+
### Test conventions
|
|
70
|
+
|
|
71
|
+
- Unit tests use HTML fixtures in `tests/fixtures/` loaded via `read_fixture()` helper.
|
|
72
|
+
- Integration tests are marked `@pytest.mark.integration` and excluded from `make unit`.
|
|
73
|
+
- Tests run from the project root; fixture paths are relative (`tests/fixtures/...`).
|