timely now has a 3 letter password by default. Configurable via .conf gamb

This commit is contained in:
Toastie (DCS Team) 2024-11-02 01:31:06 +13:00
parent 82f7c3be27
commit e47e619ef9
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
9 changed files with 179 additions and 98 deletions

View file

@ -44,9 +44,6 @@ public static class UserXpExtensions
.CountAsyncLinqToDB()
+ 1;
public static void ResetGuildUserXp(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
=> xps.Delete(x => x.UserId == userId && x.GuildId == guildId);
public static void ResetGuildXp(this DbSet<UserXpStats> xps, ulong guildId)
=> xps.Delete(x => x.GuildId == guildId);

View file

@ -14,6 +14,12 @@ using System.Text;
using EllieBot.Modules.Gambling.Rps;
using EllieBot.Common.TypeReaders;
using EllieBot.Modules.Patronage;
using SixLabors.Fonts;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Color = SixLabors.ImageSharp.Color;
namespace EllieBot.Modules.Gambling;
@ -26,6 +32,7 @@ public partial class Gambling : GamblingModule<GamblingService>
private readonly NumberFormatInfo _enUsCulture;
private readonly DownloadTracker _tracker;
private readonly GamblingConfigService _configService;
private readonly FontProvider _fonts;
private readonly IBankService _bank;
private readonly IRemindService _remind;
private readonly GamblingTxTracker _gamblingTxTracker;
@ -38,6 +45,7 @@ public partial class Gambling : GamblingModule<GamblingService>
DiscordSocketClient client,
DownloadTracker tracker,
GamblingConfigService configService,
FontProvider fonts,
IBankService bank,
IRemindService remind,
IPatronageService patronage,
@ -58,6 +66,7 @@ public partial class Gambling : GamblingModule<GamblingService>
_enUsCulture.NumberGroupSeparator = "";
_tracker = tracker;
_configService = configService;
_fonts = fonts;
}
public async Task<string> GetBalanceStringAsync(ulong userId)
@ -151,6 +160,49 @@ public partial class Gambling : GamblingModule<GamblingService>
return;
}
if (Config.Timely.RequirePassword)
{
var password = _service.GeneratePassword();
var img = new Image<Rgba32>(100, 40);
var font = _fonts.NotoSans.CreateFont(30);
var outlinePen = new SolidPen(Color.Black, 1f);
// draw password on the image
img.Mutate(x =>
{
x.DrawText(new RichTextOptions(font)
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
FallbackFontFamilies = _fonts.FallBackFonts,
Origin = new(50, 20)
},
password,
Brushes.Solid(Color.White),
outlinePen);
});
using var stream = await img.ToStreamAsync();
var captcha = await Response()
.Embed(_sender.CreateEmbed()
.WithOkColor()
.WithImageUrl("attachment://timely.png"))
.File(stream, "timely.png")
.SendAsync();
try
{
var userInput = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id);
if (userInput?.ToLowerInvariant() != password?.ToLowerInvariant())
{
return;
}
}
finally
{
_ = captcha.DeleteAsync();
}
}
if (await _service.ClaimTimelyAsync(ctx.User.Id, period) is { } remainder)
{
// Get correct time form remainder

View file

@ -11,7 +11,7 @@ namespace EllieBot.Modules.Gambling.Common;
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
{
[Comment("""DO NOT CHANGE""")]
public int Version { get; set; } = 8;
public int Version { get; set; } = 9;
[Comment("""Currency settings""")]
public CurrencyConfig Currency { get; set; }
@ -111,6 +111,11 @@ public partial class TimelyConfig
setting to 0 or less will disable this feature
""")]
public int Cooldown { get; set; } = 24;
[Comment("""
Whether the users are required to type a password when they do timely.
""")]
public bool RequirePassword { get; set; } = true;
}
[Cloneable]

View file

@ -144,6 +144,11 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("timely.pass",
gs => gs.Timely.RequirePassword,
bool.TryParse,
ConfigPrinters.ToString);
Migrate();
}
@ -167,22 +172,6 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
});
}
if (data.Version < 5)
{
ModifyConfig(c =>
{
c.Version = 5;
});
}
if (data.Version < 6)
{
ModifyConfig(c =>
{
c.Version = 6;
});
}
if (data.Version < 7)
{
ModifyConfig(c =>
@ -199,5 +188,13 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
c.Waifu.Decay.UnclaimedDecayPercent = 0;
});
}
if (data.Version < 9)
{
ModifyConfig(c =>
{
c.Version = 9;
});
}
}
}

View file

@ -16,6 +16,7 @@ public class GamblingService : IEService, IReadyExecutor
private readonly DiscordSocketClient _client;
private readonly IBotCache _cache;
private readonly GamblingConfigService _gss;
private readonly EllieRandom _rng;
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
@ -29,11 +30,19 @@ public class GamblingService : IEService, IReadyExecutor
_client = client;
_cache = cache;
_gss = gss;
_rng = new EllieRandom();
}
public Task OnReadyAsync()
=> Task.WhenAll(CurrencyDecayLoopAsync(), TransactionClearLoopAsync());
public string GeneratePassword()
{
var num = _rng.Next((int)Math.Pow(31, 2), (int)Math.Pow(32, 3));
return new kwum(num).ToString();
}
private async Task TransactionClearLoopAsync()
{
if (_client.ShardId != 0)
@ -52,7 +61,7 @@ public class GamblingService : IEService, IReadyExecutor
var days = TimeSpan.FromDays(lifetime);
await using var uow = _db.GetDbContext();
await uow.Set<CurrencyTransaction>()
.DeleteAsync(ct => ct.DateAdded == null || now - ct.DateAdded < days);
.DeleteAsync(ct => ct.DateAdded == null || now - ct.DateAdded < days);
}
catch (Exception ex)
{
@ -90,11 +99,11 @@ public class GamblingService : IEService, IReadyExecutor
}
Log.Information("""
--- Decaying users' currency ---
| decay: {ConfigDecayPercent}%
| max: {MaxDecay}
| threshold: {DecayMinTreshold}
""",
--- Decaying users' currency ---
| decay: {ConfigDecayPercent}%
| max: {MaxDecay}
| threshold: {DecayMinTreshold}
""",
config.Decay.Percent * 100,
maxDecay,
config.Decay.MinThreshold);
@ -104,14 +113,14 @@ public class GamblingService : IEService, IReadyExecutor
var decay = (double)config.Decay.Percent;
await uow.Set<DiscordUser>()
.Where(x => x.CurrencyAmount > config.Decay.MinThreshold && x.UserId != _client.CurrentUser.Id)
.UpdateAsync(old => new()
{
CurrencyAmount =
maxDecay > Sql.Round((old.CurrencyAmount * decay) - 0.5)
? (long)(old.CurrencyAmount - Sql.Round((old.CurrencyAmount * decay) - 0.5))
: old.CurrencyAmount - maxDecay
});
.Where(x => x.CurrencyAmount > config.Decay.MinThreshold && x.UserId != _client.CurrentUser.Id)
.UpdateAsync(old => new()
{
CurrencyAmount =
maxDecay > Sql.Round((old.CurrencyAmount * decay) - 0.5)
? (long)(old.CurrencyAmount - Sql.Round((old.CurrencyAmount * decay) - 0.5))
: old.CurrencyAmount - maxDecay
});
await uow.SaveChangesAsync();
@ -178,8 +187,9 @@ public class GamblingService : IEService, IReadyExecutor
public bool UserHasTimelyReminder(ulong userId)
{
var db = _db.GetDbContext();
return db.GetTable<Reminder>().Any(x => x.UserId == userId
&& x.Type == ReminderType.Timely);
return db.GetTable<Reminder>()
.Any(x => x.UserId == userId
&& x.Type == ReminderType.Timely);
}
public async Task RemoveAllTimelyClaimsAsync()

View file

@ -1,4 +1,6 @@
#nullable disable
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using EllieBot.Common.ModuleBehaviors;
using EllieBot.Db.Models;
@ -25,6 +27,7 @@ public class PlantPickService : IEService, IExecNoCommand
private readonly EllieRandom _rng;
private readonly DiscordSocketClient _client;
private readonly GamblingConfigService _gss;
private readonly GamblingService _gs;
private readonly ConcurrentHashSet<ulong> _generationChannels;
private readonly SemaphoreSlim _pickLock = new(1, 1);
@ -37,7 +40,8 @@ public class PlantPickService : IEService, IExecNoCommand
ICurrencyService cs,
CommandHandler cmdHandler,
DiscordSocketClient client,
GamblingConfigService gss)
GamblingConfigService gss,
GamblingService gs)
{
_db = db;
_strings = strings;
@ -48,6 +52,7 @@ public class PlantPickService : IEService, IExecNoCommand
_rng = new();
_client = client;
_gss = gss;
_gs = gs;
using var uow = db.GetDbContext();
var guildIds = client.Guilds.Select(x => x.Id).ToList();
@ -87,6 +92,7 @@ public class PlantPickService : IEService, IExecNoCommand
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
if (toDelete is not null)
uow.Remove(toDelete);
_generationChannels.TryRemove(cid);
enabled = false;
}
@ -208,7 +214,7 @@ public class PlantPickService : IEService, IExecNoCommand
+ " "
+ GetText(channel.GuildId, strs.pick_pl(prefix));
var pw = config.Generation.HasPassword ? GenerateCurrencyPassword().ToUpperInvariant() : null;
var pw = config.Generation.HasPassword ? _gs.GeneratePassword().ToUpperInvariant() : null;
IUserMessage sent;
var (stream, ext) = await GetRandomCurrencyImageAsync(pw);
@ -232,67 +238,44 @@ public class PlantPickService : IEService, IExecNoCommand
return Task.CompletedTask;
}
/// <summary>
/// Generate a hexadecimal string from 1000 to ffff.
/// </summary>
/// <returns>A hexadecimal string from 1000 to ffff</returns>
private string GenerateCurrencyPassword()
{
// generate a number from 1000 to ffff
var num = _rng.Next(4096, 65536);
// convert it to hexadecimal
return num.ToString("x4");
}
public async Task<long> PickAsync(
ulong gid,
ITextChannel ch,
ulong uid,
string pass)
{
await _pickLock.WaitAsync();
long amount;
ulong[] ids;
await using (var uow = _db.GetDbContext())
{
// this method will sum all plants with that password,
// remove them, and get messageids of the removed plants
pass = pass?.Trim().TrimTo(10, true)?.ToUpperInvariant();
// gets all plants in this channel with the same password
var entries = await uow.GetTable<PlantedCurrency>()
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
.DeleteWithOutputAsync();
if (!entries.Any())
return 0;
amount = entries.Sum(x => x.Amount);
ids = entries.Select(x => x.MessageId).ToArray();
}
if (amount > 0)
await _cs.AddAsync(uid, amount, new("currency", "collect"));
try
{
long amount;
ulong[] ids;
await using (var uow = _db.GetDbContext())
{
// this method will sum all plants with that password,
// remove them, and get messageids of the removed plants
pass = pass?.Trim().TrimTo(10, true).ToUpperInvariant();
// gets all plants in this channel with the same password
var entries = uow.Set<PlantedCurrency>()
.AsQueryable()
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
.ToList();
// sum how much currency that is, and get all of the message ids (so that i can delete them)
amount = entries.Sum(x => x.Amount);
ids = entries.Select(x => x.MessageId).ToArray();
// remove them from the database
uow.RemoveRange(entries);
if (amount > 0)
// give the picked currency to the user
await _cs.AddAsync(uid, amount, new("currency", "collect"));
await uow.SaveChangesAsync();
}
try
{
// delete all of the plant messages which have just been picked
_ = ch.DeleteMessagesAsync(ids);
}
catch { }
// return the amount of currency the user picked
return amount;
}
finally
{
_pickLock.Release();
_ = ch.DeleteMessagesAsync(ids);
}
catch { }
// return the amount of currency the user picked
return amount;
}
public async Task<ulong?> SendPlantMessageAsync(

View file

@ -357,7 +357,7 @@ public partial class Xp : EllieModule<XpService>
if (!await PromptUserConfirmAsync(embed))
return;
_service.XpReset(ctx.Guild.Id, userId);
await _service.XpReset(ctx.Guild.Id, userId);
await Response().Confirm(strs.reset_user(userId)).SendAsync();
}

View file

@ -20,6 +20,31 @@ using Image = SixLabors.ImageSharp.Image;
namespace EllieBot.Modules.Xp.Services;
public interface IUserService
{
Task<DiscordUser?> GetUserAsync(ulong userId);
}
public sealed class UserService : IUserService, IEService
{
private readonly DbService _db;
public UserService(DbService db)
{
_db = db;
}
public async Task<DiscordUser> GetUserAsync(ulong userId)
{
await using var uow = _db.GetDbContext();
var user = await uow
.GetTable<DiscordUser>()
.FirstOrDefaultAsyncLinqToDB(u => u.UserId == userId);
return user;
}
}
public class XpService : IEService, IReadyExecutor, IExecNoCommand
{
private readonly DbService _db;
@ -1437,11 +1462,11 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
}
}
public void XpReset(ulong guildId, ulong userId)
public async Task XpReset(ulong guildId, ulong userId)
{
using var uow = _db.GetDbContext();
uow.Set<UserXpStats>().ResetGuildUserXp(userId, guildId);
uow.SaveChanges();
await using var uow = _db.GetDbContext();
await uow.GetTable<UserXpStats>()
.DeleteAsync(x => x.UserId == userId && x.GuildId == guildId);
}
public void XpReset(ulong guildId)
@ -1637,6 +1662,15 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
public bool IsShopEnabled()
=> _xpConfig.Data.Shop.IsEnabled;
public async Task<int> GetTotalGuildUsers(ulong requestGuildId, List<ulong>? guildUsers = null)
{
await using var ctx = _db.GetDbContext();
return await ctx.GetTable<UserXpStats>()
.Where(x => x.GuildId == requestGuildId
&& (guildUsers == null || guildUsers.Contains(x.UserId)))
.CountAsyncLinqToDB();
}
}
public enum BuyResult

View file

@ -1,5 +1,5 @@
# DO NOT CHANGE
version: 8
version: 9
# Currency settings
currency:
# What is the emoji/character which represents the currency
@ -56,6 +56,8 @@ timely:
# How often (in hours) can users claim currency with .timely command
# setting to 0 or less will disable this feature
cooldown: 12
# Whether the users are required to type a password when they do timely.
requirePassword: true
# How much will each user's owned currency decay over time.
decay:
# Percentage of user's current currency which will be deducted every 24h.
@ -125,12 +127,13 @@ waifu:
# Settings for periodic waifu price decay.
# Waifu price decays only if the waifu has no claimer.
decay:
# Percentage (0 - 100) of the waifu value to reduce.
# Set 0 to disable
# Unclaimed waifus will decay by this percentage (0 - 100).
# Default is 0 (disabled)
# For example if a waifu has a price of 500$, setting this value to 10 would reduce the waifu value by 10% (50$)
unclaimedDecayPercent: 0
# Claimed waifus will decay by this percentage (0 - 100).
# Default is 0 (disabled)
# For example if a waifu has a price of 500$, setting this value to 10 would reduce the waifu value by 10% (50$)
claimedDecayPercent: 0
# How often to decay waifu values, in hours
hourInterval: 24