Files
Relaunch-Process/Discord/DiscordWebHook.cs
2025-09-26 16:50:58 +03:00

162 lines
6.7 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Discord.Webhook
{
public class DiscordWebhook
{
public string Url { get; set; }
public int queueRetryCount = 3;
public int sendTimeoutSeconds = 5;
private UInt64 totalMessages = 0;
private ConcurrentQueue<(UInt64 num, DiscordMessage message, FileInfo[] files)> _queue = new ConcurrentQueue<(UInt64 num, DiscordMessage, FileInfo[])>();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private bool _isProcessing;
private CancellationTokenSource _cts = new CancellationTokenSource();
private int queueErrorCounter = 0;
private int queueSuppressedCounter = 0;
private HttpRequestException lastHpptEx = null;
public UInt64 TotalMessages => totalMessages;
public int QueueSize => _queue.Count;
public void CancelProcessing() => _cts.Cancel();
public int ErrorCount => queueErrorCounter;
public bool IsProcessing => _isProcessing;
public HttpRequestException LastHpptEx => lastHpptEx;
public async Task<bool> SendAsync(DiscordMessage message, params FileInfo[] files)
{
if (string.IsNullOrEmpty(Url))
throw new ArgumentNullException("Invalid Webhook URL.");
string boundary = "------------------------" + DateTime.Now.Ticks.ToString("x");
using (var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(sendTimeoutSeconds) })
using (var content = new MultipartFormDataContent(boundary))
{
// Добавляем JSON payload
var jsonContent = new StringContent(message.ToString(), Encoding.UTF8, "application/json");
jsonContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data")
{
Name = "\"payload_json\""
};
content.Add(jsonContent);
// Добавляем файлы
for (int i = 0; i < files.Length; i++)
{
if (files[i].Exists)
{
var fileContent = new ByteArrayContent(File.ReadAllBytes(files[i].FullName));
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
fileContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data")
{
Name = $"\"file_{i}\"",
FileName = $"\"{files[i].Name}\""
};
content.Add(fileContent);
}
}
try
{
var response = await client.PostAsync(Url, content, _cts.Token);
response.EnsureSuccessStatusCode();
}
catch (HttpRequestException ex)
{
Debug.WriteLine($"SendAsync: Discord webhook request failed: {ex.Message}");
lastHpptEx = ex;
return false;
}
catch (TaskCanceledException ex)
{
Debug.WriteLine($"SendAsync: Discord webhook request cancelled: {ex.Message}");
return false;
}
}
return true;
}
private async Task ProcessQueueAsync()
{
queueErrorCounter = 0;
while (_queue.TryPeek(out var item))
{
if (_cts.Token.IsCancellationRequested)
{
Debug.WriteLine("ProcessQueueAsync: Discord queue processing cancelled.");
break;
}
await _semaphore.WaitAsync();
try
{
Debug.WriteLine($"ProcessQueueAsync: Processing message {item.num}. Queue size: {_queue.Count}");
if (await SendAsync(item.message, item.files))
{
_queue.TryDequeue(out var deqItem);
queueErrorCounter = 0;
}
else
{
queueErrorCounter++;
if (queueErrorCounter == queueRetryCount)
{
_queue.TryDequeue(out var deqItem);
queueErrorCounter = 0;
queueSuppressedCounter++;
Debug.WriteLine($"ProcessQueueAsync: Message dropped. Total messages dropped:{queueSuppressedCounter}. Queue size: {_queue.Count}.");
}
}
}
finally
{
_semaphore.Release();
}
try
{
await Task.Delay(1000, _cts.Token); // Discord rate limit: 1 message per second
}
catch (TaskCanceledException)
{
Debug.WriteLine($"ProcessQueueAsync: Discord queue processing cancelled during delay. Was {_queue.Count} messages in queue. {totalMessages} messages in session. ");
break;
}
}
if ( _cts.IsCancellationRequested)
{
Debug.WriteLine($"ProcessQueueAsync: Discord queue processing cancelled. Was {_queue.Count} messages in queue.");
Debug.WriteLine("Clearing queue.");
var _newqueue = new ConcurrentQueue<(UInt64 num, DiscordMessage, FileInfo[])>();
Interlocked.Exchange(ref _queue, _newqueue);
}
Debug.WriteLine($"ProcessQueueAsync: Discord queue processing finished.");
_isProcessing = false;
}
public void Send(DiscordMessage message, params FileInfo[] files)
{
_queue.Enqueue((totalMessages++, message, files));
Debug.WriteLine($"Message {totalMessages-1} added. Queue size: {_queue.Count}");
if (_isProcessing)
{
Debug.WriteLine("Already processing queue.");
return;
}
Debug.WriteLine("Run ProcessQueueAsync");
_cts.Dispose();
_cts = new CancellationTokenSource();
_isProcessing = true;
Task.Run(ProcessQueueAsync);
return;
}
}
}