@@ -243,6 +243,7 @@ MyDiscord.Setup('my_config.json'); // Uses specified file
243243procedure TDiscordClient.Setup(ConfigPath: String = '');
244244var
245245 Path: String;
246+ webhookFromConfig: String;
246247begin
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*)
298298function TDiscordClient.SetWebhook(WebhookURL: String): Boolean;
299+ var
300+ tempHTTP: Int32;
301+ response: String;
302+ statusCode: Int32;
303+ emptyPayload: String;
304+ oldURL: String;
299305begin
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;
313392end;
314393
315394(*
@@ -368,6 +447,8 @@ function TDiscordClient.Send: Boolean;
368447var
369448 Payload: String;
370449 Attempts: Int32;
450+ AttemptFailedDueToException: Boolean;
451+ StatusCode: Int32; // Added to store HTTP status code
371452begin
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;
458561 Boundary, FileContents, Ext, MimeType: String;
459562 Payload: String;
460563 Attempts: Int32;
564+ AttemptFailedDueToException: Boolean;
565+ StatusCode: Int32; // Added to store HTTP status code
461566begin
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;
618737end;
619738
739+
620740{
621741// Example Usage
622742var
623743 Discord: TDiscordClient;
624744 EmbedIdx: Int32;
625-
745+
626746begin
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+
660782end.
661783}
0 commit comments