Compare commits
No commits in common. "f318ec274181b43fecda120c7825914ccf623ef9" and "ca64765c34c367785de8ef0b45b6b7460516660e" have entirely different histories.
f318ec2741
...
ca64765c34
11 changed files with 95 additions and 299 deletions
46
build.ps1
46
build.ps1
|
@ -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"
|
|
18
build.sh
18
build.sh
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ? "+" : "-";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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; }
|
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
|
@ -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++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue