/// <summary> /// Tries to find a server user in the DB context. If it can't find it, it will /// first create the server and user if they don't exit and then create and /// return the new server user. /// </summary> /// <param name="ctx">Database context</param> /// <param name="serverId">ID of server</param> /// <param name="userId">ID of user</param> /// <returns></returns> public static ServerUser FindOrCreateUser(MoetronDBContext ctx, long serverId, long userId) { ServerUser su = ctx.ServerUsers.Find(serverId, userId); if (su != null) return su; Server s = ctx.Servers.Find(serverId); if (s == null) { s = ctx.Servers.Add(new Server() { Id = serverId }); } User u = ctx.Users.Find(userId); if (u == null) { u = ctx.Users.Add(new User() { Id = userId }); } su = ctx.ServerUsers.Add(new ServerUser() { ServerId = serverId, UserId = userId, LastAllowance = DateTime.UtcNow }); ctx.SaveChanges(); return su; }
/// <summary> /// Deletes a ServerUser /// </summary> /// <param name="server">Server</param> /// <param name="user">User</param> public static void DeleteUser(Discord.Server server, Discord.User user) { _ctx = new MoetronDBContext(); ServerUser su = _ctx.ServerUsers.Find((long)server.Id, (long)user.Id); if (su != null) _ctx.ServerUsers.Remove(su); _ctx.SaveChanges(); }
/// <summary> /// Removes users which exist in database but not on server. /// </summary> /// <param name="server">Discord server to check</param> /// <param name="msg">Return message</param> private void DeleteDeadUsers(Discord.Server server, out string msg) { msg = ""; _ctx = new MoetronDBContext(); Model.Server modelServer = _ctx.Servers.Find((long)server.Id); if (modelServer == null) { msg = "Server does not exist in database."; return; } int count = 0; Discord.User user; foreach (Model.ServerUser su in modelServer.ServerUsers.ToList()) { user = server.Users.FirstOrDefault(x => (long)x.Id == su.UserId); if (user == null) { _ctx.ServerUsers.Remove(su); count++; } } msg = $"Removed {count} users!"; _ctx.SaveChanges(); }
/// <summary> /// Check if allowance can be reset, and do so if it's been a day. /// </summary> /// <param name="ctx">Database context</param> /// <param name="su">Server user to check</param> /// <returns>Current allowance after check</returns> private static int ResetAllowance(MoetronDBContext ctx, ServerUser su) { //If a day has passed since last allowance, reset it if ((DateTime.UtcNow - su.LastAllowance).TotalDays > 1) { su.Allowance = DEFAULT_ALLOWANCE; su.LastAllowance = DateTime.UtcNow; ctx.SaveChanges(); } return su.Allowance; }
void IModule.Install(ModuleManager manager) { _manager = manager; _client = manager.Client; manager.CreateCommands("", cmd => { cmd.CreateGroup("points", (b) => { b.AddCheck((c, u, ch) => !ch.IsPrivate); b.MinPermissions((int)AccessLevel.User); b.CreateCommand("allowance") .Alias("left") .Description("Display how much points you have left to use until next allowance.") .Do(async (e) => { _ctx = new MoetronDBContext(); ServerUser su = ModelUtil.FindOrCreateUser(_ctx, (long)e.Server.Id, (long)e.User.Id); //Get time difference between now and next allowance ResetAllowance(_ctx, su); TimeSpan nextAllowance = TimeSpan.FromDays(1) - (DateTime.UtcNow - su.LastAllowance); await e.Message.Delete(); await e.Channel.SendMessage($"{e.User.Mention} You have {su.Allowance} allowance points left. Next allowance available in {nextAllowance.Hours} hours and {nextAllowance.Minutes} minutes."); }); b.CreateCommand("bal") .Alias("balance") .Description("Display your points balance. Specify a user to return their balance instead.\r\ne.g. `!points bal Moetron`") .Parameter("user", ParameterType.Unparsed) .Do(async (e) => { Discord.User user; ServerUser su; string err = ""; //If user not specified use caller if (e.GetArg("user").Length == 0) user = e.User; else user = e.Server.Users.FindUser(e.GetArg("user"), out err); //If user specified but not found if (user == null) { await e.Channel.SendMessage($"{e.User.Mention} {err}"); return; } _ctx = new MoetronDBContext(); su = ModelUtil.FindOrCreateUser(_ctx, (long)e.Server.Id, (long)user.Id); await e.Message.Delete(); await e.Channel.SendMessage($"{e.User.Mention} {user.NickOrName()} has {su.Points} points!"); }); b.CreateCommand("clean") .Description("Clean up dead users from the current server.") .MinPermissions((int)AccessLevel.BotOwner) .Do(async (e) => { string msg; DeleteDeadUsers(e.Server, out msg); await e.Message.Delete(); await e.Channel.SendMessage(msg); }); b.CreateCommand("give") .Alias(new string[] { "gift", "add" }) .Description("Give a user points for being dank. This will take the same " + "amount from your daily allowance.\r\ne.g. `!points give 100 Moetron`") .Parameter("num", ParameterType.Required) .Parameter("user", ParameterType.Unparsed) .Do(async (e) => { await ModifyPoints(e, true); }); b.CreateCommand("ranking") .Alias("rank") .Description("Get your ranking on this server.") .Do(async (e) => { _ctx = new MoetronDBContext(); ServerUser su = ModelUtil.FindOrCreateUser(_ctx, (long)e.Server.Id, (long)e.User.Id); List<ServerUser> userList = _ctx.Servers.Find((long)e.Server.Id).ServerUsers.OrderByDescending(x => x.Points).ToList(); await e.Message.Delete(); await e.Channel.SendMessage($"{e.User.Mention} You're {userList.IndexOf(su) + 1} out of {userList.Count}."); }); b.CreateCommand("remove") .Alias("rem") .Description("Remove a user's points for being lame. This will cost " + $"{REMOVE_ALLOWANCE_COST} points from your daily allowance for each " + $"point removed, as well as remove {REMOVE_COUNTER_FACTOR} times the amount " + "from your own points, so you better think twice!\r\ne.g. `!points rem 100 Moetron`") .Parameter("num", ParameterType.Required) .Parameter("user", ParameterType.Unparsed) .Do(async (e) => { await ModifyPoints(e, false); }); b.CreateCommand("reset") .Description("Reset a user to defaults. This will reset their points, current allowance and last allowance time.\r\ne.g. `!points reset Moetron`") .Parameter("user", ParameterType.Unparsed) .MinPermissions((int)AccessLevel.ServerAdmin) .Do(async (e) => { string err; Discord.User user = e.Server.Users.FindUser(e.GetArg("user"), out err); ServerUser su; if (user == null) { await e.Channel.SendMessage($"{e.User.Mention} {err}"); return; } _ctx = new MoetronDBContext(); su = ModelUtil.FindOrCreateUser(_ctx, (long)e.Server.Id, (long)user.Id); su.LastAllowance = DateTime.UtcNow; su.Allowance = DEFAULT_ALLOWANCE; su.Points = DEFAULT_POINTS; _ctx.SaveChanges(); await e.Message.Delete(); await e.Channel.SendMessage($"{e.User.Mention} {user.Mention}'s points has been reset to defaults."); }); b.CreateCommand("resetServer") .Description("Reset the whole server's point system. This will reset points, current allowances and last allowance times. Be careful with that.") .MinPermissions((int)AccessLevel.ServerAdmin) .Do(async (e) => { _ctx = new MoetronDBContext(); foreach (var su in _ctx.Servers.Find((long)e.Server.Id).ServerUsers) { su.LastAllowance = DateTime.UtcNow; su.Allowance = DEFAULT_ALLOWANCE; su.Points = DEFAULT_POINTS; } _ctx.SaveChanges(); await e.Message.Delete(); await e.Channel.SendMessage($"{e.User.Mention} All users have been reset to defaults."); }); b.CreateCommand("top") .Alias("best") .Description("Get the 5 top users of the server.") .Do(async (e) => { await DisplayRanking(e, true, 5); }); b.CreateCommand("what") .Alias("help") .Description("Get an explanation of what points how and how they work.") .Do(async (e) => { string msg = "Points (aka memepoints) are a server-wide system. Each user " + "is given a starting amount of points (referred to as just points, balance " + "or total) when they start participating. Each user also receives an " + "allowance every 24 hours which they can use to either give or remove " + "other users' points. Giving only removes from your allowance, but " + "removing also removes from your own points! This is done to encourage " + "giving rather than removing. Allowances are only refreshed when you " + "try using it or when you request it, and the timer is on an individual " + "basis (i.e. if you use your points for the first time at 8:00, your next " + "refresh will be after 8:00 the next day when you first try using it) " + ".\r\nAll in all the system is for fun and it's what you make of it!\r\n" + "Commands are used in this format: `!points <subcommand>`, and help " + "with `!help points <subcommand>`. See individual command help for more info."; await e.Message.Delete(); await e.User.SendMessage(msg); }); b.CreateCommand("worst") .Alias("bottom") .Description("Get the 5 worst users of the server.") .Do(async (e) => { await DisplayRanking(e, false, 5); }); }); }); }
/// <summary> /// Give or remove a user's points. Call from command event. /// </summary> /// <param name="e">Event from command</param> /// <param name="isGiving">True if giving points, false if removing</param> /// <returns>A task?</returns> private async System.Threading.Tasks.Task ModifyPoints(CommandEventArgs e, bool isGiving) { int num, allowanceCost; long pointCost; string msg; Discord.User receiver; ServerUser suSender, suReceiver; //Try to parse number if (!Int32.TryParse(e.GetArg("num"), out num)) { await e.Channel.SendMessage($"{e.User.Mention} Must be a valid number. Also, number goes before name, you walnut."); return; } //Number must be positive & not 0 if (num < 1) { await e.Channel.SendMessage($"{e.User.Mention} Must be a number higher than 0."); return; } //Try to find user on server receiver = e.Server.Users.FindUser(e.GetArg("user"), out msg); if (receiver == null) { await e.Channel.SendMessage($"{e.User.Mention} {msg}"); return; } else if (receiver.Id == e.User.Id) //Cannot be caller { await e.Channel.SendMessage($"{e.User.Mention} You can't {(isGiving ? "give yourself" : "remove your own")} points!"); return; } _ctx = new MoetronDBContext(); suSender = ModelUtil.FindOrCreateUser(_ctx, (long)e.Server.Id, (long)e.User.Id); if (!CheckAllowanceAmount(_ctx, suSender, num, isGiving)) { TimeSpan nextAllowance = TimeSpan.FromDays(1) - (DateTime.UtcNow - suSender.LastAllowance); await e.Channel.SendMessage($"{e.User.Mention} You don't have enough allowance points! ({suSender.Allowance} left, next allowance in {nextAllowance.Hours} hours and {nextAllowance.Minutes} minutes)"); return; } suReceiver = ModelUtil.FindOrCreateUser(_ctx, (long)e.Server.Id, (long)receiver.Id); if (isGiving) { //Give points to receiver, remove allowance from caller suReceiver.Points += num; suSender.Allowance -= num; msg = $"{e.User.Mention} gave {num} points to {receiver.Mention}, raising their total to {suReceiver.Points}!"; msg += $"\r\n{e.User.Mention} Allowance left: {suSender.Allowance}."; } else { //Remove points from receiver, remove points and allowance from caller suReceiver.Points -= num; pointCost = (long)Math.Round(num * REMOVE_COUNTER_FACTOR, MidpointRounding.AwayFromZero); allowanceCost = (int)Math.Round(num * REMOVE_ALLOWANCE_COST, MidpointRounding.AwayFromZero); suSender.Points -= pointCost; suSender.Allowance -= allowanceCost; msg = $"{e.User.Mention} removed {num} points from {receiver.Mention}, lowering their total to {suReceiver.Points}!"; msg += $"\r\n{e.User.Mention} Allowance left: {suSender.Allowance} & lost {pointCost} from your own points, putting your total at {suSender.Points}."; } _ctx.SaveChanges(); await e.Message.Delete(); await e.Channel.SendMessage(msg); }