// public override bool SaveFiles() // { // if(MyExtensions.MySerialize.SaveObject<List // } public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users) { List<JSONObject> outputs = new List<JSONObject>(); if (command.Command == "drawsubmit") { //unsavedMessages.Add(new DrawingInfo() { Drawing = command.Arguments[0], User = user, postTime = DateTime.Now }); UserMessageJSONObject drawingMessage = new UserMessageJSONObject(user, command.Arguments[0], command.tag); drawingMessage.encoding = Nickname; drawingMessage.spamValue = 0.50; drawingMessage.SetUnspammable(); outputs.Add(drawingMessage); } return outputs; //base.ProcessCommand(command, user, users); }
// protected override void OnError(ErrorEventArgs e) // { // if(e.Message != null) // Logger.Error("UID: " + uid + " - " + e.Message, "WebSocket"); // if(e.Exception != null) // Logger.Error(e.Exception.ToString(), "WebSocket"); // // //OK let's see what happens here // //OnClose(null); // //base.OnError(e); // } //I guess this is WHENEVER it receives a message? public override void ReceivedMessage(string rawMessage) { //Log("Got message: " + rawMessage, MyExtensions.Logging.LogLevel.Debug); ResponseJSONObject response = new ResponseJSONObject(); response.result = false; dynamic json = new Object(); string type = ""; //You HAVE to do this, even though it seems pointless. Users need to show up as banned immediately. ThisUser.PullInfoFromQueryPage(); //Before anything else, log the amount of incoming data if (!string.IsNullOrEmpty(rawMessage)) { manager.Bandwidth.AddIncoming(rawMessage.Length + HeaderSize); } //First, just try to parse the JSON they gave us. If it's absolute //garbage (or just not JSON), let them know and quit immediately. try { json = JsonConvert.DeserializeObject(rawMessage); type = json.type; response.from = type; } catch { response.errors.Add("Could not parse JSON"); } //If we got a bind message, let's try to authorize this channel. if (type == "bind") { if (uid > 0) { response.errors.Add("Received another bind message, but you've already been authenticated."); } else { try { //First, gather information from the JSON. This is so that if //the json is invalid, it will fail as soon as possible string key = (string)json.key; int newUser = (int)json.uid; //Oops, username was invalid if (newUser <= 0) { Log("Tried to bind a bad UID: " + newUser); response.errors.Add("UID was invalid"); } else { //List<Chat> removals; string error; if (!manager.CheckAuthentication(newUser, key, out error)) //newUser, key, out removals, out error)) { response.errors.Add(error); } else { //Before we do anything, remove other chatting sessions foreach (Chat removeChat in GetAllUsers().Select(x => (Chat)x).Where(x => x.UID == newUser && x != this)) removeChat.CloseSelf(); //Sessions.CloseSession(removeChat.ID); uid = newUser; //BEFORE adding, broadcast the "whatever has entered the chat" message if (ThisUser.ShowMessages) { manager.Broadcast(QuickParams(ChatTags.Join), new SystemMessageJSONObject(), new List<Chat> { this }); } MySend(NewSystemMessageFromTag(QuickParams(ChatTags.Welcome)).ToString()); ChatTags enterSpamWarning = ThisUser.JoinSpam(); if (enterSpamWarning != ChatTags.None) MySend(NewWarningFromTag(QuickParams(enterSpamWarning)).ToString()); //BEFORE sending out the user list, we need to perform onPing so that it looks like this user is active sessionID = ThisUser.PerformOnChatEnter(); // if (!ThisUser.PerformOnChatEnter()) // Log("Invalid session entry. Sessions may be broken", LogLevel.Warning); manager.BroadcastUserList(); Log("Authentication complete: UID " + uid + " maps to username " + ThisUser.Username + (ThisUser.CanStaffChat ? "(staff)" : "")); response.result = true; List<JSONObject> outputs = new List<JSONObject>(); Dictionary<int, UserInfo> currentUsers = manager.UsersForModules(); //Also do some other crap foreach (Module module in manager.GetModuleListCopy()) { if (Monitor.TryEnter(module.Lock, manager.ChatSettings.MaxModuleWait)) { try { outputs.AddRange(module.OnUserJoin(currentUsers[ThisUser.UID], currentUsers)); } finally { Monitor.Exit(module.Lock); } } else { Log("Skipped " + module.ModuleName + " join processing", MyExtensions.Logging.LogLevel.Warning); } } OutputMessages(outputs, ThisUser.UID); //Finally, output the "Yo accept dis" thing if they haven't already. if(!ThisUser.AcceptedPolicy) { MessageListJSONObject emptyMessages = new MessageListJSONObject(); UserListJSONObject emptyUsers = new UserListJSONObject(); ModuleJSONObject policy = new ModuleJSONObject(ChatServer.Policy); ModuleJSONObject accept = new ModuleJSONObject("\nYou must accept this chat policy before " + "using the chat. Type /accept if you accept the chat policy\n"); MySend(emptyMessages.ToString(), true); MySend(emptyUsers.ToString(), true); MySend(policy.ToString(), true); MySend(accept.ToString(), true); } else if(ThisUser.ShouldPolicyRemind) { ModuleJSONObject policy = new ModuleJSONObject(ChatServer.Policy); MySend(policy.ToString()); ThisUser.PerformOnReminder(); } //Now set up the IRC relay. Oh boy, let's hope this works! /*relay = new SimpleIRCRelay(manager.IrcServer, manager.IrcChannel, ThisUser.Username, Logger); relay.ConnectAsync();*/ //relay.IRCRelayMessageEvent += OnIrcRelayMessage; } } } catch { response.errors.Add("BIND message was missing fields"); } } } else if (type == "ping") { lastPing = DateTime.Now; bool active = true; try { active = (bool)json.active; //relay.Ping(); } catch (Exception messageError) { response.errors.Add("Internal server error: " + messageError/*.Message*/); } ThisUser.PerformOnPing(active); UpdateActiveUserList(null, null); } else if (type == "message") { try { //First, gather information from the JSON. This is so that if //the json is invalid, it will fail as soon as possible string key = json.key; string message = System.Security.SecurityElement.Escape((string)json.text); string tag = json.tag; //These first things don't increase spam score in any way if (string.IsNullOrWhiteSpace(message)) { response.errors.Add("No empty messages please"); } else if (!manager.CheckKey(uid, key)) { Log("Got invalid key " + key + " from " + UserLogString); response.errors.Add("Your key is invalid"); } else if (!ThisUser.AcceptedPolicy) { if(message != "/accept") { response.errors.Add("The only command available right now is /accept"); } else { ModuleJSONObject acceptSuccess = new ModuleJSONObject("You have accepted the SmileBASIC Source " + "chat policy. Please use the appropriate chat tab for discussion about SmileBASIC or off-topic " + "subjects!"); MySend(acceptSuccess.ToString(), true); Thread.Sleep(2000); ThisUser.AcceptPolicy(); ThisUser.PerformOnReminder(); response.result = true; MySend(manager.ChatUserList(UID)); MySend(manager.ChatMessageList(UID)); } } else if (ThisUser.Blocked) { response.errors.Add(NewWarningFromTag(QuickParams(ChatTags.Blocked)).message); } else if (ThisUser.Banned) { response.errors.Add("You are banned from chat for " + StringExtensions.LargestTime(ThisUser.BannedUntil - DateTime.Now)); } else if (tag == "admin" && !ThisUser.CanStaffChat || tag == manager.ChatSettings.GlobalTag && !ThisUser.CanGlobalChat) { response.errors.Add("You can't post messages here. I'm sorry."); } else if (!manager.ValidTagForUser(UID, tag)) { response.errors.Add("Your post has an unrecognized tag. Cannot display"); } else { Dictionary<int, UserInfo> currentUsers = manager.UsersForModules(); List<JSONObject> outputs = new List<JSONObject>(); UserMessageJSONObject userMessage = new UserMessageJSONObject(ThisUser, message, tag); UserCommand userCommand; Module commandModule; string commandError = ""; //Step 1: parse a possible command. If no command is parsed, no module will be written. if (TryCommandParse(userMessage, out commandModule, out userCommand, out commandError)) { Log("Trying to use module " + commandModule.ModuleName + " to process command " + userCommand.message + " from " + ThisUser.Username, MyExtensions.Logging.LogLevel.SuperDebug); //We found a command. Send it off to the proper module and get the output if (Monitor.TryEnter(commandModule.Lock, manager.ChatSettings.MaxModuleWait)) { try { outputs.AddRange(commandModule.ProcessCommand(userCommand, currentUsers[ThisUser.UID], currentUsers)); } finally { Monitor.Exit(commandModule.Lock); } } else { response.errors.Add("The chat server is busy and can't process your command right now"); userMessage.SetHidden(); userMessage.SetUnspammable(); } //do not update spam score if command module doesn't want it if (!userCommand.MatchedCommand.ShouldUpdateSpamScore) userMessage.SetUnspammable(); //For now, simply capture all commands no matter what. userMessage.SetHidden(); //userMessage.SetCommand(); Log("Module " + commandModule.ModuleName + " processed command from " + UserLogString, MyExtensions.Logging.LogLevel.Debug); } else { //If an error was given, add it to our response if (!string.IsNullOrWhiteSpace(commandError)) { response.errors.Add("Command error: " + commandError); userMessage.SetHidden(); userMessage.SetUnspammable(); } } ChatTags warning = manager.AddMessage(userMessage); //Send off on relay /*if(userMessage.Display && userMessage.tag == manager.IrcTag) { if(relay.SendMessage(userMessage.message)) Logger.LogGeneral("Sent message on IRC relay!", MyExtensions.Logging.LogLevel.SuperDebug); else Logger.LogGeneral("Couldn't send on IRC relay!", MyExtensions.Logging.LogLevel.SuperDebug); }*/ if (warning != ChatTags.None) outputs.Add(NewWarningFromTag(QuickParams(warning))); response.result = response.errors.Count == 0; //Now send out userlist if active status changed UpdateActiveUserList(null, null); //Since we added a new message, we need to broadcast. if (response.result && userMessage.Display) manager.BroadcastMessageList(); //CRASH ALMOST CERTAINLY HAPPENS HERE!!!!!!!###$$$$$!!!!!!!!!!*$*$*$* //Step 2: run regular message through all modules' regular message processor (probably no output?) if(manager.ChatSettings.AcceptedTags.Contains(userMessage.tag)) { foreach (Module module in manager.GetModuleListCopy()) { if (Monitor.TryEnter(module.Lock, manager.ChatSettings.MaxModuleWait)) { try { module.ProcessMessage(userMessage, currentUsers[ThisUser.UID], currentUsers); } finally { Monitor.Exit(module.Lock); } } else { Log("Skipped " + module.ModuleName + " message processing", MyExtensions.Logging.LogLevel.Warning); } } } //Step 3: run all modules' post processor (no message required) //Is this even necessary? It was necessary before because the bot ran on a timer. Without a timer, //each module can just specify that it wants to do things at random points with its own timer. //Step 4: iterate over returned messages and send them out appropriately OutputMessages(outputs, ThisUser.UID, tag); //end of regular message processing } } catch (Exception messageError) { response.errors.Add("Internal server error: " + messageError/*.Message*/); //response.errors.Add("Message was missing fields"); } } // else if (type == "createroom") // { // try // { // List<int> users = json.users.ToObject<List<int>>(); // string error; // // if(!manager.CreatePMRoom(new HashSet<int>(users), ThisUser.UID, out error)) // { // response.errors.Add(error); // } // else // { // MySend((new SystemMessageJSONObject("You created a chat room for " + string.Join(", ", users.Select(x => manager.GetUser(x).Username)))).ToString()); // manager.BroadcastUserList(); // } // } // catch // { // response.errors.Add("Could not parse PM creation room message"); // } // } else if (type == "request") { try { string wanted = json.request; if(wanted == "userList") { MySend(manager.ChatUserList(UID)); response.result = true; } else if (wanted == "messageList") { MySend(manager.ChatMessageList(UID)); response.result = true; } else { response.errors.Add("Invalid request field"); } } catch { response.errors.Add("Request was missing fields"); } } //Send the "OK" message back. MySend(response.ToString(), true); }