timely now has a 3 letter password by default. Configurable via .conf gamb
This commit is contained in:
parent
82f7c3be27
commit
e47e619ef9
9 changed files with 179 additions and 98 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue