@@ -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()); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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."); | |||
} | |||
@@ -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; | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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)); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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}/"); | |||