mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-10-01 08:37:26 +00:00
+33









deltanedas
Justin
Psychpsyo
Psychpsyo
slarticodefast
SlamBamActionman
chromiumboy
ShadowCommander
Chief-Engineer
deathride58
deltanedas
PJBot
BramvanZijp
Ilya246
Moomoobeef
Saphire Lattice
FluffMe
Theodore Lukin
August Sun
august-sun
lzk
metalgearsloth
Preston Smith
Milon
Flareguy
Alzore
Boaz1111
Jarmer123
SpaceLizard
PopGamer46
RumiTiger
ScarKy0
Minemoder5000
UBlueberry
Brandon Li
Vasilis
K-Dynamic
Ubaser
AftrLite
nikthechampiongr
Centronias
joshepvodka
Scribbles0
2620eb48a8
* Fix multiplying colors of differing nullabilities (#32991) * revert the temporary hack * Make atmos alert computer colors private variables (#32992) * Add flash reaction effect (#32377) add flash reaction effect * Automatic changelog update * Fix loneop spawnrate by reverting it to not use the shuttle event system. (#32942) Fix loneop spawnrate by reverting it to not use the custom shuttle event system. * Automatic changelog update * add atmosia to devmap (#32460) change * bowls now make drinking sounds (#32819) * Automatic changelog update * Add health analyzer unrevivability warning (#32636) * Add health analyzer unrevivability warning * Remove errornous comment * Automatic changelog update * Removed the name "Hujsak" (#32998) removed hujsak * fix pie throwing sound not playing (#33017) * Automatic changelog update * Fix TestSuicideByHeldItem and TestSuicideByHeldItemSpreadDamage (#33030) * make ai speak robotically (#33025) * Automatic changelog update * Extends the minimum round time for meteor swarm events (#32876) * adjusted minimum timers in meteorswarms.yml * Updated timer minimum from 20 to 15 minutes * Reduced minimum timer to 10 minutes as a result of other meteor changes --------- Co-authored-by: august-sun <45527070+august.sun@users.noreply.github.com> * Automatic changelog update * add StartDelay bool to actions (#33026) * add StartDelay bool to actions * forgot summary * Updated an incorrect sprite in the smite menu (#33043) changed the synth sprite used in the icon for the instrumentify smite out for the more accurate supersynth sprite * fix lava expeds (#33042) Co-authored-by: deltanedas <@deltanedas:kde.org> * Automatic changelog update * Fix separated game screen bumping (#33046) I don't really understand why RecordedSplitContainer exists but removing it looks identical and fixes the panel bumping occasionally. * Automatic changelog update * Fix: Make Plushie Damage Unexaminable (#33061) Set hidden to true * fix chameleon projector bot whitelist (#33055) fix * Various Vaugely Connected Sprite Updates™: Encryption Keys, Station Map, Brig Timer (#32786) * various resprites (encryption keys + signal screens + station map) * brig timer update * fixes n shit * Give proto-kinetic crushers, glaives, and daggers better inhands. Update the crusher and glaive icons. (#32212) * inhands * better inhands * indent * Automatic changelog update * Make the security belt contain more useful items by default (#32291) * replace tear gas and flashbang with holobarrier and sec radio * holobarrier-belt-fix * revert changes of 32291 * Automatic changelog update * Add 3 bottle boxes to nanomed plus (#33018) three bottle boxes in nanomed plus * Automatic changelog update * Nukie med bundle now costs 24 tc and contains a unique defibrillator (#32720) * a-few-injectors * comment * defib-ops-when * Automatic changelog update * Pill Bottles can only store pills now (#33074) * no longer absurd * Update Resources/Prototypes/Entities/Objects/Specific/chemistry.yml --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Automatic changelog update * Add a spare bible to PietyVend (#32363) Update chapel.yml * Automatic changelog update * [#28722 fix] Add notification for dependent wearables being dropped (#33078) * add notification for dependent wearables being dropped * fix dropped item popup redundancy - did a check to see if any item was dropped, instead of making a notification for each item being dropped. * change popup to client-only variant * fix redundant messages, add plural locale string * fix conventions, fix locale input to be more intuitive --------- Co-authored-by: Justin <justinbrick1@gmail.com> * Automatic changelog update * More pda space (#32601) * Rebalance the max programs that a PDA can hold * Give Caps PDA more programs too. * Make the max programs a static 8 * I forgor sec and med * CaseCase * Empty commit to re-run checks * The final change, I hope. * Automatic changelog update * Fixed spelling mistake in water blaster description. (#33087) Fixed minor spelling mistake * [#20285 fix] Carp Plush and Rehydratables can now be put into mop bucket (#33079) * Make shark plush janitor-bucketable * fix bucketed grey shark texture * Make sprites less shiny and adapt copyright notice * Made shark way way less shiny * Allow carp plush and rehydratables in mop bucket. * Remove old mop bucket shark sprites * Fix post-merge bugs * Fix errors * Move ReactiveContainer stuff to shared That should mean it is now predicted. * Custom eject verb for the mop bucket * Fixes OnSolutionChange, removes pop-up as there already is one. * .ftl is not necessary as the custom pop-up was removed * Review fixes * Update Content.Shared/Chemistry/Components/ReactiveContainerComponent.cs * Update Content.Shared/Chemistry/EntitySystems/ReactiveContainerSystem.cs --------- Co-authored-by: Psychpsyo <psychpsyo@gmail.com> Co-authored-by: Psychpsyo <60073468+Psychpsyo@users.noreply.github.com> Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Automatic changelog update * remove now duplicated nyano PlushieCarp tag * Fixes tailthump breaking positional audio by making it mono (#33092) * Automatic changelog update * ExaminableDamage now puts its message at the bottom and in color (#32820) * the examineableDamage component now puts its messages at the bottom, and in color * god help us if something is priority -100 :godo: * Automatic changelog update * Lower in-round votekick requirements (#32953) Initial commit * Allow votekicks to be initiated in the lobby (#32528) Initial commit * Automatic changelog update * Fixes bolt lights of previously unpowered bolted doors (#33063) fix * Automatic changelog update * Muffins (#29318) * Update meta.json * Add files via upload * Update misc.yml * Update meal_recipes.yml * Update meta.json * Add files via upload * Update plate.yml * Update food_baked_single.yml * Update dinnerware.yml * Update cooking.yml * Update misc.yml * Add files via upload * Delete Resources/Textures/Objects/Consumable/Food/Baked/misc.rsi/muffin-cherry.png * Add files via upload * Update meta.json * Update misc.yml * Update meal_recipes.yml * Update meta.json * Fix meta.json * Fix meta.json again * Update misc.yml * Update misc.yml * Update misc.yml * Update misc.yml * Update meta.json * Update meta.json * Update misc.yml * Update meal_recipes.yml * Update Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/misc.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Update dinnerware.yml * Delete cherry * Add files via upload * Delete banana * Add banana * Delete chocolate * Add chocolate * lathe recipe fix --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Automatic changelog update * Borgs can no longer see mindshield + AI can no longer toggle off seeing job icons (#33069) * :( * Removed the sprite + updated RSI * Automatic changelog update * Add CanLoad for biomes (#33050) CPUJob to come later. * Remove CargoPallet component from the cargo pallet (#33022) * Change cargo shuttle pallets to catwalks. * Remove CargoPallet component from the cargo pallet. * Undo cargo shuttle changes. * Automatic changelog update * Minor antagonist guidebook changes (#32824) * took a two month nap. accidentally pushed too many buttons. let's try this again. added thieves to antagonists.xml * even after that nap, i don't feel well-rested at all. * please don't kill me for using webedit * capitalization, typo * Apply suggestions from code review (more period moving) Thanks Evan, very cool Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * guess you could say im not pro-proper noun * typo * Update Resources/ServerInfo/Guidebook/Antagonist/Nuclear Operatives.xml * ok Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> * Give Nukies a Hand Labeler (#33053) * Add hand-labeler to nukie planet * Rearrange nukie chem table * Fix `ItemSlotSystem` popup Logic (#28856) * move popup call out of `CanInsert` into `OnInteractUsing` * im stupid and `reason` is completely unnecessary Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com> * return early when `itemSlots.Slots.Count == 0` * tweak logic for triggering popups * change popup logic again * Consolidate whitelist check * Get any popup message not just last failed slot * Apply suggestions from code review Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * yoink Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> --------- Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com> Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> * Add cvars to votekick to customize requirements for the initiator. (#32490) * Automatic changelog update * Add on-call functionality for adminning (#30443) * Add on-call functionality for adminning The first time an ahelp gets SOS it gets relayed to the specified channel with the specified ping. Every time after that it doesn't until it gets a non-SOS response received. * Remove redundant name Pretty sure this already gets chucked on the name of the msg itself I think it just didn't show in screenshot because they were subsequent. * Update Content.Server/Administration/Systems/BwoinkSystem.cs Co-authored-by: Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com> --------- Co-authored-by: Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com> Co-authored-by: deathride58 <deathride58@users.noreply.github.com> * Automatic changelog update * Pills are explosion resistant (partially reverts #15851) (#32458) * idk how to revert a pr so I just deleted some lines * pill destructible with explosion resistance * comment for explosion resist * "and" to "but" --------- Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> * Automatic changelog update * Hasten handcraft gauze recipe & decrease techfab gauze cost (#32744) * med lathe gauze price reduction * gauze craft doafter time * 3 second doafter craft * Automatic changelog update * Service workers antagonist fix. (#31359) * add * Revert "add" This reverts commit 25da34b0fead5812fe5800c9bf5dd7b10ef48d7d. * antagonism allowed™️ * Automatic changelog update * Adds a new AME sound effect! (#33097) * Changes the AME sound effect to not be the default MetalThud. * Was told on discord to make a minor change to autorerun the tests due to the Build & Test Debug failing! * Attribution and licensing, as requsted by deathride58 * Fixes the high-pitched squeak audible to some people! * Audio file tweaked by SlamBamActionMan to eliminate a weird squeak they were still able to hear. Thanks! * Automatic changelog update * Allow for the ai's laws to be changed from its core and eye (#32461) * Allow for the ai's laws to be changed from its core and eye * Address reviews * Automatic changelog update * clean up tools lathe recipes (#31521) * clean up tools lathe recipes * add medical and cooking tools * add result * add result to others * review * engine --------- Co-authored-by: deltanedas <@deltanedas:kde.org> * Automatic changelog update * Intercoms and Radios both pick up proximate speech (#32737) * Deduping of recent messages should consider the channel it's being sent to * rerun actions * Automatic changelog update * Adds headphones to loadouts (#33067) added headphones to trinkets * Automatic changelog update * Cardboard Box Capacity 4 -> 5 (#32743) * capacity upgrade * comment update * remove our duplicate pill whitelist * fix * remove broken crew monitor from dev map, how did it even get there --------- Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com> Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> Co-authored-by: deltanedas <@deltanedas:kde.org> Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> Co-authored-by: BramvanZijp <56019239+BramvanZijp@users.noreply.github.com> Co-authored-by: Ilya246 <57039557+Ilya246@users.noreply.github.com> Co-authored-by: Moomoobeef <62638182+Moomoobeef@users.noreply.github.com> Co-authored-by: Saphire Lattice <lattice@saphi.re> Co-authored-by: FluffMe <1780586+FluffMe@users.noreply.github.com> Co-authored-by: Theodore Lukin <66275205+pheenty@users.noreply.github.com> Co-authored-by: August Sun <45527070+august-sun@users.noreply.github.com> Co-authored-by: august-sun <45527070+august.sun@users.noreply.github.com> Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: Preston Smith <92108534+thetolbean@users.noreply.github.com> Co-authored-by: Milon <plmilonpl@gmail.com> Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com> Co-authored-by: Alzore <140123969+Blackern5000@users.noreply.github.com> Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com> Co-authored-by: Jarmer123 <148848017+Jarmer123@users.noreply.github.com> Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> Co-authored-by: Justin <justinbrick1@gmail.com> Co-authored-by: SpaceLizard <the.justice.league.of.canada@gmail.com> Co-authored-by: Psychpsyo <psychpsyo@gmail.com> Co-authored-by: Psychpsyo <60073468+Psychpsyo@users.noreply.github.com> Co-authored-by: deathride58 <deathride58@users.noreply.github.com> Co-authored-by: PopGamer46 <yt1popgamer@gmail.com> Co-authored-by: RumiTiger <154005209+RumiTiger@users.noreply.github.com> Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Co-authored-by: Minemoder5000 <minemoder50000@gmail.com> Co-authored-by: UBlueberry <161545003+UBlueberry@users.noreply.github.com> Co-authored-by: Brandon Li <48413902+aspiringLich@users.noreply.github.com> Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Co-authored-by: Vasilis <vasilis@pikachu.systems> Co-authored-by: Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com> Co-authored-by: K-Dynamic <20566341+K-Dynamic@users.noreply.github.com> Co-authored-by: Ubaser <134914314+UbaserB@users.noreply.github.com> Co-authored-by: AftrLite <61218133+AftrLite@users.noreply.github.com> Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Co-authored-by: Centronias <me@centronias.com> Co-authored-by: joshepvodka <86210200+joshepvodka@users.noreply.github.com> Co-authored-by: Scribbles0 <91828755+Scribbles0@users.noreply.github.com>
903 lines
36 KiB
C#
903 lines
36 KiB
C#
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using Content.Server.Administration.Managers;
|
|
using Content.Server.Afk;
|
|
using Content.Server.Database;
|
|
using Content.Server.Discord;
|
|
using Content.Server.GameTicking;
|
|
using Content.Server.Players.RateLimiting;
|
|
using Content.Shared.Administration;
|
|
using Content.Shared.CCVar;
|
|
using Content.Shared.GameTicking;
|
|
using Content.Shared.Mind;
|
|
using Content.Shared.Players.RateLimiting;
|
|
using JetBrains.Annotations;
|
|
using Robust.Server.Player;
|
|
using Robust.Shared;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.Enums;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Server.Administration.Systems
|
|
{
|
|
[UsedImplicitly]
|
|
public sealed partial class BwoinkSystem : SharedBwoinkSystem
|
|
{
|
|
private const string RateLimitKey = "AdminHelp";
|
|
|
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
|
[Dependency] private readonly IAdminManager _adminManager = default!;
|
|
[Dependency] private readonly IConfigurationManager _config = default!;
|
|
[Dependency] private readonly IGameTiming _timing = default!;
|
|
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
|
[Dependency] private readonly GameTicker _gameTicker = default!;
|
|
[Dependency] private readonly SharedMindSystem _minds = default!;
|
|
[Dependency] private readonly IAfkManager _afkManager = default!;
|
|
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
|
[Dependency] private readonly PlayerRateLimitManager _rateLimit = default!;
|
|
|
|
[GeneratedRegex(@"^https://discord\.com/api/webhooks/(\d+)/((?!.*/).*)$")]
|
|
private static partial Regex DiscordRegex();
|
|
|
|
private string _webhookUrl = string.Empty;
|
|
private WebhookData? _webhookData;
|
|
|
|
private string _onCallUrl = string.Empty;
|
|
private WebhookData? _onCallData;
|
|
|
|
private ISawmill _sawmill = default!;
|
|
private readonly HttpClient _httpClient = new();
|
|
|
|
private string _footerIconUrl = string.Empty;
|
|
private string _avatarUrl = string.Empty;
|
|
private string _serverName = string.Empty;
|
|
|
|
private readonly Dictionary<NetUserId, DiscordRelayInteraction> _relayMessages = new();
|
|
|
|
private Dictionary<NetUserId, string> _oldMessageIds = new();
|
|
private readonly Dictionary<NetUserId, Queue<DiscordRelayedData>> _messageQueues = new();
|
|
private readonly HashSet<NetUserId> _processingChannels = new();
|
|
private readonly Dictionary<NetUserId, (TimeSpan Timestamp, bool Typing)> _typingUpdateTimestamps = new();
|
|
private string _overrideClientName = string.Empty;
|
|
|
|
// 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;
|
|
|
|
// 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)**";
|
|
|
|
private int _maxAdditionalChars;
|
|
private readonly Dictionary<NetUserId, DateTime> _activeConversations = new();
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
Subs.CVar(_config, CCVars.DiscordOnCallWebhook, OnCallChanged, true);
|
|
|
|
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);
|
|
_sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("AHELP");
|
|
|
|
var defaultParams = new AHelpMessageParams(
|
|
string.Empty,
|
|
string.Empty,
|
|
true,
|
|
_gameTicker.RoundDuration().ToString("hh\\:mm\\:ss"),
|
|
_gameTicker.RunLevel,
|
|
playedSound: false
|
|
);
|
|
_maxAdditionalChars = GenerateAHelpMessage(defaultParams).Message.Length;
|
|
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
|
|
|
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnGameRunLevelChanged);
|
|
SubscribeNetworkEvent<BwoinkClientTypingUpdated>(OnClientTypingUpdated);
|
|
SubscribeLocalEvent<RoundRestartCleanupEvent>(_ => _activeConversations.Clear());
|
|
|
|
_rateLimit.Register(
|
|
RateLimitKey,
|
|
new RateLimitRegistration(CCVars.AhelpRateLimitPeriod,
|
|
CCVars.AhelpRateLimitCount,
|
|
PlayerRateLimitedAction)
|
|
);
|
|
}
|
|
|
|
private async void OnCallChanged(string url)
|
|
{
|
|
_onCallUrl = url;
|
|
|
|
if (url == string.Empty)
|
|
return;
|
|
|
|
var match = DiscordRegex().Match(url);
|
|
|
|
if (!match.Success)
|
|
{
|
|
Log.Error("On call URL does not appear to be valid.");
|
|
return;
|
|
}
|
|
|
|
if (match.Groups.Count <= 2)
|
|
{
|
|
Log.Error("Could not get webhook ID or token for on call URL.");
|
|
return;
|
|
}
|
|
|
|
var webhookId = match.Groups[1].Value;
|
|
var webhookToken = match.Groups[2].Value;
|
|
|
|
_onCallData = await GetWebhookData(webhookId, webhookToken);
|
|
}
|
|
|
|
private void PlayerRateLimitedAction(ICommonSession obj)
|
|
{
|
|
RaiseNetworkEvent(
|
|
new BwoinkTextMessage(obj.UserId, default, Loc.GetString("bwoink-system-rate-limited"), playSound: false),
|
|
obj.Channel);
|
|
}
|
|
|
|
private void OnOverrideChanged(string obj)
|
|
{
|
|
_overrideClientName = obj;
|
|
}
|
|
|
|
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
|
{
|
|
if (e.NewStatus == SessionStatus.Disconnected)
|
|
{
|
|
if (_activeConversations.TryGetValue(e.Session.UserId, out var lastMessageTime))
|
|
{
|
|
var timeSinceLastMessage = DateTime.Now - lastMessageTime;
|
|
if (timeSinceLastMessage > TimeSpan.FromMinutes(5))
|
|
{
|
|
_activeConversations.Remove(e.Session.UserId);
|
|
return; // Do not send disconnect message if timeout exceeded
|
|
}
|
|
}
|
|
|
|
// Check if the user has been banned
|
|
var ban = await _dbManager.GetServerBanAsync(null, e.Session.UserId, null);
|
|
if (ban != null)
|
|
{
|
|
var banMessage = Loc.GetString("bwoink-system-player-banned", ("banReason", ban.Reason));
|
|
NotifyAdmins(e.Session, banMessage, PlayerStatusType.Banned);
|
|
_activeConversations.Remove(e.Session.UserId);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Notify all admins if a player disconnects or reconnects
|
|
var message = e.NewStatus switch
|
|
{
|
|
SessionStatus.Connected => Loc.GetString("bwoink-system-player-reconnecting"),
|
|
SessionStatus.Disconnected => Loc.GetString("bwoink-system-player-disconnecting"),
|
|
_ => null
|
|
};
|
|
|
|
if (message != null)
|
|
{
|
|
var statusType = e.NewStatus == SessionStatus.Connected
|
|
? PlayerStatusType.Connected
|
|
: PlayerStatusType.Disconnected;
|
|
NotifyAdmins(e.Session, message, statusType);
|
|
}
|
|
|
|
if (e.NewStatus != SessionStatus.InGame)
|
|
return;
|
|
|
|
RaiseNetworkEvent(new BwoinkDiscordRelayUpdated(!string.IsNullOrWhiteSpace(_webhookUrl)), e.Session);
|
|
}
|
|
|
|
private void NotifyAdmins(ICommonSession session, string message, PlayerStatusType statusType)
|
|
{
|
|
if (!_activeConversations.ContainsKey(session.UserId))
|
|
{
|
|
// If the user is not part of an active conversation, do not notify admins.
|
|
return;
|
|
}
|
|
|
|
// Get the current timestamp
|
|
var timestamp = DateTime.Now.ToString("HH:mm:ss");
|
|
var roundTime = _gameTicker.RoundDuration().ToString("hh\\:mm\\:ss");
|
|
|
|
// Determine the icon based on the status type
|
|
string icon = statusType switch
|
|
{
|
|
PlayerStatusType.Connected => ":green_circle:",
|
|
PlayerStatusType.Disconnected => ":red_circle:",
|
|
PlayerStatusType.Banned => ":no_entry:",
|
|
_ => ":question:"
|
|
};
|
|
|
|
// Create the message parameters for Discord
|
|
var messageParams = new AHelpMessageParams(
|
|
session.Name,
|
|
message,
|
|
true,
|
|
roundTime,
|
|
_gameTicker.RunLevel,
|
|
playedSound: true,
|
|
icon: icon
|
|
);
|
|
|
|
// Create the message for in-game with username
|
|
var color = statusType switch
|
|
{
|
|
PlayerStatusType.Connected => Color.Green.ToHex(),
|
|
PlayerStatusType.Disconnected => Color.Yellow.ToHex(),
|
|
PlayerStatusType.Banned => Color.Orange.ToHex(),
|
|
_ => Color.Gray.ToHex(),
|
|
};
|
|
var inGameMessage = $"[color={color}]{session.Name} {message}[/color]";
|
|
|
|
var bwoinkMessage = new BwoinkTextMessage(
|
|
userId: session.UserId,
|
|
trueSender: SystemUserId,
|
|
text: inGameMessage,
|
|
sentAt: DateTime.Now,
|
|
playSound: false
|
|
);
|
|
|
|
var admins = GetTargetAdmins();
|
|
foreach (var admin in admins)
|
|
{
|
|
RaiseNetworkEvent(bwoinkMessage, admin);
|
|
}
|
|
|
|
// Enqueue the message for Discord relay
|
|
if (_webhookUrl != string.Empty)
|
|
{
|
|
// if (!_messageQueues.ContainsKey(session.UserId))
|
|
// _messageQueues[session.UserId] = new Queue<string>();
|
|
//
|
|
// var escapedText = FormattedMessage.EscapeText(message);
|
|
// messageParams.Message = escapedText;
|
|
//
|
|
// var discordMessage = GenerateAHelpMessage(messageParams);
|
|
// _messageQueues[session.UserId].Enqueue(discordMessage);
|
|
|
|
var queue = _messageQueues.GetOrNew(session.UserId);
|
|
var escapedText = FormattedMessage.EscapeText(message);
|
|
messageParams.Message = escapedText;
|
|
var discordMessage = GenerateAHelpMessage(messageParams);
|
|
queue.Enqueue(discordMessage);
|
|
}
|
|
}
|
|
|
|
private void OnGameRunLevelChanged(GameRunLevelChangedEvent args)
|
|
{
|
|
// 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 (user, interaction) in _relayMessages)
|
|
{
|
|
var id = interaction.Id;
|
|
if (id == null)
|
|
return;
|
|
|
|
_oldMessageIds[user] = id;
|
|
}
|
|
|
|
_relayMessages.Clear();
|
|
}
|
|
|
|
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
|
|
var isAdmin = _adminManager.GetAdminData(args.SenderSession)?.HasFlag(AdminFlags.Adminhelp) ?? false;
|
|
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);
|
|
}
|
|
}
|
|
|
|
private void OnServerNameChanged(string obj)
|
|
{
|
|
_serverName = obj;
|
|
}
|
|
|
|
private async void OnWebhookChanged(string url)
|
|
{
|
|
_webhookUrl = url;
|
|
|
|
RaiseNetworkEvent(new BwoinkDiscordRelayUpdated(!string.IsNullOrWhiteSpace(url)));
|
|
|
|
if (url == string.Empty)
|
|
return;
|
|
|
|
// Basic sanity check and capturing webhook ID and token
|
|
var match = DiscordRegex().Match(url);
|
|
|
|
if (!match.Success)
|
|
{
|
|
// TODO: Ideally, CVar validation during setting should be better integrated
|
|
Log.Warning("Webhook URL does not appear to be valid. Using anyways...");
|
|
return;
|
|
}
|
|
|
|
if (match.Groups.Count <= 2)
|
|
{
|
|
Log.Error("Could not get webhook ID or token.");
|
|
return;
|
|
}
|
|
|
|
var webhookId = match.Groups[1].Value;
|
|
var webhookToken = match.Groups[2].Value;
|
|
|
|
// Fire and forget
|
|
_webhookData = await GetWebhookData(webhookId, webhookToken);
|
|
}
|
|
|
|
private async Task<WebhookData?> GetWebhookData(string id, string token)
|
|
{
|
|
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 null;
|
|
}
|
|
|
|
return JsonSerializer.Deserialize<WebhookData>(content);
|
|
}
|
|
|
|
private void OnFooterIconChanged(string url)
|
|
{
|
|
_footerIconUrl = url;
|
|
}
|
|
|
|
private void OnAvatarChanged(string url)
|
|
{
|
|
_avatarUrl = url;
|
|
}
|
|
|
|
private async void ProcessQueue(NetUserId userId, Queue<DiscordRelayedData> messages)
|
|
{
|
|
// 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.Message.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)
|
|
{
|
|
var lookup = await _playerLocator.LookupIdAsync(userId);
|
|
|
|
if (lookup == null)
|
|
{
|
|
_sawmill.Log(LogLevel.Error,
|
|
$"Unable to find player for NetUserId {userId} when sending discord webhook.");
|
|
_relayMessages.Remove(userId);
|
|
return;
|
|
}
|
|
|
|
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";
|
|
}
|
|
}
|
|
|
|
var characterName = _minds.GetCharacterName(userId);
|
|
existingEmbed = new DiscordRelayInteraction()
|
|
{
|
|
Id = null,
|
|
CharacterName = characterName,
|
|
Description = linkToPrevious,
|
|
Username = lookup.Username,
|
|
LastRunLevel = _gameTicker.RunLevel,
|
|
};
|
|
|
|
_relayMessages[userId] = existingEmbed;
|
|
}
|
|
|
|
// 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."),
|
|
};
|
|
|
|
existingEmbed.LastRunLevel = _gameTicker.RunLevel;
|
|
}
|
|
|
|
// If last message of the new batch is SOS then relay it to on-call.
|
|
// ... as long as it hasn't been relayed already.
|
|
var discordMention = messages.Last();
|
|
var onCallRelay = !discordMention.Receivers && !existingEmbed.OnCall;
|
|
|
|
// Add available messages to the embed description
|
|
while (messages.TryDequeue(out var message))
|
|
{
|
|
string text;
|
|
|
|
// In case someone thinks they're funny
|
|
if (message.Message.Length > MessageLengthCap)
|
|
text = message.Message[..(MessageLengthCap - TooLongText.Length)] + TooLongText;
|
|
else
|
|
text = message.Message;
|
|
|
|
existingEmbed.Description += $"\n{text}";
|
|
}
|
|
|
|
var payload = GeneratePayload(existingEmbed.Description,
|
|
existingEmbed.Username,
|
|
existingEmbed.CharacterName);
|
|
|
|
// If there is no existing embed, create a new one
|
|
// Otherwise patch (edit) it
|
|
if (existingEmbed.Id == null)
|
|
{
|
|
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)
|
|
{
|
|
_sawmill.Log(LogLevel.Error,
|
|
$"Discord returned bad status code when posting message (perhaps the message is too long?): {request.StatusCode}\nResponse: {content}");
|
|
_relayMessages.Remove(userId);
|
|
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}");
|
|
_relayMessages.Remove(userId);
|
|
return;
|
|
}
|
|
|
|
existingEmbed.Id = id.ToString();
|
|
}
|
|
else
|
|
{
|
|
var request = await _httpClient.PatchAsync($"{_webhookUrl}/messages/{existingEmbed.Id}",
|
|
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"));
|
|
|
|
if (!request.IsSuccessStatusCode)
|
|
{
|
|
var content = await request.Content.ReadAsStringAsync();
|
|
_sawmill.Log(LogLevel.Error,
|
|
$"Discord returned bad status code when patching message (perhaps the message is too long?): {request.StatusCode}\nResponse: {content}");
|
|
_relayMessages.Remove(userId);
|
|
return;
|
|
}
|
|
}
|
|
|
|
_relayMessages[userId] = existingEmbed;
|
|
|
|
// Actually do the on call relay last, we just need to grab it before we dequeue every message above.
|
|
if (onCallRelay &&
|
|
_onCallData != null)
|
|
{
|
|
existingEmbed.OnCall = true;
|
|
var roleMention = _config.GetCVar(CCVars.DiscordAhelpMention);
|
|
|
|
if (!string.IsNullOrEmpty(roleMention))
|
|
{
|
|
var message = new StringBuilder();
|
|
message.AppendLine($"<@&{roleMention}>");
|
|
message.AppendLine("Unanswered SOS");
|
|
|
|
// Need webhook data to get the correct link for that channel rather than on-call data.
|
|
if (_webhookData is { GuildId: { } guildId, ChannelId: { } channelId })
|
|
{
|
|
message.AppendLine(
|
|
$"**[Go to ahelp](https://discord.com/channels/{guildId}/{channelId}/{existingEmbed.Id})**");
|
|
}
|
|
|
|
payload = GeneratePayload(message.ToString(), existingEmbed.Username, existingEmbed.CharacterName);
|
|
|
|
var request = await _httpClient.PostAsync($"{_onCallUrl}?wait=true",
|
|
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"));
|
|
|
|
var content = await request.Content.ReadAsStringAsync();
|
|
if (!request.IsSuccessStatusCode)
|
|
{
|
|
_sawmill.Log(LogLevel.Error, $"Discord returned bad status code when posting relay message (perhaps the message is too long?): {request.StatusCode}\nResponse: {content}");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
existingEmbed.OnCall = false;
|
|
}
|
|
|
|
_processingChannels.Remove(userId);
|
|
}
|
|
|
|
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
|
|
var color = GetNonAfkAdmins().Count > 0 ? 0x41F097 : 0xFF0000;
|
|
|
|
// Limit server name to 1500 characters, in case someone tries to be a little funny
|
|
var serverName = _serverName[..Math.Min(_serverName.Length, 1500)];
|
|
|
|
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."),
|
|
};
|
|
|
|
return new WebhookPayload
|
|
{
|
|
Username = username,
|
|
AvatarUrl = string.IsNullOrWhiteSpace(_avatarUrl) ? null : _avatarUrl,
|
|
Embeds = new List<WebhookEmbed>
|
|
{
|
|
new()
|
|
{
|
|
Description = messages,
|
|
Color = color,
|
|
Footer = new WebhookEmbedFooter
|
|
{
|
|
Text = $"{serverName} ({round})",
|
|
IconUrl = string.IsNullOrWhiteSpace(_footerIconUrl) ? null : _footerIconUrl
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
base.Update(frameTime);
|
|
|
|
foreach (var userId in _messageQueues.Keys.ToArray())
|
|
{
|
|
if (_processingChannels.Contains(userId))
|
|
continue;
|
|
|
|
var queue = _messageQueues[userId];
|
|
_messageQueues.Remove(userId);
|
|
if (queue.Count == 0)
|
|
continue;
|
|
|
|
_processingChannels.Add(userId);
|
|
|
|
ProcessQueue(userId, queue);
|
|
}
|
|
}
|
|
|
|
protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySessionEventArgs eventArgs)
|
|
{
|
|
base.OnBwoinkTextMessage(message, eventArgs);
|
|
_activeConversations[message.UserId] = DateTime.Now;
|
|
var senderSession = eventArgs.SenderSession;
|
|
|
|
// TODO: Sanitize text?
|
|
// Confirm that this person is actually allowed to send a message here.
|
|
var personalChannel = senderSession.UserId == message.UserId;
|
|
var senderAdmin = _adminManager.GetAdminData(senderSession);
|
|
var senderAHelpAdmin = senderAdmin?.HasFlag(AdminFlags.Adminhelp) ?? false;
|
|
var authorized = personalChannel || senderAHelpAdmin;
|
|
if (!authorized)
|
|
{
|
|
// Unauthorized bwoink (log?)
|
|
return;
|
|
}
|
|
|
|
if (_rateLimit.CountAction(eventArgs.SenderSession, RateLimitKey) != RateLimitStatus.Allowed)
|
|
return;
|
|
|
|
var escapedText = FormattedMessage.EscapeText(message.Text);
|
|
|
|
string bwoinkText;
|
|
string adminPrefix = "";
|
|
|
|
//Getting an administrator position
|
|
if (_config.GetCVar(CCVars.AhelpAdminPrefix) && senderAdmin is not null && senderAdmin.Title is not null)
|
|
{
|
|
adminPrefix = $"[bold]\\[{senderAdmin.Title}\\][/bold] ";
|
|
}
|
|
|
|
if (senderAdmin is not null &&
|
|
senderAdmin.Flags ==
|
|
AdminFlags.Adminhelp) // Mentor. Not full admin. That's why it's colored differently.
|
|
{
|
|
bwoinkText = $"[color=purple]{adminPrefix}{senderSession.Name}[/color]";
|
|
}
|
|
else if (senderAdmin is not null && senderAdmin.HasFlag(AdminFlags.Adminhelp))
|
|
{
|
|
bwoinkText = $"[color=red]{adminPrefix}{senderSession.Name}[/color]";
|
|
}
|
|
else
|
|
{
|
|
bwoinkText = $"{senderSession.Name}";
|
|
}
|
|
|
|
bwoinkText = $"{(message.PlaySound ? "" : "(S) ")}{bwoinkText}: {escapedText}";
|
|
|
|
// If it's not an admin / admin chooses to keep the sound then play it.
|
|
var playSound = !senderAHelpAdmin || message.PlaySound;
|
|
var msg = new BwoinkTextMessage(message.UserId, senderSession.UserId, bwoinkText, playSound: playSound);
|
|
|
|
LogBwoink(msg);
|
|
|
|
var admins = GetTargetAdmins();
|
|
|
|
// Notify all admins
|
|
foreach (var channel in admins)
|
|
{
|
|
RaiseNetworkEvent(msg, channel);
|
|
}
|
|
|
|
string adminPrefixWebhook = "";
|
|
|
|
if (_config.GetCVar(CCVars.AhelpAdminPrefixWebhook) && senderAdmin is not null && senderAdmin.Title is not null)
|
|
{
|
|
adminPrefixWebhook = $"[bold]\\[{senderAdmin.Title}\\][/bold] ";
|
|
}
|
|
|
|
// Notify player
|
|
if (_playerManager.TryGetSessionById(message.UserId, out var session))
|
|
{
|
|
if (!admins.Contains(session.Channel))
|
|
{
|
|
// 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.
|
|
{
|
|
overrideMsgText = $"[color=purple]{adminPrefixWebhook}{_overrideClientName}[/color]";
|
|
}
|
|
else if (senderAdmin is not null && senderAdmin.HasFlag(AdminFlags.Adminhelp))
|
|
{
|
|
overrideMsgText = $"[color=red]{adminPrefixWebhook}{_overrideClientName}[/color]";
|
|
}
|
|
else
|
|
{
|
|
overrideMsgText = $"{senderSession.Name}"; // Not an admin, name is not overridden.
|
|
}
|
|
|
|
overrideMsgText = $"{(message.PlaySound ? "" : "(S) ")}{overrideMsgText}: {escapedText}";
|
|
|
|
RaiseNetworkEvent(new BwoinkTextMessage(message.UserId,
|
|
senderSession.UserId,
|
|
overrideMsgText,
|
|
playSound: playSound),
|
|
session.Channel);
|
|
}
|
|
else
|
|
RaiseNetworkEvent(msg, session.Channel);
|
|
}
|
|
}
|
|
|
|
var sendsWebhook = _webhookUrl != string.Empty;
|
|
if (sendsWebhook)
|
|
{
|
|
if (!_messageQueues.ContainsKey(msg.UserId))
|
|
_messageQueues[msg.UserId] = new Queue<DiscordRelayedData>();
|
|
|
|
var str = message.Text;
|
|
var unameLength = senderSession.Name.Length;
|
|
|
|
if (unameLength + str.Length + _maxAdditionalChars > DescriptionMax)
|
|
{
|
|
str = str[..(DescriptionMax - _maxAdditionalChars - unameLength)];
|
|
}
|
|
|
|
var nonAfkAdmins = GetNonAfkAdmins();
|
|
var messageParams = new AHelpMessageParams(
|
|
senderSession.Name,
|
|
str,
|
|
!personalChannel,
|
|
_gameTicker.RoundDuration().ToString("hh\\:mm\\:ss"),
|
|
_gameTicker.RunLevel,
|
|
playedSound: playSound,
|
|
noReceivers: nonAfkAdmins.Count == 0
|
|
);
|
|
_messageQueues[msg.UserId].Enqueue(GenerateAHelpMessage(messageParams));
|
|
}
|
|
|
|
if (admins.Count != 0 || sendsWebhook)
|
|
return;
|
|
|
|
// No admin online, let the player know
|
|
var systemText = Loc.GetString("bwoink-system-starmute-message-no-other-users");
|
|
var starMuteMsg = new BwoinkTextMessage(message.UserId, SystemUserId, systemText);
|
|
RaiseNetworkEvent(starMuteMsg, senderSession.Channel);
|
|
}
|
|
|
|
private IList<INetChannel> GetNonAfkAdmins()
|
|
{
|
|
return _adminManager.ActiveAdmins
|
|
.Where(p => (_adminManager.GetAdminData(p)?.HasFlag(AdminFlags.Adminhelp) ?? false) &&
|
|
!_afkManager.IsAfk(p))
|
|
.Select(p => p.Channel)
|
|
.ToList();
|
|
}
|
|
|
|
private IList<INetChannel> GetTargetAdmins()
|
|
{
|
|
return _adminManager.ActiveAdmins
|
|
.Where(p => _adminManager.GetAdminData(p)?.HasFlag(AdminFlags.Adminhelp) ?? false)
|
|
.Select(p => p.Channel)
|
|
.ToList();
|
|
}
|
|
|
|
private static DiscordRelayedData GenerateAHelpMessage(AHelpMessageParams parameters)
|
|
{
|
|
var stringbuilder = new StringBuilder();
|
|
|
|
if (parameters.Icon != null)
|
|
stringbuilder.Append(parameters.Icon);
|
|
else if (parameters.IsAdmin)
|
|
stringbuilder.Append(":outbox_tray:");
|
|
else if (parameters.NoReceivers)
|
|
stringbuilder.Append(":sos:");
|
|
else
|
|
stringbuilder.Append(":inbox_tray:");
|
|
|
|
if (parameters.RoundTime != string.Empty && parameters.RoundState == GameRunLevel.InRound)
|
|
stringbuilder.Append($" **{parameters.RoundTime}**");
|
|
if (!parameters.PlayedSound)
|
|
stringbuilder.Append(" **(S)**");
|
|
if (parameters.Icon == null)
|
|
stringbuilder.Append($" **{parameters.Username}:** ");
|
|
else
|
|
stringbuilder.Append($" **{parameters.Username}** ");
|
|
stringbuilder.Append(parameters.Message);
|
|
|
|
return new DiscordRelayedData()
|
|
{
|
|
Receivers = !parameters.NoReceivers,
|
|
Message = stringbuilder.ToString(),
|
|
};
|
|
}
|
|
|
|
private record struct DiscordRelayedData
|
|
{
|
|
/// <summary>
|
|
/// Was anyone online to receive it.
|
|
/// </summary>
|
|
public bool Receivers;
|
|
|
|
/// <summary>
|
|
/// What's the payload to send to discord.
|
|
/// </summary>
|
|
public string Message;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Class specifically for holding information regarding existing Discord embeds
|
|
/// </summary>
|
|
private sealed class DiscordRelayInteraction
|
|
{
|
|
public string? Id;
|
|
|
|
public string Username = String.Empty;
|
|
|
|
public string? CharacterName;
|
|
|
|
/// <summary>
|
|
/// Contents for the discord message.
|
|
/// </summary>
|
|
public string Description = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Run level of the last interaction. If different we'll link to the last Id.
|
|
/// </summary>
|
|
public GameRunLevel LastRunLevel;
|
|
|
|
/// <summary>
|
|
/// Did we relay this interaction to OnCall previously.
|
|
/// </summary>
|
|
public bool OnCall;
|
|
}
|
|
}
|
|
|
|
public sealed class AHelpMessageParams
|
|
{
|
|
public string Username { get; set; }
|
|
public string Message { get; set; }
|
|
public bool IsAdmin { get; set; }
|
|
public string RoundTime { get; set; }
|
|
public GameRunLevel RoundState { get; set; }
|
|
public bool PlayedSound { get; set; }
|
|
public bool NoReceivers { get; set; }
|
|
public string? Icon { get; set; }
|
|
|
|
public AHelpMessageParams(
|
|
string username,
|
|
string message,
|
|
bool isAdmin,
|
|
string roundTime,
|
|
GameRunLevel roundState,
|
|
bool playedSound,
|
|
bool noReceivers = false,
|
|
string? icon = null)
|
|
{
|
|
Username = username;
|
|
Message = message;
|
|
IsAdmin = isAdmin;
|
|
RoundTime = roundTime;
|
|
RoundState = roundState;
|
|
PlayedSound = playedSound;
|
|
NoReceivers = noReceivers;
|
|
Icon = icon;
|
|
}
|
|
}
|
|
|
|
public enum PlayerStatusType
|
|
{
|
|
Connected,
|
|
Disconnected,
|
|
Banned,
|
|
}
|
|
}
|