2021-02-16 15:07:17 +01:00
using System.Linq ;
2023-12-14 20:03:32 -08:00
using System.Text.Json ;
using System.Text.Json.Nodes ;
2021-02-16 15:07:17 +01:00
using Content.Server.Administration ;
2023-02-16 18:29:44 -06:00
using Content.Server.Administration.Logs ;
2021-06-09 22:19:39 +02:00
using Content.Server.Chat.Managers ;
2023-12-14 20:03:32 -08:00
using Content.Server.Discord ;
using Content.Server.GameTicking ;
2021-06-09 22:19:39 +02:00
using Content.Server.Voting.Managers ;
2021-02-16 15:07:17 +01:00
using Content.Shared.Administration ;
2023-12-14 20:03:32 -08:00
using Content.Shared.CCVar ;
2023-02-16 18:29:44 -06:00
using Content.Shared.Database ;
2021-07-21 19:03:10 +02:00
using Content.Shared.Voting ;
2024-09-06 12:00:57 +02:00
using Robust.Server ;
2023-12-14 20:03:32 -08:00
using Robust.Shared.Configuration ;
2021-02-16 15:07:17 +01:00
using Robust.Shared.Console ;
2024-09-06 12:00:57 +02:00
using Robust.Shared.Utility ;
2021-02-16 15:07:17 +01:00
namespace Content.Server.Voting
{
[AnyCommand]
public sealed class CreateVoteCommand : IConsoleCommand
{
2023-02-16 18:29:44 -06:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2021-02-16 15:07:17 +01:00
public string Command = > "createvote" ;
2022-06-13 01:52:23 +02:00
public string Description = > Loc . GetString ( "cmd-createvote-desc" ) ;
public string Help = > Loc . GetString ( "cmd-createvote-help" ) ;
2021-02-16 15:07:17 +01:00
public void Execute ( IConsoleShell shell , string argStr , string [ ] args )
{
2024-09-26 18:32:13 +02:00
if ( args . Length ! = 1 & & args [ 0 ] ! = StandardVoteType . Votekick . ToString ( ) )
2021-02-16 15:07:17 +01:00
{
2021-06-21 02:13:54 +02:00
shell . WriteError ( Loc . GetString ( "shell-need-exactly-one-argument" ) ) ;
2021-02-16 15:07:17 +01:00
return ;
}
2024-09-26 18:32:13 +02:00
if ( args . Length ! = 3 & & args [ 0 ] = = StandardVoteType . Votekick . ToString ( ) )
{
shell . WriteError ( Loc . GetString ( "shell-wrong-arguments-number-need-specific" , ( "properAmount" , 3 ) , ( "currentAmount" , args . Length ) ) ) ;
return ;
}
2021-02-16 15:07:17 +01:00
2021-07-21 19:03:10 +02:00
if ( ! Enum . TryParse < StandardVoteType > ( args [ 0 ] , ignoreCase : true , out var type ) )
{
2022-06-13 01:52:23 +02:00
shell . WriteError ( Loc . GetString ( "cmd-createvote-invalid-vote-type" ) ) ;
2021-07-21 19:03:10 +02:00
return ;
}
2021-02-16 15:07:17 +01:00
var mgr = IoCManager . Resolve < IVoteManager > ( ) ;
2023-10-28 09:59:53 +11:00
if ( shell . Player ! = null & & ! mgr . CanCallVote ( shell . Player , type ) )
2021-02-16 15:07:17 +01:00
{
2023-02-16 18:29:44 -06:00
_adminLogger . Add ( LogType . Vote , LogImpact . Medium , $"{shell.Player} failed to start {type.ToString()} vote" ) ;
2022-06-13 01:52:23 +02:00
shell . WriteError ( Loc . GetString ( "cmd-createvote-cannot-call-vote-now" ) ) ;
2021-02-16 15:07:17 +01:00
return ;
}
2024-09-26 18:32:13 +02:00
mgr . CreateStandardVote ( shell . Player , type , args . Skip ( 1 ) . ToArray ( ) ) ;
2021-02-16 15:07:17 +01:00
}
2022-06-13 01:52:23 +02:00
public CompletionResult GetCompletion ( IConsoleShell shell , string [ ] args )
{
if ( args . Length = = 1 )
{
var options = Enum . GetNames < StandardVoteType > ( ) ;
return CompletionResult . FromHintOptions ( options , Loc . GetString ( "cmd-createvote-arg-vote-type" ) ) ;
}
return CompletionResult . Empty ;
}
2021-02-16 15:07:17 +01:00
}
2024-06-01 04:14:43 -04:00
[AdminCommand(AdminFlags.Moderator)]
2024-09-06 12:00:57 +02:00
public sealed class CreateCustomCommand : LocalizedEntityCommands
2021-02-16 15:07:17 +01:00
{
2024-09-06 12:00:57 +02:00
[Dependency] private readonly IVoteManager _voteManager = default ! ;
2023-02-16 18:29:44 -06:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2023-12-14 20:03:32 -08:00
[Dependency] private readonly IConfigurationManager _cfg = default ! ;
[Dependency] private readonly DiscordWebhook _discord = default ! ;
2024-09-06 12:00:57 +02:00
[Dependency] private readonly GameTicker _gameTicker = default ! ;
[Dependency] private readonly IBaseServer _baseServer = default ! ;
[Dependency] private readonly IChatManager _chatManager = default ! ;
2023-12-14 20:03:32 -08:00
private ISawmill _sawmill = default ! ;
2023-02-16 18:29:44 -06:00
2022-06-13 01:52:23 +02:00
private const int MaxArgCount = 10 ;
2024-09-06 12:00:57 +02:00
public override string Command = > "customvote" ;
2021-02-16 15:07:17 +01:00
2024-09-06 12:00:57 +02:00
public override void Execute ( IConsoleShell shell , string argStr , string [ ] args )
2021-02-16 15:07:17 +01:00
{
2023-12-14 20:03:32 -08:00
_sawmill = Logger . GetSawmill ( "vote" ) ;
2022-06-13 01:52:23 +02:00
if ( args . Length < 3 | | args . Length > MaxArgCount )
2021-02-16 15:07:17 +01:00
{
2021-06-21 02:13:54 +02:00
shell . WriteError ( Loc . GetString ( "shell-need-between-arguments" , ( "lower" , 3 ) , ( "upper" , 10 ) ) ) ;
2021-02-16 15:07:17 +01:00
return ;
}
var title = args [ 0 ] ;
var options = new VoteOptions
{
Title = title ,
Duration = TimeSpan . FromSeconds ( 30 ) ,
} ;
for ( var i = 1 ; i < args . Length ; i + + )
{
options . Options . Add ( ( args [ i ] , i ) ) ;
}
2023-10-28 09:59:53 +11:00
options . SetInitiatorOrServer ( shell . Player ) ;
2021-02-16 15:07:17 +01:00
2023-02-16 18:29:44 -06:00
if ( shell . Player ! = null )
_adminLogger . Add ( LogType . Vote , LogImpact . Medium , $"{shell.Player} initiated a custom vote: {options.Title} - {string.Join(" ; ", options.Options.Select(x => x.text))}" ) ;
else
_adminLogger . Add ( LogType . Vote , LogImpact . Medium , $"Initiated a custom vote: {options.Title} - {string.Join(" ; ", options.Options.Select(x => x.text))}" ) ;
2024-09-06 12:00:57 +02:00
var vote = _voteManager . CreateVote ( options ) ;
var webhookState = CreateWebhookIfConfigured ( options ) ;
2021-02-16 15:07:17 +01:00
vote . OnFinished + = ( _ , eventArgs ) = >
{
if ( eventArgs . Winner = = null )
{
var ties = string . Join ( ", " , eventArgs . Winners . Select ( c = > args [ ( int ) c ] ) ) ;
2023-02-16 18:29:44 -06:00
_adminLogger . Add ( LogType . Vote , LogImpact . Medium , $"Custom vote {options.Title} finished as tie: {ties}" ) ;
2024-09-06 12:00:57 +02:00
_chatManager . DispatchServerAnnouncement ( Loc . GetString ( "cmd-customvote-on-finished-tie" , ( "ties" , ties ) ) ) ;
2021-02-16 15:07:17 +01:00
}
else
{
2023-02-16 18:29:44 -06:00
_adminLogger . Add ( LogType . Vote , LogImpact . Medium , $"Custom vote {options.Title} finished: {args[(int) eventArgs.Winner]}" ) ;
2024-09-06 12:00:57 +02:00
_chatManager . DispatchServerAnnouncement ( Loc . GetString ( "cmd-customvote-on-finished-win" , ( "winner" , args [ ( int ) eventArgs . Winner ] ) ) ) ;
2021-02-16 15:07:17 +01:00
}
2023-12-14 20:03:32 -08:00
2024-09-06 12:00:57 +02:00
UpdateWebhookIfConfigured ( webhookState , eventArgs ) ;
} ;
2023-12-14 20:03:32 -08:00
2024-09-06 12:00:57 +02:00
vote . OnCancelled + = _ = >
{
UpdateCancelledWebhookIfConfigured ( webhookState ) ;
2021-02-16 15:07:17 +01:00
} ;
}
2022-06-13 01:52:23 +02:00
2024-09-06 12:00:57 +02:00
public override CompletionResult GetCompletion ( IConsoleShell shell , string [ ] args )
2022-06-13 01:52:23 +02:00
{
if ( args . Length = = 1 )
return CompletionResult . FromHint ( Loc . GetString ( "cmd-customvote-arg-title" ) ) ;
if ( args . Length > MaxArgCount )
return CompletionResult . Empty ;
var n = args . Length - 1 ;
return CompletionResult . FromHint ( Loc . GetString ( "cmd-customvote-arg-option-n" , ( "n" , n ) ) ) ;
}
2023-12-14 20:03:32 -08:00
2024-09-06 12:00:57 +02:00
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 )
2023-12-14 20:03:32 -08:00
{
2024-09-06 12:00:57 +02:00
if ( state = = null )
2023-12-14 20:03:32 -08:00
return ;
2024-09-06 12:00:57 +02:00
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 )
2023-12-14 20:03:32 -08:00
return ;
2024-09-06 12:00:57 +02:00
var embed = state . Payload . Embeds ! [ 0 ] ;
embed . Color = 13356304 ; // #CBCD10
embed . Description + = "\n\n" + Loc . GetString ( "custom-vote-webhook-cancelled" ) ;
2023-12-14 20:03:32 -08:00
2024-09-06 12:00:57 +02:00
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 ;
2023-12-14 20:03:32 -08:00
2024-09-06 12:00:57 +02:00
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}" ) ;
}
2023-12-14 20:03:32 -08:00
}
// Edits a pre-existing payload message, given an ID
2024-09-06 12:00:57 +02:00
private async void UpdateWebhookMessage ( WebhookState state , WebhookPayload payload , ulong id )
2023-12-14 20:03:32 -08:00
{
2024-09-06 12:00:57 +02:00
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." ) ;
2023-12-14 20:03:32 -08:00
return ;
2024-09-06 12:00:57 +02:00
}
2023-12-14 20:03:32 -08:00
2024-09-06 12:00:57 +02:00
DebugTools . Assert ( state . Identifier ! = default ) ;
2023-12-14 20:03:32 -08:00
2024-09-06 12:00:57 +02:00
try
{
await _discord . EditMessage ( state . Identifier , id , payload ) ;
}
catch ( Exception e )
{
_sawmill . Error ( $"Error while updating vote webhook on Discord: {e}" ) ;
}
}
2023-12-14 20:03:32 -08:00
2024-09-06 12:00:57 +02:00
private sealed class WebhookState
{
public required string WebhookUrl ;
public required WebhookPayload Payload ;
public WebhookIdentifier Identifier ;
public ulong MessageId ;
2023-12-14 20:03:32 -08:00
}
2021-02-16 15:07:17 +01:00
}
[AnyCommand]
public sealed class VoteCommand : IConsoleCommand
{
public string Command = > "vote" ;
2022-06-13 01:52:23 +02:00
public string Description = > Loc . GetString ( "cmd-vote-desc" ) ;
public string Help = > Loc . GetString ( "cmd-vote-help" ) ;
2021-02-16 15:07:17 +01:00
public void Execute ( IConsoleShell shell , string argStr , string [ ] args )
{
if ( shell . Player = = null )
{
2022-06-13 01:52:23 +02:00
shell . WriteError ( Loc . GetString ( "cmd-vote-on-execute-error-must-be-player" ) ) ;
2021-02-16 15:07:17 +01:00
return ;
}
if ( args . Length ! = 2 )
{
2021-06-21 02:13:54 +02:00
shell . WriteError ( Loc . GetString ( "shell-wrong-arguments-number-need-specific" , ( "properAmount" , 2 ) , ( "currentAmount" , args . Length ) ) ) ;
2021-02-16 15:07:17 +01:00
return ;
}
if ( ! int . TryParse ( args [ 0 ] , out var voteId ) )
{
2022-06-13 01:52:23 +02:00
shell . WriteError ( Loc . GetString ( "cmd-vote-on-execute-error-invalid-vote-id" ) ) ;
2021-02-16 15:07:17 +01:00
return ;
}
if ( ! int . TryParse ( args [ 1 ] , out var voteOption ) )
{
2022-06-13 01:52:23 +02:00
shell . WriteError ( Loc . GetString ( "cmd-vote-on-execute-error-invalid-vote-options" ) ) ;
2021-02-16 15:07:17 +01:00
return ;
}
var mgr = IoCManager . Resolve < IVoteManager > ( ) ;
if ( ! mgr . TryGetVote ( voteId , out var vote ) )
{
2022-06-13 01:52:23 +02:00
shell . WriteError ( Loc . GetString ( "cmd-vote-on-execute-error-invalid-vote" ) ) ;
2021-02-16 15:07:17 +01:00
return ;
}
int? optionN ;
if ( voteOption = = - 1 )
{
optionN = null ;
}
else if ( vote . IsValidOption ( voteOption ) )
{
optionN = voteOption ;
}
else
{
2022-06-13 01:52:23 +02:00
shell . WriteError ( Loc . GetString ( "cmd-vote-on-execute-error-invalid-option" ) ) ;
2021-02-16 15:07:17 +01:00
return ;
}
2023-10-28 09:59:53 +11:00
vote . CastVote ( shell . Player ! , optionN ) ;
2021-02-16 15:07:17 +01:00
}
}
[AnyCommand]
public sealed class ListVotesCommand : IConsoleCommand
{
public string Command = > "listvotes" ;
2022-06-13 01:52:23 +02:00
public string Description = > Loc . GetString ( "cmd-listvotes-desc" ) ;
public string Help = > Loc . GetString ( "cmd-listvotes-help" ) ;
2021-02-16 15:07:17 +01:00
public void Execute ( IConsoleShell shell , string argStr , string [ ] args )
{
var mgr = IoCManager . Resolve < IVoteManager > ( ) ;
foreach ( var vote in mgr . ActiveVotes )
{
shell . WriteLine ( $"[{vote.Id}] {vote.InitiatorText}: {vote.Title}" ) ;
}
}
}
2024-06-01 04:14:43 -04:00
[AdminCommand(AdminFlags.Moderator)]
2021-02-16 15:07:17 +01:00
public sealed class CancelVoteCommand : IConsoleCommand
{
2023-02-16 18:29:44 -06:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2021-02-16 15:07:17 +01:00
public string Command = > "cancelvote" ;
2022-06-13 01:52:23 +02:00
public string Description = > Loc . GetString ( "cmd-cancelvote-desc" ) ;
public string Help = > Loc . GetString ( "cmd-cancelvote-help" ) ;
2021-02-16 15:07:17 +01:00
public void Execute ( IConsoleShell shell , string argStr , string [ ] args )
{
var mgr = IoCManager . Resolve < IVoteManager > ( ) ;
if ( args . Length < 1 )
{
2022-06-13 01:52:23 +02:00
shell . WriteError ( Loc . GetString ( "cmd-cancelvote-error-missing-vote-id" ) ) ;
2021-02-16 15:07:17 +01:00
return ;
}
if ( ! int . TryParse ( args [ 0 ] , out var id ) | | ! mgr . TryGetVote ( id , out var vote ) )
{
2022-06-13 01:52:23 +02:00
shell . WriteError ( Loc . GetString ( "cmd-cancelvote-error-invalid-vote-id" ) ) ;
2021-02-16 15:07:17 +01:00
return ;
}
2023-02-16 18:29:44 -06:00
if ( shell . Player ! = null )
_adminLogger . Add ( LogType . Vote , LogImpact . Medium , $"{shell.Player} canceled vote: {vote.Title}" ) ;
else
_adminLogger . Add ( LogType . Vote , LogImpact . Medium , $"Canceled vote: {vote.Title}" ) ;
2021-02-16 15:07:17 +01:00
vote . Cancel ( ) ;
}
2022-06-13 01:52:23 +02:00
public CompletionResult GetCompletion ( IConsoleShell shell , string [ ] args )
{
var mgr = IoCManager . Resolve < IVoteManager > ( ) ;
if ( args . Length = = 1 )
{
var options = mgr . ActiveVotes
. OrderBy ( v = > v . Id )
. Select ( v = > new CompletionOption ( v . Id . ToString ( ) , v . Title ) ) ;
return CompletionResult . FromHintOptions ( options , Loc . GetString ( "cmd-cancelvote-arg-id" ) ) ;
}
return CompletionResult . Empty ;
}
2021-02-16 15:07:17 +01:00
}
}