mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 00:34:26 -05:00 
			
		
		
		
	Many IDisposable fixes. GlobalNadeko won't have file watchers for creds. Wallet simplified
This commit is contained in:
		@@ -76,6 +76,7 @@ public partial class Gambling
 | 
			
		||||
                            (race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                ar.Dispose();
 | 
			
		||||
                return SendConfirmAsync(GetText(strs.animal_race),
 | 
			
		||||
                    GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon)));
 | 
			
		||||
            }
 | 
			
		||||
@@ -127,6 +128,7 @@ public partial class Gambling
 | 
			
		||||
        private Task Ar_OnStartingFailed(AnimalRace race)
 | 
			
		||||
        {
 | 
			
		||||
            _service.AnimalRaces.TryRemove(ctx.Guild.Id, out _);
 | 
			
		||||
            race.Dispose();
 | 
			
		||||
            return ReplyErrorLocalizedAsync(strs.animal_race_failed);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ public class VoteRewardService : INService, IReadyExecutor
 | 
			
		||||
        if (_client.ShardId != 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var http = new HttpClient(new HttpClientHandler
 | 
			
		||||
        using var http = new HttpClient(new HttpClientHandler
 | 
			
		||||
        {
 | 
			
		||||
            AllowAutoRedirect = false,
 | 
			
		||||
            ServerCertificateCustomValidationCallback = delegate { return true; }
 | 
			
		||||
 
 | 
			
		||||
@@ -359,7 +359,7 @@ public partial class Help : NadekoModule<HelpService>
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            using var dlClient = new AmazonS3Client(accessKey, secretAcccessKey, config);
 | 
			
		||||
            var oldVersionObject = await dlClient.GetObjectAsync(new()
 | 
			
		||||
            using var oldVersionObject = await dlClient.GetObjectAsync(new()
 | 
			
		||||
            {
 | 
			
		||||
                BucketName = "nadeko-pictures",
 | 
			
		||||
                Key = "cmds/versions.json"
 | 
			
		||||
 
 | 
			
		||||
@@ -76,7 +76,7 @@ public sealed partial class YtLoader
 | 
			
		||||
        var mem = GetScriptResponseSpan(response);
 | 
			
		||||
        var root = JsonDocument.Parse(mem).RootElement;
 | 
			
		||||
 | 
			
		||||
        var tracksJsonItems = root
 | 
			
		||||
        using var tracksJsonItems = root
 | 
			
		||||
                              .GetProperty("contents")
 | 
			
		||||
                              .GetProperty("twoColumnSearchResultsRenderer")
 | 
			
		||||
                              .GetProperty("primaryContents")
 | 
			
		||||
 
 | 
			
		||||
@@ -98,15 +98,16 @@ public partial class Searches
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var fileName = $"{query}-sparkline.{imageData.Extension}";
 | 
			
		||||
            using var attachment = new FileAttachment(
 | 
			
		||||
                imageData.FileData,
 | 
			
		||||
                fileName
 | 
			
		||||
            );
 | 
			
		||||
            await message.ModifyAsync(mp =>
 | 
			
		||||
            {
 | 
			
		||||
                mp.Attachments =
 | 
			
		||||
                    new(new[]
 | 
			
		||||
                    {
 | 
			
		||||
                        new FileAttachment(
 | 
			
		||||
                            imageData.FileData,
 | 
			
		||||
                            fileName
 | 
			
		||||
                        )
 | 
			
		||||
                        attachment
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                mp.Embed = eb.WithImageUrl($"attachment://{fileName}").Build();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,55 +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();
 | 
			
		||||
}
 | 
			
		||||
// 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();
 | 
			
		||||
// }
 | 
			
		||||
@@ -34,7 +34,7 @@ public partial class Searches
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            using var http = _httpFactory.CreateClient("memelist");
 | 
			
		||||
            var res = await http.GetAsync("https://api.memegen.link/templates/");
 | 
			
		||||
            using var res = await http.GetAsync("https://api.memegen.link/templates/");
 | 
			
		||||
 | 
			
		||||
            var rawJson = await res.Content.ReadAsStringAsync();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -109,7 +109,8 @@ public class SearchesService : INService
 | 
			
		||||
            using (var avatarImg = Image.Load<Rgba32>(data))
 | 
			
		||||
            {
 | 
			
		||||
                avatarImg.Mutate(x => x.Resize(85, 85).ApplyRoundedCorners(42));
 | 
			
		||||
                data = avatarImg.ToStream().ToArray();
 | 
			
		||||
                await using var avStream = avatarImg.ToStream();
 | 
			
		||||
                data = avStream.ToArray();
 | 
			
		||||
                DrawAvatar(bg, avatarImg);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -141,7 +142,8 @@ public class SearchesService : INService
 | 
			
		||||
            bg.Mutate(x => x.DrawImage(flowers, new(0, 0), new GraphicsOptions()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return bg.ToStream().ToArray();
 | 
			
		||||
        await using var stream = bg.ToStream();
 | 
			
		||||
        return stream.ToArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task<WeatherData> GetWeatherDataAsync(string query)
 | 
			
		||||
@@ -532,7 +534,7 @@ public class SearchesService : INService
 | 
			
		||||
        http.DefaultRequestHeaders.Clear();
 | 
			
		||||
 | 
			
		||||
        using var response = await http.SendAsync(msg);
 | 
			
		||||
        var content = await response.Content.ReadAsStreamAsync();
 | 
			
		||||
        await using var content = await response.Content.ReadAsStreamAsync();
 | 
			
		||||
 | 
			
		||||
        using var document = await _googleParser.ParseDocumentAsync(content);
 | 
			
		||||
        var elems = document.QuerySelectorAll("div.g > div > div");
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ public class PicartoProvider : Provider
 | 
			
		||||
            {
 | 
			
		||||
                http.DefaultRequestHeaders.Accept.Add(new("application/json"));
 | 
			
		||||
                // get id based on the username
 | 
			
		||||
                var res = await http.GetAsync($"https://api.picarto.tv/v1/channel/name/{login}");
 | 
			
		||||
                using var res = await http.GetAsync($"https://api.picarto.tv/v1/channel/name/{login}");
 | 
			
		||||
 | 
			
		||||
                if (!res.IsSuccessStatusCode)
 | 
			
		||||
                    continue;
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ If you are experiencing ratelimits, you should create your own application at: h
 | 
			
		||||
        // so there is no need for ratelimit checks atm
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var res = await http.PostAsJsonAsync(
 | 
			
		||||
            using var res = await http.PostAsJsonAsync(
 | 
			
		||||
                $"https://open-api.trovo.live/openplatform/channels/id",
 | 
			
		||||
                new TrovoRequestData()
 | 
			
		||||
                {
 | 
			
		||||
 
 | 
			
		||||
@@ -84,12 +84,13 @@ public class PatreonRewardsService : INService, IReadyExecutor
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var http = _httpFactory.CreateClient();
 | 
			
		||||
            var res = await http.PostAsync("https://www.patreon.com/api/oauth2/token"
 | 
			
		||||
            using var content = new StringContent(string.Empty);
 | 
			
		||||
            using var res = await http.PostAsync("https://www.patreon.com/api/oauth2/token"
 | 
			
		||||
                                           + "?grant_type=refresh_token"
 | 
			
		||||
                                           + $"&refresh_token={creds.Patreon.RefreshToken}"
 | 
			
		||||
                                           + $"&client_id={creds.Patreon.ClientId}"
 | 
			
		||||
                                           + $"&client_secret={creds.Patreon.ClientSecret}",
 | 
			
		||||
                new StringContent(string.Empty));
 | 
			
		||||
                content);
 | 
			
		||||
 | 
			
		||||
            res.EnsureSuccessStatusCode();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ public sealed class RepeaterService : IReadyExecutor, INService
 | 
			
		||||
        _creds = creds;
 | 
			
		||||
        _client = client;
 | 
			
		||||
 | 
			
		||||
        var uow = _db.GetDbContext();
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var shardRepeaters = uow.Set<Repeater>()
 | 
			
		||||
                                .Where(x => (int)(x.GuildId / Math.Pow(2, 22)) % _creds.TotalShards
 | 
			
		||||
                                            == _client.ShardId)
 | 
			
		||||
 
 | 
			
		||||
@@ -336,7 +336,7 @@ public partial class Utility : NadekoModule
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        using var http = _httpFactory.CreateClient();
 | 
			
		||||
        var res = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
 | 
			
		||||
        using var res = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
 | 
			
		||||
        if (!res.IsImage() || res.GetImageSize() is null or > 262_144)
 | 
			
		||||
        {
 | 
			
		||||
            await ReplyErrorLocalizedAsync(strs.invalid_emoji_link);
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@
 | 
			
		||||
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
			
		||||
        </PackageReference>
 | 
			
		||||
        <PackageReference Include="Html2Markdown" Version="5.0.2.561" />
 | 
			
		||||
        
 | 
			
		||||
        <PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
 | 
			
		||||
        <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
 | 
			
		||||
        <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
 | 
			
		||||
@@ -77,6 +78,12 @@
 | 
			
		||||
        
 | 
			
		||||
        <!-- Used by stream notifications -->
 | 
			
		||||
        <PackageReference Include="TwitchLib.Api" Version="3.4.1" />
 | 
			
		||||
        
 | 
			
		||||
        <!-- Uncomment to check for disposable issues -->
 | 
			
		||||
<!--        <PackageReference Include="IDisposableAnalyzers" Version="4.0.2">-->
 | 
			
		||||
<!--            <PrivateAssets>all</PrivateAssets>-->
 | 
			
		||||
<!--            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>-->
 | 
			
		||||
<!--        </PackageReference>-->
 | 
			
		||||
    </ItemGroup>
 | 
			
		||||
 | 
			
		||||
    <ItemGroup>
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
    public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default)
 | 
			
		||||
    {
 | 
			
		||||
        if (type == CurrencyType.Default)
 | 
			
		||||
            return Task.FromResult<IWallet>(new DefaultWallet(userId, _db.GetDbContext()));
 | 
			
		||||
            return Task.FromResult<IWallet>(new DefaultWallet(userId, _db));
 | 
			
		||||
 | 
			
		||||
        throw new ArgumentOutOfRangeException(nameof(type));
 | 
			
		||||
    }
 | 
			
		||||
@@ -29,7 +29,7 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var userId in userIds)
 | 
			
		||||
            {
 | 
			
		||||
                await using var wallet = await GetWalletAsync(userId);
 | 
			
		||||
                var wallet = await GetWalletAsync(userId);
 | 
			
		||||
                await wallet.Add(amount, txData);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
@@ -68,7 +68,7 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
        long amount,
 | 
			
		||||
        TxData txData)
 | 
			
		||||
    {
 | 
			
		||||
        await using var wallet = await GetWalletAsync(userId);
 | 
			
		||||
        var wallet = await GetWalletAsync(userId);
 | 
			
		||||
        await wallet.Add(amount, txData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -77,7 +77,7 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
        long amount,
 | 
			
		||||
        TxData txData)
 | 
			
		||||
    {
 | 
			
		||||
        await using var wallet = await GetWalletAsync(user.Id);
 | 
			
		||||
        var wallet = await GetWalletAsync(user.Id);
 | 
			
		||||
        await wallet.Add(amount, txData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -86,7 +86,7 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
        long amount,
 | 
			
		||||
        TxData txData)
 | 
			
		||||
    {
 | 
			
		||||
        await using var wallet = await GetWalletAsync(userId);
 | 
			
		||||
        var wallet = await GetWalletAsync(userId);
 | 
			
		||||
        return await wallet.Take(amount, txData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -95,7 +95,7 @@ public class CurrencyService : ICurrencyService, INService
 | 
			
		||||
        long amount,
 | 
			
		||||
        TxData txData)
 | 
			
		||||
    {
 | 
			
		||||
        await using var wallet = await GetWalletAsync(user.Id);
 | 
			
		||||
        var wallet = await GetWalletAsync(user.Id);
 | 
			
		||||
        return await wallet.Take(amount, txData);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,7 +6,7 @@ public static class CurrencyServiceExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static async Task<long> GetBalanceAsync(this ICurrencyService cs, ulong userId)
 | 
			
		||||
    {
 | 
			
		||||
        await using var wallet = await cs.GetWalletAsync(userId);
 | 
			
		||||
        var wallet = await cs.GetWalletAsync(userId);
 | 
			
		||||
        return await wallet.GetBalance();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
@@ -18,8 +18,8 @@ public static class CurrencyServiceExtensions
 | 
			
		||||
        string fromName,
 | 
			
		||||
        string note)
 | 
			
		||||
    {
 | 
			
		||||
        await using var fromWallet = await cs.GetWalletAsync(fromId);
 | 
			
		||||
        await using var toWallet = await cs.GetWalletAsync(toId);
 | 
			
		||||
        var fromWallet = await cs.GetWalletAsync(fromId);
 | 
			
		||||
        var toWallet = await cs.GetWalletAsync(toId);
 | 
			
		||||
 | 
			
		||||
        var extra = new TxData("gift", fromName, note, fromId);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,48 +7,53 @@ namespace NadekoBot.Services.Currency;
 | 
			
		||||
 | 
			
		||||
public class DefaultWallet : IWallet
 | 
			
		||||
{
 | 
			
		||||
    private readonly DbService _db;
 | 
			
		||||
    public ulong UserId { get; }
 | 
			
		||||
 | 
			
		||||
    private readonly NadekoContext _ctx;
 | 
			
		||||
 | 
			
		||||
    public DefaultWallet(ulong userId, NadekoContext ctx)
 | 
			
		||||
    public DefaultWallet(ulong userId, DbService db)
 | 
			
		||||
    {
 | 
			
		||||
        UserId = userId;
 | 
			
		||||
        _ctx = ctx;
 | 
			
		||||
        _db = db;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task<long> GetBalance()
 | 
			
		||||
        => _ctx.DiscordUser
 | 
			
		||||
               .ToLinqToDBTable()
 | 
			
		||||
               .Where(x => x.UserId == UserId)
 | 
			
		||||
               .Select(x => x.CurrencyAmount)
 | 
			
		||||
               .FirstOrDefaultAsync();
 | 
			
		||||
    public async Task<long> GetBalance()
 | 
			
		||||
    {
 | 
			
		||||
        await using var ctx = _db.GetDbContext();
 | 
			
		||||
        return await ctx.DiscordUser
 | 
			
		||||
                        .ToLinqToDBTable()
 | 
			
		||||
                        .Where(x => x.UserId == UserId)
 | 
			
		||||
                        .Select(x => x.CurrencyAmount)
 | 
			
		||||
                        .FirstOrDefaultAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> Take(long amount, TxData txData)
 | 
			
		||||
    {
 | 
			
		||||
        if (amount < 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(amount), "Amount to take must be non negative.");
 | 
			
		||||
 | 
			
		||||
        var changed = await _ctx.DiscordUser
 | 
			
		||||
                                .Where(x => x.UserId == UserId && x.CurrencyAmount >= amount)
 | 
			
		||||
                                .UpdateAsync(x => new()
 | 
			
		||||
                                {
 | 
			
		||||
                                    CurrencyAmount = x.CurrencyAmount - amount
 | 
			
		||||
                                });
 | 
			
		||||
        await using var ctx = _db.GetDbContext();
 | 
			
		||||
 | 
			
		||||
        var changed = await ctx.DiscordUser
 | 
			
		||||
                               .Where(x => x.UserId == UserId && x.CurrencyAmount >= amount)
 | 
			
		||||
                               .UpdateAsync(x => new()
 | 
			
		||||
                               {
 | 
			
		||||
                                   CurrencyAmount = x.CurrencyAmount - amount
 | 
			
		||||
                               });
 | 
			
		||||
 | 
			
		||||
        if (changed == 0)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        await _ctx.CreateLinqToDbContext()
 | 
			
		||||
                  .InsertAsync(new CurrencyTransaction()
 | 
			
		||||
                  {
 | 
			
		||||
                      Amount = -amount,
 | 
			
		||||
                      Note = txData.Note,
 | 
			
		||||
                      UserId = UserId,
 | 
			
		||||
                      Type = txData.Type,
 | 
			
		||||
                      Extra = txData.Extra,
 | 
			
		||||
                      OtherId = txData.OtherId
 | 
			
		||||
                  });
 | 
			
		||||
        await using var ctx2 = ctx.CreateLinqToDbContext();
 | 
			
		||||
        await ctx2
 | 
			
		||||
            .InsertAsync(new CurrencyTransaction()
 | 
			
		||||
            {
 | 
			
		||||
                Amount = -amount,
 | 
			
		||||
                Note = txData.Note,
 | 
			
		||||
                UserId = UserId,
 | 
			
		||||
                Type = txData.Type,
 | 
			
		||||
                Extra = txData.Extra,
 | 
			
		||||
                OtherId = txData.OtherId
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
@@ -58,9 +63,11 @@ public class DefaultWallet : IWallet
 | 
			
		||||
        if (amount <= 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(amount), "Amount must be greater than 0.");
 | 
			
		||||
 | 
			
		||||
        await using (var tran = await _ctx.Database.BeginTransactionAsync())
 | 
			
		||||
        await using var ctx = _db.GetDbContext();
 | 
			
		||||
        
 | 
			
		||||
        await using (var tran = await ctx.Database.BeginTransactionAsync())
 | 
			
		||||
        {
 | 
			
		||||
            var changed = await _ctx.DiscordUser
 | 
			
		||||
            var changed = await ctx.DiscordUser
 | 
			
		||||
                                    .Where(x => x.UserId == UserId)
 | 
			
		||||
                                    .UpdateAsync(x => new()
 | 
			
		||||
                                    {
 | 
			
		||||
@@ -69,7 +76,7 @@ public class DefaultWallet : IWallet
 | 
			
		||||
 | 
			
		||||
            if (changed == 0)
 | 
			
		||||
            {
 | 
			
		||||
                await _ctx.DiscordUser
 | 
			
		||||
                await ctx.DiscordUser
 | 
			
		||||
                          .ToLinqToDBTable()
 | 
			
		||||
                          .Value(x => x.UserId, UserId)
 | 
			
		||||
                          .Value(x => x.Username, "Unknown")
 | 
			
		||||
@@ -91,19 +98,7 @@ public class DefaultWallet : IWallet
 | 
			
		||||
            OtherId = txData.OtherId
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await _ctx.CreateLinqToDbContext()
 | 
			
		||||
                  .InsertAsync(ct);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void Dispose()
 | 
			
		||||
    {
 | 
			
		||||
        _ctx.SaveChanges();
 | 
			
		||||
        _ctx.Dispose();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async ValueTask DisposeAsync()
 | 
			
		||||
    {
 | 
			
		||||
        await _ctx.SaveChangesAsync();
 | 
			
		||||
        await _ctx.DisposeAsync();
 | 
			
		||||
        await using var ctx2 = ctx.CreateLinqToDbContext();
 | 
			
		||||
        await ctx2.InsertAsync(ct);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
namespace NadekoBot.Services.Currency;
 | 
			
		||||
 | 
			
		||||
public interface IWallet : IDisposable, IAsyncDisposable
 | 
			
		||||
public interface IWallet
 | 
			
		||||
{
 | 
			
		||||
    public ulong UserId { get; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,10 +32,9 @@ public class DbService
 | 
			
		||||
        using var context = new NadekoContext(_options);
 | 
			
		||||
        if (context.Database.GetPendingMigrations().Any())
 | 
			
		||||
        {
 | 
			
		||||
            var mContext = new NadekoContext(_migrateOptions);
 | 
			
		||||
            using var mContext = new NadekoContext(_migrateOptions);
 | 
			
		||||
            mContext.Database.Migrate();
 | 
			
		||||
            mContext.SaveChanges();
 | 
			
		||||
            mContext.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        context.Database.ExecuteSqlRaw("PRAGMA journal_mode=WAL");
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ public sealed class BotCredsProvider : IBotCredsProvider
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private readonly object _reloadLock = new();
 | 
			
		||||
    private readonly IDisposable _changeToken;
 | 
			
		||||
 | 
			
		||||
    public BotCredsProvider(int? totalShards = null)
 | 
			
		||||
    {
 | 
			
		||||
@@ -52,9 +53,9 @@ public sealed class BotCredsProvider : IBotCredsProvider
 | 
			
		||||
        _config = new ConfigurationBuilder().AddYamlFile(CredsPath, false, true)
 | 
			
		||||
                                            .AddEnvironmentVariables("NadekoBot_")
 | 
			
		||||
                                            .Build();
 | 
			
		||||
 | 
			
		||||
        ChangeToken.OnChange(() => _config.GetReloadToken(), Reload);
 | 
			
		||||
 | 
			
		||||
#if !GLOBAL_NADEKO
 | 
			
		||||
        _changeToken = ChangeToken.OnChange(() => _config.GetReloadToken(), Reload);
 | 
			
		||||
#endif
 | 
			
		||||
        Reload();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ using System.Diagnostics;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
public class StatsService : IStatsService, IReadyExecutor, INService, IDisposable
 | 
			
		||||
public sealed class StatsService : IStatsService, IReadyExecutor, INService, IDisposable
 | 
			
		||||
{
 | 
			
		||||
    public const string BOT_VERSION = "4.0.4";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,87 +1,87 @@
 | 
			
		||||
// Copyright (c) .NET Foundation. All rights reserved.
 | 
			
		||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 | 
			
		||||
// https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.Process.Sources/ProcessHelper.cs
 | 
			
		||||
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class ProcessExtensions
 | 
			
		||||
{
 | 
			
		||||
    private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
 | 
			
		||||
    private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(10);
 | 
			
		||||
 | 
			
		||||
    public static void KillTree(this Process process)
 | 
			
		||||
        => process.KillTree(_defaultTimeout);
 | 
			
		||||
 | 
			
		||||
    public static void KillTree(this Process process, TimeSpan timeout)
 | 
			
		||||
    {
 | 
			
		||||
        if (_isWindows)
 | 
			
		||||
            RunProcessAndWaitForExit("taskkill", $"/T /F /PID {process.Id}", timeout, out _);
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            var children = new HashSet<int>();
 | 
			
		||||
            GetAllChildIdsUnix(process.Id, children, timeout);
 | 
			
		||||
            foreach (var childId in children)
 | 
			
		||||
                KillProcessUnix(childId, timeout);
 | 
			
		||||
 | 
			
		||||
            KillProcessUnix(process.Id, timeout);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
 | 
			
		||||
    {
 | 
			
		||||
        var exitCode = RunProcessAndWaitForExit("pgrep", $"-P {parentId}", timeout, out var stdout);
 | 
			
		||||
 | 
			
		||||
        if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
 | 
			
		||||
        {
 | 
			
		||||
            using var reader = new StringReader(stdout);
 | 
			
		||||
            while (true)
 | 
			
		||||
            {
 | 
			
		||||
                var text = reader.ReadLine();
 | 
			
		||||
                if (text is null)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                if (int.TryParse(text, out var id))
 | 
			
		||||
                {
 | 
			
		||||
                    children.Add(id);
 | 
			
		||||
                    // Recursively get the children
 | 
			
		||||
                    GetAllChildIdsUnix(id, children, timeout);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void KillProcessUnix(int processId, TimeSpan timeout)
 | 
			
		||||
        => RunProcessAndWaitForExit("kill", $"-TERM {processId}", timeout, out _);
 | 
			
		||||
 | 
			
		||||
    private static int RunProcessAndWaitForExit(
 | 
			
		||||
        string fileName,
 | 
			
		||||
        string arguments,
 | 
			
		||||
        TimeSpan timeout,
 | 
			
		||||
        out string? stdout)
 | 
			
		||||
    {
 | 
			
		||||
        stdout = null;
 | 
			
		||||
 | 
			
		||||
        var startInfo = new ProcessStartInfo
 | 
			
		||||
        {
 | 
			
		||||
            FileName = fileName,
 | 
			
		||||
            Arguments = arguments,
 | 
			
		||||
            RedirectStandardOutput = true,
 | 
			
		||||
            UseShellExecute = false
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        var process = Process.Start(startInfo);
 | 
			
		||||
 | 
			
		||||
        if (process is null)
 | 
			
		||||
            return -1;
 | 
			
		||||
 | 
			
		||||
        if (process.WaitForExit((int)timeout.TotalMilliseconds))
 | 
			
		||||
            stdout = process.StandardOutput.ReadToEnd();
 | 
			
		||||
        else
 | 
			
		||||
            process.Kill();
 | 
			
		||||
 | 
			
		||||
        return process.ExitCode;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
// // Copyright (c) .NET Foundation. All rights reserved.
 | 
			
		||||
// // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 | 
			
		||||
// // https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.Process.Sources/ProcessHelper.cs
 | 
			
		||||
//
 | 
			
		||||
// using System.Diagnostics;
 | 
			
		||||
// using System.Runtime.InteropServices;
 | 
			
		||||
//
 | 
			
		||||
// namespace NadekoBot.Extensions;
 | 
			
		||||
//
 | 
			
		||||
// public static class ProcessExtensions
 | 
			
		||||
// {
 | 
			
		||||
//     private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
 | 
			
		||||
//     private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(10);
 | 
			
		||||
//
 | 
			
		||||
//     public static void KillTree(this Process process)
 | 
			
		||||
//         => process.KillTree(_defaultTimeout);
 | 
			
		||||
//
 | 
			
		||||
//     public static void KillTree(this Process process, TimeSpan timeout)
 | 
			
		||||
//     {
 | 
			
		||||
//         if (_isWindows)
 | 
			
		||||
//             RunProcessAndWaitForExit("taskkill", $"/T /F /PID {process.Id}", timeout, out _);
 | 
			
		||||
//         else
 | 
			
		||||
//         {
 | 
			
		||||
//             var children = new HashSet<int>();
 | 
			
		||||
//             GetAllChildIdsUnix(process.Id, children, timeout);
 | 
			
		||||
//             foreach (var childId in children)
 | 
			
		||||
//                 KillProcessUnix(childId, timeout);
 | 
			
		||||
//
 | 
			
		||||
//             KillProcessUnix(process.Id, timeout);
 | 
			
		||||
//         }
 | 
			
		||||
//     }
 | 
			
		||||
//
 | 
			
		||||
//     private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
 | 
			
		||||
//     {
 | 
			
		||||
//         var exitCode = RunProcessAndWaitForExit("pgrep", $"-P {parentId}", timeout, out var stdout);
 | 
			
		||||
//
 | 
			
		||||
//         if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
 | 
			
		||||
//         {
 | 
			
		||||
//             using var reader = new StringReader(stdout);
 | 
			
		||||
//             while (true)
 | 
			
		||||
//             {
 | 
			
		||||
//                 var text = reader.ReadLine();
 | 
			
		||||
//                 if (text is null)
 | 
			
		||||
//                     return;
 | 
			
		||||
//
 | 
			
		||||
//                 if (int.TryParse(text, out var id))
 | 
			
		||||
//                 {
 | 
			
		||||
//                     children.Add(id);
 | 
			
		||||
//                     // Recursively get the children
 | 
			
		||||
//                     GetAllChildIdsUnix(id, children, timeout);
 | 
			
		||||
//                 }
 | 
			
		||||
//             }
 | 
			
		||||
//         }
 | 
			
		||||
//     }
 | 
			
		||||
//
 | 
			
		||||
//     private static void KillProcessUnix(int processId, TimeSpan timeout)
 | 
			
		||||
//         => RunProcessAndWaitForExit("kill", $"-TERM {processId}", timeout, out _);
 | 
			
		||||
//
 | 
			
		||||
//     private static int RunProcessAndWaitForExit(
 | 
			
		||||
//         string fileName,
 | 
			
		||||
//         string arguments,
 | 
			
		||||
//         TimeSpan timeout,
 | 
			
		||||
//         out string? stdout)
 | 
			
		||||
//     {
 | 
			
		||||
//         stdout = null;
 | 
			
		||||
//
 | 
			
		||||
//         var startInfo = new ProcessStartInfo
 | 
			
		||||
//         {
 | 
			
		||||
//             FileName = fileName,
 | 
			
		||||
//             Arguments = arguments,
 | 
			
		||||
//             RedirectStandardOutput = true,
 | 
			
		||||
//             UseShellExecute = false
 | 
			
		||||
//         };
 | 
			
		||||
//
 | 
			
		||||
//         using var process = Process.Start(startInfo);
 | 
			
		||||
//
 | 
			
		||||
//         if (process is null)
 | 
			
		||||
//             return -1;
 | 
			
		||||
//
 | 
			
		||||
//         if (process.WaitForExit((int)timeout.TotalMilliseconds))
 | 
			
		||||
//             stdout = process.StandardOutput.ReadToEnd();
 | 
			
		||||
//         else
 | 
			
		||||
//             process.Kill();
 | 
			
		||||
//
 | 
			
		||||
//         return process.ExitCode;
 | 
			
		||||
//     }
 | 
			
		||||
// }
 | 
			
		||||
@@ -21,7 +21,7 @@ public static class Rgba32Extensions
 | 
			
		||||
            var xOffset = 0;
 | 
			
		||||
            for (var i = 0; i < imgArray.Count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                var frame = imgArray[i].Frames.CloneFrame(frameNumber % imgArray[i].Frames.Count);
 | 
			
		||||
                using var frame = imgArray[i].Frames.CloneFrame(frameNumber % imgArray[i].Frames.Count);
 | 
			
		||||
                var offset = xOffset;
 | 
			
		||||
                imgFrame.Mutate(x => x.DrawImage(frame, new(offset, 0), new GraphicsOptions()));
 | 
			
		||||
                xOffset += imgArray[i].Bounds().Width;
 | 
			
		||||
 
 | 
			
		||||
@@ -89,7 +89,7 @@ public static class StringExtensions
 | 
			
		||||
    public static async Task<Stream> ToStream(this string str)
 | 
			
		||||
    {
 | 
			
		||||
        var ms = new MemoryStream();
 | 
			
		||||
        var sw = new StreamWriter(ms);
 | 
			
		||||
        await using var sw = new StreamWriter(ms);
 | 
			
		||||
        await sw.WriteAsync(str);
 | 
			
		||||
        await sw.FlushAsync();
 | 
			
		||||
        ms.Position = 0;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user