:3
This commit is contained in:
@@ -0,0 +1,561 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace Mocha2023.Classes
|
||||
{
|
||||
public static class DiscordLogger
|
||||
{
|
||||
private const string DISCORD_WEBHOOK_URL = "";
|
||||
private const string SHARECAM_DISCORD_WEBHOOK_URL = "";
|
||||
private const string SANITIZE_DISCORD_WEBHOOK_URL = "";
|
||||
private const string REPORT_DISCORD_WEBHOOK_URL = "";
|
||||
|
||||
private static readonly Uri? WebhookUrl = LoadWebhookUrl(
|
||||
"DISCORD_WEBHOOK_URL",
|
||||
DISCORD_WEBHOOK_URL);
|
||||
private static readonly Uri? ShareCameraWebhookUrl = LoadWebhookUrl(
|
||||
"SHARECAM_WEBHOOK_URL",
|
||||
SHARECAM_DISCORD_WEBHOOK_URL);
|
||||
private static readonly Uri? SanitizeWebhookUrl = LoadWebhookUrl(
|
||||
"SANITIZE_DISCORD_WEBHOOK_URL",
|
||||
SANITIZE_DISCORD_WEBHOOK_URL);
|
||||
private static readonly Uri? ReportWebhookUrl = LoadWebhookUrl(
|
||||
"REPORT_DISCORD_WEBHOOK_URL",
|
||||
REPORT_DISCORD_WEBHOOK_URL);
|
||||
private static readonly HttpClient _client = new()
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(10)
|
||||
};
|
||||
|
||||
private static Uri? LoadWebhookUrl(
|
||||
string environmentVariable,
|
||||
string fallbackUrl)
|
||||
{
|
||||
string? configured =
|
||||
Mocha2023.Program.LoadLocalSetting(environmentVariable);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(configured))
|
||||
configured = fallbackUrl;
|
||||
|
||||
if (!Uri.TryCreate(configured, UriKind.Absolute, out Uri? uri) ||
|
||||
uri.Scheme != Uri.UriSchemeHttps ||
|
||||
!(uri.Host.Equals("discord.com", StringComparison.OrdinalIgnoreCase) ||
|
||||
uri.Host.EndsWith(".discord.com", StringComparison.OrdinalIgnoreCase) ||
|
||||
uri.Host.Equals("discordapp.com", StringComparison.OrdinalIgnoreCase) ||
|
||||
uri.Host.EndsWith(".discordapp.com", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
public static void Log(string content)
|
||||
{
|
||||
_ = SendEmbedAsync(WebhookUrl, "Mocha Server Logs", content, 0x6F4E37);
|
||||
}
|
||||
|
||||
public static void LogSanitize(string content)
|
||||
{
|
||||
_ = SendEmbedAsync(SanitizeWebhookUrl, "Mocha Sanitize", content, 0xE67E22);
|
||||
}
|
||||
public static void LogReport(string content)
|
||||
{
|
||||
_ = SendEmbedAsync(ReportWebhookUrl, "Mocha Reports", content, 0x6F4E37);
|
||||
}
|
||||
|
||||
public static void LogBan(
|
||||
long targetPlayerId,
|
||||
string? targetUsername,
|
||||
long bannedByPlayerId,
|
||||
string? bannedByUsername,
|
||||
string reason,
|
||||
int durationSeconds,
|
||||
IEnumerable<(long PlayerId, string? Username)>? linkedAccounts = null,
|
||||
IEnumerable<(string Platform, string PlatformId)>? platformIdentities = null)
|
||||
{
|
||||
_ = SendBanEmbedAsync(
|
||||
targetPlayerId,
|
||||
targetUsername,
|
||||
bannedByPlayerId,
|
||||
bannedByUsername,
|
||||
reason,
|
||||
durationSeconds,
|
||||
linkedAccounts,
|
||||
platformIdentities);
|
||||
}
|
||||
|
||||
private static async Task SendBanEmbedAsync(
|
||||
long targetPlayerId,
|
||||
string? targetUsername,
|
||||
long bannedByPlayerId,
|
||||
string? bannedByUsername,
|
||||
string reason,
|
||||
int durationSeconds,
|
||||
IEnumerable<(long PlayerId, string? Username)>? linkedAccounts,
|
||||
IEnumerable<(string Platform, string PlatformId)>? platformIdentities)
|
||||
{
|
||||
if (WebhookUrl == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
string targetName = CleanDiscordValue(targetUsername, "Unknown", 100);
|
||||
string bannedByName = CleanDiscordValue(bannedByUsername, "Unknown", 100);
|
||||
string reasonValue = CleanDiscordValue(reason, "No reason provided.", 1_000);
|
||||
|
||||
string durationValue = durationSeconds <= 0 || durationSeconds == int.MaxValue
|
||||
? "Permanent"
|
||||
: FormatDuration(durationSeconds);
|
||||
|
||||
var linked = (linkedAccounts ?? Enumerable.Empty<(long, string?)>())
|
||||
.Where(account => account.Item1 != targetPlayerId)
|
||||
.ToList();
|
||||
|
||||
var fields = new List<object>
|
||||
{
|
||||
new
|
||||
{
|
||||
name = "🎯 Banned Player",
|
||||
value = $"**{targetName}**\n`Player ID: {targetPlayerId}`",
|
||||
inline = true
|
||||
},
|
||||
new
|
||||
{
|
||||
name = "🛡️ Banned By",
|
||||
value = $"**{bannedByName}**\n`Player ID: {bannedByPlayerId}`",
|
||||
inline = true
|
||||
},
|
||||
new
|
||||
{
|
||||
name = "⏱️ Duration",
|
||||
value = $"**{durationValue}**",
|
||||
inline = true
|
||||
},
|
||||
new
|
||||
{
|
||||
name = "📋 Reason",
|
||||
value = reasonValue,
|
||||
inline = false
|
||||
}
|
||||
};
|
||||
|
||||
if (platformIdentities != null)
|
||||
{
|
||||
string identityList = string.Join(
|
||||
"\n",
|
||||
platformIdentities.Select(identity =>
|
||||
$"`{CleanDiscordValue(identity.Platform, "Unknown", 30)}: " +
|
||||
$"{CleanDiscordValue(identity.PlatformId, "unknown", 80)}`"));
|
||||
if (!string.IsNullOrWhiteSpace(identityList))
|
||||
{
|
||||
fields.Add(new
|
||||
{
|
||||
name = "🔗 Linked Platform IDs",
|
||||
value = identityList,
|
||||
inline = false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (linked.Count > 0)
|
||||
{
|
||||
string linkedList = string.Join(
|
||||
"\n",
|
||||
linked.Select(account =>
|
||||
$"**{CleanDiscordValue(account.Item2, "Unknown", 60)}** " +
|
||||
$"`(ID: {account.Item1})`"));
|
||||
fields.Add(new
|
||||
{
|
||||
name = $"👥 Also Banned (linked accounts, {linked.Count})",
|
||||
value = linkedList.Length > 1000 ? linkedList[..1000] + "..." : linkedList,
|
||||
inline = false
|
||||
});
|
||||
}
|
||||
|
||||
var payload = new
|
||||
{
|
||||
username = "Mocha Moderation",
|
||||
allowed_mentions = new { parse = Array.Empty<string>() },
|
||||
embeds = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
title = "🔨 Player Banned",
|
||||
description = $"### {targetName} was banned",
|
||||
color = 0xC0392B,
|
||||
fields = fields.ToArray(),
|
||||
footer = new { text = "Mocha Moderation" },
|
||||
timestamp = DateTimeOffset.UtcNow.ToString("O")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(payload);
|
||||
using var body = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
using HttpResponseMessage response = await _client.PostAsync(WebhookUrl, body);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[DISCORD BAN] Webhook returned {(int)response.StatusCode} {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine($"[DISCORD BAN] Failed sending ban log: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatDuration(int totalSeconds)
|
||||
{
|
||||
if (totalSeconds <= 0)
|
||||
return "Permanent";
|
||||
|
||||
TimeSpan span = TimeSpan.FromSeconds(totalSeconds);
|
||||
if (span.TotalDays >= 1)
|
||||
return $"{span.TotalDays:0.##} day(s) ({totalSeconds}s)";
|
||||
if (span.TotalHours >= 1)
|
||||
return $"{span.TotalHours:0.##} hour(s) ({totalSeconds}s)";
|
||||
if (span.TotalMinutes >= 1)
|
||||
return $"{span.TotalMinutes:0.##} minute(s) ({totalSeconds}s)";
|
||||
return $"{totalSeconds} second(s)";
|
||||
}
|
||||
|
||||
public static void LogPlayerReport(
|
||||
Guid reportId,
|
||||
long reporterId,
|
||||
string? reporterUsername,
|
||||
long? reportedPlayerId,
|
||||
string? reportedUsername,
|
||||
int? reportCategory,
|
||||
string? details,
|
||||
long? roomId,
|
||||
string? roomInstanceType)
|
||||
{
|
||||
_ = SendPlayerReportAsync(
|
||||
reportId,
|
||||
reporterId,
|
||||
reporterUsername,
|
||||
reportedPlayerId,
|
||||
reportedUsername,
|
||||
reportCategory,
|
||||
details,
|
||||
roomId,
|
||||
roomInstanceType);
|
||||
}
|
||||
|
||||
private static async Task SendPlayerReportAsync(
|
||||
Guid reportId,
|
||||
long reporterId,
|
||||
string? reporterUsername,
|
||||
long? reportedPlayerId,
|
||||
string? reportedUsername,
|
||||
int? reportCategory,
|
||||
string? details,
|
||||
long? roomId,
|
||||
string? roomInstanceType)
|
||||
{
|
||||
if (ReportWebhookUrl == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
string reporterName = CleanDiscordValue(
|
||||
reporterUsername,
|
||||
"Unknown reporter",
|
||||
100);
|
||||
|
||||
string reportedName = CleanDiscordValue(
|
||||
reportedUsername,
|
||||
"Unknown player",
|
||||
100);
|
||||
|
||||
string reportDetails = CleanDiscordValue(
|
||||
details,
|
||||
"No additional details were provided.",
|
||||
1_000);
|
||||
|
||||
string categoryName = GetReportCategoryName(reportCategory);
|
||||
|
||||
string reportedPlayerValue = reportedPlayerId.HasValue
|
||||
? $"**{reportedName}**\n`Player ID: {reportedPlayerId.Value}`"
|
||||
: $"**{reportedName}**\n`Player ID unavailable`";
|
||||
|
||||
string reporterValue =
|
||||
$"**{reporterName}**\n`Player ID: {reporterId}`";
|
||||
|
||||
string roomValue = roomId.HasValue
|
||||
? $"`Room ID: {roomId.Value}`\n" +
|
||||
$"Instance: **{CleanDiscordValue(roomInstanceType, "Unknown", 50)}**"
|
||||
: "**Unknown room**";
|
||||
|
||||
var payload = new
|
||||
{
|
||||
username = "Mocha Reports",
|
||||
|
||||
allowed_mentions = new
|
||||
{
|
||||
parse = Array.Empty<string>()
|
||||
},
|
||||
|
||||
embeds = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
title = "🚨 New Player Report",
|
||||
|
||||
description =
|
||||
$"### {reportedName} was reported\n" +
|
||||
reportDetails,
|
||||
|
||||
color = 0x6F4E37,
|
||||
|
||||
fields = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
name = "🎯 Reported Player",
|
||||
value = reportedPlayerValue,
|
||||
inline = true
|
||||
},
|
||||
new
|
||||
{
|
||||
name = "👤 Reported By",
|
||||
value = reporterValue,
|
||||
inline = true
|
||||
},
|
||||
new
|
||||
{
|
||||
name = "📋 Category",
|
||||
value = $"**{categoryName}**",
|
||||
inline = false
|
||||
},
|
||||
new
|
||||
{
|
||||
name = "🏠 Room",
|
||||
value = roomValue,
|
||||
inline = false
|
||||
}
|
||||
},
|
||||
|
||||
footer = new
|
||||
{
|
||||
text = $"Mocha Moderation • Report {reportId}"
|
||||
},
|
||||
|
||||
timestamp = DateTimeOffset.UtcNow.ToString("O")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(payload);
|
||||
|
||||
using var body = new StringContent(
|
||||
json,
|
||||
Encoding.UTF8,
|
||||
"application/json");
|
||||
|
||||
using HttpResponseMessage response =
|
||||
await _client.PostAsync(ReportWebhookUrl, body);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[DISCORD REPORT] Webhook returned " +
|
||||
$"{(int)response.StatusCode} {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[DISCORD REPORT] Failed sending report: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string CleanDiscordValue(
|
||||
string? value,
|
||||
string fallback,
|
||||
int maximumLength)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return fallback;
|
||||
|
||||
string cleaned = value
|
||||
.Trim()
|
||||
.Replace("\r", " ")
|
||||
.Replace("\n", " ")
|
||||
.Replace("`", "'");
|
||||
|
||||
if (cleaned.Length > maximumLength)
|
||||
cleaned = cleaned[..maximumLength] + "...";
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
private static string GetReportCategoryName(int? category)
|
||||
{
|
||||
if (!category.HasValue)
|
||||
return "Unspecified";
|
||||
|
||||
return category.Value switch
|
||||
{
|
||||
104 => "Player Conduct",
|
||||
_ => $"Report Category {category.Value}"
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task SendEmbedAsync(
|
||||
Uri? webhookUrl,
|
||||
string senderName,
|
||||
string content,
|
||||
int color)
|
||||
{
|
||||
if (webhookUrl == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (content.Length > 4_000)
|
||||
content = content[..4_000] + "...";
|
||||
|
||||
var payload = JsonSerializer.Serialize(new
|
||||
{
|
||||
username = senderName,
|
||||
allowed_mentions = new { parse = Array.Empty<string>() },
|
||||
embeds = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
description = content,
|
||||
color,
|
||||
timestamp = DateTimeOffset.UtcNow.ToString("O")
|
||||
}
|
||||
}
|
||||
});
|
||||
var body = new StringContent(payload, Encoding.UTF8, "application/json");
|
||||
await _client.PostAsync(webhookUrl, body);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void LogLogin(long playerId, string? username = null)
|
||||
{
|
||||
Log($"🟢 **Login** — `{username ?? "unknown"}` (ID: `{playerId}`)");
|
||||
}
|
||||
|
||||
public static void LogChat(long playerId, string message, string? username = null)
|
||||
{
|
||||
|
||||
var safeMsg = message.Length > 1500 ? message[..1500] + "..." : message;
|
||||
Log($"💬 **Chat** — `{username ?? "unknown"}` (ID: `{playerId}`): {safeMsg}");
|
||||
}
|
||||
|
||||
public static void LogSanitizeChat(long playerId, string message, string? username = null)
|
||||
{
|
||||
var safeMsg = message.Length > 1500 ? message[..1500] + "..." : message;
|
||||
LogSanitize($"**Sanitize** - `{username ?? "unknown"}` (ID: `{playerId}`): {safeMsg}");
|
||||
}
|
||||
|
||||
public static void LogImage(string content, byte[] imageBytes, string fileName)
|
||||
{
|
||||
_ = SendImageAsync(
|
||||
WebhookUrl,
|
||||
content,
|
||||
imageBytes,
|
||||
fileName,
|
||||
"Mocha Server Logs");
|
||||
}
|
||||
|
||||
public static void LogPhotoUpload(long playerId, string? username, byte[] imageBytes, string fileName)
|
||||
{
|
||||
LogImage($"📸 **Photo taken** by `{username ?? "unknown"}` (ID: `{playerId}`)", imageBytes, fileName);
|
||||
}
|
||||
|
||||
public static void LogShareCameraPhoto(
|
||||
long playerId,
|
||||
string? username,
|
||||
byte[] imageBytes,
|
||||
string fileName,
|
||||
long? roomId,
|
||||
string? roomName)
|
||||
{
|
||||
string safeRoomName = string.IsNullOrWhiteSpace(roomName)
|
||||
? roomId.HasValue ? $"Room {roomId.Value}" : "Rooms"
|
||||
: roomName.Replace("[", "\\[").Replace("]", "\\]");
|
||||
string roomUrl = roomId.HasValue
|
||||
? $"{ServerConfig.BaseURL.TrimEnd('/')}/recnet/#room/{roomId.Value}"
|
||||
: $"{ServerConfig.BaseURL.TrimEnd('/')}/recnet/#rooms";
|
||||
string content =
|
||||
$"📸 **Share Camera photo** by `{username ?? "unknown"}` " +
|
||||
$"(ID: `{playerId}`)\n" +
|
||||
$"🏠 Room: [^{safeRoomName}]({roomUrl})";
|
||||
|
||||
_ = SendImageAsync(
|
||||
ShareCameraWebhookUrl,
|
||||
content,
|
||||
imageBytes,
|
||||
fileName,
|
||||
"Sharecam Social");
|
||||
}
|
||||
|
||||
private static async Task SendImageAsync(
|
||||
Uri? webhookUrl,
|
||||
string content,
|
||||
byte[] imageBytes,
|
||||
string fileName,
|
||||
string senderName)
|
||||
{
|
||||
if (webhookUrl == null || imageBytes.Length == 0 ||
|
||||
imageBytes.Length > 10 * 1024 * 1024)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var form = new MultipartFormDataContent();
|
||||
|
||||
fileName = Path.GetFileName(fileName);
|
||||
if (string.IsNullOrWhiteSpace(fileName))
|
||||
fileName = "upload.png";
|
||||
if (fileName.Length > 120)
|
||||
fileName = fileName[^120..];
|
||||
|
||||
var payload = JsonSerializer.Serialize(new
|
||||
{
|
||||
username = senderName,
|
||||
content = content.Length > 1_500 ? content[..1_500] : content,
|
||||
allowed_mentions = new { parse = Array.Empty<string>() },
|
||||
attachments = new[] { new { id = 0, filename = fileName } }
|
||||
});
|
||||
form.Add(new StringContent(payload, Encoding.UTF8, "application/json"), "payload_json");
|
||||
|
||||
var extension = Path.GetExtension(fileName).ToLowerInvariant();
|
||||
var mimeType = extension switch
|
||||
{
|
||||
".jpg" or ".jpeg" => "image/jpeg",
|
||||
".gif" => "image/gif",
|
||||
".webp" => "image/webp",
|
||||
_ => "image/png"
|
||||
};
|
||||
|
||||
var imageContent = new ByteArrayContent(imageBytes);
|
||||
imageContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(mimeType);
|
||||
|
||||
form.Add(imageContent, "files[0]", fileName);
|
||||
|
||||
using var response = await _client.PostAsync(webhookUrl, form);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user