mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 00:34:26 -05:00 
			
		
		
		
	Added a .stock command
This commit is contained in:
		@@ -81,8 +81,8 @@ csharp_style_expression_bodied_constructors = when_on_single_line:suggestion
 | 
			
		||||
csharp_style_expression_bodied_indexers = true:suggestion
 | 
			
		||||
csharp_style_expression_bodied_lambdas = true:suggestion
 | 
			
		||||
csharp_style_expression_bodied_local_functions = true:suggestion
 | 
			
		||||
csharp_style_expression_bodied_methods = when_on_single_line:warning
 | 
			
		||||
csharp_style_expression_bodied_operators = when_on_single_line:warning
 | 
			
		||||
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
 | 
			
		||||
csharp_style_expression_bodied_operators = when_on_single_line:suggestion
 | 
			
		||||
csharp_style_expression_bodied_properties = true:suggestion
 | 
			
		||||
 | 
			
		||||
# Pattern matching preferences
 | 
			
		||||
 
 | 
			
		||||
@@ -64,6 +64,10 @@ Used only for .time command")]
 | 
			
		||||
Used for cryptocurrency related commands.")]
 | 
			
		||||
    public string CoinmarketcapApiKey { get; set; }
 | 
			
		||||
    
 | 
			
		||||
//     [Comment(@"https://polygon.io/dashboard/api-keys api key. Free plan allows for 5 queries per minute.
 | 
			
		||||
// Used for stocks related commands.")]
 | 
			
		||||
//     public string PolygonIoApiKey { get; set; }
 | 
			
		||||
 | 
			
		||||
    [Comment(@"Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api")]
 | 
			
		||||
    public string OsuApiKey { get; set; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,110 @@ public partial class Searches
 | 
			
		||||
{
 | 
			
		||||
    public partial class CryptoCommands : NadekoSubmodule<CryptoService>
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IStockDataService _stocksService;
 | 
			
		||||
 | 
			
		||||
        public CryptoCommands(IStockDataService stocksService)
 | 
			
		||||
        {
 | 
			
		||||
            _stocksService = stocksService;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        public async partial Task Stock([Leftover]string query)
 | 
			
		||||
        {
 | 
			
		||||
            if (!query.IsAlphaNumeric())
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            using var typing = ctx.Channel.EnterTypingState();
 | 
			
		||||
            
 | 
			
		||||
            var stocks = await _stocksService.GetStockDataAsync(query);
 | 
			
		||||
 | 
			
		||||
            if (stocks.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                var symbols = await _stocksService.SearchSymbolAsync(query);
 | 
			
		||||
 | 
			
		||||
                if (symbols.Count == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalizedAsync(strs.not_found);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var symbol = symbols.First();
 | 
			
		||||
                var promptEmbed = _eb.Create()
 | 
			
		||||
                                     .WithDescription(symbol.Description)
 | 
			
		||||
                                     .WithTitle(GetText(strs.did_you_mean(symbol.Symbol)));
 | 
			
		||||
                
 | 
			
		||||
                if (!await PromptUserConfirmAsync(promptEmbed))
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                query = symbol.Symbol;
 | 
			
		||||
                stocks = await _stocksService.GetStockDataAsync(query);
 | 
			
		||||
 | 
			
		||||
                if (stocks.Count == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    await ReplyErrorLocalizedAsync(strs.not_found);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // try to find a ticker match
 | 
			
		||||
            var stock = stocks.Count == 1
 | 
			
		||||
                ? stocks.FirstOrDefault()
 | 
			
		||||
                : stocks.FirstOrDefault(x => x.Ticker == query.ToUpperInvariant());
 | 
			
		||||
            
 | 
			
		||||
            if (stock is null)
 | 
			
		||||
            {
 | 
			
		||||
                var ebImprecise = _eb.Create()
 | 
			
		||||
                                     .WithOkColor()
 | 
			
		||||
                                     .WithTitle(GetText(strs.stocks_multiple_results))
 | 
			
		||||
                                     .WithDescription(stocks.Take(20)
 | 
			
		||||
                                                            .Select(s => $"{Format.Code(s.Ticker)} {s.Name.TrimTo(50)}")
 | 
			
		||||
                                                            .Join('\n'));
 | 
			
		||||
 | 
			
		||||
                await ctx.Channel.EmbedAsync(ebImprecise);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var localCulture = (CultureInfo)Culture.Clone();
 | 
			
		||||
            localCulture.NumberFormat.CurrencySymbol = "$";
 | 
			
		||||
 | 
			
		||||
            var sign = stock.Price >= stock.Close
 | 
			
		||||
                ? "\\🔼"
 | 
			
		||||
                : "\\🔻";
 | 
			
		||||
 | 
			
		||||
            var change = (stock.Price - stock.Close).ToString("N2", Culture);
 | 
			
		||||
            var changePercent = (1 - (stock.Close / stock.Price)).ToString("P1", Culture);
 | 
			
		||||
            
 | 
			
		||||
            var sign50 = stock.Change50d >= 0
 | 
			
		||||
                ? "\\🔼"
 | 
			
		||||
                : "\\🔻";
 | 
			
		||||
 | 
			
		||||
            var change50 = (stock.Change50d / 100).ToString("P1", Culture);
 | 
			
		||||
            
 | 
			
		||||
            var sign200 = stock.Change200d >= 0
 | 
			
		||||
                ? "\\🔼"
 | 
			
		||||
                : "\\🔻";
 | 
			
		||||
            
 | 
			
		||||
            var change200 = (stock.Change200d / 100).ToString("P1", Culture);
 | 
			
		||||
            
 | 
			
		||||
            var price = stock.Price.ToString("C2", localCulture);
 | 
			
		||||
 | 
			
		||||
            var eb = _eb.Create()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithAuthor(stock.Ticker)
 | 
			
		||||
                        .WithTitle(stock.Name)
 | 
			
		||||
                        .AddField(GetText(strs.price), $"{sign} **{price}**", true)
 | 
			
		||||
                        .AddField(GetText(strs.market_cap), stock.MarketCap.ToString("C0", localCulture), true)
 | 
			
		||||
                        .AddField(GetText(strs.volume_24h), stock.DailyVolume.ToString("C0", localCulture), true)
 | 
			
		||||
                        .AddField("Change", $"{change} ({changePercent})", true)
 | 
			
		||||
                        .AddField("Change 50d", $"{sign50}{change50}", true)
 | 
			
		||||
                        .AddField("Change 200d", $"{sign200}{change200}", true)
 | 
			
		||||
                        .WithFooter(stock.Exchange);
 | 
			
		||||
            
 | 
			
		||||
            await ctx.Channel.EmbedAsync(eb);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        public async partial Task Crypto(string name)
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,7 @@
 | 
			
		||||
namespace NadekoBot.Modules.Searches;
 | 
			
		||||
 | 
			
		||||
public interface IStockDataService
 | 
			
		||||
{
 | 
			
		||||
    public Task<IReadOnlyCollection<StockData>> GetStockDataAsync(string query);
 | 
			
		||||
    Task<IReadOnlyCollection<SymbolData>> SearchSymbolAsync(string query);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Searches;
 | 
			
		||||
 | 
			
		||||
public class FinnHubSearchResponse
 | 
			
		||||
{
 | 
			
		||||
    [JsonPropertyName("count")]
 | 
			
		||||
    public int Count { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("result")]
 | 
			
		||||
    public List<FinnHubSearchResult> Result { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,19 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Searches;
 | 
			
		||||
 | 
			
		||||
public class FinnHubSearchResult
 | 
			
		||||
{
 | 
			
		||||
    [JsonPropertyName("description")]
 | 
			
		||||
    public string Description { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("displaySymbol")]
 | 
			
		||||
    public string DisplaySymbol { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("symbol")]
 | 
			
		||||
    public string Symbol { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("type")]
 | 
			
		||||
    public string Type { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
using System.Net.Http.Json;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Searches;
 | 
			
		||||
 | 
			
		||||
public sealed class PolygonApiClient : IDisposable
 | 
			
		||||
{
 | 
			
		||||
    private const string BASE_URL = "https://api.polygon.io/v3";
 | 
			
		||||
    
 | 
			
		||||
    private readonly HttpClient _httpClient;
 | 
			
		||||
    private readonly string _apiKey;
 | 
			
		||||
 | 
			
		||||
    public PolygonApiClient(HttpClient httpClient, string apiKey)
 | 
			
		||||
    {
 | 
			
		||||
        _httpClient = httpClient;
 | 
			
		||||
        _apiKey = apiKey;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public async Task<IReadOnlyCollection<PolygonTickerData>> TickersAsync(string? ticker = null, string? query = null)
 | 
			
		||||
    {
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(query))
 | 
			
		||||
            query = null; 
 | 
			
		||||
        
 | 
			
		||||
        if(query is not null)
 | 
			
		||||
            query = Uri.EscapeDataString(query);
 | 
			
		||||
        
 | 
			
		||||
        var requestString = $"{BASE_URL}/reference/tickers"
 | 
			
		||||
                            + "?type=CS"
 | 
			
		||||
                            + "&active=true"
 | 
			
		||||
                            + "&order=asc"
 | 
			
		||||
                            + "&limit=1000"
 | 
			
		||||
                            + $"&apiKey={_apiKey}";
 | 
			
		||||
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(ticker))
 | 
			
		||||
            requestString += $"&ticker={ticker}";
 | 
			
		||||
        
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(query))
 | 
			
		||||
            requestString += $"&search={query}";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var response = await _httpClient.GetFromJsonAsync<PolygonTickerResponse>(requestString);
 | 
			
		||||
 | 
			
		||||
        if (response is null)
 | 
			
		||||
            return Array.Empty<PolygonTickerData>();
 | 
			
		||||
 | 
			
		||||
        return response.Results;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // public async Task<PolygonTickerDetailsV3> TickerDetailsV3Async(string ticker)
 | 
			
		||||
    // {
 | 
			
		||||
    //     return new();
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
        => _httpClient.Dispose();
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Searches;
 | 
			
		||||
 | 
			
		||||
public class PolygonTickerData
 | 
			
		||||
{
 | 
			
		||||
    [JsonPropertyName("ticker")]
 | 
			
		||||
    public string Ticker { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("name")]
 | 
			
		||||
    public string Name { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("market")]
 | 
			
		||||
    public string Market { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("locale")]
 | 
			
		||||
    public string Locale { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("primary_exchange")]
 | 
			
		||||
    public string PrimaryExchange { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("type")]
 | 
			
		||||
    public string Type { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("active")]
 | 
			
		||||
    public bool Active { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("currency_name")]
 | 
			
		||||
    public string CurrencyName { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("cik")]
 | 
			
		||||
    public string Cik { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("composite_figi")]
 | 
			
		||||
    public string CompositeFigi { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("share_class_figi")]
 | 
			
		||||
    public string ShareClassFigi { get; set; }
 | 
			
		||||
 | 
			
		||||
    [JsonPropertyName("last_updated_utc")]
 | 
			
		||||
    public DateTime LastUpdatedUtc { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Searches;
 | 
			
		||||
 | 
			
		||||
public class PolygonTickerResponse
 | 
			
		||||
{
 | 
			
		||||
    [JsonPropertyName("status")]
 | 
			
		||||
    public string Status { get; set; }
 | 
			
		||||
    
 | 
			
		||||
    [JsonPropertyName("results")]
 | 
			
		||||
    public List<PolygonTickerData> Results { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,26 @@
 | 
			
		||||
// namespace NadekoBot.Modules.Searches;
 | 
			
		||||
//
 | 
			
		||||
// public sealed class PolygonStockDataService : IStockDataService
 | 
			
		||||
// {
 | 
			
		||||
//     private readonly IHttpClientFactory _httpClientFactory;
 | 
			
		||||
//     private readonly IBotCredsProvider _credsProvider;
 | 
			
		||||
//
 | 
			
		||||
//     public PolygonStockDataService(IHttpClientFactory httpClientFactory, IBotCredsProvider credsProvider)
 | 
			
		||||
//     {
 | 
			
		||||
//         _httpClientFactory = httpClientFactory;
 | 
			
		||||
//         _credsProvider = credsProvider;
 | 
			
		||||
//     }
 | 
			
		||||
//
 | 
			
		||||
//     public async Task<IReadOnlyCollection<StockData>> GetStockDataAsync(string? query = null)
 | 
			
		||||
//     {
 | 
			
		||||
//         using var httpClient = _httpClientFactory.CreateClient();
 | 
			
		||||
//         using var client = new PolygonApiClient(httpClient, string.Empty);
 | 
			
		||||
//         var data = await client.TickersAsync(query: query);
 | 
			
		||||
//
 | 
			
		||||
//         return data.Map(static x => new StockData()
 | 
			
		||||
//         {
 | 
			
		||||
//             Name = x.Name,
 | 
			
		||||
//             Ticker = x.Ticker,
 | 
			
		||||
//         });
 | 
			
		||||
//     }
 | 
			
		||||
// }
 | 
			
		||||
							
								
								
									
										15
									
								
								src/NadekoBot/Modules/Searches/Crypto/_Common/StockData.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/NadekoBot/Modules/Searches/Crypto/_Common/StockData.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
namespace NadekoBot.Modules.Searches;
 | 
			
		||||
 | 
			
		||||
public class StockData
 | 
			
		||||
{
 | 
			
		||||
    public string Name { get; set; }
 | 
			
		||||
    public string Ticker { get; set; }
 | 
			
		||||
    public double Price { get; set; }
 | 
			
		||||
    public long MarketCap { get; set; }
 | 
			
		||||
    public double Close { get; set; }
 | 
			
		||||
    public double Change50d { get; set; }
 | 
			
		||||
    public double Change200d { get; set; }
 | 
			
		||||
    public long DailyVolume { get; set; }
 | 
			
		||||
    public string Exchange { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,83 @@
 | 
			
		||||
using System.Net.Http.Json;
 | 
			
		||||
using YahooFinanceApi;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Searches;
 | 
			
		||||
 | 
			
		||||
public sealed class DefaultStockDataService : IStockDataService, INService
 | 
			
		||||
{
 | 
			
		||||
    private readonly IHttpClientFactory _httpClientFactory;
 | 
			
		||||
 | 
			
		||||
    public DefaultStockDataService(IHttpClientFactory httpClientFactory)
 | 
			
		||||
    {
 | 
			
		||||
        _httpClientFactory = httpClientFactory;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public async Task<IReadOnlyCollection<StockData>> GetStockDataAsync(string query)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var symbols = await Yahoo.Symbols(query)
 | 
			
		||||
                                     .Fields(Field.LongName,
 | 
			
		||||
                                         Field.Symbol,
 | 
			
		||||
                                         Field.RegularMarketPrice,
 | 
			
		||||
                                         Field.RegularMarketPreviousClose,
 | 
			
		||||
                                         Field.MarketCap,
 | 
			
		||||
                                         Field.FiftyDayAverageChangePercent,
 | 
			
		||||
                                         Field.TwoHundredDayAverageChangePercent,
 | 
			
		||||
                                         Field.AverageDailyVolume10Day,
 | 
			
		||||
                                         Field.FullExchangeName)
 | 
			
		||||
                                     .QueryAsync();
 | 
			
		||||
 | 
			
		||||
            return symbols
 | 
			
		||||
                   .Select(static x => x.Value)
 | 
			
		||||
                          .Select(static x => new StockData()
 | 
			
		||||
                          {
 | 
			
		||||
                              Name = x.LongName,
 | 
			
		||||
                              Ticker = x.Symbol,
 | 
			
		||||
                              Price = x.RegularMarketPrice,
 | 
			
		||||
                              Close = x.RegularMarketPreviousClose,
 | 
			
		||||
                              MarketCap = x.MarketCap,
 | 
			
		||||
                              Change50d = x.FiftyDayAverageChangePercent,
 | 
			
		||||
                              Change200d = x.TwoHundredDayAverageChangePercent,
 | 
			
		||||
                              DailyVolume = x.AverageDailyVolume10Day,
 | 
			
		||||
                              Exchange = x.FullExchangeName
 | 
			
		||||
                          })
 | 
			
		||||
                          .ToList();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            // what the hell is this api exception
 | 
			
		||||
            Log.Warning(ex, "Error getting stock data: {ErrorMessage}", ex.Message);
 | 
			
		||||
            return Array.Empty<StockData>();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task<IReadOnlyCollection<SymbolData>> SearchSymbolAsync(string query)
 | 
			
		||||
    {
 | 
			
		||||
        return Task.FromResult<IReadOnlyCollection<SymbolData>>(Array.Empty<SymbolData>());
 | 
			
		||||
        
 | 
			
		||||
        // try
 | 
			
		||||
        // {
 | 
			
		||||
        //     query = Uri.EscapeDataString(query);
 | 
			
		||||
        //     using var http = _httpClientFactory.CreateClient();
 | 
			
		||||
        //     var response = await http.GetFromJsonAsync<FinnHubSearchResponse>($"https://finnhub.io/api/v1/search"
 | 
			
		||||
        //                                                                       + $"?q={query}"
 | 
			
		||||
        //                                                                       + $"&token=");
 | 
			
		||||
        //
 | 
			
		||||
        //     if (response is null)
 | 
			
		||||
        //         return Array.Empty<SymbolData>();
 | 
			
		||||
        //
 | 
			
		||||
        //     return response.Result
 | 
			
		||||
        //                    .Where(x => x.Type == "Common Stock")
 | 
			
		||||
        //                    .Select(static x => new SymbolData(x.Symbol, x.Description))
 | 
			
		||||
        //                    .ToList();
 | 
			
		||||
        // }
 | 
			
		||||
        // catch (Exception ex)
 | 
			
		||||
        // {
 | 
			
		||||
        //     Log.Warning(ex, "Error searching stock symbol: {ErrorMessage}", ex.Message);
 | 
			
		||||
        //     return Array.Empty<SymbolData>();
 | 
			
		||||
        // }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public record SymbolData(string Symbol, string Description);
 | 
			
		||||
@@ -65,6 +65,9 @@
 | 
			
		||||
        
 | 
			
		||||
		<!-- Remove this when static abstract interface members support is released -->
 | 
			
		||||
        <PackageReference Include="System.Runtime.Experimental" Version="6.0.0" />
 | 
			
		||||
        
 | 
			
		||||
        <!-- Used by .crypto command -->
 | 
			
		||||
        <PackageReference Include="YahooFinanceApi" Version="2.1.2" />        
 | 
			
		||||
    </ItemGroup>
 | 
			
		||||
 | 
			
		||||
    <ItemGroup>
 | 
			
		||||
@@ -102,13 +105,9 @@
 | 
			
		||||
    </PropertyGroup>
 | 
			
		||||
 | 
			
		||||
	<!-- TODO: Remove this when the conflict issue is fixed -->
 | 
			
		||||
	<Target Name="RemoveSystemRuntimeFromRefPack"
 | 
			
		||||
		 BeforeTargets="_HandlePackageFileConflicts"
 | 
			
		||||
		 Condition="'@(Reference -> WithMetadataValue('NugetPackageId', 'System.Runtime.Experimental'))' != ''">
 | 
			
		||||
	<Target Name="RemoveSystemRuntimeFromRefPack" BeforeTargets="_HandlePackageFileConflicts" Condition="'@(Reference -> WithMetadataValue('NugetPackageId', 'System.Runtime.Experimental'))' != ''">
 | 
			
		||||
		<ItemGroup>
 | 
			
		||||
			<Reference Remove="@(Reference)"
 | 
			
		||||
					   Condition="$([System.String]::Copy(%(Reference.Identity)).Contains('System.Runtime.dll')) and
 | 
			
		||||
                            '%(Reference.NuGetPackageId)' == 'Microsoft.NETCore.App.Ref'" />
 | 
			
		||||
			<Reference Remove="@(Reference)" Condition="$([System.String]::Copy(%(Reference.Identity)).Contains('System.Runtime.dll')) and
                            '%(Reference.NuGetPackageId)' == 'Microsoft.NETCore.App.Ref'" />
 | 
			
		||||
		</ItemGroup>
 | 
			
		||||
	</Target>
 | 
			
		||||
</Project>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
using System.Buffers;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
// made for expressions because they almost never get added
 | 
			
		||||
@@ -29,4 +31,15 @@ public static class ArrayExtensions
 | 
			
		||||
    /// <returns>New array with updated elements</returns>
 | 
			
		||||
    public static TOut[] Map<TIn, TOut>(this TIn[] arr, Func<TIn, TOut> f)
 | 
			
		||||
        => Array.ConvertAll(arr, x => f(x));
 | 
			
		||||
 | 
			
		||||
    public static IReadOnlyCollection<TOut> Map<TIn, TOut>(this IReadOnlyCollection<TIn> col, Func<TIn, TOut> f)
 | 
			
		||||
    {
 | 
			
		||||
        var toReturn = new TOut[col.Count];
 | 
			
		||||
        
 | 
			
		||||
        var i = 0;
 | 
			
		||||
        foreach (var item in col)
 | 
			
		||||
            toReturn[i++] = f(item);
 | 
			
		||||
 | 
			
		||||
        return toReturn;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2033,6 +2033,11 @@ crypto:
 | 
			
		||||
  args:
 | 
			
		||||
    - "btc"
 | 
			
		||||
    - "bitcoin"
 | 
			
		||||
stock:
 | 
			
		||||
  desc: "Shows basic information about a stock. Only symbols are supported. Full company names are not supported atm."
 | 
			
		||||
  args:
 | 
			
		||||
    - "tsla"
 | 
			
		||||
    - "amd"
 | 
			
		||||
rolelevelreq:
 | 
			
		||||
  desc: "Set a level requirement on a self-assignable role."
 | 
			
		||||
  args:
 | 
			
		||||
 
 | 
			
		||||
@@ -888,6 +888,7 @@
 | 
			
		||||
  "volume_24h": "Volume (24h)",
 | 
			
		||||
  "change_7d_24h": "Change (7d / 24h)",
 | 
			
		||||
  "crypto_not_found": "CryptoCurrency with that name was not found.",
 | 
			
		||||
  "stocks_multiple_results": "Multiple results found, please use a symbol",
 | 
			
		||||
  "did_you_mean": "Did you mean {0}?",
 | 
			
		||||
  "self_assign_level_req": "Self assignable role {0} now requires at least server level {1}.",
 | 
			
		||||
  "self_assign_not_level": "That self-assignable role requires at least server level {0}.",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user