Created
May 7, 2020 22:27
-
-
Save n1tehawk/ba2f7911522d47a18946b35464bfed58 to your computer and use it in GitHub Desktop.
FreePascal unit to execute Discord webhooks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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