Implement server-side chat and chat command framework
This commit is contained in:
parent
476d6382da
commit
b8a8a535f1
17 changed files with 783 additions and 7 deletions
|
@ -18,7 +18,7 @@ namespace CLre
|
|||
{
|
||||
public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
|
||||
|
||||
public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
public override string Version { get; } = "21Q3 " + Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
|
||||
internal static Harmony harmonyInstance = null;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace CLre_server.API.Config
|
||||
{
|
||||
|
@ -9,6 +10,11 @@ namespace CLre_server.API.Config
|
|||
public bool clre_clients_only;
|
||||
public bool web_server;
|
||||
public bool terrain_exclusion_zone;
|
||||
public bool chat_commands;
|
||||
public string email_address;
|
||||
public string password;
|
||||
public string[] bans;
|
||||
public string[] moderators;
|
||||
|
||||
public static CLreConfig Default()
|
||||
{
|
||||
|
@ -17,6 +23,11 @@ namespace CLre_server.API.Config
|
|||
clre_clients_only = false,
|
||||
web_server = false,
|
||||
terrain_exclusion_zone = false,
|
||||
chat_commands = false,
|
||||
email_address = "email@address.com",
|
||||
password = "s3cur3-password",
|
||||
bans = new string[0],
|
||||
moderators = new []{ "NGuiness", "NGnius", "Zhang"},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -60,5 +71,10 @@ namespace CLre_server.API.Config
|
|||
{
|
||||
return UnityEngine.JsonUtility.ToJson(this, true);
|
||||
}
|
||||
|
||||
public void ToFile(string path)
|
||||
{
|
||||
File.WriteAllText(path, this.ToString());
|
||||
}
|
||||
}
|
||||
}
|
68
CLre_server/API/MainServer/ModerationEngine.cs
Normal file
68
CLre_server/API/MainServer/ModerationEngine.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using Svelto.Context;
|
||||
using Svelto.DataStructures;
|
||||
using Svelto.ECS;
|
||||
using User.Server;
|
||||
|
||||
namespace CLre_server.API.MainServer
|
||||
{
|
||||
class ModerationEngine : Engines.ServerEnginePostBuild
|
||||
{
|
||||
public override void Ready()
|
||||
{
|
||||
}
|
||||
|
||||
public int? FindConnectedPlayerById(string publicId)
|
||||
{
|
||||
FieldInfo f = AccessTools.Field(AccessTools.TypeByName("User.Server.AccountExclusiveGroups"), "accountGroup");
|
||||
ExclusiveGroup accountGroup = (ExclusiveGroup) f.GetValue(null);
|
||||
ReadOnlyCollectionStruct<AccountIdServerNode> accounts =
|
||||
entitiesDB.QueryEntityViews<AccountIdServerNode>(accountGroup);
|
||||
for (int i = 0; i < accounts.Count; i++)
|
||||
{
|
||||
if (accounts[i].accountId.publicId.ToString() == publicId)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int? FindConnectedPlayerByName(string displayName)
|
||||
{
|
||||
FieldInfo f = AccessTools.Field(AccessTools.TypeByName("User.Server.AccountExclusiveGroups"), "accountGroup");
|
||||
ExclusiveGroup accountGroup = (ExclusiveGroup) f.GetValue(null);
|
||||
ReadOnlyCollectionStruct<AccountIdServerNode> accounts =
|
||||
entitiesDB.QueryEntityViews<AccountIdServerNode>(accountGroup);
|
||||
for (int i = 0; i < accounts.Count; i++)
|
||||
{
|
||||
if (String.Equals(accounts[i].accountId.displayName, displayName, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Guid? FindConnectedPlayerGuidByName(string displayName)
|
||||
{
|
||||
FieldInfo f = AccessTools.Field(AccessTools.TypeByName("User.Server.AccountExclusiveGroups"), "accountGroup");
|
||||
ExclusiveGroup accountGroup = (ExclusiveGroup) f.GetValue(null);
|
||||
ReadOnlyCollectionStruct<AccountIdServerNode> accounts =
|
||||
entitiesDB.QueryEntityViews<AccountIdServerNode>(accountGroup);
|
||||
for (int i = 0; i < accounts.Count; i++)
|
||||
{
|
||||
if (String.Equals(accounts[i].accountId.displayName, displayName, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return accounts[i].accountId.publicId;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
139
CLre_server/API/MainServer/Moderator.cs
Normal file
139
CLre_server/API/MainServer/Moderator.cs
Normal file
|
@ -0,0 +1,139 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using GameNetworkLayer.Shared;
|
||||
|
||||
namespace CLre_server.API.MainServer
|
||||
{
|
||||
public class Moderator
|
||||
{
|
||||
private static Moderator _instance = null;
|
||||
|
||||
public static Moderator Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null) Init();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Init()
|
||||
{
|
||||
if (_instance == null) _instance = new Moderator();
|
||||
}
|
||||
|
||||
private ModerationEngine _moderationEngine;
|
||||
|
||||
private Moderator()
|
||||
{
|
||||
_moderationEngine = new ModerationEngine();
|
||||
Server.Instance.PlayerConnect += (sender, args) =>
|
||||
{
|
||||
#if DEBUG
|
||||
Utility.Logging.MetaLog($"Player {args.PlayerId} is connecting, starting ban checker");
|
||||
#endif
|
||||
CheckConnectingPlayerAsap(args.PlayerId).Run();
|
||||
};
|
||||
}
|
||||
|
||||
public bool DisconnectPlayerById(string publicId)
|
||||
{
|
||||
int? playerId = _moderationEngine.FindConnectedPlayerById(publicId);
|
||||
if (playerId.HasValue)
|
||||
{
|
||||
UserVerification.Instance.DisconnectPlayer(playerId.Value, NetworkDispatcherCode.GameDataVerificationFail);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool DisconnectPlayerByName(string name)
|
||||
{
|
||||
int? playerId = _moderationEngine.FindConnectedPlayerByName(name);
|
||||
if (playerId.HasValue)
|
||||
{
|
||||
UserVerification.Instance.DisconnectPlayer(playerId.Value, NetworkDispatcherCode.GameDataVerificationFail);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool BanPlayerById(string publicId)
|
||||
{
|
||||
List<string> bans = new List<string>(CLre.Config.bans);
|
||||
if (!bans.Contains(publicId))
|
||||
{
|
||||
bans.Add(publicId);
|
||||
CLre.Config.bans = bans.ToArray();
|
||||
}
|
||||
return DisconnectPlayerById(publicId);
|
||||
}
|
||||
|
||||
public bool BanPlayerByName(string name)
|
||||
{
|
||||
List<string> bans = new List<string>(CLre.Config.bans);
|
||||
if (!bans.Contains(name))
|
||||
{
|
||||
bans.Add(name);
|
||||
CLre.Config.bans = bans.ToArray();
|
||||
}
|
||||
return DisconnectPlayerByName(name);
|
||||
}
|
||||
|
||||
public bool IsModerator(string name)
|
||||
{
|
||||
foreach (string modName in CLre.Config.moderators)
|
||||
{
|
||||
if (string.Compare(name, modName, StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Guid? publicId = _moderationEngine.FindConnectedPlayerGuidByName(name);
|
||||
if (publicId.HasValue)
|
||||
{
|
||||
foreach (string modGuid in CLre.Config.moderators)
|
||||
{
|
||||
if (modGuid == publicId.ToString())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerator CheckConnectingPlayerAsap(int playerId)
|
||||
{
|
||||
while (Server.Instance.Players.Length <= playerId)
|
||||
{
|
||||
yield return null;
|
||||
yield return null;
|
||||
yield return null;
|
||||
yield return null;
|
||||
}
|
||||
var connector = Server.Instance.Players[playerId];
|
||||
if (CLre.Config.bans.Contains(connector.accountId.displayName)
|
||||
|| CLre.Config.bans.Contains(connector.accountId.publicId.ToString()))
|
||||
{
|
||||
#if DEBUG
|
||||
Utility.Logging.MetaLog($"Banned player {connector.accountId.displayName} ({connector.accountId.publicId}) tried to connect, kicking");
|
||||
#endif
|
||||
UserVerification.Instance.DisconnectPlayer(playerId, NetworkDispatcherCode.GameDataVerificationFail);
|
||||
}
|
||||
#if DEBUG
|
||||
else
|
||||
{
|
||||
Utility.Logging.MetaLog($"Player {connector.accountId.displayName} ({connector.accountId.publicId}) is not banned, skipping auto-kick");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ using GameServer;
|
|||
using HarmonyLib;
|
||||
using NetworkFramework.Server;
|
||||
using Svelto.Context;
|
||||
using User.Server;
|
||||
|
||||
namespace CLre_server.API.MainServer
|
||||
{
|
||||
|
@ -18,10 +19,15 @@ namespace CLre_server.API.MainServer
|
|||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null) _instance = new Server();
|
||||
if (_instance == null) Init();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Init()
|
||||
{
|
||||
if (_instance == null) _instance = new Server();
|
||||
}
|
||||
|
||||
// instance events
|
||||
public event EventHandler<StartingEventArgs> InitStart
|
||||
|
@ -79,9 +85,20 @@ namespace CLre_server.API.MainServer
|
|||
}
|
||||
}
|
||||
|
||||
public AccountIdServerNode[] Players
|
||||
{
|
||||
get => _serverDatabaseQueryEngine.GetConnectedAccounts();
|
||||
}
|
||||
|
||||
// fields
|
||||
|
||||
private ServerDatabaseQueryEngine _serverDatabaseQueryEngine;
|
||||
private ServerReadyEngine _serverReadyEngine;
|
||||
|
||||
private Server()
|
||||
{
|
||||
new ServerReadyEngine();
|
||||
_serverReadyEngine = new ServerReadyEngine();
|
||||
_serverDatabaseQueryEngine = new ServerDatabaseQueryEngine();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using CLre_server.API.Engines;
|
||||
using Game.DataLoader;
|
||||
using GameServer;
|
||||
using HarmonyLib;
|
||||
using Svelto.Context;
|
||||
using Svelto.DataStructures;
|
||||
using Svelto.ECS;
|
||||
using User.Server;
|
||||
|
||||
namespace CLre_server.API.MainServer
|
||||
{
|
||||
|
@ -45,4 +51,25 @@ namespace CLre_server.API.MainServer
|
|||
if (serverFrameworkDestroyed != null) serverFrameworkDestroyed(this, new StopEventArgs{});
|
||||
}
|
||||
}
|
||||
|
||||
class ServerDatabaseQueryEngine : ServerEnginePostBuild
|
||||
{
|
||||
public override void Ready()
|
||||
{
|
||||
}
|
||||
|
||||
public AccountIdServerNode[] GetConnectedAccounts()
|
||||
{
|
||||
FieldInfo f = AccessTools.Field(AccessTools.TypeByName("User.Server.AccountExclusiveGroups"), "accountGroup");
|
||||
ExclusiveGroup accountGroup = (ExclusiveGroup) f.GetValue(null);
|
||||
ReadOnlyCollectionStruct<AccountIdServerNode> accounts =
|
||||
entitiesDB.QueryEntityViews<AccountIdServerNode>(accountGroup);
|
||||
List<AccountIdServerNode> list = new List<AccountIdServerNode>();
|
||||
foreach (var a in accounts)
|
||||
{
|
||||
list.Add(a);
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
61
CLre_server/API/Utility/CardLifeUserAuthentication.cs
Normal file
61
CLre_server/API/Utility/CardLifeUserAuthentication.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace CLre_server.API.Utility
|
||||
{
|
||||
public static class CardLifeUserAuthentication
|
||||
{
|
||||
[Serializable]
|
||||
private struct AuthPayload
|
||||
{
|
||||
public string EmailAddress;
|
||||
public string Password;
|
||||
}
|
||||
|
||||
private const string LOGIN_URL = "https://live-auth.cardlifegame.com/api/auth/authenticate";
|
||||
|
||||
public delegate void OnResponse(AuthenticationResponse data);
|
||||
|
||||
public static IEnumerator Authenticate(string email, string password, OnResponse then)
|
||||
{
|
||||
UnityWebRequest req = new UnityWebRequest(LOGIN_URL, "POST");
|
||||
AuthPayload payload = new AuthPayload
|
||||
{
|
||||
EmailAddress = email,
|
||||
Password = password,
|
||||
};
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(JsonUtility.ToJson(payload));
|
||||
req.uploadHandler = new UploadHandlerRaw(bytes);
|
||||
req.downloadHandler = new DownloadHandlerBuffer();
|
||||
req.SetRequestHeader("Content-Type", "application/json");
|
||||
AsyncOperation op = req.SendWebRequest();
|
||||
while (!op.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (req.responseCode != 200)
|
||||
{
|
||||
Logging.LogError($"Authentication with email {email} returned code {req.responseCode} and was aborted. Response:\n{req.downloadHandler.text}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
AuthenticationResponse resp = JsonUtility.FromJson<AuthenticationResponse>(req.downloadHandler.text);
|
||||
then(resp);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct AuthenticationResponse
|
||||
{
|
||||
public string PublicId;
|
||||
public string EmailAddress;
|
||||
public string DisplayName;
|
||||
public bool Confirmed;
|
||||
public string Token;
|
||||
public uint ID;
|
||||
}
|
||||
}
|
|
@ -18,15 +18,18 @@ namespace CLre_server
|
|||
{
|
||||
public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name;
|
||||
|
||||
public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
public override string Version { get; } = "21Q3 " + Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
|
||||
internal static Harmony harmonyInstance = null;
|
||||
|
||||
private const string CONFIG_PATH = "CLre_server.json";
|
||||
|
||||
public static CLreConfig Config = CLreConfig.Default();
|
||||
|
||||
// called when Cardlife shuts down
|
||||
public override void OnApplicationQuit()
|
||||
{
|
||||
Config.ToFile(CONFIG_PATH);
|
||||
WebServer.Deinit();
|
||||
harmonyInstance.UnpatchAll();
|
||||
}
|
||||
|
@ -88,11 +91,14 @@ namespace CLre_server
|
|||
API.MainServer.Server.Instance.InitComplete += (_, __) => API.Utility.Logging.MetaLog("(!) Server successfully initialised");
|
||||
#endif
|
||||
// try to load config file
|
||||
Config = CLreConfig.FromFileSafely("CLre_server.json");
|
||||
Config = CLreConfig.FromFileSafely(CONFIG_PATH);
|
||||
// init config-dependent functionality
|
||||
WebServer.Init();
|
||||
API.Synergy.CLreEnforcer.Init();
|
||||
Tweaks.TerrainModificationExclusionZone.Init();
|
||||
Tweaks.Chat.ChatHandler.Init();
|
||||
API.MainServer.Server.Init();
|
||||
API.MainServer.Moderator.Init();
|
||||
// Log info
|
||||
API.Utility.Logging.MetaLog($"{Name} init complete.");
|
||||
}
|
||||
|
|
60
CLre_server/Tweaks/Chat/Attributes.cs
Normal file
60
CLre_server/Tweaks/Chat/Attributes.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
||||
namespace CLre_server.Tweaks.Chat
|
||||
{
|
||||
public class Attributes
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[System.AttributeUsage(System.AttributeTargets.Method)]
|
||||
public class ChatCommandAttribute : System.Attribute
|
||||
{
|
||||
public readonly string Name;
|
||||
private readonly Regex _pattern;
|
||||
private readonly Regex _usernamePattern;
|
||||
|
||||
public ChatCommandAttribute(string name, string pattern,
|
||||
string usernamePattern = null,
|
||||
RegexOptions options = RegexOptions.Compiled | RegexOptions.IgnoreCase)
|
||||
{
|
||||
this.Name = name;
|
||||
this._pattern = new Regex(pattern, options);
|
||||
this._usernamePattern = usernamePattern == null ? null : new Regex(pattern, options);
|
||||
Assembly asm = Assembly.GetCallingAssembly();
|
||||
if (!ChatConnectionEngine._assembliesToCheck.Contains(asm))
|
||||
{
|
||||
ChatConnectionEngine._assembliesToCheck.Add(asm);
|
||||
}
|
||||
|
||||
if (ChatHandler.IsAuthenticationReady && CLre.Config.chat_commands)
|
||||
{
|
||||
// Chat system is already started
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
public Regex GetPattern()
|
||||
{
|
||||
return _pattern;
|
||||
}
|
||||
|
||||
public Match RegexMatch(string sender, string message)
|
||||
{
|
||||
if (this._usernamePattern != null)
|
||||
{
|
||||
if (this._usernamePattern.IsMatch(sender))
|
||||
{
|
||||
return _pattern.Match(message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return _pattern.Match(message);
|
||||
}
|
||||
return System.Text.RegularExpressions.Match.Empty;
|
||||
}
|
||||
}
|
||||
}
|
21
CLre_server/Tweaks/Chat/AuthenticationEngine.cs
Normal file
21
CLre_server/Tweaks/Chat/AuthenticationEngine.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
namespace CLre_server.Tweaks.Chat
|
||||
{
|
||||
public class AuthenticationEngine: API.Engines.ServerEnginePostBuild
|
||||
{
|
||||
public API.Utility.AuthenticationResponse response = default;
|
||||
public bool IsAuthenticated = false;
|
||||
|
||||
public override void Ready()
|
||||
{
|
||||
API.Utility.CardLifeUserAuthentication.Authenticate(
|
||||
CLre.Config.email_address,
|
||||
CLre.Config.password,
|
||||
(data) =>
|
||||
{
|
||||
this.IsAuthenticated = true;
|
||||
this.response = data;
|
||||
API.Utility.Logging.Log("CLre chat credentials successfully authenticated");
|
||||
}).Run();
|
||||
}
|
||||
}
|
||||
}
|
12
CLre_server/Tweaks/Chat/ChatConfig.cs
Normal file
12
CLre_server/Tweaks/Chat/ChatConfig.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace CLre_server.Tweaks.Chat
|
||||
{
|
||||
[Serializable]
|
||||
public struct ChatConfig
|
||||
{
|
||||
public bool commands_enabled;
|
||||
public string email_address;
|
||||
public string password;
|
||||
}
|
||||
}
|
88
CLre_server/Tweaks/Chat/ChatConnectionEngine.cs
Normal file
88
CLre_server/Tweaks/Chat/ChatConnectionEngine.cs
Normal file
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using ExitGames.Client.Photon;
|
||||
using ExitGames.Client.Photon.Chat;
|
||||
using HarmonyLib;
|
||||
using Svelto.Context;
|
||||
|
||||
namespace CLre_server.Tweaks.Chat
|
||||
{
|
||||
public class ChatConnectionEngine: API.Engines.ServerEnginePostBuild, IWaitForFrameworkInitialization, IWaitForFrameworkDestruction
|
||||
{
|
||||
private bool _running = false;
|
||||
private ChatClient _chatClient;
|
||||
private ChatListener _chatListener;
|
||||
|
||||
public delegate void CommandHandler(Match messageMatch, ChatClient connection, string username);
|
||||
|
||||
private Dictionary<ChatCommandAttribute, CommandHandler> _handlers;
|
||||
|
||||
internal static List<Assembly> _assembliesToCheck = new List<Assembly>(new []{typeof(CLre).Assembly});
|
||||
|
||||
public override void Ready()
|
||||
{
|
||||
_running = true;
|
||||
}
|
||||
|
||||
private IEnumerator connectify()
|
||||
{
|
||||
LoadHandlers(); // find & load commands
|
||||
// wait for login to succeed (it may never)
|
||||
while (!ChatHandler.IsAuthenticationReady && _running)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
// login with authenticated credentials
|
||||
// shout-out to however made an identical AuthenticationValues struct in the global namespace
|
||||
ExitGames.Client.Photon.Chat.AuthenticationValues auth = new ExitGames.Client.Photon.Chat.AuthenticationValues();
|
||||
auth.AuthType = ExitGames.Client.Photon.Chat.CustomAuthenticationType.Custom;
|
||||
auth.AddAuthParameter("publicId", ChatHandler.PublicId);
|
||||
auth.AddAuthParameter("token", ChatHandler.Token);
|
||||
auth.UserId = ChatHandler.PublicId;
|
||||
_chatListener= new ChatListener(_handlers);
|
||||
_chatClient = new ChatClient(_chatListener, ConnectionProtocol.Udp);
|
||||
_chatListener._chatClient = _chatClient;
|
||||
_chatClient.Connect(Game.Utilities.CardLifePhotonSettings.PhotonChatAppID, "1.0", auth);
|
||||
// run forever (until server shutsdown)
|
||||
while (_running)
|
||||
{
|
||||
_chatClient.Service();
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnFrameworkInitialized()
|
||||
{
|
||||
connectify().Run();
|
||||
}
|
||||
|
||||
public void OnFrameworkDestroyed()
|
||||
{
|
||||
_running = false;
|
||||
}
|
||||
|
||||
private void LoadHandlers()
|
||||
{
|
||||
_handlers = new Dictionary<ChatCommandAttribute, CommandHandler>();
|
||||
foreach (Assembly asm in _assembliesToCheck.ToArray())
|
||||
{
|
||||
foreach (Type t in asm.GetTypes())
|
||||
{
|
||||
foreach (MethodInfo m in t.GetMethods(AccessTools.all))
|
||||
{
|
||||
ChatCommandAttribute attr = m.GetCustomAttribute<ChatCommandAttribute>();
|
||||
if (attr != null)
|
||||
{
|
||||
// TODO validate that method signature matches that of CommandHandler
|
||||
API.Utility.Logging.MetaLog($"{t.FullName}:{m.Name} is handling {attr.Name}");
|
||||
_handlers.Add(attr, (CommandHandler) Delegate.CreateDelegate(typeof(CommandHandler), m));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
CLre_server/Tweaks/Chat/ChatHandler.cs
Normal file
33
CLre_server/Tweaks/Chat/ChatHandler.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CLre_server.Tweaks.Chat
|
||||
{
|
||||
public static class ChatHandler
|
||||
{
|
||||
private static AuthenticationEngine _chatAuthEngine = null;
|
||||
private static ChatConnectionEngine _chatConnectionEngine = null;
|
||||
|
||||
internal static bool IsAuthenticationReady
|
||||
{
|
||||
get => _chatAuthEngine.IsAuthenticated;
|
||||
}
|
||||
|
||||
internal static string PublicId
|
||||
{
|
||||
get => _chatAuthEngine.response.PublicId;
|
||||
}
|
||||
|
||||
internal static string Token
|
||||
{
|
||||
get => _chatAuthEngine.response.Token;
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
if (!CLre.Config.chat_commands) return;
|
||||
_chatAuthEngine = new AuthenticationEngine();
|
||||
_chatConnectionEngine = new ChatConnectionEngine();
|
||||
}
|
||||
}
|
||||
}
|
116
CLre_server/Tweaks/Chat/ChatListener.cs
Normal file
116
CLre_server/Tweaks/Chat/ChatListener.cs
Normal file
|
@ -0,0 +1,116 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ExitGames.Client.Photon;
|
||||
using ExitGames.Client.Photon.Chat;
|
||||
|
||||
namespace CLre_server.Tweaks.Chat
|
||||
{
|
||||
public class ChatListener: IChatClientListener
|
||||
{
|
||||
internal ChatClient _chatClient;
|
||||
public static string ChatName;
|
||||
private Dictionary<ChatCommandAttribute, ChatConnectionEngine.CommandHandler> _handlers;
|
||||
|
||||
public ChatListener(Dictionary<ChatCommandAttribute, ChatConnectionEngine.CommandHandler> handlers)
|
||||
{
|
||||
_handlers = handlers;
|
||||
}
|
||||
|
||||
public void DebugReturn(DebugLevel level, string message)
|
||||
{
|
||||
API.Utility.Logging.Log($"ServerChatLog<{level}>: {message}");
|
||||
}
|
||||
|
||||
public void OnDisconnected()
|
||||
{
|
||||
API.Utility.Logging.MetaLog("Chat disconnected");
|
||||
}
|
||||
|
||||
public void OnConnected()
|
||||
{
|
||||
API.Utility.Logging.MetaLog("Chat connected");
|
||||
// autoconnect to server's chat room
|
||||
Room room = PhotonNetwork.room;
|
||||
ChatName = "";
|
||||
if (room != null)
|
||||
{
|
||||
ChatName = $"{room.Name}_chat_";
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool result = _chatClient.Subscribe(new[] {ChatName}, 10);
|
||||
API.Utility.Logging.MetaLog($"Subscribed to chat: {result}");
|
||||
}
|
||||
|
||||
public void OnChatStateChange(ChatState state)
|
||||
{
|
||||
API.Utility.Logging.MetaLog($"Chat state changed to {state}");
|
||||
}
|
||||
|
||||
public void OnGetMessages(string channelName, string[] senders, object[] messages)
|
||||
{
|
||||
if (channelName != ChatName) return; // just in case the server somehow gets subscribed to the wrong chat
|
||||
for (int i = 0; i < senders.Length; i++)
|
||||
{
|
||||
string message = messages[i].ToString();
|
||||
#if DEBUG
|
||||
API.Utility.Logging.MetaLog($"Chat: `{senders[i]}` said `{messages[i]}` in `{channelName}`");
|
||||
#endif
|
||||
if (messages[i].ToString().ToLower() == "hello server")
|
||||
{
|
||||
_chatClient.PublishMessage(channelName, $"Hi {senders[i]}!");
|
||||
}
|
||||
else if (messages[i].ToString().ToLower() == "hello world")
|
||||
{
|
||||
_chatClient.PublishMessage(channelName, $"Hello fellow programmer {senders[i]}!");
|
||||
}
|
||||
|
||||
if (message.StartsWith("/"))
|
||||
{
|
||||
string command = message.Substring(1);
|
||||
string username = stripUsernameTag(senders[i]);
|
||||
foreach (ChatCommandAttribute cca in _handlers.Keys)
|
||||
{
|
||||
var match = cca.RegexMatch(senders[i], command);
|
||||
if (match.Success)
|
||||
{
|
||||
_handlers[cca](match, _chatClient, username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPrivateMessage(string sender, object message, string channelName)
|
||||
{
|
||||
#if DEBUG
|
||||
API.Utility.Logging.MetaLog($"Chat (private): `{sender}` said `{message}` in `{channelName}`");
|
||||
#endif
|
||||
}
|
||||
|
||||
public void OnSubscribed(string[] channels, bool[] results)
|
||||
{
|
||||
API.Utility.Logging.MetaLog($"Subscribed");
|
||||
}
|
||||
|
||||
public void OnUnsubscribed(string[] channels)
|
||||
{
|
||||
API.Utility.Logging.MetaLog($"Unsubscribed");
|
||||
}
|
||||
|
||||
public void OnStatusUpdate(string user, int status, bool gotMessage, object message)
|
||||
{
|
||||
API.Utility.Logging.MetaLog($"Status update: {user}->{status} ({gotMessage}:{message})");
|
||||
}
|
||||
|
||||
private string stripUsernameTag(string sender)
|
||||
{
|
||||
if (!sender.Contains("]")) return sender;
|
||||
return sender.Split(']')[1].Trim();
|
||||
}
|
||||
}
|
||||
}
|
111
CLre_server/Tweaks/Chat/ModeratorCommands.cs
Normal file
111
CLre_server/Tweaks/Chat/ModeratorCommands.cs
Normal file
|
@ -0,0 +1,111 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using ExitGames.Client.Photon.Chat;
|
||||
|
||||
namespace CLre_server.Tweaks.Chat
|
||||
{
|
||||
public static class ModeratorCommands
|
||||
{
|
||||
[ChatCommand("KICK", "kick ([\\w\\d\\-_]+)")]
|
||||
public static void KickInButt(Match messageMatch, ChatClient connection, string sender)
|
||||
{
|
||||
if (!API.MainServer.Moderator.Instance.IsModerator(sender))
|
||||
{
|
||||
connection.PublishMessage(ChatListener.ChatName, "Ban failure: You're not a mod :(");
|
||||
return;
|
||||
}
|
||||
string target = messageMatch.Groups[1].Value;
|
||||
#if DEBUG
|
||||
API.Utility.Logging.MetaLog($"/kick {target}");
|
||||
#endif
|
||||
if (API.MainServer.Moderator.Instance.DisconnectPlayerById(target))
|
||||
{
|
||||
connection.PublishMessage(ChatListener.ChatName, $"Adios {target}");
|
||||
}
|
||||
else if (API.MainServer.Moderator.Instance.DisconnectPlayerByName(target))
|
||||
{
|
||||
connection.PublishMessage(ChatListener.ChatName, $"Bye bye {target}");
|
||||
}
|
||||
else
|
||||
{
|
||||
connection.PublishMessage(ChatListener.ChatName, "Kick failure: User not found :(");
|
||||
}
|
||||
}
|
||||
|
||||
[ChatCommand("BAN", "ban ([\\w\\d\\-_]+)")]
|
||||
public static void BanishIdiot(Match messageMatch, ChatClient connection, string sender)
|
||||
{
|
||||
if (!API.MainServer.Moderator.Instance.IsModerator(sender))
|
||||
{
|
||||
connection.PublishMessage(ChatListener.ChatName, "Ban failure: You're not a mod :(");
|
||||
return;
|
||||
}
|
||||
string target = messageMatch.Groups[1].Value;
|
||||
#if DEBUG
|
||||
API.Utility.Logging.MetaLog($"/ban {target}");
|
||||
#endif
|
||||
if (API.MainServer.Moderator.Instance.BanPlayerById(target))
|
||||
{
|
||||
connection.PublishMessage(ChatListener.ChatName, $"And {target} is no more!");
|
||||
}
|
||||
else if (API.MainServer.Moderator.Instance.BanPlayerByName(target))
|
||||
{
|
||||
connection.PublishMessage(ChatListener.ChatName, $"And {target} was never seen again...");
|
||||
}
|
||||
else
|
||||
{
|
||||
connection.PublishMessage(ChatListener.ChatName, "Ban failure: User not found :(");
|
||||
}
|
||||
}
|
||||
|
||||
[ChatCommand("(SH)OPIFY", "(mod|op) ([\\w\\d\\-_]+)")]
|
||||
public static void GoPro(Match messageMatch, ChatClient connection, string sender)
|
||||
{
|
||||
if (!API.MainServer.Moderator.Instance.IsModerator(sender))
|
||||
{
|
||||
connection.PublishMessage(ChatListener.ChatName, "Op failure: You're not a mod :(");
|
||||
return;
|
||||
}
|
||||
string target = messageMatch.Groups[2].Value;
|
||||
#if DEBUG
|
||||
API.Utility.Logging.MetaLog($"/op {target}");
|
||||
#endif
|
||||
List<string> moderators = new List<string>(CLre.Config.moderators);
|
||||
moderators.Add(target);
|
||||
CLre.Config.moderators = moderators.ToArray();
|
||||
connection.PublishMessage(ChatListener.ChatName, $"Promoted {target} to moderator");
|
||||
}
|
||||
|
||||
[ChatCommand("De(SH)OPIFY", "(demod|deop) ([\\w\\d\\-_]+)")]
|
||||
public static void AntiProton(Match messageMatch, ChatClient connection, string sender)
|
||||
{
|
||||
if (!API.MainServer.Moderator.Instance.IsModerator(sender))
|
||||
{
|
||||
connection.PublishMessage(ChatListener.ChatName, "DeOp failure: You're not a mod :(");
|
||||
return;
|
||||
}
|
||||
string target = messageMatch.Groups[2].Value;
|
||||
#if DEBUG
|
||||
API.Utility.Logging.MetaLog($"/deop {target}");
|
||||
#endif
|
||||
List<string> moderators = new List<string>(CLre.Config.moderators);
|
||||
if (moderators.Remove(target))
|
||||
{
|
||||
CLre.Config.moderators = moderators.ToArray();
|
||||
connection.PublishMessage(ChatListener.ChatName, $"Demoted {target} to regular user");
|
||||
}
|
||||
connection.PublishMessage(ChatListener.ChatName, "DeOp failure: User not found :(");
|
||||
}
|
||||
|
||||
[ChatCommand("ECHO", "echo (.+)$")]
|
||||
public static void EchoEchoEcho(Match messageMatch, ChatClient connection, string sender)
|
||||
{
|
||||
string target = messageMatch.Groups[1].Value;
|
||||
#if DEBUG
|
||||
API.Utility.Logging.MetaLog($"/echo {target}");
|
||||
#endif
|
||||
connection.PublishMessage(ChatListener.ChatName, target);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,7 +23,8 @@ namespace CLre_server.WebStatus
|
|||
|
||||
if (WebServer.MainInstance != null && WebServer.MainInstance.IsRunning)
|
||||
{
|
||||
|
||||
// Web server is already running
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace CLre_server.WebStatus
|
|||
{
|
||||
if (!HttpListener.IsSupported)
|
||||
{
|
||||
API.Utility.Logging.LogWarning("HTTP Server is unsupported on earlier Windows versions. It will fail to start.");
|
||||
API.Utility.Logging.LogWarning("HTTP Server is unsupported on earlier Windows versions. CLre webserver will fail to start.");
|
||||
}
|
||||
_httpListener = new HttpListener();
|
||||
_httpListener.Prefixes.Add($"http://{ip}:{port}/");
|
||||
|
|
Loading…
Reference in a new issue