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.
Files changed (37) hide show
  1. pyspamcop-0.1.0/.claude/settings.local.json +7 -0
  2. pyspamcop-0.1.0/.gitignore +173 -0
  3. pyspamcop-0.1.0/.python-version +1 -0
  4. pyspamcop-0.1.0/CLAUDE.md +73 -0
  5. pyspamcop-0.1.0/LICENSE +674 -0
  6. pyspamcop-0.1.0/Makefile +79 -0
  7. pyspamcop-0.1.0/PKG-INFO +10 -0
  8. pyspamcop-0.1.0/README.md +174 -0
  9. pyspamcop-0.1.0/pyproject.toml +85 -0
  10. pyspamcop-0.1.0/src/pyspamcop/config.py +111 -0
  11. pyspamcop-0.1.0/src/pyspamcop/db.py +126 -0
  12. pyspamcop-0.1.0/src/pyspamcop/domain.py +212 -0
  13. pyspamcop-0.1.0/src/pyspamcop/exception.py +15 -0
  14. pyspamcop-0.1.0/src/pyspamcop/html.py +388 -0
  15. pyspamcop-0.1.0/src/pyspamcop/http/client.py +87 -0
  16. pyspamcop-0.1.0/src/pyspamcop/main.py +63 -0
  17. pyspamcop-0.1.0/src/pyspamcop/py.typed +0 -0
  18. pyspamcop-0.1.0/src/pyspamcop/runner.py +126 -0
  19. pyspamcop-0.1.0/src/pyspamcop/spamcop/client.py +46 -0
  20. pyspamcop-0.1.0/tests/fixtures/after_login.html +75 -0
  21. pyspamcop-0.1.0/tests/fixtures/bounce_error.html +70 -0
  22. pyspamcop-0.1.0/tests/fixtures/boundary.html +208 -0
  23. pyspamcop-0.1.0/tests/fixtures/failed_load_header.html +59 -0
  24. pyspamcop-0.1.0/tests/fixtures/login_failed.html +95 -0
  25. pyspamcop-0.1.0/tests/fixtures/mailhost_problem.html +137 -0
  26. pyspamcop-0.1.0/tests/fixtures/missing_sendreport_form.html +174 -0
  27. pyspamcop-0.1.0/tests/fixtures/post_reporting.html +84 -0
  28. pyspamcop-0.1.0/tests/fixtures/reports_disabled.html +110 -0
  29. pyspamcop-0.1.0/tests/fixtures/sample_cfg.yaml +15 -0
  30. pyspamcop-0.1.0/tests/fixtures/sendreport_form_ok.html +209 -0
  31. pyspamcop-0.1.0/tests/spamcop/test_client.py +27 -0
  32. pyspamcop-0.1.0/tests/test_config.py +58 -0
  33. pyspamcop-0.1.0/tests/test_db.py +200 -0
  34. pyspamcop-0.1.0/tests/test_domain.py +133 -0
  35. pyspamcop-0.1.0/tests/test_html_parser.py +210 -0
  36. pyspamcop-0.1.0/tests/test_http_client.py +19 -0
  37. pyspamcop-0.1.0/uv.lock +500 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(grep -E \"\\\\.\\(html|yaml\\)$\")"
5
+ ]
6
+ }
7
+ }
@@ -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/...`).