public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         User thisRealUser = ChatRunner.Server.GetUser(user.UID);

         if (command.Command == "uptonogood")
         {
            /*if (!user.ChatControl)
               return FastMessage("This command doesn't *AHEM* exist");*/

            thisRealUser.Hiding = !thisRealUser.Hiding;

            if (thisRealUser.Hiding)
            {
               ChatRunner.Server.BroadcastUserList();
               ChatRunner.Server.Broadcast(new LanguageTagParameters(ChatTags.Leave, thisRealUser), new SystemMessageJSONObject());

               return FastMessage("You're now hiding. Hiding persists across reloads. Be careful, you can still use commands!");
            }
            else
            {
               UnhideUser(user.UID);
               /*thisRealUser.LastJoin = DateTime.Now;
               ChatRunner.Server.BroadcastUserList();
               ChatRunner.Server.Broadcast(new LanguageTagParameters(ChatTags.Join, thisRealUser), new SystemMessageJSONObject());*/

               return FastMessage("You've come out of hiding.");
            }
         }

         return new List<JSONObject>();
      }
//      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);
      }
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         List<JSONObject> outputs = new List<JSONObject>();
         ModuleJSONObject moduleOutput;

         switch(command.Command)
         {
            case "me":
               moduleOutput = new ModuleJSONObject();
               moduleOutput.broadcast = true;
               moduleOutput.message = user.Username + " " + command.Arguments[0]; //System.Security.SecurityElement.Escape(command.Arguments[0]);
               moduleOutput.tag = command.tag;
               outputs.Add(moduleOutput);
               break;

            case "emotes":
               moduleOutput = new ModuleJSONObject();
               moduleOutput.message = "Emote list:\n";
             
               try
               {
                  string htmlCode;

                  using (WebClient client = new WebClient())
                  {
                     htmlCode = client.DownloadString(GetOption<string>("emoteLink"));
                  }

                  EmoteJSONObject emotes = JsonConvert.DeserializeObject<EmoteJSONObject>(htmlCode);

                  foreach(string emote in emotes.mapping.Keys)
                     moduleOutput.message += "\n" + emotes.format.Replace("emote", emote);
               }
               catch (Exception e)
               {
                  moduleOutput.message = "Sorry, could not retrieve emote list!\nException: " + e.ToString();
               }

               outputs.Add(moduleOutput);
               break;
         }

         return outputs;
      }
      //Get a copy of the list of modules. If a user is given, only get the modules that this user can see.
      public List<Module> GetModuleListCopy(int user = 0)
      {
         //If a user was given, only return modules that the user can see or use
         if (user > 0)
         {
            UserInfo realUser = new UserInfo(GetUser(user), LoggedInUsers().ContainsKey(user));
            List<Module> returnModules = modules.Where(x => !x.Hidden(realUser)).ToList();

            return returnModules;
         }

         return new List<Module>(modules);
      }
 public UserMessageJSONObject(UserInfo user, string message, string tag = "") : base("message")
 {
    this.uid = user.UID;
    this.username = user.Username;
    this.avatar = user.Avatar;
    this.stars = user.StarString;
    this.level = user.Level;
    this.message = message;
    this.tag = tag;
    this.postTime = DateTime.Now;
    this.id = Interlocked.Increment(ref NextID);
    this.badges = user.Badges;
 }
		// For a given player, get their quick information (used for "top 5" lists and junk)
		public string QuickInfo(UserInfo user)
		{
         if (!collectors.ContainsKey(user.UID))
				return "";

         return user.Username + " " + collectors[user.UID].StarString() + " " + collectors[user.UID].Journal.CompletionCount() + "% ";
		}
      //THIS is the function you're looking for. When the chat server detects a command for your module, it passes it along
      //to this function. You can process the command here, then send the output as the return object. Notice that the return
      //is a list; you can return multiple messages per command. This function is called ONCE for each command detected. 
      //"user" is the user who performed the command; the class UserInfo contains a lot of information about a user, so
      //use it as needed. The provided dictionary is a list of ALL users ever seen by the chat, so it's like the chat server's
      //database. Each UserInfo object has a "LoggedIn" field, so you can tell if they're logged in or not. "command" has
      //two important fields: "Command" and "Arguments". Command is simply the string containing just the command performed,
      //and Arguments is a list (array) of the arguments, each as a string. The returned messages are in a JSON format, which
      //is masked by the JSONObject class. You will almost ALWAYS be returning one or more ModuleJSONObjects, which is a 
      //chat server message specific to modules. You can also return system messages, etc. All the X_JSONObjects classes like
      //ModuleJSONObject derive from the JSONObject class, so you can return any of them. The "message" field of the 
      //ModuleJSONObject holds the output, and you can change the recipient by adding users (as integers) to the "recipients"
      //field. If you do not specify any recipients, it defaults to the sender (this is usually what you want). If you
      //want to broadcast a message to everyone (please don't do this for every command), you can set the "broadcast" field
      //to true.
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         //This is what we're returning from our function
         List<JSONObject> outputs = new List<JSONObject>();

         //Our commands (and probably yours too) usually returns just one message, so that's what this is.
         //It gets added to the above "outputs" list.
         ModuleJSONObject moduleOutput = new ModuleJSONObject();

         List<UserStatistics> allStats = userStatistics.Select(x => x.Value).ToList();

         //Run different code depending on the command (duh)
         switch(command.Command)
         {
            case "mystatistics":
               //Always use the UID, NOT the username to identifiy users. Usernames can change.
               if (!userStatistics.ContainsKey(user.UID))
               {
                  //Here, we're setting the message that will be displayed for this chat message object.
                  moduleOutput.message = "You have no statistics yet. You will after this message!";
               }
               else
               {
                  moduleOutput.message = GetUserStats(user);
               }

               //We add just the one output to our list of outputs (we only want to output one thing)
               outputs.Add(moduleOutput);
               break;

            case "statistics":
               if (command.Arguments.Count == 0)
               {
                  moduleOutput.message = "---Global Chat Statistics---\n";
                  moduleOutput.message += "Total messages: " + allStats.Sum(x => x.TotalMessages) + "\n";
                  moduleOutput.message += "Average message size: " + (allStats.Count == 0 ? 0 : (int)(allStats.Sum(x => x.AverageMessageLength) / allStats.Count)) + " characters\n";
                  moduleOutput.message += "Total users seen: " + allStats.Count + "\n";
                  moduleOutput.message += "Total user chat time: " + StringExtensions.LargestTime(new TimeSpan(users.Sum(x => x.Value.TotalChatTime.Ticks))) + "\n";
                  moduleOutput.message += "Average session time: " + (users.Count == 0 ? "error" : StringExtensions.LargestTime(new TimeSpan(users.Sum(x => x.Value.AverageSessionTime.Ticks) / users.Count))) + "\n";
                  outputs.Add(moduleOutput);
               }
               else
               {
                  //THIS IS IMPORTANT! Arguments containing a user will be given to you as a USERNAME! However, you want
                  //to store their information based on their UID. This function "GetUserFromArgument()" will search through
                  //the given dictionary to find you the user with the given username. It should always succeed, and puts the
                  //result in the "out" parameter. You probably don't need to check the return of GetUserFromArgument like I
                  //do, but just to be save, if it fails, you can just fail with a generic error. AddError adds a generic
                  //module error to the given list of outputs, then you can return outputs and it'll contain that error.
                  UserInfo findUser;
                  if (!GetUserFromArgument(command.Arguments[0], users, out findUser))
                  {
                     AddError(outputs);
                     break;
                  }

                  moduleOutput.message = GetUserStats(findUser);
                  outputs.Add(moduleOutput);
               }
               break;
         }

         return outputs;
      }
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         List<JSONObject> outputs = new List<JSONObject>();
         ModuleJSONObject moduleOutput = new ModuleJSONObject();
         VoteBallot ballot;
         string error = "";
         string output = "";
         long pollNumber = 0;

         if (!userBallots.ContainsKey(user.UID))
            userBallots.Add(user.UID, new List<VoteBallot>());

         try
         {
            switch(command.Command)
            {
               case "pollcreate":
                  int maxPolls = MaxUserPolls * (user.CanStaffChat ? 5 : 1);
                  if (userBallots[user.UID].Count >= maxPolls)
                     return FastMessage("You've reached the maximum amount of allowed polls (" + maxPolls + ") and cannot post a new one", true);
                  else if (command.ArgumentParts[1].Count > MaxPollChoices)
                     return FastMessage("There are too many choices in your poll! The max is " + MaxPollChoices, true);

                  VoteBallot newBallot = new VoteBallot(command.Arguments[0], new HashSet<string>(command.ArgumentParts[1]));

                  if (newBallot.GetChoices().Count < 2)
                     return FastMessage("Your poll must have at least 2 options", true);

                  userBallots[user.UID].Add(newBallot);
                  moduleOutput.broadcast = true;
                  moduleOutput.message = "A new poll has been created by " + user.Username + ":\n\n" + userBallots[user.UID].Last();
                  outputs.Add(moduleOutput);

                  break;

               case "vote":

                  if(!long.TryParse(command.Arguments[1], out pollNumber))
                     return FastMessage("Your poll number is out of bounds!", true);

                  if(!GetBallot(pollNumber, false, out ballot))
                  {
                     return FastMessage("There is no open poll with ID " + pollNumber);
                  }
                  else
                  {
                     if(!ballot.AddVote(user.UID, command.Arguments[0], out error))
                        return FastMessage(error);
                     else
                        return FastMessage("You voted on this poll: \n\n" + ballot.GetResultString(user.UID));
                  }

               case "polls":
                  
                  output = "The top polls right now: \n";
                  output += PrintList(userBallots.SelectMany(x => x.Value).OrderByDescending(x => x.TotalVotes).Take(10));

                  return FastMessage(output);

               case "poll":
                  
                  if(!long.TryParse(command.Arguments[0], out pollNumber))
                     return FastMessage("Your poll number is out of bounds!", true);

                  if(!GetBallot(pollNumber, true, out ballot))
                  {
                     return FastMessage("There is no open poll with ID " + pollNumber);
                  }
                  else
                  {
                     output = "";
                     bool closed = archivedBallots.SelectMany(x => x.Value).Contains(ballot);

                     if(ballot.DidVote(user.UID) || closed)
                        output = ballot.GetResultString(user.UID);
                     else
                        output = ballot.ToString();

                     int ballotCreator = userBallots.Union(archivedBallots).First(x => x.Value.Contains(ballot)).Key;
                     output += "\nPoll by: " + users[ballotCreator].Username + (closed ? " (closed)" : "");

                     return FastMessage(output);
                  }

               case "pollclose":

                  if(!long.TryParse(command.Arguments[0], out pollNumber))
                     return FastMessage("Your poll number is out of bounds!", true);

                  if(!userBallots[user.UID].Any(x => x.ID == pollNumber))
                     return FastMessage("You don't have any open polls with this ID!");

                  if(!GetBallot(pollNumber, false, out ballot))
                  {
                     return FastMessage("There is no open poll with ID " + pollNumber);
                  }
                  else
                  {
                     if(!archivedBallots.ContainsKey(user.UID))
                        archivedBallots.Add(user.UID, new List<VoteBallot>());

                     archivedBallots[user.UID].Add(ballot);
                     userBallots[user.UID].Remove(ballot);

                     moduleOutput.broadcast = true;
                     moduleOutput.message = "The poll " + ballot.Title + " has just been closed by " + user.Username + ". The results:\n\n" + ballot.GetResultString();
                     outputs.Add(moduleOutput);
                  }

                  break;

               case "pollsearch":

                  List<Tuple<double, VoteBallot>> sortedBallots = new List<Tuple<double, VoteBallot>>();

                  foreach(VoteBallot searchBallot in userBallots.SelectMany(x => x.Value).Union(archivedBallots.SelectMany(x => x.Value)))
                     sortedBallots.Add(Tuple.Create(StringExtensions.StringDifference(command.Arguments[0].ToLower(), searchBallot.Title.ToLower()), searchBallot));

                  output = "These ballots have a similar title: \n";
                  output += PrintList(sortedBallots.OrderBy(x => x.Item1).Select(x => x.Item2).Take(SearchResults));

                  return FastMessage(output);

               case "pollsopen":

                  output = "Your open polls right now are: \n" + PrintList(userBallots[user.UID]);
                  return FastMessage(output);
            }
         }
         catch(Exception e)
         {
            return new List<JSONObject>() { 
               new ModuleJSONObject() { 
                  message = "Something terrible happened in the Vote module: " + e, broadcast = true 
               } };
         }

         return outputs;
      }
      public LanguageTagParameters(ChatTags tag, User user, User userForDictionary = null)
      {
         if (userForDictionary == null)
            userForDictionary = user;

         Tag = tag;
         this.user = new UserInfo(user, true);
         Replacements = LanguageTags.QuickDictionary(new UserInfo(userForDictionary, true));
      }
 public override void ProcessMessage(UserMessageJSONObject message, UserInfo user, Dictionary<int, UserInfo> users)
 {
    if(message.Display)
       unsavedMessages.Add(message);
 }
 public override bool Hidden(UserInfo user)
 {
    return true;
 }
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         try
         {
   			//Match match;
            string cmd = command.Command;
            int myUID = user.UID;

            UserInfo player;
            ModuleJSONObject mainOutput = new ModuleJSONObject();
            List<JSONObject> outputs = new List<JSONObject>();

            TryRegister(user.UID);

   			//Top collectors
   			#region topcollectors
            if (cmd == "cgametop")
   			{
   				var topCollectors = collectors.ToList().OrderByDescending(x => x.Value.Score()).ToList();
   				string output = "The top collectors are:";

   				for (int i = 0; i < 5; i++)
   					if (i < topCollectors.Count())
   						output += "\n" + (i + 1) + ": " + QuickInfo(users[topCollectors[i].Key]);

               return QuickQuit(output);
   			}
   			#endregion

   			//Close collectors
   			#region closecollectors
            else if (cmd == "cgameclose")
   			{
   				var topCollectors = collectors.ToList().OrderByDescending(x => x.Value.Score()).ToList();
               int index = topCollectors.FindIndex(x => x.Key == myUID);

               string output = "Your competitors are:";

   				for (int i = index - 2; i <= index + 2; i++)
   					if (i < topCollectors.Count() && i >= 0)
   						output += "\n" + (i + 1) + ": " + QuickInfo(users[topCollectors[i].Key]);

               return QuickQuit(output);
   			}
   			#endregion

   			//Collect your daily coins
   			#region coinrestock
            else if (cmd == "cgamerestock")
   			{
   				if (collectors[myUID].RestockCoins(DateTime.Now))
   				{
                  return QuickQuit("You received " + collectors[myUID].RestockCoinsAmount() + " coins for the " +
                     CollectionManager.RestockHours + "-hour reset. You now have " + collectors[myUID].Coins + " coins");
   				}
   				else
   				{
   					return QuickQuit("You've already received your coins for this " + CollectionManager.RestockHours + " hour period.\n" +
                     "Please wait another " + StringExtensions.LargestTime(collectors[myUID].RestockWait));
   				}
   			}
   			#endregion

   			//Try to rank up
   			#region rankup
            else if (cmd == "cgamerankup")
   			{
   				if (collectors[myUID].RankUp())
   				{
                  string output = "You've ranked up! Your inventory and journal have been cleared, and you are now rank " + collectors[myUID].Stars;
   //                    if (!StandardCalls_RequestCoinUpdate(RankupCoins, username))
   //                        output += Module.SendMessageBreak + "Unfortunately, the ChatCoin module seems to be malfunctioning, so you will not receive coins";

                  mainOutput.broadcast = true;
                  mainOutput.message = "Hey, " + user.Username + " has become rank " + collectors[myUID].Stars + "(" + collectors[myUID].StarString() + ") in the cgame!";

                  outputs = QuickQuit(output);
                  outputs.Add(mainOutput);
                  return outputs;

                  //return QuickQuit(output);
   				}
   				else
   				{
                  return QuickQuit("You can't rank up just yet. Complete your journal first!");
   				}
   			}
   			#endregion

   			//View your collection
   			#region mycollection
            else if (cmd == "cgamestock")
   			{
               return QuickQuit("-=Your collection=-\n" + collectors[myUID].Statistics(false, JournalStyle), false, false);
   			}
            else if (cmd == "cgamestockm")
            {
               return QuickQuit("-=Your collection=-\n" + collectors[myUID].Statistics(true, JournalStyle), false, false);
            }
   			#endregion

   			//View your top items
   			#region mytopitems
            else if (cmd == "cgametopitems")
   			{
   				var topItems = collectors[myUID].Inventory.AllItemCounts().ToList().OrderByDescending(x => x.Value).ToList();
   				string output = "The top items in your inventory are: ";

   				for (int i = 0; i < 5; i++)
   					if (i < topItems.Count)
   						output += "\n" + CollectionSymbols.GetPointAndSymbol(topItems[i].Key) + " - " + topItems[i].Value;

               return QuickQuit(output);
   			}
   			#endregion

   			//Get multiple items
   			#region drawitems
   			//match = Regex.Match(message, @"^\s*/cgamedraw\s*([0-9]+)?\s*([xX][0-9]+)?\s*$");
            else if (cmd == "cgamedraw")
   			{
   				int multiplier = 1;
   				int amount;

   				//Oops, bad amount number
   				if (!int.TryParse(command.Arguments[0], out amount))
                  return QuickQuit("The amount of items to draw is invalid", true);
               else if (amount <= 0 || amount > MaxWithdraw)
                  return QuickQuit("You can't withdraw that many items!", true);

               //We had a multiplier!
               if(command.Arguments.Count == 2)
               {
                  if (!int.TryParse(command.Arguments[1].ToLower().Replace("x", ""), out multiplier))
                     return QuickQuit("The multiplier is invalid", true);
                  else if (multiplier > CollectionManager.MaxMultiplier || multiplier < CollectionManager.MinMultiplier)
                     return QuickQuit("Your multiplier is out of range. Use values from " + 
                        CollectionManager.MinMultiplier + "-" + CollectionManager.MaxMultiplier, true);
               }

   				int cost = CollectionManager.LotteryCost * multiplier * amount;

   				//Oops, you don't have enough coins
   				if (collectors[myUID].Coins < cost)
                  return QuickQuit("You don't have enough coins. Drawing " + amount + " item".Pluralify(amount) + " with a multiplier of X" + multiplier + " costs " + cost + " coins.");

   				//Generate a list of items (based on the given amount)
   				List<SpecialPoint> items = new List<SpecialPoint>();
   				List<SpecialPoint> newItems = new List<SpecialPoint>();
   				for (int i = 0; i < amount; i++)
   				{
   					items.Add(generator.DrawItemForPlayer(collectors[myUID], multiplier));
   					if (!collectors[myUID].Journal.HasItem(items[i]))
   						newItems.Add(items[i]);

   					//Make the player "buy" the item
   					collectors[myUID].BuyItem(items[i], cost / amount);
   				}

   				string output = "You spent " + cost + " coins on item".Pluralify(items.Count) + ": ";
   				int newItemCount = 0;

   				//Go through each item and buy it/produce output
   				foreach (SpecialPoint drawnItem in items)
   				{
   					//Determine if the item is new. Update values if so
   					bool newItem = false;

   					if (newItems.Contains(drawnItem))
   					{
   						newItem = true;
   						newItems.RemoveAll(x => x == drawnItem);
   						newItemCount++;
   					}

   					//Update output
   					output += CollectionSymbols.GetPointAndSymbol(drawnItem) + (newItem ? "**" : "") + ", ";
   				}

   				output += ListTerminator + " (" + newItemCount + " new)";

               return QuickQuit(output.Replace(", " + ListTerminator, ""));
   			}
   			#endregion

   			//See the chance of getting a new item
   			#region drawchance
   			//match = Regex.Match(message, @"^\s*/cgamechance\s*([xX][0-9]+)?\s*$");
            else if (cmd == "cgamechance")
   			{
   				int multiplier = 1;

               //Oops, bad multiplier
               if (!int.TryParse(command.Arguments[0].ToLower().Replace("x", ""), out multiplier))
                  return QuickQuit("The multiplier is invalid!", true);
   				else if (multiplier > CollectionManager.MaxMultiplier || multiplier < CollectionManager.MinMultiplier)
                  return QuickQuit("Your multiplier is out of range. Use a value between " + 
                     CollectionManager.MinMultiplier + "-" + CollectionManager.MaxMultiplier, true);

   				//Oops, make sure we're not already using the chance calculator thread
   				if (chanceSimulator.IsBusy)
                  return QuickQuit("The chance calculator is busy right now.");
               //Calculate the chance on a background thread
   				else
   					chanceSimulator.RunWorkerAsync(Tuple.Create(myUID, multiplier));

               return new List<JSONObject>();
   			}
   			#endregion

   			//How much do you have of item (given)
   			#region amount
   			//match = Regex.Match(message, @"^\s*/cgameamount\s+([a-zA-Z][0-9])\s*$");
            else if (cmd == "cgameamount")
   			{
               SpecialPoint point = SpecialPoint.Parse(command.Arguments[0]);

   				if (!CollectionManager.ValidPoint(point))
                  return QuickQuit("That item is out of range.", true);

               return QuickQuit("You have " + collectors[myUID].Inventory[point] + " of item " + CollectionSymbols.GetPointAndSymbol(point));
   			}
   			#endregion

   			//Compare your inventory to another's
   			#region compare
   			//match = Regex.Match(message, @"^\s*/cgamecompare\s+(.+)\s*$");
            else if (cmd == "cgamecompare")
            {
               //Generic error (shouldn't happen, but just in case!)
               if(!GetUserFromArgument(command.Arguments[0], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

   				//Oops, the user entered the wrong user
               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

               if (player.UID == myUID)
                  return QuickQuit("You can't compare with yourself!", true);

   				//Get the unique journal entries
   				List<SpecialPoint> myUnique, theirUnique;
               GetUnique(myUID, player.UID, out myUnique, out theirUnique);

   				string output = "Note: Only the top 5 are shown, so there may be more.\nOnly items that you actually have in your inventory are shown\n-Your unique items are: ";

   				//List out items that only you have
   				for (int i = 0; i < 5; i++)
   					if (i < myUnique.Count)
   						output += CollectionSymbols.GetPointAndSymbol(myUnique[i]) + " ";

               output += "\n-" + player.Username + " has these unique items: ";

   				//List out items that only the other guy has
   				for (int i = 0; i < 5; i++)
   					if (i < theirUnique.Count)
   						output += CollectionSymbols.GetPointAndSymbol(theirUnique[i]) + " ";

               return QuickQuit(output);
   			}
   			#endregion

   			//Perform a quicktrade with the given user
   			#region quicktrade
   			//match = Regex.Match(message, @"^\s*/cgamequicktrade\s+(.+)\s*$");
            else if (cmd == "cgamequicktrade")
   			{
               if(!GetUserFromArgument(command.Arguments[0], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }
                  
   				//Oops, there is another offer still standing
               try
               {
      				if (offer.OfferStanding)
                     return QuickQuit(OfferStanding(offer, users));
               }
               catch
               {
                  AddError(outputs);
                  return outputs;
               }

   				//Oops, the user entered the wrong person
               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

   				//Oops, can't trade with yourself!
               if (myUID == player.UID)
                  return QuickQuit("You can't trade with yourself!", true);

   				//Get the unique journal entries
   				List<SpecialPoint> myUnique, theirUnique;
               GetUnique(myUID, player.UID, out myUnique, out theirUnique);

   				//Oops, can't have a nice trade because one of you doesn't have any unique items
   				int tradeItems = Math.Min(myUnique.Count, theirUnique.Count);
   				if (tradeItems == 0)
                  return QuickQuit("There cannot be an even trade between you and " + player.Username);

   				myUnique = myUnique.Take(tradeItems).ToList();
   				theirUnique = theirUnique.Take(tradeItems).ToList();

               offer.CreateTrade(myUID, player.UID, myUnique, theirUnique);

               ModuleJSONObject playerOutput = new ModuleJSONObject();
               playerOutput.recipients.Add(player.UID);
               playerOutput.message = user.Username + " wants to perform a quick-trade with you.\nOffered item".Pluralify(myUnique.Count) + ": "
                  + PrintPointList(myUnique) + "\nDesired item".Pluralify(theirUnique.Count) + ": " + PrintPointList(theirUnique) + "\nYou have " + offer.OfferTimeout
                  + " seconds to decide (/cgameaccept or /cgamedecline). Note that you two will get the items in your journal, but the item is used up in the process."
                  + " You will not get the item in your inventory";

               outputs.Add(playerOutput);

               mainOutput.message = "Your trade of " + PrintPointList(myUnique) + " for " + PrintPointList(theirUnique) + " with " + player.Username +
                  " has been sent! You'll receive a message when they accept or decline";

               outputs.Add(mainOutput);

               return outputs;
   			}
   			#endregion

   			//Sell to player
   			#region sellPlayer
            else if (cmd == "cgamesell")
   			{
               if(!GetUserFromArgument(command.Arguments[1], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

               if(player.UID == myUID)
                  return QuickQuit("You can't sell to yourself!", true);

   				//Set up some initial info
   				List<SpecialPoint> offeredItems;
   				int desiredCoins;

   				//Oops, there is another offer still standing
   				if (offer.OfferStanding)
                  return QuickQuit(OfferStanding(offer, users));

   				//Ugh, bad values
               if (!int.TryParse(command.Arguments[2], out desiredCoins))
                  return QuickQuit("Your sell amount is out of bounds", true);

   				//Oops, the user entered the wrong person
               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

   				//Oops, the other user does not have that many coins
               if (collectors[player.UID].Coins < desiredCoins)
                  return QuickQuit("You don't have " + desiredCoins + " coin".Pluralify(desiredCoins) + " to spend.");

   				//Try to extract the points to sell
   				try
   				{
                  offeredItems = ExtractPoints(command.Arguments[0], user);
   				}
   				catch (Exception e)
   				{
                  return QuickQuit(e.Message, true);
   				}

   				//Create the sale
               offer.CreateSale(myUID, player.UID, desiredCoins, offeredItems);

               ModuleJSONObject playerOutput = new ModuleJSONObject();
               playerOutput.recipients.Add(player.UID);
               playerOutput.message = user.Username + " has offered to sell you " + PrintPointList(offer.SellerItems) + " for " + offer.BuyerCoins + " coin".Pluralify(offer.BuyerCoins) + ".\nYou have " +
                  offer.OfferTimeout + " seconds to decide (/cgameaccept or /cgamedecline). Note that you will get the item in your journal, but NOT your inventory.";

               outputs.Add(playerOutput);

               mainOutput.message = "Your offer of " + PrintPointList(offer.SellerItems) + " for " + offer.BuyerCoins + " coin".Pluralify(offer.BuyerCoins) + 
                  " with " + player.Username + " has been sent! You'll receive a message when they accept or decline";

               outputs.Add(mainOutput);

               return outputs;
   			}
   			#endregion

   			//Buy from player
   			#region buyPlayer
            else if (cmd == "cgamebuy")
   			{
               if(!GetUserFromArgument(command.Arguments[1], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

   				//Set up some initial info
   				List<SpecialPoint> desiredItems;
   				int offeredCoins;

   				//Oops, there is another offer still standing
   				if (offer.OfferStanding)
                  return QuickQuit(OfferStanding(offer, users), true);

   				//Ugh, bad values
               if (!int.TryParse(command.Arguments[2], out offeredCoins))
                  return QuickQuit("Your buy amount is out of bounds", true);

   				//Oops, the user entered the wrong person
               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

   				//Oops, this user does not have that many coins
               if (collectors[myUID].Coins < offeredCoins)
                  return QuickQuit("You do not have " + offeredCoins + " coin".Pluralify(offeredCoins) + " to spend.");

   				try
   				{
                  desiredItems = ExtractPoints(command.Arguments[0], player);
   				}
   				catch (Exception e)
   				{
                  return QuickQuit(e.Message, true);
   				}

   				//Create the offer
               offer.CreatePurchase(player.UID, myUID, offeredCoins, desiredItems);

               ModuleJSONObject playerOutput = new ModuleJSONObject();
               playerOutput.recipients.Add(player.UID);
               playerOutput.message = user.Username + " has offered to buy " + PrintPointList(offer.SellerItems) + " from you for " + offer.BuyerCoins + " coin".Pluralify(offer.BuyerCoins) + ".\nYou have " +
                  offer.OfferTimeout + " seconds to decide (/cgameaccept or /cgamedecline). ";

               outputs.Add(playerOutput);

               mainOutput.message = "Your offer of " + offer.BuyerCoins + " coin".Pluralify(offer.BuyerCoins) + " for " + PrintPointList(offer.SellerItems) + 
                  " with " + player.Username + " has been sent! You'll receive a message when they accept or decline";

               outputs.Add(mainOutput);

               return outputs;
   			}
   			#endregion

   			//Give away an item
   			#region give
            else if (cmd == "cgamegive")
   			{
               if(!GetUserFromArgument(command.Arguments[1], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

   				//Set up some initial info
   				//string potentialReceiver = match.Groups[3].Value;
   				List<SpecialPoint> points;

   				try
   				{
                  points = ExtractPoints(command.Arguments[0], user);
   				}
   				catch (Exception e)
   				{
                  return QuickQuit(e.Message, true);
   				}

   				//Oops, the user entered the wrong player
               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

               PerformGive(myUID, player.UID, points);

               ModuleJSONObject playerMessage = new ModuleJSONObject();
               playerMessage.recipients.Add(player.UID);
               playerMessage.message = user.Username + " has given you " + PrintPointList(points) + " for free! How nice!";
               outputs.Add(playerMessage);

               mainOutput.message = "You've given away " + PrintPointList(points) + " to " + player.Username + ". You're nice!";
               outputs.Add(mainOutput);

               return outputs;
   			}
   			#endregion

   			//Trade items with another player
   			#region trade
            else if (cmd == "cgametrade")
   			{
   				//Get the other user
               if(!GetUserFromArgument(command.Arguments[1], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

   				//Oops, there is another offer still standing
   				if (offer.OfferStanding)
                  return QuickQuit(OfferStanding(offer, users));

   				//Oops, the user entered the wrong person
               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

   				//Oops, can't trade with yourself!
               if (player.UID == myUID)
                  return QuickQuit("You can't trade with yourself!");

   				//Get the points you want to give to the other user
   				List<SpecialPoint> myPoints;
   				List<SpecialPoint> theirPoints;

   				try
   				{
   					myPoints = ExtractPoints(command.Arguments[0], user);
                  theirPoints = ExtractPoints(command.Arguments[2], player);
   				}
   				catch (Exception e)
   				{
                  return QuickQuit(e.Message);
   				}

               offer.CreateTrade(myUID, player.UID, myPoints, theirPoints);

               ModuleJSONObject playerOutput = new ModuleJSONObject();
               playerOutput.recipients.Add(player.UID);
               playerOutput.message = user.Username + " wants to perform a trade with you.\nOffered item".Pluralify(myPoints.Count) + ": "
                  + PrintPointList(myPoints) + "\nDesired item".Pluralify(theirPoints.Count) + ": " + PrintPointList(theirPoints) + "\nYou have " + offer.OfferTimeout
                  + " seconds to decide (/cgameaccept or /cgamedecline). Note that you two will get the items in your journal, but the item is used up in the process."
                  + " You will not get the item in your inventory";

               outputs.Add(playerOutput);

               mainOutput.message = "Your trade of " + PrintPointList(myPoints) + " for " + PrintPointList(theirPoints) + " with " + player.Username +
                  " has been sent! You'll receive a message when they accept or decline";

               outputs.Add(mainOutput);

               return outputs;
   			}

   			#endregion

   			//Sell all of the given items
   			#region sellall
   			//match = Regex.Match(message, @"^\s*/cgamesellall\s+(([a-zA-Z][0-9]\s*)+)\s*$");
            else if (cmd == "cgameshopsell")
   			{
   				//Build up the list of captured points
   				List<SpecialPoint> points;

   				try
   				{
                  points = ExtractPoints(command.Arguments[0], user);
   				}
   				catch (Exception e)
   				{
                  return QuickQuit(e.Message, true);
   				}

   				int totalAmount = 0;

   				//Now for the actual selling
   				foreach (SpecialPoint point in points)
   				{
   					//Get the total amount of that item we can sell
   					int pointAmount = collectors[myUID].Inventory[point];
   					//totalAmount += pointAmount;

   					//Sell it all
   					for (int i = 0; i < pointAmount; i++)
                  {
                     int sellPrice = CollectionManager.SellPrice(point, collectors[myUID]);
                     collectors[myUID].SellItem(point, sellPrice);
                     totalAmount += sellPrice;
                  }
   				}

               return QuickQuit("You sold all " + PrintPointList(points) + " for " + totalAmount + " coins.");
   			}
   			#endregion

   			//Sell ALL of the items
   			#region sellallall
            if (cmd == "cgameshopsellall") //Regex.IsMatch(message, @"^\s*/cgamesellallall\s*$"))
   			{
   				int totalAmount = 0;

   				//Now for the actual selling
   				foreach (SpecialPoint point in collectors[myUID].Inventory.AllItemCounts().Where(x => x.Value > 0).Select(x => x.Key).ToList())
   				{
   					//Get the total amount of that item we can sell
   					int pointAmount = collectors[myUID].Inventory[point];
   					//totalAmount += pointAmount;

   					//Sell it all
   					for (int i = 0; i < pointAmount; i++)
                  {
                     int sellPrice = CollectionManager.SellPrice(point, collectors[myUID]);
                     collectors[myUID].SellItem(point, sellPrice);
                     totalAmount += sellPrice;
                  }
   				}

               return QuickQuit("You sold everything for " + totalAmount + " coins.");
   			}
   			#endregion

   			//Accept a selling offer
   			#region acceptoffer
            else if (cmd == "cgameaccept") //Regex.IsMatch(message, @"^\s*/cgameaccept\s*$"))
            {
   				//Pfft, the sale isn't for you!
   				if (!offer.OfferStanding || myUID != offer.Accepter)
                  return QuickQuit("There are no offers for you right now");

   				string output = "";

   				if (offer.Type == CollectionOffer.OfferType.Buy || offer.Type == CollectionOffer.OfferType.Sell)
   				{
   					//This variable is used so that only the first item in the sale exchanges money. After that, they're basically a trade
   					bool firstItem = true;

   					foreach (SpecialPoint sellItem in offer.SellerItems)
   					{
   						//Complete the sale
   						collectors[offer.Seller].SellItem(sellItem, (firstItem ? offer.BuyerCoins : 0));
   						collectors[offer.Buyer].BuyItem(sellItem, (firstItem ? offer.BuyerCoins : 0));
   						collectors[offer.Buyer].Inventory.UnobtainItem(sellItem);

   						firstItem = false;
   					}

   					output = users[offer.Buyer].Username + " has bought a journal entry for " + PrintPointList(offer.SellerItems) + " from " + users[offer.Seller].Username + " for " + offer.BuyerCoins + " coin".Pluralify(offer.BuyerCoins) + ".";
   				}
   				else if (offer.Type == CollectionOffer.OfferType.Trade)
   				{
   					output = users[offer.Offerer].Username + " gave these items to " + users[offer.Accepter].Username + ": \n";

   					//Perform the trade
   					foreach (SpecialPoint item in offer.OffererItems)
   					{
   						output += CollectionSymbols.GetPointAndSymbol(item) + " ";
   						collectors[offer.Offerer].SellItem(item, 0);
   						collectors[offer.Accepter].BuyItem(item, 0);
   						collectors[offer.Accepter].Inventory.UnobtainItem(item);
   					}
   					output += "\nAnd got these items in return: \n";
   					foreach (SpecialPoint item in offer.AccepterItems)
   					{
   						output += CollectionSymbols.GetPointAndSymbol(item) + " ";
   						collectors[offer.Accepter].SellItem(item, 0);
   						collectors[offer.Offerer].BuyItem(item, 0);
   						collectors[offer.Offerer].Inventory.UnobtainItem(item);
   					}
   				}

               mainOutput.message = output;
               mainOutput.recipients.Add(offer.Accepter);
               mainOutput.recipients.Add(offer.Offerer);
               outputs.Add(mainOutput);

   				//Reset the sell information
   				offer.Reset();

   				//We done yo.
   				return outputs;
   			}
   			#endregion

   			//Decline a selling offer
   			#region declineoffer
            else if (cmd == "cgamedecline")//Regex.IsMatch(message, @"^\s*/cgamedecline\s*$"))
   			{
   				//Pfft, the sale isn't for you!
   				if (!offer.OfferStanding || myUID != offer.Accepter)
                  return QuickQuit("There are no offers for you right now");

               ModuleJSONObject playerOutput = new ModuleJSONObject();
               playerOutput.message = user.Username + " has declined the offer";
               playerOutput.recipients.Add(offer.Offerer);
               outputs.Add(playerOutput);

               mainOutput.message = "You have declined the offer";
               outputs.Add(mainOutput);
   				
   				offer.Reset();

   				return outputs;
   			}
   			#endregion

   			//View your forever statistics (like total coins, total items collected, etc.
   			#region cgameforeverstats
            else if (cmd == "cgamestats")//Regex.IsMatch(message, @"^\s*/cgamestats\s*$"))
   			{
   				if (collectors[myUID].Stars < 1)
                  return QuickQuit("You must be at least rank 1 to see your permanent collection game stats");

               return QuickQuit(collectors[myUID].ForeverStats());
   			}
   			#endregion

   			#region cgamequery
            //match = Regex.Match(message, @"^\s*/cgamequery\s+(([a-zA-Z][0-9]\s*)+)\s*$");
            if (cmd == "cgamequery")//match.Success)
   			{
               List<SpecialPoint> points;

               try
   				{
                  points = ExtractPoints(command.Arguments[0]).Distinct().ToList();
   				}
   				catch (Exception e)
   				{
                  return QuickQuit(e.Message, true);
   				}

               return QuickQuit(GetQuery(points, users));
   			}

            if(cmd == "cgamequeryall")
            {
               List<SpecialPoint> points = collectors[myUID].Journal.Unobtained();

               return QuickQuit(GetQuery(points, users));
            }
   			#endregion

   			#region cgamelove
   			//match = Regex.Match(message, @"^\s*/cgamelove\s+(.+)\s*$");
            else if (cmd == "cgamelove") //match.Success)
   			{
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;
               
               //Get the other user
               if(!GetUserFromArgument(command.Arguments[0], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

               return PerformLove(user, player);
   			}
   			#endregion

            #region cgameaddlove
            //match = Regex.Match(message, @"^\s*/cgameaddlove\s+(.+)\s*$");
            else if(cmd == "cgameaddlove")//match.Success)
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;
               
               //Get the other user
               if(!GetUserFromArgument(command.Arguments[0], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

               if (myUID == player.UID)
                  return QuickQuit("You can't add yourself to the love list!", true);

               if (collectors[myUID].GetLovers().Count >= MaxLovers)
                  return QuickQuit("You're already at your love limit (" + MaxLovers + ")");

               if (!collectors[myUID].AddLover(player.UID))
                  return QuickQuit(player.Username + " is already in your love list!");

               return QuickQuit("You've added " + player.Username + " to your love list <3");
            }
            #endregion

            #region cgameremovelove
            //match = Regex.Match(message, @"^\s*/cgameremovelove\s+(.+)\s*$");
            else if (cmd == "cgameremovelove")//match.Success)
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;
               
               //Get the other user
               if(!GetUserFromArgument(command.Arguments[0], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

               if (!collectors[myUID].RemoveLover(player.UID))
                  return QuickQuit(player.Username + " is not in your love list!");

               return QuickQuit("You've removed " + player.Username + " from your love list </3");
            }
            #endregion

            #region cgamelovelist
            else if (cmd == "cgamelovelist")//Regex.IsMatch(message, @"^\s*/cgamelovelist\s*$"))
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;
               
               List<int> lovers = collectors[myUID].GetLovers();

               if(lovers.Count == 0)
               return QuickQuit("There is nobody on your love list ='(");

               string output = "These are all the people on your love list:";

               foreach (int lover in lovers)
                  if(users.ContainsKey(lover))
                     output += "\n* " + users[lover].Username;

               return QuickQuit(output);
            }
            #endregion

            #region cgameloveall
            if (cmd == "cgameloveall")//Regex.IsMatch(message, @"^\s*/cgameloveall\s*$"))
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;
               
               List<Tuple<int, int>> giveCounts = new List<Tuple<int,int>>();
               List<int> lovers = collectors[myUID].GetLovers().Where(x => collectors.ContainsKey(x) && users.ContainsKey(x)).ToList();

               if (lovers.Count == 0)
                  return QuickQuit("There is nobody on your love list ='(");

               foreach (int lover in lovers)
               {
                  List<SpecialPoint> myUnique, theirUnique;
                  GetUnique(myUID, lover, out myUnique, out theirUnique);
                  giveCounts.Add(Tuple.Create(lover, myUnique.Count));
               }

               if(giveCounts.Any(x => x.Item2 == 0))
               {
                  mainOutput.message = "Love-all skipped: " + string.Join(", ", giveCounts.Where(x => x.Item2 == 0).Select(x => users[x.Item1].Username)) + "\n";
                  outputs.Add(mainOutput);
               }

               foreach (int lover in giveCounts.Where(x => x.Item2 != 0).OrderBy(x => x.Item2).Select(x => x.Item1))
                  outputs.AddRange(PerformLove(user, users[lover]));

               return outputs;
            }
            #endregion

            if(cmd == "cgamecheckjealousy")
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;

               var canSteal = GetJealousy(myUID);

               return QuickQuit("If you tap into the darkness in your twisted heart now, you'll get " + canSteal.Count + " item".Pluralify(canSteal.Count) +
                  " plus " + JealousyCoins * users.Count(x => x.Value.LoggedIn) + " coins (1000 coins for each logged in user)");
            }

            if(cmd == "cgamejealousy")
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;

               Dictionary<SpecialPoint, int> gonnaSteal = GetJealousy(myUID);
               HashSet<int> usersToInform = new HashSet<int>();

               if(gonnaSteal.Count == 0)
                  return QuickQuit("Your jealousy couldn't change the fact that nobody had any items you needed");

               foreach(KeyValuePair<SpecialPoint, int> steal in gonnaSteal)
               {
                  PerformGive(steal.Value, myUID, new List<SpecialPoint> { steal.Key });
                  usersToInform.Add(steal.Value);
               }

               double multiplier = 0.5 + 1.5 * collectors.ToList().OrderBy(x => x.Value.Score()).Select(x => x.Key).ToList().IndexOf(myUID) / (double)collectors.Count;
               collectors[myUID].JealousyPerformed(gonnaSteal.Count, multiplier);

               foreach(int userID in usersToInform)
               {
                  ModuleJSONObject userOutput = new ModuleJSONObject();
                  int itemCount = gonnaSteal.Count(x => x.Value == userID);
                  userOutput.message = user.Username + " took " + itemCount + " item".Pluralify(itemCount) + " from you!";
                  userOutput.recipients.Add(userID);
                  outputs.Add(userOutput);
               }

               int getCoins = JealousyCoins * users.Count(x => x.Value.LoggedIn);
               collectors[myUID].GetCoins(getCoins);

               //WarningJSONObject warnOutput = new WarningJSONObject();
               mainOutput.message = "In a fit of jealousy, " + user.Username + " used the darkness in their twisted heart to " +
                  "generate " + getCoins + " coins and steal " + gonnaSteal.Count + " item".Pluralify(gonnaSteal.Count) + 
                  " from unsuspecting users. 8(>_<)8";
               mainOutput.broadcast = true;
               outputs.Add(mainOutput);

               return outputs;
            }

            if(cmd == "cgamehug")
            {
               if(CheckIfJealous(myUID, out outputs))
                  return outputs;

               //Get the other user
               if(!GetUserFromArgument(command.Arguments[0], users, out player))
               {
                  AddError(outputs);
                  return outputs;
               }

               if (!collectors.ContainsKey(player.UID))
                  return QuickQuit(NotAPlayer(player));

               if(myUID == player.UID)
                  return QuickQuit("You can't hug yourself!", true);

               if(CollectionManager.PerformHug(collectors, myUID, player.UID, JealousyTime))
               {
                  ModuleJSONObject playerOutput = new ModuleJSONObject();
                  playerOutput.recipients.Add(player.UID);
                  playerOutput.message = "You were hugged by " + user.Username + ". The darkness in your heart receded a bit!";
                  mainOutput.message = "You gave " + player.Username + " a hug and cheered them up a bit!";
                  outputs.Add(mainOutput);
                  outputs.Add(playerOutput);
                  return outputs;
               }
               else
               {
                  ModuleJSONObject playerOutput = new ModuleJSONObject();
                  playerOutput.recipients.Add(player.UID);
                  playerOutput.message = "You were hugged by " + user.Username + ". " + HugSayings[localRandom.Next(HugSayings.Count)];
                  mainOutput.message = "You gave " + player.Username + " a hug. What a nice person!";
                  outputs.Add(mainOutput);
                  outputs.Add(playerOutput);
                  return outputs;
               }
            }
         }
         catch(Exception e)
         {
            ModuleJSONObject error = new ModuleJSONObject();
            error.broadcast = true;
            error.message = "The CGAME module has encountered an unknown error. Please tell staff\n\n" +
            "Error message: " + e.ToString();

            return new List<JSONObject> { error };
         }

         return new List<JSONObject>();
		}
 public string NotAPlayer(UserInfo user)
 {
    return user.Username + " isn't a player yet. You should ask them to play!";
 }
		public List<SpecialPoint> ExtractPoints(string points, UserInfo player)
		{
         List<SpecialPoint> thePoints = ExtractPoints(points);

			//Now, let's make sure that the base user has these items. This will look at the counts of each
			//unique item in the list of items to trade, so you can place multiple instances of the same
			//item in your list and it should detect the correct amount. It will also check for the 
			//validity of the point
			foreach (SpecialPoint point in thePoints.Distinct())
			{
            if (thePoints.Count(x => x.Equals(point)) > collectors[player.UID].Inventory[point])
               throw new Exception(player.Username + " doesn't have enough " + CollectionSymbols.GetPointAndSymbol(point) + " for this command.");
			}

			return thePoints;
		}
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         List<JSONObject> outputs = new List<JSONObject>();
         ModuleJSONObject moduleOutput = new ModuleJSONObject();

         string message = "";
         UserInfo parsedInfo;

         try
         {
            switch(command.Command)
            {
               case "spamscore":
                  moduleOutput.message = "Your spam score is: " + user.SpamScore + ", offense score: " + user.GlobalSpamScore;
                  outputs.Add(moduleOutput);
                  break;

               case "resetserver":
                  int timeout = 5;

                  //Get the real timeout if one was given
                  if (command.Arguments.Count > 0 && !string.IsNullOrWhiteSpace(command.Arguments[0]))
                     timeout = int.Parse(command.Arguments[0]);

                  TimeSpan realTimeout = TimeSpan.FromSeconds(timeout);

                  //Make sure the user can even run such a high command
                  if(!user.ChatControl)
                     return FastMessage("You don't have access to this command!", true); 

                  //Request the reset we wanted
                  ChatRunner.PerformRequest(new SystemRequest(SystemRequests.Reset, realTimeout));

                  moduleOutput.message = user.Username + " is resetting the server in " + StringExtensions.LargestTime(realTimeout);
                  moduleOutput.broadcast = true;
                  outputs.Add(moduleOutput);

                  break;

               case "simulatelock":

                  //Make sure the user can even run such a high command
                  if(!user.ChatControlExtended)
                     return FastMessage("You don't have access to this command!", true); 
                  
                  //Request the lock we wanted
                  ChatRunner.PerformRequest(new SystemRequest(SystemRequests.LockDeath, TimeSpan.FromSeconds(5)));

                  moduleOutput.message = user.Username + " is simulating a server crash. The server WILL be unresponsive if successful";
                  moduleOutput.broadcast = true;
                  outputs.Add(moduleOutput);

                  break;

               case "savemodules":

                  //Make sure the user can even run such a high command
                  if(!user.ChatControlExtended)
                     return FastMessage("You don't have access to this command!", true); 

                  ChatRunner.PerformRequest(new SystemRequest(SystemRequests.SaveModules));

                  moduleOutput.message = user.Username + " is saving all module data. This may cause a small hiccup";
                  moduleOutput.broadcast = true;
                  outputs.Add(moduleOutput);

                  break;

               case "checkserver":
                  
                  //Make sure the user can even run such a high command
                  if(!user.ChatControl)
                     return FastMessage("You don't have access to this command!", true); 

                  message = "Information about the chat server:\n\n";

                  Dictionary<string, List<UserMessageJSONObject>> history = ChatRunner.Server.GetHistory();
                  List<UserMessageJSONObject> messages = ChatRunner.Server.GetMessages();
                  List<LogMessage> logMessages = ChatRunner.Server.Settings.LogProvider.GetMessages();
                  List<LogMessage> logFileBuffer = ChatRunner.Server.Settings.LogProvider.GetFileBuffer();
                  List<Module> modules = ChatRunner.Server.GetModuleListCopy();

                  message += "Rooms: " + history.Keys.Count + "\n";
                  message += "History messages: " + history.Sum(x => x.Value.Count) + "\n";
                  message += "Registered users: " + users.Count + "\n";
                  message += "Total user sessions: " + users.Sum(x => (long)x.Value.SessionCount) + "\n";
                  message += "Stored messages: " + messages.Count + "\n";
                  message += "Stored log: " + logMessages.Count + "\n";
                  message += "Log file buffer: " + logFileBuffer.Count + "\n";
                  message += "Modules loaded: " + string.Join(", ", modules.Select(x => x.GetType().Name)) + " (" + modules.Count + ")\n";
                  message += "Subscribed handlers for extra output: " + this.ExtraCommandHandlerCount + "\n";
                  message += "Registered connections: " + ChatRunner.Server.ConnectedUsers().Count + "\n";

                  using(Process process = Process.GetCurrentProcess())
                  {
                     message += "Virtual Memory: " + (process.PrivateMemorySize64 / 1048576) + "MiB\n";
                     message += "Heap Allocated: " + (GC.GetTotalMemory(true) / 1048576) + "MiB\n"; 
                     message += "Threads: " + process.Threads.Count;
                  }

                  moduleOutput.message = message;
                  outputs.Add(moduleOutput);

                  break;

               case "debuginfo":

                  //if we can't parse the user, use yourself. Otherwise if it parsed but they don't have
                  //access to this command, stop and let them know.
                  if(string.IsNullOrWhiteSpace(command.Arguments[0]) || !GetUserFromArgument(command.Arguments[0], users, out parsedInfo))
                     parsedInfo = user;
                  else if(!user.ChatControl && parsedInfo.UID != user.UID)
                     return FastMessage("You don't have access to this command!", true); 

                  message = parsedInfo.Username + "'s debug information: \n\n";

                  message += "Session ID: " + parsedInfo.LastSessionID + "\n";
                  message += "Open sessions: " + parsedInfo.OpenSessionCount + "\n";
                  message += "Bad sessions: " + parsedInfo.BadSessionCount + "\n";
                  message += "Current session time: " + StringExtensions.LargestTime(parsedInfo.CurrentSessionTime) + "\n";
                  message += "UID: " + parsedInfo.UID + "\n";
                  message += "Active: " + parsedInfo.Active + "\n";
                  message += "Last ping: " + StringExtensions.LargestTime(DateTime.Now - parsedInfo.LastPing) + "\n";
                  message += "Last post: " + StringExtensions.LargestTime(DateTime.Now - parsedInfo.LastPost) + "\n";
                  message += "Last entry: " + StringExtensions.LargestTime(DateTime.Now - parsedInfo.LastJoin) + "\n";
                  message += "Staff chat: " + parsedInfo.CanStaffChat + "\n";
                  message += "Global chat: " + parsedInfo.CanGlobalChat + "\n";
                  message += "Chat control: " + parsedInfo.ChatControl + "\n";
                  message += "Chat control extended: " + parsedInfo.ChatControlExtended + "\n";
                  message += "Avatar: " + user.Avatar + "\n";

                  moduleOutput.message = message;
                  outputs.Add(moduleOutput);

                  break;
            }
         }
         catch (Exception e)
         {
            return FastMessage("An error has occurred in the debug module: " + e.Message, true);
         }

         return outputs;
      }
 public static Dictionary<ChatReplaceables, string> QuickDictionary(UserInfo user)
 {
    return new Dictionary<ChatReplaceables, string>
    {
       { ChatReplaceables.Username, user.Username },
       { ChatReplaceables.Seconds, user.SecondsToUnblock.ToString() }
    }.ToDictionary(x => x.Key, y => y.Value);
 }
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         string output = "";
         UserInfo parsedUser = null;

         if (command.Command == "showhiding")
         {
            if (!user.ChatControlExtended)
               return FastMessage("This command doesn't *AHEM* exist");

            output = "Users that are hiding:\n" + String.Join("\n", GetHidingUsers(users).Select(x => x.Username));

            return FastMessage(output);
         }
         else if (command.Command == "expose")
         {
            if (!user.ChatControlExtended)
               return FastMessage("This command doesn't *AHEM* exist");

            //This should eventually use AddError instead.
            if(!GetUserFromArgument(command.Arguments[0], users, out parsedUser))
               return FastMessage("Something weird happened in the backend during user parse!", true); 
            else if (!GetHidingUsers(users).Any(x => x.UID == parsedUser.UID))
               return FastMessage(parsedUser.Username + " isn't hiding!", true);

            SneakyModule.UnhideUser(parsedUser.UID);
            return FastMessage("You forced " + parsedUser.Username + " out of hiding!");
         }

         return new List<JSONObject>();
      }
 public void UpdateUser(User user)
 {
    this.user = new UserInfo(user, true);
 }
 //Ugh hidden returns false IF the user CAN'T view it.
 public override bool Hidden(UserInfo user)
 {
    return !(user.ChatControl || user.ChatControlExtended);
 }
 //Most modules will not need this. This function is called by the chat server whenever it recieves a message 
 //(any message, including non commands). Unless you're performing analysis or saving information on each message
 //(like this statistics class), you won't need to use this. Notice that it does not return any information; you
 //cannot give output to the chat server here. Also notice the "Log" function call here. This is a wrapper provided
 //by Module which lets you write to the chat server log. There's a whole system in place for creating your own logs,
 //but you probably just want to write to the main log. You can also specify the level of the message, which defaults
 //to "Normal". If the message is unimportant, you probably want "Debug".
 public override void ProcessMessage(UserMessageJSONObject message, UserInfo user, Dictionary<int, UserInfo> users)
 {
    //Add user to statistics dictionary if they don't already exist.
    if (!userStatistics.ContainsKey(user.UID))
    {
       userStatistics.Add(user.UID, new UserStatistics());
       Log("Added new user: " + user.Username);
    }
       
    if (message.Display)
    {
       userStatistics[user.UID].AddMessage(message.message);
       userStatistics[user.UID].AddUsers(users.Where(x => x.Value.LoggedIn).Select(x => x.Value.UID).ToList());
    }
 }
Ejemplo n.º 21
0
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         List<JSONObject> outputs = new List<JSONObject>();

         ModuleJSONObject output = new ModuleJSONObject();

         switch (command.Command)
         {
            case "about":
               output = new ModuleJSONObject();
               BandwidthContainer bandwidth = ChatRunner.Bandwidth; //Chat.GetBandwidth();
               DateTime built = ChatRunner.MyBuildDate();
               DateTime crashed = ChatRunner.LastCrash();
               string crashedString = "never";

               if (crashed.Ticks > 0)
                  crashedString = StringExtensions.LargestTime(DateTime.Now - crashed) + " ago";
                  
               output.message = 
                  "---Build info---\n" +
                  "Version: " + ChatRunner.AssemblyVersion() + "\n" +
                  "Runtime: " + StringExtensions.LargestTime(DateTime.Now - ChatRunner.Startup) + "\n" +
                  "Built " + StringExtensions.LargestTime(DateTime.Now - built) + " ago (" + built.ToString("R") + ")\n" +
                  "---Data usage---\n" +
                  "Outgoing: " + bandwidth.GetTotalBandwidthOutgoing() + " (1h: " + bandwidth.GetHourBandwidthOutgoing() + ")\n" +
                  "Incoming: " + bandwidth.GetTotalBandwidthIncoming() + " (1h: " + bandwidth.GetHourBandwidthIncoming() + ")\n" +
                  "---Websocket---\n" +
                  "Library Version: " + WebSocketServer.Version + "\n" +
                  "Last full crash: " + crashedString;
               outputs.Add(output);
               break;

            case "policy":
               output = new ModuleJSONObject(ChatServer.Policy);
               outputs.Add(output);
               break;

            case "help":
               output = new ModuleJSONObject();
               if (command.Arguments.Count == 0)
               {
                  output.message = "Which module would you like help with?\n";

                  foreach (Module module in ChatRunner.Server.GetModuleListCopy(user.UID))
                     output.message += "\n" + module.Nickname;

                  output.message += "\n\nRerun help command with a module name to see commands for that module";
                  outputs.Add(output);
               }
               else
               {
                  output.message = GetModuleHelp(command.Arguments[0], user);
                  outputs.Add(output);
               }
               break;
            case "helpregex":
               output.message = GetModuleHelp(command.Arguments[0], user, true);
               outputs.Add(output);
               break;
            case "uactest":
               output = new ModuleJSONObject();
               output.message = "User " + command.OriginalArguments[0] + " corrects to " + command.Arguments[0];
               outputs.Add(output);
               break;
         }

         return outputs;
      }
      //Get stats for given user. This is a statistics-specific function
      public string GetUserStats(UserInfo user)
      {
         if(!userStatistics.ContainsKey(user.UID))
            return "No statistics found for " + user.Username + "!";

         List<UserStatistics> allStats = userStatistics.Select(x => x.Value).ToList();
         UserStatistics myStats = userStatistics[user.UID];
         long totalMessages = allStats.Sum(x => x.TotalMessages);

         if (totalMessages == 0)
            totalMessages = 1;

         string message = "---" + user.Username + "'s Chat Statistics---\n";
         message += "Total messages: " + myStats.TotalMessages + 
            " (" + string.Format("{0:N2}%", myStats.TotalMessages * 100.0 / totalMessages) + 
            ", #" + (allStats.OrderByDescending(x => x.TotalMessages).ToList().IndexOf(myStats) + 1) + ")\n";
         message += "Average message size: " + (int)myStats.AverageMessageLength +
            " characters (#" + (allStats.OrderByDescending(x => x.AverageMessageLength).ToList().IndexOf(myStats) + 1) + ")\n";
         message += "Average users while chatting: " + (int)myStats.AverageUsersWhenChatting + "\n";
         message += "Total users you've seen: " + myStats.UniqueUsersSeen + "\n";
         message += "Total chat time: " + StringExtensions.LargestTime(user.TotalChatTime) + "\n";
         message += "Average session time: " + StringExtensions.LargestTime(user.AverageSessionTime) + "\n";
         message += "Current session time: " + StringExtensions.LargestTime(user.CurrentSessionTime) + "\n";

         return message;
      }
Ejemplo n.º 23
0
      public string GetModuleHelp(string moduleString, UserInfo user, bool showRegex = false)
      {
         string message = "Help for the " + moduleString + " module:\n";

         Module module = null;

         try
         {
            module = ChatRunner.Server.GetModuleListCopy(user.UID).First(x => x.Nickname == moduleString);

            if (!string.IsNullOrWhiteSpace(module.GeneralHelp))
               message += "\n" + module.GeneralHelp + "\n";

            foreach(ModuleCommand moduleCommand in module.Commands)
               message += "\n" + moduleCommand.DisplayString + (showRegex ? " : " + moduleCommand.FullRegex : "");

            if (showRegex)
            {
               if (module.ArgumentHelp.Count > 0)
                  message += "\n\nSome argument regex (uses standard regex syntax):";

               foreach (KeyValuePair<string, string> argHelp in module.ArgumentHelp)
                  message += "\n" + argHelp.Key + " - " + argHelp.Value;
            }
         }
         catch
         {
            message += "\nThis module is hidden or does not exist.";
         }

         return message;
      }
      public override List<JSONObject> ProcessCommand(UserCommand command, UserInfo user, Dictionary<int, UserInfo> users)
      {
         List<JSONObject> outputs = new List<JSONObject>();
         ModuleJSONObject output = new ModuleJSONObject();
         string error = "";

         switch (command.Command)
         {
            case "pm":
               //First, make sure this even works
               UserInfo recipient;
               output = new ModuleJSONObject();

               if (!GetUserFromArgument(command.Arguments[0], 
                      users.Where(x => x.Value.LoggedIn).ToDictionary(x => x.Key, y => y.Value), out recipient))
               {
                  AddError(outputs);
                  break;
               }
               
               output.tag = "any";
               output.message = user.Username + " -> " + command.Arguments[0] + ":\n" + command.Arguments[1];
                  //System.Security.SecurityElement.Escape(command.Arguments[1]);
               output.recipients.Add(recipient.UID);
               output.recipients.Add(command.uid);
               outputs.Add(output);
               break;
            case "pmcreateroom":
               HashSet<int> roomUsers = new HashSet<int>();

               foreach (string roomUser in command.ArgumentParts[0])
               {
                  UserInfo tempUser;
                  if (!GetUserFromArgument(roomUser, users, out tempUser))
                  {
                     error = "User not found!";
                     break;
                  }
                  else if (!roomUsers.Add(tempUser.UID))
                  {
                     error = "Duplicate user in list: " + tempUser.Username;
                     break;
                  }
               }

               roomUsers.Add(user.UID);

               if (!string.IsNullOrWhiteSpace(error) || !ChatRunner.Server.CreatePMRoom(roomUsers, user.UID, out error))
               {
                  WarningJSONObject warning = new WarningJSONObject(error);
                  outputs.Add(warning);
               }
               else
               {
                  output.message = "You created a chatroom for " + string.Join(", ", roomUsers.Select(x => users[x].Username));
                  outputs.Add(output);
               }

               break;

            case "pmleaveroom":
               if (!ChatRunner.Server.LeavePMRoom(user.UID, command.tag, out error))
               {
                  WarningJSONObject warning = new WarningJSONObject(error);
                  outputs.Add(warning);
               }
               else
               {
                  output.message = "You left this PM room";
                  outputs.Add(output);
               }
               break;
         }

         return outputs;
      }
      public List<JSONObject> PerformLove(UserInfo user, UserInfo player)
      {
         List<JSONObject> outputs = new List<JSONObject>();

         if (CheckIfJealous(player.UID, out outputs))
            return QuickQuit("The intense dark aura surrounding " + player.Username + " prevents you from loving them :'(");
         
         //Oops, the user entered the wrong player
         if (!collectors.ContainsKey(player.UID))
            return QuickQuit(NotAPlayer(player));

         List<SpecialPoint> myUnique, theirUnique;
         GetUnique(user.UID, player.UID, out myUnique, out theirUnique);

         if (myUnique.Count == 0)
            return QuickQuit("It was a nice gesture, but you don't have any unique items to give to " + player.Username + ".");

         PerformGive(user.UID, player.UID, myUnique);
         collectors[user.UID].LoveGiven(myUnique.Count);
         collectors[player.UID].GotLoved(myUnique.Count);

         ModuleJSONObject playerOutput = new ModuleJSONObject();
         playerOutput.recipients.Add(player.UID);
         playerOutput.message = user.Username + " generously gave you " + myUnique.Count + " item".Pluralify(myUnique.Count) + 
            " - " + PrintPointList(myUnique);
         outputs.Add(playerOutput);

         ModuleJSONObject mainOutput = new ModuleJSONObject();
         mainOutput.message = "You generously gave " + myUnique.Count + " item".Pluralify(myUnique.Count) + 
            " to " + player.Username + " - " + PrintPointList(myUnique);
         outputs.Add(mainOutput);

         return outputs;
      }