Compare commits

..

No commits in common. "f318ec274181b43fecda120c7825914ccf623ef9" and "ca64765c34c367785de8ef0b45b6b7460516660e" have entirely different histories.

11 changed files with 95 additions and 299 deletions

View file

@ -1,46 +0,0 @@
echo ""
echo "███████╗██╗ ██╗ ██╗███████╗██████╗ ██████╗ ████████╗"
echo "██╔════╝██║ ██║ ██║██╔════╝██╔══██╗██╔═══██╗╚══██╔══╝"
echo "█████╗ ██║ ██║ ██║█████╗ ██████╔╝██║ ██║ ██║ "
echo "██╔══╝ ██║ ██║ ██║██╔══╝ ██╔══██╗██║ ██║ ██║ "
echo "███████╗███████╗███████╗██║███████╗██████╔╝╚██████╔╝ ██║ "
echo "╚══════╝╚══════╝╚══════╝╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═╝ "
echo ""
echo "Copyright © 2024 Toastie_t0ast & EllieBotDevs"
echo ""
echo ""
echo "Publishing EllieBot"
echo ""
echo ""
dotnet publish -c Release -r linux-x64 --self-contained -o elliebot-linux-x64 src/EllieBot/EllieBot.csproj
echo ""
dotnet publish -c Release -r linux-arm64 --self-contained -o elliebot-linux-arm64 src/EllieBot/EllieBot.csproj
echo ""
dotnet publish -c Release -r win-x64 --self-contained -o elliebot-windows-x64 src/EllieBot/EllieBot.csproj
echo ""
dotnet publish -c Release -r win-arm64 --self-contained -o elliebot-windows-arm64 src/EllieBot/EllieBot.csproj
echo ""
dotnet publish -c Release -r osx-x64 --self-contained -o elliebot-osx-x64 src/EllieBot/EllieBot.csproj
echo ""
dotnet publish -c Release -r osx-arm64 --self-contained -o elliebot-osx-arm64 src/EllieBot/EllieBot.csproj
echo ""
echo "Preparing the Windows installer build."
echo ""
dotnet clean
dotnet restore -f --no-cache -v n
dotnet publish -c Release --self-contained --runtime win-x64 /p:Version=5.1.3 src/EllieBot
echo ""
echo ""
echo "Finished the initial build script"
echo ""
echo "To build the Windows installer please install Inno Setup from"
echo "https://jrsoftware.org/isdl.php"
echo "And compile the exe_builder.iss"
echo ""
echo "If you are running on Windows please run the wsl command"
echo "then run build.sh"

View file

@ -1,18 +0,0 @@
echo ""
echo "Compressing build files"
echo ""
tar cvf 5.1.3-linux-x64-build.tar elliebot-linux-x64/*
tar cvf 5.1.3-linux-arm64-build.tar elliebot-linux-arm64/*
tar cvf 5.1.3-osx-x64-build.tar elliebot-osx-x64/*
tar cvf 5.1.3-osx-arm64-build.tar elliebot-osx-arm64/*
zip -r 5.1.3-windows-x64-build.zip elliebot-windows-x64/*
zip -r 5.1.3-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.3/ellie-setup-5.1.3.exe ellie-setup-5.1.3.exe

View file

@ -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

View file

@ -16,12 +16,12 @@ public partial class Searches
_stocksService = stocksService; _stocksService = stocksService;
_stockDrawingService = stockDrawingService; _stockDrawingService = stockDrawingService;
} }
[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();
var stock = await _stocksService.GetStockDataAsync(query); var stock = await _stocksService.GetStockDataAsync(query);
if (stock is null) if (stock is null)
@ -36,9 +36,9 @@ 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;
@ -54,7 +54,7 @@ public partial class Searches
var candles = await _stocksService.GetCandleDataAsync(query); var candles = await _stocksService.GetCandleDataAsync(query);
var stockImageTask = _stockDrawingService.GenerateCombinedChartAsync(candles); var stockImageTask = _stockDrawingService.GenerateCombinedChartAsync(candles);
var localCulture = (CultureInfo)Culture.Clone(); var localCulture = (CultureInfo)Culture.Clone();
localCulture.NumberFormat.CurrencySymbol = "$"; localCulture.NumberFormat.CurrencySymbol = "$";
@ -64,34 +64,34 @@ public partial class Searches
var change = (stock.Price - stock.Close).ToString("N2", Culture); var change = (stock.Price - stock.Close).ToString("N2", Culture);
var changePercent = (1 - (stock.Close / stock.Price)).ToString("P1", Culture); var changePercent = (1 - (stock.Close / stock.Price)).ToString("P1", Culture);
var sign50 = stock.Change50d >= 0 var sign50 = stock.Change50d >= 0
? "\\🔼" ? "\\🔼"
: "\\🔻"; : "\\🔻";
var change50 = (stock.Change50d).ToString("P1", Culture); var change50 = (stock.Change50d).ToString("P1", Culture);
var sign200 = stock.Change200d >= 0 var sign200 = stock.Change200d >= 0
? "\\🔼" ? "\\🔼"
: "\\🔻"; : "\\🔻";
var change200 = (stock.Change200d).ToString("P1", Culture); var change200 = (stock.Change200d).ToString("P1", Culture);
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;
if (imageData is null) if (imageData is null)
@ -105,12 +105,15 @@ public partial class Searches
await message.ModifyAsync(mp => await message.ModifyAsync(mp =>
{ {
mp.Attachments = mp.Attachments =
new(new[] { attachment }); new(new[]
{
attachment
});
mp.Embed = eb.WithImageUrl($"attachment://{fileName}").Build(); mp.Embed = eb.WithImageUrl($"attachment://{fileName}").Build();
}); });
} }
[Cmd] [Cmd]
public async Task Crypto(string name) public async Task Crypto(string name)
@ -125,9 +128,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;
@ -143,7 +146,7 @@ public partial class Searches
var localCulture = (CultureInfo)Culture.Clone(); var localCulture = (CultureInfo)Culture.Clone();
localCulture.NumberFormat.CurrencySymbol = "$"; localCulture.NumberFormat.CurrencySymbol = "$";
var sevenDay = (usd.PercentChange7d / 100).ToString("P2", localCulture); var sevenDay = (usd.PercentChange7d / 100).ToString("P2", localCulture);
var lastDay = (usd.PercentChange24h / 100).ToString("P2", localCulture); var lastDay = (usd.PercentChange24h / 100).ToString("P2", localCulture);
var price = usd.Price < 0.01 var price = usd.Price < 0.01
@ -156,29 +159,28 @@ public partial class Searches
await using var sparkline = await _service.GetSparklineAsync(crypto.Id, usd.PercentChange7d >= 0); await using var sparkline = await _service.GetSparklineAsync(crypto.Id, usd.PercentChange7d >= 0);
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( .WithThumbnailUrl($"https://s3.coinmarketcap.com/static/img/coins/128x128/{crypto.Id}.png")
$"https://s3.coinmarketcap.com/static/img/coins/128x128/{crypto.Id}.png") .AddField(GetText(strs.market_cap), marketCap, true)
.AddField(GetText(strs.market_cap), marketCap, true) .AddField(GetText(strs.price), price, true)
.AddField(GetText(strs.price), price, true) .AddField(GetText(strs.volume_24h), volume, true)
.AddField(GetText(strs.volume_24h), volume, true) .AddField(GetText(strs.change_7d_24h), $"{sevenDay} / {lastDay}", true)
.AddField(GetText(strs.change_7d_24h), $"{sevenDay} / {lastDay}", true) .AddField(GetText(strs.market_cap_dominance), dominance, true)
.AddField(GetText(strs.market_cap_dominance), dominance, true) .WithImageUrl($"attachment://{fileName}");
.WithImageUrl($"attachment://{fileName}");
if (crypto.CirculatingSupply is double cs) if (crypto.CirculatingSupply is double cs)
{ {
var csStr = cs.ToString("N0", localCulture); var csStr = cs.ToString("N0", localCulture);
if (crypto.MaxSupply is double ms) if (crypto.MaxSupply is double ms)
{ {
var perc = (cs / ms).ToString("P1", localCulture); var perc = (cs / ms).ToString("P1", localCulture);
toSend.AddField(GetText(strs.circulating_supply), $"{csStr} ({perc})", true); toSend.AddField(GetText(strs.circulating_supply), $"{csStr} ({perc})", true);
} }
else else
@ -190,54 +192,5 @@ 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 ? "+" : "-";
} }
} }

View file

@ -4,10 +4,8 @@ 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;
@ -214,55 +212,4 @@ 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; }
} }

View file

@ -245,7 +245,7 @@ public sealed class AiAssistantService
{ {
if (guild is not SocketGuild sg) if (guild is not SocketGuild sg)
return false; return false;
var sess = _cbs.GetOrCreateSession(guild.Id); var sess = _cbs.GetOrCreateSession(guild.Id);
if (sess is null) if (sess is null)
return false; return false;
@ -302,7 +302,7 @@ public sealed class AiAssistantService
await _sender.Response(channel) await _sender.Response(channel)
.Error(errorMsg) .Error(errorMsg)
.SendAsync(); .SendAsync();
return true; return true;
} }

View file

@ -113,18 +113,16 @@ 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)
@ -137,7 +135,6 @@ 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),

View file

@ -23,22 +23,22 @@ public class ClubService : IEService, IClubService
{ {
if (!CheckClubName(clubName)) if (!CheckClubName(clubName))
return ClubCreateResult.NameTooLong; return ClubCreateResult.NameTooLong;
//must be lvl 5 and must not be in a club already //must be lvl 5 and must not be in a club already
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var du = uow.GetOrCreateUser(user); var du = uow.GetOrCreateUser(user);
var xp = new LevelStats(du.TotalXp); var xp = new LevelStats(du.TotalXp);
if (xp.Level < 5) if (xp.Level < 5)
return ClubCreateResult.InsufficientLevel; return ClubCreateResult.InsufficientLevel;
if (du.ClubId is not null) if (du.ClubId is not null)
return ClubCreateResult.AlreadyInAClub; return ClubCreateResult.AlreadyInAClub;
if (await uow.Set<ClubInfo>().AnyAsyncEF(x => x.Name == clubName)) if (await uow.Set<ClubInfo>().AnyAsyncEF(x => x.Name == clubName))
return ClubCreateResult.NameTaken; return ClubCreateResult.NameTaken;
du.IsClubAdmin = true; du.IsClubAdmin = true;
du.Club = new() du.Club = new()
{ {
@ -53,7 +53,7 @@ public class ClubService : IEService, IClubService
return ClubCreateResult.Success; return ClubCreateResult.Success;
} }
public OneOf<ClubInfo, ClubTransferError> TransferClub(IUser from, IUser newOwner) public OneOf<ClubInfo, ClubTransferError> TransferClub(IUser from, IUser newOwner)
{ {
using var uow = _db.GetDbContext(); using var uow = _db.GetDbContext();
@ -62,7 +62,7 @@ public class ClubService : IEService, IClubService
if (club is null || club.Owner.UserId != from.Id) if (club is null || club.Owner.UserId != from.Id)
return ClubTransferError.NotOwner; return ClubTransferError.NotOwner;
if (!club.Members.Contains(newOwnerUser)) if (!club.Members.Contains(newOwnerUser))
return ClubTransferError.TargetNotMember; return ClubTransferError.TargetNotMember;
@ -72,22 +72,22 @@ public class ClubService : IEService, IClubService
uow.SaveChanges(); uow.SaveChanges();
return club; return club;
} }
public async Task<ToggleAdminResult> ToggleAdminAsync(IUser owner, IUser toAdmin) public async Task<ToggleAdminResult> ToggleAdminAsync(IUser owner, IUser toAdmin)
{ {
if (owner.Id == toAdmin.Id) if (owner.Id == toAdmin.Id)
return ToggleAdminResult.CantTargetThyself; return ToggleAdminResult.CantTargetThyself;
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var club = uow.Set<ClubInfo>().GetByOwner(owner.Id); var club = uow.Set<ClubInfo>().GetByOwner(owner.Id);
var adminUser = uow.GetOrCreateUser(toAdmin); var adminUser = uow.GetOrCreateUser(toAdmin);
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;
await uow.SaveChangesAsync(); await uow.SaveChangesAsync();
return newState ? ToggleAdminResult.AddedAdmin : ToggleAdminResult.RemovedAdmin; return newState ? ToggleAdminResult.AddedAdmin : ToggleAdminResult.RemovedAdmin;
@ -99,17 +99,17 @@ public class ClubService : IEService, IClubService
var member = uow.Set<ClubInfo>().GetByMember(user.Id); var member = uow.Set<ClubInfo>().GetByMember(user.Id);
return member; return member;
} }
public async Task<SetClubIconResult> SetClubIconAsync(ulong ownerUserId, string url) public async Task<SetClubIconResult> SetClubIconAsync(ulong ownerUserId, string url)
{ {
if (url is not null) if (url is not null)
{ {
using var http = _httpFactory.CreateClient(); using var http = _httpFactory.CreateClient();
using var temp = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); using var temp = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
if (!temp.IsImage()) if (!temp.IsImage())
return SetClubIconResult.InvalidFileType; return SetClubIconResult.InvalidFileType;
if (temp.GetContentLength() > 5.Megabytes()) if (temp.GetContentLength() > 5.Megabytes())
return SetClubIconResult.TooLarge; return SetClubIconResult.TooLarge;
} }
@ -134,18 +134,6 @@ 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();
@ -156,10 +144,10 @@ public class ClubService : IEService, IClubService
// or doesn't min minumum level requirement, can't apply // or doesn't min minumum level requirement, can't apply
if (du.ClubId is not null) if (du.ClubId is not null)
return ClubApplyResult.AlreadyInAClub; return ClubApplyResult.AlreadyInAClub;
if (club.Bans.Any(x => x.UserId == du.Id)) if (club.Bans.Any(x => x.UserId == du.Id))
return ClubApplyResult.Banned; return ClubApplyResult.Banned;
if (club.Applicants.Any(x => x.UserId == du.Id)) if (club.Applicants.Any(x => x.UserId == du.Id))
return ClubApplyResult.AlreadyApplied; return ClubApplyResult.AlreadyApplied;
@ -174,7 +162,7 @@ public class ClubService : IEService, IClubService
return ClubApplyResult.Success; return ClubApplyResult.Success;
} }
public ClubAcceptResult AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser) public ClubAcceptResult AcceptApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser)
{ {
discordUser = null; discordUser = null;
@ -200,7 +188,7 @@ public class ClubService : IEService, IClubService
uow.SaveChanges(); uow.SaveChanges();
return ClubAcceptResult.Accepted; return ClubAcceptResult.Accepted;
} }
public ClubDenyResult RejectApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser) public ClubDenyResult RejectApplication(ulong clubOwnerUserId, string userName, out DiscordUser discordUser)
{ {
discordUser = null; discordUser = null;
@ -213,9 +201,9 @@ public class ClubService : IEService, IClubService
club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant()); club.Applicants.FirstOrDefault(x => x.User.ToString().ToUpperInvariant() == userName.ToUpperInvariant());
if (applicant is null) if (applicant is null)
return ClubDenyResult.NoSuchApplicant; return ClubDenyResult.NoSuchApplicant;
club.Applicants.Remove(applicant); club.Applicants.Remove(applicant);
discordUser = applicant.User; discordUser = applicant.User;
uow.SaveChanges(); uow.SaveChanges();
return ClubDenyResult.Rejected; return ClubDenyResult.Rejected;
@ -232,7 +220,7 @@ public class ClubService : IEService, IClubService
using var uow = _db.GetDbContext(); using var uow = _db.GetDbContext();
var du = uow.GetOrCreateUser(user, x => x.Include(u => u.Club)); var du = uow.GetOrCreateUser(user, x => x.Include(u => u.Club));
if (du.Club is null) if (du.Club is null)
return ClubLeaveResult.NotInAClub; return ClubLeaveResult.NotInAClub;
if (du.Club.OwnerId == du.Id) if (du.Club.OwnerId == du.Id)
return ClubLeaveResult.OwnerCantLeave; return ClubLeaveResult.OwnerCantLeave;
@ -318,7 +306,7 @@ public class ClubService : IEService, IClubService
return ClubUnbanResult.Success; return ClubUnbanResult.Success;
} }
public ClubKickResult Kick(ulong kickerId, string userName, out ClubInfo club) public ClubKickResult Kick(ulong kickerId, string userName, out ClubInfo club)
{ {
using var uow = _db.GetDbContext(); using var uow = _db.GetDbContext();
@ -354,14 +342,14 @@ public class ClubService : IEService, IClubService
{ {
if (!CheckClubName(clubName)) if (!CheckClubName(clubName))
return ClubRenameResult.NameTooLong; return ClubRenameResult.NameTooLong;
await using var uow = _db.GetDbContext(); await using var uow = _db.GetDbContext();
var club = uow.Set<ClubInfo>().GetByOwnerOrAdmin(userId); var club = uow.Set<ClubInfo>().GetByOwnerOrAdmin(userId);
if (club is null) if (club is null)
return ClubRenameResult.NotOwnerOrAdmin; return ClubRenameResult.NotOwnerOrAdmin;
if (await uow.Set<ClubInfo>().AnyAsyncEF(x => x.Name == clubName)) if (await uow.Set<ClubInfo>().AnyAsyncEF(x => x.Name == clubName))
return ClubRenameResult.NameTaken; return ClubRenameResult.NameTaken;

View file

@ -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,7 +23,6 @@ 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

View file

@ -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,5 +147,4 @@ public static class StringExtensions
var newString = str.UnescapeUnicodeCodePoint(); var newString = str.UnescapeUnicodeCodePoint();
return newString; return newString;
}); });
} }

View file

@ -1,30 +1,7 @@
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++;
}
}
} }