Skip to content

Instantly share code, notes, and snippets.

@n1tehawk
Created May 7, 2020 22:27
Show Gist options
  • Save n1tehawk/ba2f7911522d47a18946b35464bfed58 to your computer and use it in GitHub Desktop.
Save n1tehawk/ba2f7911522d47a18946b35464bfed58 to your computer and use it in GitHub Desktop.
FreePascal unit to execute Discord webhooks
unit webhooks;
(*
A webhook is a simple way to post messages to channels in Discord, by sending
suitable JSON data via a POST request. You'll have to create the webhook first
(using API calls or administrative functions in Discord's UI) and retrieve its
specific URL that contains two distinct elements: the webhook ID and a token.
see e.g.
https://discordapp.com/developers/docs/resources/webhook#execute-webhook
https://birdie0.github.io/discord-webhooks-guide/tools/curl.html
Example
-------
program test;
{$mode objfpc}
uses
webhooks;
const
WEBHOOK_URL = 'https://discordapp.com/api/webhooks/your-webhook-ID/your-webhook-TOKEN';
var
webhook: TDiscordWebhook;
begin
webhook := TDiscordWebhook.Create(WEBHOOK_URL);
try
webhook.Execute('Hello %s :earth_africa:, the answer is %d.', ['Discord', 42]);
finally
webhook.Free;
end;
end.
*)
{$mode objfpc}{$H+}
interface
uses
libcurl, fpjson, Classes;
type
TDiscordWebhook = class
private
FURL: PChar;
FUsername: string;
FContentType: Pcurl_slist; // a libcurl string list
procedure PostRequest(json: PChar);
public
constructor Create(const URL: string);
destructor Destroy; override;
procedure Execute(json: TJSONObject);
procedure Execute(msg: string);
procedure Execute(Fmt: string; Args: array of const);
property Username: string read FUsername write FUsername;
end;
implementation
uses
ctypes, SysUtils;
{ This is a "dummy" write callback that we will use to suppress the (body)
output that libcurl normally would produce. }
function null_write_callback(ptr: PChar; size, nmemb: csize_t;
userdata: pointer): csize_t; cdecl;
begin
Result := size * nmemb;
end;
{ TDiscordWebhook }
constructor TDiscordWebhook.Create(const URL: string);
begin
{ The constructor sets up elements that aren't likely to change, so they
can be reused across requests. This currently includes the webhook URL,
and a libcurl string list we'll be using for the custom header to specify
the content type (JSON).
The (webhook) username may be changed individually for each request. }
FURL := StrNew(PChar(URL));
FUsername := '';
FContentType := curl_slist_append(nil, 'Content-Type: application/json');
end;
destructor TDiscordWebhook.Destroy;
begin
curl_slist_free_all(FContentType); // free the list
StrDispose(FURL);
inherited;
end;
// transfer JSON data string using POST request via libcurl
procedure TDiscordWebhook.PostRequest(json: PChar);
var
hCurl: pCurl;
begin
hCurl := curl_easy_init;
if assigned(hCurl) then begin
//curl_easy_setopt(hCurl, CURLOPT_VERBOSE, [True]); // (DEBUG ONLY)
curl_easy_setopt(hCurl, CURLOPT_URL, [FURL]); // set URL
curl_easy_setopt(hCurl, CURLOPT_POST, [True]); // select POST method
curl_easy_setopt(hCurl, CURLOPT_HTTPHEADER, [FContentType]); // custom header(s)
curl_easy_setopt(hCurl, CURLOPT_POSTFIELDS, [json]); // set query data
curl_easy_setopt(hCurl, CURLOPT_WRITEFUNCTION, [@null_write_callback]); // suppress output
curl_easy_perform(hCurl);
curl_easy_cleanup(hCurl);
end;
end;
// execute webhook with JSON object, by converting it to a string first
procedure TDiscordWebhook.Execute(json: TJSONObject);
begin
// if no username is set and we have a default one, add it
if (json.IndexOfName('username') < 0) and (FUsername <> '') then
json.Add('username', username);
// sanity checks
if json.IndexOfName('content') < 0 then
raise EJSON.Create('Invalid webhook JSON: missing mandatory key "content"');
if json.Types['content'] <> jtString then
raise EJSON.Create('Invalid webhook JSON: value of "content" must be string');
PostRequest(PChar(json.AsJSON));
end;
// execute webhook for a simple string
procedure TDiscordWebhook.Execute(msg: string);
var
jObject: TJSONObject;
begin
jObject := TJSONObject.Create(['content', msg]);
try
Execute(jObject); // send JSON data
finally
jObject.Free;
end;
end;
// printf-style wrapper
procedure TDiscordWebhook.Execute(Fmt: string; Args: array of const);
begin
Execute(Format(Fmt, Args));
end;
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment