mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
add: Added .coins command which lists top 10 cryptos ordered by marketcap, paginated with 10 per page
This commit is contained in:
@@ -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,15 +105,12 @@ 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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
public async Task Crypto(string name)
|
public async Task Crypto(string name)
|
||||||
@@ -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;
|
||||||
@@ -146,7 +143,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
|
||||||
@@ -159,28 +156,29 @@ 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($"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)
|
||||||
{
|
{
|
||||||
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
|
||||||
@@ -192,5 +190,51 @@ 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;
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Paginated()
|
||||||
|
.PageItems(async (page) =>
|
||||||
|
{
|
||||||
|
var coins = await _service.GetTopCoins(page + 1);
|
||||||
|
|
||||||
|
return coins;
|
||||||
|
})
|
||||||
|
.PageSize(1)
|
||||||
|
.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;
|
||||||
|
})
|
||||||
|
.AddFooter(false)
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetArrowEmoji(decimal value)
|
||||||
|
=> value > 0 ? "▲" : "▼";
|
||||||
|
|
||||||
|
private static string GetSign(decimal value)
|
||||||
|
=> value >= 0 ? "+" : "-";
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -6,6 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
|
|||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
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 = NadekoBot.Extensions.StringExtensions;
|
using StringExtensions = NadekoBot.Extensions.StringExtensions;
|
||||||
@@ -212,4 +213,46 @@ public class CryptoService : INService
|
|||||||
var points = GetSparklinePointsFromSvgText(str);
|
var points = GetSparklinePointsFromSvgText(str);
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyCollection<GeckoCoinsResult>?> GetTopCoins(int page)
|
||||||
|
{
|
||||||
|
using var http = _httpFactory.CreateClient();
|
||||||
|
|
||||||
|
http.AddFakeHeaders();
|
||||||
|
|
||||||
|
var result = await http.GetFromJsonAsync<List<GeckoCoinsResult>>(
|
||||||
|
$"https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&page={page}&per_page=10");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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; }
|
||||||
}
|
}
|
@@ -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 NadekoBot.Extensions;
|
namespace NadekoBot.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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1404,4 +1404,8 @@ cleanupguilddata:
|
|||||||
prompt:
|
prompt:
|
||||||
- prompt
|
- prompt
|
||||||
honeypot:
|
honeypot:
|
||||||
- honeypot
|
- honeypot
|
||||||
|
coins:
|
||||||
|
- coins
|
||||||
|
- crypto
|
||||||
|
- cryptos
|
@@ -4545,4 +4545,15 @@ honeypot:
|
|||||||
ex:
|
ex:
|
||||||
- ''
|
- ''
|
||||||
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."
|
Reference in New Issue
Block a user