Transmission's watch dir scanner runs every ~10s, so a 10s deadline
could expire before the first scan completes. 30s gives 2-3 scan
cycles of headroom.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Suppress transmission-daemon stdout/stderr (background process)
- Create a watch/ subdir in the session temp config dir; start daemon
with --watch-dir instead of --no-watch-dir
- Replace transmission-remote -a with shutil.copy2 into the watch dir,
then poll until the daemon picks it up
- Save .torrent metadata files to tempfile.gettempdir() instead of the
wordlist directory; update cleanup to match
- Update TransmissionSession.add tests to cover the new watch-dir flow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After make install, run uv sync --reinstall-package hate_crack so
setuptools-scm regenerates the version from the new git tag. Without
this, the installed version stays at the old value and the update
checker loops indefinitely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_run_upgrade now runs `git pull && git fetch --tags && make install`
instead of the bespoke _install_system_deps function. make install is
idempotent, handles all platforms, installs system deps (transmission-
daemon, p7zip), rebuilds the Python package, and updates the CLI shim.
Remove the now-unnecessary uv binary pre-check (make install locates
it) and its stale test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add _install_system_deps() called by _run_upgrade() after a successful
git pull. Installs transmission-daemon (Linux) or transmission-cli brew
formula (macOS) so users who upgrade don't need to re-run make install
manually. Also corrects _run_upgrade to exit 1 on failure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevent implicit None return when no data row matches the expected
transmission-remote --info-files format. Also clarify README macOS
install note.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delete wordlists/kill_transmission.sh (replaced by Python extraction).
Update Makefile, Dockerfile.test, lima config, README, TESTING.md, and
test_dependencies.py to reference transmission-daemon/transmission-remote
instead of transmission-cli.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pass fetch_torrent_metadata (HTTP-only) to download_all_weakpass_torrents
so the batch collects all .torrent files first, then runs exactly one
TransmissionSession rather than N single-torrent sessions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split download_torrent_file into fetch_torrent_metadata (HTTP-only) and
a backwards-compat shim. Update weakpass_wordlist_menu,
download_all_weakpass_torrents, and download_weakpass_torrent to collect
.torrent files first, then run a single TransmissionSession batch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace check_transmission_cli with check_transmission_daemon, add
_pick_free_port, TransmissionSession context manager, and
run_torrent_session. A single transmission-daemon process handles all
selected torrents in parallel and tears down via transmission-remote
--exit on completion or interrupt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
download_official_wordlist now propagates KeyboardInterrupt (via
_streamed_download) so callers like list_and_download_official_wordlists
can catch it and return to the menu. The old test expected False to be
returned, which no longer matches the intended design.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix two test issues:
1. Patch time.sleep in hate_crack.api namespace to properly mock the module import (lines 384, 403). Tests were patching the global time.sleep, allowing real sleeps to execute and making tests slow.
2. Remove vacuous assertion in test_meta_refresh_redirect_uses_verbatim_url (line 440) that is always True due to URL domain mismatch. The equality assertion above it already covers the correctness requirement.
These changes speed up TestHashmobBackoff tests from slow to instant, and remove misleading logic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers the four new private helpers (_stream_response_to_file,
_streamed_download, _with_hashmob_backoff, _Hashmob429) and the
list_and_download_official_wordlists skip-existing path (10 new tests,
40 total in the file).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The worker() function inside fetch_all_weakpass_wordlists_multithreaded()
called q.task_done() inside the except block. If the except block itself
throws (e.g., KeyboardInterrupt during print), task_done() is skipped
and q.join() hangs forever.
Move task_done() to a finally block to ensure it always runs, allowing
the queue manager to correctly track completion even if error handling
itself fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the hardcoded `total_pages=67` default with `None` (auto-detect).
On first call the function probes page 1 to read `last_page` from the
Inertia `data-page` payload; if found it drives the thread pool with that
count, if not found it falls back to a sequential walk until an empty page
is returned, and if the probe itself fails it degrades gracefully to 67.
Callers that pass `total_pages` explicitly are unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Before each download in both the 'all' and indexed-selection branches,
check whether the destination file already exists and has nonzero size
(accounting for .7z archives whose extracted name drops the suffix).
Mirrors the same guard already in place for rule downloads.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix 1: _with_hashmob_backoff no longer sleeps after the last attempt —
breaks out of the loop immediately so we don't waste up to 300s before
giving up.
Fix 2: download_hashmob_wordlist and download_hashmob_rule now open a
single streaming connection inside _attempt() and pass the already-open
response to _stream_response_to_file, eliminating the probe-then-reopen
pattern that caused 429 responses on the second request to bypass the
backoff retry machinery.
Fix 3: HashviewAPI.download_wordlist wraps _stream_response_to_file in
try/finally so resp.close() is guaranteed even on KeyboardInterrupt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add _stream_response_to_file and _streamed_download helpers that
implement atomic .part-file writes, unified progress bars, and
KeyboardInterrupt cleanup in one place. Add _with_hashmob_backoff
for bounded 429 retry logic (max 6 attempts, step-doubling delay)
and _Hashmob429 sentinel exception so callers signal rate-limits
without re-implementing the backoff loop.
Refactor all four callers onto the helpers:
- download_hashmob_wordlist: removes inline backoff loop and fixes
the redirect recursion bug (redirect URL was passed as file_name,
bypassing the API prefix logic)
- download_hashmob_rule: removes duplicate backoff loop; keeps the
60-entry pinned URL dict and 404→alt_url fallback verbatim
- download_official_wordlist: delegates streaming to _streamed_download;
keeps .7z extraction tail
- HashviewAPI.download_wordlist: uses session.get + _stream_response_to_file
so cookie auth is preserved; removes the separate progress-bar
implementation
All public signatures are unchanged; 40 existing tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consolidates Pushover notification menu options under a new submenu
at main-menu option 82, and promotes notify_per_crack_enabled from
config-file-only to a runtime toggle with a UI-level guard that
refuses to enable it while global notifications are OFF.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Isolates blame churn from the ruff format pass in commit 9b684bb,
which reformatted pre-existing lines in hate_crack/main.py that are
outside the scope of the notifications-submenu feature.
Enable locally with:
git config blame.ignoreRevsFile .git-blame-ignore-revs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Added `test_labels_refresh_between_iterations` that sequences a toggle
then captures the submenu items twice, asserting the label flips
between renders. Guards against a regression where `items` is hoisted
out of the while-loop.
Also documented why the inline `from hate_crack.menu import
interactive_menu` is not actually redundant with the module-scope
import at main.py:77 — it re-reads the attribute on every call, which
is what lets tests patch `hate_crack.menu.interactive_menu`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Promotes notify_per_crack_enabled from config-file-only to a runtime
toggle with the same style (global-decl, default-init, OSError-via-logger)
as the existing toggle_enabled, with full TDD coverage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add save_per_crack_enabled() as a data-layer sibling to save_enabled(),
using the same _atomic_rewrite primitive so mid-write crashes cannot
corrupt config.json.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TDD-structured plan with 7 tasks covering: atomic per-crack config
persistence, runtime toggle in the notify module, UI handler with
global-OFF guard, submenu dispatcher, main-menu rewiring in both
main.py and the hate_crack.py proxy, README documentation, and a
final lint+test verification task.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Design doc for consolidating Pushover menu options under a new
submenu at main-menu option 82, promoting notify_per_crack_enabled
to a runtime toggle, and guarding it so per-crack cannot be enabled
while global Pushover notifications are OFF.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds menu option 84 (Send Test Pushover Notification) so users can verify
their Pushover credentials without running an attack. Ignores the global
notify_enabled toggle by design (prints a note when OFF).
tests/test_random_rules_attack.py purges and re-imports hate_crack.*
modules, which leaves main._notify pointing at a different notify
object than a top-level patch("hate_crack.notify._send_pushover")
would touch. Under the full suite that caused the test's mock to
miss and the production call to hit the real Pushover API.
Switch to patch.object(hc_main, "_notify") -- same pattern as
tests/test_run_hcat_cmd.py but anchored to the exact module object
already bound as hc_main, so it is immune to sys.modules churn
regardless of import order. Drop the now-redundant _install_settings
helper and _reset_notify_state fixture.
Canned send path so a user can verify Pushover credentials without
running an attack. Ignores the global notify_enabled toggle — the test's
purpose is to confirm the pipe is live, not that attack notifications
are enabled. Prints a note when the global toggle is OFF so the user is
not confused later.
Five-task TDD plan: unit tests + function, main.py menu wiring,
hate_crack.py proxy wiring, parametrized menu test, full verification.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Why: user needs a one-click way to verify Pushover credentials without
running an attack. This spec captures the menu wiring, credential/toggle
handling (option A — ignore global toggle), and test plan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hate_crack.main calls notify.init() at import time with whatever
config.json is resolved from the developer's environment (often
~/.hate_crack/config.json). If that file has notify_enabled: true, the
per-attack prompt in attacks.py fires input() during tests and trips
pytest's capture fd, failing unrelated tests.
Add an autouse conftest fixture that clears notify module state before
and after every test so the suite is hermetic regardless of local
config. Notify-specific tests already use their own
clear_state_for_tests() fixture; this change covers the rest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>