Skip to content

fix: webhook query string, inverted bounds checks and handle leaks - #8

Merged
Rushaway merged 1 commit into
masterfrom
fix/webhook-api-bugs
Sep 12, 2026
Merged

fix: webhook query string, inverted bounds checks and handle leaks#8
Rushaway merged 1 commit into
masterfrom
fix/webhook-api-bugs

Conversation

@Rushaway

Copy link
Copy Markdown
Member

Summary

While reviewing the include I found a few real bugs (one of them breaks Webhook.Edit()), some handle leaks, and a couple of rough edges in example.sp / README.md. Everything here is behaviour-preserving except where the previous behaviour was broken.

Bugs fixed

Webhook.Execute() built an invalid query string

Format(webhook_query, sizeof webhook_query, "%s?thread_id=%s&?wait=true", webhook, threadID);

The &?wait=true makes the parameter name ?wait instead of wait. Discord then treats wait as false, replies 204 No Content instead of 200 with the message body, and OnWebHookExecuted can no longer read the message idWebhook.Edit() is unusable whenever a thread ID is passed. Fixed to &wait=true.

Also: when thread_name is set, the thread_id query parameter is now omitted, because Discord rejects a forum webhook that carries both (error 220002). Previously only example.sp worked around this.

Embed.GetField() / Webhook.GetEmbed() inverted bounds check

if(fields != null && fields.Length < index)   // wrong

For an array of length 3, GetField(0) returns null and GetField(5) reads out of bounds. Every valid index fails. Changed to index >= 0 && index < fields.Length.

Handle leaks

AddField(), AddEmbed(), GetField() and GetEmbed() obtain a JSONArray handle (new JSONArray() or this.Get(...), which json_increfs) and never delete it. Added the missing delete.

Sub-object getters raised a native error

GetFooter, GetImage, GetThumbnail, GetVideo, GetProvider, GetAuthor, GetFields, GetEmbeds call this.Get("<key>"), which throws Could not retrieve value for key when the key is absent. They now return null first via HasKey.

Embed.SetTimeStampNow() malformed format string

"%FT\%T.000%z" contains a stray escape (\%). Replaced with an explicit, portable "%Y-%m-%dT%H:%M:%S%z".

DEBUG build path

this.toString(...) (wrong case, does not compile) → this.ToString(...), and the JSON is now printed with PrintToServer("%s", debug) instead of being used as the format string.

Improvements

  • URL buffers in Execute() / Edit() are sized from WEBHOOK_URL_MAX_SIZE instead of a bare 1024 that a near-maximum-length webhook URL plus ?thread_id=…&wait=true could overflow.
  • Webhook.SetThreadName() now takes const char[] (so string literals are accepted) and its doc reflects the real Discord limit of 100 characters (WEBHOOK_THREAD_NAME_MAX_SIZE), not 1000.
  • Added Webhook.GetThreadName().
  • DiscordWebhookAPI_VERSION bumped to 1.1.0 + changelog entry.

example.sp

  • PrintToServer("… n°%s …", client) used %s with an int%d.
  • The DataPack passed to Execute() / Edit() was never freed in the callbacks.
  • PrintToServer(messageId)PrintToServer("%s", messageId).

README.md

  • Snippet checked HTTPStatus_NoContent, but Execute() always appends wait=true so Discord answers 200 OK; also delete the webhook, drop the trailing slash (the doc explicitly warns against it) and use discord.com.

Notes

  • The Discord API version used is entirely determined by the webhook URL the caller passes; an unversioned URL currently resolves to the deprecated v6. Defaulting the wrapper to /api/v10/ is a behaviour change worth discussing separately — opened as an issue on the upstream repo (issues are disabled here).

Testing

example.sp still compiles (CI). GetField / GetEmbed / the sub-object getters are not exercised by example.sp; changes there are straightforward.

🤖 Generated with Claude Code

Include (discordWebhookAPI.inc):
- Execute(): the thread_id branch produced "&?wait=true", so the "wait"
  query parameter was named "?wait" and Discord never returned the created
  message body (200 -> 204), which also broke a subsequent Webhook.Edit().
- Execute(): skip the thread_id query parameter when thread_name is set to
  avoid Discord error 220002 (a forum webhook cannot carry both).
- Embed.GetField() / Webhook.GetEmbed(): the bounds check was inverted
  (array.Length < index), so every valid index returned null and out-of-range
  indices read past the array. Use "index >= 0 && index < Length".
- AddField(), AddEmbed(), GetField(), GetEmbed(): free the JSONArray handle
  that was leaked on every call.
- GetFooter/GetImage/GetThumbnail/GetVideo/GetProvider/GetAuthor/GetFields/
  GetEmbeds: return null instead of raising a native error when the key is
  not set.
- SetTimeStampNow(): drop the malformed "%FT\%T.000%z" format string.
- DEBUG path: this.toString -> this.ToString (did not compile) and pass the
  JSON through a "%s" format instead of as the format string itself.
- Size the Execute()/Edit() URL buffers from WEBHOOK_URL_MAX_SIZE.
- SetThreadName(): take const char[]; fix the doc (Discord limit is 100).
- Add Webhook.GetThreadName(); bump version to 1.1.0.

example.sp:
- PrintToServer used %s with an int client argument -> use %d.
- Free the DataPack in both HTTP callbacks.
- PrintToServer(messageId) -> PrintToServer("%s", messageId).

README.md:
- Success check is HTTPStatus_OK (Execute() forces wait=true), delete the
  webhook, drop the trailing slash, use discord.com.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Rushaway
Rushaway merged commit bccd509 into master Sep 12, 2026
2 checks passed
@Rushaway
Rushaway deleted the fix/webhook-api-bugs branch September 12, 2026 11:48
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