// 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); }
//Get a JSON string representing a list of the last 10 messages public string ChatMessageList(int user) { MessageListJSONObject jsonMessages = new MessageListJSONObject(); // List<UserMessageJSONObject> visibleMessages; // // lock (managerLock) // { // Log("Enter chatmessagelist lock", MyExtensions.Logging.LogLevel.Locks); // //Messages are all readonly, so it's OK to have just references // visibleMessages = messages.Where(x => x.Display && (DateTime.Now - x.PostTime()).TotalDays < 1.0).ToList(); // Log("Exit chatmessagelist lock", MyExtensions.Logging.LogLevel.Locks); // } jsonMessages.messages = history.Where(x => AllAcceptedTagsForUser(user).Contains(x.Key)).SelectMany(x => x.Value).OrderBy(x => x.id).ToList(); // // foreach (string tag in AllAcceptedTagsForUser(user)) // { // List<UserMessageJSONObject> tagMessages = visibleMessages.Where(x => x.tag == tag).ToList(); // for (int i = 0; i < Math.Min(MaxMessageSend, tagMessages.Count); i++) // jsonMessages.messages.Add(tagMessages[tagMessages.Count - 1 - i]); // } //Oops, remember we added them in reverse order. Fix that //jsonMessages.messages = jsonMessages.messages.OrderBy(x => x.id).ToList(); return jsonMessages.ToString(); }
//Get a JSON string representing a list of the last X messages public string ChatMessageList(int user, int messageCount = 2) { MessageListJSONObject jsonMessages = new MessageListJSONObject(); //This may change to a legacy number, like 10 if (messageCount < 1) messageCount = ChatSettings.MaxMessageSend; lock (managerLock) { jsonMessages.messages = history .Where(x => AllAcceptedTagsForUser(user).Contains(x.Key)) .SelectMany(x => x.Value.Take(messageCount)) .Where(x => users.ContainsKey(x.uid) && (!users[x.uid].ShadowBanned || x.uid == user)) .OrderBy(x => x.id).ToList(); bool animatedAvatars = GetUser(user).AnimatedAvatars; //A HORRIBLE hack! This will make the avatars bounce back and forth! foreach(UserMessageJSONObject message in jsonMessages.messages) message.avatar = animatedAvatars ? GetUser(message.uid).Avatar : GetUser(message.uid).AvatarStatic; } return jsonMessages.ToString(); }