Skip to content

Commit e644bdf

Browse files
fix: Merge pull request #231 from TazE-scripts/master
Update discord.simba
2 parents 05db61d + 0b5f536 commit e644bdf

File tree

1 file changed

+161
-39
lines changed

1 file changed

+161
-39
lines changed

optional/handlers/discord.simba

Lines changed: 161 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ MyDiscord.Setup('my_config.json'); // Uses specified file
243243
procedure TDiscordClient.Setup(ConfigPath: String = '');
244244
var
245245
Path: String;
246+
webhookFromConfig: String;
246247
begin
247248
Self.Name := 'Discord';
248249
if ConfigPath = '' then
@@ -252,27 +253,26 @@ begin
252253

253254
Self.Config.Setup(Path);
254255

255-
// Only set webhook URL if not already set
256-
if (Self.Webhook.URL = '') and Self.Config.Has('webhook_url') then
256+
// Load webhook URL from config but don't validate it yet
257+
if Self.Config.Has('webhook_url') then
257258
begin
258-
Self.Webhook.Setup(Self.Config.GetString('webhook_url'));
259-
Self.DebugLn('Loaded webhook URL from config');
260-
end
261-
else if Self.Webhook.URL = '' then
262-
begin
263-
Self.LastError := 'Missing webhook_url in configuration';
264-
Exit;
259+
webhookFromConfig := Self.Config.GetString('webhook_url');
260+
if webhookFromConfig <> '' then
261+
begin
262+
Self.Webhook.URL := webhookFromConfig; // Just set the URL without validation
263+
Self.DebugLn('Loaded webhook URL from config');
264+
end;
265265
end;
266266

267-
// Only set username if not already set
268-
if (Self.Webhook.Username = '') and Self.Config.Has('webhook_username') then
267+
// Load username if available
268+
if Self.Config.Has('webhook_username') then
269269
begin
270270
Self.Webhook.Username := Self.Config.GetString('webhook_username');
271271
Self.DebugLn('Loaded username from config');
272272
end;
273273

274-
// Only set avatar if not already set
275-
if (Self.Webhook.AvatarURL = '') and Self.Config.Has('webhook_avatar') then
274+
// Load avatar if available
275+
if Self.Config.Has('webhook_avatar') then
276276
begin
277277
Self.Webhook.AvatarURL := Self.Config.GetString('webhook_avatar');
278278
Self.DebugLn('Loaded avatar from config');
@@ -296,6 +296,12 @@ MyDiscord.SetWebhook('https://discord.com/api/webhooks/your_webhook_id/your_webh
296296
```
297297
*)
298298
function TDiscordClient.SetWebhook(WebhookURL: String): Boolean;
299+
var
300+
tempHTTP: Int32;
301+
response: String;
302+
statusCode: Int32;
303+
emptyPayload: String;
304+
oldURL: String;
299305
begin
300306
Self.LastError := '';
301307

@@ -305,11 +311,84 @@ begin
305311
Exit(False);
306312
end;
307313

308-
Self.Webhook.Setup(WebhookURL);
309-
Self.Config.Put('webhook_url', WebhookURL);
310-
Self.DebugLn('Webhook URL set to: ' + WebhookURL);
314+
// Store the old URL in case validation fails
315+
oldURL := Self.Webhook.URL;
311316

312-
Result := True;
317+
// Basic pattern validation for Discord webhook URLs
318+
if not (Pos('https://discord.com/api/webhooks/', WebhookURL) = 1) then
319+
begin
320+
Self.LastError := 'Invalid webhook URL format: ' + WebhookURL;
321+
Self.DebugLn('Webhook URL does not match Discord pattern');
322+
Exit(False);
323+
end;
324+
325+
// Split by '/' and expect at least 6 parts for a valid webhook URL
326+
// Format: https://discord.com/api/webhooks/{webhook_id}/{webhook_token}
327+
if Length(WebhookURL.Split('/')) < 6 then
328+
begin
329+
Self.LastError := 'Incomplete webhook URL: ' + WebhookURL;
330+
Self.DebugLn('Webhook URL is missing ID or token');
331+
Exit(False);
332+
end;
333+
334+
// Validate the webhook URL by making a minimal POST request
335+
try
336+
tempHTTP := InitializeHTTPClient(False);
337+
SetHTTPHeader(tempHTTP, 'Content-Type', 'application/json');
338+
339+
// Empty payload that won't actually send a message
340+
emptyPayload := '{}';
341+
342+
response := PostHTTPPage(tempHTTP, WebhookURL, emptyPayload);
343+
statusCode := GetHTTPResponseCode(tempHTTP);
344+
FreeHTTPClient(tempHTTP);
345+
346+
// Check for specific webhook errors
347+
if (statusCode = 404) or (Pos('"code": 10015', response) > 0) then
348+
begin
349+
Self.LastError := 'Invalid webhook URL: ' + WebhookURL;
350+
Self.DebugLn('Webhook validation failed: ' + response);
351+
// Restore previous URL if there was one
352+
if oldURL <> '' then
353+
Self.Webhook.URL := oldURL;
354+
Exit(False);
355+
end;
356+
357+
// A 400 status might mean the webhook is valid but the payload is invalid
358+
// which is what we expect with our empty payload
359+
if (statusCode = 400) and (Pos('"code": 50006', response) > 0) then
360+
begin
361+
// This is actually good - webhook exists but empty payload is invalid
362+
Self.Webhook.Setup(WebhookURL);
363+
Self.Config.Put('webhook_url', WebhookURL);
364+
Self.DebugLn('Webhook URL set and validated');
365+
Exit(True);
366+
end;
367+
368+
// For other successful responses, accept the webhook
369+
if (statusCode >= 200) and (statusCode < 300) then
370+
begin
371+
Self.Webhook.Setup(WebhookURL);
372+
Self.Config.Put('webhook_url', WebhookURL);
373+
Self.DebugLn('Webhook URL set and validated');
374+
Exit(True);
375+
end;
376+
377+
// If we get here, something unexpected happened
378+
Self.LastError := 'Webhook validation received unexpected response: ' + IntToStr(statusCode) + ' - ' + response;
379+
Self.DebugLn(Self.LastError);
380+
// Restore previous URL if there was one
381+
if oldURL <> '' then
382+
Self.Webhook.URL := oldURL;
383+
Result := False;
384+
except
385+
Self.LastError := 'Error validating webhook: ' + GetExceptionMessage;
386+
Self.DebugLn('Webhook validation exception: ' + Self.LastError);
387+
// Restore previous URL if there was one
388+
if oldURL <> '' then
389+
Self.Webhook.URL := oldURL;
390+
Result := False;
391+
end;
313392
end;
314393

315394
(*
@@ -368,6 +447,8 @@ function TDiscordClient.Send: Boolean;
368447
var
369448
Payload: String;
370449
Attempts: Int32;
450+
AttemptFailedDueToException: Boolean;
451+
StatusCode: Int32; // Added to store HTTP status code
371452
begin
372453
Self.LastError := '';
373454
Self.LastResponse := '';
@@ -378,11 +459,19 @@ begin
378459
Exit(False);
379460
end;
380461

462+
// Abort if this webhook was previously found to be invalid via SetWebhook
463+
if Self.LastError.Contains('Invalid webhook URL') or Self.LastError.Contains('webhook validation failed') then
464+
begin
465+
Self.DebugLn('Aborting send: webhook previously failed validation: ' + Self.LastError);
466+
Exit(False);
467+
end;
468+
381469
Payload := Self.Webhook.ToJSON();
382470
Attempts := 0;
383471

384472
repeat
385473
try
474+
AttemptFailedDueToException := False; // Reset flag for this attempt
386475
Inc(Attempts);
387476

388477
if Self.HTTP <> 0 then
@@ -392,16 +481,26 @@ begin
392481
SetHTTPHeader(Self.HTTP, 'Content-Type', 'application/json');
393482

394483
Self.LastResponse := PostHTTPPage(Self.HTTP, Self.Webhook.URL, Payload);
484+
StatusCode := GetHTTPResponseCode(Self.HTTP);
395485

396-
if Self.LastResponse = '' then
486+
// Check for 2xx status code and no exception for success
487+
if (StatusCode >= 200) and (StatusCode < 300) and (not AttemptFailedDueToException) then
397488
begin
398-
Self.DebugLn('Discord webhook sent successfully');
399-
400-
// Clear content and embeds after successful send
489+
Self.DebugLn('Discord webhook sent successfully (Status: ' + IntToStr(StatusCode) + ')');
401490
Self.Webhook.ClearAll();
402-
403491
Exit(True);
404492
end;
493+
494+
// If not successful yet, store error details if not already set by exception
495+
if (Self.LastError = '') and (not AttemptFailedDueToException) then
496+
Self.LastError := 'HTTP Error: ' + IntToStr(StatusCode) + ' - Response: ' + Self.LastResponse;
497+
498+
// Check for permanent errors that shouldn't be retried
499+
if (StatusCode = 404) or (Pos('"code": 10015', Self.LastResponse) > 0) then
500+
begin
501+
Self.DebugLn('Permanent error detected - webhook not found. Aborting retries.');
502+
Exit(False);
503+
end;
405504

406505
if Pos('"code": 429', Self.LastResponse) > 0 then
407506
begin
@@ -419,18 +518,22 @@ begin
419518
SetHTTPHeader(Self.HTTP, 'Content-Type', 'application/x-www-form-urlencoded');
420519
AddPostVariable(Self.HTTP, 'content', Self.Webhook.Content);
421520

521+
// Note: This alternative method might need status code checking too,
522+
// but leaving it as is for now unless it also proves problematic.
422523
Self.LastResponse := PostHTTPPageEx(Self.HTTP, Self.Webhook.URL);
423524

424-
if Self.LastResponse = '' then
525+
if (Self.LastResponse = '') and (not AttemptFailedDueToException) then
425526
begin
426-
Self.Webhook.Content := '';
527+
Self.Webhook.Content := ''; // Assuming empty response means success here
427528
Exit(True);
428529
end;
429530
end;
430531

431-
Self.DebugLn('Webhook response: ', Self.LastResponse);
532+
// Log error before potential retry
533+
Self.DebugLn('Webhook attempt failed. Status: ' + IntToStr(StatusCode) + ', Error: ' + Self.LastError + ', Response: ' + Self.LastResponse);
432534

433535
except
536+
AttemptFailedDueToException := True;
434537
Self.LastError := GetExceptionMessage;
435538
Self.DebugLn('Discord error: ', Self.LastError);
436539
end;
@@ -458,6 +561,8 @@ var
458561
Boundary, FileContents, Ext, MimeType: String;
459562
Payload: String;
460563
Attempts: Int32;
564+
AttemptFailedDueToException: Boolean;
565+
StatusCode: Int32; // Added to store HTTP status code
461566
begin
462567
Self.LastError := '';
463568
Self.LastResponse := '';
@@ -491,6 +596,7 @@ begin
491596

492597
repeat
493598
try
599+
AttemptFailedDueToException := False; // Reset flag for this attempt
494600
Inc(Attempts);
495601

496602
if Self.HTTP <> 0 then
@@ -517,25 +623,38 @@ begin
517623
Payload += '--' + Boundary + '--' + LineEnding;
518624

519625
Self.LastResponse := PostHTTPPage(Self.HTTP, Self.Webhook.URL, Payload);
626+
StatusCode := GetHTTPResponseCode(Self.HTTP); // Assumes this function exists!
520627

521-
if Pos('"attachments":', Self.LastResponse) > 0 then
628+
// Check for 2xx status, "attachments" in response, and no exception for success
629+
if (StatusCode >= 200) and (StatusCode < 300) and (Pos('"attachments":', Self.LastResponse) > 0) and (not AttemptFailedDueToException) then
522630
begin
523-
Self.DebugLn('Discord file sent successfully');
524-
631+
Self.DebugLn('Discord file sent successfully (Status: ' + IntToStr(StatusCode) + ')');
525632
Self.Webhook.ClearAll();
526-
527633
Exit(True);
528634
end;
529635

636+
// If not successful yet, store error details if not already set by exception
637+
if (Self.LastError = '') and (not AttemptFailedDueToException) then
638+
Self.LastError := 'HTTP Error: ' + IntToStr(StatusCode) + ' - Response: ' + Self.LastResponse;
639+
640+
// Check for permanent errors that shouldn't be retried
641+
if (StatusCode = 404) or (Pos('"code": 10015', Self.LastResponse) > 0) then
642+
begin
643+
Self.DebugLn('Permanent error detected - webhook not found. Aborting retries.');
644+
Exit(False);
645+
end;
646+
530647
if Pos('"code": 429', Self.LastResponse) > 0 then
531648
begin
532649
Wait(Self.RetryDelay);
533650
Continue;
534651
end;
535652

536-
Self.DebugLn('Webhook response: ', Self.LastResponse);
653+
// Log error before potential retry
654+
Self.DebugLn('Webhook file attempt failed. Status: ' + IntToStr(StatusCode) + ', Error: ' + Self.LastError + ', Response: ' + Self.LastResponse);
537655

538656
except
657+
AttemptFailedDueToException := True;
539658
Self.LastError := GetExceptionMessage;
540659
Self.DebugLn('Discord error: ', Self.LastError);
541660
end;
@@ -617,45 +736,48 @@ begin
617736
Self.HTTP := 0;
618737
end;
619738

739+
620740
{
621741
// Example Usage
622742
var
623743
Discord: TDiscordClient;
624744
EmbedIdx: Int32;
625-
745+
626746
begin
627747
// First setup the client
628748
Discord.Setup();
629-
749+
630750
// Then set/override specific properties
631-
Discord.SetWebhook('https://discord.com/api/webhooks/your_webhook_id/your_webhook_token');
751+
if not Discord.SetWebhook('https://discord.com/api/webhooks/your_webhook_id/your_webhook_token') then
752+
Exit;
632753
Discord.SetUsername('OSRS Bot');
633754
Discord.SetAvatar('https://raw.githubusercontent.com/Torwent/wasp-webapp/refs/heads/main/static/favicon.png');
634-
755+
635756
try
636757
// Send a simple text message
637758
Discord.Webhook.Content := 'Hello from Simba!';
638759
if Discord.Send() then
639-
WriteLn('Message sent successfully')
760+
Discord.DebugLn('Message sent successfully')
640761
else
641-
WriteLn('Failed to send message: ', Discord.LastError);
642-
762+
Discord.DebugLn('Failed to send message: ' + Discord.LastError);
763+
643764
// Send a message with an embed
644765
Discord.Webhook.Content := 'Player stats:';
645766
EmbedIdx := Discord.Webhook.AddEmbed();
646767
with Discord.Webhook.Embeds[EmbedIdx] do
647768
begin
648769
Title := 'Current Status';
649-
Description := 'Combat Level: ' + ToStr(126);
770+
Description := ':crown: Combat Level: ' + ToStr(126);
650771
Color := clGreen;
651772
end;
652773
Discord.Send();
653-
774+
654775
// Send a screenshot
655776
Discord.Webhook.Content := 'Current game state:';
656777
Discord.SendScreenshot(True);
657778
finally
658779
Discord.Free();
659780
end;
781+
660782
end.
661783
}

0 commit comments

Comments
 (0)