2023-12-24 22:57:00 -08:00
using System.Linq ;
2021-12-22 13:34:09 +01:00
using System.Net.Http ;
using System.Text ;
using System.Text.Json ;
2021-12-29 21:12:07 +01:00
using System.Text.Json.Nodes ;
2022-09-14 19:19:32 +02:00
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
2021-10-06 16:25:27 +01:00
using Content.Server.Administration.Managers ;
2024-02-02 02:03:49 +13:00
using Content.Server.Afk ;
2023-08-24 14:50:07 -07:00
using Content.Server.Discord ;
2022-05-27 02:41:18 -05:00
using Content.Server.GameTicking ;
2021-10-06 16:25:27 +01:00
using Content.Shared.Administration ;
2021-12-22 13:34:09 +01:00
using Content.Shared.CCVar ;
2023-08-30 21:46:11 -07:00
using Content.Shared.Mind ;
2021-10-06 16:25:27 +01:00
using JetBrains.Annotations ;
using Robust.Server.Player ;
2021-12-22 13:34:09 +01:00
using Robust.Shared ;
using Robust.Shared.Configuration ;
2023-04-11 17:19:09 -07:00
using Robust.Shared.Enums ;
2021-12-29 21:12:07 +01:00
using Robust.Shared.Network ;
2023-10-28 09:59:53 +11:00
using Robust.Shared.Player ;
2023-08-13 16:03:17 -07:00
using Robust.Shared.Timing ;
2021-11-14 20:21:18 +01:00
using Robust.Shared.Utility ;
2021-10-06 16:25:27 +01:00
2022-05-27 02:41:18 -05:00
namespace Content.Server.Administration.Systems
2021-10-06 16:25:27 +01:00
{
[UsedImplicitly]
2022-02-16 00:23:23 -07:00
public sealed class BwoinkSystem : SharedBwoinkSystem
2021-10-06 16:25:27 +01:00
{
[Dependency] private readonly IPlayerManager _playerManager = default ! ;
[Dependency] private readonly IAdminManager _adminManager = default ! ;
2021-12-22 13:34:09 +01:00
[Dependency] private readonly IConfigurationManager _config = default ! ;
2023-08-13 16:03:17 -07:00
[Dependency] private readonly IGameTiming _timing = default ! ;
2021-12-22 13:34:09 +01:00
[Dependency] private readonly IPlayerLocator _playerLocator = default ! ;
2022-04-13 15:32:28 -07:00
[Dependency] private readonly GameTicker _gameTicker = default ! ;
2023-08-30 21:46:11 -07:00
[Dependency] private readonly SharedMindSystem _minds = default ! ;
2024-02-02 02:03:49 +13:00
[Dependency] private readonly IAfkManager _afkManager = default ! ;
2021-12-22 13:34:09 +01:00
2021-12-29 21:12:07 +01:00
private ISawmill _sawmill = default ! ;
2021-12-22 13:34:09 +01:00
private readonly HttpClient _httpClient = new ( ) ;
private string _webhookUrl = string . Empty ;
2022-09-14 19:19:32 +02:00
private WebhookData ? _webhookData ;
2022-09-11 17:43:38 +02:00
private string _footerIconUrl = string . Empty ;
private string _avatarUrl = string . Empty ;
2021-12-22 13:34:09 +01:00
private string _serverName = string . Empty ;
2022-09-14 19:19:32 +02:00
private readonly Dictionary < NetUserId , ( string? id , string username , string description , string? characterName , GameRunLevel lastRunLevel ) > _relayMessages = new ( ) ;
private Dictionary < NetUserId , string > _oldMessageIds = new ( ) ;
2021-12-29 21:12:07 +01:00
private readonly Dictionary < NetUserId , Queue < string > > _messageQueues = new ( ) ;
private readonly HashSet < NetUserId > _processingChannels = new ( ) ;
2023-08-13 16:03:17 -07:00
private readonly Dictionary < NetUserId , ( TimeSpan Timestamp , bool Typing ) > _typingUpdateTimestamps = new ( ) ;
2023-12-20 00:40:37 +01:00
private string _overrideClientName = string . Empty ;
2022-09-11 17:43:38 +02:00
// Max embed description length is 4096, according to https://discord.com/developers/docs/resources/channel#embed-object-embed-limits
// Keep small margin, just to be safe
private const ushort DescriptionMax = 4000 ;
2022-09-14 19:19:32 +02:00
// Maximum length a message can be before it is cut off
// Should be shorter than DescriptionMax
private const ushort MessageLengthCap = 3000 ;
// Text to be used to cut off messages that are too long. Should be shorter than MessageLengthCap
private const string TooLongText = "... **(too long)**" ;
2021-12-29 21:12:07 +01:00
private int _maxAdditionalChars ;
2021-12-22 13:34:09 +01:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2024-02-13 22:48:39 +01:00
Subs . CVar ( _config , CCVars . DiscordAHelpWebhook , OnWebhookChanged , true ) ;
Subs . CVar ( _config , CCVars . DiscordAHelpFooterIcon , OnFooterIconChanged , true ) ;
Subs . CVar ( _config , CCVars . DiscordAHelpAvatar , OnAvatarChanged , true ) ;
Subs . CVar ( _config , CVars . GameHostName , OnServerNameChanged , true ) ;
Subs . CVar ( _config , CCVars . AdminAhelpOverrideClientName , OnOverrideChanged , true ) ;
2021-12-29 21:12:07 +01:00
_sawmill = IoCManager . Resolve < ILogManager > ( ) . GetSawmill ( "AHELP" ) ;
2024-02-22 09:14:57 +11:00
_maxAdditionalChars = GenerateAHelpMessage ( "" , "" , true , _gameTicker . RoundDuration ( ) . ToString ( "hh\\:mm\\:ss" ) , _gameTicker . RunLevel , playedSound : false ) . Length ;
2023-04-11 17:19:09 -07:00
_playerManager . PlayerStatusChanged + = OnPlayerStatusChanged ;
2022-04-13 15:32:28 -07:00
2022-09-14 19:19:32 +02:00
SubscribeLocalEvent < GameRunLevelChangedEvent > ( OnGameRunLevelChanged ) ;
2023-08-13 16:03:17 -07:00
SubscribeNetworkEvent < BwoinkClientTypingUpdated > ( OnClientTypingUpdated ) ;
2022-04-13 15:32:28 -07:00
}
2023-12-20 00:40:37 +01:00
private void OnOverrideChanged ( string obj )
{
_overrideClientName = obj ;
}
2023-04-11 17:19:09 -07:00
private void OnPlayerStatusChanged ( object? sender , SessionStatusEventArgs e )
{
if ( e . NewStatus ! = SessionStatus . InGame )
return ;
RaiseNetworkEvent ( new BwoinkDiscordRelayUpdated ( ! string . IsNullOrWhiteSpace ( _webhookUrl ) ) , e . Session ) ;
}
2022-09-14 19:19:32 +02:00
private void OnGameRunLevelChanged ( GameRunLevelChangedEvent args )
2022-04-13 15:32:28 -07:00
{
2022-09-14 19:19:32 +02:00
// Don't make a new embed if we
// 1. were in the lobby just now, and
// 2. are not entering the lobby or directly into a new round.
if ( args . Old is GameRunLevel . PreRoundLobby | |
args . New is not ( GameRunLevel . PreRoundLobby or GameRunLevel . InRound ) )
{
return ;
}
// Store the Discord message IDs of the previous round
_oldMessageIds = new Dictionary < NetUserId , string > ( ) ;
foreach ( var message in _relayMessages )
{
var id = message . Value . id ;
if ( id = = null )
return ;
_oldMessageIds [ message . Key ] = id ;
}
2022-04-13 15:32:28 -07:00
_relayMessages . Clear ( ) ;
2021-12-22 13:34:09 +01:00
}
2023-08-13 16:03:17 -07:00
private void OnClientTypingUpdated ( BwoinkClientTypingUpdated msg , EntitySessionEventArgs args )
{
if ( _typingUpdateTimestamps . TryGetValue ( args . SenderSession . UserId , out var tuple ) & &
tuple . Typing = = msg . Typing & &
tuple . Timestamp + TimeSpan . FromSeconds ( 1 ) > _timing . RealTime )
{
return ;
}
_typingUpdateTimestamps [ args . SenderSession . UserId ] = ( _timing . RealTime , msg . Typing ) ;
// Non-admins can only ever type on their own ahelp, guard against fake messages
2023-10-28 09:59:53 +11:00
var isAdmin = _adminManager . GetAdminData ( args . SenderSession ) ? . HasFlag ( AdminFlags . Adminhelp ) ? ? false ;
2023-08-13 16:03:17 -07:00
var channel = isAdmin ? msg . Channel : args . SenderSession . UserId ;
var update = new BwoinkPlayerTypingUpdated ( channel , args . SenderSession . Name , msg . Typing ) ;
foreach ( var admin in GetTargetAdmins ( ) )
{
if ( admin . UserId = = args . SenderSession . UserId )
continue ;
RaiseNetworkEvent ( update , admin ) ;
}
}
2021-12-22 13:34:09 +01:00
private void OnServerNameChanged ( string obj )
{
_serverName = obj ;
}
2023-08-24 22:59:43 -07:00
private async void OnWebhookChanged ( string url )
2022-09-14 19:19:32 +02:00
{
_webhookUrl = url ;
2023-03-28 14:27:21 -07:00
RaiseNetworkEvent ( new BwoinkDiscordRelayUpdated ( ! string . IsNullOrWhiteSpace ( url ) ) ) ;
2022-09-14 19:19:32 +02:00
if ( url = = string . Empty )
return ;
// Basic sanity check and capturing webhook ID and token
var match = Regex . Match ( url , @"^https://discord\.com/api/webhooks/(\d+)/((?!.*/).*)$" ) ;
if ( ! match . Success )
{
// TODO: Ideally, CVar validation during setting should be better integrated
2023-06-27 23:56:52 +10:00
Log . Warning ( "Webhook URL does not appear to be valid. Using anyways..." ) ;
2022-09-14 19:19:32 +02:00
return ;
}
if ( match . Groups . Count < = 2 )
{
2023-06-27 23:56:52 +10:00
Log . Error ( "Could not get webhook ID or token." ) ;
2022-09-14 19:19:32 +02:00
return ;
}
var webhookId = match . Groups [ 1 ] . Value ;
var webhookToken = match . Groups [ 2 ] . Value ;
// Fire and forget
2023-08-24 22:59:43 -07:00
await SetWebhookData ( webhookId , webhookToken ) ;
2022-09-14 19:19:32 +02:00
}
private async Task SetWebhookData ( string id , string token )
2021-12-22 13:34:09 +01:00
{
2022-09-14 19:19:32 +02:00
var response = await _httpClient . GetAsync ( $"https://discord.com/api/v10/webhooks/{id}/{token}" ) ;
var content = await response . Content . ReadAsStringAsync ( ) ;
if ( ! response . IsSuccessStatusCode )
{
_sawmill . Log ( LogLevel . Error , $"Discord returned bad status code when trying to get webhook data (perhaps the webhook URL is invalid?): {response.StatusCode}\nResponse: {content}" ) ;
return ;
}
_webhookData = JsonSerializer . Deserialize < WebhookData > ( content ) ;
2021-12-22 13:34:09 +01:00
}
2021-10-06 16:25:27 +01:00
2022-09-11 17:43:38 +02:00
private void OnFooterIconChanged ( string url )
{
_footerIconUrl = url ;
}
private void OnAvatarChanged ( string url )
{
_avatarUrl = url ;
}
2022-09-14 19:19:32 +02:00
private async void ProcessQueue ( NetUserId userId , Queue < string > messages )
2021-12-29 21:12:07 +01:00
{
2022-09-14 19:19:32 +02:00
// Whether an embed already exists for this player
var exists = _relayMessages . TryGetValue ( userId , out var existingEmbed ) ;
// Whether the message will become too long after adding these new messages
var tooLong = exists & & messages . Sum ( msg = > Math . Min ( msg . Length , MessageLengthCap ) + "\n" . Length )
+ existingEmbed . description . Length > DescriptionMax ;
// If there is no existing embed, or it is getting too long, we create a new embed
if ( ! exists | | tooLong )
2021-12-29 21:12:07 +01:00
{
2022-09-14 19:19:32 +02:00
var lookup = await _playerLocator . LookupIdAsync ( userId ) ;
2021-12-29 21:12:07 +01:00
if ( lookup = = null )
{
2022-09-14 19:19:32 +02:00
_sawmill . Log ( LogLevel . Error , $"Unable to find player for NetUserId {userId} when sending discord webhook." ) ;
_relayMessages . Remove ( userId ) ;
2021-12-29 21:12:07 +01:00
return ;
}
2022-09-14 19:19:32 +02:00
var linkToPrevious = string . Empty ;
// If we have all the data required, we can link to the embed of the previous round or embed that was too long
if ( _webhookData is { GuildId : { } guildId , ChannelId : { } channelId } )
{
if ( tooLong & & existingEmbed . id ! = null )
{
linkToPrevious = $"**[Go to previous embed of this round](https://discord.com/channels/{guildId}/{channelId}/{existingEmbed.id})**\n" ;
}
else if ( _oldMessageIds . TryGetValue ( userId , out var id ) & & ! string . IsNullOrEmpty ( id ) )
{
linkToPrevious = $"**[Go to last round's conversation with this player](https://discord.com/channels/{guildId}/{channelId}/{id})**\n" ;
}
}
2023-08-28 16:53:24 -07:00
var characterName = _minds . GetCharacterName ( userId ) ;
2022-09-14 19:19:32 +02:00
existingEmbed = ( null , lookup . Username , linkToPrevious , characterName , _gameTicker . RunLevel ) ;
}
// Previous message was in another RunLevel, so show that in the embed
if ( existingEmbed . lastRunLevel ! = _gameTicker . RunLevel )
{
existingEmbed . description + = _gameTicker . RunLevel switch
{
GameRunLevel . PreRoundLobby = > "\n\n:arrow_forward: _**Pre-round lobby started**_\n" ,
GameRunLevel . InRound = > "\n\n:arrow_forward: _**Round started**_\n" ,
GameRunLevel . PostRound = > "\n\n:stop_button: _**Post-round started**_\n" ,
_ = > throw new ArgumentOutOfRangeException ( nameof ( _gameTicker . RunLevel ) , $"{_gameTicker.RunLevel} was not matched." ) ,
} ;
2022-09-11 17:43:38 +02:00
2022-09-14 19:19:32 +02:00
existingEmbed . lastRunLevel = _gameTicker . RunLevel ;
2021-12-29 21:12:07 +01:00
}
2022-09-14 19:19:32 +02:00
// Add available messages to the embed description
2021-12-29 21:12:07 +01:00
while ( messages . TryDequeue ( out var message ) )
{
2022-09-14 19:19:32 +02:00
// In case someone thinks they're funny
if ( message . Length > MessageLengthCap )
message = message [ . . ( MessageLengthCap - TooLongText . Length ) ] + TooLongText ;
existingEmbed . description + = $"\n{message}" ;
2021-12-29 21:12:07 +01:00
}
2022-09-14 19:19:32 +02:00
var payload = GeneratePayload ( existingEmbed . description , existingEmbed . username , existingEmbed . characterName ) ;
2021-12-29 21:12:07 +01:00
2022-09-14 19:19:32 +02:00
// If there is no existing embed, create a new one
// Otherwise patch (edit) it
if ( existingEmbed . id = = null )
2021-12-29 21:12:07 +01:00
{
var request = await _httpClient . PostAsync ( $"{_webhookUrl}?wait=true" ,
new StringContent ( JsonSerializer . Serialize ( payload ) , Encoding . UTF8 , "application/json" ) ) ;
var content = await request . Content . ReadAsStringAsync ( ) ;
if ( ! request . IsSuccessStatusCode )
{
2022-09-11 17:43:38 +02:00
_sawmill . Log ( LogLevel . Error , $"Discord returned bad status code when posting message (perhaps the message is too long?): {request.StatusCode}\nResponse: {content}" ) ;
2022-09-14 19:19:32 +02:00
_relayMessages . Remove ( userId ) ;
2021-12-29 21:12:07 +01:00
return ;
}
var id = JsonNode . Parse ( content ) ? [ "id" ] ;
if ( id = = null )
{
_sawmill . Log ( LogLevel . Error , $"Could not find id in json-content returned from discord webhook: {content}" ) ;
2022-09-14 19:19:32 +02:00
_relayMessages . Remove ( userId ) ;
2021-12-29 21:12:07 +01:00
return ;
}
2022-09-14 19:19:32 +02:00
existingEmbed . id = id . ToString ( ) ;
2021-12-29 21:12:07 +01:00
}
else
{
2022-09-14 19:19:32 +02:00
var request = await _httpClient . PatchAsync ( $"{_webhookUrl}/messages/{existingEmbed.id}" ,
2021-12-29 21:12:07 +01:00
new StringContent ( JsonSerializer . Serialize ( payload ) , Encoding . UTF8 , "application/json" ) ) ;
if ( ! request . IsSuccessStatusCode )
{
var content = await request . Content . ReadAsStringAsync ( ) ;
2022-09-11 17:43:38 +02:00
_sawmill . Log ( LogLevel . Error , $"Discord returned bad status code when patching message (perhaps the message is too long?): {request.StatusCode}\nResponse: {content}" ) ;
2022-09-14 19:19:32 +02:00
_relayMessages . Remove ( userId ) ;
2021-12-29 21:12:07 +01:00
return ;
}
}
2022-09-14 19:19:32 +02:00
_relayMessages [ userId ] = existingEmbed ;
2021-12-29 21:12:07 +01:00
2022-09-14 19:19:32 +02:00
_processingChannels . Remove ( userId ) ;
2021-12-29 21:12:07 +01:00
}
2022-09-12 05:52:27 +02:00
private WebhookPayload GeneratePayload ( string messages , string username , string? characterName = null )
{
// Add character name
if ( characterName ! = null )
username + = $" ({characterName})" ;
// If no admins are online, set embed color to red. Otherwise green
2024-02-02 02:03:49 +13:00
var color = GetNonAfkAdmins ( ) . Count > 0 ? 0x41F097 : 0xFF0000 ;
2022-09-12 05:52:27 +02:00
// Limit server name to 1500 characters, in case someone tries to be a little funny
var serverName = _serverName [ . . Math . Min ( _serverName . Length , 1500 ) ] ;
2022-09-14 19:19:32 +02:00
var round = _gameTicker . RunLevel switch
{
GameRunLevel . PreRoundLobby = > _gameTicker . RoundId = = 0
? "pre-round lobby after server restart" // first round after server restart has ID == 0
: $"pre-round lobby for round {_gameTicker.RoundId + 1}" ,
GameRunLevel . InRound = > $"round {_gameTicker.RoundId}" ,
GameRunLevel . PostRound = > $"post-round {_gameTicker.RoundId}" ,
_ = > throw new ArgumentOutOfRangeException ( nameof ( _gameTicker . RunLevel ) , $"{_gameTicker.RunLevel} was not matched." ) ,
} ;
2022-09-12 05:52:27 +02:00
return new WebhookPayload
{
Username = username ,
2023-01-10 17:33:38 +01:00
AvatarUrl = string . IsNullOrWhiteSpace ( _avatarUrl ) ? null : _avatarUrl ,
2023-08-24 14:50:07 -07:00
Embeds = new List < WebhookEmbed >
2022-09-12 05:52:27 +02:00
{
2023-01-10 17:33:38 +01:00
new ( )
2022-09-12 05:52:27 +02:00
{
Description = messages ,
Color = color ,
2023-08-24 14:50:07 -07:00
Footer = new WebhookEmbedFooter
2022-09-12 05:52:27 +02:00
{
Text = $"{serverName} ({round})" ,
2023-01-10 17:33:38 +01:00
IconUrl = string . IsNullOrWhiteSpace ( _footerIconUrl ) ? null : _footerIconUrl
2022-09-12 05:52:27 +02:00
} ,
} ,
} ,
} ;
}
2021-12-29 21:12:07 +01:00
public override void Update ( float frameTime )
{
base . Update ( frameTime ) ;
2022-09-14 19:19:32 +02:00
foreach ( var userId in _messageQueues . Keys . ToArray ( ) )
2021-12-29 21:12:07 +01:00
{
2022-09-14 19:19:32 +02:00
if ( _processingChannels . Contains ( userId ) )
2022-09-11 17:43:38 +02:00
continue ;
2021-12-29 21:12:07 +01:00
2022-09-14 19:19:32 +02:00
var queue = _messageQueues [ userId ] ;
_messageQueues . Remove ( userId ) ;
2022-09-11 17:43:38 +02:00
if ( queue . Count = = 0 )
continue ;
2022-09-14 19:19:32 +02:00
_processingChannels . Add ( userId ) ;
2021-12-29 21:12:07 +01:00
2022-09-14 19:19:32 +02:00
ProcessQueue ( userId , queue ) ;
2021-12-29 21:12:07 +01:00
}
}
2021-10-06 16:25:27 +01:00
protected override void OnBwoinkTextMessage ( BwoinkTextMessage message , EntitySessionEventArgs eventArgs )
{
base . OnBwoinkTextMessage ( message , eventArgs ) ;
2023-10-28 09:59:53 +11:00
var senderSession = eventArgs . SenderSession ;
2021-10-06 16:25:27 +01:00
// TODO: Sanitize text?
// Confirm that this person is actually allowed to send a message here.
2022-09-14 19:19:32 +02:00
var personalChannel = senderSession . UserId = = message . UserId ;
2021-12-14 15:17:08 -06:00
var senderAdmin = _adminManager . GetAdminData ( senderSession ) ;
2022-09-05 16:50:52 +01:00
var senderAHelpAdmin = senderAdmin ? . HasFlag ( AdminFlags . Adminhelp ) ? ? false ;
var authorized = personalChannel | | senderAHelpAdmin ;
2021-11-11 09:47:45 +00:00
if ( ! authorized )
2021-10-06 16:25:27 +01:00
{
// Unauthorized bwoink (log?)
return ;
}
2021-12-20 12:42:42 +01:00
var escapedText = FormattedMessage . EscapeText ( message . Text ) ;
2021-11-14 20:21:18 +01:00
2023-12-20 00:40:37 +01:00
string bwoinkText ;
if ( senderAdmin is not null & & senderAdmin . Flags = = AdminFlags . Adminhelp ) // Mentor. Not full admin. That's why it's colored differently.
2021-12-14 15:17:08 -06:00
{
2024-02-21 00:52:03 -06:00
bwoinkText = $"[color=purple]{senderSession.Name}[/color]" ;
2023-12-20 00:40:37 +01:00
}
else if ( senderAdmin is not null & & senderAdmin . HasFlag ( AdminFlags . Adminhelp ) )
{
2024-02-21 00:52:03 -06:00
bwoinkText = $"[color=red]{senderSession.Name}[/color]" ;
2023-12-20 00:40:37 +01:00
}
else
{
2024-02-21 00:52:03 -06:00
bwoinkText = $"{senderSession.Name}" ;
2023-12-20 00:40:37 +01:00
}
2021-12-14 15:17:08 -06:00
2024-02-21 00:52:03 -06:00
bwoinkText = $"{(message.PlaySound ? "" : " ( S ) ")}{bwoinkText}: {escapedText}" ;
2024-02-22 09:14:57 +11:00
// If it's not an admin / admin chooses to keep the sound then play it.
var playSound = ! senderAHelpAdmin | | message . PlaySound ;
2024-02-21 00:52:03 -06:00
var msg = new BwoinkTextMessage ( message . UserId , senderSession . UserId , bwoinkText , playSound : playSound ) ;
2021-10-06 16:25:27 +01:00
LogBwoink ( msg ) ;
2022-09-11 17:43:38 +02:00
var admins = GetTargetAdmins ( ) ;
2021-10-06 16:25:27 +01:00
2022-09-11 17:43:38 +02:00
// Notify all admins
foreach ( var channel in admins )
{
2021-11-11 09:47:45 +00:00
RaiseNetworkEvent ( msg , channel ) ;
2022-09-11 17:43:38 +02:00
}
2021-11-11 09:47:45 +00:00
2022-09-11 17:43:38 +02:00
// Notify player
2022-09-14 19:19:32 +02:00
if ( _playerManager . TryGetSessionById ( message . UserId , out var session ) )
2022-09-11 17:43:38 +02:00
{
2024-01-22 23:14:13 +01:00
if ( ! admins . Contains ( session . Channel ) )
2023-12-20 00:40:37 +01:00
{
// If _overrideClientName is set, we generate a new message with the override name. The admins name will still be the original name for the webhooks.
if ( _overrideClientName ! = string . Empty )
{
string overrideMsgText ;
// Doing the same thing as above, but with the override name. Theres probably a better way to do this.
if ( senderAdmin is not null & & senderAdmin . Flags = = AdminFlags . Adminhelp ) // Mentor. Not full admin. That's why it's colored differently.
{
2024-02-21 00:52:03 -06:00
overrideMsgText = $"[color=purple]{_overrideClientName}[/color]" ;
2023-12-20 00:40:37 +01:00
}
else if ( senderAdmin is not null & & senderAdmin . HasFlag ( AdminFlags . Adminhelp ) )
{
2024-02-21 00:52:03 -06:00
overrideMsgText = $"[color=red]{_overrideClientName}[/color]" ;
2023-12-20 00:40:37 +01:00
}
else
{
2024-02-21 00:52:03 -06:00
overrideMsgText = $"{senderSession.Name}" ; // Not an admin, name is not overridden.
2023-12-20 00:40:37 +01:00
}
2024-02-21 00:52:03 -06:00
overrideMsgText = $"{(message.PlaySound ? "" : " ( S ) ")}{overrideMsgText}: {escapedText}" ;
RaiseNetworkEvent ( new BwoinkTextMessage ( message . UserId , senderSession . UserId , overrideMsgText , playSound : playSound ) , session . Channel ) ;
2023-12-20 00:40:37 +01:00
}
else
2024-01-22 23:14:13 +01:00
RaiseNetworkEvent ( msg , session . Channel ) ;
2023-12-20 00:40:37 +01:00
}
2022-09-11 17:43:38 +02:00
}
2021-12-29 21:12:07 +01:00
2021-12-22 13:34:09 +01:00
var sendsWebhook = _webhookUrl ! = string . Empty ;
if ( sendsWebhook )
{
2022-09-14 19:19:32 +02:00
if ( ! _messageQueues . ContainsKey ( msg . UserId ) )
_messageQueues [ msg . UserId ] = new Queue < string > ( ) ;
2021-12-29 21:12:07 +01:00
var str = message . Text ;
var unameLength = senderSession . Name . Length ;
2022-09-11 17:43:38 +02:00
if ( unameLength + str . Length + _maxAdditionalChars > DescriptionMax )
2021-12-22 13:34:09 +01:00
{
2022-09-11 17:43:38 +02:00
str = str [ . . ( DescriptionMax - _maxAdditionalChars - unameLength ) ] ;
2021-12-22 13:34:09 +01:00
}
2024-02-02 02:03:49 +13:00
var nonAfkAdmins = GetNonAfkAdmins ( ) ;
2024-02-22 09:14:57 +11:00
_messageQueues [ msg . UserId ] . Enqueue ( GenerateAHelpMessage ( senderSession . Name , str , ! personalChannel , _gameTicker . RoundDuration ( ) . ToString ( "hh\\:mm\\:ss" ) , _gameTicker . RunLevel , playedSound : playSound , noReceivers : nonAfkAdmins . Count = = 0 ) ) ;
2021-12-22 13:34:09 +01:00
}
2023-03-28 14:27:21 -07:00
if ( admins . Count ! = 0 | | sendsWebhook )
2022-09-11 17:43:38 +02:00
return ;
// No admin online, let the player know
2023-03-28 14:27:21 -07:00
var systemText = Loc . GetString ( "bwoink-system-starmute-message-no-other-users" ) ;
2022-09-14 19:19:32 +02:00
var starMuteMsg = new BwoinkTextMessage ( message . UserId , SystemUserId , systemText ) ;
2024-01-22 23:14:13 +01:00
RaiseNetworkEvent ( starMuteMsg , senderSession . Channel ) ;
2021-10-06 16:25:27 +01:00
}
2021-12-22 13:34:09 +01:00
2024-02-02 02:03:49 +13:00
private IList < INetChannel > GetNonAfkAdmins ( )
{
return _adminManager . ActiveAdmins
. Where ( p = > ( _adminManager . GetAdminData ( p ) ? . HasFlag ( AdminFlags . Adminhelp ) ? ? false ) & & ! _afkManager . IsAfk ( p ) )
. Select ( p = > p . Channel )
. ToList ( ) ;
}
2022-09-11 17:43:38 +02:00
private IList < INetChannel > GetTargetAdmins ( )
{
return _adminManager . ActiveAdmins
2024-02-02 02:03:49 +13:00
. Where ( p = > _adminManager . GetAdminData ( p ) ? . HasFlag ( AdminFlags . Adminhelp ) ? ? false )
. Select ( p = > p . Channel )
. ToList ( ) ;
2022-09-11 17:43:38 +02:00
}
2024-02-21 00:52:03 -06:00
private static string GenerateAHelpMessage ( string username , string message , bool admin , string roundTime , GameRunLevel roundState , bool playedSound , bool noReceivers = false )
2021-12-29 21:12:07 +01:00
{
var stringbuilder = new StringBuilder ( ) ;
2024-02-02 02:03:49 +13:00
2022-09-12 05:52:27 +02:00
if ( admin )
stringbuilder . Append ( ":outbox_tray:" ) ;
else if ( noReceivers )
stringbuilder . Append ( ":sos:" ) ;
else
stringbuilder . Append ( ":inbox_tray:" ) ;
2023-12-24 22:57:00 -08:00
if ( roundTime ! = string . Empty & & roundState = = GameRunLevel . InRound )
stringbuilder . Append ( $" **{roundTime}**" ) ;
2024-02-21 00:52:03 -06:00
if ( ! playedSound )
stringbuilder . Append ( " **(S)**" ) ;
2022-09-11 17:43:38 +02:00
stringbuilder . Append ( $" **{username}:** " ) ;
2021-12-29 21:12:07 +01:00
stringbuilder . Append ( message ) ;
return stringbuilder . ToString ( ) ;
}
2021-10-06 16:25:27 +01:00
}
}