Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
## Release Notes
## Release Notes

## [1.1.0]

### Fixed

- `Webhook.Execute()` built an invalid query string (`&?wait=true`) when a `threadID` was
passed, so Discord never returned the created message body and `Webhook.Edit()` could not
be used afterwards.
- `Embed.GetField()` / `Webhook.GetEmbed()` used an inverted bounds check
(`array.Length < index`), returning `null` for every valid index and reading out of bounds
for invalid ones.
- Memory leaks: the `JSONArray` handles obtained in `AddField()`, `AddEmbed()`, `GetField()`
and `GetEmbed()` were never freed.
- Sub-object / array getters (`GetFooter`, `GetImage`, `GetThumbnail`, `GetVideo`,
`GetProvider`, `GetAuthor`, `GetFields`, `GetEmbeds`) raised a native error instead of
returning `null` when the key was not set.
- `Embed.SetTimeStampNow()` used a malformed format string (`"%FT\%T.000%z"`).
- `DEBUG` build path did not compile (`this.toString` instead of `this.ToString`) and passed
unescaped JSON as a format string to `PrintToServer`.
- URL buffers in `Execute()` / `Edit()` could truncate a near-maximum-length webhook URL.

### Added

- `Webhook.GetThreadName()`.
- `Webhook.Execute()` now skips the `thread_id` query parameter when `thread_name` is set,
avoiding Discord error `220002` (a forum webhook cannot use both).

### Changed

- `Webhook.SetThreadName()` now takes a `const char[]` and its documentation reflects the
real Discord limit of 100 characters.

## [1.0.0]

### Added

- Initial release.
- Initial release.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,15 @@ Contrary to other includes, discordWebhookAPI:
public Action SendDiscordWebhook(int client, int args)
{
Webhook webhook = new Webhook("This is the content of the webhook.");
webhook.Execute("https://discordapp.com/api/webhooks/6758765876/769876789009/", OnWebHookExecuted);
webhook.Execute("https://discord.com/api/webhooks/6758765876/769876789009", OnWebHookExecuted);
delete webhook;
return Plugin_Continue;
}

public void OnWebHookExecuted(HTTPResponse response, DataPack pack)
public void OnWebHookExecuted(HTTPResponse response, any data)
{
if (response.Status == HTTPStatus_NoContent)
// Execute() always appends ?wait=true, so Discord answers 200 OK with the message body.
if (response.Status == HTTPStatus_OK)
{
PrintToServer("Webhook sent successfully!");
}
Expand Down
9 changes: 6 additions & 3 deletions example.sp
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,9 @@ Action SendDiscordWebhook(int client, int args)
public void OnWebHookExecuted(HTTPResponse response, DataPack pack)
{
int client = pack.ReadCell();
delete pack;

PrintToServer("Processed client n°%s's webhook, status %d", client, response.Status);
PrintToServer("Processed client n°%d's webhook, status %d", client, response.Status);
if (response.Status != HTTPStatus_OK)
{
PrintToServer("An error has occured while sending the webhook.");
Expand All @@ -167,10 +168,11 @@ public void OnWebHookExecuted(HTTPResponse response, DataPack pack)
PrintToServer("The webhook has been sent successfuly.");

// Retrieve the message's id.
// Note: response.Data is owned by the extension, do not delete it.
JSONObject resData = view_as<JSONObject>(response.Data);
char messageId[64];
resData.GetString("id", messageId, sizeof messageId);
PrintToServer(messageId);
PrintToServer("%s", messageId);
editWebhook(messageId, client);
}

Expand Down Expand Up @@ -205,8 +207,9 @@ void editWebhook(const char[] messageId, int client)
void OnWebHookEdited(HTTPResponse response, DataPack pack)
{
int client = pack.ReadCell();
delete pack;

PrintToServer("Edited client n°%s's webhook, status %d", client, response.Status);
PrintToServer("Edited client n°%d's webhook, status %d", client, response.Status);
if (response.Status != HTTPStatus_OK)
{
PrintToServer("An error has occured while editing the webhook.");
Expand Down
143 changes: 101 additions & 42 deletions include/discordWebhookAPI.inc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#endif
#define _discordWebhookAPI_included_

#define DiscordWebhookAPI_VERSION "1.0.1"
#define DiscordWebhookAPI_VERSION "1.1.0"
#define WEBHOOK_MSG_MAX_SIZE 2000
#define WEBHOOK_URL_MAX_SIZE 1000
#define WEBHOOK_THREAD_NAME_MAX_SIZE 100
Expand Down Expand Up @@ -617,8 +617,8 @@ methodmap Embed < JSONObject
*/
public bool SetTimeStampNow()
{
char timeNow[256];
FormatTime(timeNow, sizeof timeNow, "%FT\%T.000%z", GetTime());
char timeNow[64];
FormatTime(timeNow, sizeof timeNow, "%Y-%m-%dT%H:%M:%S%z", GetTime());
return this.SetString("timestamp", timeNow);
}

Expand Down Expand Up @@ -650,6 +650,10 @@ methodmap Embed < JSONObject
*/
public EmbedFooter GetFooter()
{
if (!this.HasKey("footer"))
{
return null;
}
return view_as<EmbedFooter>(this.Get("footer"));
}

Expand All @@ -671,6 +675,10 @@ methodmap Embed < JSONObject
*/
public EmbedImage GetImage()
{
if (!this.HasKey("image"))
{
return null;
}
return view_as<EmbedImage>(this.Get("image"));
}

Expand All @@ -692,6 +700,10 @@ methodmap Embed < JSONObject
*/
public EmbedThumbnail GetThumbnail()
{
if (!this.HasKey("thumbnail"))
{
return null;
}
return view_as<EmbedThumbnail>(this.Get("thumbnail"));
}

Expand All @@ -713,6 +725,10 @@ methodmap Embed < JSONObject
*/
public EmbedVideo GetVideo()
{
if (!this.HasKey("video"))
{
return null;
}
return view_as<EmbedVideo>(this.Get("video"));
}

Expand All @@ -734,6 +750,10 @@ methodmap Embed < JSONObject
*/
public EmbedProvider GetProvider()
{
if (!this.HasKey("provider"))
{
return null;
}
return view_as<EmbedProvider>(this.Get("provider"));
}

Expand All @@ -755,6 +775,10 @@ methodmap Embed < JSONObject
*/
public EmbedAuthor GetAuthor()
{
if (!this.HasKey("author"))
{
return null;
}
return view_as<EmbedAuthor>(this.Get("author"));
}

Expand All @@ -776,22 +800,33 @@ methodmap Embed < JSONObject
*/
public JSONArray GetFields()
{
if (!this.HasKey("fields"))
{
return null;
}
return view_as<JSONArray>(this.Get("fields"));
}

/**
* Retrieve a field of the embed.
*
* @return Field corresponding to the input index. null if an error occurs.
*
* @param index Index of the field to retrieve.
* @return Field corresponding to the input index. null if the index is out of bounds.
*/
public EmbedField GetField(int index)
{
if (!this.HasKey("fields"))
{
return null;
}
JSONArray fields = view_as<JSONArray>(this.Get("fields"));
if(fields != null && fields.Length < index)
EmbedField field = null;
if (fields != null && index >= 0 && index < fields.Length)
{
return view_as<EmbedField>(fields.Get(index));
field = view_as<EmbedField>(fields.Get(index));
}
return null;
delete fields;
return field;
}

/**
Expand All @@ -811,28 +846,27 @@ methodmap Embed < JSONObject
{
fields = new JSONArray();
}
if(fields.Push(view_as<JSON>(field)))
int count = -1;
if(fields.Push(view_as<JSON>(field)) && this.Set("fields", fields))
{
if(this.Set("fields", fields))
{
delete field;
return fields.Length;
}
count = fields.Length;
}
return -1;
delete field;
delete fields;
return count;
}
}


methodmap Webhook < JSONObject
{
/**
* Constructor for the Embed methodmap.
*
* Constructor for the Webhook methodmap.
*
* @param content Content of the webhook.
* @return Returns the Embed.
* @return Returns the Webhook.
*/
public Webhook(const char[] content="")
public Webhook(const char[] content="")
{
JSONObject jsonObject = new JSONObject();
jsonObject.SetString("content", content);
Expand Down Expand Up @@ -887,15 +921,29 @@ methodmap Webhook < JSONObject

/**
* Set the Thread Name of the webhook.
*
* @param threadName Thread Name of the webhook. (char max is 1000)
* When set, executing the webhook on a forum channel creates a new thread with this name.
* Note: Discord limits thread names to WEBHOOK_THREAD_NAME_MAX_SIZE (100) characters.
*
* @param threadName Thread Name of the webhook.
* @return True on success. False otherwise.
*/
public bool SetThreadName(char[] threadName)
public bool SetThreadName(const char[] threadName)
{
return this.SetString("thread_name", threadName);
}

/**
* Retrieve the thread name of the webhook.
*
* @param buffer String buffer to store value.
* @param maxlength Maximum length of the string buffer.
* @return True on success. False otherwise.
*/
public bool GetThreadName(char[] buffer, int maxlength)
{
return this.GetString("thread_name", buffer, maxlength);
}

/**
* Retrieve the avatar_url of the webhook.
*
Expand Down Expand Up @@ -947,27 +995,38 @@ methodmap Webhook < JSONObject
*/
public JSONArray GetEmbeds()
{
if (!this.HasKey("embeds"))
{
return null;
}
return view_as<JSONArray>(this.Get("embeds"));
}

/**
* Retrieve an embed of the webook from its index.
*
* @return Embed corresponding to the input index. null if an error occurs.
*
* @param index Index of the embed to retrieve.
* @return Embed corresponding to the input index. null if the index is out of bounds.
*/
public Embed GetEmbed(int index)
{
if (!this.HasKey("embeds"))
{
return null;
}
JSONArray embeds = view_as<JSONArray>(this.Get("embeds"));
if(embeds != null && embeds.Length < index)
Embed embed = null;
if(embeds != null && index >= 0 && index < embeds.Length)
{
return view_as<Embed>(embeds.Get(index));
embed = view_as<Embed>(embeds.Get(index));
}
return null;
delete embeds;
return embed;
}

/**
* Add an embed to the webhook. This will delete the handle to the embed.
*
*
* @param embed Embed to add to the webhook.
* @return The number of embeds after the new one was added. -1 otherwise.
*/
Expand All @@ -982,15 +1041,14 @@ methodmap Webhook < JSONObject
{
embeds = new JSONArray();
}
if(embeds.Push(view_as<JSON>(embed)))
int count = -1;
if(embeds.Push(view_as<JSON>(embed)) && this.Set("embeds", embeds))
{
if(this.Set("embeds", embeds))
{
delete embed;
return embeds.Length;
}
count = embeds.Length;
}
return -1;
delete embed;
delete embeds;
return count;
}

/**
Expand All @@ -1003,17 +1061,18 @@ methodmap Webhook < JSONObject
*/
public void Execute(const char[] webhook, HTTPRequestCallback callback, any data = 0, const char[] threadID = "")
{
char webhook_query[1024];
if (!threadID[0])
char webhook_query[WEBHOOK_URL_MAX_SIZE + 64];
// Discord rejects a request that carries both thread_name (body) and thread_id (query).
if (!threadID[0] || this.HasKey("thread_name"))
Format(webhook_query, sizeof webhook_query, "%s?wait=true", webhook);
else
Format(webhook_query, sizeof webhook_query, "%s?thread_id=%s&?wait=true", webhook, threadID);
Format(webhook_query, sizeof webhook_query, "%s?thread_id=%s&wait=true", webhook, threadID);

HTTPRequest httpRequest = new HTTPRequest(webhook_query);
#if defined DEBUG
char debug[9999];
this.toString(debug, sizeof debug);
PrintToServer(debug);
char debug[4096];
this.ToString(debug, sizeof debug);
PrintToServer("%s", debug);
#endif
httpRequest.Post(view_as<JSON>(this), callback, data);
}
Expand All @@ -1028,7 +1087,7 @@ methodmap Webhook < JSONObject
*/
public void Edit(const char[] webhook, const char[] messageId, HTTPRequestCallback callback, any data = 0)
{
char webhook_patch[1024];
char webhook_patch[WEBHOOK_URL_MAX_SIZE + 64];
Format(webhook_patch, sizeof webhook_patch, "%s/messages/%s", webhook, messageId);
HTTPRequest httpRequest = new HTTPRequest(webhook_patch);
httpRequest.Patch(view_as<JSON>(this), callback, data);
Expand Down
Loading