mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Merge branch 'v3-dev' into 'v3'
Slots redesign nad images moved to images.yml See merge request Kwoth/nadekobot!181
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;
|
||||
|
||||
@@ -83,6 +85,41 @@ WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentU
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
public decimal Cash { get; set; }
|
||||
|
@@ -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
|
||||
@@ -142,79 +150,102 @@ namespace NadekoBot.Modules.Gambling
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (!await _cs.RemoveAsync(ctx.User, "Slot Machine", amount, false, gamble: true).ConfigureAwait(false))
|
||||
await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
|
||||
var result = await _service.SlotAsync(ctx.User.Id, amount);
|
||||
|
||||
if (result.Error != GamblingError.None)
|
||||
{
|
||||
if (result.Error == GamblingError.NotEnough)
|
||||
{
|
||||
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;
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
Interlocked.Add(ref _totalBet, amount);
|
||||
Interlocked.Add(ref _totalPaidOut, result.Won);
|
||||
|
||||
long ownedAmount;
|
||||
using (var uow = _db.GetDbContext())
|
||||
{
|
||||
using (var randomImage = Image.Load(_images.SlotEmojis[numbers[i]]))
|
||||
{
|
||||
bgImage.Mutate(x => x.DrawImage(randomImage, new Point(95 + 142 * i, 330), new GraphicsOptions()));
|
||||
}
|
||||
ownedAmount = uow.Set<DiscordUser>()
|
||||
.FirstOrDefault(x => x.UserId == ctx.User.Id)
|
||||
?.CurrencyAmount ?? 0;
|
||||
}
|
||||
|
||||
var won = amount * result.Multiplier;
|
||||
var printWon = won;
|
||||
var n = 0;
|
||||
do
|
||||
using (var bgImage = Image.Load<Rgba32>(_images.SlotBackground, out var format))
|
||||
{
|
||||
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);
|
||||
var numbers = new int[3];
|
||||
result.Rolls.CopyTo(numbers, 0);
|
||||
|
||||
var printAmount = amount;
|
||||
n = 0;
|
||||
do
|
||||
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
|
||||
{
|
||||
var digit = (int)(printAmount % 10);
|
||||
using (var img = Image.Load(_images.SlotNumbers[digit]))
|
||||
TextOptions = new TextOptions()
|
||||
{
|
||||
bgImage.Mutate(x => x.DrawImage(img, new Point(395 - n * 16, 462), new GraphicsOptions()));
|
||||
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));
|
||||
}
|
||||
}
|
||||
n++;
|
||||
} while ((printAmount /= 10) != 0);
|
||||
|
||||
var msg = GetText(strs.better_luck);
|
||||
if (result.Multiplier != 0)
|
||||
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)
|
||||
if (result.Multiplier == 1f)
|
||||
msg = GetText(strs.slot_single(CurrencySign, 1));
|
||||
else if (result.Multiplier == 4)
|
||||
else if (result.Multiplier == 4f)
|
||||
msg = GetText(strs.slot_two(CurrencySign, 4));
|
||||
else if (result.Multiplier == 10)
|
||||
else if (result.Multiplier == 10f)
|
||||
msg = GetText(strs.slot_three(10));
|
||||
else if (result.Multiplier == 30)
|
||||
else if (result.Multiplier == 30f)
|
||||
msg = GetText(strs.slot_jackpot(30));
|
||||
}
|
||||
|
||||
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);
|
||||
await ctx.Channel.SendFileAsync(imgStream,
|
||||
filename: "result.png",
|
||||
text: Format.Bold(ctx.User.ToString()) + " " + msg).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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