mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2025-10-14 21:22:49 +00:00
Move SessionFileMonitor into a generic FileMonitor class Use FileMonintor for both SessionCache and the Mailer bot Allows multiple MCC instances to share the same database files (Add files missing in the previous commit)
380 lines
17 KiB
C#
380 lines
17 KiB
C#
using System;
|
|
using System.Data;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
|
|
namespace MinecraftClient.ChatBots
|
|
{
|
|
/// <summary>
|
|
/// ChatBot for storing and delivering Mails
|
|
/// </summary>
|
|
public class Mailer : ChatBot
|
|
{
|
|
/// <summary>
|
|
/// Holds the list of ignored players
|
|
/// </summary>
|
|
private class IgnoreList : HashSet<string>
|
|
{
|
|
/// <summary>
|
|
/// Read ignore list from file
|
|
/// </summary>
|
|
/// <param name="filePath">Path to the ignore list</param>
|
|
/// <returns>Ignore list</returns>
|
|
public static IgnoreList FromFile(string filePath)
|
|
{
|
|
IgnoreList ignoreList = new IgnoreList();
|
|
foreach (string line in FileMonitor.ReadAllLinesWithRetries(filePath))
|
|
{
|
|
if (!line.StartsWith("#"))
|
|
{
|
|
string entry = line.ToLower();
|
|
if (!ignoreList.Contains(entry))
|
|
ignoreList.Add(entry);
|
|
}
|
|
}
|
|
return ignoreList;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Save ignore list to file
|
|
/// </summary>
|
|
/// <param name="filePath">Path to destination file</param>
|
|
public void SaveToFile(string filePath)
|
|
{
|
|
List<string> lines = new List<string>();
|
|
lines.Add("#Ignored Players");
|
|
foreach (string player in this)
|
|
lines.Add(player);
|
|
FileMonitor.WriteAllLinesWithRetries(filePath, lines);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Holds the Mail database: a collection of Mails sent from a player to another player
|
|
/// </summary>
|
|
private class MailDatabase : List<Mail>
|
|
{
|
|
/// <summary>
|
|
/// Read mail database from file
|
|
/// </summary>
|
|
/// <param name="filePath">Path to the database</param>
|
|
/// <returns>Mail database</returns>
|
|
public static MailDatabase FromFile(string filePath)
|
|
{
|
|
MailDatabase database = new MailDatabase();
|
|
Dictionary<string, Dictionary<string, string>> iniFileDict = INIFile.ParseFile(FileMonitor.ReadAllLinesWithRetries(filePath));
|
|
foreach (KeyValuePair<string, Dictionary<string, string>> iniSection in iniFileDict)
|
|
{
|
|
//iniSection.Key is "mailXX" but we don't need it here
|
|
string sender = iniSection.Value["sender"];
|
|
string recipient = iniSection.Value["recipient"];
|
|
string content = iniSection.Value["content"];
|
|
DateTime timestamp = DateTime.Parse(iniSection.Value["timestamp"]);
|
|
bool anonymous = INIFile.Str2Bool(iniSection.Value["anonymous"]);
|
|
database.Add(new Mail(sender, recipient, content, anonymous, timestamp));
|
|
}
|
|
return database;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Save mail database to file
|
|
/// </summary>
|
|
/// <param name="filePath">Path to destination file</param>
|
|
public void SaveToFile(string filePath)
|
|
{
|
|
Dictionary<string, Dictionary<string, string>> iniFileDict = new Dictionary<string, Dictionary<string, string>>();
|
|
int mailCount = 0;
|
|
foreach (Mail mail in this)
|
|
{
|
|
mailCount++;
|
|
Dictionary<string, string> iniSection = new Dictionary<string, string>();
|
|
iniSection["sender"] = mail.Sender;
|
|
iniSection["recipient"] = mail.Recipient;
|
|
iniSection["content"] = mail.Content;
|
|
iniSection["timestamp"] = mail.DateSent.ToString();
|
|
iniSection["anonymous"] = mail.Anonymous.ToString();
|
|
iniFileDict["mail" + mailCount] = iniSection;
|
|
}
|
|
FileMonitor.WriteAllLinesWithRetries(filePath, INIFile.Generate(iniFileDict, "Mail Database"));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a Mail sent from a player to another player
|
|
/// </summary>
|
|
private class Mail
|
|
{
|
|
private string sender;
|
|
private string senderLower;
|
|
private string recipient;
|
|
private string recipientLower;
|
|
private string message;
|
|
private DateTime datesent;
|
|
private bool delivered;
|
|
private bool anonymous;
|
|
|
|
public Mail(string sender, string recipient, string message, bool anonymous, DateTime datesent)
|
|
{
|
|
this.sender = sender;
|
|
this.senderLower = sender.ToLower();
|
|
this.recipient = recipient;
|
|
this.recipientLower = recipient.ToLower();
|
|
this.message = message;
|
|
this.datesent = datesent;
|
|
this.delivered = false;
|
|
this.anonymous = anonymous;
|
|
}
|
|
|
|
public string Sender { get { return sender; } }
|
|
public string SenderLowercase { get { return senderLower; } }
|
|
public string Recipient { get { return recipient; } }
|
|
public string RecipientLowercase { get { return recipientLower; } }
|
|
public string Content { get { return message; } }
|
|
public DateTime DateSent { get { return datesent; } }
|
|
public bool Delivered { get { return delivered; } }
|
|
public bool Anonymous { get { return anonymous; } }
|
|
public void setDelivered() { delivered = true; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return String.Format("{0} {1} {2} {3}", Sender, Recipient, Content, DateSent);
|
|
}
|
|
}
|
|
|
|
// Internal variables
|
|
private int maxMessageLength = 0;
|
|
private DateTime nextMailSend = DateTime.Now;
|
|
private MailDatabase mailDatabase = new MailDatabase();
|
|
private IgnoreList ignoreList = new IgnoreList();
|
|
private FileMonitor mailDbFileMonitor;
|
|
private FileMonitor ignoreListFileMonitor;
|
|
private object readWriteLock = new object();
|
|
|
|
/// <summary>
|
|
/// Initialization of the Mailer bot
|
|
/// </summary>
|
|
public override void Initialize()
|
|
{
|
|
LogDebugToConsole("Initializing Mailer with settings:");
|
|
LogDebugToConsole(" - Database File: " + Settings.Mailer_DatabaseFile);
|
|
LogDebugToConsole(" - Ignore List: " + Settings.Mailer_IgnoreListFile);
|
|
LogDebugToConsole(" - Public Interactions: " + Settings.Mailer_PublicInteractions);
|
|
LogDebugToConsole(" - Max Mails per Player: " + Settings.Mailer_MaxMailsPerPlayer);
|
|
LogDebugToConsole(" - Max Database Size: " + Settings.Mailer_MaxDatabaseSize);
|
|
LogDebugToConsole(" - Mail Retention: " + Settings.Mailer_MailRetentionDays + " days");
|
|
|
|
if (Settings.Mailer_MaxDatabaseSize <= 0)
|
|
{
|
|
LogToConsole("Cannot enable Mailer: Max Database Size must be greater than zero. Please review the settings.");
|
|
UnloadBot();
|
|
return;
|
|
}
|
|
|
|
if (Settings.Mailer_MaxMailsPerPlayer <= 0)
|
|
{
|
|
LogToConsole("Cannot enable Mailer: Max Mails per Player must be greater than zero. Please review the settings.");
|
|
UnloadBot();
|
|
return;
|
|
}
|
|
|
|
if (Settings.Mailer_MailRetentionDays <= 0)
|
|
{
|
|
LogToConsole("Cannot enable Mailer: Mail Retention must be greater than zero. Please review the settings.");
|
|
UnloadBot();
|
|
return;
|
|
}
|
|
|
|
if (!File.Exists(Settings.Mailer_DatabaseFile))
|
|
{
|
|
LogToConsole("Creating new database file: " + Path.GetFullPath(Settings.Mailer_DatabaseFile));
|
|
new MailDatabase().SaveToFile(Settings.Mailer_DatabaseFile);
|
|
}
|
|
|
|
if (!File.Exists(Settings.Mailer_IgnoreListFile))
|
|
{
|
|
LogToConsole("Creating new ignore list: " + Path.GetFullPath(Settings.Mailer_IgnoreListFile));
|
|
new IgnoreList().SaveToFile(Settings.Mailer_IgnoreListFile);
|
|
}
|
|
|
|
lock (readWriteLock)
|
|
{
|
|
LogDebugToConsole("Loading database file: " + Path.GetFullPath(Settings.Mailer_DatabaseFile));
|
|
mailDatabase = MailDatabase.FromFile(Settings.Mailer_DatabaseFile);
|
|
|
|
LogDebugToConsole("Loading ignore list: " + Path.GetFullPath(Settings.Mailer_IgnoreListFile));
|
|
ignoreList = IgnoreList.FromFile(Settings.Mailer_IgnoreListFile);
|
|
}
|
|
|
|
//Initialize file monitors. In case the bot needs to unload for some reason in the future, do not forget to .Dispose() them
|
|
mailDbFileMonitor = new FileMonitor(Path.GetDirectoryName(Settings.Mailer_DatabaseFile), Path.GetFileName(Settings.Mailer_DatabaseFile), FileMonitorCallback);
|
|
ignoreListFileMonitor = new FileMonitor(Path.GetDirectoryName(Settings.Mailer_IgnoreListFile), Path.GetFileName(Settings.Mailer_IgnoreListFile), FileMonitorCallback);
|
|
|
|
RegisterChatBotCommand("mailer", "Subcommands: getmails, addignored, getignored, removeignored", ProcessInternalCommand);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Standard settings for the bot.
|
|
/// </summary>
|
|
public override void AfterGameJoined()
|
|
{
|
|
maxMessageLength = GetMaxChatMessageLength()
|
|
- 44 // Deduct length of "/ 16CharPlayerName 16CharPlayerName mailed: "
|
|
- Settings.PrivateMsgsCmdName.Length; // Deduct length of "tell" command
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process chat messages from the server
|
|
/// </summary>
|
|
public override void GetText(string text)
|
|
{
|
|
string message = "";
|
|
string username = "";
|
|
text = GetVerbatim(text);
|
|
|
|
if (IsPrivateMessage(text, ref message, ref username) || (Settings.Mailer_PublicInteractions && IsChatMessage(text, ref message, ref username)))
|
|
{
|
|
string usernameLower = username.ToLower();
|
|
if (!ignoreList.Contains(usernameLower))
|
|
{
|
|
string command = message.Split(' ')[0].ToLower();
|
|
switch (command)
|
|
{
|
|
case "mail":
|
|
case "tellonym":
|
|
if (usernameLower != GetUsername().ToLower()
|
|
&& mailDatabase.Count < Settings.Mailer_MaxDatabaseSize
|
|
&& mailDatabase.Where(mail => mail.SenderLowercase == usernameLower).Count() < Settings.Mailer_MaxMailsPerPlayer)
|
|
{
|
|
Queue<string> args = new Queue<string>(Command.getArgs(message));
|
|
if (args.Count >= 2)
|
|
{
|
|
bool anonymous = (command == "tellonym");
|
|
string recipient = args.Dequeue();
|
|
message = string.Join(" ", args);
|
|
|
|
if (IsValidName(recipient))
|
|
{
|
|
if (message.Length <= maxMessageLength)
|
|
{
|
|
Mail mail = new Mail(username, recipient, message, anonymous, DateTime.Now);
|
|
LogToConsole("Saving message: " + mail.ToString());
|
|
lock (readWriteLock)
|
|
{
|
|
mailDatabase.Add(mail);
|
|
mailDatabase.SaveToFile(Settings.Mailer_DatabaseFile);
|
|
}
|
|
SendPrivateMessage(username, "Message saved!");
|
|
}
|
|
else SendPrivateMessage(username, "Your message cannot be longer than " + maxMessageLength + " characters.");
|
|
}
|
|
else SendPrivateMessage(username, "Recipient '" + recipient + "' is not a valid player name.");
|
|
}
|
|
else SendPrivateMessage(username, "Usage: " + command + " <recipient> <message>");
|
|
}
|
|
else SendPrivateMessage(username, "Couldn't save Message. Limit reached!");
|
|
break;
|
|
}
|
|
}
|
|
else LogDebugToConsole(username + " is ignored!");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called on each MCC tick, around 10 times per second
|
|
/// </summary>
|
|
public override void Update()
|
|
{
|
|
DateTime dateNow = DateTime.Now;
|
|
if (nextMailSend < dateNow)
|
|
{
|
|
LogDebugToConsole("Looking for mails to send @ " + DateTime.Now);
|
|
|
|
// Process at most 3 mails at a time to avoid spamming. Other mails will be processed on next mail send
|
|
HashSet<string> onlinePlayersLowercase = new HashSet<string>(GetOnlinePlayers().Select(name => name.ToLower()));
|
|
foreach (Mail mail in mailDatabase.Where(mail => !mail.Delivered && onlinePlayersLowercase.Contains(mail.RecipientLowercase)).Take(3))
|
|
{
|
|
string sender = mail.Anonymous ? "Anonymous" : mail.Sender;
|
|
SendPrivateMessage(mail.Recipient, sender + " mailed: " + mail.Content);
|
|
mail.setDelivered();
|
|
LogDebugToConsole("Delivered: " + mail.ToString());
|
|
}
|
|
|
|
lock (readWriteLock)
|
|
{
|
|
mailDatabase.RemoveAll(mail => mail.Delivered);
|
|
mailDatabase.RemoveAll(mail => mail.DateSent.AddDays(Settings.Mailer_MailRetentionDays) < DateTime.Now);
|
|
mailDatabase.SaveToFile(Settings.Mailer_DatabaseFile);
|
|
}
|
|
|
|
nextMailSend = dateNow.AddSeconds(10);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the Mail Database or Ignore list has changed on disk
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void FileMonitorCallback(object sender, FileSystemEventArgs e)
|
|
{
|
|
lock (readWriteLock)
|
|
{
|
|
mailDatabase = MailDatabase.FromFile(Settings.Mailer_DatabaseFile);
|
|
ignoreList = IgnoreList.FromFile(Settings.Mailer_IgnoreListFile);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interprets local commands.
|
|
/// </summary>
|
|
private string ProcessInternalCommand(string cmd, string[] args)
|
|
{
|
|
if (args.Length > 0)
|
|
{
|
|
string commandName = args[0].ToLower();
|
|
switch (commandName)
|
|
{
|
|
case "getmails":
|
|
return "== Mails in database ==\n" + string.Join("\n", mailDatabase);
|
|
|
|
case "getignored":
|
|
return "== Ignore list ==\n" + string.Join("\n", ignoreList);
|
|
|
|
case "addignored":
|
|
case "removeignored":
|
|
if (args.Length > 1 && IsValidName(args[1]))
|
|
{
|
|
string username = args[1].ToLower();
|
|
if (commandName == "addignored")
|
|
{
|
|
lock (readWriteLock)
|
|
{
|
|
if (!ignoreList.Contains(username))
|
|
{
|
|
ignoreList.Add(username);
|
|
ignoreList.SaveToFile(Settings.Mailer_IgnoreListFile);
|
|
}
|
|
}
|
|
return "Added " + args[1] + " to the ignore list!";
|
|
}
|
|
else
|
|
{
|
|
lock (readWriteLock)
|
|
{
|
|
if (ignoreList.Contains(username))
|
|
{
|
|
ignoreList.Remove(username);
|
|
ignoreList.SaveToFile(Settings.Mailer_IgnoreListFile);
|
|
}
|
|
}
|
|
return "Removed " + args[1] + " from the ignore list!";
|
|
}
|
|
}
|
|
else return "Missing or invalid name. Usage: " + commandName + " <username>";
|
|
}
|
|
}
|
|
return "See usage: /help mailer";
|
|
}
|
|
}
|
|
}
|