Files
mochaisvibecoded 42f7f57b72 :3
2026-08-24 17:00:26 -04:00

562 lines
20 KiB
C#

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
{
}
}
}
}