Skip to content

fix(bot): update embeds after flag edits - #94

Merged
rosethornbush merged 2 commits into
mainfrom
fix/embed-flags-on-edit
Sep 5, 2026
Merged

fix(bot): update embeds after flag edits#94
rosethornbush merged 2 commits into
mainfrom
fix/embed-flags-on-edit

Conversation

@rosethornbush

Copy link
Copy Markdown
Contributor

Summary

  • update existing Embedly messages when link modifiers change
  • keep multi-link updates aligned through request-index cache metadata
  • preserve legacy cache behavior without storing message content or URLs
  • fix spoiler-wrapped URL extraction so spoiler edits work

Behavior

Edits update in place only when URL values and order stay unchanged. URL additions, removals, replacements, and reordering are ignored. Failed refreshes keep the existing Embedly message and react to the source message with ❌.

Validation

  • apps/bot/node_modules/.bin/oxlint apps/bot/src
  • apps/bot/node_modules/.bin/oxfmt --check on changed bot files
  • direct tsdown bot build
  • parser assertions for @, !, ?@, ?!, and spoiler modifiers
  • git diff --check

Raw bot tsc remains noisy from pre-existing stale workspace declaration outputs.

@changeset-bot

changeset-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: e075458

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 16, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
embedly-docs e075458 Commit Preview URL

Branch Preview URL
Sep 05 2026, 03:59 AM

@rosethornbush

Copy link
Copy Markdown
Contributor Author

@greptile

@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown

Greptile Summary

This PR teaches the bot to update its existing embed replies in-place when a user edits only the flag modifiers (@, !, ?@, ?!, spoiler) on a URL whose value and position are unchanged, and fixes spoiler-wrapped URL extraction so the regex's greedy || inclusion is stripped before URLs are stored or matched.

  • In-place embed updates: messageUpdate now parses old and new URL lists, builds an updateTargets map of requestIndex → botMessageId for entries where the URL is identical but flags differ, and passes this to handleUrls which fetches and edits only those bot messages; failed refreshes react with ❌ on the source message.
  • requestIndex cache metadata: MessageCache.save now stores a botMessageIndexes record alongside botMessageIds; legacy entries without the field continue to work with graceful requestIndex: undefined fallback that skips index-based updates.
  • Spoiler URL fix: extractURLs now strips a trailing || that the URL regex greedily captured, and adjusts endIndex to match, so parseMessageURLs receives the correct URL string and the after slice used for angle-bracket suppression starts at the right position.

Confidence Score: 5/5

  • Safe to merge. All error paths are internally handled, legacy cache entries degrade gracefully, and the update path correctly scopes its edits to only the bot messages whose flags changed.
  • The in-place update logic is well-guarded: sameUrls prevents index reuse when URL order changes, legacy cache entries without botMessageIndexes are skipped via requestIndex === undefined, and every Discord and API failure reacts with ❌ rather than silently dropping. The spoiler endIndex fix is correct and does not regress the <url> suppression check. No new data loss, auth, or correctness defects were found in the changed paths.
  • No files require special attention. messageUpdate.ts and handleUrls.ts contain the most logic but both are well-structured.

Important Files Changed

Filename Overview
apps/bot/src/lib/utils.ts Adds trailing `
apps/bot/src/lib/handleUrls.ts Promotes parseMessageURLs from messageCreate.ts into the shared module, threads requestIndex through the match pipeline, introduces HandleUrlsOptions with updateTargets for in-place edits, and gates cache saves on the create-only path. Logic is correct; reactToFailure double-fire is safely guarded.
apps/bot/src/lib/messageCache.ts Adds botMessageIndexes as an optional field with full runtime validation, exposes getBotMessages and clearBotMessageIndexes, and correctly cleans up indexes in removeBotMessage. Legacy cache entries (no botMessageIndexes) are handled gracefully via ??.
apps/bot/src/listeners/messageUpdate.ts Replaces the old native-embed-suppression-only handler with full URL flag change detection. Partial-old-message fallback (content === null) correctly clears indexes to avoid stale mapping. Update targets are built only when URLs are identical in value and order.
apps/bot/src/listeners/messageCreate.ts Imports the now-shared parseMessageURLs instead of defining it locally; no behavioral change to the create path.
apps/bot/src/commands/embed.ts Adapts two handleUrls call sites from a bare source string to the new { source } options object; mechanical change with no logic impact.

Sequence Diagram

sequenceDiagram
    participant Discord
    participant MessageUpdateListener
    participant MessageCache
    participant handleUrls
    participant DiscordAPI

    Discord->>MessageUpdateListener: messageUpdate(oldMsg, newMsg)
    MessageUpdateListener->>MessageUpdateListener: fetch full message if partial
    MessageUpdateListener->>MessageCache: getBotMessages(message.id)
    MessageCache-->>MessageUpdateListener: "[{id, requestIndex}, ...]"

    alt botMessages is empty
        MessageUpdateListener-->>Discord: return (no-op)
    else "oldMessage.content === null (partial)"
        MessageUpdateListener->>MessageCache: clearBotMessageIndexes(message.id)
    else content changed
        MessageUpdateListener->>MessageUpdateListener: parseMessageURLs(old) + parseMessageURLs(new)
        alt URLs differ (added/removed/reordered)
            MessageUpdateListener->>MessageCache: clearBotMessageIndexes(message.id)
        else same URLs, check flags
            loop each botMessage with requestIndex
                MessageUpdateListener->>MessageUpdateListener: hasSameOptions(oldReq, newReq)?
                alt flags changed
                    MessageUpdateListener->>MessageUpdateListener: updateTargets.set(requestIndex, botMsgId)
                end
            end
            alt "updateTargets.size > 0"
                MessageUpdateListener->>handleUrls: "handleUrls(newUrls, message, {updateTargets})"
                handleUrls->>handleUrls: filter to target requestIndexes only
                handleUrls->>DiscordAPI: matchURL(url) per target
                handleUrls->>DiscordAPI: fetch botMessage + message.edit(embed)
                alt any target failed
                    handleUrls->>Discord: react ❌ on source message
                end
            end
        end
    end

    alt oldMsg had no embeds AND new message has embeds AND not SuppressEmbeds
        MessageUpdateListener->>DiscordAPI: "message.edit({flags: SuppressEmbeds})"
    end
Loading

Reviews (2): Last reviewed commit: "fix(bot): invalidate stale embed edit in..." | Re-trigger Greptile

Comment thread apps/bot/src/listeners/messageUpdate.ts Outdated
Comment on lines +6 to +13
function hasSameOptions(left: EmbedURLRequest, right: EmbedURLRequest) {
return (
left.force === right.force &&
left.flags?.MediaOnly === right.flags?.MediaOnly &&
left.flags?.SourceOnly === right.flags?.SourceOnly &&
left.flags?.Spoiler === right.flags?.Spoiler
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 hasSameOptions uses === on optional force?: boolean, so undefined and false are treated as different values. Since parseMessageURLs always writes force = false (never undefined), this won't trigger today, but it's a latent trap if another caller ever omits force. Normalizing with ?? makes the comparison explicit and safe.

Suggested change
function hasSameOptions(left: EmbedURLRequest, right: EmbedURLRequest) {
return (
left.force === right.force &&
left.flags?.MediaOnly === right.flags?.MediaOnly &&
left.flags?.SourceOnly === right.flags?.SourceOnly &&
left.flags?.Spoiler === right.flags?.Spoiler
);
}
function hasSameOptions(left: EmbedURLRequest, right: EmbedURLRequest) {
return (
(left.force ?? false) === (right.force ?? false) &&
left.flags?.MediaOnly === right.flags?.MediaOnly &&
left.flags?.SourceOnly === right.flags?.SourceOnly &&
left.flags?.Spoiler === right.flags?.Spoiler
);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/bot/src/listeners/messageUpdate.ts
Line: 6-13

Comment:
`hasSameOptions` uses `===` on optional `force?: boolean`, so `undefined` and `false` are treated as different values. Since `parseMessageURLs` always writes `force = false` (never `undefined`), this won't trigger today, but it's a latent trap if another caller ever omits `force`. Normalizing with `??` makes the comparison explicit and safe.

```suggestion
function hasSameOptions(left: EmbedURLRequest, right: EmbedURLRequest) {
  return (
    (left.force ?? false) === (right.force ?? false) &&
    left.flags?.MediaOnly === right.flags?.MediaOnly &&
    left.flags?.SourceOnly === right.flags?.SourceOnly &&
    left.flags?.Spoiler === right.flags?.Spoiler
  );
}
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

Comment thread apps/bot/src/lib/handleUrls.ts Outdated
@rosethornbush
rosethornbush force-pushed the fix/embed-flags-on-edit branch from fea3c67 to e075458 Compare September 5, 2026 03:59
@rosethornbush
rosethornbush marked this pull request as ready for review September 5, 2026 04:03
@rosethornbush
rosethornbush merged commit c1730ec into main Sep 5, 2026
4 checks passed
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.

1 participant