mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 00:34:26 -05:00 
			
		
		
		
	Slots redesign nad images moved to images.yml
This commit is contained in:
		@@ -145,7 +145,8 @@ namespace NadekoBot
 | 
			
		||||
            
 | 
			
		||||
            svcs.Scan(scan => scan
 | 
			
		||||
                .FromAssemblyOf<IReadyExecutor>()
 | 
			
		||||
                .AddClasses(classes => classes.AssignableToAny(
 | 
			
		||||
                .AddClasses(classes => classes
 | 
			
		||||
                        .AssignableToAny(
 | 
			
		||||
                    // services
 | 
			
		||||
                    typeof(INService),
 | 
			
		||||
                    
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
using System;
 | 
			
		||||
using NadekoBot.Common.Yml;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
{
 | 
			
		||||
    public class ImageUrls
 | 
			
		||||
    {
 | 
			
		||||
        [Comment("DO NOT CHANGE")]
 | 
			
		||||
        public int Version { get; set; } = 2;
 | 
			
		||||
 | 
			
		||||
        public CoinData Coins { get; set; }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										49
									
								
								src/NadekoBot/Common/OldImageUrls.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/NadekoBot/Common/OldImageUrls.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Common
 | 
			
		||||
{
 | 
			
		||||
    public class OldImageUrls
 | 
			
		||||
    {
 | 
			
		||||
        public int Version { get; set; } = 2;
 | 
			
		||||
 | 
			
		||||
        public CoinData Coins { get; set; }
 | 
			
		||||
        public Uri[] Currency { get; set; }
 | 
			
		||||
        public Uri[] Dice { get; set; }
 | 
			
		||||
        public RategirlData Rategirl { get; set; }
 | 
			
		||||
        public XpData Xp { get; set; }
 | 
			
		||||
 | 
			
		||||
        //new
 | 
			
		||||
        public RipData Rip { get; set; }
 | 
			
		||||
        public SlotData Slots { get; set; }
 | 
			
		||||
 | 
			
		||||
        public class RipData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri Bg { get; set; }
 | 
			
		||||
            public Uri Overlay { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class SlotData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri[] Emojis { get; set; }
 | 
			
		||||
            public Uri[] Numbers { get; set; }
 | 
			
		||||
            public Uri Bg { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class CoinData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri[] Heads { get; set; }
 | 
			
		||||
            public Uri[] Tails { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class RategirlData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri Matrix { get; set; }
 | 
			
		||||
            public Uri Dot { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public class XpData
 | 
			
		||||
        {
 | 
			
		||||
            public Uri Bg { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -149,7 +149,7 @@ namespace NadekoBot.Modules.CustomReactions
 | 
			
		||||
                await ctx.Channel.EmbedAsync(_eb.Create().WithOkColor()
 | 
			
		||||
                    .WithDescription($"#{id}")
 | 
			
		||||
                    .AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
 | 
			
		||||
                    .AddField(GetText(strs.response), (found.Response + "\n```css\n" + found.Response).TrimTo(1020) + "```")
 | 
			
		||||
                    .AddField(GetText(strs.response), found.Response.TrimTo(1000).Replace("](", "]\\("))
 | 
			
		||||
                    ).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								src/NadekoBot/Modules/Gambling/Common/GamblingError.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/NadekoBot/Modules/Gambling/Common/GamblingError.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
namespace NadekoBot.Modules.Gambling
 | 
			
		||||
{
 | 
			
		||||
    public enum GamblingError
 | 
			
		||||
    {
 | 
			
		||||
        None,
 | 
			
		||||
        NotEnough
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								src/NadekoBot/Modules/Gambling/Common/Slot/SlotGame.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/NadekoBot/Modules/Gambling/Common/Slot/SlotGame.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using NadekoBot.Common;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Gambling.Common.Slot
 | 
			
		||||
{
 | 
			
		||||
    public class SlotGame
 | 
			
		||||
    {
 | 
			
		||||
        public class Result
 | 
			
		||||
        {
 | 
			
		||||
            public float Multiplier { get; }
 | 
			
		||||
            public int[] Rolls { get; }
 | 
			
		||||
 | 
			
		||||
            public Result(float multiplier, int[] rolls)
 | 
			
		||||
            {
 | 
			
		||||
                Multiplier = multiplier;
 | 
			
		||||
                Rolls = rolls;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static readonly Random _rng = new NadekoRandom();
 | 
			
		||||
 | 
			
		||||
        public SlotGame()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Result Spin()
 | 
			
		||||
        {
 | 
			
		||||
            var rolls = new int[] { _rng.Next(0, 6), _rng.Next(0, 6), _rng.Next(0, 6) };
 | 
			
		||||
            var multi = 0;
 | 
			
		||||
 | 
			
		||||
            if (rolls.All(x => x == 5))
 | 
			
		||||
                multi = 30;
 | 
			
		||||
            else if (rolls.All(x => x == rolls[0]))
 | 
			
		||||
                multi = 10;
 | 
			
		||||
            else if (rolls.Count(x => x == 5) == 2)
 | 
			
		||||
                multi = 4;
 | 
			
		||||
            else if (rolls.Any(x => x == 5))
 | 
			
		||||
                multi = 1;
 | 
			
		||||
 | 
			
		||||
            return new Result(multi, rolls);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/NadekoBot/Modules/Gambling/Common/SlotResponse.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/NadekoBot/Modules/Gambling/Common/SlotResponse.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Gambling
 | 
			
		||||
{
 | 
			
		||||
    public class SlotResponse
 | 
			
		||||
    {
 | 
			
		||||
        public float Multiplier { get; set; }
 | 
			
		||||
        public long Won { get; set; }
 | 
			
		||||
        public List<int> Rolls { get; set; } = new List<int>();
 | 
			
		||||
        public GamblingError Error { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -22,15 +22,12 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
        {
 | 
			
		||||
            private readonly IImageCache _images;
 | 
			
		||||
            private readonly ICurrencyService _cs;
 | 
			
		||||
            private readonly DbService _db;
 | 
			
		||||
            private static readonly NadekoRandom rng = new NadekoRandom();
 | 
			
		||||
 | 
			
		||||
            public FlipCoinCommands(IDataCache data, ICurrencyService cs, DbService db,
 | 
			
		||||
                GamblingConfigService gss) : base(gss)
 | 
			
		||||
            public FlipCoinCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gss) : base(gss)
 | 
			
		||||
            {
 | 
			
		||||
                _images = data.LocalImages;
 | 
			
		||||
                _cs = cs;
 | 
			
		||||
                _db = db;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [NadekoCommand, Aliases]
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,14 @@ using NadekoBot.Modules.Gambling.Common.WheelOfFortune;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using NadekoBot.Common;
 | 
			
		||||
using NadekoBot.Db;
 | 
			
		||||
using NadekoBot.Modules.Gambling.Common.Slot;
 | 
			
		||||
using NadekoBot.Modules.Gambling.Services;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
@@ -82,6 +84,41 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU
 | 
			
		||||
                }, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public async Task<SlotResponse> SlotAsync(ulong userId, long amount)
 | 
			
		||||
        {
 | 
			
		||||
            var takeRes = await _cs.RemoveAsync(userId, "Slot Machine", amount, true);
 | 
			
		||||
            
 | 
			
		||||
            if (!takeRes)
 | 
			
		||||
            {
 | 
			
		||||
                return new SlotResponse
 | 
			
		||||
                {
 | 
			
		||||
                    Error = GamblingError.NotEnough
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var game = new SlotGame();
 | 
			
		||||
            var result = game.Spin();
 | 
			
		||||
            long won = 0;
 | 
			
		||||
 | 
			
		||||
            if (result.Multiplier > 0)
 | 
			
		||||
            {
 | 
			
		||||
                won = (long)(result.Multiplier * amount);
 | 
			
		||||
 | 
			
		||||
                await _cs.AddAsync(userId, $"Slot Machine x{result.Multiplier}", won, true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var toReturn = new SlotResponse
 | 
			
		||||
            {
 | 
			
		||||
                Multiplier = result.Multiplier,
 | 
			
		||||
                Won = won,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            toReturn.Rolls.AddRange(result.Rolls);
 | 
			
		||||
 | 
			
		||||
            return toReturn;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        public struct EconomyResult
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,12 +9,16 @@ using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Common;
 | 
			
		||||
using NadekoBot.Common.Attributes;
 | 
			
		||||
using NadekoBot.Db.Models;
 | 
			
		||||
using NadekoBot.Modules.Gambling.Services;
 | 
			
		||||
using NadekoBot.Modules.Gambling.Common;
 | 
			
		||||
using NadekoBot.Services;
 | 
			
		||||
using SixLabors.Fonts;
 | 
			
		||||
using Image = SixLabors.ImageSharp.Image;
 | 
			
		||||
using SixLabors.ImageSharp.Processing;
 | 
			
		||||
using SixLabors.ImageSharp;
 | 
			
		||||
using SixLabors.ImageSharp.Drawing.Processing;
 | 
			
		||||
using SixLabors.ImageSharp.PixelFormats;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Gambling
 | 
			
		||||
{
 | 
			
		||||
@@ -33,12 +37,16 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
            //thanks to judge for helping me with this
 | 
			
		||||
 | 
			
		||||
            private readonly IImageCache _images;
 | 
			
		||||
            private readonly ICurrencyService _cs;
 | 
			
		||||
            private FontProvider _fonts;
 | 
			
		||||
            private readonly DbService _db;
 | 
			
		||||
 | 
			
		||||
            public SlotCommands(IDataCache data, ICurrencyService cs, GamblingConfigService gamb) : base(gamb)
 | 
			
		||||
            public SlotCommands(IDataCache data,
 | 
			
		||||
                FontProvider fonts, DbService db,
 | 
			
		||||
                GamblingConfigService gamb) : base(gamb)
 | 
			
		||||
            {
 | 
			
		||||
                _images = data.LocalImages;
 | 
			
		||||
                _cs = cs;
 | 
			
		||||
                _fonts = fonts;
 | 
			
		||||
                _db = db;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public sealed class SlotMachine
 | 
			
		||||
@@ -140,92 +148,115 @@ namespace NadekoBot.Modules.Gambling
 | 
			
		||||
            [NadekoCommand, Aliases]
 | 
			
		||||
            public async Task Slot(ShmartNumber amount)
 | 
			
		||||
            {
 | 
			
		||||
                if (!_runningUsers.Add(ctx.User.Id))
 | 
			
		||||
               if (!_runningUsers.Add(ctx.User.Id))
 | 
			
		||||
                    return;
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    if (!await CheckBetMandatory(amount).ConfigureAwait(false))
 | 
			
		||||
                        return;
 | 
			
		||||
                    const int maxAmount = 9999;
 | 
			
		||||
                    if (amount > maxAmount)
 | 
			
		||||
                    {
 | 
			
		||||
                        await ReplyErrorLocalizedAsync(strs.max_bet_limit(maxAmount + CurrencySign));
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
               
 | 
			
		||||
               try
 | 
			
		||||
               {
 | 
			
		||||
                   if (!await CheckBetMandatory(amount).ConfigureAwait(false))
 | 
			
		||||
                       return;
 | 
			
		||||
                    
 | 
			
		||||
                   await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    if (!await _cs.RemoveAsync(ctx.User, "Slot Machine", amount, false, gamble: true).ConfigureAwait(false))
 | 
			
		||||
                    {
 | 
			
		||||
                        await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    Interlocked.Add(ref _totalBet, amount.Value);
 | 
			
		||||
                    using (var bgImage = Image.Load(_images.SlotBackground))
 | 
			
		||||
                    {
 | 
			
		||||
                        var result = SlotMachine.Pull();
 | 
			
		||||
                        int[] numbers = result.Numbers;
 | 
			
		||||
                   var result = await _service.SlotAsync(ctx.User.Id, amount);
 | 
			
		||||
 | 
			
		||||
                        for (int i = 0; i < 3; i++)
 | 
			
		||||
                        {
 | 
			
		||||
                            using (var randomImage = Image.Load(_images.SlotEmojis[numbers[i]]))
 | 
			
		||||
                            {
 | 
			
		||||
                                bgImage.Mutate(x => x.DrawImage(randomImage, new Point(95 + 142 * i, 330), new GraphicsOptions()));
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                   if (result.Error != GamblingError.None)
 | 
			
		||||
                   {
 | 
			
		||||
                       if (result.Error == GamblingError.NotEnough)
 | 
			
		||||
                       {
 | 
			
		||||
                           await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
 | 
			
		||||
                       }
 | 
			
		||||
 | 
			
		||||
                        var won = amount * result.Multiplier;
 | 
			
		||||
                        var printWon = won;
 | 
			
		||||
                        var n = 0;
 | 
			
		||||
                        do
 | 
			
		||||
                        {
 | 
			
		||||
                            var digit = (int)(printWon % 10);
 | 
			
		||||
                            using (var img = Image.Load(_images.SlotNumbers[digit]))
 | 
			
		||||
                            {
 | 
			
		||||
                                bgImage.Mutate(x => x.DrawImage(img, new Point(230 - n * 16, 462), new GraphicsOptions()));
 | 
			
		||||
                            }
 | 
			
		||||
                            n++;
 | 
			
		||||
                        } while ((printWon /= 10) != 0);
 | 
			
		||||
                       return;
 | 
			
		||||
                   }
 | 
			
		||||
 | 
			
		||||
                        var printAmount = amount;
 | 
			
		||||
                        n = 0;
 | 
			
		||||
                        do
 | 
			
		||||
                        {
 | 
			
		||||
                            var digit = (int)(printAmount % 10);
 | 
			
		||||
                            using (var img = Image.Load(_images.SlotNumbers[digit]))
 | 
			
		||||
                            {
 | 
			
		||||
                                bgImage.Mutate(x => x.DrawImage(img, new Point(395 - n * 16, 462), new GraphicsOptions()));
 | 
			
		||||
                            }
 | 
			
		||||
                            n++;
 | 
			
		||||
                        } while ((printAmount /= 10) != 0);
 | 
			
		||||
                   Interlocked.Add(ref _totalBet, amount);
 | 
			
		||||
                   Interlocked.Add(ref _totalPaidOut, result.Won);
 | 
			
		||||
 | 
			
		||||
                        var msg = GetText(strs.better_luck);
 | 
			
		||||
                        if (result.Multiplier != 0)
 | 
			
		||||
                        {
 | 
			
		||||
                            await _cs.AddAsync(ctx.User, $"Slot Machine x{result.Multiplier}", amount * result.Multiplier, false, gamble: true).ConfigureAwait(false);
 | 
			
		||||
                            Interlocked.Add(ref _totalPaidOut, amount * result.Multiplier);
 | 
			
		||||
                            if (result.Multiplier == 1)
 | 
			
		||||
                                msg = GetText(strs.slot_single(CurrencySign, 1));
 | 
			
		||||
                            else if (result.Multiplier == 4)
 | 
			
		||||
                                msg = GetText(strs.slot_two(CurrencySign, 4));
 | 
			
		||||
                            else if (result.Multiplier == 10)
 | 
			
		||||
                                msg = GetText(strs.slot_three(10));
 | 
			
		||||
                            else if (result.Multiplier == 30)
 | 
			
		||||
                                msg = GetText(strs.slot_jackpot(30));
 | 
			
		||||
                        }
 | 
			
		||||
                   long ownedAmount;
 | 
			
		||||
                   using (var uow = _db.GetDbContext())
 | 
			
		||||
                   {
 | 
			
		||||
                       ownedAmount = uow.Set<DiscordUser>()
 | 
			
		||||
                           .FirstOrDefault(x => x.UserId == ctx.User.Id)
 | 
			
		||||
                           ?.CurrencyAmount ?? 0;
 | 
			
		||||
                   }
 | 
			
		||||
 | 
			
		||||
                        using (var imgStream = bgImage.ToStream())
 | 
			
		||||
                        {
 | 
			
		||||
                            await ctx.Channel.SendFileAsync(imgStream, "result.png", ctx.User.Mention + " " + msg + $"\n`{GetText(strs.slot_bet)}:`{amount} `{GetText(strs.won)}:` {amount * result.Multiplier}{CurrencySign}").ConfigureAwait(false);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                finally
 | 
			
		||||
                {
 | 
			
		||||
                    var _ = Task.Run(async () =>
 | 
			
		||||
                    {
 | 
			
		||||
                        await Task.Delay(1000).ConfigureAwait(false);
 | 
			
		||||
                        _runningUsers.Remove(ctx.User.Id);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                   using (var bgImage = Image.Load<Rgba32>(_images.SlotBackground, out var format))
 | 
			
		||||
                   {
 | 
			
		||||
                       var numbers = new int[3];
 | 
			
		||||
                       result.Rolls.CopyTo(numbers, 0);
 | 
			
		||||
 | 
			
		||||
                       bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
 | 
			
		||||
                           {
 | 
			
		||||
                               TextOptions = new TextOptions()
 | 
			
		||||
                               {
 | 
			
		||||
                                   HorizontalAlignment = HorizontalAlignment.Center,
 | 
			
		||||
                                   VerticalAlignment = VerticalAlignment.Center,
 | 
			
		||||
                                   WrapTextWidth = 140,
 | 
			
		||||
                               }
 | 
			
		||||
                           }, result.Won.ToString(), _fonts.DottyFont.CreateFont(65), SixLabors.ImageSharp.Color.Red,
 | 
			
		||||
                           new PointF(227, 92)));
 | 
			
		||||
 | 
			
		||||
                       bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
 | 
			
		||||
                           {
 | 
			
		||||
                               TextOptions = new TextOptions()
 | 
			
		||||
                               {
 | 
			
		||||
                                   HorizontalAlignment = HorizontalAlignment.Center,
 | 
			
		||||
                                   VerticalAlignment = VerticalAlignment.Center,
 | 
			
		||||
                                   WrapTextWidth = 135,
 | 
			
		||||
                               }
 | 
			
		||||
                           }, amount.ToString(), _fonts.DottyFont.CreateFont(50), SixLabors.ImageSharp.Color.Red,
 | 
			
		||||
                           new PointF(129, 472)));
 | 
			
		||||
 | 
			
		||||
                       bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
 | 
			
		||||
                           {
 | 
			
		||||
                               TextOptions = new TextOptions()
 | 
			
		||||
                               {
 | 
			
		||||
                                   HorizontalAlignment = HorizontalAlignment.Center,
 | 
			
		||||
                                   VerticalAlignment = VerticalAlignment.Center,
 | 
			
		||||
                                   WrapTextWidth = 135,
 | 
			
		||||
                               }
 | 
			
		||||
                           }, ownedAmount.ToString(), _fonts.DottyFont.CreateFont(50), SixLabors.ImageSharp.Color.Red,
 | 
			
		||||
                           new PointF(325, 472)));
 | 
			
		||||
                       //sw.PrintLap("drew red text");
 | 
			
		||||
 | 
			
		||||
                       for (var i = 0; i < 3; i++)
 | 
			
		||||
                       {
 | 
			
		||||
                           using (var img = Image.Load(_images.SlotEmojis[numbers[i]]))
 | 
			
		||||
                           {
 | 
			
		||||
                               bgImage.Mutate(x => x.DrawImage(img, new Point(148 + 105 * i, 217), 1f));
 | 
			
		||||
                           }
 | 
			
		||||
                       }
 | 
			
		||||
 | 
			
		||||
                       var msg = GetText(strs.better_luck);
 | 
			
		||||
                       if (result.Multiplier > 0)
 | 
			
		||||
                       {
 | 
			
		||||
                           if (result.Multiplier == 1f)
 | 
			
		||||
                               msg = GetText(strs.slot_single(CurrencySign, 1));
 | 
			
		||||
                           else if (result.Multiplier == 4f)
 | 
			
		||||
                               msg = GetText(strs.slot_two(CurrencySign, 4));
 | 
			
		||||
                           else if (result.Multiplier == 10f)
 | 
			
		||||
                               msg = GetText(strs.slot_three(10));
 | 
			
		||||
                           else if (result.Multiplier == 30f)
 | 
			
		||||
                               msg = GetText(strs.slot_jackpot(30));
 | 
			
		||||
                       }
 | 
			
		||||
 | 
			
		||||
                       using (var imgStream = bgImage.ToStream())
 | 
			
		||||
                       {
 | 
			
		||||
                           await ctx.Channel.SendFileAsync(imgStream,
 | 
			
		||||
                               filename: "result.png",
 | 
			
		||||
                               text: Format.Bold(ctx.User.ToString()) + " " + msg).ConfigureAwait(false);
 | 
			
		||||
                       }
 | 
			
		||||
                   }
 | 
			
		||||
               }
 | 
			
		||||
               finally
 | 
			
		||||
               {
 | 
			
		||||
                   var _ = Task.Run(async () =>
 | 
			
		||||
                   {
 | 
			
		||||
                       await Task.Delay(1000).ConfigureAwait(false);
 | 
			
		||||
                       _runningUsers.Remove(ctx.User.Id);
 | 
			
		||||
                   });
 | 
			
		||||
               }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -115,9 +115,7 @@ namespace NadekoBot.Modules.Utility
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle(GetText(strs.quote_id($"#{data.Id}")))
 | 
			
		||||
                    .AddField(GetText(strs.trigger), data.Keyword)
 | 
			
		||||
                    .AddField(GetText(strs.response), data.Text.Length > 1000
 | 
			
		||||
                        ? GetText(strs.redacted_too_long)
 | 
			
		||||
                        : Format.Sanitize(data.Text))
 | 
			
		||||
                    .AddField(GetText(strs.response), Format.Sanitize(data.Text).Replace("](", "]\\("))
 | 
			
		||||
                    .WithFooter(GetText(strs.created_by($"{data.AuthorName} ({data.AuthorId})")))
 | 
			
		||||
                ).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,141 +0,0 @@
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
using StackExchange.Redis;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Common
 | 
			
		||||
{
 | 
			
		||||
    public class ImageLoader
 | 
			
		||||
    {
 | 
			
		||||
        private readonly HttpClient _http;
 | 
			
		||||
        private readonly ConnectionMultiplexer _con;
 | 
			
		||||
 | 
			
		||||
        public Func<string, RedisKey> GetKey { get; }
 | 
			
		||||
 | 
			
		||||
        private IDatabase _db => _con.GetDatabase();
 | 
			
		||||
 | 
			
		||||
        private readonly List<Task<KeyValuePair<RedisKey, RedisValue>>> uriTasks = new List<Task<KeyValuePair<RedisKey, RedisValue>>>();
 | 
			
		||||
 | 
			
		||||
        public ImageLoader(HttpClient http, ConnectionMultiplexer con, Func<string, RedisKey> getKey)
 | 
			
		||||
        {
 | 
			
		||||
            _http = http;
 | 
			
		||||
            _con = con;
 | 
			
		||||
            GetKey = getKey;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task<byte[]> GetImageData(Uri uri)
 | 
			
		||||
        {
 | 
			
		||||
            if (uri.IsFile)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var bytes = await File.ReadAllBytesAsync(uri.LocalPath);
 | 
			
		||||
                    return bytes;
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    Log.Warning(ex, "Failed reading image bytes");
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return await _http.GetByteArrayAsync(uri);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async Task HandleJArray(JArray arr, string key)
 | 
			
		||||
        {
 | 
			
		||||
            var tasks = arr.Where(x => x.Type == JTokenType.String)
 | 
			
		||||
                .Select(async x =>
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        return await GetImageData((Uri)x).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    catch
 | 
			
		||||
                    {
 | 
			
		||||
                        Log.Error("Error retreiving image for key {Key}: {Data}", key, x);
 | 
			
		||||
                        return null;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            byte[][] vals = Array.Empty<byte[]>();
 | 
			
		||||
            vals = await Task.WhenAll(tasks).ConfigureAwait(false);
 | 
			
		||||
            if (vals.Any(x => x is null))
 | 
			
		||||
                vals = vals.Where(x => x != null).ToArray();
 | 
			
		||||
 | 
			
		||||
            await _db.KeyDeleteAsync(GetKey(key)).ConfigureAwait(false);
 | 
			
		||||
            await _db.ListRightPushAsync(GetKey(key),
 | 
			
		||||
                vals.Where(x => x != null)
 | 
			
		||||
                    .Select(x => (RedisValue)x)
 | 
			
		||||
                    .ToArray()).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            if (arr.Count != vals.Length)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Information("{2}/{1} URIs for the key '{0}' have been loaded. Some of the supplied URIs are either unavailable or invalid.", key, arr.Count, vals.Count());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async Task<KeyValuePair<RedisKey, RedisValue>> HandleUri(Uri uri, string key)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                RedisValue data = await GetImageData(uri).ConfigureAwait(false);
 | 
			
		||||
                return new KeyValuePair<RedisKey, RedisValue>(GetKey(key), data);
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                Log.Information("Setting '{0}' image failed. The URI you provided is either unavailable or invalid.", key.ToLowerInvariant());
 | 
			
		||||
                return new KeyValuePair<RedisKey, RedisValue>("", "");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Task HandleJObject(JObject obj, string parent = "")
 | 
			
		||||
        {
 | 
			
		||||
            string GetParentString()
 | 
			
		||||
            {
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(parent))
 | 
			
		||||
                    return "";
 | 
			
		||||
                else
 | 
			
		||||
                    return parent + "_";
 | 
			
		||||
            }
 | 
			
		||||
            List<Task> tasks = new List<Task>();
 | 
			
		||||
            Task t;
 | 
			
		||||
            // go through all of the kvps in the object
 | 
			
		||||
            foreach (var kvp in obj)
 | 
			
		||||
            {
 | 
			
		||||
                // if it's a JArray, resole it using jarray method which will
 | 
			
		||||
                // return task<byte[][]> aka an array of all images' bytes
 | 
			
		||||
                if (kvp.Value.Type == JTokenType.Array)
 | 
			
		||||
                {
 | 
			
		||||
                    t = HandleJArray((JArray)kvp.Value, GetParentString() + kvp.Key);
 | 
			
		||||
                    tasks.Add(t);
 | 
			
		||||
                }
 | 
			
		||||
                else if (kvp.Value.Type == JTokenType.String)
 | 
			
		||||
                {
 | 
			
		||||
                    var uriTask = HandleUri((Uri)kvp.Value, GetParentString() + kvp.Key);
 | 
			
		||||
                    uriTasks.Add(uriTask);
 | 
			
		||||
                }
 | 
			
		||||
                else if (kvp.Value.Type == JTokenType.Object)
 | 
			
		||||
                {
 | 
			
		||||
                    t = HandleJObject((JObject)kvp.Value, GetParentString() + kvp.Key);
 | 
			
		||||
                    tasks.Add(t);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return Task.WhenAll(tasks);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task LoadAsync(JObject obj)
 | 
			
		||||
        {
 | 
			
		||||
            await HandleJObject(obj).ConfigureAwait(false);
 | 
			
		||||
            var results = await Task.WhenAll(uriTasks).ConfigureAwait(false);
 | 
			
		||||
            await _db.StringSetAsync(results.Where(x => x.Key != "").ToArray()).ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,52 +0,0 @@
 | 
			
		||||
using StackExchange.Redis;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services.Common
 | 
			
		||||
{
 | 
			
		||||
    public sealed class RedisImageArray : IReadOnlyList<byte[]>
 | 
			
		||||
    {
 | 
			
		||||
        public byte[] this[int index]
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                if (index < 0)
 | 
			
		||||
                    throw new ArgumentOutOfRangeException(nameof(index));
 | 
			
		||||
 | 
			
		||||
                return _con.GetDatabase().ListGetByIndex(_key, index);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int Count => _data.IsValueCreated
 | 
			
		||||
            ? _data.Value.Length
 | 
			
		||||
            : (int)_con.GetDatabase().ListLength(_key);
 | 
			
		||||
 | 
			
		||||
        private readonly ConnectionMultiplexer _con;
 | 
			
		||||
        private readonly string _key;
 | 
			
		||||
 | 
			
		||||
        private readonly Lazy<byte[][]> _data;
 | 
			
		||||
 | 
			
		||||
        public RedisImageArray(string key, ConnectionMultiplexer con)
 | 
			
		||||
        {
 | 
			
		||||
            _con = con;
 | 
			
		||||
            _key = key;
 | 
			
		||||
            _data = new Lazy<byte[][]>(() => _con.GetDatabase().ListRange(_key).Select(x => (byte[])x).ToArray(), true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerator<byte[]> GetEnumerator()
 | 
			
		||||
        {
 | 
			
		||||
            var actualData = _data.Value;
 | 
			
		||||
            for (int i = 0; i < actualData.Length; i++)
 | 
			
		||||
            {
 | 
			
		||||
                yield return actualData[i];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        IEnumerator IEnumerable.GetEnumerator()
 | 
			
		||||
        {
 | 
			
		||||
            return _data.Value.GetEnumerator();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
@@ -48,8 +49,11 @@ namespace NadekoBot.Services
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            RipFont = NotoSans.CreateFont(20, FontStyle.Bold);
 | 
			
		||||
            DottyFont = FallBackFonts.First(x => x.Name == "dotty");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public FontFamily DottyFont { get; }
 | 
			
		||||
 | 
			
		||||
        public FontFamily UniSans { get; }
 | 
			
		||||
        public FontFamily NotoSans { get; }
 | 
			
		||||
        //public FontFamily Emojis { get; }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,181 +0,0 @@
 | 
			
		||||
using NadekoBot.Common;
 | 
			
		||||
using NadekoBot.Services.Common;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using Newtonsoft.Json.Linq;
 | 
			
		||||
using StackExchange.Redis;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public sealed class RedisImagesCache : IImageCache, IReadyExecutor
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ConnectionMultiplexer _con;
 | 
			
		||||
        private readonly IBotCredentials _creds;
 | 
			
		||||
        private readonly HttpClient _http;
 | 
			
		||||
 | 
			
		||||
        private IDatabase _db => _con.GetDatabase();
 | 
			
		||||
 | 
			
		||||
        private const string _basePath = "data/";
 | 
			
		||||
        private const string _cardsPath = "data/images/cards";
 | 
			
		||||
 | 
			
		||||
        public ImageUrls ImageUrls { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public IReadOnlyList<byte[]> Heads => GetByteArrayData(ImageKey.Coins_Heads);
 | 
			
		||||
 | 
			
		||||
        public IReadOnlyList<byte[]> Tails => GetByteArrayData(ImageKey.Coins_Tails);
 | 
			
		||||
 | 
			
		||||
        public IReadOnlyList<byte[]> Dice => GetByteArrayData(ImageKey.Dice);
 | 
			
		||||
 | 
			
		||||
        public IReadOnlyList<byte[]> SlotEmojis => GetByteArrayData(ImageKey.Slots_Emojis);
 | 
			
		||||
 | 
			
		||||
        public IReadOnlyList<byte[]> SlotNumbers => GetByteArrayData(ImageKey.Slots_Numbers);
 | 
			
		||||
 | 
			
		||||
        public IReadOnlyList<byte[]> Currency => GetByteArrayData(ImageKey.Currency);
 | 
			
		||||
 | 
			
		||||
        public byte[] SlotBackground => GetByteData(ImageKey.Slots_Bg);
 | 
			
		||||
 | 
			
		||||
        public byte[] RategirlMatrix => GetByteData(ImageKey.Rategirl_Matrix);
 | 
			
		||||
 | 
			
		||||
        public byte[] RategirlDot => GetByteData(ImageKey.Rategirl_Dot);
 | 
			
		||||
 | 
			
		||||
        public byte[] XpBackground => GetByteData(ImageKey.Xp_Bg);
 | 
			
		||||
 | 
			
		||||
        public byte[] Rip => GetByteData(ImageKey.Rip_Bg);
 | 
			
		||||
 | 
			
		||||
        public byte[] RipOverlay => GetByteData(ImageKey.Rip_Overlay);
 | 
			
		||||
 | 
			
		||||
        public byte[] GetCard(string key)
 | 
			
		||||
        {
 | 
			
		||||
            return _con.GetDatabase().StringGet(GetKey("card_" + key));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public enum ImageKey
 | 
			
		||||
        {
 | 
			
		||||
            Coins_Heads,
 | 
			
		||||
            Coins_Tails,
 | 
			
		||||
            Dice,
 | 
			
		||||
            Slots_Bg,
 | 
			
		||||
            Slots_Numbers,
 | 
			
		||||
            Slots_Emojis,
 | 
			
		||||
            Rategirl_Matrix,
 | 
			
		||||
            Rategirl_Dot,
 | 
			
		||||
            Xp_Bg,
 | 
			
		||||
            Rip_Bg,
 | 
			
		||||
            Rip_Overlay,
 | 
			
		||||
            Currency,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task OnReadyAsync()
 | 
			
		||||
        {
 | 
			
		||||
            if (await AllKeysExist())
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            await Reload();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public RedisImagesCache(ConnectionMultiplexer con, IBotCredentials creds)
 | 
			
		||||
        {
 | 
			
		||||
            _con = con;
 | 
			
		||||
            _creds = creds;
 | 
			
		||||
            _http = new HttpClient();
 | 
			
		||||
            
 | 
			
		||||
            ImageUrls = JsonConvert.DeserializeObject<ImageUrls>(
 | 
			
		||||
                        File.ReadAllText(Path.Combine(_basePath, "images.json")));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<bool> AllKeysExist()
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var results = await Task.WhenAll(Enum.GetNames(typeof(ImageKey))
 | 
			
		||||
                    .Select(x => x.ToLowerInvariant())
 | 
			
		||||
                    .Select(x => _db.KeyExistsAsync(GetKey(x))))
 | 
			
		||||
                    .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                var cardsExist = await Task.WhenAll(GetAllCardNames()
 | 
			
		||||
                    .Select(x => "card_" + x)
 | 
			
		||||
                    .Select(x => _db.KeyExistsAsync(GetKey(x))))
 | 
			
		||||
                    .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                var num = results.Where(x => !x).Count();
 | 
			
		||||
 | 
			
		||||
                return results.All(x => x) && cardsExist.All(x => x);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning(ex, "Error checking for Image keys");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task Reload()
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var sw = Stopwatch.StartNew();
 | 
			
		||||
                var obj = JObject.Parse(
 | 
			
		||||
                    File.ReadAllText(Path.Combine(_basePath, "images.json")));
 | 
			
		||||
 | 
			
		||||
                ImageUrls = obj.ToObject<ImageUrls>();
 | 
			
		||||
                var t = new ImageLoader(_http, _con, GetKey)
 | 
			
		||||
                    .LoadAsync(obj);
 | 
			
		||||
 | 
			
		||||
                var loadCards = Task.Run(async () =>
 | 
			
		||||
                {
 | 
			
		||||
                    await _db.StringSetAsync(Directory.GetFiles(_cardsPath)
 | 
			
		||||
                        .ToDictionary(
 | 
			
		||||
                            x => GetKey("card_" + Path.GetFileNameWithoutExtension(x)),
 | 
			
		||||
                            x => (RedisValue)File.ReadAllBytes(x)) // loads them and creates <name, bytes> pairs to store in redis
 | 
			
		||||
                        .ToArray())
 | 
			
		||||
                        .ConfigureAwait(false);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                await Task.WhenAll(t, loadCards).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                sw.Stop();
 | 
			
		||||
                Log.Information($"Images reloaded in {sw.Elapsed.TotalSeconds:F2}s");
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Error(ex, "Error reloading image service");
 | 
			
		||||
                throw;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private IEnumerable<string> GetAllCardNames(bool showExtension = false)
 | 
			
		||||
        {
 | 
			
		||||
            return Directory.GetFiles(_cardsPath) // gets all cards from the cards folder
 | 
			
		||||
                           .Select(x => showExtension
 | 
			
		||||
                                ? Path.GetFileName(x)
 | 
			
		||||
                                : Path.GetFileNameWithoutExtension(x)); // gets their names
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public RedisKey GetKey(string key)
 | 
			
		||||
        {
 | 
			
		||||
            return $"{_creds.RedisKey()}_localimg_{key.ToLowerInvariant()}";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public byte[] GetByteData(string key)
 | 
			
		||||
        {
 | 
			
		||||
            return _db.StringGet(GetKey(key));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public byte[] GetByteData(ImageKey key) => GetByteData(key.ToString());
 | 
			
		||||
 | 
			
		||||
        public RedisImageArray GetByteArrayData(string key)
 | 
			
		||||
        {
 | 
			
		||||
            return new RedisImageArray(GetKey(key), _con);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public RedisImageArray GetByteArrayData(ImageKey key) => GetByteArrayData(key.ToString());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/NadekoBot/Services/Impl/RedisImageExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/NadekoBot/Services/Impl/RedisImageExtensions.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public static class RedisImageExtensions
 | 
			
		||||
    {
 | 
			
		||||
        private const string OldCdnUrl = "nadeko-pictures.nyc3.digitaloceanspaces.com";
 | 
			
		||||
        private const string NewCdnUrl = "cdn.nadeko.bot";
 | 
			
		||||
        
 | 
			
		||||
        public static Uri ToNewCdn(this Uri uri)
 | 
			
		||||
            => new(uri.ToString().Replace(OldCdnUrl, NewCdnUrl));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										312
									
								
								src/NadekoBot/Services/Impl/RedisImagesCache.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								src/NadekoBot/Services/Impl/RedisImagesCache.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,312 @@
 | 
			
		||||
using NadekoBot.Common;
 | 
			
		||||
using NadekoBot.Extensions;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using StackExchange.Redis;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
using NadekoBot.Common.Yml;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services
 | 
			
		||||
{
 | 
			
		||||
    public sealed class RedisImagesCache : IImageCache, IReadyExecutor
 | 
			
		||||
    {
 | 
			
		||||
        
 | 
			
		||||
        private readonly ConnectionMultiplexer _con;
 | 
			
		||||
        private readonly IBotCredentials _creds;
 | 
			
		||||
        private readonly HttpClient _http;
 | 
			
		||||
        private readonly string _imagesPath;
 | 
			
		||||
 | 
			
		||||
        private IDatabase _db => _con.GetDatabase();
 | 
			
		||||
 | 
			
		||||
        private const string _basePath = "data/";
 | 
			
		||||
        private const string _cardsPath = "data/images/cards";
 | 
			
		||||
 | 
			
		||||
        public ImageUrls ImageUrls { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public enum ImageKeys
 | 
			
		||||
        {
 | 
			
		||||
            CoinHeads,
 | 
			
		||||
            CoinTails,
 | 
			
		||||
            Dice,
 | 
			
		||||
            SlotBg,
 | 
			
		||||
            SlotEmojis,
 | 
			
		||||
            SlotNumbers,
 | 
			
		||||
            Currency,
 | 
			
		||||
            RategirlMatrix,
 | 
			
		||||
            RategirlDot,
 | 
			
		||||
            RipOverlay,
 | 
			
		||||
            RipBg,
 | 
			
		||||
            XpBg
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IReadOnlyList<byte[]> Heads 
 | 
			
		||||
            => GetByteArrayData(ImageKeys.CoinHeads);
 | 
			
		||||
 | 
			
		||||
        public IReadOnlyList<byte[]> Tails 
 | 
			
		||||
            => GetByteArrayData(ImageKeys.CoinTails);
 | 
			
		||||
 | 
			
		||||
        public IReadOnlyList<byte[]> Dice 
 | 
			
		||||
            => GetByteArrayData(ImageKeys.Dice);
 | 
			
		||||
 | 
			
		||||
        public IReadOnlyList<byte[]> SlotEmojis 
 | 
			
		||||
            => GetByteArrayData(ImageKeys.SlotEmojis);
 | 
			
		||||
 | 
			
		||||
        public IReadOnlyList<byte[]> SlotNumbers 
 | 
			
		||||
            => GetByteArrayData(ImageKeys.SlotNumbers);
 | 
			
		||||
 | 
			
		||||
        public IReadOnlyList<byte[]> Currency 
 | 
			
		||||
            => GetByteArrayData(ImageKeys.Currency);
 | 
			
		||||
 | 
			
		||||
        public byte[] SlotBackground 
 | 
			
		||||
            => GetByteData(ImageKeys.SlotBg);
 | 
			
		||||
 | 
			
		||||
        public byte[] RategirlMatrix 
 | 
			
		||||
            => GetByteData(ImageKeys.RategirlMatrix);
 | 
			
		||||
 | 
			
		||||
        public byte[] RategirlDot 
 | 
			
		||||
            => GetByteData(ImageKeys.RategirlDot);
 | 
			
		||||
 | 
			
		||||
        public byte[] XpBackground 
 | 
			
		||||
            => GetByteData(ImageKeys.XpBg);
 | 
			
		||||
 | 
			
		||||
        public byte[] Rip 
 | 
			
		||||
            => GetByteData(ImageKeys.RipBg);
 | 
			
		||||
 | 
			
		||||
        public byte[] RipOverlay 
 | 
			
		||||
            => GetByteData(ImageKeys.RipOverlay);
 | 
			
		||||
 | 
			
		||||
        public byte[] GetCard(string key)
 | 
			
		||||
        {
 | 
			
		||||
            // since cards are always local for now, don't cache them
 | 
			
		||||
            return File.ReadAllBytes(Path.Join(_cardsPath, key + ".jpg"));
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task OnReadyAsync()
 | 
			
		||||
        {
 | 
			
		||||
            if (await AllKeysExist())
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            await Reload();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public RedisImagesCache(ConnectionMultiplexer con, IBotCredentials creds)
 | 
			
		||||
        {
 | 
			
		||||
            _con = con;
 | 
			
		||||
            _creds = creds;
 | 
			
		||||
            _http = new HttpClient();
 | 
			
		||||
            _imagesPath = Path.Combine(_basePath, "images.yml");
 | 
			
		||||
 | 
			
		||||
            Migrate();
 | 
			
		||||
 | 
			
		||||
            ImageUrls = Yaml.Deserializer.Deserialize<ImageUrls>(File.ReadAllText(_imagesPath));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Migrate()
 | 
			
		||||
        {
 | 
			
		||||
            // migrate to yml
 | 
			
		||||
            if (File.Exists(Path.Combine(_basePath, "images.json")))
 | 
			
		||||
            {
 | 
			
		||||
                var oldFilePath = Path.Combine(_basePath, "images.json");
 | 
			
		||||
                var backupFilePath = Path.Combine(_basePath, "images.json.backup");
 | 
			
		||||
                
 | 
			
		||||
                var oldData = JsonConvert.DeserializeObject<OldImageUrls>(
 | 
			
		||||
                    File.ReadAllText(oldFilePath));
 | 
			
		||||
 | 
			
		||||
                if (oldData is not null)
 | 
			
		||||
                {
 | 
			
		||||
                    var newData = new ImageUrls()
 | 
			
		||||
                    {
 | 
			
		||||
                        Coins = new ImageUrls.CoinData()
 | 
			
		||||
                        {
 | 
			
		||||
                            Heads = oldData.Coins.Heads.Length == 1 && 
 | 
			
		||||
                                oldData.Coins.Heads[0].ToString() == "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/heads.png"
 | 
			
		||||
                            ? new[] { new Uri("https://cdn.nadeko.bot/coins/heads3.png") }
 | 
			
		||||
                            : oldData.Coins.Heads,
 | 
			
		||||
                            Tails = oldData.Coins.Tails.Length == 1 && 
 | 
			
		||||
                                    oldData.Coins.Tails[0].ToString() == "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/tails.png"
 | 
			
		||||
                                ? new[] { new Uri("https://cdn.nadeko.bot/coins/tails3.png") }
 | 
			
		||||
                                : oldData.Coins.Tails,
 | 
			
		||||
                        },
 | 
			
		||||
                        Dice = oldData.Dice.Map(x => x.ToNewCdn()),
 | 
			
		||||
                        Currency = oldData.Currency.Map(x => x.ToNewCdn()),
 | 
			
		||||
                        Rategirl = new ImageUrls.RategirlData()
 | 
			
		||||
                        {
 | 
			
		||||
                            Dot = oldData.Rategirl.Dot.ToNewCdn(),
 | 
			
		||||
                            Matrix = oldData.Rategirl.Matrix.ToNewCdn()
 | 
			
		||||
                        },
 | 
			
		||||
                        Rip = new ImageUrls.RipData()
 | 
			
		||||
                        {
 | 
			
		||||
                            Bg = oldData.Rip.Bg.ToNewCdn(),
 | 
			
		||||
                            Overlay = oldData.Rip.Overlay.ToNewCdn(),
 | 
			
		||||
                        },
 | 
			
		||||
                        Slots = new ImageUrls.SlotData()
 | 
			
		||||
                        {
 | 
			
		||||
                            Bg = new Uri("https://cdn.nadeko.bot/slots/slots_bg.png"),
 | 
			
		||||
                            Emojis = new[]
 | 
			
		||||
                            {
 | 
			
		||||
                                "https://cdn.nadeko.bot/slots/0.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/slots/1.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/slots/2.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/slots/3.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/slots/4.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/slots/5.png"
 | 
			
		||||
                            }.Map(x => new Uri(x)),
 | 
			
		||||
                            Numbers = new[]
 | 
			
		||||
                            {
 | 
			
		||||
                                "https://cdn.nadeko.bot/other/slots/numbers/0.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/other/slots/numbers/1.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/other/slots/numbers/2.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/other/slots/numbers/3.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/other/slots/numbers/4.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/other/slots/numbers/5.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/other/slots/numbers/6.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/other/slots/numbers/7.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/other/slots/numbers/8.png",
 | 
			
		||||
                                "https://cdn.nadeko.bot/other/slots/numbers/9.png"
 | 
			
		||||
                            }.Map(x => new Uri(x)),
 | 
			
		||||
                        },
 | 
			
		||||
                        Xp = new ImageUrls.XpData()
 | 
			
		||||
                        {
 | 
			
		||||
                            Bg = oldData.Xp.Bg.ToNewCdn(),
 | 
			
		||||
                        },
 | 
			
		||||
                        Version = 2,
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    File.Move(oldFilePath, backupFilePath, true);
 | 
			
		||||
                    File.WriteAllText(_imagesPath, Yaml.Serializer.Serialize(newData));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task Reload()
 | 
			
		||||
        {
 | 
			
		||||
            ImageUrls = Yaml.Deserializer.Deserialize<ImageUrls>(await File.ReadAllTextAsync(_imagesPath));
 | 
			
		||||
            foreach (var key in GetAllKeys())
 | 
			
		||||
            {
 | 
			
		||||
                switch (key)
 | 
			
		||||
                {
 | 
			
		||||
                    case ImageKeys.CoinHeads:
 | 
			
		||||
                        await Load(key, ImageUrls.Coins.Heads);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ImageKeys.CoinTails:
 | 
			
		||||
                        await Load(key, ImageUrls.Coins.Tails);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ImageKeys.Dice:
 | 
			
		||||
                        await Load(key, ImageUrls.Dice);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ImageKeys.SlotBg:
 | 
			
		||||
                        await Load(key, ImageUrls.Slots.Bg);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ImageKeys.SlotEmojis:
 | 
			
		||||
                        await Load(key, ImageUrls.Slots.Emojis);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ImageKeys.SlotNumbers:
 | 
			
		||||
                        await Load(key, ImageUrls.Slots.Numbers);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ImageKeys.Currency:
 | 
			
		||||
                        await Load(key, ImageUrls.Currency);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ImageKeys.RategirlMatrix:
 | 
			
		||||
                        await Load(key, ImageUrls.Rategirl.Matrix);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ImageKeys.RategirlDot:
 | 
			
		||||
                        await Load(key, ImageUrls.Rategirl.Dot);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ImageKeys.RipOverlay:
 | 
			
		||||
                        await Load(key, ImageUrls.Rip.Overlay);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ImageKeys.RipBg:
 | 
			
		||||
                        await Load(key, ImageUrls.Rip.Bg);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ImageKeys.XpBg:
 | 
			
		||||
                        await Load(key, ImageUrls.Xp.Bg);
 | 
			
		||||
                        break;
 | 
			
		||||
                    default:
 | 
			
		||||
                        throw new ArgumentOutOfRangeException();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task Load(ImageKeys key, Uri uri)
 | 
			
		||||
        {
 | 
			
		||||
            var data = await GetImageData(uri);
 | 
			
		||||
            if (data is null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            await _db.StringSetAsync(GetRedisKey(key), data);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task Load(ImageKeys key, Uri[] uris)
 | 
			
		||||
        {
 | 
			
		||||
            await _db.KeyDeleteAsync(GetRedisKey(key));
 | 
			
		||||
            var imageData = await Task.WhenAll(uris.Select(GetImageData));
 | 
			
		||||
            var vals = imageData
 | 
			
		||||
                .Where(x => x is not null)
 | 
			
		||||
                .Select(x => (RedisValue)x)
 | 
			
		||||
                .ToArray();
 | 
			
		||||
 | 
			
		||||
            await _db.ListRightPushAsync(GetRedisKey(key), vals);
 | 
			
		||||
            
 | 
			
		||||
            if (uris.Length != vals.Length)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Information("{Loaded}/{Max} URIs for the key '{ImageKey}' have been loaded.\n" +
 | 
			
		||||
                                "Some of the supplied URIs are either unavailable or invalid.", 
 | 
			
		||||
                    vals.Length, uris.Length, key);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task<byte[]> GetImageData(Uri uri)
 | 
			
		||||
        {
 | 
			
		||||
            if (uri.IsFile)
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var bytes = await File.ReadAllBytesAsync(uri.LocalPath);
 | 
			
		||||
                    return bytes;
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    Log.Warning(ex, "Failed reading image bytes from uri: {Uri}", uri.ToString());
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                return await _http.GetByteArrayAsync(uri);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning(ex, "Image url you provided is not a valid image: {Uri}", uri.ToString());
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        private async Task<bool> AllKeysExist()
 | 
			
		||||
        {
 | 
			
		||||
            var tasks = await Task.WhenAll(GetAllKeys()
 | 
			
		||||
                .Select(x => _db.KeyExistsAsync(GetRedisKey(x))));
 | 
			
		||||
 | 
			
		||||
            return tasks.All(exist => exist);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private IEnumerable<ImageKeys> GetAllKeys() =>
 | 
			
		||||
            Enum.GetValues<ImageKeys>();
 | 
			
		||||
 | 
			
		||||
        private byte[][] GetByteArrayData(ImageKeys key)
 | 
			
		||||
            => _db.ListRange(GetRedisKey(key)).Map(x => (byte[])x);
 | 
			
		||||
 | 
			
		||||
        private byte[] GetByteData(ImageKeys key)
 | 
			
		||||
            => _db.StringGet(GetRedisKey(key));
 | 
			
		||||
 | 
			
		||||
        private RedisKey GetRedisKey(ImageKeys key) 
 | 
			
		||||
            => _creds.RedisKey() + "_image_" + key;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/NadekoBot/data/fonts/dotty.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/NadekoBot/data/fonts/dotty.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -1,63 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "Version": 3,
 | 
			
		||||
  "Coins": {
 | 
			
		||||
    "Heads": [
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/heads.png"
 | 
			
		||||
    ],
 | 
			
		||||
    "Tails": [
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/tails.png"
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  "Currency": [
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/currency/0.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/currency/1.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/currency/2.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/currency/3.png"
 | 
			
		||||
  ],
 | 
			
		||||
  "Dice": [
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/0.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/1.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/2.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/3.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/4.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/5.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/6.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/7.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/8.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/9.png"
 | 
			
		||||
  ],
 | 
			
		||||
  "Rategirl": {
 | 
			
		||||
    "Matrix": "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rategirl/matrix.png",
 | 
			
		||||
    "Dot": "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rategirl/dot.png"
 | 
			
		||||
  },
 | 
			
		||||
  "Xp": {
 | 
			
		||||
    "Bg": "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/xp/bg.png"
 | 
			
		||||
  },
 | 
			
		||||
  "Rip": {
 | 
			
		||||
    "Bg": "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rip/rip.png",
 | 
			
		||||
    "Overlay": "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rip/overlay.png"
 | 
			
		||||
  },
 | 
			
		||||
  "Slots": {
 | 
			
		||||
    "Emojis": [
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/emojis/0.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/emojis/1.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/emojis/2.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/emojis/3.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/emojis/4.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/emojis/5.png"
 | 
			
		||||
    ],
 | 
			
		||||
    "Numbers": [
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/0.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/1.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/2.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/3.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/4.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/5.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/6.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/7.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/8.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/9.png"
 | 
			
		||||
    ],
 | 
			
		||||
    "Bg": "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/bg.png"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								src/NadekoBot/data/images.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/NadekoBot/data/images.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
# DO NOT CHANGE
 | 
			
		||||
version: 2
 | 
			
		||||
coins:
 | 
			
		||||
  heads:
 | 
			
		||||
    - https://cdn.nadeko.bot/coins/heads3.png
 | 
			
		||||
  tails:
 | 
			
		||||
    - https://cdn.nadeko.bot/coins/tails3.png
 | 
			
		||||
currency:
 | 
			
		||||
  - https://cdn.nadeko.bot/other/currency/0.jpg
 | 
			
		||||
  - https://cdn.nadeko.bot/other/currency/1.jpg
 | 
			
		||||
  - https://cdn.nadeko.bot/other/currency/2.jpg
 | 
			
		||||
dice:
 | 
			
		||||
  - https://cdn.nadeko.bot/other/dice/0.png
 | 
			
		||||
  - https://cdn.nadeko.bot/other/dice/1.png
 | 
			
		||||
  - https://cdn.nadeko.bot/other/dice/2.png
 | 
			
		||||
  - https://cdn.nadeko.bot/other/dice/3.png
 | 
			
		||||
  - https://cdn.nadeko.bot/other/dice/4.png
 | 
			
		||||
  - https://cdn.nadeko.bot/other/dice/5.png
 | 
			
		||||
  - https://cdn.nadeko.bot/other/dice/6.png
 | 
			
		||||
  - https://cdn.nadeko.bot/other/dice/7.png
 | 
			
		||||
  - https://cdn.nadeko.bot/other/dice/8.png
 | 
			
		||||
  - https://cdn.nadeko.bot/other/dice/9.png
 | 
			
		||||
rategirl:
 | 
			
		||||
  matrix: https://cdn.nadeko.bot/other/rategirl/matrix.png
 | 
			
		||||
  dot: https://cdn.nadeko.bot/other/rategirl/dot.png
 | 
			
		||||
xp:
 | 
			
		||||
  bg: https://cdn.nadeko.bot/other/xp/bg.png
 | 
			
		||||
rip:
 | 
			
		||||
  bg: https://cdn.nadeko.bot/other/rip/rip.png
 | 
			
		||||
  overlay: https://cdn.nadeko.bot/other/rip/overlay.png
 | 
			
		||||
slots:
 | 
			
		||||
  emojis:
 | 
			
		||||
    - https://cdn.nadeko.bot/slots/0.png
 | 
			
		||||
    - https://cdn.nadeko.bot/slots/1.png
 | 
			
		||||
    - https://cdn.nadeko.bot/slots/2.png
 | 
			
		||||
    - https://cdn.nadeko.bot/slots/3.png
 | 
			
		||||
    - https://cdn.nadeko.bot/slots/4.png
 | 
			
		||||
    - https://cdn.nadeko.bot/slots/5.png
 | 
			
		||||
  numbers:
 | 
			
		||||
    - https://cdn.nadeko.bot/other/slots/numbers/0.png
 | 
			
		||||
    - https://cdn.nadeko.bot/other/slots/numbers/1.png
 | 
			
		||||
    - https://cdn.nadeko.bot/other/slots/numbers/2.png
 | 
			
		||||
    - https://cdn.nadeko.bot/other/slots/numbers/3.png
 | 
			
		||||
    - https://cdn.nadeko.bot/other/slots/numbers/4.png
 | 
			
		||||
    - https://cdn.nadeko.bot/other/slots/numbers/5.png
 | 
			
		||||
    - https://cdn.nadeko.bot/other/slots/numbers/6.png
 | 
			
		||||
    - https://cdn.nadeko.bot/other/slots/numbers/7.png
 | 
			
		||||
    - https://cdn.nadeko.bot/other/slots/numbers/8.png
 | 
			
		||||
    - https://cdn.nadeko.bot/other/slots/numbers/9.png
 | 
			
		||||
  bg: https://cdn.nadeko.bot/slots/slots_bg.png
 | 
			
		||||
@@ -1,62 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "Version": 1,
 | 
			
		||||
  "Coins": {
 | 
			
		||||
    "Heads": [
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/heads.png"
 | 
			
		||||
    ],
 | 
			
		||||
    "Tails": [
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/coins/tails.png"
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  "Currency": [
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/currency/0.jpg",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/currency/1.jpg",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/currency/2.jpg"
 | 
			
		||||
  ],
 | 
			
		||||
  "Dice": [
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/0.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/1.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/2.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/3.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/4.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/5.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/6.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/7.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/8.png",
 | 
			
		||||
    "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/dice/9.png"
 | 
			
		||||
  ],
 | 
			
		||||
  "Rategirl": {
 | 
			
		||||
    "Matrix": "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rategirl/matrix.png",
 | 
			
		||||
    "Dot": "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rategirl/dot.png"
 | 
			
		||||
  },
 | 
			
		||||
  "Xp": {
 | 
			
		||||
    "Bg": "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/xp/bg.png"
 | 
			
		||||
  },
 | 
			
		||||
  "Rip": {
 | 
			
		||||
    "Bg": "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rip/rip.png",
 | 
			
		||||
    "Overlay": "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/rip/overlay.png"
 | 
			
		||||
  },
 | 
			
		||||
  "Slots": {
 | 
			
		||||
    "Emojis": [
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/emojis/0.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/emojis/1.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/emojis/2.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/emojis/3.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/emojis/4.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/emojis/5.png"
 | 
			
		||||
    ],
 | 
			
		||||
    "Numbers": [
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/0.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/1.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/2.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/3.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/4.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/5.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/6.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/7.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/8.png",
 | 
			
		||||
      "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/numbers/9.png"
 | 
			
		||||
    ],
 | 
			
		||||
    "Bg": "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/slots/bg.png"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user