Commit Graph
236 Commits
Author SHA1 Message Date
YoursFunny 153b43c84d chore: bump version to 1.9.4 2026-10-31 18:56:27 +08:00
YoursFunny 132fdd2999 fix: reply the edit-before-forward prompt to the post's own media message
One user message carrying several links opens one prompt per post; every
prompt used to reply to that shared link message, so they were
indistinguishable. The prompt now replies to the first media message it
edits, falling back to the link message when there is none.
2026-10-31 18:53:29 +08:00
YoursFunny 8f6e78395b Merge pull request #9 from TheFunny/dependabot/cargo/parking_lot-0.13.0
build(deps): bump parking_lot from 0.12.5 to 0.13.0
2026-10-31 18:52:09 +08:00
dependabot[bot] f11ceb29e1 build(deps): bump rusqlite from 0.40.2 to 0.41.0
Bumps [rusqlite](https://github.com/rusqlite/rusqlite) from 0.40.2 to 0.41.0.
- [Release notes](https://github.com/rusqlite/rusqlite/releases)
- [Changelog](https://github.com/rusqlite/rusqlite/blob/master/Changelog.md)
- [Commits](https://github.com/rusqlite/rusqlite/compare/v0.40.2...v0.41.0)

---
updated-dependencies:
- dependency-name: rusqlite
  dependency-version: 0.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-31 10:48:52 +00:00
dependabot[bot] 6b6f1f6437 build(deps): bump html-escape from 0.2.15 to 0.3.0
Bumps [html-escape](https://github.com/magiclen/html-escape) from 0.2.15 to 0.3.0.
- [Commits](https://github.com/magiclen/html-escape/compare/v0.2.15...v0.3.0)

---
updated-dependencies:
- dependency-name: html-escape
  dependency-version: 0.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-29 10:15:09 +00:00
dependabot[bot] 8d46879fed build(deps): bump parking_lot from 0.12.5 to 0.13.0
Bumps [parking_lot](https://github.com/Amanieu/parking_lot) from 0.12.5 to 0.13.0.
- [Release notes](https://github.com/Amanieu/parking_lot/releases)
- [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Amanieu/parking_lot/compare/parking_lot-v0.12.5...parking_lot-v0.13.0)

---
updated-dependencies:
- dependency-name: parking_lot
  dependency-version: 0.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-29 10:15:03 +00:00
YoursFunny 47c0cb97ee chore: bump version to 1.9.3 2026-10-26 22:53:36 +08:00
YoursFunny 3b955cf321 fix(queue): gate post-send side effects on the row's lease
A handler whose row was re-leased while it sat between heartbeats (a
>=120 s stall) still ran the channel forward, opened the edit prompt and
released the keep-alive temp media the new holder was using; only the
terminal write was token-guarded, which left the side effects to run
twice. The lease is now exposed to the handler on a task-local
(Lease/lease_still_held) and the completion path re-reads it, since the
loss may have gone unnoticed by the heartbeat. The forward retry is gated
the same way.
2026-10-14 22:10:36 +08:00
YoursFunny 632cbb7c89 fix(callback): settle the prompt when a forward fails partway
A Permanent forward failure kept the prompt live, so the next Confirm
rebuilt the task with forward_offset 0 and copied the batches that had
already reached the channel a second time. A failure whose task offset is
non-zero now settles the prompt (as the retryable path does); a failure
that copied nothing still leaves it live for a retry.
2026-10-14 22:10:36 +08:00
YoursFunny 2ffe647939 fix(settings): drop a link send whose chat settings could not be read
The pipeline read settings through get(), which degrades a failed read to
defaults. A send then went out with the chat's channel forward and
edit-before-forward silently ignored, and the task it built carried those
defaults into the retry payload. try_get() is the strict read; an
unreadable row now drops the send and tells the user.
2026-10-14 22:10:36 +08:00
YoursFunny 3a944c1361 fix(queue): keep a running attempt through a failed lease heartbeat
A heartbeat write error abandoned the attempt at once. The lease is still
valid until locked_until, so the attempt was dropped for nothing: the row
was recovered later and the task re-ran from the start, although its side
effects (a sent album, a copied post) may already have happened. A failed
beat now retries at the next tick; the attempt is abandoned only once the
next beat could land at or past the last confirmed expiry.
2026-10-14 22:10:35 +08:00
YoursFunny a7d4d1af8c fix: derive the photo decode cap so one photo's peak fits the memory budget 2026-10-14 22:10:35 +08:00
YoursFunny c937a66e5f fix: admit adapter temp media in the local-path guard 2026-10-14 22:10:34 +08:00
YoursFunny 8063777962 chore: bump version to 1.9.2 2026-09-25 03:12:07 +08:00
YoursFunny ac10b8715c docs: sync template limits and live-test count 2026-09-25 02:12:17 +08:00
YoursFunny 50299bacbb fix: redact user-controlled log fields 2026-09-25 01:49:31 +08:00
YoursFunny 9d40f3bc50 fix: bound template storage and callback data 2026-09-25 01:49:31 +08:00
YoursFunny 2045c3cea1 fix: keep photo memory permit until processing ends 2026-09-25 01:49:30 +08:00
YoursFunny 09656810fe fix: prune persisted expired prompts 2026-09-25 01:49:30 +08:00
YoursFunny 786bafca42 fix: account for photo processing peak memory 2026-09-25 01:49:30 +08:00
YoursFunny 18da6e97c2 fix: reject oversized local media before upload 2026-09-25 01:49:29 +08:00
YoursFunny 7b816d4153 fix: split long message forwards 2026-09-25 01:49:29 +08:00
YoursFunny d8b9a06453 fix: bound direct command fetches 2026-09-25 01:40:09 +08:00
YoursFunny 21437d1843 fix: bound queue panic retries 2026-09-25 01:11:28 +08:00
YoursFunny a54029c19b fix: propagate startup repair database errors 2026-09-25 01:06:09 +08:00
YoursFunny c6120b7cc0 fix: make sqlite migrations atomic 2026-09-24 21:47:48 +08:00
YoursFunny 1ba64e858d fix: enforce inline result limit centrally 2026-09-24 21:00:00 +08:00
YoursFunny 008d31b9bd test: cover inline answer edge cases 2026-09-24 20:57:08 +08:00
YoursFunny 66ec14662c style: simplify inline answer returns 2026-09-24 20:57:08 +08:00
YoursFunny 8ae7d9947d fix: honor media retry-after delay 2026-09-24 20:57:08 +08:00
YoursFunny 28e8fec68d fix: rate limit plain chat operations 2026-09-24 20:57:08 +08:00
YoursFunny b2b01721b2 fix: debounce inline generations and cap results 2026-09-24 20:40:27 +08:00
YoursFunny 85c3be5ef5 fix: always answer inline query 2026-09-24 20:33:23 +08:00
YoursFunny 5e7a87c0dd fix: send text posts as HTML 2026-09-24 20:29:04 +08:00
YoursFunny 6a2ba6a50a fix: handle empty media and prompt persistence 2026-09-24 19:51:50 +08:00
YoursFunny fb354c9434 test: handle chat state Result in callback 2026-09-24 19:46:23 +08:00
YoursFunny bc7f249c20 fix: filter local animation cache sources 2026-09-24 19:44:48 +08:00
YoursFunny f08b187964 fix: drop unsafe upstream media entries 2026-09-24 19:39:46 +08:00
YoursFunny 8428468712 fix: surface chat state read failures 2026-09-24 19:37:29 +08:00
YoursFunny 412359e84c fix: do not cache local media paths 2026-09-24 18:52:18 +08:00
YoursFunny 4694e60ee3 fix: reject upstream local media paths 2026-09-24 18:41:23 +08:00
YoursFunny 88dc0ccb9e fix: stop persistent queue before URL workers 2026-09-24 18:37:17 +08:00
YoursFunny b563b949bb fix: avoid overwriting chat state after read failure 2026-09-24 18:35:52 +08:00
YoursFunny e7855b02fe fix: fence queue dead-letter side effects 2026-09-24 18:33:21 +08:00
YoursFunny 5e265f2413 chore(log): align levels with the documented convention
Three places disagreed with AGENTS' own level rules: get_chat/get_chat_administrators failures during /set_channel printed at error even though the usual cause is a mistyped channel name the user fixes on the spot (error is reserved for dead letters and the like — now warn, same as other user-served degradations); the edit-before-forward callback arrival line logged at info while the sibling arrival lines for commands and inline queries log at debug (now debug, still carries the escaped payload); and the two upload-fallback lines logged at info while photo.rs's parallel degradations log at warn (now warn both — Telegram failing to fetch our media is a degradation that leaves the user served). AGENTS' convention sentence is now explicit about all three instead of the ambiguous '(fallback, retry enqueue, dead-letter is error)'.
2026-09-24 15:38:25 +08:00
YoursFunny 7019f34801 fix(send): refuse a file id at the upload fallback's door
download_to_temp guarded against a file id, but prepare_upload_item answered a non-http media_url first — and item_url maps FileId to the id itself, so the id was presented as a local *path* and died later with a confusing file-open error instead of a classification. The guard now sits at the entry, download_to_temp takes the already-narrowed URL (the inner guard deleted as dead), and the test pins the refusal as Permanent before any I/O. The file also carries item10's download-classification arm (RateLimited is retryable there too) — same file, landed here to keep each tree compiling.
2026-09-24 15:35:55 +08:00
YoursFunny d05450d88a fix(fetch): jitter the retry backoff and honor a429's Retry-After
Two gaps in fetch_with_attempts: the sleep was the bare 1 << attempt, so every worker that failed together (a source coming back, a shared proxy blip) also recovered on the same tick and re-stamped the source; and a429 arrived as an anonymous Transient, its Retry-After header — the one place a source tells you exactly how long it wants silence — dropped on the floor.

retry_wait(attempt, roll, err) is the pure decision: doubling base plus a random slice of itself ([base, 2x base)), floored at a named Retry-After. status_error now takes the response, reads the seconds-form header and returns the new FetchError::RateLimited { site, retry_after_secs } (HTTP-date parses to None and stays transient); the pure table moved to classify_status so tests still build it from bare status codes. The delay is capped at MAX_RETRY_AFTER_SECS = 60 — the header is server-supplied and must not park one of the eight fetch slots.

Everywhere a Transient meant 'retryable' the new variant joins: the Site trait default, pixiv's override (plus its ugoira-zip mapping), bsky's HLS second attempt, the download classifier, and the user-facing message arm. Tests pin the429 rows (with and without the header, cap included) and retry_wait's math; AGENTS' retries bullet and variant list follow.
2026-09-24 15:35:55 +08:00
YoursFunny 053ae0ec25 fix(send): charge the photo download window to the memory budget
A non-photo body charged the process-wide budget for as long as download_to_temp held it; a photo charged nothing until its prepare step, so the download itself — up to MAX_PHOTO_DOWNLOAD_BYTES per item, six items per batch, eight prep slots process-wide — was memory the MEMORY_BUDGET comment promised was bounded but was not (~6x32 MiB on top of the accounted512 MiB). Photos now reserve their own download cap for that window; the prepare step still charges header probe + decode buffer, the only overlap that is actually held in memory together. Net accounting stays within the declared budget instead of exceeding it whenever a batch of large photos downloads at once (audit: upload.rs photo window uncharged).
2026-09-24 15:29:17 +08:00
YoursFunny d1f2ae09b2 fix(rate-limit): charge plain messages and chat actions to the global bucket
send_media_group, send_animation and copy_messages paced themselves against both buckets; send_message and send_chat_action paced against neither. A dead-letter storm — or one error reply per failed item, plus the action refresh loop's repeated typing requests — could therefore burst past Telegram's per-bot30/s ceiling with only429s left to absorb it, exactly what the bot-wide bucket exists to prevent. Both now acquire one global token like their siblings (per-chat pacing for messages already happens at their call sites; chat actions are cheap and frequent, so only the global bucket applies to them).
2026-09-24 15:28:59 +08:00
YoursFunny 715d6a59b0 fix(photo): sum dimensions in u64 so a huge header cannot wrap
plan_photo, target_dims and both pipeline branches added the two u32 dimensions before comparing against PHOTO_MAX_DIMENSION_SUM: u32::MAX + 2 wrapped to 1 and read 'within limits' — the plan said AsIs, and an absurd-sized image would go to Telegram untouched (in debug builds the addition panics instead). The four production sums now widen to u64 first; the test pins the wrap case as TooLarge. PNG caps each dimension at 2^31-1, so a spec-valid file cannot reach this today — the raw header is parsed before any crate validation, which is exactly where a hostile file lands (audit: photo.rs u32 wrap).
2026-09-24 15:25:19 +08:00