mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-26 23:49:02 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RadicadeServer.DB;
|
||||
using RadicadeServer.RecNet.Enums.Moderation;
|
||||
using RadicadeServer.Discord.Webhooks;
|
||||
|
||||
namespace RadicadeServer.Radicade.Discord;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static class AdminActionDiscord
|
||||
{
|
||||
|
||||
public static void LogAction(string action, string? admin, string? details)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(WebhookConfig.AdminActivityUrl)) return;
|
||||
if (string.IsNullOrWhiteSpace(action)) return;
|
||||
|
||||
ReportDiscord.SendAsync(WebhookConfig.AdminActivityUrl, $"🛠️ Admin Action: {action}",
|
||||
new DiscordPayload
|
||||
{
|
||||
Title = $"Admin Action: {action}",
|
||||
ThumbnailUrl = WebhookImages.AdminActivity,
|
||||
Color = 0x5865F2,
|
||||
Fields = new List<DiscordField>
|
||||
{
|
||||
new() { Name = "Action", Value = Truncate(action, 200), Inline = true },
|
||||
new() { Name = "Admin", Value = Truncate(string.IsNullOrWhiteSpace(admin) ? "Unknown" : admin, 100), Inline = true },
|
||||
new() { Name = "Details", Value = Truncate(string.IsNullOrWhiteSpace(details) ? "—" : details, 900), Inline = false }
|
||||
},
|
||||
TimestampUtc = DateTime.UtcNow.ToString("O")
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static async Task LogBanAsync(RecDBContext db, long bannedPlayerId, string? bannedBy,
|
||||
bool causedByReports, string? reason, ReportCategory category, DateTime? expiry, bool isModerator,
|
||||
string? banMethod = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(WebhookConfig.InGameBansUrl)) return;
|
||||
|
||||
var playerName = bannedPlayerId.ToString();
|
||||
try
|
||||
{
|
||||
var profile = await db.Profiles.AsNoTracking().FirstOrDefaultAsync(x => x.accountId == bannedPlayerId);
|
||||
if (profile != null)
|
||||
playerName = string.IsNullOrWhiteSpace(profile.displayName)
|
||||
? (profile.username ?? bannedPlayerId.ToString())
|
||||
: profile.displayName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
var durationText = FormatDuration(expiry);
|
||||
|
||||
var fields = new List<DiscordField>
|
||||
{
|
||||
new() { Name = "Banned Player", Value = $"{playerName} ({bannedPlayerId})", Inline = true },
|
||||
new() { Name = "Banned By", Value = Truncate(string.IsNullOrWhiteSpace(bannedBy) ? "Unknown" : bannedBy, 100), Inline = true },
|
||||
new() { Name = "Caused by Reports", Value = causedByReports ? "Yes" : "No", Inline = true },
|
||||
new() { Name = "Category", Value = category.ToString(), Inline = true },
|
||||
new() { Name = "Duration", Value = durationText, Inline = true },
|
||||
new() { Name = "Is Moderator", Value = isModerator ? "Yes" : "No", Inline = true },
|
||||
new() { Name = "Reason", Value = Truncate(string.IsNullOrWhiteSpace(reason) ? "—" : reason, 900), Inline = false }
|
||||
};
|
||||
if (!string.IsNullOrWhiteSpace(banMethod))
|
||||
{
|
||||
fields.Insert(5, new DiscordField { Name = "Ban Method", Value = Truncate(banMethod, 100), Inline = true });
|
||||
}
|
||||
|
||||
ReportDiscord.SendAsync(WebhookConfig.InGameBansUrl, $"🚫 Player Banned: {playerName}",
|
||||
new DiscordPayload
|
||||
{
|
||||
Title = $"Player Banned: {playerName}",
|
||||
ThumbnailUrl = WebhookImages.InGameBan,
|
||||
Color = 0xE74C3C,
|
||||
Fields = fields,
|
||||
TimestampUtc = DateTime.UtcNow.ToString("O")
|
||||
});
|
||||
}
|
||||
|
||||
private static string FormatDuration(DateTime? expiry)
|
||||
{
|
||||
if (expiry == null || expiry.Value == DateTime.MaxValue)
|
||||
return "Permanent";
|
||||
|
||||
var remaining = expiry.Value - DateTime.UtcNow;
|
||||
if (remaining <= TimeSpan.Zero)
|
||||
return $"Expires {expiry.Value:yyyy-MM-dd HH:mm} UTC";
|
||||
|
||||
var parts = new List<string>();
|
||||
if (remaining.Days > 0) parts.Add($"{remaining.Days}d");
|
||||
if (remaining.Hours > 0) parts.Add($"{remaining.Hours}h");
|
||||
if (remaining.Minutes > 0) parts.Add($"{remaining.Minutes}m");
|
||||
if (remaining.Seconds > 0 || parts.Count == 0) parts.Add($"{remaining.Seconds}s");
|
||||
return $"{string.Join(" ", parts)} (until {expiry.Value:yyyy-MM-dd HH:mm} UTC)";
|
||||
}
|
||||
|
||||
private static string Truncate(string value, int max)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return "";
|
||||
if (value.Length <= max) return value;
|
||||
var cut = max;
|
||||
if (char.IsHighSurrogate(value[cut - 1]) && cut < value.Length && char.IsLowSurrogate(value[cut]))
|
||||
cut--;
|
||||
return value[..cut];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace RadicadeServer.Radicade.Discord;
|
||||
|
||||
public sealed class DiscordPayload
|
||||
{
|
||||
public string Title { get; set; } = "";
|
||||
public string Description { get; set; } = "";
|
||||
public int Color { get; set; }
|
||||
public List<DiscordField> Fields { get; set; } = new();
|
||||
public string TimestampUtc { get; set; } = "";
|
||||
public string ImageUrl { get; set; } = "";
|
||||
public string ThumbnailUrl { get; set; } = "";
|
||||
}
|
||||
|
||||
public sealed class DiscordField
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string Value { get; set; } = "";
|
||||
public bool Inline { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace RadicadeServer.Radicade.Discord;
|
||||
|
||||
public static class DiscordPayloadJson
|
||||
{
|
||||
private const int MaxContentLength = 2000;
|
||||
private const int MaxTitleLength = 256;
|
||||
private const int MaxDescriptionLength = 4096;
|
||||
private const int MaxFieldNameLength = 256;
|
||||
private const int MaxFieldValueLength = 1024;
|
||||
private const int MaxFieldCount = 25;
|
||||
|
||||
public static string Build(DiscordPayload payload, string? contentOverride = null, string? embedImageUrl = null)
|
||||
{
|
||||
var embed = new JsonObject();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(payload.Title))
|
||||
embed["title"] = Truncate(payload.Title, MaxTitleLength);
|
||||
if (!string.IsNullOrWhiteSpace(payload.Description))
|
||||
embed["description"] = Truncate(payload.Description, MaxDescriptionLength);
|
||||
embed["color"] = payload.Color;
|
||||
|
||||
var imageUrl = string.IsNullOrWhiteSpace(embedImageUrl) ? payload.ImageUrl : embedImageUrl;
|
||||
if (!string.IsNullOrWhiteSpace(imageUrl))
|
||||
embed["image"] = new JsonObject { ["url"] = Truncate(imageUrl, MaxTitleLength) };
|
||||
if (!string.IsNullOrWhiteSpace(payload.ThumbnailUrl))
|
||||
embed["thumbnail"] = new JsonObject { ["url"] = Truncate(payload.ThumbnailUrl, MaxTitleLength) };
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(payload.TimestampUtc)
|
||||
&& DateTime.TryParse(payload.TimestampUtc, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var parsed))
|
||||
{
|
||||
embed["timestamp"] = parsed.ToString("O");
|
||||
}
|
||||
|
||||
var fields = new JsonArray();
|
||||
foreach (var field in (payload.Fields ?? new List<DiscordField>()).Take(MaxFieldCount))
|
||||
{
|
||||
if (field == null) continue;
|
||||
fields.Add(new JsonObject
|
||||
{
|
||||
["name"] = Truncate(field.Name ?? "", MaxFieldNameLength),
|
||||
["value"] = Truncate(field.Value ?? "", MaxFieldValueLength),
|
||||
["inline"] = field.Inline
|
||||
});
|
||||
}
|
||||
if (fields.Count > 0)
|
||||
embed["fields"] = fields;
|
||||
|
||||
var message = new JsonObject
|
||||
{
|
||||
["content"] = Truncate(contentOverride ?? payload.Title ?? "", MaxContentLength)
|
||||
};
|
||||
message["embeds"] = new JsonArray { embed };
|
||||
|
||||
return message.ToJsonString();
|
||||
}
|
||||
|
||||
private static string Truncate(string value, int max)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return "";
|
||||
if (value.Length <= max) return value;
|
||||
|
||||
var cut = max;
|
||||
if (char.IsHighSurrogate(value[cut - 1]) && cut < value.Length && char.IsLowSurrogate(value[cut]))
|
||||
cut--;
|
||||
return value[..cut];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using CSharpDiscordWebhook.Objects;
|
||||
|
||||
namespace RadicadeServer.Radicade.Discord
|
||||
{
|
||||
public interface IWebhookTemplate
|
||||
{
|
||||
public static MessageBuilder Construct(params object[] args) { return null!; }
|
||||
public static async Task Send(params object[] args) { await RadicadeWebhook.SendMessage(Construct(args)); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using CSharpDiscordWebhook;
|
||||
using CSharpDiscordWebhook.Objects;
|
||||
using System.Runtime.InteropServices;
|
||||
using RadicadeServer.RecNet.Responses;
|
||||
using RadicadeServer.Discord.Webhooks;
|
||||
|
||||
namespace RadicadeServer.Radicade.Discord;
|
||||
public static class RadicadeWebhook
|
||||
{
|
||||
private static DiscordWebhook? Webhook;
|
||||
|
||||
private static string WebhookUrl => WebhookConfig.GeneralActivityUrl;
|
||||
|
||||
private static readonly Success WebhookInvalid = Success.Error("Webhook is disabled/invalid.");
|
||||
private static readonly Success CouldntSendMessage = Success.Error("Couldn't send message.");
|
||||
|
||||
public static bool IsWebhookValid()
|
||||
{
|
||||
return Uri.TryCreate(WebhookUrl, UriKind.Absolute, out var uri)
|
||||
&& (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
|
||||
}
|
||||
|
||||
private static bool TryInit()
|
||||
{
|
||||
if (!IsWebhookValid()) return false;
|
||||
if (Webhook != null) return true;
|
||||
|
||||
try
|
||||
{
|
||||
Webhook = new DiscordWebhook(new Uri(WebhookUrl));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Webhook = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async Task<Success> SendMessage(
|
||||
[Optional] string content,
|
||||
[Optional] List<EmbedBuilder> embed,
|
||||
[Optional] PollBuilder poll,
|
||||
[Optional] List<AttachmentBuilder> attachments
|
||||
)
|
||||
{
|
||||
if (!TryInit()) return WebhookInvalid;
|
||||
|
||||
try
|
||||
{
|
||||
var result = await Webhook!.SendMessageAsync(new MessageBuilder
|
||||
{
|
||||
Content = content,
|
||||
Embeds = embed,
|
||||
Poll = poll,
|
||||
Attachments = attachments
|
||||
});
|
||||
|
||||
return (result.Success) ? Success.Ok : CouldntSendMessage;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return CouldntSendMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Success> SendMessage(MessageBuilder message)
|
||||
{
|
||||
if (!TryInit()) return WebhookInvalid;
|
||||
|
||||
try
|
||||
{
|
||||
var result = await Webhook!.SendMessageAsync(message);
|
||||
|
||||
return (result.Success) ? Success.Ok : CouldntSendMessage;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return CouldntSendMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
|
||||
namespace RadicadeServer.Radicade.Discord;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static class ReportDiscord
|
||||
{
|
||||
private static readonly HttpClient Http = CreateHttpClient();
|
||||
|
||||
private static HttpClient CreateHttpClient()
|
||||
{
|
||||
var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("DiscordBot (https://radicade.app, 1.0)");
|
||||
return client;
|
||||
}
|
||||
|
||||
|
||||
public sealed record DiscordAttachment(string Filename, byte[] Data, string MimeType);
|
||||
|
||||
public static void SendAsync(string webhookUrl, string content, DiscordPayload payload)
|
||||
=> SendAsync(webhookUrl, content, payload, null, null);
|
||||
|
||||
public static void SendAsync(string webhookUrl, string content, DiscordPayload payload,
|
||||
IReadOnlyList<DiscordAttachment>? attachments, string? embedImageUrl = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(webhookUrl)) return;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var response = attachments is { Count: > 0 }
|
||||
? await PostWithFilesAsync(webhookUrl, content, payload, attachments, embedImageUrl)
|
||||
: await PostJsonAsync(webhookUrl, content, payload, embedImageUrl);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorBody = await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<HttpResponseMessage> PostJsonAsync(string url, string content, DiscordPayload payload, string? embedImageUrl)
|
||||
{
|
||||
var body = DiscordPayloadJson.Build(payload, content, embedImageUrl);
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||
return await Http.SendAsync(request);
|
||||
}
|
||||
|
||||
private static async Task<HttpResponseMessage> PostWithFilesAsync(string url, string content, DiscordPayload payload,
|
||||
IReadOnlyList<DiscordAttachment> attachments, string? embedImageUrl)
|
||||
{
|
||||
var json = DiscordPayloadJson.Build(payload, content, embedImageUrl);
|
||||
using var form = new MultipartFormDataContent();
|
||||
form.Add(new StringContent(json, Encoding.UTF8, "application/json"), "payload_json");
|
||||
for (int i = 0; i < attachments.Count; i++)
|
||||
{
|
||||
var file = new ByteArrayContent(attachments[i].Data);
|
||||
file.Headers.ContentType = new MediaTypeHeaderValue(attachments[i].MimeType);
|
||||
form.Add(file, $"files[{i}]", attachments[i].Filename);
|
||||
}
|
||||
return await Http.PostAsync(url, form);
|
||||
}
|
||||
|
||||
private static string Truncate(string value, int max)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return "";
|
||||
if (value.Length <= max) return value;
|
||||
var cut = max;
|
||||
if (char.IsHighSurrogate(value[cut - 1]) && cut < value.Length && char.IsLowSurrogate(value[cut]))
|
||||
cut--;
|
||||
return value[..cut];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace RadicadeServer.Discord.Webhooks;
|
||||
|
||||
public static class WebhookImages
|
||||
{
|
||||
public const string BanAppeal = "https://cdn.radicade.app/BanAppeal.png";
|
||||
public const string AdminActivity = "https://cdn.radicade.app/AuditLogs.png";
|
||||
public const string InGameBan = "https://cdn.radicade.app/BanRecords.png";
|
||||
public const string BugReport = "https://cdn.radicade.app/BugReport.png";
|
||||
public const string ClubReport = "https://cdn.radicade.app/ClubReport.png";
|
||||
public const string PlayerReport = "https://cdn.radicade.app/PlayerReport.png";
|
||||
public const string RoomReport = "https://cdn.radicade.app/RoomReport.png";
|
||||
public const string EventReport = "https://cdn.radicade.app/EventReport.png";
|
||||
public const string InventionReport = "https://cdn.radicade.app/InventionReport.png";
|
||||
|
||||
public static string? ForReport(string? reportType) => reportType?.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"club" => ClubReport,
|
||||
"player" => PlayerReport,
|
||||
"room" => RoomReport,
|
||||
"event" => EventReport,
|
||||
"invention" => InventionReport,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public static class WebhookConfig
|
||||
{
|
||||
private const string ConfigurationPrefix = "Radicade:Discord:Webhooks:";
|
||||
|
||||
public static string BanAppealsUrl { get; private set; } = "";
|
||||
public static string AdminActivityUrl { get; private set; } = "";
|
||||
public static string InGameBansUrl { get; private set; } = "";
|
||||
public static string ReportsUrl { get; private set; } = "";
|
||||
public static string GeneralActivityUrl { get; private set; } = "";
|
||||
|
||||
public static void Load(IConfiguration configuration, string contentRootPath)
|
||||
{
|
||||
var fileValues = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
||||
var filePath = Path.Combine(contentRootPath, "Discord", "Webhooks", "webhooks.json");
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
fileValues = JsonSerializer.Deserialize<Dictionary<string, string?>>(File.ReadAllText(filePath))
|
||||
?? fileValues;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
BanAppealsUrl = ReadUrl("BanAppeals");
|
||||
AdminActivityUrl = ReadUrl("AdminActivity");
|
||||
InGameBansUrl = ReadUrl("InGameBans");
|
||||
ReportsUrl = ReadUrl("Reports");
|
||||
GeneralActivityUrl = ReadUrl("GeneralActivity");
|
||||
|
||||
string ReadUrl(string name)
|
||||
{
|
||||
var configuredValue = configuration[ConfigurationPrefix + name];
|
||||
var value = configuredValue ?? (fileValues.TryGetValue(name, out var fileValue) ? fileValue : "");
|
||||
if (string.IsNullOrWhiteSpace(value)) return "";
|
||||
|
||||
value = value.Trim();
|
||||
if (Uri.TryCreate(value, UriKind.Absolute, out var uri)
|
||||
&& uri.Scheme == Uri.UriSchemeHttps
|
||||
&& string.Equals(uri.Host, "discord.com", StringComparison.OrdinalIgnoreCase)
|
||||
&& uri.AbsolutePath.StartsWith("/api/webhooks/", StringComparison.Ordinal))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using CSharpDiscordWebhook.Objects;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RadicadeServer.Radicade.Discord.Templates
|
||||
{
|
||||
public class WebhookImageUpload : IWebhookTemplate
|
||||
{
|
||||
public static async Task Send(string image, string username, string displayName, string profileImage, string? roomName = null) { await RadicadeWebhook.SendMessage(Construct(image,username,displayName,profileImage,roomName!)); }
|
||||
|
||||
public static MessageBuilder Construct(params object[] args)
|
||||
{
|
||||
if (args.Length < 4) {
|
||||
throw new ArgumentException("you FUCKER i expected: image, username, display name, profile image (and probably room name)");
|
||||
}
|
||||
|
||||
string image = (string)args[0];
|
||||
string username = (string)args[1];
|
||||
string displayName = (string)args[2];
|
||||
string profileImage = (string)args[3];
|
||||
string? roomName = (args.Length >= 5) ? (string)args[4] : null;
|
||||
|
||||
return new MessageBuilder
|
||||
{
|
||||
Embeds = [
|
||||
new EmbedBuilder {
|
||||
Author = new EmbedAuthorBuilder {
|
||||
Name = $"@{username}",
|
||||
IconUrl = profileImage + "?width=256&cropSquare=1"
|
||||
},
|
||||
Color = Color.LimeGreen,
|
||||
Description = (roomName == null) ? $"{displayName} just uploaded an image!" : $"{displayName} just uploaded an image in **{roomName}**!",
|
||||
Title = $"New image uploaded by {displayName}!",
|
||||
Image = new EmbedMediaBuilder {
|
||||
Url = image,
|
||||
Width = 768
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using CSharpDiscordWebhook.Objects;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using RadicadeServer.RecNet.Rooms;
|
||||
|
||||
namespace RadicadeServer.Radicade.Discord.Templates
|
||||
{
|
||||
public class WebhookRoomUpload : IWebhookTemplate
|
||||
{
|
||||
public static async Task Send(string image, string username, string displayName, string profileImage, string roomName, bool republish = false, string description = "None.") { await RadicadeWebhook.SendMessage(Construct(image, username, displayName, profileImage, roomName, republish, description)); }
|
||||
|
||||
public static MessageBuilder Construct(string image, string username, string displayName, string profileImage, string roomName, bool republish = false, string description = "None.")
|
||||
{
|
||||
return new MessageBuilder
|
||||
{
|
||||
Embeds = [
|
||||
new EmbedBuilder {
|
||||
Author = new EmbedAuthorBuilder {
|
||||
Name = $"@{username}",
|
||||
IconUrl = profileImage + "?width=256&cropSquare=1"
|
||||
},
|
||||
Color = Color.Aqua,
|
||||
Description = !republish ? $"{displayName} just published **{roomName}**!" : $"{displayName} just republished **{roomName}**!",
|
||||
Title = !republish ? $"New room published by {displayName}!" : $"Room republished by {displayName}!",
|
||||
Image = new EmbedMediaBuilder {
|
||||
Url = image + "?width=768",
|
||||
Width = 768
|
||||
},
|
||||
Fields = [
|
||||
new EmbedFieldBuilder {
|
||||
Inline = false,
|
||||
Name = "Description",
|
||||
Value = description
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"BanAppeals": "",
|
||||
"AntiCheat": "",
|
||||
"AdminActivity": "",
|
||||
"InGameBans": "",
|
||||
"Reports": "",
|
||||
"GeneralActivity": ""
|
||||
}
|
||||
Reference in New Issue
Block a user