Commit Graph

1836 Commits

Author SHA1 Message Date
Estrella Pan
487bdfec54 fix(api): harden pre-auth setup endpoints (#1041, #1044)
/setup/test-downloader now validates the URL scheme (http/https only) like
test-rss already did, and accepts qBittorrent 5.2's 204 login response.
Raw exception and response detail is no longer echoed back from the
pre-authentication setup endpoints; it goes to the server log only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014w1Z6Nxy6XTRgkFXqPr9Zh
2026-07-02 11:39:10 +02:00
Estrella Pan
e6aac59954 fix(downloader): make torrent deletion actually delete and report failures (#1046)
torrents_delete received a Python list, which httpx form-encodes as repeated
'hashes' fields that qBittorrent ignores — pipe-join it as the API expects.
Check the response status instead of logging success unconditionally, and
propagate the failure up through delete_torrent to the API ResponseModel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014w1Z6Nxy6XTRgkFXqPr9Zh
2026-07-02 11:39:10 +02:00
Estrella Pan
930dd01220 fix(downloader): accept qBittorrent 5.2 login response and close leaked client (#1044, #1034, #1043)
qBittorrent >= 5.2 answers a successful login with HTTP 204 and an empty
body instead of 200 + 'Ok.', so every login was treated as bad credentials.
Accept both variants and keep 200 + 'Fails.' as a failure.

Also close the httpx.AsyncClient when auth() fails: DownloadClient.__aenter__
raises before __aexit__ can run, so each failed connect leaked a client and
its connection pool (qb and aria2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014w1Z6Nxy6XTRgkFXqPr9Zh
2026-07-02 11:39:10 +02:00
Estrella Pan
8169a2cc13 fix(downloader): cap qB httpx keepalive to prevent stale-socket storms (#1028)
Closes #984

Apply httpx.Limits(keepalive_expiry=30, max_connections=10, max_keepalive_connections=5) to the qBittorrent AsyncClient — same recipe as #1018 for the RSS side. Prevents the "Server disconnected" cascade when a proxy / NAS silently reaps idle sockets between renamer cycles.
2026-04-19 13:10:30 +02:00
Estrella Pan
1fbc0e3543 Merge pull request #1027 from EstrellaXD/fix/batch-3.2.7-followup
fix: batch bug fixes for 3.2.7 (#1016, #983, #1025, #994, #1015)
2026-04-19 13:04:17 +02:00
Estrella Pan
07522ae284 ci(release): include pyproject.toml / uv.lock / requirements.txt (#994, #1015)
The release artifact only zipped backend/src, omitting pyproject.toml and
uv.lock, so local deployments couldn't install dependencies. #994 also
reported the missing requirements.txt after the uv migration in 3.2.0.

Install uv in the release job and generate a production-only
requirements.txt via `uv export --no-dev`, then bundle all three alongside
src in the app zip.

Closes #994
Closes #1015
2026-04-19 13:01:27 +02:00
Estrella Pan
90239b0843 fix(parser): stop destroying titles without a [group] prefix (#1025)
prefix_process called re.sub(f".{group}.", "", raw) unconditionally. When
group was empty (torrents that don't start with [group]), the pattern
degenerated to ".." and every pair of characters in the title was deleted,
leaving a stub the downstream splitter couldn't turn into title_en/zh/jp.

Guarded the substitution with `if group:`. Titles like
"冰之城墙「氷の城壁」The Ramparts of Ice S01E02 1080p 日英双语-多国字幕"
and "Girls Band Cry S01E05 ..." now parse correctly. Updated #764's test
which had been pinned to the broken behavior.

Closes #1025
2026-04-19 13:00:35 +02:00
Estrella Pan
813078913a fix(network): follow 302 redirects in shared httpx client (#983)
Mikanime's mirror (and some CDN-fronted sources) respond with 302 to the
canonical host. httpx AsyncClient defaults to follow_redirects=False, so
raise_for_status surfaced the 302 as an HTTPStatusError and the RSS pull
failed in a retry loop.

Enable follow_redirects=True for every construction of the shared client
(proxy, socks5, and direct branches) via a shared kwargs dict.

Closes #983
2026-04-19 12:57:40 +02:00
Estrella Pan
4e9d8b34ab fix(downloader): parse Windows qB save_path on Linux hosts (#1016)
When AB runs in a Linux container and qBittorrent runs on a Windows host,
qB returns save_path strings with backslash separators. PurePosixPath
treats those as a single unsplittable segment, so _path_to_bangumi never
matched "Season N" and every non-S1 bangumi collapsed to Season 1.

PureWindowsPath accepts both "\\" and "/" as separators, so using it for
the parsing side handles both layouts. Path generation (_gen_save_path /
_join_path) still uses the platform-conditional Path alias, so AB keeps
emitting native paths for the host it runs on.

Diagnosis and fix credited to the reporter in #1016.

Closes #1016
2026-04-19 12:56:46 +02:00
ZzzzSsssWwww
d849edf89f fix(network): harden shared httpx client against stale connections (#1018)
RSS loop runs every 900s but reuses the same httpx.AsyncClient connection pool, well past server keep-alive expiry (60-120s). Reusing dead sockets produced ConnectTimeout errors over time.

- Configure httpx.Limits with keepalive_expiry=60s so idle connections are dropped proactively
- reset_shared_client() called on retry paths to force a fresh pool after ConnectError
- asyncio.Semaphore(5) in refresh_rss() caps concurrent RSS fetches to avoid tripping site rate limits

Closes #1008
Related #1010, #742, #701
2026-04-19 12:50:18 +02:00
ZzzzSsssWwww
6235892de2 fix(rss): cascade-delete torrents when deleting RSSItem (#1019)
Deleting an RSSItem failed with sqlite3.IntegrityError: FOREIGN KEY constraint failed when child torrents still referenced it, leaving the sidebar entry stuck in the UI.

- delete() now removes referencing torrents in the same transaction before removing the RSSItem
- Adds missing session.rollback() in the exception path
- rss_loop wraps each iteration to skip stale RSSItems deleted mid-loop via API
- Tests enable PRAGMA foreign_keys=ON to match production and gain 5 cascade-delete regression tests

Closes #1010
Closes #1017
2026-04-19 12:50:09 +02:00
Alex Tazman
117c24ce77 fix(checker): respect SSL setting when probing qBittorrent (#1014)
Startup probe hardcoded http:// ignoring settings.downloader.ssl, causing 5-minute startup delay when qBittorrent is HTTPS-only or redirects http→https.

Now derives the scheme from settings.downloader.ssl.
2026-04-19 12:49:59 +02:00
stonebeta
e819a77656 perf(entrypoint): narrow chown scope to skip .venv (#1011)
Reduces container startup time on low-spec devices (TrueNAS etc.) by limiting recursive chown to /app/data, /app/config, /home/ab instead of /app.

Closes #969 (related performance).
2026-04-19 12:49:48 +02:00
Estrella Pan
0dd8ce99ad test: add regression tests for issues #992 and #1005
- #992: title_parser returns None for non-episodic resources instead of crashing
- #1005: BangumiDatabase.search_official_title method exists and works correctly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 08:14:06 +01:00
Estrella Pan
d980c3c714 fix(parser): handle None return from raw_parser to avoid AttributeError (#992)
When raw_parser fails to parse non-episodic resources (movies, collections),
it returns None. Add guard clause in title_parser to skip these gracefully
instead of crashing on attribute access. Downgrade log level from error to
info since this is expected behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 08:12:18 +01:00
Estrella Pan
66a2b45f57 Merge remote-tracking branch 'origin/3.2-dev' into 3.2-dev 2026-03-01 08:07:26 +01:00
Estrella Pan
1d35b654e7 Merge pull request #998 from HuajunGao/fix/bangumi-search-official-title 2026-03-01 08:02:40 +01:00
Estrella Pan
ccafd04432 Merge pull request #1003 from EstrellaXD/3.2-dev
Release 3.2.5
3.2.5
2026-02-28 22:52:18 +01:00
Estrella Pan
61ff20fef6 fix(api): preserve masked passwords on config save and allow private IPs in setup (#995, #1001)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:48:38 +01:00
Estrella Pan
17de8fa8fe Merge pull request #1000 from HuajunGao/fix/mcp-sse-none-response
Thanks for catching this! Clean fix for the SSE disconnect issue.
2026-02-28 22:46:44 +01:00
Estrella Pan
903ecaf84a Merge pull request #999 from HuajunGao/fix/default-filter-regex
Thanks for the fix! The regex alignment makes sense.
2026-02-28 22:46:42 +01:00
Estrella Pan
62ee9f02ec Merge pull request #997 from HuajunGao/fix/config-sensitive-field-overwrite 2026-02-28 22:32:00 +01:00
HuajunGao
ab64186a38 fix(mcp): return Response() from handle_sse to prevent NoneType error
When an MCP client disconnects, Starlette tries to call the return value
of the handle_sse endpoint as an ASGI response. Since the function had
no return statement it returned None, causing:

  TypeError: 'NoneType' object is not callable

The mcp library's own SseServerTransport docstring explicitly documents
this requirement. Fix by adding 'return Response()' and importing
starlette.responses.Response.
2026-02-28 14:54:43 +11:00
HuajunGao
a263c0821e fix(config): align default filter regex from \d+-\d to \d+-\d+
The per-bangumi default in Bangumi model already uses \d+-\d+ to filter
batch/collection torrents (e.g. '01-13'). The global RSSParser default
was inconsistently using \d+-\d (one fewer +), which has subtly different
matching behaviour.

Align both defaults to \d+-\d+ for consistency.
2026-02-28 14:32:59 +11:00
HuajunGao
d4e50509dd fix(database): add missing search_official_title method to BangumiDatabase
BangumiDatabase.search_official_title() was called in
notification/manager.py (_get_poster) but never implemented,
causing a RenameThread crash:

  'BangumiDatabase' object has no attribute 'search_official_title'

Add the method as a simple exact-match lookup on Bangumi.official_title,
consistent with the existing search_id / search_rss pattern.
2026-02-28 12:14:50 +11:00
HuajunGao
6fb8421000 fix(config): restore sensitive fields before saving to prevent credential overwrite
When GET /config/get returns config to the frontend, sensitive fields
(password, token, api_key, secret) are masked as '********'.  If the
user changes any non-sensitive setting (e.g. ssl: true → false) and
saves, the frontend sends back the masked placeholder verbatim.  The
backend was saving it directly, overwriting the real credential with
the literal string '********', breaking authentication silently.

Add _restore_sensitive() to substitute any '********' placeholder with
the current in-memory value before writing to disk.  Non-sensitive
fields and genuinely new values are unaffected.

Fixes #995
2026-02-28 11:40:25 +11:00
Estrella Pan
e1b90c9eec 3.2.4
3.2.4
3.2.4
2026-02-24 19:56:57 +01:00
Estrella Pan
ded24b10da fix(downloader): fix qBittorrent SSL connection and rename verification (#923)
- Decouple HTTPS scheme selection from TLS certificate verification:
  `verify=False` always, since self-signed certs are the norm for
  home-server/NAS/Docker qBittorrent setups
- Bump connect timeout from 3.1s to 5.0s for slow TLS handshakes
- Add actionable error messages when HTTPS connection fails
- Fix `continue` → `break` bug in torrents_rename_file verification loop
- Consolidate json imports to top-level
- Add 31 unit tests for QbDownloader constructor, auth, and error handling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 19:52:50 +01:00
Estrella Pan
fe1858f0e6 fix(webui): close EventSource and clear timers on component unmount
Prevent memory leaks by ensuring the search EventSource connection is
closed when the modal unmounts and setTimeout handles are cleared in
copy-to-clipboard flows across modal components.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 19:44:49 +01:00
Estrella Pan
ead16ba4cf feat(parser): add fallback episode parser for TITLE_RE failures (#876, #910, #773)
Add _fallback_parse() tried when TITLE_RE.match() returns None, using two
regex patterns to extract episode numbers from formats the main regex misses:
- digits before [ bracket (issues #876, #910)
- compound [02(57)] format (issue #773)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:12:24 +01:00
Estrella Pan
c261caa022 test(parser): add regression tests for issue #990
10 tests covering the full bug chain:
- raw_parser misparses leading number as episode
- TitleParser.raw_parser returns None for unparseable titles
- add_title_alias rejects None and empty string
- _get_aliases_list filters null values from JSON
- get_all_title_patterns skips None title_raw
- match_torrent and match_list handle corrupted data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 08:44:18 +01:00
Estrella Pan
c7206a351f fix(parser): prevent crash on titles starting with numbers (#990)
Titles like "29 岁单身冒险家的日常" cause the regex to match the leading
number as episode, leaving title_raw as None. This cascades into storing
null aliases and crashing match_torrent with TypeError.

- Fall back to title_jp when title_en and title_zh are both None
- Return None from raw_parser when no title can be extracted
- Reject None/empty aliases in add_title_alias
- Filter null values from parsed title_aliases JSON
- Skip None title_raw in get_all_title_patterns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 08:36:25 +01:00
Estrella Pan
b57d3c49ae feat(security): add security config UI and improve auth/MCP security
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 17:19:14 +01:00
Estrella Pan
a0bf878b7e feat(calendar): add drag-and-drop to assign unknown bangumi to weekdays
Allow users to drag bangumi cards from the "Unknown" section into weekday
columns in the calendar view. Manual assignments are locked so calendar
refresh from Bangumi.tv doesn't overwrite them. A reset button lets users
unlock and send cards back to Unknown.

Backend:
- Add weekday_locked field to Bangumi model (migration v9)
- Add PATCH /api/v1/bangumi/{id}/weekday endpoint
- Skip locked items in refresh_calendar()

Frontend:
- Add vuedraggable for smooth drag-and-drop
- Pin indicator and unpin button on manually-assigned cards
- Drop zone highlighting during drag
- i18n strings for drag/pin/unpin

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 14:21:00 +01:00
Estrella Pan
ba61194d3d Merge pull request #972 from EstrellaXD/3.2-dev
3.2.3
3.2.3
2026-02-23 12:57:06 +01:00
Estrella Pan
326d31dd0f Merge origin/main into 3.2-dev (resolve version conflict)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 12:55:18 +01:00
Estrella Pan
b1497aa6b1 fix(test): invalidate module-level bangumi cache between tests
The BangumiDatabase.search_all() uses a module-level TTL cache that
persists across tests using different in-memory SQLite databases.
This caused test_migrate_preserves_existing_data and test_migrate_idempotent
to return stale cached results (1 bangumi instead of 2).

Add an autouse fixture in conftest.py to clear the cache before and after
each test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 12:11:28 +01:00
Estrella Pan
eefe85db4b chore: bump version to 3.2.3 and update changelog for release
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 12:06:30 +01:00
Estrella Pan
adf44d140a fix(renamer,rss): preserve episode 0 specials and handle invalid filter regex
- Skip episode offset for episode 0 (specials/OVAs) to prevent overwriting
  regular episodes (fixes #977)
- Catch re.PatternError in RSS filter compilation and fall back to literal
  matching when user filter contains invalid regex chars (fixes #974)
- Remove Aria2 and Transmission from README supported downloaders list
  (addresses #987)
- Add regression tests for issues #974, #976, #977, #986

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 12:03:59 +01:00
Estrella Pan
e82e6ab128 fix(ui): fix auth routing, i18n init, and component lifecycle issues
- useAuth: replace watcher with explicit router.replace on login/logout
- useMyI18n: create single i18n instance at module level (avoid dupes)
- usePasskey: detect WebAuthn support synchronously (no onMounted)
- main.ts: import i18n from hook module instead of calling composable
- ab-add-rss: hoist useApi composables outside functions to avoid
  recreating them on each call
- calendar: prevent duplicate refreshes when already refreshing
- downloader: guard interval polling against stale activation state
- router: only mark setupChecked on successful status check
- log store: stop SSE log updates on logout
- i18n: add missing "edit" translation key (en + zh-CN)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:46:56 +01:00
Estrella Pan
52580d08c8 fix(downloader): improve client interfaces and renamer reliability
- Aria2: add stub methods for full duck-typing compatibility
- MockDownloader: add verify parameter to rename_file signature
- DownloadClient: raise ConnectionError on auth failure
- Path: fallback bangumi_name from torrent_name when path is flat
- Renamer: remove unused check_pool and dead compare_ep_version
- Pass torrent_name to _path_to_bangumi for better name resolution
- Remove check_pool unit test (feature removed)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:46:44 +01:00
Estrella Pan
41298f2f8e fix(backend): improve database migrations, parsers, and network handling
Database:
- Add error handling and per-step version tracking in migrations
- Enable SQLite foreign keys via PRAGMA on connect
- Fix SQLAlchemy .is_(None) usage, add session.merge() for detached
- Batch commit for semantic alias merges
- Quote table/field names in fill-null-defaults SQL
- Guard against empty user data in migration

Parsers:
- TMDB: bounded LRU cache (512), asyncio.gather for parallel season
  fetches, fix season regex \d -> \d+, null-safe year, fix id shadowing
- Raw parser: re.escape() for group/prefix regex, None guard on match
- OpenAI: handle Pydantic model_dump, catch ValueError

Network:
- Null-safe get_html() return
- Error handling per RSS item in mikan parser
- Progressive retry delays (5/15/45/120/300s) with specific exceptions
- Platform detection via sys.platform instead of path heuristic
- Move filter cache to instance variable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:46:35 +01:00
Estrella Pan
ec4aca5aba fix(core): improve program lifecycle and background task management
- Use per-task stop events instead of shared stop_event to prevent
  stopping one task from killing all others
- Track running state via _tasks_started flag instead of stop_event
- Add error handling in RSS, rename, scan, and calendar loops
- Make restart() resilient to stop failures (catch and continue)
- Cache downloader status check with 60s TTL
- Fix _startup_done set before start() completes (race condition)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:46:22 +01:00
Estrella Pan
c7c709fa66 fix(security): harden auth, JWT, WebAuthn, and API endpoints
- Persist JWT secret to config/.jwt_secret (survives restarts)
- Change active_user from list to dict with timestamps
- Extract username from cookie token instead of list index
- Add SSRF protection (_validate_url) for setup test endpoints
- Mask sensitive config fields (password, api_key, token, secret)
- Add auth guards to notification test endpoints
- Fix path traversal in /posters endpoint using resolved path check
- Add CORS middleware with empty allow_origins
- WebAuthn: add challenge TTL (300s), max capacity (100), cleanup
- Remove hardcoded default password from User model
- Use timezone-aware datetime in passkey models
- Adapt unit tests for active_user dict and cookie-based auth

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:46:12 +01:00
Estrella Pan
339166508b test(e2e): add comprehensive E2E integration test suite
67 tests across 11 phases exercising the full AutoBangumi workflow
against Docker infrastructure (qBittorrent + mock RSS server).
Covers setup wizard, auth, config, RSS CRUD, bangumi, downloader,
program lifecycle, log, search, notification, and credential updates
with both happy paths and error conditions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:44:03 +01:00
Estrella Pan
6cbe7090fe fix(database): clean up torrent records on bangumi deletion
When a bangumi was deleted, its associated Torrent records remained in
the database. This prevented re-downloading the same torrents if the
user re-added the anime, because check_new() deduplicates by URL and
would filter out the orphaned records.

Now delete_rule() removes Torrent records before deleting the Bangumi,
so re-adding the same anime correctly treats those torrents as new.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 09:45:58 +01:00
Estrella Pan
9d4bd48ce5 test(parser): expand coverage for raw, torrent, and path parsers
Add 13 new test cases across three parser test files:
- raw_parser: Chinese 第二季, 2160p resolution, bracketed Season N, multi-group with Chinese punctuation, English-only title
- torrent_parser: EP format, tc/zh-tw subtitle, no-language subtitle (ValidationError), multi-level path, [NNvN] version suffix
- path_parser: season=2/no-offset, large positive offset, offset yielding exactly Season 1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 20:48:53 +01:00
Estrella Pan
fb2dd17c0c feat(mcp): add MCP server for LLM tool integration via SSE
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3.2.3-beta.5
2026-02-22 16:42:39 +01:00
Estrella Pan
ba263e0f69 Merge branch 'claude/inspiring-williams' into 3.2-dev
fix(ui): align notification settings with project design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 14:44:22 +01:00
Estrella Pan
b570073000 fix(ui): align notification settings with project design system
Replace non-existent ab-dialog with ab-popup for proper modal behavior,
use ab-button/ab-label components and consistent spacing/styles matching
other settings panels (search-provider, proxy).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 14:44:11 +01:00