diff --git a/LorisAngel/Database/ProfileDatabase.cs b/LorisAngel/Database/ProfileDatabase.cs new file mode 100644 index 0000000..4290255 --- /dev/null +++ b/LorisAngel/Database/ProfileDatabase.cs @@ -0,0 +1,273 @@ +using Discord; +using Discord.Net.Bot; +using Discord.Net.Bot.Database.Sql; +using MySql.Data.MySqlClient; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace LorisAngel.Database +{ + public class ProfileDatabase + { + private static List Users; + private static bool ReadyToUpdate = false; + + // Loop through all users + public static async Task ProcessUsers() + { + var SaveUsers = Task.Run(async () => + { + await Util.Logger(new LogMessage(LogSeverity.Info, "Profiles", "Start of SaveUsers thread.")); + await Task.Delay(5000); + + int newUsers = 0; + foreach (var g in CommandHandler.GetBot().Guilds) + { + foreach (var u in CommandHandler.GetBot().GetGuild(g.Id).Users) + { + if(!DoesUserExist(u.Id)) + { + LoriUser newUser = new LoriUser(u.Id, u.Username, u.CreatedAt.DateTime, DateTime.Now, new DateTime(), u.Status.ToString(), ""); + await AddUserToDatabaseAsync(newUser); + newUsers++; + } + } + } + + if (newUsers > 0) await Util.Logger(new LogMessage(LogSeverity.Info, "Profiles", $"Added {newUsers} new users.")); + Users = await GetAllUsersAsync(); + ReadyToUpdate = true; + + await Task.Delay(5000); + while (true) + { + DateTime startTime = DateTime.Now; + await SaveAllUsersAsync(Users); + int timetosave = (int)((DateTime.Now - startTime).TotalSeconds); + if (timetosave > 5) await Util.Logger(new LogMessage(LogSeverity.Warning, "Profiles", $"Saving users took {timetosave} seconds")); + await Task.Delay(60000); // Save users once a minute + } + }); + + var UpdateUsers = Task.Run(async () => + { + await Util.Logger(new LogMessage(LogSeverity.Info, "Profiles", "Start of UpdateUsers thread.")); + var bot = CommandHandler.GetBot(); + while (!ReadyToUpdate) await Task.Delay(500); + while (true) + { + if (Users.Count != 0) + { + DateTime startTime = DateTime.Now; + + foreach (LoriUser usr in Users) + { + var discUsr = bot.GetUser(usr.Id); + if (discUsr != null) + { + usr.UpdateStatus(discUsr.Status); + usr.UpdateName(discUsr.Username); + } + } + + int timetoupdate = (int)((DateTime.Now - startTime).TotalSeconds); + if (timetoupdate > 5) await Util.Logger(new LogMessage(LogSeverity.Warning, "Profiles", $"Updating users took {timetoupdate} seconds")); + } + await Task.Delay(500); + } + }); + } + + // Get all users + private static async Task> GetAllUsersAsync() + { + List users = new List(); + + var dbCon = DBConnection.Instance(); + dbCon.DatabaseName = LCommandHandler.DATABASE_NAME; + + if (dbCon.IsConnect()) + { + var cmd = new MySqlCommand($"SELECT * FROM users", dbCon.Connection); + var reader = cmd.ExecuteReader(); + if (reader.HasRows) + { + while (reader.Read()) + { + ulong id = reader.GetUInt64(0); + string name = reader.GetString(1); + DateTime createdOn = reader.GetDateTime(2); + DateTime joinedOn = reader.GetDateTime(3); + DateTime lastSeen = reader.GetDateTime(4); + string status = reader.GetString(5); + + LoriUser newUser = new LoriUser(id, name, createdOn, joinedOn, lastSeen, status, ""); + users.Add(newUser); + } + } + + reader.Close(); + cmd.Dispose(); + dbCon.Close(); + } + + return users; + } + + // Save all users + private static async Task SaveAllUsersAsync(List users) + { + var dbCon = DBConnection.Instance(); + dbCon.DatabaseName = LCommandHandler.DATABASE_NAME; + if (dbCon.IsConnect()) + { + foreach (LoriUser user in users) + { + if (user.HasChanged) + { + var cmd = new MySqlCommand($"UPDATE users SET name = @name, lastseen = @lastseen, status = @status WHERE id = @id", dbCon.Connection); + cmd.Parameters.Add("@id", MySqlDbType.UInt64).Value = user.Id; + cmd.Parameters.Add("@name", MySqlDbType.String).Value = ""; + cmd.Parameters.Add("@lastseen", MySqlDbType.DateTime).Value = user.LastSeen; + cmd.Parameters.Add("@status", MySqlDbType.String).Value = user.Status; + + try + { + await cmd.ExecuteNonQueryAsync(); + user.HasChanged = false; + cmd.Dispose(); + } + catch (Exception e) + { + Console.WriteLine($"Failed to save user: {e.Message}"); + cmd.Dispose(); + } + } + } + + dbCon.Close(); + } + } + + + // Check if user exists in database + private static bool DoesUserExist(ulong id) + { + var dbCon = DBConnection.Instance(); + dbCon.DatabaseName = LCommandHandler.DATABASE_NAME; + + if (dbCon.IsConnect()) + { + var cmd = new MySqlCommand($"SELECT * FROM users WHERE id = '{id}'", dbCon.Connection); + var reader = cmd.ExecuteReader(); + if (reader.HasRows) + { + reader.Close(); + cmd.Dispose(); + dbCon.Close(); + return true; + } + else + { + reader.Close(); + cmd.Dispose(); + dbCon.Close(); + return false; + } + } + + return false; + } + + + // Add user to database + private static async Task AddUserToDatabaseAsync(LoriUser user) + { + var dbCon = DBConnection.Instance(); + dbCon.DatabaseName = LCommandHandler.DATABASE_NAME; + if (dbCon.IsConnect()) + { + var cmd = new MySqlCommand($"INSERT INTO users (id, name, createdon, joinedon, lastseen, status, badges) VALUES (@id, @name, @createdon, @joinedon, @lastseen, @status, @badges)", dbCon.Connection); + cmd.Parameters.Add("@id", MySqlDbType.UInt64).Value = user.Id; + cmd.Parameters.Add("@name", MySqlDbType.String).Value = ""; + cmd.Parameters.Add("@createdon", MySqlDbType.DateTime).Value = user.CreatedOn; + cmd.Parameters.Add("@joinedon", MySqlDbType.DateTime).Value = user.JoinedOn; + cmd.Parameters.Add("@lastseen", MySqlDbType.DateTime).Value = user.LastSeen; + cmd.Parameters.Add("@status", MySqlDbType.String).Value = user.Status; + cmd.Parameters.Add("@badges", MySqlDbType.String).Value = ""; + + try + { + await cmd.ExecuteNonQueryAsync(); + cmd.Dispose(); + } + catch (Exception e) + { + Console.WriteLine($"Failed to save user: {e.Message}"); + cmd.Dispose(); + } + + dbCon.Close(); + } + } + + // Remove user from database + private static async Task RemoveUserAsync(ulong id) + { + + } + } + + public class LoriUser + { + public ulong Id { get; private set; } + public string Name { get; private set; } + public DateTime CreatedOn { get; private set; } + public DateTime JoinedOn { get; private set; } + public DateTime LastSeen { get; private set; } + public string Status { get; private set; } + public string Badges { get; private set; } // WILL BE A LIST OF BADGES ONCE BADGES ADDED + public bool HasChanged { get; set; } + + public LoriUser(ulong id, string name, DateTime createdOn, DateTime joinedOn, DateTime lastSeen, string status, string badges = "") + { + Id = id; + Name = name.Normalize() ?? throw new ArgumentNullException(nameof(name)); + CreatedOn = createdOn; + JoinedOn = joinedOn; + LastSeen = lastSeen; + Status = status ?? throw new ArgumentNullException(nameof(status)); + Badges = badges; + HasChanged = false; + } + + public void UpdateStatus(UserStatus newStatus) + { + if (!newStatus.ToString().Equals(Status)) + { + Status = newStatus.ToString(); + HasChanged = true; + } + if (!(newStatus == UserStatus.Offline || newStatus == UserStatus.Invisible)) + { + LastSeen = DateTime.Now; + HasChanged = true; + } + } + + public void UpdateName(string newName) + { + if (newName.Normalize() == Name) return; + Name = newName.Normalize(); + HasChanged = true; + } + + public override bool Equals(object obj) + { + return obj is LoriUser user && + Id == user.Id; + } + } +} diff --git a/LorisAngel/Files/SQLPlan.txt b/LorisAngel/Files/SQLPlan.txt index 00c78a9..8f6e6d9 100644 --- a/LorisAngel/Files/SQLPlan.txt +++ b/LorisAngel/Files/SQLPlan.txt @@ -1,9 +1,4 @@ -UserProfile - ulong id - string profile - - -On start up, load the users from database +On start up, load the users from database Loop every user of every guild every 5 seconds Add any missing user to the stored list of users Check status of any user, if changed store last seen as current time and update status diff --git a/LorisAngel/Files/changelog.txt b/LorisAngel/Files/changelog.txt index b46fefd..4201eff 100644 --- a/LorisAngel/Files/changelog.txt +++ b/LorisAngel/Files/changelog.txt @@ -1,4 +1,7 @@ -# Version 2.0.2 +# Version 2.0.3 +- + +# Version 2.0.2 - Added better command error feedback - Added command to add censored words (-settings addcensor ) - Fixed SQL error on relationship command diff --git a/LorisAngel/LCommandHandler.cs b/LorisAngel/LCommandHandler.cs index 2fbf720..cebae39 100644 --- a/LorisAngel/LCommandHandler.cs +++ b/LorisAngel/LCommandHandler.cs @@ -106,6 +106,8 @@ private async Task ReadyAsync() await Task.Delay(15000); } }); + + await ProfileDatabase.ProcessUsers(); } private async Task JoinedGuildAsync(SocketGuild guild) diff --git a/users.sql b/users.sql new file mode 100644 index 0000000..b7ade78 --- /dev/null +++ b/users.sql @@ -0,0 +1,34 @@ +-- -------------------------------------------------------- +-- Host: 127.0.0.1 +-- Server version: 10.4.14-MariaDB - mariadb.org binary distribution +-- Server OS: Win64 +-- HeidiSQL Version: 10.1.0.5464 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; + + +-- Dumping database structure for lorisangel +CREATE DATABASE IF NOT EXISTS `lorisangel` /*!40100 DEFAULT CHARACTER SET latin1 */; +USE `lorisangel`; + +-- Dumping structure for table lorisangel.users +CREATE TABLE IF NOT EXISTS `users` ( + `id` bigint(20) NOT NULL COMMENT 'Discord ID', + `name` varchar(50) DEFAULT NULL COMMENT 'Discord username', + `createdon` datetime DEFAULT NULL COMMENT 'Discord Account Creation Time', + `joinedon` datetime DEFAULT NULL COMMENT 'Lori''s Angel Profile Creating Time', + `lastseen` datetime DEFAULT NULL COMMENT 'Last Seen Active Time', + `status` varchar(50) DEFAULT NULL COMMENT 'Discord Status', + `badges` longtext DEFAULT NULL COMMENT 'List of all Lori''s Angel badges', + UNIQUE KEY `id` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='A table of all the Lori''s Angels users'; + +-- Data exporting was unselected. +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;