From 97a9c04d4e53d3bd87060d8f91a930e03a4c258d Mon Sep 17 00:00:00 2001 From: HitPanda <104197232+EnefFlow@users.noreply.github.com> Date: Tue, 9 Jan 2024 15:54:16 +0300 Subject: [PATCH] [Feat] Panda socket (#616) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * base server side * token check & base command response * panda base command response addition & some commands * web event, rest of commands & events * fix empty api * [DRAFT] Panda HTTP server (#570) * [Sponsor] Nairsark ghost (#492) * [Sponsor] Trora ghost (#493) * [Sponsor] Trora ghost * ckey * Vtergot fluff (#494) * Твики (#491) * Economy additions * Tweak implant cooldowns * Cult stuff * Random appearance aspect nuke ops fix * Auto shuttle enable on round end * Holy water threshold * Automatic changelog update * [Sponsor] Geraldiy fluff (#496) * [Fix] Reputation respawn hotfix (#497) * Automatic changelog update * Новые аспекты (#495) * Add ReflectAspect * Add SlipperyAspect * Add TraitorRichAspect * Add WhisperAspect * Add DarknessAspect & StolenFloorAspect * Add WindowLeakAspect * Add CatEarsAspect * Add NothingAspect * Fix fast and furious clone * Add SkeletonAspect * Add cvar ceanup * Automatic changelog update * [Sponsor] Fluff knife cappy (#498) * [Sponsor] Fluff KnifeCappy * loadout * Всякое (#501) * eftpos form jurist * ebal parameda * Automatic changelog update * [Sponsor] Zilendorie ghost upgrade (#502) * Апдейт карт, станция прибытия (#503) * Automatic changelog update * Fix aspects (#500) * Automatic changelog update * Rules popup fix (#504) * Defib fix (#499) * Automatic changelog update * Фикс флаффа (#507) * Фикс прибытия (#509) * Automatic changelog update * Фиксы (#508) * Fix cult blindfold * Add stamina resistances * Energy bolt is energy * Laser shield is anti-laser * Cult blindfold welding protection * Eject id cards on deconstruct * Wires panel power fix * Add markings for species * Ebow gaming * feat: настенные консольки (#505) * Automatic changelog update * [Sponsor] Fluff Medicgaming (#510) * Привязка банковского аккаунта (#506) * Sustenance vend price fix * Account link * Automatic changelog update * antag ban fix (#511) * antag ban fix * rename some shit * Bugfixes (#512) * Bullets go through open crates * Bullets don't hit pulled dead bodies * No glued cuffs * Missed reflect aspect mark * vehicles cannot be shot (#18910) Co-authored-by: deltanedas <@deltanedas:kde.org> * Cleanup --------- Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> * Automatic changelog update * атмос гейминг indeed (#513) * fix hypernob + plasma and trit fire * add 11 new gas types * actually fix hypernob --------- Co-authored-by: halicopter * Automatic changelog update * [Sponsor] thechaotic fluff * Revert "[Sponsor] thechaotic fluff" This reverts commit d19c807751258f9cfb205db01b0e4617b638fe98. * [Sponsor] Fluff updates (#518) * warete update * renosan & warete size * [Sponsor] thechaotic fluff (#519) (cherry picked from commit d19c807751258f9cfb205db01b0e4617b638fe98) * fix tts sanitize (#517) * Automatic changelog update * [Fluff] ghost regari * Buffs (#515) * FIRE axe * Bow pro * Bang * Meleeeee * Disarm two-handed hard * Automatic changelog update * Игнор вайтлист требования ролей если стоит флаг TOOLS (#514) * forAmins be like * >вайтлист в девелопменте * а описание кто напишет а * Размеры попапов в чате (#525) * попытка деконить магнит пока он активен гибает интернет приколистов (#524) * Интернет прикол * попап * Prevent using cultist items & halberd (#523) * Fix skeleton aspect (#522) * Fix cult door (#521) * Fix cult door * Prevent emagging * Walls and griders * Fix * Drone fix (#520) * Drone fix * RCD * Automatic changelog update * Смешное в конце раунда с аспектом (#516) * Cats * Fix * Automatic changelog update * Fix cult win conditions (#527) * Runes stuff (#526) * Blood boil 2.0 * Attach to grid * No * Automatic changelog update * [Sponsor] thefrendlypsychopath ghost (#529) * knifeCappy (#528) Co-authored-by: Mona Hmiza * [Feat] TTS 8 new voices * borg fix * Automatic changelog update * мапперы тестили * fix void prototype * [Fix] Cult chat fix (#536) * ERT tweak (#530) * tweak values * tweak spawn logic * Invisible rune (#531) * Automatic changelog update * [Sponsor] Oleg_Tinkoff fluff (#534) * [Sponsor] Oleg_Tinkoff fluff * name fix * Small fixes (#537) * No sleeping emotes * Beakers & jugs in fridge * Drone fix * Fix teleport pulling * Fix loc * More fixes * Fix ghost role * Stuff * Automatic changelog update * Nerf grilles (#541) * Nerf grilles * tweak * Bola fix (#540) * Automatic changelog update * Куча говна (#539) * No Emp resistance (#538) * No EMP resistance * Cleanup * Automatic changelog update * mappews mapped some mowe~ * anothew update UwU * [Sponsor] Geraldiy fluff 2 (#542) * fix drydock * Objective changes (#543) * Automatic changelog update * UwU spawnews are now fixed, hopefully~ * UwU tired of it alweady * Automatic changelog update * Респрайт капитанского лазера (#546) * antique lasergun resprite lol * лицуха * Automatic changelog update * Апдейт арахнидов (#544) * Web underwear * Fix layers * Change melee sound * Arachnid 2: Episode 2 (#19984) * Shield * minor sprite changes and buffs * structure buff * Crafting stuff * tweaks * 88-88 * Better web pocket sprites. * yeah it's fine now. * Fix * Sprite tweaks * This I guess * Eye sprite --------- Co-authored-by: PixelTK <85175107+PixelTheKermit@users.noreply.github.com> * [Sponsor] Zilendorie fluff (#545) * Automatic changelog update * Fire buff (#549) * EntityStorage deletion fix? (#548) * strip fix (#21552) (#547) Co-authored-by: PixelTK <85175107+PixelTheKermit@users.noreply.github.com> * Automatic changelog update * [Fluff] SSAO Ghost * [Fluff] svinka ghost tweak * [Fluff] Svinka Coat * Meaty-Ore Idea (#550) * qwe * yeah * Vtergot fluff fix (#552) * наяриваем на лишние пиксели * Чтобы мапперы не втыкали * fix atmos (#551) * fix atmos * fixie * Automatic changelog update * Meow hotfix (#553) * Many stuff (#555) * Drone bucket * Weird insuls * Not too strong * No cult door bolts * Emergency shuttle after round end fix * Fix spiders * Automatic changelog update * [FEAT] Всякие прикольные разности и вкусности (#554) * feat: трикодер * feat: принтер документов * fix: текст фелинидов * feat: возможность менять голос эмоутов * feat: мяукаем при аспекте мяуканья * feat: ПНВ * fix: забирай свои метадаты * fix: oopsies * fix: линтер снова * fix: пожалуйста линтер отстань * Automatic changelog update * Гарпии (#533) * harpy initial * fix and some locale * ru locale * actions refactor shit * пофиксив гавпий~~~ пойду тестить~~ * hawpies are ready UwU * cweanup OwO * hawpies fixed a bit, still cant seawch them nya~ * hawpies can be stwipped now, fixie-dixie awwived~ * emotes fixie-dixied nya~ * говно * говно говна линтер соси * Automatic changelog update * Revert "Гарпии (#533)" (#557) This reverts commit e3f2166bf7cbd775278a396a3f8d8215a2d5c506. * Doom fluff (#556) * DOOMMAX fluff * Detective meow * Сеньёр помидор офицер * RSI validator su4ka * [Fluff] MR_Regari ghost tweak * [Fluff] svinka ghost tweak * [Fluff] Antohag gasmask fluff * ГАРПИИ (#559) * harpy initial * fix and some locale * ru locale * actions refactor shit * пофиксив гавпий~~~ пойду тестить~~ * hawpies are ready UwU * cweanup OwO * hawpies fixed a bit, still cant seawch them nya~ * hawpies can be stwipped now, fixie-dixie awwived~ * emotes fixie-dixied nya~ * говно * говно говна линтер соси * Automatic changelog update * [Tweak] Бумажная работа и фикс крафта пнв. (#560) * fix: персонал станции вспомнил как делать пнв * tweak: блюспейс технологии убраны у принтера документов * feat: заказ бумаги в карго * feat: бумажная дверь * Automatic changelog update * doommaxx-fluff nothing interesting * fluff skufa (#562) * [Sponsor] Fluff Forg (#567) * [Sponsor] Fluff Forg * fix size * sound * [Feat] TTS 15 new voices (#568) * Automatic changelog update * base server side * token check & base command response * panda base command response addition & some commands * web event, rest of commands & events * fix empty api --------- Co-authored-by: Cinkafox <70429757+Cinkafox@users.noreply.github.com> Co-authored-by: Aviu00 <93730715+Aviu00@users.noreply.github.com> Co-authored-by: RavmorganButOnCocaine Co-authored-by: Kotovskiy <77529717+wCATw@users.noreply.github.com> Co-authored-by: ThereDrD0 <88589686+ThereDrD0@users.noreply.github.com> Co-authored-by: Remuchi <72476615+Remuchi@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: KurokoTurbo <92106367+melanoTurbo@users.noreply.github.com> Co-authored-by: halicopter Co-authored-by: rhailrake <49613070+rhailrake@users.noreply.github.com> Co-authored-by: Subversionary <109166122+Subversionary@users.noreply.github.com> Co-authored-by: RavMorgan <48182970+RavMorgan@users.noreply.github.com> Co-authored-by: Mona Hmiza Co-authored-by: Valtos Co-authored-by: REBOLUTION228-a11 <128076300+REBOLUTION228-a11@users.noreply.github.com> Co-authored-by: PixelTK <85175107+PixelTheKermit@users.noreply.github.com> * newtonsoft version change * remove semaphore * remove double dooc * fix admin stealth * cleanup * remove utka sockets --------- Co-authored-by: Cinkafox <70429757+Cinkafox@users.noreply.github.com> Co-authored-by: Aviu00 <93730715+Aviu00@users.noreply.github.com> Co-authored-by: RavmorganButOnCocaine Co-authored-by: Kotovskiy <77529717+wCATw@users.noreply.github.com> Co-authored-by: ThereDrD0 <88589686+ThereDrD0@users.noreply.github.com> Co-authored-by: Remuchi <72476615+Remuchi@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: KurokoTurbo <92106367+melanoTurbo@users.noreply.github.com> Co-authored-by: halicopter Co-authored-by: rhailrake <49613070+rhailrake@users.noreply.github.com> Co-authored-by: Subversionary <109166122+Subversionary@users.noreply.github.com> Co-authored-by: RavMorgan <48182970+RavMorgan@users.noreply.github.com> Co-authored-by: Mona Hmiza Co-authored-by: Valtos Co-authored-by: REBOLUTION228-a11 <128076300+REBOLUTION228-a11@users.noreply.github.com> Co-authored-by: PixelTK <85175107+PixelTheKermit@users.noreply.github.com> --- .../Administration/Commands/BanCommand.cs | 9 +- .../Administration/Managers/BanManager.cs | 245 ++++++++++ .../Administration/Managers/IBanManager.cs | 11 + .../Administration/Managers/RoleBanManager.cs | 451 ------------------ .../Administration/Systems/BwoinkSystem.cs | 6 +- Content.Server/Chat/Managers/ChatManager.cs | 18 +- Content.Server/Chat/Systems/ChatSystem.cs | 6 +- Content.Server/Entry/EntryPoint.cs | 11 +- .../GameTicking/GameTicker.RoundFlow.cs | 9 +- Content.Server/IoC/ServerContentIoC.cs | 5 +- Content.Server/RoundEnd/RoundEndSystem.cs | 6 +- .../Systems/EmergencyShuttleSystem.cs | 6 +- .../UtkaIntegration/Commands/IUtkaCommand.cs | 11 - .../Commands/UtkaAssayCommand.cs | 29 -- .../Commands/UtkaAuthenticationCommand.cs | 57 --- .../UtkaIntegration/Commands/UtkaPmCommand.cs | 36 -- .../Commands/UtkaRestartRoundCommand.cs | 25 - .../Commands/UtkaSendOOCMessage.cs | 21 - .../UtkaIntegration/TCP/UtkaTCPServer.cs | 116 ----- .../UtkaIntegration/TCP/UtkaTCPSession.cs | 107 ----- .../UtkaIntegration/TCP/UtkaTCPWrapper.cs | 64 --- .../Commands/PandaAdminWhoCommand.cs} | 30 +- .../PandaSocket/Commands/PandaAsayCommand.cs | 29 ++ .../PandaSocket/Commands/PandaBanCommand.cs} | 31 +- .../Commands/PandaJobBanCommand.cs} | 23 +- .../PandaSocket/Commands/PandaPmCommand.cs | 48 ++ .../Commands/PandaRestartRoundCommand.cs | 31 ++ .../Commands/PandaSendOOCCommand.cs | 27 ++ .../Commands/PandaStatusCommand.cs} | 41 +- .../Commands/PandaUnJobBanCommand.cs} | 28 +- .../Commands/PandaUnbanCommand.cs} | 28 +- .../PandaSocket/Commands/PandaWhoCommand.cs} | 29 +- .../PandaSocket/Interfaces/IPandaCommand.cs | 11 + .../Interfaces/IPandaStatusHandlerContext.cs | 55 +++ .../PandaSocket/Main/PandaCommunication.cs} | 354 +++++++------- .../Main/PandaStatusHost.Handlers.cs | 58 +++ .../White/PandaSocket/Main/PandaStatusHost.cs | 403 ++++++++++++++++ .../White/PandaSocket/Main/PandaWebManager.cs | 53 ++ .../White/Reputation/ReputationSystem.cs | 3 +- Content.Shared/White/WhiteCVars.cs | 15 + 40 files changed, 1294 insertions(+), 1252 deletions(-) delete mode 100644 Content.Server/Administration/Managers/RoleBanManager.cs delete mode 100644 Content.Server/UtkaIntegration/Commands/IUtkaCommand.cs delete mode 100644 Content.Server/UtkaIntegration/Commands/UtkaAssayCommand.cs delete mode 100644 Content.Server/UtkaIntegration/Commands/UtkaAuthenticationCommand.cs delete mode 100644 Content.Server/UtkaIntegration/Commands/UtkaPmCommand.cs delete mode 100644 Content.Server/UtkaIntegration/Commands/UtkaRestartRoundCommand.cs delete mode 100644 Content.Server/UtkaIntegration/Commands/UtkaSendOOCMessage.cs delete mode 100644 Content.Server/UtkaIntegration/TCP/UtkaTCPServer.cs delete mode 100644 Content.Server/UtkaIntegration/TCP/UtkaTCPSession.cs delete mode 100644 Content.Server/UtkaIntegration/TCP/UtkaTCPWrapper.cs rename Content.Server/{UtkaIntegration/Commands/UtkaAdminWhoCommand.cs => White/PandaSocket/Commands/PandaAdminWhoCommand.cs} (52%) create mode 100644 Content.Server/White/PandaSocket/Commands/PandaAsayCommand.cs rename Content.Server/{UtkaIntegration/Commands/UtkaBanCommand.cs => White/PandaSocket/Commands/PandaBanCommand.cs} (81%) rename Content.Server/{UtkaIntegration/Commands/UtkaJobBanCommand.cs => White/PandaSocket/Commands/PandaJobBanCommand.cs} (53%) create mode 100644 Content.Server/White/PandaSocket/Commands/PandaPmCommand.cs create mode 100644 Content.Server/White/PandaSocket/Commands/PandaRestartRoundCommand.cs create mode 100644 Content.Server/White/PandaSocket/Commands/PandaSendOOCCommand.cs rename Content.Server/{UtkaIntegration/Commands/UtkaStatusCommand.cs => White/PandaSocket/Commands/PandaStatusCommand.cs} (71%) rename Content.Server/{UtkaIntegration/Commands/UtkaUnJobBanCommand.cs => White/PandaSocket/Commands/PandaUnJobBanCommand.cs} (62%) rename Content.Server/{UtkaIntegration/Commands/UtkaUnbanCommand.cs => White/PandaSocket/Commands/PandaUnbanCommand.cs} (62%) rename Content.Server/{UtkaIntegration/Commands/UtkaWhoCommand.cs => White/PandaSocket/Commands/PandaWhoCommand.cs} (55%) create mode 100644 Content.Server/White/PandaSocket/Interfaces/IPandaCommand.cs create mode 100644 Content.Server/White/PandaSocket/Interfaces/IPandaStatusHandlerContext.cs rename Content.Server/{UtkaIntegration/UtkaCommunication.cs => White/PandaSocket/Main/PandaCommunication.cs} (69%) create mode 100644 Content.Server/White/PandaSocket/Main/PandaStatusHost.Handlers.cs create mode 100644 Content.Server/White/PandaSocket/Main/PandaStatusHost.cs create mode 100644 Content.Server/White/PandaSocket/Main/PandaWebManager.cs diff --git a/Content.Server/Administration/Commands/BanCommand.cs b/Content.Server/Administration/Commands/BanCommand.cs index 1837b0005d..45040a5b21 100644 --- a/Content.Server/Administration/Commands/BanCommand.cs +++ b/Content.Server/Administration/Commands/BanCommand.cs @@ -1,10 +1,7 @@ using Content.Server.Administration.Managers; -using System.Net; -using System.Net.Sockets; -using System.Text; using Content.Server.Database; using Content.Server.GameTicking; -using Content.Server.UtkaIntegration; +using Content.Server.White.PandaSocket.Main; using Content.Shared.Administration; using Content.Shared.CCVar; using Content.Shared.Database; @@ -19,7 +16,7 @@ public sealed class BanCommand : LocalizedCommands [Dependency] private readonly IPlayerLocator _locator = default!; [Dependency] private readonly IBanManager _bans = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly UtkaTCPWrapper _utkaSockets = default!; // WD + [Dependency] private readonly PandaWebManager _pandaWeb = default!; // WD [Dependency] private readonly IEntityManager _entMan = default!; // WD public override string Command => "ban"; @@ -128,7 +125,7 @@ public sealed class BanCommand : LocalizedCommands Rid = EntitySystem.Get().RoundId, BanId = banId }; - _utkaSockets.SendMessageToAll(utkaBanned); + _pandaWeb.SendBotMessage(utkaBanned); _entMan.EventBus.RaiseEvent(EventSource.Local, utkaBanned); //WD end } diff --git a/Content.Server/Administration/Managers/BanManager.cs b/Content.Server/Administration/Managers/BanManager.cs index 218e7cc3e1..17f965ac2a 100644 --- a/Content.Server/Administration/Managers/BanManager.cs +++ b/Content.Server/Administration/Managers/BanManager.cs @@ -1,11 +1,15 @@ using System.Collections.Immutable; using System.Linq; using System.Net; +using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using Content.Server.Chat.Managers; using Content.Server.Database; using Content.Server.GameTicking; +using Content.Server.White; +using Content.Server.White.PandaSocket.Interfaces; +using Content.Server.White.PandaSocket.Main; using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.Players; @@ -32,6 +36,9 @@ public sealed class BanManager : IBanManager, IPostInjectInit [Dependency] private readonly IChatManager _chat = default!; [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly IPlayerLocator _playerLocator = default!; // WD + [Dependency] private readonly PandaWebManager _pandaWeb = default!; // WD + [Dependency] private readonly IEntityManager _entMan = default!; // WD private ISawmill _sawmill = default!; @@ -317,4 +324,242 @@ public sealed class BanManager : IBanManager, IPostInjectInit { _sawmill = _logManager.GetSawmill(SawmillId); } + + //WD start + public async void UtkaCreateDepartmentBan(string admin, string target, DepartmentPrototype department, string reason, uint minutes, bool isGlobalBan, + IPandaStatusHandlerContext context) + { + var located = await _playerLocator.LookupIdByNameOrIdAsync(target); + if (located == null) + { + UtkaSendResponse(false, context); + return; + } + + var targetUid = located.UserId; + var targetHWid = located.LastHWId; + var targetAddress = located.LastAddress; + + DateTimeOffset? expires = null; + if (minutes > 0) + { + expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes); + } + + (IPAddress, int)? addressRange = null; + if (targetAddress != null) + { + if (targetAddress.IsIPv4MappedToIPv6) + targetAddress = targetAddress.MapToIPv4(); + + // Ban /64 for IPv4, /32 for IPv4. + var cidr = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 64 : 32; + addressRange = (targetAddress, cidr); + } + + var cfg = UnsafePseudoIoC.ConfigurationManager; + var serverName = cfg.GetCVar(CCVars.AdminLogsServerName); + + if (isGlobalBan) + { + serverName = "unknown"; + } + + var locatedPlayer = await _playerLocator.LookupIdByNameOrIdAsync(admin); + if (locatedPlayer == null) + { + UtkaSendResponse(false, context); + return; + } + var player = locatedPlayer.UserId; + + UtkaSendResponse(true, context); + + _systems.TryGetEntitySystem(out var ticker); + int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId; + var playtime = (await _db.GetPlayTimes(targetUid)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero; + + foreach (var job in department.Roles) + { + var role = string.Concat(JobPrefix, job); + + var banDef = new ServerRoleBanDef( + null, + targetUid, + addressRange, + targetHWid, + DateTimeOffset.Now, + expires, + roundId, + playtime, + reason, + NoteSeverity.High, + player, + null, + role, + serverName); + + if (!await AddRoleBan(banDef)) + continue; + + var banId = await UtkaGetBanId(reason, role, targetUid); + + UtkaSendJobBanEvent(admin, target, minutes, job, isGlobalBan, reason, banId); + } + + SendRoleBans(located); + } + + public async void UtkaCreateJobBan(string admin, string target, string job, string reason, uint minutes, bool isGlobalBan, + IPandaStatusHandlerContext context) + { + if (!_prototypeManager.TryIndex(job, out _)) + { + UtkaSendResponse(false, context); + return; + } + + var role = string.Concat(JobPrefix, job); + + var located = await _playerLocator.LookupIdByNameOrIdAsync(target); + if (located == null) + { + UtkaSendResponse(false, context); + return; + } + + var targetUid = located.UserId; + var targetHWid = located.LastHWId; + var targetAddress = located.LastAddress; + + DateTimeOffset? expires = null; + if (minutes > 0) + { + expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes); + } + + (IPAddress, int)? addressRange = null; + if (targetAddress != null) + { + if (targetAddress.IsIPv4MappedToIPv6) + targetAddress = targetAddress.MapToIPv4(); + + // Ban /64 for IPv4, /32 for IPv4. + var cidr = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 64 : 32; + addressRange = (targetAddress, cidr); + } + + var cfg = UnsafePseudoIoC.ConfigurationManager; + var serverName = cfg.GetCVar(CCVars.AdminLogsServerName); + + if (isGlobalBan) + { + serverName = "unknown"; + } + + var locatedPlayer = await _playerLocator.LookupIdByNameOrIdAsync(admin); + if (locatedPlayer == null) + { + UtkaSendResponse(false, context); + return; + } + + _systems.TryGetEntitySystem(out var ticker); + int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId; + var playtime = (await _db.GetPlayTimes(targetUid)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero; + + var player = locatedPlayer.UserId; + var banDef = new ServerRoleBanDef( + null, + targetUid, + addressRange, + targetHWid, + DateTimeOffset.Now, + expires, + roundId, + playtime, + reason, + NoteSeverity.High, + player, + null, + role, + serverName); + + if (!await AddRoleBan(banDef)) + { + UtkaSendResponse(false, context); + return; + } + + var banId = await UtkaGetBanId(reason, role, targetUid); + + UtkaSendJobBanEvent(admin, target, minutes, job, isGlobalBan, reason, banId); + UtkaSendResponse(true, context); + + SendRoleBans(located); + } + + private void UtkaSendResponse(bool banned, IPandaStatusHandlerContext context) + { + var utkaBanned = new UtkaJobBanResponse() + { + Banned = banned + }; + + context.RespondJsonAsync(utkaBanned); + } + + private async void UtkaSendJobBanEvent(string ackey, string ckey, uint duration, string job, bool global, + string reason, int banId) + { + if (job.Contains("Job:")) + { + job = job.Replace("Job:", ""); + } + + var utkaBanned = new UtkaBannedEvent() + { + ACkey = ackey, + Ckey = ckey, + Duration = duration, + Bantype = job, + Global = global, + Reason = reason, + Rid = EntitySystem.Get().RoundId, + BanId = banId + }; + + _pandaWeb.SendBotMessage(utkaBanned); + _entMan.EventBus.RaiseEvent(EventSource.Local, utkaBanned); + } + + private async Task UtkaGetBanId(string reason, string role, NetUserId targetUid) + { + var banId = 0; + var banList = await _db.GetServerRoleBansAsync(null, targetUid, null); + + foreach (var ban in banList) + { + if (ban.Reason == reason) + { + if (ban.Role == role && ban.Id != null) + { + banId = ban.Id.Value; + } + } + } + + return banId; + } + + public void SendRoleBans(LocatedPlayerData located) + { + if (!_playerManager.TryGetSessionById(located.UserId, out var player)) + { + return; + } + + SendRoleBans(player); + } + //WD end } diff --git a/Content.Server/Administration/Managers/IBanManager.cs b/Content.Server/Administration/Managers/IBanManager.cs index a43dfb8b6c..2dbf219a8c 100644 --- a/Content.Server/Administration/Managers/IBanManager.cs +++ b/Content.Server/Administration/Managers/IBanManager.cs @@ -1,7 +1,9 @@ using System.Collections.Immutable; using System.Net; using System.Threading.Tasks; +using Content.Server.White.PandaSocket.Interfaces; using Content.Shared.Database; +using Content.Shared.Roles; using Robust.Shared.Network; using Robust.Shared.Player; @@ -56,4 +58,13 @@ public interface IBanManager /// /// Player's session public void SendRoleBans(ICommonSession pSession); + + // WD START + public void UtkaCreateDepartmentBan(string admin, string target, DepartmentPrototype department, + string reason, uint minutes, bool isGlobalBan, + IPandaStatusHandlerContext context); + + public void UtkaCreateJobBan(string admin, string target, string job, string reason, uint minutes, bool isGlobalBan, + IPandaStatusHandlerContext context); + // WD END } diff --git a/Content.Server/Administration/Managers/RoleBanManager.cs b/Content.Server/Administration/Managers/RoleBanManager.cs deleted file mode 100644 index 190991f31d..0000000000 --- a/Content.Server/Administration/Managers/RoleBanManager.cs +++ /dev/null @@ -1,451 +0,0 @@ -using System.Collections.Immutable; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading.Tasks; -using Content.Server.Database; -using Content.Server.GameTicking; -using Content.Server.UtkaIntegration; -using Content.Server.White; -using Content.Shared.CCVar; -using Content.Shared.Database; -using Content.Shared.Players.PlayTimeTracking; -using Content.Shared.Roles; -using Robust.Server.Player; -using Robust.Shared.Configuration; -using Robust.Shared.Console; -using Robust.Shared.Enums; -using Robust.Shared.Network; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; - -namespace Content.Server.Administration.Managers; - -public sealed class RoleBanManager -{ - [Dependency] private readonly IServerDbManager _db = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IPlayerLocator _playerLocator = default!; - [Dependency] private readonly UtkaTCPWrapper _utkaSockets = default!; // WD - [Dependency] private readonly IEntitySystemManager _systems = default!; // WD - [Dependency] private readonly IBanManager _banManager = default!; // WD - [Dependency] private readonly IEntityManager _entMan = default!; // WD - - private const string JobPrefix = "Job:"; - - private readonly Dictionary> _cachedRoleBans = new(); - - public void Initialize() - { - _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; - } - - private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) - { - if (e.NewStatus != SessionStatus.Connected - || _cachedRoleBans.ContainsKey(e.Session.UserId)) - return; - - var netChannel = e.Session.ConnectedClient; - await CacheDbRoleBans(e.Session.UserId, netChannel.RemoteEndPoint.Address, netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId); - } - - private async Task AddRoleBan(ServerRoleBanDef banDef) - { - if (banDef.UserId != null) - { - if (!_cachedRoleBans.TryGetValue(banDef.UserId.Value, out var roleBans)) - { - roleBans = new HashSet(); - _cachedRoleBans.Add(banDef.UserId.Value, roleBans); - } - if (!roleBans.Contains(banDef)) - roleBans.Add(banDef); - } - - await _db.AddServerRoleBanAsync(banDef); - return true; - } - - public void SendRoleBans(LocatedPlayerData located) - { - if (!_playerManager.TryGetSessionById(located.UserId, out var player)) - { - return; - } - - _banManager.SendRoleBans(player); - } - - public HashSet? GetRoleBans(NetUserId playerUserId) - { - return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) ? roleBans.Select(banDef => banDef.Role).ToHashSet() : null; - } - - private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray? hwId = null) - { - var roleBans = await _db.GetServerRoleBansAsync(address, userId, hwId, false); - - var userRoleBans = new HashSet(); - foreach (var ban in roleBans) - { - userRoleBans.Add(ban); - } - - _cachedRoleBans[userId] = userRoleBans; - } - - public void Restart() - { - // Clear out players that have disconnected. - var toRemove = new List(); - foreach (var player in _cachedRoleBans.Keys) - { - if (!_playerManager.TryGetSessionById(player, out _)) - toRemove.Add(player); - } - - foreach (var player in toRemove) - { - _cachedRoleBans.Remove(player); - } - - // Check for expired bans - foreach (var (_, roleBans) in _cachedRoleBans) - { - roleBans.RemoveWhere(ban => DateTimeOffset.Now > ban.ExpirationTime); - } - } - - #region Job Bans - public async void CreateJobBan(IConsoleShell shell, string target, string job, string reason, uint minutes, bool isGlobalBan) - { - if (!_prototypeManager.TryIndex(job, out JobPrototype? _)) - { - shell.WriteError(Loc.GetString("cmd-roleban-job-parse", ("job", job))); - return; - } - - var role = string.Concat(JobPrefix, job); // WD edit - CreateRoleBan(shell, target, role, reason, minutes, isGlobalBan, job); // WD edit - } - - //WD start - public async void UtkaCreateDepartmentBan(string admin, string target, DepartmentPrototype department, string reason, uint minutes, bool isGlobalBan) - { - var located = await _playerLocator.LookupIdByNameOrIdAsync(target); - if (located == null) - { - UtkaSendResponse(false); - return; - } - - var targetUid = located.UserId; - var targetHWid = located.LastHWId; - var targetAddress = located.LastAddress; - - DateTimeOffset? expires = null; - if (minutes > 0) - { - expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes); - } - - (IPAddress, int)? addressRange = null; - if (targetAddress != null) - { - if (targetAddress.IsIPv4MappedToIPv6) - targetAddress = targetAddress.MapToIPv4(); - - // Ban /64 for IPv4, /32 for IPv4. - var cidr = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 64 : 32; - addressRange = (targetAddress, cidr); - } - - var cfg = UnsafePseudoIoC.ConfigurationManager; - var serverName = cfg.GetCVar(CCVars.AdminLogsServerName); - - if (isGlobalBan) - { - serverName = "unknown"; - } - - var locatedPlayer = await _playerLocator.LookupIdByNameOrIdAsync(admin); - if (locatedPlayer == null) - { - UtkaSendResponse(false); - return; - } - var player = locatedPlayer.UserId; - - UtkaSendResponse(true); - - _systems.TryGetEntitySystem(out var ticker); - int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId; - var playtime = (await _db.GetPlayTimes(targetUid)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero; - - foreach (var job in department.Roles) - { - var role = string.Concat(JobPrefix, job); - - var banDef = new ServerRoleBanDef( - null, - targetUid, - addressRange, - targetHWid, - DateTimeOffset.Now, - expires, - roundId, - playtime, - reason, - NoteSeverity.High, - player, - null, - role, - serverName); - - if (!await AddRoleBan(banDef)) - continue; - - var banId = await UtkaGetBanId(reason, role, targetUid); - - UtkaSendJobBanEvent(admin, target, minutes, job, isGlobalBan, reason, banId); - } - - SendRoleBans(located); - } - - public async void UtkaCreateJobBan(string admin, string target, string job, string reason, uint minutes, bool isGlobalBan) - { - if (!_prototypeManager.TryIndex(job, out _)) - { - UtkaSendResponse(false); - return; - } - - var role = string.Concat(JobPrefix, job); - - var located = await _playerLocator.LookupIdByNameOrIdAsync(target); - if (located == null) - { - UtkaSendResponse(false); - return; - } - - var targetUid = located.UserId; - var targetHWid = located.LastHWId; - var targetAddress = located.LastAddress; - - DateTimeOffset? expires = null; - if (minutes > 0) - { - expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes); - } - - (IPAddress, int)? addressRange = null; - if (targetAddress != null) - { - if (targetAddress.IsIPv4MappedToIPv6) - targetAddress = targetAddress.MapToIPv4(); - - // Ban /64 for IPv4, /32 for IPv4. - var cidr = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 64 : 32; - addressRange = (targetAddress, cidr); - } - - var cfg = UnsafePseudoIoC.ConfigurationManager; - var serverName = cfg.GetCVar(CCVars.AdminLogsServerName); - - if (isGlobalBan) - { - serverName = "unknown"; - } - - var locatedPlayer = await _playerLocator.LookupIdByNameOrIdAsync(admin); - if (locatedPlayer == null) - { - UtkaSendResponse(false); - return; - } - - _systems.TryGetEntitySystem(out var ticker); - int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId; - var playtime = (await _db.GetPlayTimes(targetUid)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero; - - var player = locatedPlayer.UserId; - var banDef = new ServerRoleBanDef( - null, - targetUid, - addressRange, - targetHWid, - DateTimeOffset.Now, - expires, - roundId, - playtime, - reason, - NoteSeverity.High, - player, - null, - role, - serverName); - - if (!await AddRoleBan(banDef)) - { - UtkaSendResponse(false); - return; - } - - var banId = await UtkaGetBanId(reason, role, targetUid); - - UtkaSendJobBanEvent(admin, target, minutes, job, isGlobalBan, reason, banId); - UtkaSendResponse(true); - - SendRoleBans(located); - } - //WD end - - public HashSet? GetJobBans(NetUserId playerUserId) - { - if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans)) - return null; - return roleBans - .Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal)) - .Select(ban => ban.Role[JobPrefix.Length..]) - .ToHashSet(); - } - #endregion - - #region Commands - private async void CreateRoleBan(IConsoleShell shell, string target, string role, string reason, uint minutes, bool isGlobalBan, string? job = null) // WD edit - { - var located = await _playerLocator.LookupIdByNameOrIdAsync(target); - if (located == null) - { - shell.WriteError(Loc.GetString("cmd-roleban-name-parse")); - return; - } - - var targetUid = located.UserId; - var targetHWid = located.LastHWId; - var targetAddress = located.LastAddress; - - DateTimeOffset? expires = null; - if (minutes > 0) - { - expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes); - } - - (IPAddress, int)? addressRange = null; - if (targetAddress != null) - { - if (targetAddress.IsIPv4MappedToIPv6) - targetAddress = targetAddress.MapToIPv4(); - - // Ban /64 for IPv4, /32 for IPv4. - var cidr = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 64 : 32; - addressRange = (targetAddress, cidr); - } - - var cfg = UnsafePseudoIoC.ConfigurationManager; - var serverName = cfg.GetCVar(CCVars.AdminLogsServerName); - - if (isGlobalBan) - { - serverName = "unknown"; - } - - _systems.TryGetEntitySystem(out var ticker); - int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId; - var playtime = (await _db.GetPlayTimes(targetUid)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero; - - var player = shell.Player; - var banDef = new ServerRoleBanDef( - null, - targetUid, - addressRange, - targetHWid, - DateTimeOffset.Now, - expires, - roundId, - playtime, - reason, - NoteSeverity.High, - player?.UserId, - null, - role, - serverName); - - if (!await AddRoleBan(banDef)) - { - shell.WriteLine(Loc.GetString("cmd-roleban-existing", ("target", target), ("role", role))); - return; - } - - var length = expires == null ? Loc.GetString("cmd-roleban-inf") : Loc.GetString("cmd-roleban-until", ("expires", expires)); - shell.WriteLine(Loc.GetString("cmd-roleban-success", ("target", target), ("role", role), ("reason", reason), ("length", length), ("server", serverName))); - - // WD start - var banId = await UtkaGetBanId(reason, role, targetUid); - - if (job != null) - UtkaSendJobBanEvent(shell.Player!.Name, target, minutes, job, isGlobalBan, reason, banId); - //WD end - } - #endregion - - //WD start - private void UtkaSendResponse(bool banned) - { - var utkaBanned = new UtkaJobBanResponse() - { - Banned = banned - }; - - _utkaSockets.SendMessageToAll(utkaBanned); - } - - private async void UtkaSendJobBanEvent(string ackey, string ckey, uint duration, string job, bool global, - string reason, int banId) - { - if (job.Contains("Job:")) - { - job = job.Replace("Job:", ""); - } - - var utkaBanned = new UtkaBannedEvent() - { - ACkey = ackey, - Ckey = ckey, - Duration = duration, - Bantype = job, - Global = global, - Reason = reason, - Rid = EntitySystem.Get().RoundId, - BanId = banId - }; - - _utkaSockets.SendMessageToAll(utkaBanned); - _entMan.EventBus.RaiseEvent(EventSource.Local, utkaBanned); - } - - private async Task UtkaGetBanId(string reason, string role, NetUserId targetUid) - { - var banId = 0; - var banList = await _db.GetServerRoleBansAsync(null, targetUid, null); - - foreach (var ban in banList) - { - if (ban.Reason == reason) - { - if (ban.Role == role && ban.Id != null) - { - banId = ban.Id.Value; - } - } - } - - return banId; - } - //WD end -} diff --git a/Content.Server/Administration/Systems/BwoinkSystem.cs b/Content.Server/Administration/Systems/BwoinkSystem.cs index 7db7abbc00..028c267c5e 100644 --- a/Content.Server/Administration/Systems/BwoinkSystem.cs +++ b/Content.Server/Administration/Systems/BwoinkSystem.cs @@ -8,7 +8,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Content.Server.Administration.Managers; using Content.Server.GameTicking; -using Content.Server.UtkaIntegration; +using Content.Server.White.PandaSocket.Main; using Content.Shared.Administration; using Content.Shared.CCVar; using Content.Shared.Mind; @@ -34,7 +34,7 @@ namespace Content.Server.Administration.Systems [Dependency] private readonly IPlayerLocator _playerLocator = default!; [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly SharedMindSystem _minds = default!; - [Dependency] private readonly UtkaTCPWrapper _utkaSockets = default!; // WD + [Dependency] private readonly PandaWebManager _pandaWeb = default!; // WD private ISawmill _sawmill = default!; private readonly HttpClient _httpClient = new(); @@ -658,7 +658,7 @@ namespace Content.Server.Administration.Systems Entity = entity }; - _utkaSockets.SendMessageToAll(utkaAhelpEvent); + _pandaWeb.SendBotMessage(utkaAhelpEvent); } //WD-EDIT } diff --git a/Content.Server/Chat/Managers/ChatManager.cs b/Content.Server/Chat/Managers/ChatManager.cs index cc93bfd6d3..10f41aad79 100644 --- a/Content.Server/Chat/Managers/ChatManager.cs +++ b/Content.Server/Chat/Managers/ChatManager.cs @@ -6,9 +6,8 @@ using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.Administration.Systems; using Content.Server.MoMMI; -using Content.Server.Players; using Content.Server.Preferences.Managers; -using Content.Server.UtkaIntegration; +using Content.Server.White.PandaSocket.Main; using Content.Server.White.Reputation; using Content.Server.White.Sponsors; using Content.Shared.Administration; @@ -55,7 +54,7 @@ namespace Content.Server.Chat.Managers /// WD-EDIT [Dependency] private readonly SponsorsManager _sponsorsManager = default!; - [Dependency] private readonly UtkaTCPWrapper _utkaSocketWrapper = default!; + [Dependency] private readonly PandaWebManager _pandaWeb = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ReputationManager _repManager = default!; [Dependency] private readonly ITaskManager _taskManager = default!; @@ -210,15 +209,6 @@ namespace Content.Server.Chat.Managers ("playerName", sender), ("message", FormattedMessage.EscapeText(message))); ChatMessageToMany(ChatChannel.Admin, message, wrappedMessage, EntityUid.Invalid, false, false, admins.Select(p => p.ConnectedClient)); - - var asayEventMessage = new UtkaChatEventMessage() - { - Command = "asay", - Ckey = sender, - Message = message - }; - - _utkaSocketWrapper.SendMessageToAll(asayEventMessage); } public bool TrySendNewMessage(ICommonSession session, string newMessage, bool checkLength = false) @@ -348,7 +338,7 @@ namespace Content.Server.Chat.Managers Message = message, }; - _utkaSocketWrapper.SendMessageToAll(toUtkaMessage); + _pandaWeb.SendBotMessage(toUtkaMessage); //WD-EDIT } @@ -391,7 +381,7 @@ namespace Content.Server.Chat.Managers Message = message }; - _utkaSocketWrapper.SendMessageToAll(asayEventMessage); + _pandaWeb.SendBotMessage(asayEventMessage); //WD-EDIT } diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 7481f0d661..d2cf68e1ed 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -12,9 +12,9 @@ using Content.Server.Popups; using Content.Server.Speech.Components; using Content.Server.Station.Components; using Content.Server.Station.Systems; -using Content.Server.UtkaIntegration; using Content.Server.White.AspectsSystem.Aspects.Components; using Content.Server.White.Other.Speech; +using Content.Server.White.PandaSocket.Main; using Content.Shared.ActionBlocker; using Content.Shared.Administration; using Content.Shared.CCVar; @@ -67,7 +67,7 @@ public sealed partial class ChatSystem : SharedChatSystem [Dependency] private readonly GameTicker _gameTicker = default!; // WD //WD-EDIT - [Dependency] private readonly UtkaTCPWrapper _utkaSockets = default!; + [Dependency] private readonly PandaWebManager _pandaWeb = default!; //WD-EDIT public const int VoiceRange = 10; // how far voice goes in world units @@ -648,7 +648,7 @@ public sealed partial class ChatSystem : SharedChatSystem CharacterName = MetaData(source).EntityName }; - _utkaSockets.SendMessageToAll(utkaEmoteEvent); + _pandaWeb.SendBotMessage(utkaEmoteEvent); //WD-EDIT } diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index b0d61e6888..6e82c3829d 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -29,11 +29,10 @@ using Robust.Shared.ContentPack; using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Utility; -using Content.Server.UtkaIntegration; using Content.Server.White; using Content.Server.White.JoinQueue; using Content.Server.White.Jukebox; -using Content.Server.White.Reputation; +using Content.Server.White.PandaSocket.Main; using Content.Server.White.Sponsors; using Content.Server.White.Stalin; using Content.Server.White.TTS; @@ -119,6 +118,8 @@ namespace Content.Server.Entry IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); + IoCManager.Resolve().Start(); + IoCManager.Resolve().Initialize(); //WD-EDIT _voteManager.Initialize(); @@ -157,12 +158,6 @@ namespace Content.Server.Entry IoCManager.Resolve().Initialize(); IoCManager.Resolve().GetEntitySystem().PostInitialize(); IoCManager.Resolve().Initialize(); - - //WD-EDIT - IoCManager.Resolve().Initialize(); - UtkaTCPServer.RegisterCommands(); - //WD-EDIT - } } diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 62690f88d7..d759f2ee43 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -20,13 +20,10 @@ using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Utility; -using Content.Server.UtkaIntegration; -using System.Threading.Tasks; +using Content.Server.White.PandaSocket.Main; using Content.Server.White.Reputation; using Content.Server.White.Stalin; -using Content.Shared.Database; using Content.Shared.White; -using Robust.Shared.Asynchronous; namespace Content.Server.GameTicking { @@ -36,7 +33,7 @@ namespace Content.Server.GameTicking [Dependency] private readonly ITaskManager _taskManager = default!; //WD-EDIT - [Dependency] private readonly UtkaTCPWrapper _utkaSocketWrapper = default!; + [Dependency] private readonly PandaWebManager _pandaWeb = default!; [Dependency] private readonly StalinManager _stalinManager = default!; [Dependency] private readonly ReputationSystem _repSys = default!; //WD-EDIT @@ -570,7 +567,7 @@ namespace Content.Server.GameTicking Message = status }; - _utkaSocketWrapper.SendMessageToAll(utkaRoundStatusEvent); + _pandaWeb.SendBotMessage(utkaRoundStatusEvent); } diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index 67d67e61d5..9ef8179b7d 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -19,10 +19,10 @@ using Content.Server.ServerInfo; using Content.Server.ServerUpdates; using Content.Server.Voting.Managers; using Content.Server.Worldgen.Tools; -using Content.Server.UtkaIntegration; using Content.Server.White; using Content.Server.White.JoinQueue; using Content.Server.White.Jukebox; +using Content.Server.White.PandaSocket.Main; using Content.Server.White.Reputation; using Content.Server.White.Sponsors; using Content.Server.White.Stalin; @@ -71,12 +71,13 @@ namespace Content.Server.IoC // WD-EDIT IoCManager.Register(); IoCManager.Register(); - IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); + IoCManager.Register(); // WD-EDIT } } diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs index 618b9ba765..6798f2eae0 100644 --- a/Content.Server/RoundEnd/RoundEndSystem.cs +++ b/Content.Server/RoundEnd/RoundEndSystem.cs @@ -12,7 +12,7 @@ using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Systems; using Content.Server.Station.Components; using Content.Server.Station.Systems; -using Content.Server.UtkaIntegration; +using Content.Server.White.PandaSocket.Main; using Content.Shared.Database; using Content.Shared.GameTicking; using Robust.Shared.Audio.Systems; @@ -47,7 +47,7 @@ namespace Content.Server.RoundEnd [Dependency] private readonly StationSystem _stationSystem = default!; //WD-EDIT - [Dependency] private readonly UtkaTCPWrapper _utkaSocketWrapper = default!; + [Dependency] private readonly PandaWebManager _pandaWeb = default!; //WD-EDIT public TimeSpan DefaultCooldownDuration { get; set; } = TimeSpan.FromSeconds(30); @@ -307,7 +307,7 @@ namespace Content.Server.RoundEnd Message = status }; - _utkaSocketWrapper.SendMessageToAll(utkaRoundStatusEvent); + _pandaWeb.SendBotMessage(utkaRoundStatusEvent); } //WD-EDIT diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index 53744077bd..de569955e0 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -15,7 +15,7 @@ using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Events; using Content.Server.Station.Components; using Content.Server.Station.Systems; -using Content.Server.UtkaIntegration; +using Content.Server.White.PandaSocket.Main; using Content.Shared.Access.Systems; using Content.Shared.CCVar; using Content.Shared.Database; @@ -64,7 +64,7 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; //WD-EDIT - [Dependency] private readonly UtkaTCPWrapper _utkaSocketWrapper = default!; + [Dependency] private readonly PandaWebManager _pandaWeb = default!; //WD-EDIT private ISawmill _sawmill = default!; @@ -584,6 +584,6 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem Message = status }; - _utkaSocketWrapper.SendMessageToAll(utkaRoundStatusEvent); + _pandaWeb.SendBotMessage(utkaRoundStatusEvent); } } diff --git a/Content.Server/UtkaIntegration/Commands/IUtkaCommand.cs b/Content.Server/UtkaIntegration/Commands/IUtkaCommand.cs deleted file mode 100644 index fd69e5f725..0000000000 --- a/Content.Server/UtkaIntegration/Commands/IUtkaCommand.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Net; -using Content.Server.UtkaIntegration.TCP; - -namespace Content.Server.UtkaIntegration; - -public interface IUtkaCommand -{ - string Name { get; } - Type RequestMessageType { get; } - public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage); -} diff --git a/Content.Server/UtkaIntegration/Commands/UtkaAssayCommand.cs b/Content.Server/UtkaIntegration/Commands/UtkaAssayCommand.cs deleted file mode 100644 index 22c32f2ab2..0000000000 --- a/Content.Server/UtkaIntegration/Commands/UtkaAssayCommand.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Linq; -using System.Net; -using Content.Server.Administration.Managers; -using Content.Server.Chat.Managers; -using Content.Server.UtkaIntegration.TCP; -using Content.Shared.Chat; -using Robust.Shared.Utility; - -namespace Content.Server.UtkaIntegration; - -public sealed class UtkaAssayCommand : IUtkaCommand -{ - public string Name => "asay"; - public Type RequestMessageType => typeof(UtkaAsayRequest); - - public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage) - { - if(baseMessage is not UtkaAsayRequest message) return; - - var ckey = message.ACkey; - - if(string.IsNullOrWhiteSpace(message.Message) || string.IsNullOrWhiteSpace(ckey)) return; - - var chatManager = IoCManager.Resolve(); - - chatManager.SendHookAdminChat(ckey, message.Message); - - } -} diff --git a/Content.Server/UtkaIntegration/Commands/UtkaAuthenticationCommand.cs b/Content.Server/UtkaIntegration/Commands/UtkaAuthenticationCommand.cs deleted file mode 100644 index 6ccf916787..0000000000 --- a/Content.Server/UtkaIntegration/Commands/UtkaAuthenticationCommand.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Content.Server.UtkaIntegration.TCP; -using Content.Shared.CCVar; -using Content.Shared.White; -using Robust.Shared.Configuration; - -namespace Content.Server.UtkaIntegration; - -public sealed class UtkaAuthenticationCommand : IUtkaCommand -{ - [Dependency] private readonly IConfigurationManager _configurationManager = default!; - [Dependency] private readonly UtkaTCPWrapper _utkaTcpWrapper = default!; - - public string Name => "handshake"; - public Type RequestMessageType => typeof(UtkaHandshakeMessage); - - public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage) - { - if (baseMessage is not UtkaHandshakeMessage message) - return; - - IoCManager.InjectDependencies(this); - - if (string.IsNullOrWhiteSpace(message.Key)) - { - SendMessage(session, "key_missmatch"); - return; - } - - var key = _configurationManager.GetCVar(WhiteCVars.UtkaSocketKey); - - if (key != message.Key) - { - SendMessage(session, "key_missmatch"); - return; - } - - if (session.Authenticated) - { - SendMessage(session, "already_authentificated"); - return; - } - - session.Authenticated = true; - SendMessage(session, "handshake_accepted"); - } - - private void SendMessage(UtkaTCPSession session, string message) - { - var response = new UtkaHandshakeMessage() - { - Key = _configurationManager.GetCVar(WhiteCVars.UtkaSocketKey), - Message = message - }; - - _utkaTcpWrapper.SendMessageToClient(session, response); - } -} diff --git a/Content.Server/UtkaIntegration/Commands/UtkaPmCommand.cs b/Content.Server/UtkaIntegration/Commands/UtkaPmCommand.cs deleted file mode 100644 index a52a3edf32..0000000000 --- a/Content.Server/UtkaIntegration/Commands/UtkaPmCommand.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Content.Server.Administration.Systems; -using Content.Server.UtkaIntegration.TCP; -using Robust.Server.Player; - -namespace Content.Server.UtkaIntegration; - -public sealed class UtkaPmCommand : IUtkaCommand -{ - public string Name => "discord_pm"; - public Type RequestMessageType => typeof(UtkaPmRequest); - - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private UtkaTCPWrapper _utkaSocketWrapper = default!; - - public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage) - { - if(baseMessage is not UtkaPmRequest message) return; - var _bwoink = EntitySystem.Get(); - IoCManager.InjectDependencies(this); - - if(string.IsNullOrWhiteSpace(message.Message) || string.IsNullOrWhiteSpace(message.Sender) || string.IsNullOrWhiteSpace(message.Reciever)) return; - - var toUtkaMessage = new UtkaPmResponse(); - if (!_playerManager.TryGetUserId(message.Reciever, out var reciever)) - { - toUtkaMessage.Message = false; - _utkaSocketWrapper.SendMessageToAll(toUtkaMessage); - return; - } - - _bwoink.SendUtkaBwoinkMessage(reciever, message.Sender, message.Message); - - toUtkaMessage.Message = true; - _utkaSocketWrapper.SendMessageToAll(toUtkaMessage); - } -} diff --git a/Content.Server/UtkaIntegration/Commands/UtkaRestartRoundCommand.cs b/Content.Server/UtkaIntegration/Commands/UtkaRestartRoundCommand.cs deleted file mode 100644 index 7a12349787..0000000000 --- a/Content.Server/UtkaIntegration/Commands/UtkaRestartRoundCommand.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Content.Server.GameTicking; -using Content.Server.UtkaIntegration.TCP; - -namespace Content.Server.UtkaIntegration; - -public sealed class UtkaRestartRoundCommand : IUtkaCommand -{ - [Dependency] private UtkaTCPWrapper _utkaSocket = default!; - - public string Name => "restart_round"; - public Type RequestMessageType => typeof(UtkaRestartRoundRequest); - public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage) - { - IoCManager.InjectDependencies(this); - - EntitySystem.Get().RestartRound(); - - var response = new UtkaRestartRoundResponse() - { - Restarted = true - }; - - _utkaSocket.SendMessageToAll(response); - } -} diff --git a/Content.Server/UtkaIntegration/Commands/UtkaSendOOCMessage.cs b/Content.Server/UtkaIntegration/Commands/UtkaSendOOCMessage.cs deleted file mode 100644 index c1923391b4..0000000000 --- a/Content.Server/UtkaIntegration/Commands/UtkaSendOOCMessage.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Net; -using Content.Server.Chat.Managers; -using Content.Server.UtkaIntegration.TCP; - -namespace Content.Server.UtkaIntegration; - -public sealed class UtkaSendOOCMessage : IUtkaCommand -{ - public string Name => "ooc"; - public Type RequestMessageType => typeof(UtkaOOCRequest); - - public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage) - { - if (baseMessage is not UtkaOOCRequest message) return; - if(string.IsNullOrWhiteSpace(message.Message) || string.IsNullOrWhiteSpace(message.CKey)) return; - - - var chatSystem = IoCManager.Resolve(); - chatSystem.SendHookOOC($"{message.CKey}", $"{message.Message}"); - } -} diff --git a/Content.Server/UtkaIntegration/TCP/UtkaTCPServer.cs b/Content.Server/UtkaIntegration/TCP/UtkaTCPServer.cs deleted file mode 100644 index 75b512016d..0000000000 --- a/Content.Server/UtkaIntegration/TCP/UtkaTCPServer.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System.Collections.Concurrent; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Reflection; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Content.Server.UtkaIntegration.TCP; -using Content.Shared.CCVar; -using Content.Shared.White; -using NetCoreServer; -using Robust.Shared.Asynchronous; -using Robust.Shared.Configuration; -using Robust.Shared.Timing; -using YamlDotNet.Core.Tokens; -using Timer = Robust.Shared.Timing.Timer; - -namespace Content.Server.UtkaIntegration; - -public sealed class UtkaTCPServer : TcpServer -{ - - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly ITaskManager _taskManager = default!; - [Dependency] private readonly ITimerManager _timerManager = default!; - - public static readonly ConcurrentDictionary Commands = new(); - private List _authenticatedSessions = new(); - - private string? _key; - - protected override TcpSession CreateSession() { return new UtkaTCPSession(this); } - public UtkaTCPServer(IPAddress address, int port) : base(address, port) - { - IoCManager.InjectDependencies(this); - _cfg.OnValueChanged(WhiteCVars.UtkaSocketKey, key => _key = key, true); - OptionKeepAlive = true; - } - - public void SendMessageToAll(UtkaBaseMessage message) - { - foreach (var session in Sessions.Values.Cast()) - { - if(!session.Authenticated) continue; - - var json = JsonSerializer.Serialize(message, message.GetType()); - - session.SendAsync(json + "&%^sep^%&"); - } - } - - public void SendMessageToClient(UtkaTCPSession session, UtkaBaseMessage message) - { - session.SendAsync(JsonSerializer.Serialize(message, message.GetType()) + "&%^sep^%&"); - } - - protected override void OnConnected(TcpSession session) - { - var utkaSession = (UtkaTCPSession) session; - var cancellationToken = new CancellationTokenSource(); - - utkaSession.OnMessageReceived += (sender, message) => - { - ExecuteCommand(utkaSession, message); - }; - - var autoDisconnectionTimer = new Timer(25000, false, () => - { - if (!utkaSession.Authenticated) - { - utkaSession.Disconnect(); - } - }); - - _timerManager.AddTimer(autoDisconnectionTimer, cancellationToken.Token); - } - - protected override void OnDisconnecting(TcpSession session) - { - _authenticatedSessions.Remove((session as UtkaTCPSession)!); - base.OnDisconnecting(session); - } - - protected override void OnError(SocketError error) - { - } - private void ExecuteCommand(UtkaTCPSession session, UtkaBaseMessage fromUtkaMessage) - { - var command = fromUtkaMessage.Command!; - - if (!Commands.ContainsKey(command)) - { - return; - } - - _taskManager.RunOnMainThread(() => Commands[command].Execute(session, fromUtkaMessage)); - } - - public static void RegisterCommands() - { - var assembly = Assembly.GetExecutingAssembly(); - var types = assembly.GetTypes(); - - var commands = types.Where(type => typeof(IUtkaCommand).IsAssignableFrom(type) && type.GetInterfaces().Contains(typeof(IUtkaCommand))).ToList(); - - foreach (var command in commands) - { - if (Activator.CreateInstance(command) is IUtkaCommand utkaCommand) - { - Commands[utkaCommand.Name] = utkaCommand; - } - } - } -} diff --git a/Content.Server/UtkaIntegration/TCP/UtkaTCPSession.cs b/Content.Server/UtkaIntegration/TCP/UtkaTCPSession.cs deleted file mode 100644 index 3938996018..0000000000 --- a/Content.Server/UtkaIntegration/TCP/UtkaTCPSession.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Linq; -using System.Net.Sockets; -using System.Text; -using System.Text.Json; -using NetCoreServer; -using Newtonsoft.Json.Linq; -using System.Text.RegularExpressions; - -namespace Content.Server.UtkaIntegration.TCP; - -public sealed class UtkaTCPSession : TcpSession -{ - public event EventHandler? OnMessageReceived; - private string BufferCahce = string.Empty; - - public bool Authenticated { get; set; } - - public UtkaTCPSession(TcpServer server) : base(server) - { - } - - protected override void OnReceived(byte[] buffer, long offset, long size) - { - BufferCahce += Encoding.UTF8.GetString(buffer, (int) offset, (int) size); - - HandleCache(); - } - - protected override void OnError(SocketError error) - { - SendAsync($"{error.ToString()}&%^sep^%&"); - base.OnError(error); - } - - protected override void OnConnected() - { - SendAsync("Utka sosal handshake&%^sep^%&"); - base.OnConnected(); - } - - private bool ValidateMessage(string message, out UtkaBaseMessage? fromDiscordMessage) - { - fromDiscordMessage = null; - - if (string.IsNullOrEmpty(message)) - { - return false; - } - - var commandName = JObject.Parse(message)["command"]; - if (commandName == null) - return false; - - var utkaCommand = UtkaTCPServer.Commands.Values.FirstOrDefault(x => x.Name == commandName.ToString()); - - if (utkaCommand == null) - return false; - - var messageType = utkaCommand.RequestMessageType; - - try - { - fromDiscordMessage = JsonSerializer.Deserialize(message, messageType) as UtkaBaseMessage; - } - catch (Exception e) - { - return false; - } - - return true; - } - - protected override void OnDisconnected() - { - OnDisconnecting(); - Dispose(); - BufferCahce = string.Empty; - } - - private void HandleCache() - { - var handles = BufferCahce.Split("&%^sep^%&"); - - for (var i = 0; i < handles.Length; i++) - { - var handle = handles[i]; - - if (i + 1 == handles.Length && !BufferCahce.EndsWith("&%^sep^%&")) - continue; - - if (handle.Length == 0 || !handle.StartsWith("{") || !handle.EndsWith("}")) - continue; - - var pos = BufferCahce.IndexOf(handle); - - BufferCahce = BufferCahce.Substring(0, pos) + BufferCahce.Substring(pos + handle.Length + "&%^sep^%&".Length); - - if (!ValidateMessage(handle, out var message)) - { - SendAsync("Validation fail&%^sep^%&"); - return; - } - - OnMessageReceived?.Invoke(this, message!); - } - } -} diff --git a/Content.Server/UtkaIntegration/TCP/UtkaTCPWrapper.cs b/Content.Server/UtkaIntegration/TCP/UtkaTCPWrapper.cs deleted file mode 100644 index f41e2f5499..0000000000 --- a/Content.Server/UtkaIntegration/TCP/UtkaTCPWrapper.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Net; -using Content.Server.UtkaIntegration.TCP; -using Content.Shared.CCVar; -using Content.Shared.White; -using Robust.Shared; -using Robust.Shared.Configuration; - -namespace Content.Server.UtkaIntegration; - -public sealed class UtkaTCPWrapper -{ - [Dependency] private readonly IConfigurationManager _cfg = default!; - - private UtkaTCPServer _server = default!; - private string _key = string.Empty; - - private bool _initialized; - - public void Initialize() - { - if(_initialized) return; - - _key = _cfg.GetCVar(WhiteCVars.UtkaSocketKey); - - if (string.IsNullOrEmpty(_key)) - { - return; - } - - var port = _cfg.GetCVar(CVars.NetPort) + 100; - - try - { - _server = new UtkaTCPServer(IPAddress.Any, port); - _server.Start(); - _initialized = true; - - } - catch (Exception e) - { - _initialized = false; - return; - } - - } - - public void SendMessageToAll(UtkaBaseMessage message) - { - _server.SendMessageToAll(message); - } - - public void SendMessageToClient(UtkaTCPSession session, UtkaBaseMessage message) - { - _server.SendMessageToClient(session, message); - } - - public void Shutdown() - { - _server.Stop(); - _server.Multicast("Server shutting down."); - _server.DisconnectAll(); - _server.Dispose(); - } -} diff --git a/Content.Server/UtkaIntegration/Commands/UtkaAdminWhoCommand.cs b/Content.Server/White/PandaSocket/Commands/PandaAdminWhoCommand.cs similarity index 52% rename from Content.Server/UtkaIntegration/Commands/UtkaAdminWhoCommand.cs rename to Content.Server/White/PandaSocket/Commands/PandaAdminWhoCommand.cs index bb3cacc58d..229e44aa66 100644 --- a/Content.Server/UtkaIntegration/Commands/UtkaAdminWhoCommand.cs +++ b/Content.Server/White/PandaSocket/Commands/PandaAdminWhoCommand.cs @@ -1,23 +1,15 @@ using System.Linq; -using System.Net; -using System.Text; -using System.Text.Json; using Content.Server.Administration.Managers; -using Content.Server.UtkaIntegration.TCP; -using Content.Shared.CCVar; -using Robust.Shared; -using Robust.Shared.Configuration; +using Content.Server.White.PandaSocket.Interfaces; +using Content.Server.White.PandaSocket.Main; -namespace Content.Server.UtkaIntegration; +namespace Content.Server.White.PandaSocket.Commands; -public sealed class UtkaAdminWhoCommand : IUtkaCommand +public sealed class PandaAdminWhoCommand : IPandaCommand { public string Name => "adminwho"; public Type RequestMessageType => typeof(UtkaAdminWhoRequest); - - [Dependency] private readonly UtkaTCPWrapper _utkaSocketWrapper = default!; - - public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage) + public void Execute(IPandaStatusHandlerContext context, PandaBaseMessage baseMessage) { if(baseMessage is not UtkaAdminWhoRequest message) return; IoCManager.InjectDependencies(this); @@ -30,6 +22,11 @@ public sealed class UtkaAdminWhoCommand : IUtkaCommand foreach (var admin in admins) { + var adminData = adminManager.GetAdminData(admin)!; + + if (adminData.Stealth) + continue; + adminsList.Add(admin.Name); } @@ -38,6 +35,11 @@ public sealed class UtkaAdminWhoCommand : IUtkaCommand Admins = adminsList }; - _utkaSocketWrapper.SendMessageToAll(toUtkaMessage); + Response(context, toUtkaMessage); + } + + public void Response(IPandaStatusHandlerContext context, PandaBaseMessage? message = null) + { + context.RespondJsonAsync(message!); } } diff --git a/Content.Server/White/PandaSocket/Commands/PandaAsayCommand.cs b/Content.Server/White/PandaSocket/Commands/PandaAsayCommand.cs new file mode 100644 index 0000000000..60996058bd --- /dev/null +++ b/Content.Server/White/PandaSocket/Commands/PandaAsayCommand.cs @@ -0,0 +1,29 @@ +using System.Net; +using Content.Server.Chat.Managers; +using Content.Server.White.PandaSocket.Interfaces; +using Content.Server.White.PandaSocket.Main; + +namespace Content.Server.White.PandaSocket.Commands; + +public sealed class PandaAsayCommand : IPandaCommand +{ + public string Name => "asay"; + public Type RequestMessageType => typeof(UtkaAsayRequest); + public void Execute(IPandaStatusHandlerContext context, PandaBaseMessage baseMessage) + { + if (baseMessage is not UtkaAsayRequest message) return; + if(string.IsNullOrWhiteSpace(message.Message) || string.IsNullOrWhiteSpace(message.ACkey)) return; + + var ckey = message.ACkey; + var chatManager = IoCManager.Resolve(); + + chatManager.SendHookAdminChat(ckey, message.Message); + + Response(context); + } + + public void Response(IPandaStatusHandlerContext context, PandaBaseMessage? message = null) + { + context.RespondAsync("Success", HttpStatusCode.OK); + } +} diff --git a/Content.Server/UtkaIntegration/Commands/UtkaBanCommand.cs b/Content.Server/White/PandaSocket/Commands/PandaBanCommand.cs similarity index 81% rename from Content.Server/UtkaIntegration/Commands/UtkaBanCommand.cs rename to Content.Server/White/PandaSocket/Commands/PandaBanCommand.cs index 85478a0af4..2bfe08c7db 100644 --- a/Content.Server/UtkaIntegration/Commands/UtkaBanCommand.cs +++ b/Content.Server/White/PandaSocket/Commands/PandaBanCommand.cs @@ -3,27 +3,28 @@ using System.Net.Sockets; using Content.Server.Administration; using Content.Server.Database; using Content.Server.GameTicking; -using Content.Server.UtkaIntegration.TCP; +using Content.Server.White.PandaSocket.Interfaces; +using Content.Server.White.PandaSocket.Main; using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.Players.PlayTimeTracking; using Robust.Server.Player; using Robust.Shared.Configuration; -namespace Content.Server.UtkaIntegration; +namespace Content.Server.White.PandaSocket.Commands; -public sealed class UtkaBanCommand : IUtkaCommand +public sealed class PandaBanCommand : IPandaCommand { [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private UtkaTCPWrapper _utkaSocketWrapper = default!; [Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly PandaWebManager _pandaWeb = default!; private const ILocalizationManager LocalizationManager = default!; public string Name => "ban"; public Type RequestMessageType => typeof(UtkaBanRequest); - public async void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage) + public async void Execute(IPandaStatusHandlerContext context, PandaBaseMessage baseMessage) { if (baseMessage is not UtkaBanRequest message) return; @@ -34,7 +35,7 @@ public sealed class UtkaBanCommand : IUtkaCommand var locatedPlayer = await locator.LookupIdByNameOrIdAsync(message.ACkey!); if (locatedPlayer == null) { - UtkaSendResponse(false); + UtkaSendResponse(context, false); return; } @@ -48,7 +49,7 @@ public sealed class UtkaBanCommand : IUtkaCommand var located = await locator.LookupIdByNameOrIdAsync(target); if (located == null) { - UtkaSendResponse(false); + UtkaSendResponse(context, false); return; } @@ -58,7 +59,7 @@ public sealed class UtkaBanCommand : IUtkaCommand if (player == targetUid) { - UtkaSendResponse(false); + UtkaSendResponse(context, false); return; } @@ -105,7 +106,7 @@ public sealed class UtkaBanCommand : IUtkaCommand null, serverName); - UtkaSendResponse(true); + UtkaSendResponse(context, true); await _db.AddServerBanAsync(banDef); @@ -129,17 +130,23 @@ public sealed class UtkaBanCommand : IUtkaCommand Rid = EntitySystem.Get().RoundId, BanId = banId }; - _utkaSocketWrapper.SendMessageToAll(utkaBanned); + + _pandaWeb.SendBotMessage(utkaBanned); _entMan.EventBus.RaiseEvent(EventSource.Local, utkaBanned); } - private void UtkaSendResponse(bool banned) + public void Response(IPandaStatusHandlerContext context, PandaBaseMessage? message = null) + { + context.RespondJsonAsync(message!); + } + + private void UtkaSendResponse(IPandaStatusHandlerContext context, bool banned) { var utkaResponse = new UtkaBanResponse() { Banned = banned }; - _utkaSocketWrapper.SendMessageToAll(utkaResponse); + Response(context, utkaResponse); } } diff --git a/Content.Server/UtkaIntegration/Commands/UtkaJobBanCommand.cs b/Content.Server/White/PandaSocket/Commands/PandaJobBanCommand.cs similarity index 53% rename from Content.Server/UtkaIntegration/Commands/UtkaJobBanCommand.cs rename to Content.Server/White/PandaSocket/Commands/PandaJobBanCommand.cs index 37efbcf0c4..cdc51bce77 100644 --- a/Content.Server/UtkaIntegration/Commands/UtkaJobBanCommand.cs +++ b/Content.Server/White/PandaSocket/Commands/PandaJobBanCommand.cs @@ -1,21 +1,22 @@ using Content.Server.Administration.Managers; -using Content.Server.UtkaIntegration.TCP; +using Content.Server.White.PandaSocket.Interfaces; +using Content.Server.White.PandaSocket.Main; using Content.Shared.Roles; using Robust.Shared.Prototypes; -namespace Content.Server.UtkaIntegration; +namespace Content.Server.White.PandaSocket.Commands; -public sealed class UtkaJobBanCommand : IUtkaCommand +public sealed class PandaJobBanCommand : IPandaCommand { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; public string Name => "jobban"; public Type RequestMessageType => typeof(UtkaJobBanRequest); - public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage) + public void Execute(IPandaStatusHandlerContext context, PandaBaseMessage baseMessage) { + if (baseMessage is not UtkaJobBanRequest message) return; IoCManager.InjectDependencies(this); - if (baseMessage is not UtkaJobBanRequest message) return; var target = message.Ckey!; var job = message.Type!; var reason = message.Reason!; @@ -23,14 +24,16 @@ public sealed class UtkaJobBanCommand : IUtkaCommand var isGlobalBan = (bool) message.Global!; var admin = message.ACkey!; - var roleBanManager = IoCManager.Resolve(); + var banManager = IoCManager.Resolve(); if (_prototypeManager.TryIndex(job, out var departmentProto)) - roleBanManager.UtkaCreateDepartmentBan(admin, target, departmentProto, reason, minutes, isGlobalBan); + banManager.UtkaCreateDepartmentBan(admin, target, departmentProto, reason, minutes, isGlobalBan, context); else - roleBanManager.UtkaCreateJobBan(admin, target, job, reason, minutes, isGlobalBan); + banManager.UtkaCreateJobBan(admin, target, job, reason, minutes, isGlobalBan, context); + } + + public void Response(IPandaStatusHandlerContext context, PandaBaseMessage? message = null) + { } } - - diff --git a/Content.Server/White/PandaSocket/Commands/PandaPmCommand.cs b/Content.Server/White/PandaSocket/Commands/PandaPmCommand.cs new file mode 100644 index 0000000000..4d203507f7 --- /dev/null +++ b/Content.Server/White/PandaSocket/Commands/PandaPmCommand.cs @@ -0,0 +1,48 @@ +using System.Net; +using Content.Server.Administration.Systems; +using Content.Server.White.PandaSocket.Interfaces; +using Content.Server.White.PandaSocket.Main; +using Robust.Server.Player; + +namespace Content.Server.White.PandaSocket.Commands; + +public sealed class PandaPmCommand : IPandaCommand +{ + [Dependency] private readonly IPlayerManager _playerManager = default!; + + public string Name => "discordpm"; + public Type RequestMessageType => typeof(UtkaPmRequest); + public void Execute(IPandaStatusHandlerContext context, PandaBaseMessage baseMessage) + { + if (baseMessage is not UtkaPmRequest message) return; + var _bwoink = EntitySystem.Get(); + IoCManager.InjectDependencies(this); + + var toUtkaMessage = new UtkaPmResponse(); + + if(string.IsNullOrWhiteSpace(message.Message) || string.IsNullOrWhiteSpace(message.Sender) || string.IsNullOrWhiteSpace(message.Receiver)) + { + toUtkaMessage.Message = false; + Response(context, toUtkaMessage); + return; + } + + if (!_playerManager.TryGetUserId(message.Receiver, out var reciever)) + { + toUtkaMessage.Message = false; + Response(context, toUtkaMessage); + return; + } + + _bwoink.SendUtkaBwoinkMessage(reciever, message.Sender, message.Message); + + toUtkaMessage.Message = true; + + Response(context, toUtkaMessage); + } + + public void Response(IPandaStatusHandlerContext context, PandaBaseMessage? message = null) + { + context.RespondJsonAsync(message!); + } +} diff --git a/Content.Server/White/PandaSocket/Commands/PandaRestartRoundCommand.cs b/Content.Server/White/PandaSocket/Commands/PandaRestartRoundCommand.cs new file mode 100644 index 0000000000..0eab97542f --- /dev/null +++ b/Content.Server/White/PandaSocket/Commands/PandaRestartRoundCommand.cs @@ -0,0 +1,31 @@ +using Content.Server.GameTicking; +using Content.Server.White.PandaSocket.Interfaces; +using Content.Server.White.PandaSocket.Main; + +namespace Content.Server.White.PandaSocket.Commands; + +public sealed class PandaRestartRoundCommand : IPandaCommand +{ + public string Name => "restartround"; + public Type RequestMessageType => typeof(UtkaRestartRoundRequest); + public void Execute(IPandaStatusHandlerContext context, PandaBaseMessage baseMessage) + { + if (baseMessage is not UtkaRestartRoundRequest message) return; + + IoCManager.InjectDependencies(this); + + EntitySystem.Get().RestartRound(); + + var response = new UtkaRestartRoundResponse() + { + Restarted = true + }; + + Response(context, response); + } + + public void Response(IPandaStatusHandlerContext context, PandaBaseMessage? message = null) + { + context.RespondJsonAsync(message!); + } +} diff --git a/Content.Server/White/PandaSocket/Commands/PandaSendOOCCommand.cs b/Content.Server/White/PandaSocket/Commands/PandaSendOOCCommand.cs new file mode 100644 index 0000000000..3ab5adbeea --- /dev/null +++ b/Content.Server/White/PandaSocket/Commands/PandaSendOOCCommand.cs @@ -0,0 +1,27 @@ +using System.Net; +using Content.Server.Chat.Managers; +using Content.Server.White.PandaSocket.Interfaces; +using Content.Server.White.PandaSocket.Main; + +namespace Content.Server.White.PandaSocket.Commands; + +public sealed class PandaSendOOCCommand : IPandaCommand +{ + public string Name => "ooc"; + public Type RequestMessageType => typeof(UtkaOOCRequest); + public async void Execute(IPandaStatusHandlerContext context, PandaBaseMessage baseMessage) + { + if (baseMessage is not UtkaOOCRequest message) return; + if(string.IsNullOrWhiteSpace(message.Message) || string.IsNullOrWhiteSpace(message.CKey)) return; + + var chatSystem = IoCManager.Resolve(); + chatSystem.SendHookOOC($"{message.CKey}", $"{message.Message}"); + + Response(context); + } + + public async void Response(IPandaStatusHandlerContext context, PandaBaseMessage? message = null) + { + await context.RespondAsync("Success", HttpStatusCode.OK); + } +} diff --git a/Content.Server/UtkaIntegration/Commands/UtkaStatusCommand.cs b/Content.Server/White/PandaSocket/Commands/PandaStatusCommand.cs similarity index 71% rename from Content.Server/UtkaIntegration/Commands/UtkaStatusCommand.cs rename to Content.Server/White/PandaSocket/Commands/PandaStatusCommand.cs index bc0c84436e..db93272e30 100644 --- a/Content.Server/UtkaIntegration/Commands/UtkaStatusCommand.cs +++ b/Content.Server/White/PandaSocket/Commands/PandaStatusCommand.cs @@ -1,37 +1,36 @@ using System.Linq; -using System.Net; using Content.Server.Administration.Managers; using Content.Server.AlertLevel; using Content.Server.GameTicking; using Content.Server.Maps; using Content.Server.RoundEnd; using Content.Server.Station.Systems; -using Content.Server.UtkaIntegration.TCP; +using Content.Server.White.PandaSocket.Interfaces; +using Content.Server.White.PandaSocket.Main; using Robust.Server.Player; using Robust.Shared.Configuration; -using Robust.Shared.Enums; using Robust.Shared.Player; -namespace Content.Server.UtkaIntegration; +namespace Content.Server.White.PandaSocket.Commands; -public sealed class UtkaStatusCommand : IUtkaCommand +public sealed class PandaStatusCommand : IPandaCommand { - public string Name => "status"; - public Type RequestMessageType => typeof(UtkaStatusRequsets); - [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly UtkaTCPWrapper _utkaSocketWrapper = default!; [Dependency] private readonly IEntityManager _entMan = default!; [Dependency] private readonly IGameMapManager _gameMapManager = default!; - public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage) + public string Name => "status"; + public Type RequestMessageType => typeof(UtkaStatusRequest); + public void Execute(IPandaStatusHandlerContext context, PandaBaseMessage baseMessage) { - if(baseMessage is not UtkaStatusRequsets message) return; + if (baseMessage is not UtkaStatusRequest message) return; + var _gameTicker = EntitySystem.Get(); var _roundEndSystem = EntitySystem.Get(); var _station = EntitySystem.Get(); + IoCManager.InjectDependencies(this); @@ -39,16 +38,9 @@ public sealed class UtkaStatusCommand : IUtkaCommand var admins = _adminManager.ActiveAdmins.Select(x => x.Name).ToList().Count; - string shuttleData = string.Empty; + var shuttleData = string.Empty; - if (_roundEndSystem.ExpectedCountdownEnd == null) - { - shuttleData = "idle"; - } - else - { - shuttleData = "called"; - } + shuttleData = _roundEndSystem.ExpectedCountdownEnd == null ? "idle" : "called"; var roundDuration = _gameTicker.RoundDuration().TotalSeconds; @@ -61,7 +53,7 @@ public sealed class UtkaStatusCommand : IUtkaCommand continue; } - if (alert is { CurrentLevel: { } }) + if (alert is { CurrentLevel: not null }) { stationCode = alert.CurrentLevel; @@ -80,6 +72,11 @@ public sealed class UtkaStatusCommand : IUtkaCommand StationCode = stationCode }; - _utkaSocketWrapper.SendMessageToAll(toUtkaMessage); + Response(context, toUtkaMessage); + } + + public void Response(IPandaStatusHandlerContext context, PandaBaseMessage? message = null) + { + context.RespondJsonAsync(message!); } } diff --git a/Content.Server/UtkaIntegration/Commands/UtkaUnJobBanCommand.cs b/Content.Server/White/PandaSocket/Commands/PandaUnJobBanCommand.cs similarity index 62% rename from Content.Server/UtkaIntegration/Commands/UtkaUnJobBanCommand.cs rename to Content.Server/White/PandaSocket/Commands/PandaUnJobBanCommand.cs index 2560806e54..a8c68ebad2 100644 --- a/Content.Server/UtkaIntegration/Commands/UtkaUnJobBanCommand.cs +++ b/Content.Server/White/PandaSocket/Commands/PandaUnJobBanCommand.cs @@ -1,16 +1,15 @@ using Content.Server.Administration; using Content.Server.Database; -using Content.Server.UtkaIntegration.TCP; +using Content.Server.White.PandaSocket.Interfaces; +using Content.Server.White.PandaSocket.Main; -namespace Content.Server.UtkaIntegration; +namespace Content.Server.White.PandaSocket.Commands; -public sealed class UtkaUnJobBanCommand : IUtkaCommand +public sealed class PandaUnJobBanCommand : IPandaCommand { - [Dependency] private UtkaTCPWrapper _utkaSocketWrapper = default!; - public string Name => "unjobban"; public Type RequestMessageType => typeof(UtkaUnJobBanRequest); - public async void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage) + public async void Execute(IPandaStatusHandlerContext context, PandaBaseMessage baseMessage) { if (baseMessage is not UtkaUnJobBanRequest message) return; @@ -21,7 +20,7 @@ public sealed class UtkaUnJobBanCommand : IUtkaCommand var located = await locator.LookupIdByNameOrIdAsync(message.ACkey!); if (located == null) { - UtkaSendResponse(false); + UtkaSendResponse(false, context); return; } @@ -30,29 +29,34 @@ public sealed class UtkaUnJobBanCommand : IUtkaCommand var ban = await dbMan.GetServerRoleBanAsync(message.Bid!.Value); if (ban == null || ban.Unban != null) { - UtkaSendResponse(false); + UtkaSendResponse(false, context); return; } var adminData = await dbMan.GetAdminDataForAsync(player); if (adminData?.AdminRank == null || ban.ServerName != "unknown" && adminData.AdminServer is not (null or "unknown") && adminData.AdminServer != ban.ServerName) { - UtkaSendResponse(false); + UtkaSendResponse(false, context); return; } await dbMan.AddServerRoleUnbanAsync(new ServerRoleUnbanDef(message.Bid!.Value, player, DateTimeOffset.Now)); - UtkaSendResponse(true); + UtkaSendResponse(true, context); } - private void UtkaSendResponse(bool unbanned) + public void Response(IPandaStatusHandlerContext context, PandaBaseMessage? message = null) + { + context.RespondJsonAsync(message!); + } + + private void UtkaSendResponse(bool unbanned, IPandaStatusHandlerContext context) { var utkaResponse = new UtkaUnJobBanResponse() { Unbanned = unbanned }; - _utkaSocketWrapper.SendMessageToAll(utkaResponse); + Response(context, utkaResponse); } } diff --git a/Content.Server/UtkaIntegration/Commands/UtkaUnbanCommand.cs b/Content.Server/White/PandaSocket/Commands/PandaUnbanCommand.cs similarity index 62% rename from Content.Server/UtkaIntegration/Commands/UtkaUnbanCommand.cs rename to Content.Server/White/PandaSocket/Commands/PandaUnbanCommand.cs index 967ed0c599..8f7d1443bf 100644 --- a/Content.Server/UtkaIntegration/Commands/UtkaUnbanCommand.cs +++ b/Content.Server/White/PandaSocket/Commands/PandaUnbanCommand.cs @@ -1,16 +1,15 @@ using Content.Server.Administration; using Content.Server.Database; -using Content.Server.UtkaIntegration.TCP; +using Content.Server.White.PandaSocket.Interfaces; +using Content.Server.White.PandaSocket.Main; -namespace Content.Server.UtkaIntegration; +namespace Content.Server.White.PandaSocket.Commands; -public sealed class UtkaUnbanCommand : IUtkaCommand +public sealed class PandaUnbanCommand : IPandaCommand { - [Dependency] private UtkaTCPWrapper _utkaSocketWrapper = default!; - public string Name => "unban"; public Type RequestMessageType => typeof(UtkaUnbanRequest); - public async void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage) + public async void Execute(IPandaStatusHandlerContext context, PandaBaseMessage baseMessage) { if (baseMessage is not UtkaUnbanRequest message) return; @@ -21,7 +20,7 @@ public sealed class UtkaUnbanCommand : IUtkaCommand var located = await locator.LookupIdByNameOrIdAsync(message.ACkey!); if (located == null) { - UtkaSendResponse(false); + UtkaSendResponse(false, context); return; } var player = located.UserId; @@ -31,29 +30,34 @@ public sealed class UtkaUnbanCommand : IUtkaCommand if (ban == null || ban.Unban != null) { - UtkaSendResponse(false); + UtkaSendResponse(false, context); return; } var adminData = await dbMan.GetAdminDataForAsync(player); if (adminData?.AdminRank == null || ban.ServerName != "unknown" && adminData.AdminServer is not (null or "unknown") && adminData.AdminServer != ban.ServerName) { - UtkaSendResponse(false); + UtkaSendResponse(false, context); return; } await dbMan.AddServerUnbanAsync(new ServerUnbanDef(banId, player, DateTimeOffset.Now)); - UtkaSendResponse(true); + UtkaSendResponse(true, context); } - private void UtkaSendResponse(bool unbanned) + public void Response(IPandaStatusHandlerContext context, PandaBaseMessage? message = null) + { + context.RespondJsonAsync(message!); + } + + private void UtkaSendResponse(bool unbanned, IPandaStatusHandlerContext context) { var utkaResponse = new UtkaUnbanResponse() { Unbanned = unbanned }; - _utkaSocketWrapper.SendMessageToAll(utkaResponse); + Response(context, utkaResponse); } } diff --git a/Content.Server/UtkaIntegration/Commands/UtkaWhoCommand.cs b/Content.Server/White/PandaSocket/Commands/PandaWhoCommand.cs similarity index 55% rename from Content.Server/UtkaIntegration/Commands/UtkaWhoCommand.cs rename to Content.Server/White/PandaSocket/Commands/PandaWhoCommand.cs index d3912e68a3..76a4096773 100644 --- a/Content.Server/UtkaIntegration/Commands/UtkaWhoCommand.cs +++ b/Content.Server/White/PandaSocket/Commands/PandaWhoCommand.cs @@ -1,27 +1,21 @@ using System.Linq; -using System.Net; -using System.Text; -using System.Text.Json; -using Content.Server.UtkaIntegration.TCP; -using Content.Shared.CCVar; -using Robust.Server.Player; +using Content.Server.White.PandaSocket.Interfaces; +using Content.Server.White.PandaSocket.Main; using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.Player; -namespace Content.Server.UtkaIntegration; +namespace Content.Server.White.PandaSocket.Commands; -public sealed class UtkaWhoCommand : IUtkaCommand +public sealed class PandaWhoCommand : IPandaCommand { + [Dependency] private readonly IConfigurationManager _cfg = default!; + public string Name => "who"; public Type RequestMessageType => typeof(UtkaWhoRequest); - - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private UtkaTCPWrapper _utkaSocketWrapper = default!; - - public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage) + public void Execute(IPandaStatusHandlerContext context, PandaBaseMessage baseMessage) { - if(baseMessage is not UtkaWhoRequest _) return; + if (baseMessage is not UtkaWhoRequest) return; IoCManager.InjectDependencies(this); @@ -35,6 +29,11 @@ public sealed class UtkaWhoCommand : IUtkaCommand Players = playerNames.ToList() }; - _utkaSocketWrapper.SendMessageToAll(toUtkaMessage); + Response(context, toUtkaMessage); + } + + public void Response(IPandaStatusHandlerContext context, PandaBaseMessage? message = null) + { + context.RespondJsonAsync(message!); } } diff --git a/Content.Server/White/PandaSocket/Interfaces/IPandaCommand.cs b/Content.Server/White/PandaSocket/Interfaces/IPandaCommand.cs new file mode 100644 index 0000000000..6c7d495175 --- /dev/null +++ b/Content.Server/White/PandaSocket/Interfaces/IPandaCommand.cs @@ -0,0 +1,11 @@ +using Content.Server.White.PandaSocket.Main; + +namespace Content.Server.White.PandaSocket.Interfaces; + +public interface IPandaCommand +{ + string Name { get; } + Type RequestMessageType { get; } + public void Execute(IPandaStatusHandlerContext context, PandaBaseMessage baseMessage); + public void Response(IPandaStatusHandlerContext context, PandaBaseMessage? message = null); +} diff --git a/Content.Server/White/PandaSocket/Interfaces/IPandaStatusHandlerContext.cs b/Content.Server/White/PandaSocket/Interfaces/IPandaStatusHandlerContext.cs new file mode 100644 index 0000000000..7e478b64bf --- /dev/null +++ b/Content.Server/White/PandaSocket/Interfaces/IPandaStatusHandlerContext.cs @@ -0,0 +1,55 @@ +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Extensions.Primitives; + +namespace Content.Server.White.PandaSocket.Interfaces; + +public interface IPandaStatusHandlerContext +{ + HttpMethod RequestMethod { get; } + IPEndPoint RemoteEndPoint { get; } + + /// + /// Stream that reads the request body data, + /// + Stream RequestBody { get; } + Uri Url { get; } + bool IsGetLike { get; } + IReadOnlyDictionary RequestHeaders { get; } + + IDictionary ResponseHeaders { get; } + bool KeepAlive { get; set; } + + Task RespondNoContentAsync(); + + Task RespondAsync( + string text, + HttpStatusCode code = HttpStatusCode.OK, + string contentType = "text/plain; charset=utf-8"); + + Task RespondAsync( + string text, + int code = 200, + string contentType = "text/plain; charset=utf-8"); + + Task RespondAsync( + byte[] data, + HttpStatusCode code = HttpStatusCode.OK, + string contentType = "text/plain; charset=utf-8"); + + Task RespondAsync( + byte[] data, + int code = 200, + string contentType = "text/plain; charset=utf-8"); + + Task RespondErrorAsync(HttpStatusCode code); + + Task RespondJsonAsync(object jsonData, HttpStatusCode code = HttpStatusCode.OK); + + Task RespondStreamAsync(HttpStatusCode code = HttpStatusCode.OK); +} + +public delegate Task PandaStatusHostHandlerAsync( + IPandaStatusHandlerContext context); diff --git a/Content.Server/UtkaIntegration/UtkaCommunication.cs b/Content.Server/White/PandaSocket/Main/PandaCommunication.cs similarity index 69% rename from Content.Server/UtkaIntegration/UtkaCommunication.cs rename to Content.Server/White/PandaSocket/Main/PandaCommunication.cs index 98cd146d3a..e0472ab617 100644 --- a/Content.Server/UtkaIntegration/UtkaCommunication.cs +++ b/Content.Server/White/PandaSocket/Main/PandaCommunication.cs @@ -1,26 +1,14 @@ using System.Text.Json.Serialization; -namespace Content.Server.UtkaIntegration; +namespace Content.Server.White.PandaSocket.Main; -public class UtkaBaseMessage +public class PandaBaseMessage { [JsonPropertyName("command")] public virtual string? Command { get; set; } } -public class UtkaHandshakeMessage : UtkaBaseMessage -{ - [JsonPropertyName("command")] - public override string Command => "handshake"; - - [JsonPropertyName("key")] - public string? Key { get; set; } - - [JsonPropertyName("message")] - public string? Message { get; set; } -} - -public class UtkaOOCRequest : UtkaBaseMessage +public class UtkaOOCRequest : PandaBaseMessage { [JsonPropertyName("command")] public override string? Command => "ooc"; @@ -32,49 +20,49 @@ public class UtkaOOCRequest : UtkaBaseMessage public string? Message { get; set; } } -public class UtkaAsayRequest : UtkaBaseMessage +public class UtkaAsayRequest : PandaBaseMessage { [JsonPropertyName("command")] public override string? Command => "asay"; - [JsonPropertyName("a_ckey")] + [JsonPropertyName("ackey")] public string? ACkey { get; set; } [JsonPropertyName("message")] public string? Message { get; set; } } -public class UtkaPmRequest : UtkaBaseMessage +public class UtkaPmRequest : PandaBaseMessage { [JsonPropertyName("command")] - public override string? Command => "discord_pm"; + public override string? Command => "discordpm"; [JsonPropertyName("sender")] public string? Sender { get; set; } [JsonPropertyName("receiver")] - public string? Reciever { get; set; } + public string? Receiver { get; set; } [JsonPropertyName("message")] public string? Message { get; set; } } -public class UtkaPmResponse : UtkaBaseMessage +public class UtkaPmResponse : PandaBaseMessage { [JsonPropertyName("command")] - public override string? Command => "discord_pm"; + public override string? Command => "discordpm"; [JsonPropertyName("message")] public bool? Message { get; set; } } -public class UtkaWhoRequest : UtkaBaseMessage +public class UtkaWhoRequest : PandaBaseMessage { [JsonPropertyName("command")] public override string? Command => "who"; } -public class UtkaWhoResponse : UtkaBaseMessage +public class UtkaWhoResponse : PandaBaseMessage { [JsonPropertyName("command")] public override string? Command => "who"; @@ -83,13 +71,13 @@ public class UtkaWhoResponse : UtkaBaseMessage public List? Players { get; set; } } -public class UtkaAdminWhoRequest : UtkaBaseMessage +public class UtkaAdminWhoRequest : PandaBaseMessage { [JsonPropertyName("command")] public override string? Command => "adminwho"; } -public class UtkaAdminWhoResponse : UtkaBaseMessage +public class UtkaAdminWhoResponse : PandaBaseMessage { [JsonPropertyName("command")] public override string? Command => "adminwho"; @@ -98,13 +86,13 @@ public class UtkaAdminWhoResponse : UtkaBaseMessage public List? Admins { get; set; } } -public class UtkaStatusRequsets : UtkaBaseMessage +public class UtkaStatusRequest : PandaBaseMessage { [JsonPropertyName("command")] public override string? Command => "status"; } -public class UtkaStatusResponse : UtkaBaseMessage +public class UtkaStatusResponse : PandaBaseMessage { [JsonPropertyName("command")] public override string? Command => "status"; @@ -118,27 +106,166 @@ public class UtkaStatusResponse : UtkaBaseMessage [JsonPropertyName("map")] public string? Map { get; set; } - [JsonPropertyName("round_duration")] + [JsonPropertyName("roundduration")] public double RoundDuration { get; set; } - [JsonPropertyName("shuttle_status")] + [JsonPropertyName("shuttlestatus")] public string? ShuttleStatus { get; set; } - [JsonPropertyName("station_code")] + [JsonPropertyName("stationcode")] public string? StationCode { get; set; } } -public class UtkaRoundstatusUpdate : UtkaBaseMessage +public sealed class UtkaBanRequest : PandaBaseMessage { [JsonPropertyName("command")] - public override string? Command => "roundstatus"; + public override string? Command => "ban"; - [JsonPropertyName("message")] - public string? Message { get; set; } + [JsonPropertyName("ckey")] + public string? Ckey { get; set; } + + [JsonPropertyName("ackey")] + public string? ACkey { get; set; } + + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + [JsonPropertyName("duration")] + public uint? Duration { get; set; } + + [JsonPropertyName("global")] + public bool? Global { get; set; } } +public sealed class UtkaBanResponse : PandaBaseMessage +{ + [JsonPropertyName("command")] + public override string? Command => "ban"; -public sealed class UtkaChatEventMessage : UtkaBaseMessage + [JsonPropertyName("banned")] + public bool? Banned { get; set; } +} + +public sealed class UtkaJobBanRequest : PandaBaseMessage +{ + public override string? Command => "jobban"; + + [JsonPropertyName("ckey")] + public string? Ckey { get; set; } + + [JsonPropertyName("ackey")] + public string? ACkey { get; set; } + + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + [JsonPropertyName("duration")] + public uint? Duration { get; set; } + + [JsonPropertyName("global")] + public bool? Global { get; set; } + + [JsonPropertyName("type")] + public string? Type { get; set; } +} + +public sealed class UtkaJobBanResponse : PandaBaseMessage +{ + [JsonPropertyName("command")] + public override string? Command => "jobban"; + + [JsonPropertyName("banned")] + public bool? Banned { get; set; } +} + +public sealed class UtkaRestartRoundRequest : PandaBaseMessage +{ + [JsonPropertyName("command")] + public override string? Command => "restartround"; +} + +public sealed class UtkaRestartRoundResponse : PandaBaseMessage +{ + [JsonPropertyName("command")] + public override string? Command => "restartround"; + + [JsonPropertyName("restarted")] + public bool? Restarted { get; set; } +} + +public sealed class UtkaUnbanRequest : PandaBaseMessage +{ + [JsonPropertyName("command")] + public override string? Command => "unban"; + + [JsonPropertyName("ackey")] + public string? ACkey { get; set; } + + [JsonPropertyName("bid")] + public int? Bid { get; set; } +} + +public sealed class UtkaUnbanResponse : PandaBaseMessage +{ + [JsonPropertyName("command")] + public override string? Command => "unban"; + + [JsonPropertyName("unbanned")] + public bool? Unbanned { get; set; } +} + +public sealed class UtkaUnJobBanRequest : PandaBaseMessage +{ + [JsonPropertyName("command")] + public override string? Command => "unjobban"; + + [JsonPropertyName("ackey")] + public string? ACkey { get; set; } + + [JsonPropertyName("bid")] + public int? Bid { get; set; } +} + +public sealed class UtkaUnJobBanResponse : PandaBaseMessage +{ + [JsonPropertyName("command")] + public override string? Command => "unjobban"; + + [JsonPropertyName("unbanned")] + public bool? Unbanned { get; set; } +} + +public sealed class UtkaBannedEvent : PandaBaseMessage +{ + [JsonPropertyName("command")] + public override string? Command => "banned"; + + [JsonPropertyName("ckey")] + public string? Ckey { get; set; } + + [JsonPropertyName("ackey")] + public string? ACkey { get; set; } + + [JsonPropertyName("bantype")] + public string? Bantype { get; set; } + + [JsonPropertyName("duration")] + public uint? Duration { get; set; } + + [JsonPropertyName("global")] + public bool? Global { get; set; } + + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + [JsonPropertyName("rid")] + public int? Rid { get; set; } + + [JsonPropertyName("banid")] + public int? BanId { get; set; } +} + +public sealed class UtkaChatEventMessage : PandaBaseMessage { [JsonPropertyName("ckey")] public string? Ckey { get; set; } @@ -147,7 +274,7 @@ public sealed class UtkaChatEventMessage : UtkaBaseMessage public string? Message { get; set; } } -public sealed class UtkaRoundStatusEvent : UtkaBaseMessage +public sealed class UtkaRoundStatusEvent : PandaBaseMessage { [JsonPropertyName("command")] public override string? Command => "roundstatus"; @@ -156,7 +283,7 @@ public sealed class UtkaRoundStatusEvent : UtkaBaseMessage public string? Message { get; set; } } -public sealed class UtkaChatMeEvent : UtkaBaseMessage +public sealed class UtkaChatMeEvent : PandaBaseMessage { [JsonPropertyName("command")] public override string? Command => "me"; @@ -171,7 +298,7 @@ public sealed class UtkaChatMeEvent : UtkaBaseMessage public string? CharacterName { get; set; } } -public sealed class UtkaAhelpPmEvent : UtkaBaseMessage +public sealed class UtkaAhelpPmEvent : PandaBaseMessage { [JsonPropertyName("command")] public override string? Command => "pm"; @@ -194,152 +321,3 @@ public sealed class UtkaAhelpPmEvent : UtkaBaseMessage [JsonPropertyName("entity")] public string? Entity { get; set; } } - -public sealed class UtkaBannedEvent : UtkaBaseMessage -{ - [JsonPropertyName("command")] - public override string? Command => "banned"; - - [JsonPropertyName("ckey")] - public string? Ckey { get; set; } - - [JsonPropertyName("a_ckey")] - public string? ACkey { get; set; } - - [JsonPropertyName("bantype")] - public string? Bantype { get; set; } - - [JsonPropertyName("duration")] - public uint? Duration { get; set; } - - [JsonPropertyName("global")] - public bool? Global { get; set; } - - [JsonPropertyName("reason")] - public string? Reason { get; set; } - - [JsonPropertyName("rid")] - public int? Rid { get; set; } - - [JsonPropertyName("ban_id")] - public int? BanId { get; set; } -} - -public sealed class UtkaBanRequest : UtkaBaseMessage -{ - [JsonPropertyName("command")] - public override string? Command => "ban"; - - [JsonPropertyName("ckey")] - public string? Ckey { get; set; } - - [JsonPropertyName("a_ckey")] - public string? ACkey { get; set; } - - [JsonPropertyName("reason")] - public string? Reason { get; set; } - - [JsonPropertyName("duration")] - public uint? Duration { get; set; } - - [JsonPropertyName("global")] - public bool? Global { get; set; } -} - -public sealed class UtkaBanResponse : UtkaBaseMessage -{ - [JsonPropertyName("command")] - public override string? Command => "ban"; - - [JsonPropertyName("banned")] - public bool? Banned { get; set; } -} - -public sealed class UtkaJobBanRequest : UtkaBaseMessage -{ - public override string? Command => "jobban"; - - [JsonPropertyName("ckey")] - public string? Ckey { get; set; } - - [JsonPropertyName("a_ckey")] - public string? ACkey { get; set; } - - [JsonPropertyName("reason")] - public string? Reason { get; set; } - - [JsonPropertyName("duration")] - public uint? Duration { get; set; } - - [JsonPropertyName("global")] - public bool? Global { get; set; } - - [JsonPropertyName("type")] - public string? Type { get; set; } -} - -public sealed class UtkaJobBanResponse : UtkaBaseMessage -{ - [JsonPropertyName("command")] - public override string? Command => "jobban"; - - [JsonPropertyName("banned")] - public bool? Banned { get; set; } -} - -public sealed class UtkaRestartRoundRequest : UtkaBaseMessage -{ - [JsonPropertyName("command")] - public override string? Command => "restart_round"; -} - -public sealed class UtkaRestartRoundResponse : UtkaBaseMessage -{ - [JsonPropertyName("command")] - public override string? Command => "restart_round"; - - [JsonPropertyName("restarted")] - public bool? Restarted { get; set; } -} - -public sealed class UtkaUnbanRequest : UtkaBaseMessage -{ - [JsonPropertyName("command")] - public override string? Command => "unban"; - - [JsonPropertyName("a_ckey")] - public string? ACkey { get; set; } - - [JsonPropertyName("bid")] - public int? Bid { get; set; } -} - -public sealed class UtkaUnbanResponse : UtkaBaseMessage -{ - [JsonPropertyName("command")] - public override string? Command => "unban"; - - [JsonPropertyName("unbanned")] - public bool? Unbanned { get; set; } -} - -public sealed class UtkaUnJobBanRequest : UtkaBaseMessage -{ - [JsonPropertyName("command")] - public override string? Command => "unjobban"; - - [JsonPropertyName("a_ckey")] - public string? ACkey { get; set; } - - [JsonPropertyName("bid")] - public int? Bid { get; set; } -} - -public sealed class UtkaUnJobBanResponse : UtkaBaseMessage -{ - [JsonPropertyName("command")] - public override string? Command => "unjobban"; - - [JsonPropertyName("unbanned")] - public bool? Unbanned { get; set; } -} diff --git a/Content.Server/White/PandaSocket/Main/PandaStatusHost.Handlers.cs b/Content.Server/White/PandaSocket/Main/PandaStatusHost.Handlers.cs new file mode 100644 index 0000000000..bafa651c74 --- /dev/null +++ b/Content.Server/White/PandaSocket/Main/PandaStatusHost.Handlers.cs @@ -0,0 +1,58 @@ +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using System.Web; +using Content.Server.White.PandaSocket.Interfaces; + +namespace Content.Server.White.PandaSocket.Main; + +public sealed partial class PandaStatusHost +{ + + private void RegisterHandlers() + { + AddHandler(HandleChecker); + AddHandler(HandleQueryCheck); + AddHandler(HandleRequest); + } + + private async Task HandleChecker(IPandaStatusHandlerContext context) + { + if (!context.IsGetLike || context.Url!.AbsolutePath != "/checker") + { + return false; + } + + await context.RespondAsync("Checking panda socket, ukta loh.", (HttpStatusCode) 418); + return true; + } + + private async Task HandleQueryCheck(IPandaStatusHandlerContext context) + { + if (!context.IsGetLike || context.Url!.AbsolutePath != "/querycheck") + { + return false; + } + + var query = HttpUtility.ParseQueryString(context.Url.Query); + var text = query["text"] ?? "None"; + + await context.RespondAsync(text, (HttpStatusCode) 418); + + return true; + } + + private async Task HandleRequest(IPandaStatusHandlerContext context) + { + if (!context.IsGetLike || context.Url!.AbsolutePath != "/request") + { + return false; + } + + if (!ValidateMessage(context.Url.Query, out var message) || message == null) + return false; + + ExecuteCommand(context, message); + return true; + } +} diff --git a/Content.Server/White/PandaSocket/Main/PandaStatusHost.cs b/Content.Server/White/PandaSocket/Main/PandaStatusHost.cs new file mode 100644 index 0000000000..b33a04fd21 --- /dev/null +++ b/Content.Server/White/PandaSocket/Main/PandaStatusHost.cs @@ -0,0 +1,403 @@ +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Mime; +using System.Reflection; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using Content.Server.White.PandaSocket.Interfaces; +using Content.Shared.White; +using Microsoft.Extensions.Primitives; +using Newtonsoft.Json; +using Robust.Server.Player; +using Robust.Shared.Asynchronous; +using Robust.Shared.Configuration; +using Robust.Shared.Network; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace Content.Server.White.PandaSocket.Main; + +public sealed partial class PandaStatusHost : IDisposable +{ + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IServerNetManager _netManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IDependencyCollection _deps = default!; + [Dependency] private readonly ILogManager _logMan = default!; + [Dependency] private readonly ITaskManager _taskManager = default!; + + private const string Sawmill = "panda.socket"; + + private static readonly ConcurrentDictionary Commands = new(); + private readonly List _handlers = new(); + private HttpListener? _listener; + private TaskCompletionSource? _stopSource; + private ISawmill _httpSawmill = default!; + private string? _token; + + public void Start() + { + var statusBind = _cfg.GetCVar(WhiteCVars.PandaStatusBind); + if (statusBind == "") + return; + + _httpSawmill = _logMan.GetSawmill($"{Sawmill}.http"); + + RegisterHandlers(); + RegisterCommands(); + + _token = _cfg.GetCVar(WhiteCVars.PandaToken); + _cfg.OnValueChanged(WhiteCVars.PandaToken, token => _token = token, true); + + _stopSource = new TaskCompletionSource(); + _listener = new HttpListener(); + _listener.Prefixes.Add($"http://{statusBind}/"); + _listener.Start(); + + Task.Run(ListenerThread); + } + + private void RegisterCommands() + { + var assembly = Assembly.GetExecutingAssembly(); + var types = assembly.GetTypes(); + + var commands = types.Where(type => + typeof(IPandaCommand).IsAssignableFrom(type) && type.GetInterfaces().Contains(typeof(IPandaCommand))).ToList(); + + foreach (var command in commands) + { + if (Activator.CreateInstance(command) is IPandaCommand pandaCommand) + { + Commands[pandaCommand.Name] = pandaCommand; + } + } + } + + private void AddHandler(PandaStatusHostHandlerAsync handler) + { + _handlers.Add(handler); + } + + private void ExecuteCommand(IPandaStatusHandlerContext context, PandaBaseMessage baseMessage) + { + var command = baseMessage.Command!; + + if (!Commands.ContainsKey(command)) + { + return; + } + + _taskManager.RunOnMainThread(() => Commands[command].Execute(context, baseMessage)); + } + + private async Task ListenerThread() + { + while (true) + { + var getContextTask = _listener!.GetContextAsync(); + var task = await Task.WhenAny(getContextTask, _stopSource!.Task); + + if (task == _stopSource.Task) + { + return; + } + + // Task.Run this so it gets run on another thread pool thread. +#pragma warning disable CS4014 + Task.Run(async () => +#pragma warning restore CS4014 + { + try + { + var ctx = await getContextTask; + await ProcessRequestAsync(ctx); + } + catch (Exception e) + { + _httpSawmill.Error($"Error inside ProcessRequestAsync:\n{e}"); + } + }); + } + } + + private async Task ProcessRequestAsync(HttpListenerContext context) + { + var apiContext = (IPandaStatusHandlerContext) new ContextImpl(context); + + _httpSawmill.Info( + $"{apiContext.RequestMethod} {apiContext.Url.PathAndQuery} from {apiContext.RemoteEndPoint}"); + + try + { + foreach (var handler in _handlers) + { + if (await handler(apiContext)) + { + return; + } + } + + // No handler returned true, assume no handlers care about this. + // 404. + await apiContext.RespondAsync("Not Found", HttpStatusCode.NotFound); + } + catch (Exception e) + { + _httpSawmill.Error($"Exception in StatusHost: {e}"); + await apiContext.RespondErrorAsync(HttpStatusCode.InternalServerError); + } + } + + private bool ValidateMessage(string message, out PandaBaseMessage? baseMessage) + { + baseMessage = null; + + if (string.IsNullOrEmpty(message)) + return false; + + var collection = HttpUtility.ParseQueryString(message); +#pragma warning disable CS8714 + var json = JsonSerializer.Serialize(collection.AllKeys.ToDictionary(y => y, y => collection[y])); +#pragma warning restore CS8714 + var jsonDocument = JsonDocument.Parse(json); + var root = jsonDocument.RootElement; + + if (!root.TryGetProperty("token", out var token)) + return false; + + if (token.GetString() != _token) + return false; + + if (!root.TryGetProperty("command", out var commandNameElement)) + return false; + + var commandName = commandNameElement.GetString(); + if (commandName == null) + return false; + + var pandaCommand = Commands.Values.FirstOrDefault(x => x.Name == commandName); + if (pandaCommand == null) + return false; + + var messageType = pandaCommand.RequestMessageType; + + try + { + baseMessage = JsonConvert.DeserializeObject(json, messageType) as PandaBaseMessage; + } + catch (Exception e) + { + return false; + } + + return true; + } + + public void Dispose() + { + if (_stopSource == null) + { + return; + } + + _stopSource.SetResult(); + _listener!.Stop(); + } + + private sealed class ContextImpl : IPandaStatusHandlerContext + { + private readonly HttpListenerContext _context; + private readonly Dictionary _responseHeaders; + public HttpMethod RequestMethod { get; } + public IPEndPoint RemoteEndPoint => _context.Request.RemoteEndPoint!; + public Stream RequestBody => _context.Request.InputStream; + public Uri Url => _context.Request.Url!; + public bool IsGetLike => RequestMethod == HttpMethod.Head || RequestMethod == HttpMethod.Get; + public IReadOnlyDictionary RequestHeaders { get; } + + public bool KeepAlive + { + get => _context.Response.KeepAlive; + set => _context.Response.KeepAlive = value; + } + + public IDictionary ResponseHeaders => _responseHeaders; + + public ContextImpl(HttpListenerContext context) + { + _context = context; + RequestMethod = new HttpMethod(context.Request.HttpMethod!); + + var headers = new Dictionary(); + foreach (string? key in context.Request.Headers.Keys) + { + if (key == null) + continue; + + headers.Add(key, context.Request.Headers.GetValues(key)); + } + + RequestHeaders = headers; + _responseHeaders = new Dictionary(); + } + + public T? RequestBodyJson() + { + return JsonSerializer.Deserialize(RequestBody); + } + + public async Task RequestBodyJsonAsync() + { + return await JsonSerializer.DeserializeAsync(RequestBody); + } + + public void Respond(string text, HttpStatusCode code = HttpStatusCode.OK, string contentType = MediaTypeNames.Text.Plain) + { + Respond(text, (int) code, contentType); + } + + public void Respond(string text, int code = 200, string contentType = MediaTypeNames.Text.Plain) + { + _context.Response.StatusCode = code; + _context.Response.ContentType = contentType; + + if (RequestMethod == HttpMethod.Head) + { + return; + } + + using var writer = new StreamWriter(_context.Response.OutputStream, new UTF8Encoding()); + + writer.Write(text); + } + + public void Respond(byte[] data, HttpStatusCode code = HttpStatusCode.OK, string contentType = MediaTypeNames.Text.Plain) + { + Respond(data, (int) code, contentType); + } + + public void Respond(byte[] data, int code = 200, string contentType = MediaTypeNames.Text.Plain) + { + _context.Response.StatusCode = code; + _context.Response.ContentType = contentType; + _context.Response.ContentLength64 = data.Length; + + if (RequestMethod == HttpMethod.Head) + { + _context.Response.Close(); + return; + } + + _context.Response.OutputStream.Write(data); + _context.Response.Close(); + } + + public Task RespondNoContentAsync() + { + RespondShared(); + + _context.Response.StatusCode = (int) HttpStatusCode.NoContent; + _context.Response.Close(); + + return Task.CompletedTask; + } + + public Task RespondAsync(string text, HttpStatusCode code = HttpStatusCode.OK, string contentType = "text/plain") + { + return RespondAsync(text, (int) code, contentType); + } + + public async Task RespondAsync(string text, int code = 200, string contentType = "text/plain") + { + RespondShared(); + + _context.Response.StatusCode = code; + _context.Response.ContentType = contentType; + + if (RequestMethod == HttpMethod.Head) + return; + + using var writer = new StreamWriter(_context.Response.OutputStream, new UTF8Encoding()); + + await writer.WriteAsync(text); + } + + public Task RespondAsync(byte[] data, HttpStatusCode code = HttpStatusCode.OK, string contentType = "text/plain") + { + return RespondAsync(data, (int) code, contentType); + } + + public async Task RespondAsync(byte[] data, int code = 200, string contentType = "text/plain") + { + RespondShared(); + + _context.Response.StatusCode = code; + _context.Response.ContentType = contentType; + _context.Response.ContentLength64 = data.Length; + + if (RequestMethod == HttpMethod.Head) + { + _context.Response.Close(); + return; + } + + await _context.Response.OutputStream.WriteAsync(data); + _context.Response.Close(); + } + + public void RespondError(HttpStatusCode code) + { + Respond(code.ToString(), code); + } + + public Task RespondErrorAsync(HttpStatusCode code) + { + return RespondAsync(code.ToString(), code); + } + + public void RespondJson(object jsonData, HttpStatusCode code = HttpStatusCode.OK) + { + RespondShared(); + + _context.Response.ContentType = "application/json"; + + JsonSerializer.Serialize(_context.Response.OutputStream, jsonData); + + _context.Response.Close(); + } + + public async Task RespondJsonAsync(object jsonData, HttpStatusCode code = HttpStatusCode.OK) + { + RespondShared(); + + _context.Response.ContentType = "application/json"; + + await JsonSerializer.SerializeAsync(_context.Response.OutputStream, jsonData); + + _context.Response.Close(); + } + + public Task RespondStreamAsync(HttpStatusCode code = HttpStatusCode.OK) + { + RespondShared(); + + _context.Response.StatusCode = (int) code; + + return Task.FromResult(_context.Response.OutputStream); + } + + private void RespondShared() + { + foreach (var (header, value) in _responseHeaders) + { + _context.Response.AddHeader(header, value); + } + } + } +} diff --git a/Content.Server/White/PandaSocket/Main/PandaWebManager.cs b/Content.Server/White/PandaSocket/Main/PandaWebManager.cs new file mode 100644 index 0000000000..b3f045bf5e --- /dev/null +++ b/Content.Server/White/PandaSocket/Main/PandaWebManager.cs @@ -0,0 +1,53 @@ +using System.Linq; +using System.Net.Http; +using System.Web; +using Content.Shared.White; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Robust.Shared.Configuration; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace Content.Server.White.PandaSocket.Main; + +public sealed class PandaWebManager +{ + [Dependency] private readonly IConfigurationManager _cfg = default!; + + private readonly HttpClient _httpClient = new(); + private string? _token; + private string? _utkaUri; + + public void Initialize() + { + IoCManager.InjectDependencies(this); + + _token = _cfg.GetCVar(WhiteCVars.PandaToken); + _cfg.OnValueChanged(WhiteCVars.PandaToken, token => _token = token, true); + + _utkaUri = _cfg.GetCVar(WhiteCVars.UtkaClientBind); + _cfg.OnValueChanged(WhiteCVars.UtkaClientBind, uri => _utkaUri = uri, true); + } + + public async void SendBotMessage(PandaBaseMessage message) + { + if (_utkaUri is null || _token is null) + return; + + var json = JsonSerializer.Serialize(message, message.GetType()); + var jObj = (JObject) JsonConvert.DeserializeObject(json)!; + var query = String.Join("&", + jObj.Children().Cast() + .Select(jp=>jp.Name + "=" + HttpUtility.UrlEncode(jp.Value.ToString()))); + + var request = $"http://{_utkaUri}?token={_token}&{query}"; + + try + { + await _httpClient.GetAsync(request); + } + catch (Exception e) + { + return; + } + } +} diff --git a/Content.Server/White/Reputation/ReputationSystem.cs b/Content.Server/White/Reputation/ReputationSystem.cs index ebb1701c1a..dc9be0a755 100644 --- a/Content.Server/White/Reputation/ReputationSystem.cs +++ b/Content.Server/White/Reputation/ReputationSystem.cs @@ -3,13 +3,12 @@ using Content.Server.Administration; using Content.Server.GameTicking; using Content.Server.Objectives; using Content.Server.Roles; -using Content.Server.UtkaIntegration; using Content.Server.White.AspectsSystem.Base; using Content.Shared.Mind; using Content.Shared.Mind.Components; +using Content.Server.White.PandaSocket.Main; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; -using Content.Shared.Objectives.Components; using Content.Shared.White; using Robust.Server.Player; using Robust.Shared.Configuration; diff --git a/Content.Shared/White/WhiteCVars.cs b/Content.Shared/White/WhiteCVars.cs index 9b74b704d1..632ef3c0cd 100644 --- a/Content.Shared/White/WhiteCVars.cs +++ b/Content.Shared/White/WhiteCVars.cs @@ -341,4 +341,19 @@ public sealed class WhiteCVars */ public static readonly CVarDef EnableGrantAntag = CVarDef.Create("white.antag_grant_enabled", true, CVar.SERVERONLY); + + /* + * Panda Socket + */ + public static readonly CVarDef PandaStatusBind = + CVarDef.Create("white.panda_status_bind", "", CVar.SERVERONLY | CVar.ARCHIVE); + + public static readonly CVarDef PandaStatusMaxConnections = + CVarDef.Create("white.panda_status_max_connections", 100, CVar.SERVERONLY); + + public static readonly CVarDef PandaToken = + CVarDef.Create("white.panda_token", "ass", CVar.SERVERONLY | CVar.CONFIDENTIAL); + + public static readonly CVarDef UtkaClientBind = + CVarDef.Create("white.utka_client_bind", "", CVar.SERVERONLY); }