Skip to content

fix: unreliable retry logic - #743

Open
ayubun wants to merge 1 commit into
victornpb:masterfrom
ayubun:respect-delete-responses
Open

fix: unreliable retry logic#743
ayubun wants to merge 1 commit into
victornpb:masterfrom
ayubun:respect-delete-responses

Conversation

@ayubun

@ayubun ayubun commented Nov 13, 2025

Copy link
Copy Markdown

the deleteMessage function returns helpful string statuses that can indicate to the retry logic what to do. this is currently only used for the 'RETRY' status, but there are two other unrecognized statuses: 'FAILED' & 'FAIL_SKIP'.

in order to recognize these, and also to fix some of the retry logic (for example, a response of 'RETRY' would cause the deletion script to wait to retry even if there are no remaining retries), i refactored the retry code.

now, 'RETRY' and 'FAILED' are respected as retryable. if there are too many attempts reached, the script will gracefully mark the message as a fail and adjust the offset. if 'FAIL_SKIP' is returned, then the fail count and offset will be incremented but no retries will occur.

finally, i moved all of the fail count and offset state management outside of the deleteMessage method and into the centralized deleteMessagesFromList, which is important when factoring in retries

this fixes #637 at a more fundamental level than #742 because it doesn't rely on supporting every status code to avoid retry-looping forever. however, #742 is still valuable to prevent retrying errors that we know aren't retryable (insufficient permissions)


note: my personal contributions to and usage of this project do not represent discord. opinions are my own

@ayubun ayubun changed the title fix: no retries on 'FAILED' & misc retry bugs fix: unreliable retry logic Nov 13, 2025
TheCellMaster added a commit to TheCellMaster/undiscord that referenced this pull request Apr 15, 2026
Major refactor: modular architecture + upstream PR features + security fixes.

=== ARCHITECTURE (complete rewrite) ===
- Split monolithic undiscord-core.js (584 lines) into 5 focused modules:
  - src/core/undiscord-core.js: Orchestrator (run, runBatch, stop, confirm)
  - src/core/search.js: Search with iterative retry (202/429 handling)
  - src/core/filter.js: Pure message filtering (types, pinned, bots, threads, regex)
  - src/core/delete.js: Delete with retry loop + rate limit adaptation
  - src/core/unarchive.js: Thread unarchive before delete
- Split monolithic undiscord-ui.js (378 lines) into 4 modules:
  - src/ui/init.js: DOM mount, CSS injection, toolbar button + MutationObserver
  - src/ui/handlers.js: All event handlers (start, stop, getChannel, pick, etc)
  - src/ui/progress.js: onStart/onProgress/onStop callbacks
  - src/ui/logger.js: XSS-safe log rendering with ring buffer
- Created src/api/discord-api.js: Pure fetch layer with AbortSignal.timeout(30s)
- Split helpers.js (8 functions in 8 lines) into semantic modules:
  - src/utils/time.js: wait(), msToHMS()
  - src/utils/html.js: escapeHTML(), redact(), replaceInterpolations()
  - src/utils/discord.js: queryString(), ask(), toSnowflake()
- Merged createElm.js + insertCss.js into src/utils/dom.js
- Split CSS (theme.css 355 lines + main.css 172 lines) into 6 modules:
  - layout.css, components.css, scrollbar.css, redact.css, log.css, drag.css
- Moved HTML templates to src/ui/html/
- Renamed utils to kebab-case (getIds -> get-ids, messagePicker -> message-picker)
- Removed 12 legacy files replaced by modular architecture
- Created src/utils/constants.js with shared constants

=== NEW FEATURES (from upstream PRs) ===
- victornpb#741: Poll messages (type 46) can now be deleted
- victornpb#741: Bot slash command responses (type 20) excluded from deletion
- victornpb#742: HTTP 403 on delete returns FAIL_SKIP instead of infinite retry loop
- victornpb#743: Retry logic refactored: FAILED/FAIL_SKIP properly handled, failCount centralized
- victornpb#740: HTTP 403 on search gracefully skips channel instead of canceling batch
- victornpb#739: 30s delay between batch jobs to prevent API spam
- victornpb#737: Thread unarchiving: attempts PATCH to unarchive before skipping
- victornpb#729: Empty page retries (configurable, default 2) before stopping
- victornpb#629: "Include bot/application messages" checkbox in Filter section
- victornpb#610: Thread auto-detection via API when clicking "current" channel button
- victornpb#603: Graceful handling of API errors 50024 (channel not found) and 50001 (missing access)
- victornpb#643: Date filter warning: "Make sure you enter both date AND time"
- victornpb#527/victornpb#519: Rate limit delay adds on top (never decreases) with caps

=== SECURITY FIXES ===
- S1 XSS fix: printLog now escapes all external data via escapeHTML(),
  preserving <x> redact tags via split pattern for streamer mode
- escapeHTML() now also escapes > character
- Log type validated against whitelist before becoming CSS class name
- AbortSignal.timeout(30s) on ALL fetch calls (search, delete, unarchive, getChannel)
  replacing leaky setTimeout-based AbortController
- retry_after clamped with Math.max(w, 0) to prevent negative values causing tight loops

=== BUG FIXES ===
- Fixed onStop called twice (stop() + end of run()) via guard check
- Fixed missing return DELETE_RESULT.FAILED in JSON.parse catch path
- Fixed filterResponse was async unnecessarily (now sync)
- Fixed _searchResponse null crash with guard in filterResponse
- Fixed .filter(Boolean) after map().find() to prevent undefined entries
- Fixed replaceInterpolations treating falsy values (0, false, "") as missing (|| -> ??)

=== QUALITY IMPROVEMENTS ===
- DELETE_RESULT enum (Object.freeze) replaces magic strings
- DELETABLE_MSG_TYPES Set replaces compound conditional
- Set-based lookup for skipped messages (O(n) vs O(n^2))
- search() converted from recursive to iterative with MAX_SEARCH_RETRIES=20
- searchDelay capped at MAX_SEARCH_DELAY_MS (60s)
- deleteDelay capped at MAX_DELETE_DELAY_MS (30s)
- Log ring buffer: MAX_LOG_ENTRIES=5000 prevents unbounded DOM growth
- Confirm preview limited to 10 messages
- messagePicker timeout (30s) with automatic cleanup
- drag.css selectors scoped with #undiscord
- .resize-handle scoped with #undiscord
- Orphan .logarea CSS class removed
- MutationObserver throttle extracted to OBSERVER_THROTTLE_MS constant
- All CSS variables use fallback pattern var(--new, var(--old))
- JSDoc on all public methods

=== BUILD ===
- metadata.mjs supports contributors array from package.json
- @author now shows both victornpb and TheCellMaster
- CLAUDE.md created with project documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Undiscord repeatedly tries to delete messages that the user has no permission to do so.

1 participant