183
Content.Server/Discord/WebhookMessages/VoteWebhooks.cs
Normal file
183
Content.Server/Discord/WebhookMessages/VoteWebhooks.cs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
using Content.Server.GameTicking;
|
||||||
|
using Content.Server.Voting;
|
||||||
|
using Robust.Server;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace Content.Server.Discord.WebhookMessages;
|
||||||
|
|
||||||
|
public sealed class VoteWebhooks : IPostInjectInit
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
|
[Dependency] private readonly IEntitySystemManager _entSys = default!;
|
||||||
|
[Dependency] private readonly DiscordWebhook _discord = default!;
|
||||||
|
[Dependency] private readonly IBaseServer _baseServer = default!;
|
||||||
|
|
||||||
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
|
public WebhookState? CreateWebhookIfConfigured(VoteOptions voteOptions, string? webhookUrl = null, string? customVoteName = null, string? customVoteMessage = null)
|
||||||
|
{
|
||||||
|
// All this webhook code is complete garbage.
|
||||||
|
// I tried to clean it up somewhat, at least to fix the glaring bugs in it.
|
||||||
|
// Jesus christ man what is with our code review process.
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(webhookUrl))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Set up the webhook payload
|
||||||
|
var serverName = _baseServer.ServerName;
|
||||||
|
|
||||||
|
var fields = new List<WebhookEmbedField>();
|
||||||
|
|
||||||
|
foreach (var voteOption in voteOptions.Options)
|
||||||
|
{
|
||||||
|
var newVote = new WebhookEmbedField
|
||||||
|
{
|
||||||
|
Name = voteOption.text,
|
||||||
|
Value = Loc.GetString("custom-vote-webhook-option-pending")
|
||||||
|
};
|
||||||
|
fields.Add(newVote);
|
||||||
|
}
|
||||||
|
|
||||||
|
var gameTicker = _entSys.GetEntitySystemOrNull<GameTicker>();
|
||||||
|
_sawmill = Logger.GetSawmill("discord");
|
||||||
|
|
||||||
|
var runLevel = gameTicker != null ? Loc.GetString($"game-run-level-{gameTicker.RunLevel}") : "";
|
||||||
|
var runId = gameTicker != null ? gameTicker.RoundId : 0;
|
||||||
|
|
||||||
|
var voteName = customVoteName ?? Loc.GetString("custom-vote-webhook-name");
|
||||||
|
var description = customVoteMessage ?? voteOptions.Title;
|
||||||
|
|
||||||
|
var payload = new WebhookPayload()
|
||||||
|
{
|
||||||
|
Username = voteName,
|
||||||
|
Embeds = new List<WebhookEmbed>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Title = voteOptions.InitiatorText,
|
||||||
|
Color = 13438992, // #CD1010
|
||||||
|
Description = description,
|
||||||
|
Footer = new WebhookEmbedFooter
|
||||||
|
{
|
||||||
|
Text = Loc.GetString(
|
||||||
|
"custom-vote-webhook-footer",
|
||||||
|
("serverName", serverName),
|
||||||
|
("roundId", runId),
|
||||||
|
("runLevel", runLevel)),
|
||||||
|
},
|
||||||
|
|
||||||
|
Fields = fields,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var state = new WebhookState
|
||||||
|
{
|
||||||
|
WebhookUrl = webhookUrl,
|
||||||
|
Payload = payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
CreateWebhookMessage(state, payload);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateWebhookIfConfigured(WebhookState? state, VoteFinishedEventArgs finished)
|
||||||
|
{
|
||||||
|
if (state == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var embed = state.Payload.Embeds![0];
|
||||||
|
embed.Color = 2353993; // #23EB49
|
||||||
|
|
||||||
|
for (var i = 0; i < finished.Votes.Count; i++)
|
||||||
|
{
|
||||||
|
var oldName = embed.Fields[i].Name;
|
||||||
|
var newValue = finished.Votes[i].ToString();
|
||||||
|
embed.Fields[i] = new WebhookEmbedField { Name = oldName, Value = newValue, Inline = true };
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Payload.Embeds[0] = embed;
|
||||||
|
|
||||||
|
UpdateWebhookMessage(state, state.Payload, state.MessageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateCancelledWebhookIfConfigured(WebhookState? state, string? customCancelReason = null)
|
||||||
|
{
|
||||||
|
if (state == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var embed = state.Payload.Embeds![0];
|
||||||
|
embed.Color = 13356304; // #CBCD10
|
||||||
|
if (customCancelReason == null)
|
||||||
|
embed.Description += "\n\n" + Loc.GetString("custom-vote-webhook-cancelled");
|
||||||
|
else
|
||||||
|
embed.Description += "\n\n" + customCancelReason;
|
||||||
|
|
||||||
|
for (var i = 0; i < embed.Fields.Count; i++)
|
||||||
|
{
|
||||||
|
var oldName = embed.Fields[i].Name;
|
||||||
|
embed.Fields[i] = new WebhookEmbedField { Name = oldName, Value = Loc.GetString("custom-vote-webhook-option-cancelled"), Inline = true };
|
||||||
|
}
|
||||||
|
|
||||||
|
state.Payload.Embeds[0] = embed;
|
||||||
|
|
||||||
|
UpdateWebhookMessage(state, state.Payload, state.MessageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends the payload's message.
|
||||||
|
public async void CreateWebhookMessage(WebhookState state, WebhookPayload payload)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (await _discord.GetWebhook(state.WebhookUrl) is not { } identifier)
|
||||||
|
return;
|
||||||
|
|
||||||
|
state.Identifier = identifier.ToIdentifier();
|
||||||
|
_sawmill.Debug(JsonSerializer.Serialize(payload));
|
||||||
|
|
||||||
|
var request = await _discord.CreateMessage(identifier.ToIdentifier(), payload);
|
||||||
|
var content = await request.Content.ReadAsStringAsync();
|
||||||
|
state.MessageId = ulong.Parse(JsonNode.Parse(content)?["id"]!.GetValue<string>()!);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_sawmill.Error($"Error while sending vote webhook to Discord: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edits a pre-existing payload message, given an ID
|
||||||
|
public async void UpdateWebhookMessage(WebhookState state, WebhookPayload payload, ulong id)
|
||||||
|
{
|
||||||
|
if (state.MessageId == 0)
|
||||||
|
{
|
||||||
|
_sawmill.Warning("Failed to deliver update to custom vote webhook: message ID was zero. This likely indicates a previous connection error sending the original message.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugTools.Assert(state.Identifier != default);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _discord.EditMessage(state.Identifier, id, payload);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_sawmill.Error($"Error while updating vote webhook on Discord: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class WebhookState
|
||||||
|
{
|
||||||
|
public required string WebhookUrl;
|
||||||
|
public required WebhookPayload Payload;
|
||||||
|
public WebhookIdentifier Identifier;
|
||||||
|
public ulong MessageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPostInjectInit.PostInject() { }
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ using Content.Server.Chat.Managers;
|
|||||||
using Content.Server.Connection;
|
using Content.Server.Connection;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Server.Discord;
|
using Content.Server.Discord;
|
||||||
|
using Content.Server.Discord.WebhookMessages;
|
||||||
using Content.Server.EUI;
|
using Content.Server.EUI;
|
||||||
using Content.Server.GhostKick;
|
using Content.Server.GhostKick;
|
||||||
using Content.Server.Info;
|
using Content.Server.Info;
|
||||||
@@ -64,6 +65,7 @@ namespace Content.Server.IoC
|
|||||||
IoCManager.Register<ServerInfoManager>();
|
IoCManager.Register<ServerInfoManager>();
|
||||||
IoCManager.Register<PoissonDiskSampler>();
|
IoCManager.Register<PoissonDiskSampler>();
|
||||||
IoCManager.Register<DiscordWebhook>();
|
IoCManager.Register<DiscordWebhook>();
|
||||||
|
IoCManager.Register<VoteWebhooks>();
|
||||||
IoCManager.Register<ServerDbEntryManager>();
|
IoCManager.Register<ServerDbEntryManager>();
|
||||||
IoCManager.Register<ISharedPlaytimeManager, PlayTimeTrackingManager>();
|
IoCManager.Register<ISharedPlaytimeManager, PlayTimeTrackingManager>();
|
||||||
IoCManager.Register<ServerApi>();
|
IoCManager.Register<ServerApi>();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Linq;
|
|||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
using Content.Server.Administration.Managers;
|
using Content.Server.Administration.Managers;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
|
using Content.Server.Discord.WebhookMessages;
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
using Content.Server.GameTicking.Presets;
|
using Content.Server.GameTicking.Presets;
|
||||||
using Content.Server.Maps;
|
using Content.Server.Maps;
|
||||||
@@ -27,6 +28,7 @@ namespace Content.Server.Voting.Managers
|
|||||||
[Dependency] private readonly ILogManager _logManager = default!;
|
[Dependency] private readonly ILogManager _logManager = default!;
|
||||||
[Dependency] private readonly IBanManager _bans = default!;
|
[Dependency] private readonly IBanManager _bans = default!;
|
||||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||||
|
[Dependency] private readonly VoteWebhooks _voteWebhooks = default!;
|
||||||
|
|
||||||
private VotingSystem? _votingSystem;
|
private VotingSystem? _votingSystem;
|
||||||
private RoleSystem? _roleSystem;
|
private RoleSystem? _roleSystem;
|
||||||
@@ -449,10 +451,13 @@ namespace Content.Server.Voting.Managers
|
|||||||
var vote = CreateVote(options);
|
var vote = CreateVote(options);
|
||||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} ({targetEntityName}) due to {reason} started, initiated by {initiator}.");
|
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} ({targetEntityName}) due to {reason} started, initiated by {initiator}.");
|
||||||
|
|
||||||
|
// Create Discord webhook
|
||||||
|
var webhookState = _voteWebhooks.CreateWebhookIfConfigured(options, _cfg.GetCVar(CCVars.DiscordVotekickWebhook), Loc.GetString("votekick-webhook-name"), options.Title + "\n" + Loc.GetString("votekick-webhook-description", ("initiator", initiatorName), ("target", targetSession)));
|
||||||
|
|
||||||
// Time out the vote now that we know it will happen
|
// Time out the vote now that we know it will happen
|
||||||
TimeoutStandardVote(StandardVoteType.Votekick);
|
TimeoutStandardVote(StandardVoteType.Votekick);
|
||||||
|
|
||||||
vote.OnFinished += (_, _) =>
|
vote.OnFinished += (_, eventArgs) =>
|
||||||
{
|
{
|
||||||
|
|
||||||
var votesYes = vote.VotesPerOption["yes"];
|
var votesYes = vote.VotesPerOption["yes"];
|
||||||
@@ -487,6 +492,7 @@ namespace Content.Server.Voting.Managers
|
|||||||
{
|
{
|
||||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} attempted to pass, but an admin was online. Yes: {votesYes} / No: {votesNo}. Yes: {yesVotersString} / No: {noVotersString}");
|
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} attempted to pass, but an admin was online. Yes: {votesYes} / No: {votesNo}. Yes: {yesVotersString} / No: {noVotersString}");
|
||||||
AnnounceCancelledVotekickForVoters(targetEntityName);
|
AnnounceCancelledVotekickForVoters(targetEntityName);
|
||||||
|
_voteWebhooks.UpdateCancelledWebhookIfConfigured(webhookState, Loc.GetString("votekick-webhook-cancelled-admin-online"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Check if the target is an antag and the vote reason is raiding (this is to prevent false positives)
|
// Check if the target is an antag and the vote reason is raiding (this is to prevent false positives)
|
||||||
@@ -494,6 +500,7 @@ namespace Content.Server.Voting.Managers
|
|||||||
{
|
{
|
||||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} due to {reason} finished, created by {initiator}, but was cancelled due to the target being an antagonist.");
|
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} due to {reason} finished, created by {initiator}, but was cancelled due to the target being an antagonist.");
|
||||||
AnnounceCancelledVotekickForVoters(targetEntityName);
|
AnnounceCancelledVotekickForVoters(targetEntityName);
|
||||||
|
_voteWebhooks.UpdateCancelledWebhookIfConfigured(webhookState, Loc.GetString("votekick-webhook-cancelled-antag-target"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Check if the target is an admin/de-admined admin
|
// Check if the target is an admin/de-admined admin
|
||||||
@@ -501,6 +508,7 @@ namespace Content.Server.Voting.Managers
|
|||||||
{
|
{
|
||||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} due to {reason} finished, created by {initiator}, but was cancelled due to the target being a de-admined admin.");
|
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} due to {reason} finished, created by {initiator}, but was cancelled due to the target being a de-admined admin.");
|
||||||
AnnounceCancelledVotekickForVoters(targetEntityName);
|
AnnounceCancelledVotekickForVoters(targetEntityName);
|
||||||
|
_voteWebhooks.UpdateCancelledWebhookIfConfigured(webhookState, Loc.GetString("votekick-webhook-cancelled-admin-target"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -515,6 +523,9 @@ namespace Content.Server.Voting.Managers
|
|||||||
severity = NoteSeverity.High;
|
severity = NoteSeverity.High;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Discord webhook, success
|
||||||
|
_voteWebhooks.UpdateWebhookIfConfigured(webhookState, eventArgs);
|
||||||
|
|
||||||
uint minutes = (uint)_cfg.GetCVar(CCVars.VotekickBanDuration);
|
uint minutes = (uint)_cfg.GetCVar(CCVars.VotekickBanDuration);
|
||||||
|
|
||||||
_bans.CreateServerBan(targetUid, target, null, null, targetHWid, minutes, severity, reason);
|
_bans.CreateServerBan(targetUid, target, null, null, targetHWid, minutes, severity, reason);
|
||||||
@@ -522,6 +533,10 @@ namespace Content.Server.Voting.Managers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// Discord webhook, failure
|
||||||
|
_voteWebhooks.UpdateWebhookIfConfigured(webhookState, eventArgs);
|
||||||
|
|
||||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick failed: Yes: {votesYes} / No: {votesNo}. Yes: {yesVotersString} / No: {noVotersString}");
|
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick failed: Yes: {votesYes} / No: {votesNo}. Yes: {yesVotersString} / No: {noVotersString}");
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("ui-vote-votekick-failure", ("target", targetEntityName), ("reason", reason)));
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("ui-vote-votekick-failure", ("target", targetEntityName), ("reason", reason)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.Discord;
|
using Content.Server.Discord.WebhookMessages;
|
||||||
using Content.Server.GameTicking;
|
|
||||||
using Content.Server.Voting.Managers;
|
using Content.Server.Voting.Managers;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
@@ -76,11 +73,9 @@ namespace Content.Server.Voting
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IVoteManager _voteManager = default!;
|
[Dependency] private readonly IVoteManager _voteManager = default!;
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
|
||||||
[Dependency] private readonly DiscordWebhook _discord = default!;
|
|
||||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
|
||||||
[Dependency] private readonly IBaseServer _baseServer = default!;
|
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||||
|
[Dependency] private readonly VoteWebhooks _voteWebhooks = default!;
|
||||||
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
|
|
||||||
private ISawmill _sawmill = default!;
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
@@ -120,7 +115,7 @@ namespace Content.Server.Voting
|
|||||||
|
|
||||||
var vote = _voteManager.CreateVote(options);
|
var vote = _voteManager.CreateVote(options);
|
||||||
|
|
||||||
var webhookState = CreateWebhookIfConfigured(options);
|
var webhookState = _voteWebhooks.CreateWebhookIfConfigured(options, _cfg.GetCVar(CCVars.DiscordVoteWebhook));
|
||||||
|
|
||||||
vote.OnFinished += (_, eventArgs) =>
|
vote.OnFinished += (_, eventArgs) =>
|
||||||
{
|
{
|
||||||
@@ -136,12 +131,12 @@ namespace Content.Server.Voting
|
|||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("cmd-customvote-on-finished-win", ("winner", args[(int) eventArgs.Winner])));
|
_chatManager.DispatchServerAnnouncement(Loc.GetString("cmd-customvote-on-finished-win", ("winner", args[(int) eventArgs.Winner])));
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateWebhookIfConfigured(webhookState, eventArgs);
|
_voteWebhooks.UpdateWebhookIfConfigured(webhookState, eventArgs);
|
||||||
};
|
};
|
||||||
|
|
||||||
vote.OnCancelled += _ =>
|
vote.OnCancelled += _ =>
|
||||||
{
|
{
|
||||||
UpdateCancelledWebhookIfConfigured(webhookState);
|
_voteWebhooks.UpdateCancelledWebhookIfConfigured(webhookState);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,159 +151,6 @@ namespace Content.Server.Voting
|
|||||||
var n = args.Length - 1;
|
var n = args.Length - 1;
|
||||||
return CompletionResult.FromHint(Loc.GetString("cmd-customvote-arg-option-n", ("n", n)));
|
return CompletionResult.FromHint(Loc.GetString("cmd-customvote-arg-option-n", ("n", n)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private WebhookState? CreateWebhookIfConfigured(VoteOptions voteOptions)
|
|
||||||
{
|
|
||||||
// All this webhook code is complete garbage.
|
|
||||||
// I tried to clean it up somewhat, at least to fix the glaring bugs in it.
|
|
||||||
// Jesus christ man what is with our code review process.
|
|
||||||
|
|
||||||
var webhookUrl = _cfg.GetCVar(CCVars.DiscordVoteWebhook);
|
|
||||||
if (string.IsNullOrEmpty(webhookUrl))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Set up the webhook payload
|
|
||||||
var serverName = _baseServer.ServerName;
|
|
||||||
|
|
||||||
var fields = new List<WebhookEmbedField>();
|
|
||||||
|
|
||||||
foreach (var voteOption in voteOptions.Options)
|
|
||||||
{
|
|
||||||
var newVote = new WebhookEmbedField
|
|
||||||
{
|
|
||||||
Name = voteOption.text,
|
|
||||||
Value = Loc.GetString("custom-vote-webhook-option-pending")
|
|
||||||
};
|
|
||||||
fields.Add(newVote);
|
|
||||||
}
|
|
||||||
|
|
||||||
var runLevel = Loc.GetString($"game-run-level-{_gameTicker.RunLevel}");
|
|
||||||
|
|
||||||
var payload = new WebhookPayload()
|
|
||||||
{
|
|
||||||
Username = Loc.GetString("custom-vote-webhook-name"),
|
|
||||||
Embeds = new List<WebhookEmbed>
|
|
||||||
{
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Title = voteOptions.InitiatorText,
|
|
||||||
Color = 13438992, // #CD1010
|
|
||||||
Description = voteOptions.Title,
|
|
||||||
Footer = new WebhookEmbedFooter
|
|
||||||
{
|
|
||||||
Text = Loc.GetString(
|
|
||||||
"custom-vote-webhook-footer",
|
|
||||||
("serverName", serverName),
|
|
||||||
("roundId", _gameTicker.RoundId),
|
|
||||||
("runLevel", runLevel)),
|
|
||||||
},
|
|
||||||
|
|
||||||
Fields = fields,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var state = new WebhookState
|
|
||||||
{
|
|
||||||
WebhookUrl = webhookUrl,
|
|
||||||
Payload = payload,
|
|
||||||
};
|
|
||||||
|
|
||||||
CreateWebhookMessage(state, payload);
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateWebhookIfConfigured(WebhookState? state, VoteFinishedEventArgs finished)
|
|
||||||
{
|
|
||||||
if (state == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var embed = state.Payload.Embeds![0];
|
|
||||||
embed.Color = 2353993; // #23EB49
|
|
||||||
|
|
||||||
for (var i = 0; i < finished.Votes.Count; i++)
|
|
||||||
{
|
|
||||||
var oldName = embed.Fields[i].Name;
|
|
||||||
var newValue = finished.Votes[i].ToString();
|
|
||||||
embed.Fields[i] = new WebhookEmbedField { Name = oldName, Value = newValue, Inline = true};
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Payload.Embeds[0] = embed;
|
|
||||||
|
|
||||||
UpdateWebhookMessage(state, state.Payload, state.MessageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateCancelledWebhookIfConfigured(WebhookState? state)
|
|
||||||
{
|
|
||||||
if (state == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var embed = state.Payload.Embeds![0];
|
|
||||||
embed.Color = 13356304; // #CBCD10
|
|
||||||
embed.Description += "\n\n" + Loc.GetString("custom-vote-webhook-cancelled");
|
|
||||||
|
|
||||||
for (var i = 0; i < embed.Fields.Count; i++)
|
|
||||||
{
|
|
||||||
var oldName = embed.Fields[i].Name;
|
|
||||||
embed.Fields[i] = new WebhookEmbedField { Name = oldName, Value = Loc.GetString("custom-vote-webhook-option-cancelled"), Inline = true};
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Payload.Embeds[0] = embed;
|
|
||||||
|
|
||||||
UpdateWebhookMessage(state, state.Payload, state.MessageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sends the payload's message.
|
|
||||||
private async void CreateWebhookMessage(WebhookState state, WebhookPayload payload)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (await _discord.GetWebhook(state.WebhookUrl) is not { } identifier)
|
|
||||||
return;
|
|
||||||
|
|
||||||
state.Identifier = identifier.ToIdentifier();
|
|
||||||
|
|
||||||
_sawmill.Debug(JsonSerializer.Serialize(payload));
|
|
||||||
|
|
||||||
var request = await _discord.CreateMessage(identifier.ToIdentifier(), payload);
|
|
||||||
var content = await request.Content.ReadAsStringAsync();
|
|
||||||
state.MessageId = ulong.Parse(JsonNode.Parse(content)?["id"]!.GetValue<string>()!);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_sawmill.Error($"Error while sending vote webhook to Discord: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edits a pre-existing payload message, given an ID
|
|
||||||
private async void UpdateWebhookMessage(WebhookState state, WebhookPayload payload, ulong id)
|
|
||||||
{
|
|
||||||
if (state.MessageId == 0)
|
|
||||||
{
|
|
||||||
_sawmill.Warning("Failed to deliver update to custom vote webhook: message ID was zero. This likely indicates a previous connection error sending the original message.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DebugTools.Assert(state.Identifier != default);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _discord.EditMessage(state.Identifier, id, payload);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_sawmill.Error($"Error while updating vote webhook on Discord: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class WebhookState
|
|
||||||
{
|
|
||||||
public required string WebhookUrl;
|
|
||||||
public required WebhookPayload Payload;
|
|
||||||
public WebhookIdentifier Identifier;
|
|
||||||
public ulong MessageId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[AnyCommand]
|
[AnyCommand]
|
||||||
|
|||||||
@@ -465,6 +465,12 @@ namespace Content.Shared.CCVar
|
|||||||
public static readonly CVarDef<string> DiscordVoteWebhook =
|
public static readonly CVarDef<string> DiscordVoteWebhook =
|
||||||
CVarDef.Create("discord.vote_webhook", string.Empty, CVar.SERVERONLY);
|
CVarDef.Create("discord.vote_webhook", string.Empty, CVar.SERVERONLY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// URL of the Discord webhook which will relay all votekick votes. If left empty, disables the webhook.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly CVarDef<string> DiscordVotekickWebhook =
|
||||||
|
CVarDef.Create("discord.votekick_webhook", string.Empty, CVar.SERVERONLY);
|
||||||
|
|
||||||
/// URL of the Discord webhook which will relay round restart messages.
|
/// URL of the Discord webhook which will relay round restart messages.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly CVarDef<string> DiscordRoundUpdateWebhook =
|
public static readonly CVarDef<string> DiscordRoundUpdateWebhook =
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
custom-vote-webhook-name = Custom Vote Held
|
|
||||||
custom-vote-webhook-footer = server: { $serverName }, round: { $roundId } { $runLevel }
|
|
||||||
custom-vote-webhook-cancelled = **Vote cancelled**
|
|
||||||
custom-vote-webhook-option-pending = TBD
|
|
||||||
custom-vote-webhook-option-cancelled = N/A
|
|
||||||
11
Resources/Locale/en-US/discord/vote-notifications.ftl
Normal file
11
Resources/Locale/en-US/discord/vote-notifications.ftl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
custom-vote-webhook-name = Custom Vote Held
|
||||||
|
custom-vote-webhook-footer = server: { $serverName }, round: { $roundId } { $runLevel }
|
||||||
|
custom-vote-webhook-cancelled = **Vote cancelled**
|
||||||
|
custom-vote-webhook-option-pending = TBD
|
||||||
|
custom-vote-webhook-option-cancelled = N/A
|
||||||
|
|
||||||
|
votekick-webhook-name = Votekick Held
|
||||||
|
votekick-webhook-description = Initiator: { $initiator }; Target: { $target }
|
||||||
|
votekick-webhook-cancelled-admin-online = **Vote cancelled due to admins online**
|
||||||
|
votekick-webhook-cancelled-admin-target = **Vote cancelled due to target being admin**
|
||||||
|
votekick-webhook-cancelled-antag-target = **Vote cancelled due to target being antag**
|
||||||
Reference in New Issue
Block a user