Compare commits
15 commits
e521c82d96
...
81b8b6fa1d
Author | SHA1 | Date | |
---|---|---|---|
81b8b6fa1d | |||
8ac523a8ce | |||
668d9e4734 | |||
151146378b | |||
7e66063990 | |||
e49dfda0db | |||
f318ec2741 | |||
81ebad702b | |||
624171c35f | |||
b2b8e4c3d3 | |||
6b6f822ec8 | |||
ca64765c34 | |||
90dd47e013 | |||
32db627aef | |||
c3340a16ba |
35 changed files with 845 additions and 398 deletions
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -1,6 +1,29 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except date format. a-c-f-r-o
|
||||||
|
|
||||||
|
## [5.1.4] - 15.07.2024
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `'coins` command which lists top 10 cryptos ordered by marketcap
|
||||||
|
- Added Clubs rank in the leaderboard to `'clubinfo`
|
||||||
|
- Bot owners can now check other people's bank balance (Not server owners, only bot owner, the person who is hosting the bot)
|
||||||
|
- You can now send multiple waifu gifts at once to waifus. For example `'waifugift 3xRose @user` will give that user 3 roses
|
||||||
|
- The format is `<NUMBER>x<ITEM>`, no spaces
|
||||||
|
- Added `'boosttest` command
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated command strings to clarify `'say` and `'send` usages
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed `'waifugift` help string
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed selfhost button from `'donate` command, no idea why it was there in the first place
|
||||||
|
|
||||||
## [5.1.3] - 08.07.2024
|
## [5.1.3] - 08.07.2024
|
||||||
|
|
||||||
|
|
46
build.ps1
Normal file
46
build.ps1
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "███████╗██╗ ██╗ ██╗███████╗██████╗ ██████╗ ████████╗"
|
||||||
|
Write-Output "██╔════╝██║ ██║ ██║██╔════╝██╔══██╗██╔═══██╗╚══██╔══╝"
|
||||||
|
Write-Output "█████╗ ██║ ██║ ██║█████╗ ██████╔╝██║ ██║ ██║ "
|
||||||
|
Write-Output "██╔══╝ ██║ ██║ ██║██╔══╝ ██╔══██╗██║ ██║ ██║ "
|
||||||
|
Write-Output "███████╗███████╗███████╗██║███████╗██████╔╝╚██████╔╝ ██║ "
|
||||||
|
Write-Output "╚══════╝╚══════╝╚══════╝╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═╝ "
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "Copyright © 2024 Toastie_t0ast & EllieBotDevs"
|
||||||
|
Write-Output ""
|
||||||
|
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "Publishing EllieBot"
|
||||||
|
Write-Output ""
|
||||||
|
|
||||||
|
Write-Output ""
|
||||||
|
dotnet publish -c Release -r linux-x64 --self-contained -o elliebot-linux-x64 src/EllieBot/EllieBot.csproj
|
||||||
|
Write-Output ""
|
||||||
|
dotnet publish -c Release -r linux-arm64 --self-contained -o elliebot-linux-arm64 src/EllieBot/EllieBot.csproj
|
||||||
|
Write-Output ""
|
||||||
|
dotnet publish -c Release -r win-x64 --self-contained -o elliebot-windows-x64 src/EllieBot/EllieBot.csproj
|
||||||
|
Write-Output ""
|
||||||
|
dotnet publish -c Release -r win-arm64 --self-contained -o elliebot-windows-arm64 src/EllieBot/EllieBot.csproj
|
||||||
|
Write-Output ""
|
||||||
|
dotnet publish -c Release -r osx-x64 --self-contained -o elliebot-osx-x64 src/EllieBot/EllieBot.csproj
|
||||||
|
Write-Output ""
|
||||||
|
dotnet publish -c Release -r osx-arm64 --self-contained -o elliebot-osx-arm64 src/EllieBot/EllieBot.csproj
|
||||||
|
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "Preparing the Windows installer build."
|
||||||
|
Write-Output ""
|
||||||
|
|
||||||
|
dotnet clean
|
||||||
|
dotnet restore -f --no-cache -v n
|
||||||
|
dotnet publish -c Release --self-contained --runtime win-x64 /p:Version=5.1.4 src/EllieBot
|
||||||
|
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "Finished the initial build script"
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "To build the Windows installer please install Inno Setup from"
|
||||||
|
Write-Output "https://jrsoftware.org/isdl.php"
|
||||||
|
Write-Output "And compile the exe_builder.iss"
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "If you are running on Windows please run the wsl command"
|
||||||
|
Write-Output "then run build.sh"
|
18
build.sh
Normal file
18
build.sh
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
echo ""
|
||||||
|
echo "Compressing build files"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
tar cvf 5.1.4-linux-x64-build.tar elliebot-linux-x64/*
|
||||||
|
tar cvf 5.1.4-linux-arm64-build.tar elliebot-linux-arm64/*
|
||||||
|
tar cvf 5.1.4-osx-x64-build.tar elliebot-osx-x64/*
|
||||||
|
tar cvf 5.1.4-osx-arm64-build.tar elliebot-osx-arm64/*
|
||||||
|
zip -r 5.1.4-windows-x64-build.zip elliebot-windows-x64/*
|
||||||
|
zip -r 5.1.4-windows-arm64-build.zip elliebot-windows-arm64/*
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Moving the installer file you would have generated"
|
||||||
|
echo "if you followed the instructions in the bottom of the build.ps1 file"
|
||||||
|
echo "to the directory this script in run in."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
mv ellie-installers/5.1.4/ellie-setup-5.1.4.exe ellie-setup-5.1.4.exe
|
|
@ -181,9 +181,9 @@ dotnet_naming_rule.private_readonly_field.symbols = private_readonly_field
|
||||||
dotnet_naming_rule.private_readonly_field.style = begins_with_underscore
|
dotnet_naming_rule.private_readonly_field.style = begins_with_underscore
|
||||||
dotnet_naming_rule.private_readonly_field.severity = warning
|
dotnet_naming_rule.private_readonly_field.severity = warning
|
||||||
|
|
||||||
dotnet_naming_rule.private_field.symbols = private_field
|
# dotnet_naming_rule.private_field.symbols = private_field
|
||||||
dotnet_naming_rule.private_field.style = camel_case
|
# dotnet_naming_rule.private_field.style = camel_case
|
||||||
dotnet_naming_rule.private_field.severity = warning
|
# dotnet_naming_rule.private_field.severity = warning
|
||||||
|
|
||||||
dotnet_naming_rule.const_fields.symbols = const_fields
|
dotnet_naming_rule.const_fields.symbols = const_fields
|
||||||
dotnet_naming_rule.const_fields.style = all_upper
|
dotnet_naming_rule.const_fields.style = all_upper
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>true</ImplicitUsings>
|
<ImplicitUsings>true</ImplicitUsings>
|
||||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
<Version>5.1.3</Version>
|
<Version>5.1.4</Version>
|
||||||
|
|
||||||
<!-- Output/build -->
|
<!-- Output/build -->
|
||||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||||
|
|
|
@ -225,5 +225,19 @@ public partial class Administration
|
||||||
if (!enabled)
|
if (!enabled)
|
||||||
await Response().Pending(strs.greetdmmsg_enable($"`{prefix}greetdm`")).SendAsync();
|
await Response().Pending(strs.greetdmmsg_enable($"`{prefix}greetdm`")).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.ManageGuild)]
|
||||||
|
[Ratelimit(5)]
|
||||||
|
public async Task BoostTest([Leftover] IGuildUser? user = null)
|
||||||
|
{
|
||||||
|
user ??= (IGuildUser)ctx.User;
|
||||||
|
|
||||||
|
await _service.BoostTest((ITextChannel)ctx.Channel, user);
|
||||||
|
var enabled = _service.GetBoostEnabled(ctx.Guild.Id);
|
||||||
|
if (!enabled)
|
||||||
|
await Response().Pending(strs.boostmsg_enable($"`{prefix}boost`")).SendAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -630,6 +630,13 @@ public class GreetService : IEService, IReadyExecutor
|
||||||
return conf.SendChannelByeMessage;
|
return conf.SendChannelByeMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool GetBoostEnabled(ulong guildId)
|
||||||
|
{
|
||||||
|
using var uow = _db.GetDbContext();
|
||||||
|
var conf = uow.GuildConfigsForId(guildId, set => set);
|
||||||
|
return conf.SendBoostMessage;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Test Messages
|
#region Test Messages
|
||||||
|
|
|
@ -74,6 +74,27 @@ public partial class Gambling
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[OwnerOnly]
|
||||||
|
public async Task BankBalance([Leftover] IUser user)
|
||||||
|
{
|
||||||
|
var bal = await _bank.GetBalanceAsync(user.Id);
|
||||||
|
|
||||||
|
var eb = _sender.CreateEmbed()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithDescription(GetText(strs.bank_balance_other(user.ToString(), N(bal))));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Response().User(ctx.User).Embed(eb).SendAsync();
|
||||||
|
await ctx.OkAsync();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await Response().Error(strs.cant_dm).SendAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task BankTakeInternalAsync(long amount, ulong userId)
|
private async Task BankTakeInternalAsync(long amount, ulong userId)
|
||||||
{
|
{
|
||||||
if (await _bank.TakeAsync(userId, amount))
|
if (await _bank.TakeAsync(userId, amount))
|
||||||
|
|
|
@ -3,6 +3,7 @@ using EllieBot.Modules.Gambling.Common;
|
||||||
using EllieBot.Modules.Gambling.Common.Waifu;
|
using EllieBot.Modules.Gambling.Common.Waifu;
|
||||||
using EllieBot.Modules.Gambling.Services;
|
using EllieBot.Modules.Gambling.Services;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
using TwitchLib.Api.Helix.Models.Teams;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling;
|
namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
|
@ -21,8 +22,8 @@ public partial class Gambling
|
||||||
{
|
{
|
||||||
var price = _service.GetResetPrice(ctx.User);
|
var price = _service.GetResetPrice(ctx.User);
|
||||||
var embed = _sender.CreateEmbed()
|
var embed = _sender.CreateEmbed()
|
||||||
.WithTitle(GetText(strs.waifu_reset_confirm))
|
.WithTitle(GetText(strs.waifu_reset_confirm))
|
||||||
.WithDescription(GetText(strs.waifu_reset_price(Format.Bold(N(price)))));
|
.WithDescription(GetText(strs.waifu_reset_price(Format.Bold(N(price)))));
|
||||||
|
|
||||||
if (!await PromptUserConfirmAsync(embed))
|
if (!await PromptUserConfirmAsync(embed))
|
||||||
return;
|
return;
|
||||||
|
@ -307,24 +308,26 @@ public partial class Gambling
|
||||||
fansStr = "-";
|
fansStr = "-";
|
||||||
|
|
||||||
var embed = _sender.CreateEmbed()
|
var embed = _sender.CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithTitle(GetText(strs.waifu)
|
.WithTitle(GetText(strs.waifu)
|
||||||
+ " "
|
+ " "
|
||||||
+ (wi.FullName ?? name ?? targetId.ToString())
|
+ (wi.FullName ?? name ?? targetId.ToString())
|
||||||
+ " - \"the "
|
+ " - \"the "
|
||||||
+ _service.GetClaimTitle(wi.ClaimCount)
|
+ _service.GetClaimTitle(wi.ClaimCount)
|
||||||
+ "\"")
|
+ "\"")
|
||||||
.AddField(GetText(strs.price), N(wi.Price), true)
|
.AddField(GetText(strs.price), N(wi.Price), true)
|
||||||
.AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true)
|
.AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true)
|
||||||
.AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true)
|
.AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true)
|
||||||
.AddField(GetText(strs.changes_of_heart), $"{wi.AffinityCount} - \"the {affInfo}\"", true)
|
.AddField(GetText(strs.changes_of_heart),
|
||||||
.AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true)
|
$"{wi.AffinityCount} - \"the {affInfo}\"",
|
||||||
.AddField("\u200B", "\u200B", true)
|
true)
|
||||||
.AddField(GetText(strs.fans(fansList.Count)), fansStr, true)
|
.AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true)
|
||||||
.AddField($"Waifus ({wi.ClaimCount})",
|
.AddField("\u200B", "\u200B", true)
|
||||||
wi.ClaimCount == 0 ? nobody : claimsStr,
|
.AddField(GetText(strs.fans(fansList.Count)), fansStr, true)
|
||||||
true)
|
.AddField($"Waifus ({wi.ClaimCount})",
|
||||||
.AddField(GetText(strs.gifts), itemsStr, true);
|
wi.ClaimCount == 0 ? nobody : claimsStr,
|
||||||
|
true)
|
||||||
|
.AddField(GetText(strs.gifts), itemsStr, true);
|
||||||
|
|
||||||
await Response().Embed(embed).SendAsync();
|
await Response().Embed(embed).SendAsync();
|
||||||
}
|
}
|
||||||
|
@ -364,30 +367,27 @@ public partial class Gambling
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[Priority(0)]
|
[Priority(0)]
|
||||||
public async Task WaifuGift(string itemName, [Leftover] IUser waifu)
|
public async Task WaifuGift(MultipleWaifuItems items, [Leftover] IUser waifu)
|
||||||
{
|
{
|
||||||
if (waifu.Id == ctx.User.Id)
|
if (waifu.Id == ctx.User.Id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var allItems = _service.GetWaifuItems();
|
var sucess = await _service.GiftWaifuAsync(ctx.User, waifu, items.Item, items.Count);
|
||||||
var item = allItems.FirstOrDefault(x => x.Name.ToLowerInvariant() == itemName.ToLowerInvariant());
|
|
||||||
if (item is null)
|
|
||||||
{
|
|
||||||
await Response().Error(strs.waifu_gift_not_exist).SendAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sucess = await _service.GiftWaifuAsync(ctx.User, waifu, item);
|
|
||||||
|
|
||||||
if (sucess)
|
if (sucess)
|
||||||
{
|
{
|
||||||
await Response()
|
await Response()
|
||||||
.Confirm(strs.waifu_gift(Format.Bold(item + " " + item.ItemEmoji),
|
.Confirm(strs.waifu_gift(Format.Bold($"{GetCountString(items)}{items.Item} {items.Item.ItemEmoji}"),
|
||||||
Format.Bold(waifu.ToString())))
|
Format.Bold(waifu.ToString())))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
|
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetCountString(MultipleWaifuItems items)
|
||||||
|
=> items.Count > 1
|
||||||
|
? $"{items.Count}x "
|
||||||
|
: string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ using EllieBot.Db;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using EllieBot.Modules.Gambling.Common;
|
using EllieBot.Modules.Gambling.Common;
|
||||||
using EllieBot.Modules.Gambling.Common.Waifu;
|
using EllieBot.Modules.Gambling.Common.Waifu;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Gambling.Services;
|
namespace EllieBot.Modules.Gambling.Services;
|
||||||
|
|
||||||
|
@ -89,9 +90,14 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
if (waifu is null)
|
if (waifu is null)
|
||||||
return settings.Waifu.MinPrice;
|
return settings.Waifu.MinPrice;
|
||||||
|
|
||||||
var divorces = uow.Set<WaifuUpdate>().Count(x
|
var divorces = uow.Set<WaifuUpdate>()
|
||||||
=> x.Old != null && x.Old.UserId == user.Id && x.UpdateType == WaifuUpdateType.Claimed && x.New == null);
|
.Count(x
|
||||||
var affs = uow.Set<WaifuUpdate>().AsQueryable()
|
=> x.Old != null
|
||||||
|
&& x.Old.UserId == user.Id
|
||||||
|
&& x.UpdateType == WaifuUpdateType.Claimed
|
||||||
|
&& x.New == null);
|
||||||
|
var affs = uow.Set<WaifuUpdate>()
|
||||||
|
.AsQueryable()
|
||||||
.Where(w => w.User.UserId == user.Id
|
.Where(w => w.User.UserId == user.Id
|
||||||
&& w.UpdateType == WaifuUpdateType.AffinityChanged
|
&& w.UpdateType == WaifuUpdateType.AffinityChanged
|
||||||
&& w.New != null)
|
&& w.New != null)
|
||||||
|
@ -110,12 +116,14 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
if (!await _cs.RemoveAsync(user.Id, price, new("waifu", "reset")))
|
if (!await _cs.RemoveAsync(user.Id, price, new("waifu", "reset")))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var affs = uow.Set<WaifuUpdate>().AsQueryable()
|
var affs = uow.Set<WaifuUpdate>()
|
||||||
|
.AsQueryable()
|
||||||
.Where(w => w.User.UserId == user.Id
|
.Where(w => w.User.UserId == user.Id
|
||||||
&& w.UpdateType == WaifuUpdateType.AffinityChanged
|
&& w.UpdateType == WaifuUpdateType.AffinityChanged
|
||||||
&& w.New != null);
|
&& w.New != null);
|
||||||
|
|
||||||
var divorces = uow.Set<WaifuUpdate>().AsQueryable()
|
var divorces = uow.Set<WaifuUpdate>()
|
||||||
|
.AsQueryable()
|
||||||
.Where(x => x.Old != null
|
.Where(x => x.Old != null
|
||||||
&& x.Old.UserId == user.Id
|
&& x.Old.UserId == user.Id
|
||||||
&& x.UpdateType == WaifuUpdateType.Claimed
|
&& x.UpdateType == WaifuUpdateType.Claimed
|
||||||
|
@ -158,20 +166,22 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
result = WaifuClaimResult.NotEnoughFunds;
|
result = WaifuClaimResult.NotEnoughFunds;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
uow.Set<WaifuInfo>().Add(w = new()
|
uow.Set<WaifuInfo>()
|
||||||
{
|
.Add(w = new()
|
||||||
Waifu = waifu,
|
{
|
||||||
Claimer = claimer,
|
Waifu = waifu,
|
||||||
Affinity = null,
|
Claimer = claimer,
|
||||||
Price = amount
|
Affinity = null,
|
||||||
});
|
Price = amount
|
||||||
uow.Set<WaifuUpdate>().Add(new()
|
});
|
||||||
{
|
uow.Set<WaifuUpdate>()
|
||||||
User = waifu,
|
.Add(new()
|
||||||
Old = null,
|
{
|
||||||
New = claimer,
|
User = waifu,
|
||||||
UpdateType = WaifuUpdateType.Claimed
|
Old = null,
|
||||||
});
|
New = claimer,
|
||||||
|
UpdateType = WaifuUpdateType.Claimed
|
||||||
|
});
|
||||||
result = WaifuClaimResult.Success;
|
result = WaifuClaimResult.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,13 +196,14 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
w.Price = amount + (amount / 4);
|
w.Price = amount + (amount / 4);
|
||||||
result = WaifuClaimResult.Success;
|
result = WaifuClaimResult.Success;
|
||||||
|
|
||||||
uow.Set<WaifuUpdate>().Add(new()
|
uow.Set<WaifuUpdate>()
|
||||||
{
|
.Add(new()
|
||||||
User = w.Waifu,
|
{
|
||||||
Old = oldClaimer,
|
User = w.Waifu,
|
||||||
New = w.Claimer,
|
Old = oldClaimer,
|
||||||
UpdateType = WaifuUpdateType.Claimed
|
New = w.Claimer,
|
||||||
});
|
UpdateType = WaifuUpdateType.Claimed
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (amount >= w.Price * settings.Waifu.Multipliers.NormalClaim) // if no affinity
|
else if (amount >= w.Price * settings.Waifu.Multipliers.NormalClaim) // if no affinity
|
||||||
|
@ -206,13 +217,14 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
w.Price = amount;
|
w.Price = amount;
|
||||||
result = WaifuClaimResult.Success;
|
result = WaifuClaimResult.Success;
|
||||||
|
|
||||||
uow.Set<WaifuUpdate>().Add(new()
|
uow.Set<WaifuUpdate>()
|
||||||
{
|
.Add(new()
|
||||||
User = w.Waifu,
|
{
|
||||||
Old = oldClaimer,
|
User = w.Waifu,
|
||||||
New = w.Claimer,
|
Old = oldClaimer,
|
||||||
UpdateType = WaifuUpdateType.Claimed
|
New = w.Claimer,
|
||||||
});
|
UpdateType = WaifuUpdateType.Claimed
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -248,22 +260,24 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
else if (w is null)
|
else if (w is null)
|
||||||
{
|
{
|
||||||
var thisUser = uow.GetOrCreateUser(user);
|
var thisUser = uow.GetOrCreateUser(user);
|
||||||
uow.Set<WaifuInfo>().Add(new()
|
uow.Set<WaifuInfo>()
|
||||||
{
|
.Add(new()
|
||||||
Affinity = newAff,
|
{
|
||||||
Waifu = thisUser,
|
Affinity = newAff,
|
||||||
Price = 1,
|
Waifu = thisUser,
|
||||||
Claimer = null
|
Price = 1,
|
||||||
});
|
Claimer = null
|
||||||
|
});
|
||||||
success = true;
|
success = true;
|
||||||
|
|
||||||
uow.Set<WaifuUpdate>().Add(new()
|
uow.Set<WaifuUpdate>()
|
||||||
{
|
.Add(new()
|
||||||
User = thisUser,
|
{
|
||||||
Old = null,
|
User = thisUser,
|
||||||
New = newAff,
|
Old = null,
|
||||||
UpdateType = WaifuUpdateType.AffinityChanged
|
New = newAff,
|
||||||
});
|
UpdateType = WaifuUpdateType.AffinityChanged
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -272,13 +286,14 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
w.Affinity = newAff;
|
w.Affinity = newAff;
|
||||||
success = true;
|
success = true;
|
||||||
|
|
||||||
uow.Set<WaifuUpdate>().Add(new()
|
uow.Set<WaifuUpdate>()
|
||||||
{
|
.Add(new()
|
||||||
User = w.Waifu,
|
{
|
||||||
Old = oldAff,
|
User = w.Waifu,
|
||||||
New = newAff,
|
Old = oldAff,
|
||||||
UpdateType = WaifuUpdateType.AffinityChanged
|
New = newAff,
|
||||||
});
|
UpdateType = WaifuUpdateType.AffinityChanged
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
|
@ -343,13 +358,14 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
var oldClaimer = w.Claimer;
|
var oldClaimer = w.Claimer;
|
||||||
w.Claimer = null;
|
w.Claimer = null;
|
||||||
|
|
||||||
uow.Set<WaifuUpdate>().Add(new()
|
uow.Set<WaifuUpdate>()
|
||||||
{
|
.Add(new()
|
||||||
User = w.Waifu,
|
{
|
||||||
Old = oldClaimer,
|
User = w.Waifu,
|
||||||
New = null,
|
Old = oldClaimer,
|
||||||
UpdateType = WaifuUpdateType.Claimed
|
New = null,
|
||||||
});
|
UpdateType = WaifuUpdateType.Claimed
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
|
@ -358,40 +374,54 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
return (w, result, amount, remaining);
|
return (w, result, amount, remaining);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> GiftWaifuAsync(IUser from, IUser giftedWaifu, WaifuItemModel itemObj)
|
public async Task<bool> GiftWaifuAsync(
|
||||||
|
IUser from,
|
||||||
|
IUser giftedWaifu,
|
||||||
|
WaifuItemModel itemObj,
|
||||||
|
int count)
|
||||||
{
|
{
|
||||||
if (!await _cs.RemoveAsync(from, itemObj.Price, new("waifu", "item")))
|
ArgumentOutOfRangeException.ThrowIfLessThan(count, 1, nameof(count));
|
||||||
|
|
||||||
|
if (!await _cs.RemoveAsync(from, itemObj.Price * count, new("waifu", "item")))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
var totalValue = itemObj.Price * count;
|
||||||
|
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
var w = uow.Set<WaifuInfo>().ByWaifuUserId(giftedWaifu.Id, set => set.Include(x => x.Items).Include(x => x.Claimer));
|
var w = uow.Set<WaifuInfo>()
|
||||||
|
.ByWaifuUserId(giftedWaifu.Id,
|
||||||
|
set => set
|
||||||
|
.Include(x => x.Items)
|
||||||
|
.Include(x => x.Claimer));
|
||||||
if (w is null)
|
if (w is null)
|
||||||
{
|
{
|
||||||
uow.Set<WaifuInfo>().Add(w = new()
|
uow.Set<WaifuInfo>()
|
||||||
{
|
.Add(w = new()
|
||||||
Affinity = null,
|
{
|
||||||
Claimer = null,
|
Affinity = null,
|
||||||
Price = 1,
|
Claimer = null,
|
||||||
Waifu = uow.GetOrCreateUser(giftedWaifu)
|
Price = 1,
|
||||||
});
|
Waifu = uow.GetOrCreateUser(giftedWaifu)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!itemObj.Negative)
|
if (!itemObj.Negative)
|
||||||
{
|
{
|
||||||
w.Items.Add(new()
|
w.Items.AddRange(Enumerable.Range(0, count)
|
||||||
{
|
.Select((_) => new WaifuItem()
|
||||||
Name = itemObj.Name.ToLowerInvariant(),
|
{
|
||||||
ItemEmoji = itemObj.ItemEmoji
|
Name = itemObj.Name.ToLowerInvariant(),
|
||||||
});
|
ItemEmoji = itemObj.ItemEmoji
|
||||||
|
}));
|
||||||
|
|
||||||
if (w.Claimer?.UserId == from.Id)
|
if (w.Claimer?.UserId == from.Id)
|
||||||
w.Price += (long)(itemObj.Price * _gss.Data.Waifu.Multipliers.GiftEffect);
|
w.Price += (long)(totalValue * _gss.Data.Waifu.Multipliers.GiftEffect);
|
||||||
else
|
else
|
||||||
w.Price += itemObj.Price / 2;
|
w.Price += totalValue / 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
w.Price -= (long)(itemObj.Price * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
|
w.Price -= (long)(totalValue * _gss.Data.Waifu.Multipliers.NegativeGiftEffect);
|
||||||
if (w.Price < 1)
|
if (w.Price < 1)
|
||||||
w.Price = 1;
|
w.Price = 1;
|
||||||
}
|
}
|
||||||
|
@ -492,6 +522,7 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly TypedKey<long> _waifuDecayKey = $"waifu:last_decay";
|
private static readonly TypedKey<long> _waifuDecayKey = $"waifu:last_decay";
|
||||||
|
|
||||||
public async Task OnReadyAsync()
|
public async Task OnReadyAsync()
|
||||||
{
|
{
|
||||||
// only decay waifu values from shard 0
|
// only decay waifu values from shard 0
|
||||||
|
@ -533,7 +564,6 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
{
|
{
|
||||||
Price = (long)(old.Price * multi)
|
Price = (long)(old.Price * multi)
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -550,33 +580,35 @@ public class WaifuService : IEService, IReadyExecutor
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
return await ctx.GetTable<DiscordUser>()
|
return await ctx.GetTable<DiscordUser>()
|
||||||
.Where(x => ctx.GetTable<WaifuInfo>()
|
.Where(x => ctx.GetTable<WaifuInfo>()
|
||||||
.Where(wi => wi.ClaimerId == waifuId)
|
.Where(wi => wi.ClaimerId == waifuId)
|
||||||
.Select(wi => wi.WaifuId)
|
.Select(wi => wi.WaifuId)
|
||||||
.Contains(x.Id))
|
.Contains(x.Id))
|
||||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
.Select(x => $"{x.Username}#{x.Discriminator}")
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyCollection<string>> GetFansNames(int waifuId)
|
public async Task<IReadOnlyCollection<string>> GetFansNames(int waifuId)
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
return await ctx.GetTable<DiscordUser>()
|
return await ctx.GetTable<DiscordUser>()
|
||||||
.Where(x => ctx.GetTable<WaifuInfo>()
|
.Where(x => ctx.GetTable<WaifuInfo>()
|
||||||
.Where(wi => wi.AffinityId == waifuId)
|
.Where(wi => wi.AffinityId == waifuId)
|
||||||
.Select(wi => wi.WaifuId)
|
.Select(wi => wi.WaifuId)
|
||||||
.Contains(x.Id))
|
.Contains(x.Id))
|
||||||
.Select(x => $"{x.Username}#{x.Discriminator}")
|
.Select(x => $"{x.Username}#{x.Discriminator}")
|
||||||
.ToListAsyncEF();
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyCollection<WaifuItem>> GetItems(int waifuId)
|
public async Task<IReadOnlyCollection<WaifuItem>> GetItems(int waifuId)
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
return await ctx.GetTable<WaifuItem>()
|
return await ctx.GetTable<WaifuItem>()
|
||||||
.Where(x => x.WaifuInfoId == ctx.GetTable<WaifuInfo>()
|
.Where(x => x.WaifuInfoId
|
||||||
.Where(x => x.WaifuId == waifuId)
|
== ctx.GetTable<WaifuInfo>()
|
||||||
.Select(x => x.Id)
|
.Where(x => x.WaifuId == waifuId)
|
||||||
.FirstOrDefault())
|
.Select(x => x.Id)
|
||||||
.ToListAsyncEF();
|
.FirstOrDefault())
|
||||||
|
.ToListAsyncEF();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
#nullable disable
|
||||||
|
using EllieBot.Modules.Gambling.Common;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
|
public record class MultipleWaifuItems(int Count, WaifuItemModel Item);
|
|
@ -0,0 +1,47 @@
|
||||||
|
#nullable disable
|
||||||
|
using EllieBot.Common.TypeReaders;
|
||||||
|
using EllieBot.Modules.Gambling.Services;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Gambling;
|
||||||
|
|
||||||
|
public partial class MultipleWaifuItemsTypeReader : EllieTypeReader<MultipleWaifuItems>
|
||||||
|
{
|
||||||
|
private readonly WaifuService _service;
|
||||||
|
|
||||||
|
[GeneratedRegex(@"(?:(?<count>\d+)[x*])?(?<item>.+)")]
|
||||||
|
private static partial Regex ItemRegex();
|
||||||
|
|
||||||
|
public MultipleWaifuItemsTypeReader(WaifuService service)
|
||||||
|
{
|
||||||
|
_service = service;
|
||||||
|
}
|
||||||
|
public override ValueTask<TypeReaderResult<MultipleWaifuItems>> ReadAsync(ICommandContext ctx, string input)
|
||||||
|
{
|
||||||
|
input = input.ToLowerInvariant();
|
||||||
|
var match = ItemRegex().Match(input);
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
return new(Discord.Commands.TypeReaderResult.FromError(CommandError.ParseFailed, "Invalid input."));
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 1;
|
||||||
|
if (match.Groups["count"].Success)
|
||||||
|
{
|
||||||
|
if (!int.TryParse(match.Groups["count"].Value, out count) || count < 1)
|
||||||
|
{
|
||||||
|
return new(Discord.Commands.TypeReaderResult.FromError(CommandError.ParseFailed, "Invalid count."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemName = match.Groups["item"].Value?.ToLowerInvariant();
|
||||||
|
var allItems = _service.GetWaifuItems();
|
||||||
|
var item = allItems.FirstOrDefault(x => x.Name.ToLowerInvariant() == itemName);
|
||||||
|
if (item is null)
|
||||||
|
{
|
||||||
|
return new(Discord.Commands.TypeReaderResult.FromError(CommandError.ParseFailed, "Waifu gift does not exist."));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(Discord.Commands.TypeReaderResult.FromSuccess(new MultipleWaifuItems(count, item)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
using EllieBot.Db.Models;
|
|
||||||
using EllieBot.Modules.Games.Common;
|
using EllieBot.Modules.Games.Common;
|
||||||
using EllieBot.Modules.Games.Common.ChatterBot;
|
using EllieBot.Modules.Games.Common.ChatterBot;
|
||||||
using EllieBot.Modules.Patronage;
|
using EllieBot.Modules.Patronage;
|
||||||
|
@ -58,18 +57,21 @@ public class ChatterBotService : IExecOnMessage
|
||||||
|
|
||||||
Log.Information("Cleverbot will not work as the api key is missing");
|
Log.Information("Cleverbot will not work as the api key is missing");
|
||||||
return null;
|
return null;
|
||||||
case ChatBotImplementation.Gpt:
|
case ChatBotImplementation.OpenAi:
|
||||||
|
var data = _gcs.Data;
|
||||||
if (!string.IsNullOrWhiteSpace(_creds.Gpt3ApiKey))
|
if (!string.IsNullOrWhiteSpace(_creds.Gpt3ApiKey))
|
||||||
return new OfficialGptSession(_creds.Gpt3ApiKey,
|
return new OpenAiApiSession(
|
||||||
_gcs.Data.ChatGpt.ModelName,
|
data.ChatGpt.ApiUrl,
|
||||||
_gcs.Data.ChatGpt.ChatHistory,
|
_creds.Gpt3ApiKey,
|
||||||
_gcs.Data.ChatGpt.MaxTokens,
|
data.ChatGpt.ModelName,
|
||||||
_gcs.Data.ChatGpt.MinTokens,
|
data.ChatGpt.ChatHistory,
|
||||||
_gcs.Data.ChatGpt.PersonalityPrompt,
|
data.ChatGpt.MaxTokens,
|
||||||
|
data.ChatGpt.MinTokens,
|
||||||
|
data.ChatGpt.PersonalityPrompt,
|
||||||
_client.CurrentUser.Username,
|
_client.CurrentUser.Username,
|
||||||
_httpFactory);
|
_httpFactory);
|
||||||
|
|
||||||
Log.Information("Gpt3 will not work as the api key is missing");
|
Log.Information("Openai Api will likely not work as the api key is missing");
|
||||||
return null;
|
return null;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
|
9
src/EllieBot/Modules/Games/ChatterBot/_common/Choice.cs
Normal file
9
src/EllieBot/Modules/Games/ChatterBot/_common/Choice.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Games.Common.ChatterBot;
|
||||||
|
|
||||||
|
public class Choice
|
||||||
|
{
|
||||||
|
[JsonPropertyName("message")]
|
||||||
|
public Message Message { get; init; }
|
||||||
|
}
|
|
@ -1,61 +0,0 @@
|
||||||
#nullable disable
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Games.Common.ChatterBot;
|
|
||||||
|
|
||||||
public class OpenAiCompletionResponse
|
|
||||||
{
|
|
||||||
[JsonPropertyName("choices")]
|
|
||||||
public Choice[] Choices { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("usage")]
|
|
||||||
public OpenAiUsageData Usage { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class OpenAiUsageData
|
|
||||||
{
|
|
||||||
[JsonPropertyName("prompt_tokens")]
|
|
||||||
public int PromptTokens { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("completion_tokens")]
|
|
||||||
public int CompletionTokens { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("total_tokens")]
|
|
||||||
public int TotalTokens { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Choice
|
|
||||||
{
|
|
||||||
[JsonPropertyName("message")]
|
|
||||||
public Message Message { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Message {
|
|
||||||
[JsonPropertyName("content")]
|
|
||||||
public string Content { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Gpt3ApiRequest
|
|
||||||
{
|
|
||||||
[JsonPropertyName("model")]
|
|
||||||
public string Model { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("messages")]
|
|
||||||
public List<GPTMessage> Messages { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("temperature")]
|
|
||||||
public int Temperature { get; init; }
|
|
||||||
|
|
||||||
[JsonPropertyName("max_tokens")]
|
|
||||||
public int MaxTokens { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class GPTMessage
|
|
||||||
{
|
|
||||||
[JsonPropertyName("role")]
|
|
||||||
public string Role {get; init;}
|
|
||||||
[JsonPropertyName("content")]
|
|
||||||
public string Content {get; init;}
|
|
||||||
[JsonPropertyName("name")]
|
|
||||||
public string Name {get; init;}
|
|
||||||
}
|
|
9
src/EllieBot/Modules/Games/ChatterBot/_common/Message.cs
Normal file
9
src/EllieBot/Modules/Games/ChatterBot/_common/Message.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Games.Common.ChatterBot;
|
||||||
|
|
||||||
|
public class Message
|
||||||
|
{
|
||||||
|
[JsonPropertyName("content")]
|
||||||
|
public string Content { get; init; }
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Games.Common.ChatterBot;
|
||||||
|
|
||||||
|
public class OpenAiApiMessage
|
||||||
|
{
|
||||||
|
[JsonPropertyName("role")]
|
||||||
|
public string Role { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("content")]
|
||||||
|
public string Content { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; init; }
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Games.Common.ChatterBot;
|
||||||
|
|
||||||
|
public class OpenAiApiRequest
|
||||||
|
{
|
||||||
|
[JsonPropertyName("model")]
|
||||||
|
public string Model { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("messages")]
|
||||||
|
public List<OpenAiApiMessage> Messages { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("temperature")]
|
||||||
|
public int Temperature { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("max_tokens")]
|
||||||
|
public int MaxTokens { get; init; }
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Games.Common.ChatterBot;
|
||||||
|
|
||||||
|
public class OpenAiApiUsageData
|
||||||
|
{
|
||||||
|
[JsonPropertyName("prompt_tokens")]
|
||||||
|
public int PromptTokens { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("completion_tokens")]
|
||||||
|
public int CompletionTokens { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("total_tokens")]
|
||||||
|
public int TotalTokens { get; set; }
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#nullable disable
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Games.Common.ChatterBot;
|
||||||
|
|
||||||
|
public class OpenAiCompletionResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("choices")]
|
||||||
|
public Choice[] Choices { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("usage")]
|
||||||
|
public OpenAiApiUsageData Usage { get; set; }
|
||||||
|
}
|
|
@ -1,18 +1,15 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OneOf.Types;
|
using OneOf.Types;
|
||||||
using System.Net.Http.Json;
|
|
||||||
using SharpToken;
|
using SharpToken;
|
||||||
using System.CodeDom;
|
using System.Net.Http.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Games.Common.ChatterBot;
|
namespace EllieBot.Modules.Games.Common.ChatterBot;
|
||||||
|
|
||||||
public partial class OfficialGptSession : IChatterBotSession
|
public partial class OpenAiApiSession : IChatterBotSession
|
||||||
{
|
{
|
||||||
private string Uri
|
private readonly string _baseUrl;
|
||||||
=> $"https://api.openai.com/v1/chat/completions";
|
|
||||||
|
|
||||||
private readonly string _apiKey;
|
private readonly string _apiKey;
|
||||||
private readonly string _model;
|
private readonly string _model;
|
||||||
private readonly int _maxHistory;
|
private readonly int _maxHistory;
|
||||||
|
@ -20,13 +17,14 @@ public partial class OfficialGptSession : IChatterBotSession
|
||||||
private readonly int _minTokens;
|
private readonly int _minTokens;
|
||||||
private readonly string _ellieUsername;
|
private readonly string _ellieUsername;
|
||||||
private readonly GptEncoding _encoding;
|
private readonly GptEncoding _encoding;
|
||||||
private List<GPTMessage> messages = new();
|
private List<OpenAiApiMessage> messages = new();
|
||||||
private readonly IHttpClientFactory _httpFactory;
|
private readonly IHttpClientFactory _httpFactory;
|
||||||
|
|
||||||
|
|
||||||
public OfficialGptSession(
|
public OpenAiApiSession(
|
||||||
|
string url,
|
||||||
string apiKey,
|
string apiKey,
|
||||||
ChatGptModel model,
|
string model,
|
||||||
int chatHistory,
|
int chatHistory,
|
||||||
int maxTokens,
|
int maxTokens,
|
||||||
int minTokens,
|
int minTokens,
|
||||||
|
@ -34,27 +32,30 @@ public partial class OfficialGptSession : IChatterBotSession
|
||||||
string ellieUsername,
|
string ellieUsername,
|
||||||
IHttpClientFactory factory)
|
IHttpClientFactory factory)
|
||||||
{
|
{
|
||||||
_apiKey = apiKey;
|
if (string.IsNullOrWhiteSpace(url) || !Uri.TryCreate(url, UriKind.Absolute, out _))
|
||||||
_httpFactory = factory;
|
|
||||||
|
|
||||||
_model = model switch
|
|
||||||
{
|
{
|
||||||
ChatGptModel.Gpt35Turbo => "gpt-3.5-turbo",
|
throw new ArgumentException("Invalid OpenAi api url provided", nameof(url));
|
||||||
ChatGptModel.Gpt4o => "gpt-4o",
|
}
|
||||||
_ => throw new ArgumentException("Unknown, unsupported or obsolete model", nameof(model))
|
|
||||||
};
|
|
||||||
|
|
||||||
|
_baseUrl = url.TrimEnd('/');
|
||||||
|
|
||||||
|
_apiKey = apiKey;
|
||||||
|
_model = model;
|
||||||
|
_httpFactory = factory;
|
||||||
_maxHistory = chatHistory;
|
_maxHistory = chatHistory;
|
||||||
_maxTokens = maxTokens;
|
_maxTokens = maxTokens;
|
||||||
_minTokens = minTokens;
|
_minTokens = minTokens;
|
||||||
_ellieUsername = UsernameCleaner().Replace(ellieUsername, "");
|
_ellieUsername = UsernameCleaner().Replace(ellieUsername, "");
|
||||||
_encoding = GptEncoding.GetEncodingForModel(_model);
|
_encoding = GptEncoding.GetEncodingForModel("gpt-4o");
|
||||||
messages.Add(new()
|
if (!string.IsNullOrWhiteSpace(personality))
|
||||||
{
|
{
|
||||||
Role = "system",
|
messages.Add(new()
|
||||||
Content = personality,
|
{
|
||||||
Name = _ellieUsername
|
Role = "system",
|
||||||
});
|
Content = personality,
|
||||||
|
Name = _ellieUsername
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,15 +93,16 @@ public partial class OfficialGptSession : IChatterBotSession
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new Error<string>("Token count exceeded, please increase the number of tokens in the bot config and restart.");
|
return new Error<string>(
|
||||||
|
"Token count exceeded, please increase the number of tokens in the bot config and restart.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using var http = _httpFactory.CreateClient();
|
using var http = _httpFactory.CreateClient();
|
||||||
http.DefaultRequestHeaders.Authorization = new("Bearer", _apiKey);
|
http.DefaultRequestHeaders.Authorization = new("Bearer", _apiKey);
|
||||||
|
|
||||||
var data = await http.PostAsJsonAsync(Uri,
|
var data = await http.PostAsJsonAsync($"{_baseUrl}/v1/chat/completions",
|
||||||
new Gpt3ApiRequest()
|
new OpenAiApiRequest()
|
||||||
{
|
{
|
||||||
Model = _model,
|
Model = _model,
|
||||||
Messages = messages,
|
Messages = messages,
|
||||||
|
@ -113,7 +115,7 @@ public partial class OfficialGptSession : IChatterBotSession
|
||||||
{
|
{
|
||||||
var response = JsonConvert.DeserializeObject<OpenAiCompletionResponse>(dataString);
|
var response = JsonConvert.DeserializeObject<OpenAiCompletionResponse>(dataString);
|
||||||
|
|
||||||
Log.Information("Received response: {response} ", dataString);
|
// Log.Information("Received response: {Response} ", dataString);
|
||||||
var res = response?.Choices?[0];
|
var res = response?.Choices?[0];
|
||||||
var message = res?.Message?.Content;
|
var message = res?.Message?.Content;
|
||||||
|
|
||||||
|
@ -143,10 +145,3 @@ public partial class OfficialGptSession : IChatterBotSession
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ThinkResult
|
|
||||||
{
|
|
||||||
public string Text { get; set; }
|
|
||||||
public int TokensIn { get; set; }
|
|
||||||
public int TokensOut { get; set; }
|
|
||||||
}
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
#nullable disable
|
||||||
|
using System.CodeDom;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Games.Common.ChatterBot;
|
||||||
|
|
||||||
|
public sealed class ThinkResult
|
||||||
|
{
|
||||||
|
public string Text { get; set; }
|
||||||
|
public int TokensIn { get; set; }
|
||||||
|
public int TokensOut { get; set; }
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ namespace EllieBot.Modules.Games.Common;
|
||||||
public sealed partial class GamesConfig : ICloneable<GamesConfig>
|
public sealed partial class GamesConfig : ICloneable<GamesConfig>
|
||||||
{
|
{
|
||||||
[Comment("DO NOT CHANGE")]
|
[Comment("DO NOT CHANGE")]
|
||||||
public int Version { get; set; } = 4;
|
public int Version { get; set; } = 5;
|
||||||
|
|
||||||
[Comment("Hangman related settings (.hangman command)")]
|
[Comment("Hangman related settings (.hangman command)")]
|
||||||
public HangmanConfig Hangman { get; set; } = new()
|
public HangmanConfig Hangman { get; set; } = new()
|
||||||
|
@ -103,10 +103,13 @@ public sealed partial class GamesConfig : ICloneable<GamesConfig>
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
[Comment(@"Which chatbot API should bot use.
|
[Comment(
|
||||||
'cleverbot' - bot will use Cleverbot API.
|
"""
|
||||||
'gpt' - bot will use GPT API")]
|
Which chatbot API should bot use.
|
||||||
public ChatBotImplementation ChatBot { get; set; } = ChatBotImplementation.Gpt;
|
'cleverbot' - bot will use Cleverbot API.
|
||||||
|
'openai' - bot will use OpenAi API
|
||||||
|
""")]
|
||||||
|
public ChatBotImplementation ChatBot { get; set; } = ChatBotImplementation.OpenAi;
|
||||||
|
|
||||||
public ChatGptConfig ChatGpt { get; set; } = new();
|
public ChatGptConfig ChatGpt { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
@ -114,19 +117,38 @@ public sealed partial class GamesConfig : ICloneable<GamesConfig>
|
||||||
[Cloneable]
|
[Cloneable]
|
||||||
public sealed partial class ChatGptConfig
|
public sealed partial class ChatGptConfig
|
||||||
{
|
{
|
||||||
[Comment(@"Which GPT Model should bot use.
|
[Comment("""
|
||||||
gpt35turbo - cheapest
|
Url to any openai api compatible url.
|
||||||
gpt4o - more expensive, higher quality
|
Make sure to modify the modelName appropriately
|
||||||
")]
|
DO NOT add /v1/chat/completions suffix to the url
|
||||||
public ChatGptModel ModelName { get; set; } = ChatGptModel.Gpt35Turbo;
|
""")]
|
||||||
|
public string ApiUrl { get; set; } = "https://api.openai.com";
|
||||||
|
|
||||||
[Comment(@"How should the chat bot behave, what's its personality? (Usage of this counts towards the max tokens)")]
|
[Comment("""
|
||||||
public string PersonalityPrompt { get; set; } = "You are a chat bot willing to have a conversation with anyone about anything.";
|
Which GPT Model should bot use.
|
||||||
|
gpt-3.5-turbo - cheapest
|
||||||
|
gpt-4o - more expensive, higher quality
|
||||||
|
|
||||||
[Comment(@"The maximum number of messages in a conversation that can be remembered. (This will increase the number of tokens used)")]
|
If you are using another openai compatible api, you may use any of the models supported by that api
|
||||||
|
""")]
|
||||||
|
public string ModelName { get; set; } = "gpt-3.5-turbo";
|
||||||
|
|
||||||
|
[Comment("""
|
||||||
|
How should the chatbot behave, what's its personality?
|
||||||
|
This will be sent as a system message.
|
||||||
|
Usage of this counts towards the max tokens.
|
||||||
|
""")]
|
||||||
|
public string PersonalityPrompt { get; set; } =
|
||||||
|
"You are a chat bot willing to have a conversation with anyone about anything.";
|
||||||
|
|
||||||
|
[Comment(
|
||||||
|
"""
|
||||||
|
The maximum number of messages in a conversation that can be remembered.
|
||||||
|
This will increase the number of tokens used.
|
||||||
|
""")]
|
||||||
public int ChatHistory { get; set; } = 5;
|
public int ChatHistory { get; set; } = 5;
|
||||||
|
|
||||||
[Comment(@"The maximum number of tokens to use per GPT API call")]
|
[Comment(@"The maximum number of tokens to use per OpenAi API call")]
|
||||||
public int MaxTokens { get; set; } = 100;
|
public int MaxTokens { get; set; } = 100;
|
||||||
|
|
||||||
[Comment(@"The minimum number of tokens to use per GPT API call, such that chat history is removed to make room.")]
|
[Comment(@"The minimum number of tokens to use per GPT API call, such that chat history is removed to make room.")]
|
||||||
|
@ -147,9 +169,9 @@ public sealed partial class TriviaConfig
|
||||||
public long CurrencyReward { get; set; }
|
public long CurrencyReward { get; set; }
|
||||||
|
|
||||||
[Comment("""
|
[Comment("""
|
||||||
Users won't be able to start trivia games which have
|
Users won't be able to start trivia games which have
|
||||||
a smaller win requirement than the one specified by this setting.
|
a smaller win requirement than the one specified by this setting.
|
||||||
""")]
|
""")]
|
||||||
public int MinimumWinReq { get; set; } = 1;
|
public int MinimumWinReq { get; set; } = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,18 +185,11 @@ public sealed partial class RaceAnimal
|
||||||
public enum ChatBotImplementation
|
public enum ChatBotImplementation
|
||||||
{
|
{
|
||||||
Cleverbot,
|
Cleverbot,
|
||||||
|
OpenAi = 1,
|
||||||
|
|
||||||
|
[Obsolete]
|
||||||
Gpt = 1,
|
Gpt = 1,
|
||||||
|
|
||||||
[Obsolete]
|
[Obsolete]
|
||||||
Gpt3 = 1,
|
Gpt3 = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ChatGptModel
|
|
||||||
{
|
|
||||||
[Obsolete]
|
|
||||||
Gpt4,
|
|
||||||
[Obsolete]
|
|
||||||
Gpt432k,
|
|
||||||
|
|
||||||
Gpt35Turbo,
|
|
||||||
Gpt4o,
|
|
||||||
}
|
|
|
@ -32,29 +32,21 @@ public sealed class GamesConfigService : ConfigServiceBase<GamesConfig>
|
||||||
gs => gs.ChatBot,
|
gs => gs.ChatBot,
|
||||||
ConfigParsers.InsensitiveEnum,
|
ConfigParsers.InsensitiveEnum,
|
||||||
ConfigPrinters.ToString);
|
ConfigPrinters.ToString);
|
||||||
|
|
||||||
|
AddParsedProp("gpt.apiUrl",
|
||||||
|
gs => gs.ChatGpt.ApiUrl,
|
||||||
|
ConfigParsers.String,
|
||||||
|
ConfigPrinters.ToString);
|
||||||
|
|
||||||
AddParsedProp("gpt.modelName",
|
AddParsedProp("gpt.modelName",
|
||||||
gs => gs.ChatGpt.ModelName,
|
gs => gs.ChatGpt.ModelName,
|
||||||
ConfigParsers.InsensitiveEnum,
|
ConfigParsers.String,
|
||||||
ConfigPrinters.ToString);
|
ConfigPrinters.ToString);
|
||||||
|
|
||||||
AddParsedProp("gpt.personality",
|
AddParsedProp("gpt.personality",
|
||||||
gs => gs.ChatGpt.PersonalityPrompt,
|
gs => gs.ChatGpt.PersonalityPrompt,
|
||||||
ConfigParsers.String,
|
ConfigParsers.String,
|
||||||
ConfigPrinters.ToString);
|
ConfigPrinters.ToString);
|
||||||
AddParsedProp("gpt.chathistory",
|
|
||||||
gs => gs.ChatGpt.ChatHistory,
|
|
||||||
int.TryParse,
|
|
||||||
ConfigPrinters.ToString,
|
|
||||||
val => val > 0);
|
|
||||||
AddParsedProp("gpt.max_tokens",
|
|
||||||
gs => gs.ChatGpt.MaxTokens,
|
|
||||||
int.TryParse,
|
|
||||||
ConfigPrinters.ToString,
|
|
||||||
val => val > 0);
|
|
||||||
AddParsedProp("gpt.min_tokens",
|
|
||||||
gs => gs.ChatGpt.MinTokens,
|
|
||||||
int.TryParse,
|
|
||||||
ConfigPrinters.ToString,
|
|
||||||
val => val > 0);
|
|
||||||
|
|
||||||
Migrate();
|
Migrate();
|
||||||
}
|
}
|
||||||
|
@ -78,7 +70,7 @@ public sealed class GamesConfigService : ConfigServiceBase<GamesConfig>
|
||||||
ModifyConfig(c =>
|
ModifyConfig(c =>
|
||||||
{
|
{
|
||||||
c.Version = 3;
|
c.Version = 3;
|
||||||
c.ChatGpt.ModelName = ChatGptModel.Gpt35Turbo;
|
c.ChatGpt.ModelName = "gpt35turbo";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,11 +81,40 @@ public sealed class GamesConfigService : ConfigServiceBase<GamesConfig>
|
||||||
c.Version = 4;
|
c.Version = 4;
|
||||||
#pragma warning disable CS0612 // Type or member is obsolete
|
#pragma warning disable CS0612 // Type or member is obsolete
|
||||||
c.ChatGpt.ModelName =
|
c.ChatGpt.ModelName =
|
||||||
c.ChatGpt.ModelName == ChatGptModel.Gpt4 || c.ChatGpt.ModelName == ChatGptModel.Gpt432k
|
c.ChatGpt.ModelName.Equals("gpt4", StringComparison.OrdinalIgnoreCase)
|
||||||
? ChatGptModel.Gpt4o
|
|| c.ChatGpt.ModelName.Equals("gpt432k", StringComparison.OrdinalIgnoreCase)
|
||||||
: c.ChatGpt.ModelName;
|
? "gpt-4o"
|
||||||
|
: "gpt-3.5-turbo";
|
||||||
#pragma warning restore CS0612 // Type or member is obsolete
|
#pragma warning restore CS0612 // Type or member is obsolete
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.Version < 5)
|
||||||
|
{
|
||||||
|
ModifyConfig(c =>
|
||||||
|
{
|
||||||
|
c.Version = 5;
|
||||||
|
c.ChatBot = c.ChatBot == ChatBotImplementation.OpenAi
|
||||||
|
? ChatBotImplementation.OpenAi
|
||||||
|
: c.ChatBot;
|
||||||
|
|
||||||
|
if (c.ChatGpt.ModelName.Equals("gpt4o", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
c.ChatGpt.ModelName = "gpt-4o";
|
||||||
|
}
|
||||||
|
else if (c.ChatGpt.ModelName.Equals("gpt35turbo", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
c.ChatGpt.ModelName = "gpt-3.5-turbo";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Warning(
|
||||||
|
"Unknown OpenAI api model name: {ModelName}. "
|
||||||
|
+ "It will be reset to 'gpt-3.5-turbo' only this time",
|
||||||
|
c.ChatGpt.ModelName);
|
||||||
|
c.ChatGpt.ModelName = "gpt-3.5-turbo";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,7 +18,7 @@ public partial class Searches
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
public async Task Stock([Leftover]string query)
|
public async Task Stock([Leftover] string query)
|
||||||
{
|
{
|
||||||
using var typing = ctx.Channel.EnterTypingState();
|
using var typing = ctx.Channel.EnterTypingState();
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ public partial class Searches
|
||||||
|
|
||||||
var symbol = symbols.First();
|
var symbol = symbols.First();
|
||||||
var promptEmbed = _sender.CreateEmbed()
|
var promptEmbed = _sender.CreateEmbed()
|
||||||
.WithDescription(symbol.Description)
|
.WithDescription(symbol.Description)
|
||||||
.WithTitle(GetText(strs.did_you_mean(symbol.Symbol)));
|
.WithTitle(GetText(strs.did_you_mean(symbol.Symbol)));
|
||||||
|
|
||||||
if (!await PromptUserConfirmAsync(promptEmbed))
|
if (!await PromptUserConfirmAsync(promptEmbed))
|
||||||
return;
|
return;
|
||||||
|
@ -80,17 +80,17 @@ public partial class Searches
|
||||||
var price = stock.Price.ToString("C2", localCulture);
|
var price = stock.Price.ToString("C2", localCulture);
|
||||||
|
|
||||||
var eb = _sender.CreateEmbed()
|
var eb = _sender.CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithAuthor(stock.Symbol)
|
.WithAuthor(stock.Symbol)
|
||||||
.WithUrl($"https://www.tradingview.com/chart/?symbol={stock.Symbol}")
|
.WithUrl($"https://www.tradingview.com/chart/?symbol={stock.Symbol}")
|
||||||
.WithTitle(stock.Name)
|
.WithTitle(stock.Name)
|
||||||
.AddField(GetText(strs.price), $"{sign} **{price}**", true)
|
.AddField(GetText(strs.price), $"{sign} **{price}**", true)
|
||||||
.AddField(GetText(strs.market_cap), stock.MarketCap, true)
|
.AddField(GetText(strs.market_cap), stock.MarketCap, true)
|
||||||
.AddField(GetText(strs.volume_24h), stock.DailyVolume.ToString("C0", localCulture), true)
|
.AddField(GetText(strs.volume_24h), stock.DailyVolume.ToString("C0", localCulture), true)
|
||||||
.AddField("Change", $"{change} ({changePercent})", true)
|
.AddField("Change", $"{change} ({changePercent})", true)
|
||||||
// .AddField("Change 50d", $"{sign50}{change50}", true)
|
// .AddField("Change 50d", $"{sign50}{change50}", true)
|
||||||
// .AddField("Change 200d", $"{sign200}{change200}", true)
|
// .AddField("Change 200d", $"{sign200}{change200}", true)
|
||||||
.WithFooter(stock.Exchange);
|
.WithFooter(stock.Exchange);
|
||||||
|
|
||||||
var message = await Response().Embed(eb).SendAsync();
|
var message = await Response().Embed(eb).SendAsync();
|
||||||
await using var imageData = await stockImageTask;
|
await using var imageData = await stockImageTask;
|
||||||
|
@ -105,10 +105,7 @@ public partial class Searches
|
||||||
await message.ModifyAsync(mp =>
|
await message.ModifyAsync(mp =>
|
||||||
{
|
{
|
||||||
mp.Attachments =
|
mp.Attachments =
|
||||||
new(new[]
|
new(new[] { attachment });
|
||||||
{
|
|
||||||
attachment
|
|
||||||
});
|
|
||||||
|
|
||||||
mp.Embed = eb.WithImageUrl($"attachment://{fileName}").Build();
|
mp.Embed = eb.WithImageUrl($"attachment://{fileName}").Build();
|
||||||
});
|
});
|
||||||
|
@ -128,9 +125,9 @@ public partial class Searches
|
||||||
if (nearest is not null)
|
if (nearest is not null)
|
||||||
{
|
{
|
||||||
var embed = _sender.CreateEmbed()
|
var embed = _sender.CreateEmbed()
|
||||||
.WithTitle(GetText(strs.crypto_not_found))
|
.WithTitle(GetText(strs.crypto_not_found))
|
||||||
.WithDescription(
|
.WithDescription(
|
||||||
GetText(strs.did_you_mean(Format.Bold($"{nearest.Name} ({nearest.Symbol})"))));
|
GetText(strs.did_you_mean(Format.Bold($"{nearest.Name} ({nearest.Symbol})"))));
|
||||||
|
|
||||||
if (await PromptUserConfirmAsync(embed))
|
if (await PromptUserConfirmAsync(embed))
|
||||||
crypto = nearest;
|
crypto = nearest;
|
||||||
|
@ -161,17 +158,18 @@ public partial class Searches
|
||||||
var fileName = $"{crypto.Slug}_7d.png";
|
var fileName = $"{crypto.Slug}_7d.png";
|
||||||
|
|
||||||
var toSend = _sender.CreateEmbed()
|
var toSend = _sender.CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithAuthor($"#{crypto.CmcRank}")
|
.WithAuthor($"#{crypto.CmcRank}")
|
||||||
.WithTitle($"{crypto.Name} ({crypto.Symbol})")
|
.WithTitle($"{crypto.Name} ({crypto.Symbol})")
|
||||||
.WithUrl($"https://coinmarketcap.com/currencies/{crypto.Slug}/")
|
.WithUrl($"https://coinmarketcap.com/currencies/{crypto.Slug}/")
|
||||||
.WithThumbnailUrl($"https://s3.coinmarketcap.com/static/img/coins/128x128/{crypto.Id}.png")
|
.WithThumbnailUrl(
|
||||||
.AddField(GetText(strs.market_cap), marketCap, true)
|
$"https://s3.coinmarketcap.com/static/img/coins/128x128/{crypto.Id}.png")
|
||||||
.AddField(GetText(strs.price), price, true)
|
.AddField(GetText(strs.market_cap), marketCap, true)
|
||||||
.AddField(GetText(strs.volume_24h), volume, true)
|
.AddField(GetText(strs.price), price, true)
|
||||||
.AddField(GetText(strs.change_7d_24h), $"{sevenDay} / {lastDay}", true)
|
.AddField(GetText(strs.volume_24h), volume, true)
|
||||||
.AddField(GetText(strs.market_cap_dominance), dominance, true)
|
.AddField(GetText(strs.change_7d_24h), $"{sevenDay} / {lastDay}", true)
|
||||||
.WithImageUrl($"attachment://{fileName}");
|
.AddField(GetText(strs.market_cap_dominance), dominance, true)
|
||||||
|
.WithImageUrl($"attachment://{fileName}");
|
||||||
|
|
||||||
if (crypto.CirculatingSupply is double cs)
|
if (crypto.CirculatingSupply is double cs)
|
||||||
{
|
{
|
||||||
|
@ -192,5 +190,54 @@ public partial class Searches
|
||||||
|
|
||||||
await ctx.Channel.SendFileAsync(sparkline, fileName, embed: toSend.Build());
|
await ctx.Channel.SendFileAsync(sparkline, fileName, embed: toSend.Build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task Coins(int page = 1)
|
||||||
|
{
|
||||||
|
if (--page < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (page > 25)
|
||||||
|
page = 25;
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Paginated()
|
||||||
|
.PageItems(async (page) =>
|
||||||
|
{
|
||||||
|
var coins = await _service.GetTopCoins(page);
|
||||||
|
return coins;
|
||||||
|
})
|
||||||
|
.PageSize(10)
|
||||||
|
.Page((items, _) =>
|
||||||
|
{
|
||||||
|
var embed = _sender.CreateEmbed()
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
if (items.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var coin in items)
|
||||||
|
{
|
||||||
|
embed.AddField($"#{coin.MarketCapRank} {coin.Symbol} - {coin.Name}",
|
||||||
|
$"""
|
||||||
|
`Price:` {GetArrowEmoji(coin.PercentChange24h)} {coin.CurrentPrice.ToShortString()}$ ({GetSign(coin.PercentChange24h)}{Math.Round(coin.PercentChange24h, 2)}%)
|
||||||
|
`MarketCap:` {coin.MarketCap.ToShortString()}$
|
||||||
|
`Supply:` {(coin.CirculatingSupply?.ToShortString() ?? "?")} / {(coin.TotalSupply?.ToShortString() ?? "?")}
|
||||||
|
""",
|
||||||
|
inline: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return embed;
|
||||||
|
})
|
||||||
|
.CurrentPage(page)
|
||||||
|
.AddFooter(false)
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetArrowEmoji(decimal value)
|
||||||
|
=> value > 0 ? "▲" : "▼";
|
||||||
|
|
||||||
|
private static string GetSign(decimal value)
|
||||||
|
=> value >= 0 ? "+" : "-";
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,8 +4,10 @@ using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Drawing.Processing;
|
using SixLabors.ImageSharp.Drawing.Processing;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using Color = SixLabors.ImageSharp.Color;
|
using Color = SixLabors.ImageSharp.Color;
|
||||||
using StringExtensions = EllieBot.Extensions.StringExtensions;
|
using StringExtensions = EllieBot.Extensions.StringExtensions;
|
||||||
|
@ -212,4 +214,55 @@ public class CryptoService : IEService
|
||||||
var points = GetSparklinePointsFromSvgText(str);
|
var points = GetSparklinePointsFromSvgText(str);
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static TypedKey<IReadOnlyCollection<GeckoCoinsResult>> GetTopCoinsKey()
|
||||||
|
=> new($"crypto:top_coins");
|
||||||
|
|
||||||
|
public async Task<IReadOnlyCollection<GeckoCoinsResult>?> GetTopCoins(int page)
|
||||||
|
{
|
||||||
|
if (page >= 25)
|
||||||
|
page = 24;
|
||||||
|
|
||||||
|
using var http = _httpFactory.CreateClient();
|
||||||
|
|
||||||
|
http.AddFakeHeaders();
|
||||||
|
|
||||||
|
var result = await _cache.GetOrAddAsync<IReadOnlyCollection<GeckoCoinsResult>>(GetTopCoinsKey(),
|
||||||
|
async () => await http.GetFromJsonAsync<List<GeckoCoinsResult>>(
|
||||||
|
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250")
|
||||||
|
?? [],
|
||||||
|
expiry: TimeSpan.FromHours(1));
|
||||||
|
|
||||||
|
return result!.Skip(page * 10).Take(10).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class GeckoCoinsResult
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public required string Id { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public required string Name { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("symbol")]
|
||||||
|
public required string Symbol { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("current_price")]
|
||||||
|
public required decimal CurrentPrice { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("price_change_percentage_24h")]
|
||||||
|
public required decimal PercentChange24h { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("market_cap")]
|
||||||
|
public required decimal MarketCap { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("circulating_supply")]
|
||||||
|
public required decimal? CirculatingSupply { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("total_supply")]
|
||||||
|
public required decimal? TotalSupply { get; init; }
|
||||||
|
|
||||||
|
[JsonPropertyName("market_cap_rank")]
|
||||||
|
public required int MarketCapRank { get; init; }
|
||||||
}
|
}
|
|
@ -113,16 +113,18 @@ public partial class Xp
|
||||||
{
|
{
|
||||||
var lvl = new LevelStats(club.Xp);
|
var lvl = new LevelStats(club.Xp);
|
||||||
var allUsers = club.Members.OrderByDescending(x =>
|
var allUsers = club.Members.OrderByDescending(x =>
|
||||||
{
|
{
|
||||||
var l = new LevelStats(x.TotalXp).Level;
|
var l = new LevelStats(x.TotalXp).Level;
|
||||||
if (club.OwnerId == x.Id)
|
if (club.OwnerId == x.Id)
|
||||||
return int.MaxValue;
|
return int.MaxValue;
|
||||||
if (x.IsClubAdmin)
|
if (x.IsClubAdmin)
|
||||||
return (int.MaxValue / 2) + l;
|
return (int.MaxValue / 2) + l;
|
||||||
return l;
|
return l;
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
var rank = await _service.GetClubRankAsync(club.Id);
|
||||||
|
|
||||||
await Response()
|
await Response()
|
||||||
.Paginated()
|
.Paginated()
|
||||||
.Items(allUsers)
|
.Items(allUsers)
|
||||||
|
@ -135,6 +137,7 @@ public partial class Xp
|
||||||
.WithDescription(GetText(strs.level_x(lvl.Level + $" ({club.Xp} xp)")))
|
.WithDescription(GetText(strs.level_x(lvl.Level + $" ({club.Xp} xp)")))
|
||||||
.AddField(GetText(strs.desc),
|
.AddField(GetText(strs.desc),
|
||||||
string.IsNullOrWhiteSpace(club.Description) ? "-" : club.Description)
|
string.IsNullOrWhiteSpace(club.Description) ? "-" : club.Description)
|
||||||
|
.AddField(GetText(strs.rank), $"#{rank}", true)
|
||||||
.AddField(GetText(strs.owner), club.Owner.ToString(), true)
|
.AddField(GetText(strs.owner), club.Owner.ToString(), true)
|
||||||
// .AddField(GetText(strs.level_req), club.MinimumLevelReq.ToString(), true)
|
// .AddField(GetText(strs.level_req), club.MinimumLevelReq.ToString(), true)
|
||||||
.AddField(GetText(strs.members),
|
.AddField(GetText(strs.members),
|
||||||
|
|
|
@ -85,7 +85,7 @@ public class ClubService : IEService, IClubService
|
||||||
if (club is null)
|
if (club is null)
|
||||||
return ToggleAdminResult.NotOwner;
|
return ToggleAdminResult.NotOwner;
|
||||||
|
|
||||||
if(!club.Members.Contains(adminUser))
|
if (!club.Members.Contains(adminUser))
|
||||||
return ToggleAdminResult.TargetNotMember;
|
return ToggleAdminResult.TargetNotMember;
|
||||||
|
|
||||||
var newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin;
|
var newState = adminUser.IsClubAdmin = !adminUser.IsClubAdmin;
|
||||||
|
@ -134,6 +134,18 @@ public class ClubService : IEService, IClubService
|
||||||
return club is not null;
|
return club is not null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetClubRankAsync(int clubId)
|
||||||
|
{
|
||||||
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
|
var rank = await uow.Clubs
|
||||||
|
.ToLinqToDBTable()
|
||||||
|
.Where(x => x.Xp > (uow.Clubs.First(c => c.Id == clubId).Xp))
|
||||||
|
.CountAsyncLinqToDB();
|
||||||
|
|
||||||
|
return rank + 1;
|
||||||
|
}
|
||||||
|
|
||||||
public ClubApplyResult ApplyToClub(IUser user, ClubInfo club)
|
public ClubApplyResult ApplyToClub(IUser user, ClubInfo club)
|
||||||
{
|
{
|
||||||
using var uow = _db.GetDbContext();
|
using var uow = _db.GetDbContext();
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace EllieBot.Modules.Xp.Services;
|
||||||
public interface IClubService
|
public interface IClubService
|
||||||
{
|
{
|
||||||
Task<ClubCreateResult> CreateClubAsync(IUser user, string clubName);
|
Task<ClubCreateResult> CreateClubAsync(IUser user, string clubName);
|
||||||
OneOf<ClubInfo,ClubTransferError> TransferClub(IUser from, IUser newOwner);
|
OneOf<ClubInfo, ClubTransferError> TransferClub(IUser from, IUser newOwner);
|
||||||
Task<ToggleAdminResult> ToggleAdminAsync(IUser owner, IUser toAdmin);
|
Task<ToggleAdminResult> ToggleAdminAsync(IUser owner, IUser toAdmin);
|
||||||
ClubInfo? GetClubByMember(IUser user);
|
ClubInfo? GetClubByMember(IUser user);
|
||||||
Task<SetClubIconResult> SetClubIconAsync(ulong ownerUserId, string? url);
|
Task<SetClubIconResult> SetClubIconAsync(ulong ownerUserId, string? url);
|
||||||
|
@ -23,6 +23,7 @@ public interface IClubService
|
||||||
ClubKickResult Kick(ulong kickerId, string userName, out ClubInfo club);
|
ClubKickResult Kick(ulong kickerId, string userName, out ClubInfo club);
|
||||||
List<ClubInfo> GetClubLeaderboardPage(int page);
|
List<ClubInfo> GetClubLeaderboardPage(int page);
|
||||||
Task<ClubRenameResult> RenameClubAsync(ulong userId, string clubName);
|
Task<ClubRenameResult> RenameClubAsync(ulong userId, string clubName);
|
||||||
|
Task<int> GetClubRankAsync(int clubId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ClubApplyResult
|
public enum ClubApplyResult
|
||||||
|
|
|
@ -82,14 +82,14 @@ public static class StringExtensions
|
||||||
// Step 3
|
// Step 3
|
||||||
for (var i = 1; i <= n; i++)
|
for (var i = 1; i <= n; i++)
|
||||||
//Step 4
|
//Step 4
|
||||||
for (var j = 1; j <= m; j++)
|
for (var j = 1; j <= m; j++)
|
||||||
{
|
{
|
||||||
// Step 5
|
// Step 5
|
||||||
var cost = t[j - 1] == s[i - 1] ? 0 : 1;
|
var cost = t[j - 1] == s[i - 1] ? 0 : 1;
|
||||||
|
|
||||||
// Step 6
|
// Step 6
|
||||||
d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost);
|
d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 7
|
// Step 7
|
||||||
return d[n, m];
|
return d[n, m];
|
||||||
|
@ -147,4 +147,5 @@ public static class StringExtensions
|
||||||
var newString = str.UnescapeUnicodeCodePoint();
|
var newString = str.UnescapeUnicodeCodePoint();
|
||||||
return newString;
|
return newString;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,7 +1,30 @@
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace EllieBot.Extensions;
|
namespace EllieBot.Extensions;
|
||||||
|
|
||||||
public static class NumberExtensions
|
public static class NumberExtensions
|
||||||
{
|
{
|
||||||
public static DateTimeOffset ToUnixTimestamp(this double number)
|
public static DateTimeOffset ToUnixTimestamp(this double number)
|
||||||
=> new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero).AddSeconds(number);
|
=> new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero).AddSeconds(number);
|
||||||
|
|
||||||
|
public static string ToShortString(this decimal value)
|
||||||
|
{
|
||||||
|
if (value <= 1_000)
|
||||||
|
return Math.Round(value, 2).ToString(CultureInfo.InvariantCulture);
|
||||||
|
if (value <= 1_000_000)
|
||||||
|
return Math.Round(value, 1).ToString(CultureInfo.InvariantCulture);
|
||||||
|
var tokens = " MBtq";
|
||||||
|
var i = 2;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var num = (decimal)Math.Pow(1000, i);
|
||||||
|
if (num > value)
|
||||||
|
{
|
||||||
|
var num2 = (decimal)Math.Pow(1000, i - 1);
|
||||||
|
return $"{Math.Round((value / num2), 1)}{tokens[i - 1]}".Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -30,6 +30,8 @@ greettest:
|
||||||
- greettest
|
- greettest
|
||||||
greetdmtest:
|
greetdmtest:
|
||||||
- greetdmtest
|
- greetdmtest
|
||||||
|
boosttest:
|
||||||
|
- boosttest
|
||||||
byetest:
|
byetest:
|
||||||
- byetest
|
- byetest
|
||||||
boost:
|
boost:
|
||||||
|
@ -1405,3 +1407,7 @@ prompt:
|
||||||
- prompt
|
- prompt
|
||||||
honeypot:
|
honeypot:
|
||||||
- honeypot
|
- honeypot
|
||||||
|
coins:
|
||||||
|
- coins
|
||||||
|
- crypto
|
||||||
|
- cryptos
|
|
@ -112,6 +112,14 @@ greettest:
|
||||||
params:
|
params:
|
||||||
- user:
|
- user:
|
||||||
desc: "The user to impersonate when sending the greeting, defaulting to yourself if not specified."
|
desc: "The user to impersonate when sending the greeting, defaulting to yourself if not specified."
|
||||||
|
boosttest:
|
||||||
|
desc: Sends the boost message in the current channel as if you just boosted the server. You can optionally specify a different user.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
- '@SomeoneElse'
|
||||||
|
params:
|
||||||
|
- user:
|
||||||
|
desc: "The user to impersonate when sending the boost message, defaulting to yourself if not specified."
|
||||||
greetdmtest:
|
greetdmtest:
|
||||||
desc: Sends the greet direct message to you as if you just joined the server. You can optionally specify a different user.
|
desc: Sends the greet direct message to you as if you just joined the server. You can optionally specify a different user.
|
||||||
ex:
|
ex:
|
||||||
|
@ -2748,8 +2756,10 @@ waifutransfer:
|
||||||
desc: "The user to whom ownership of the waifu is being transferred."
|
desc: "The user to whom ownership of the waifu is being transferred."
|
||||||
waifugift:
|
waifugift:
|
||||||
desc: -|
|
desc: -|
|
||||||
Gift an item to someone.
|
Gift an item to a waifu user.
|
||||||
This will increase their waifu value by a percentage of the gift's value.
|
The waifu's value will be increased by the percentage of the gift's value.
|
||||||
|
You can optionally prefix the gift with a multiplier to gift the item that many times.
|
||||||
|
For example, 3xRose will give the waifu 3 roses, 10xBread will give the waifu 10 breads. Do not use plural forms.
|
||||||
Negative gifts will not show up in waifuinfo.
|
Negative gifts will not show up in waifuinfo.
|
||||||
Provide no parameters to see a list of items that you can gift.
|
Provide no parameters to see a list of items that you can gift.
|
||||||
ex:
|
ex:
|
||||||
|
@ -2757,9 +2767,9 @@ waifugift:
|
||||||
- Rose @Himesama
|
- Rose @Himesama
|
||||||
params:
|
params:
|
||||||
- page:
|
- page:
|
||||||
desc: "The number of pages to display when listing available gifting options."
|
desc: "The number of the page to display."
|
||||||
- itemName:
|
- items:
|
||||||
desc: "The name of an item to be gifted, which is used to determine the percentage increase in waifu value."
|
desc: "The name of an item to be gifted. With an optional multiplier prefix."
|
||||||
waifu:
|
waifu:
|
||||||
desc: "The user who is receiving the gift."
|
desc: "The user who is receiving the gift."
|
||||||
waifulb:
|
waifulb:
|
||||||
|
@ -4256,8 +4266,10 @@ bankbalance:
|
||||||
Shows how much currency is in your bank account.
|
Shows how much currency is in your bank account.
|
||||||
This differs from your cash amount, as the cash amount is publicly available, but only you have access to your bank balance.
|
This differs from your cash amount, as the cash amount is publicly available, but only you have access to your bank balance.
|
||||||
However, you have to withdraw it first in order to use it.
|
However, you have to withdraw it first in order to use it.
|
||||||
|
Bot Owner can also check another user's bank balance.
|
||||||
ex:
|
ex:
|
||||||
- ''
|
- ''
|
||||||
|
- '@User'
|
||||||
params:
|
params:
|
||||||
- {}
|
- {}
|
||||||
banktake:
|
banktake:
|
||||||
|
@ -4546,3 +4558,14 @@ honeypot:
|
||||||
- ''
|
- ''
|
||||||
params:
|
params:
|
||||||
- {}
|
- {}
|
||||||
|
coins:
|
||||||
|
desc: |-
|
||||||
|
Shows a list of 10 crypto currencies ordered by market cap.
|
||||||
|
Shows their price, change in the last24h, market cap and circulating and total supply.
|
||||||
|
Paginated with 10 per page.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
- '2'
|
||||||
|
params:
|
||||||
|
- page:
|
||||||
|
desc: "Page number to show. Starts at 1."
|
||||||
|
|
|
@ -889,6 +889,7 @@
|
||||||
"club_kick_hierarchy": "Only club owner can kick club admins. Owner can't be kicked.",
|
"club_kick_hierarchy": "Only club owner can kick club admins. Owner can't be kicked.",
|
||||||
"club_renamed": "Club has been renamed to {0}",
|
"club_renamed": "Club has been renamed to {0}",
|
||||||
"club_name_taken": "A club with that name already exists.",
|
"club_name_taken": "A club with that name already exists.",
|
||||||
|
"rank": "Rank",
|
||||||
"template_reloaded": "Xp template has been reloaded.",
|
"template_reloaded": "Xp template has been reloaded.",
|
||||||
"expr_edited": "Expression Edited",
|
"expr_edited": "Expression Edited",
|
||||||
"self_assign_are_exclusive": "You can only choose 1 role from each group.",
|
"self_assign_are_exclusive": "You can only choose 1 role from each group.",
|
||||||
|
@ -1039,6 +1040,7 @@
|
||||||
"marmalade_already_loaded": "Marmalade {0} is already loaded",
|
"marmalade_already_loaded": "Marmalade {0} is already loaded",
|
||||||
"marmalade_invalid_not_found": "Marmalade with that name wasn't found or the file was invalid",
|
"marmalade_invalid_not_found": "Marmalade with that name wasn't found or the file was invalid",
|
||||||
"bank_balance": "You have {0} in your bank account.",
|
"bank_balance": "You have {0} in your bank account.",
|
||||||
|
"bank_balance_other": "User {0} has {1} in the bank.",
|
||||||
"bank_deposited": "You deposited {0} to your bank account.",
|
"bank_deposited": "You deposited {0} to your bank account.",
|
||||||
"bank_withdrew": "You withdrew {0} from your bank account.",
|
"bank_withdrew": "You withdrew {0} from your bank account.",
|
||||||
"bank_withdraw_insuff": "You don't have sufficient {0} in your bank account.",
|
"bank_withdraw_insuff": "You don't have sufficient {0} in your bank account.",
|
||||||
|
|
Loading…
Reference in a new issue