Compare commits

...

56 Commits
4.3.1 ... 4.3.5

Author SHA1 Message Date
Kwoth
7c8756096d Upped version to 4.3.5 2022-08-17 22:10:07 +02:00
Kwoth
7d9e456fe5 Fixed .xpshopbuy having mixed up arguments for frames and bgs 2022-08-17 22:02:36 +02:00
Kwoth
df1a775fec Revert "Reverted club xp updates, and non-opt in xp system as it's causing db locking"
This reverts commit 6c169e057b.
2022-08-14 23:35:13 +02:00
Kwoth
6c169e057b Reverted club xp updates, and non-opt in xp system as it's causing db locking 2022-08-14 22:45:14 +02:00
Kwoth
b164da38e3 Added .roleinfo commands, added the ability to show colors other than ok error and pending to IEmbedBuilder 2022-08-11 17:04:51 +02:00
Kwoth
dc229ea2b3 Added Timeout as a punishment to warnpunish and anti* commands 2022-08-11 16:19:02 +02:00
Kwoth
b853495d65 Added .emojiremove command 2022-08-11 16:05:00 +02:00
Kwoth
0ff02cede9 Added .emojiremove 2022-08-11 15:56:07 +02:00
Kwoth
f7e0e635e6 Added .threadcreate and .threaddelete commadns which create and delete public threads 2022-08-11 15:42:37 +02:00
Kwoth
a065189023 Fixed .bank take, added .bank seize alias. Added .bank award 2022-08-11 15:14:08 +02:00
Kwoth
1affeb0683 Update responses.de-DE.json (POEditor.com) 2022-08-11 12:43:54 +00:00
Kwoth
6b3c9f01ca Update responses.fr-FR.json (POEditor.com) 2022-08-11 12:43:53 +00:00
Kwoth
e9eb6ff2ad Added '.bank take' owner only command 2022-08-11 14:39:44 +02:00
Kwoth
0170536d1c Guild XP is no longer opt-in. Only global xp 2022-08-11 13:54:20 +02:00
Kwoth
c65e769128 Removed Badges from README.md 2022-08-11 11:00:22 +00:00
Kwoth
998779203a Fixed users not gaining club xp 2022-08-10 12:31:52 +02:00
Kwoth
e0e4d697c3 Added pull again button to slot, fixed a double 'you don't have enough' message 2022-08-09 23:15:27 +02:00
Kwoth
e9c7293014 Running .timely command too early will now show a pending color, not an error color 2022-08-09 22:43:22 +02:00
Kwoth
cf876a4148 removed obsolete xp database columns 2022-08-08 17:36:39 +02:00
Kwoth
6b9a858f28 Added Use button on sucessful .xpshopbuy or when a user already owns the item 2022-08-08 16:17:30 +02:00
Kwoth
38e3badb87 Greatly unboilerplated and simplified nadeko interaction button construction 2022-08-07 17:00:36 +02:00
Kwoth
13d2fbd560 Fixed VoiceXP bug, closes #374. Upped version to 4.3.4, Updated changelog 2022-08-07 13:08:07 +02:00
Kwoth
8d3f2f186a Merge branch 'v4' of https://gitlab.com/kwoth/nadekobot into v4 2022-08-06 17:57:32 +02:00
Kwoth
9572b9dc43 Updated changelog.md 2022-08-06 17:56:53 +02:00
Kwoth
ca32086089 Updated changelog.md 2022-08-06 17:56:14 +02:00
Kwoth
57f839dbcd Added client id as a potential fix for VoiceXp 'bug'. The solution may be to use different redis instances for each bot, or to switch from botCache: from 'redis' to 'memory' in creds.yml 2022-08-06 17:47:24 +02:00
Kwoth
71d6eeb9dd Possibly fixed #375 trivia bug 2022-08-06 17:28:31 +02:00
Kwoth
8c51cf8537 Added .xpshopbuy and .xpshopuse convenience commands 2022-08-06 13:26:41 +02:00
Kwoth
b683026cf3 Owner should also be able to buy items from the xp shop 2022-08-01 21:14:28 +02:00
Kwoth
bca2bc5af1 Fixed awarded amount position on the xp card 2022-08-01 20:59:35 +02:00
Kwoth
b385a83bdd Updated position of the username and club in the .xp image 2022-08-01 17:46:41 +02:00
Kwoth
3bf0286c81 Feature limit should have IsPatronLimit enabled 2022-08-01 16:08:23 +02:00
Kwoth
98272f66e7 Made improvements to the XP card text visibility with other backgrounds 2022-08-01 12:01:01 +02:00
Kwoth
cf3788c6ea Added an optional preview url to the xp shop items 2022-08-01 10:53:59 +02:00
Kwoth
4b3fc53cb6 Updated changelog 2022-07-31 21:31:29 +02:00
Kwoth
4e17dca856 Merge branch 'v4' of https://gitlab.com/kwoth/nadekobot into v4 2022-07-31 21:26:46 +02:00
Kwoth
82d89148f3 Added betroll bettest, fixed a bug which caused betroll to have very low payout 2022-07-31 21:26:34 +02:00
Kwoth
cc4c09b4d7 Merge branch 'hokutochen-v4-patch-85503' into 'v4'
updated redis requirement for release

See merge request Kwoth/nadekobot!262
2022-07-30 10:37:29 +00:00
Kwoth
616f01f8b2 Make sure broken youtube-dl didn't cache invalid data, closes #373 2022-07-30 10:29:33 +02:00
Hokuto Chen
56f89a02bc updated redis requirement for release 2022-07-30 07:32:30 +00:00
Kwoth
48ce988d20 Fixed a mistake in CHANGELOG.md 2022-07-29 19:53:45 +02:00
Kwoth
119b1cdec2 Changelog fixed 2022-07-29 18:38:54 +02:00
Kwoth
43fa5a22f5 Updated changelog.md, Upped version to 4.3.2 2022-07-28 14:33:32 +02:00
Kwoth
3c715a29ca .bank withdraw <expression> will now correctly use bank amount for calculations. Fixed .br giving double win amounts 2022-07-28 12:41:38 +02:00
Kwoth
31e1cbb19f Fixed an unreported bug with postgresql reminder loop, ref #370 2022-07-28 09:21:19 +02:00
Kwoth
8e464e9f09 Fixed mysql and postgresql reactionrole migration, closes #370 2022-07-28 09:10:21 +02:00
Kwoth
a190a3d933 Fixed Reaction Roles not working properly with animated emojis, closes #369 2022-07-28 08:30:28 +02:00
Kwoth
bedba98130 Fixed medusa Reply*LocalizedAsync not working with placeholders 2022-07-28 07:39:46 +02:00
Kwoth
d69f8435f6 Fixed .slot alignment, closes #372 2022-07-28 05:47:40 +02:00
Kwoth
8440b34338 Forgot to update the version to 4.3.1 2022-07-27 11:21:08 +02:00
Kwoth
a2715740c1 Merge branch 'v4' of https://gitlab.com/kwoth/nadekobot into v4 2022-07-27 11:20:06 +02:00
Kwoth
36b7fd2352 Possible fix for .reroa working even for non-patrons 2022-07-27 11:18:35 +02:00
Kwoth
6563cb507a Merge branch 'feature/v4-creds.yml-location' into 'v4'
NEW: add NadekoBot__creds env to specify alternative creds.yml

See merge request Kwoth/nadekobot!257
2022-07-27 05:00:28 +00:00
Veovis
cc6128997e NEW: add NadekoBot__creds env to specify alternative creds.yml 2022-07-27 05:00:27 +00:00
Kwoth
e942da4470 Merge branch 'hokutochen-v4-patch-80656' into 'v4'
updated notepad ++ descrption

See merge request Kwoth/nadekobot!261
2022-07-27 04:59:33 +00:00
Hokuto Chen
ab2bcdf00d updated notepad ++ descrption 2022-07-27 04:59:33 +00:00
86 changed files with 12980 additions and 847 deletions

View File

@@ -2,6 +2,74 @@
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
## Unreleased
## [4.3.5] - 17.08.2022
### Added
- Added a 'Use' button when a user already owns an item
- Added a 'Pull Again' button to slots
- Added `.roleinfo` command
- Added `.emojiremove` command
- Added `.threadcreate` and `.threaddelete` commands
- Added `.bank seize` / `.bank award` owner only commands
### Changed
- Running a .timely command early now shows a pending color
- .xp system is once again no longer opt in for servers
- It's still opt-in for global and requires users to run .xp at least once in order to start gaining global xp
### Fixed
- Fixed users not getting club xp
## [4.3.4] - 07.08.2022
### Fixed
- Fixed users getting XP out of nowhere while voice xp is enabled
## [4.3.3] - 06.08.2022
### Added
- Added `betroll` option to `.bettest` command
- Added `.xpshopbuy` and `.xpshopuse` convenience commands
- Added an optional preview url to teh xp shop item config model which will be shown instead of the real Url
### Changed
- Updated position of Username and Club name on the .xp card
- Improved text visibility on the .xp card
### Fixed
- Possibly fixed .trivia not stopping bug
- Fixed very low payout rate on `.betroll`
- Fixed an issue with youtube song resolver which caused invalid data to be cached
- Added client id to the cache key as a potential fix for VoiceXp 'bug'. The solution may be to use different redis instances for each bot, or to switch from botCache: from 'redis' to 'memory' in creds.yml
- Bot owner should now be able to buy items from the xpshop when patron requirement is set
- Fixed youtube-dl caching invalid data. Please use yt-dlp instead
## [4.3.2] - 28.07.2022
### Fixed
- Fixed Reaction Roles not working properly with animated emojis
- Fixed `.slot` alignment
- Fixed `mysql` and `postgresql` reactionrole migration
- Fixed repeat loop with `postgresql` db provider
- Fixed `.bank withdraw <expression>` will now correctly use bank amount for calculation
- [dev] Fixed medusa Reply*LocalizedAsync not working with placeholders
## [4.3.1] - 27.07.2022
### Changed
- Check for updates will run once per hour as it was supposed to
## [4.3.0] - 27.07.2022
### Added
@@ -15,9 +83,10 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
- They payouts are very good, but seven always loses
- Added `.lula` command. Plays the same as `.wof` but looks much nicer, and is easily customizable from gambling.yml without any changes to the sourcecode needed.
- Added `.repeatskip` command which makes the next repeat trigger not post anything
- Added `.imageonly` which will make the bot only allow link posts in the channel. Exclusive with `.imageonly`
- Added `.linkonly` which will make the bot only allow link posts in the channel. Exclusive with `.imageonly`
- Added release notifications. Bot owners will now receive new release notifications in dms if they have `checkForUpdates` set to `true` in data/bot.yml
- You can also configure it via `.conf bot checkforupdates <true/false>`
- You can also configure it via `.conf bot checkfor
- updates <true/false>`
- Added `.xpshop` which lets bot owners add xp backgrounds and xp frames for sale by configuring `data/xp.yml`
- You can also toggle xpshop feature via `.conf xp shop.is_enabled`

View File

@@ -42,6 +42,7 @@ COPY docker-entrypoint.sh /usr/local/sbin
ENV shard_id=0
ENV total_shards=1
ENV NadekoBot__creds=/app/data/creds.yml
VOLUME [ "/app/data" ]
ENTRYPOINT [ "/usr/local/sbin/docker-entrypoint.sh" ]

View File

@@ -1,8 +1,3 @@
[![Discord](https://discordapp.com/api/guilds/117523346618318850/widget.png)](https://discord.gg/nadekobot)
[![Documentation Status](https://readthedocs.org/projects/nadekobot/badge/?version=latest)](http://nadekobot.readthedocs.io/en/v4/?badge=v4)
[![Discord Bots](https://discordbots.org/api/widget/status/116275390695079945.svg)](https://top.gg/bot/116275390695079945)
[![nadeko0](https://cdn.nadeko.bot/tutorial/docs-top.png)](https://nadeko.bot/)
[![nadeko1](https://cdn.nadeko.bot/tutorial/docs-mid.png)](https://invite.nadeko.bot/)

View File

@@ -13,7 +13,15 @@ do
fi
done
# fix folder permissions
# creds.yml migration
if [ -f /app/creds.yml ]; then
echo "Default location for creds.yml is now /app/data/creds.yml."
echo "Please move your creds.yml and update your docker-compose.yml accordingly."
export Nadeko_creds=/app/creds.yml
fi
# ensure nadeko can write on /app/data
chown -R nadeko:nadeko "$data"
# drop to regular user and launch command

View File

@@ -69,7 +69,7 @@ Open Terminal (if you're on an installation with a window manager) and navigate
###### Prerequisites
1. Nadeko requires redis to function
1. (Optional) Installing Redis
- ubuntu installation command: `sudo apt-get install redis-server`
2. Playing music requires `ffmpeg`, `libopus`, `libsodium` and `youtube-dl` (which in turn requires python3)
- ubuntu installation command: `sudo apt-get install ffmpeg libopus0 opus-tools libopus-dev libsodium-dev -y`

View File

@@ -19,7 +19,7 @@
**Optional**
- [Notepad++] (makes it easier to edit your credentials)
- [Visual Studio Code](https://code.visualstudio.com/Download) (Highly suggested if you plan on editing files)
- [Visual C++ 2010 (x86)] and [Visual C++ 2017 (x64)] (both are required if you want Nadeko to play music - restart Windows after installation)
#### Setup

View File

@@ -26,20 +26,6 @@ public class NadekoRandom : Random
return Math.Abs(BitConverter.ToInt32(bytes, 0)) % maxValue;
}
public byte Next(byte minValue, byte maxValue)
{
if (minValue > maxValue)
throw new ArgumentOutOfRangeException(nameof(maxValue));
if (minValue == maxValue)
return minValue;
var bytes = new byte[1];
_rng.GetBytes(bytes);
return (byte)((bytes[0] % (maxValue - minValue)) + minValue);
}
public override int Next(int minValue, int maxValue)
{
if (minValue > maxValue)

View File

@@ -0,0 +1,19 @@
namespace Nadeko.Common;
public readonly struct ShmartBankAmount
{
public long Amount { get; }
public ShmartBankAmount(long amount)
{
Amount = amount;
}
public static implicit operator ShmartBankAmount(long num)
=> new(num);
public static implicit operator long(ShmartBankAmount num)
=> num.Amount;
public static implicit operator ShmartBankAmount(int num)
=> new(num);
}

View File

@@ -3,12 +3,10 @@
public readonly struct ShmartNumber : IEquatable<ShmartNumber>
{
public long Value { get; }
public string? Input { get; }
public ShmartNumber(long val, string? input = null)
public ShmartNumber(long val)
{
Value = val;
Input = input;
}
public static implicit operator ShmartNumber(long num)

View File

@@ -12,7 +12,7 @@ public sealed class BetflipGame
public BetflipResult Flip(byte guess, decimal amount)
{
var side = _rng.Next(0, 2);
var side = (byte)_rng.Next(0, 2);
if (side == guess)
{
return new BetflipResult()

View File

@@ -13,7 +13,7 @@ public sealed class BetrollGame
public BetrollResult Roll(decimal amount = 0)
{
var roll = _rng.Next(0, 101);
var roll = _rng.Next(1, 101);
for (var i = 0; i < _thresholdPairs.Length; i++)
{

View File

@@ -1,5 +1,8 @@
namespace Nadeko.Econ.Gambling;
//here is a payout chart
//https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg
//thanks to judge for helping me with this
public class SlotGame
{
private static readonly NadekoRandom _rng = new NadekoRandom();
@@ -8,9 +11,9 @@ public class SlotGame
{
var rolls = new[]
{
_rng.Next(0, 6),
_rng.Next(0, 6),
_rng.Next(0, 6)
(byte)_rng.Next(0, 6),
(byte)_rng.Next(0, 6),
(byte)_rng.Next(0, 6)
};
ref var a = ref rolls[0];

View File

@@ -47,7 +47,7 @@ public static class MedusaExtensions
=> ctx.Message.AddReactionAsync(new Emoji("🤔"));
public static Task<IUserMessage> ErrorLocalizedAsync(this AnyContext ctx, string key, params object[]? args)
=> ctx.SendErrorAsync(ctx.GetText(key));
=> ctx.SendErrorAsync(ctx.GetText(key, args));
public static Task<IUserMessage> PendingLocalizedAsync(this AnyContext ctx, string key, params object[]? args)
=> ctx.SendPendingAsync(ctx.GetText(key, args));
@@ -56,11 +56,11 @@ public static class MedusaExtensions
=> ctx.SendConfirmAsync(ctx.GetText(key, args));
public static Task<IUserMessage> ReplyErrorLocalizedAsync(this AnyContext ctx, string key, params object[]? args)
=> ctx.SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {ctx.GetText(key)}");
=> ctx.SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {ctx.GetText(key, args)}");
public static Task<IUserMessage> ReplyPendingLocalizedAsync(this AnyContext ctx, string key, params object[]? args)
=> ctx.SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {ctx.GetText(key)}");
=> ctx.SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {ctx.GetText(key, args)}");
public static Task<IUserMessage> ReplyConfirmLocalizedAsync(this AnyContext ctx, string key, params object[]? args)
=> ctx.SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {ctx.GetText(key)}");
=> ctx.SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {ctx.GetText(key, args)}");
}

View File

@@ -10,6 +10,7 @@ public interface IEmbedBuilder
IEmbedBuilder WithFooter(string text, string? iconUrl = null);
IEmbedBuilder WithAuthor(string name, string? iconUrl = null, string? url = null);
IEmbedBuilder WithColor(EmbedColor color);
IEmbedBuilder WithDiscordColor(Color color);
Embed Build();
IEmbedBuilder WithUrl(string url);
IEmbedBuilder WithImageUrl(string url);

View File

@@ -35,13 +35,13 @@ public sealed class Bot
private readonly IBotCredsProvider _credsProvider;
// private readonly InteractionService _interactionService;
public Bot(int shardId, int? totalShards)
public Bot(int shardId, int? totalShards, string credPath = null)
{
if (shardId < 0)
throw new ArgumentOutOfRangeException(nameof(shardId));
ShardId = shardId;
_credsProvider = new BotCredsProvider(totalShards);
_credsProvider = new BotCredsProvider(totalShards, credPath);
_creds = _credsProvider.GetCreds();
_db = new(_credsProvider);

View File

@@ -0,0 +1,8 @@
namespace NadekoBot;
public interface INadekoInteractionService
{
public NadekoInteraction Create<T>(
ulong userId,
SimpleInteraction<T> inter);
}

View File

@@ -1,29 +0,0 @@
namespace NadekoBot;
public sealed class NadekoButtonActionInteraction : NadekoButtonOwnInteraction
{
private readonly NadekoInteractionData _data;
private readonly Func<SocketMessageComponent, Task> _action;
public NadekoButtonActionInteraction(
DiscordSocketClient client,
ulong authorId,
NadekoInteractionData data,
Func<SocketMessageComponent, Task> action
)
: base(client, authorId)
{
_data = data;
_action = action;
}
protected override string Name
=> _data.CustomId;
protected override IEmote Emote
=> _data.Emote;
protected override string? Text
=> _data.Text;
public override Task ExecuteOnActionAsync(SocketMessageComponent smc)
=> _action(smc);
}

View File

@@ -1,25 +1,30 @@
namespace NadekoBot;
public abstract class NadekoButtonInteraction
public sealed class NadekoInteraction
{
// improvements:
// - state in OnAction
// - configurable delay
// -
protected abstract string Name { get; }
protected abstract IEmote Emote { get; }
protected virtual string? Text { get; } = null;
private readonly ulong _authorId;
private readonly ButtonBuilder _button;
private readonly Func<SocketMessageComponent, Task> _onClick;
private readonly bool _onlyAuthor;
public DiscordSocketClient Client { get; }
protected readonly TaskCompletionSource<bool> _interactionCompletedSource;
private readonly TaskCompletionSource<bool> _interactionCompletedSource;
protected IUserMessage message = null!;
private IUserMessage message = null!;
protected NadekoButtonInteraction(DiscordSocketClient client)
public NadekoInteraction(DiscordSocketClient client,
ulong authorId,
ButtonBuilder button,
Func<SocketMessageComponent, Task> onClick,
bool onlyAuthor)
{
Client = client;
_authorId = authorId;
_button = button;
_onClick = onClick;
_onlyAuthor = onlyAuthor;
_interactionCompletedSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
Client = client;
}
public async Task RunAsync(IUserMessage msg)
@@ -27,13 +32,12 @@ public abstract class NadekoButtonInteraction
message = msg;
Client.InteractionCreated += OnInteraction;
await Task.WhenAny(Task.Delay(10_000), _interactionCompletedSource.Task);
await Task.WhenAny(Task.Delay(15_000), _interactionCompletedSource.Task);
Client.InteractionCreated -= OnInteraction;
await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
}
protected abstract ValueTask<bool> Validate(SocketMessageComponent smc);
private async Task OnInteraction(SocketInteraction arg)
{
if (arg is not SocketMessageComponent smc)
@@ -42,14 +46,11 @@ public abstract class NadekoButtonInteraction
if (smc.Message.Id != message.Id)
return;
if (smc.Data.CustomId != Name)
if (_onlyAuthor && smc.User.Id != _authorId)
return;
if (!await Validate(smc))
{
await smc.DeferAsync();
if (smc.Data.CustomId != _button.CustomId)
return;
}
_ = Task.Run(async () =>
{
@@ -66,18 +67,14 @@ public abstract class NadekoButtonInteraction
}
public virtual MessageComponent CreateComponent()
public MessageComponent CreateComponent()
{
var comp = new ComponentBuilder()
.WithButton(GetButtonBuilder());
.WithButton(_button);
return comp.Build();
}
public ButtonBuilder GetButtonBuilder()
=> new ButtonBuilder(style: ButtonStyle.Secondary, emote: Emote, customId: Name, label: Text);
public abstract Task ExecuteOnActionAsync(SocketMessageComponent smc);
public Task ExecuteOnActionAsync(SocketMessageComponent smc)
=> _onClick(smc);
}
// this is all so wrong ...

View File

@@ -1,43 +0,0 @@
// namespace NadekoBot;
//
// public class NadekoButtonInteractionArray : NadekoButtonInteraction
// {
// private readonly ButtonBuilder[] _bbs;
// private readonly NadekoButtonInteraction[] _inters;
//
// public NadekoButtonInteractionArray(params NadekoButtonInteraction[] inters)
// : base(inters[0].Client)
// {
// _inters = inters;
// _bbs = inters.Map(x => x.GetButtonBuilder());
// }
//
// protected override string Name
// => throw new NotSupportedException();
// protected override IEmote Emote
// => throw new NotSupportedException();
//
// protected override ValueTask<bool> Validate(SocketMessageComponent smc)
// => new(true);
//
// public override Task ExecuteOnActionAsync(SocketMessageComponent smc)
// {
// for (var i = 0; i < _bbs.Length; i++)
// {
// if (_bbs[i].CustomId == smc.Data.CustomId)
// return _inters[i].ExecuteOnActionAsync(smc);
// }
//
// return Task.CompletedTask;
// }
//
// public override MessageComponent CreateComponent()
// {
// var comp = new ComponentBuilder();
//
// foreach (var bb in _bbs)
// comp.WithButton(bb);
//
// return comp.Build();
// }
// }

View File

@@ -1,42 +0,0 @@
namespace NadekoBot;
/// <summary>
/// Builder class for NadekoInteractions
/// </summary>
public class NadekoInteractionBuilder
{
private NadekoInteractionData? iData;
private Func<SocketMessageComponent, Task>? action;
// private bool isOwn;
public NadekoInteractionBuilder WithData<T>(in T data)
where T : NadekoInteractionData
{
iData = data;
return this;
}
// public NadekoOwnInteractionBuiler WithIsOwn(bool isOwn = true)
// {
// this.isOwn = isOwn;
// return this;
// }
public NadekoInteractionBuilder WithAction(in Func<SocketMessageComponent, Task> fn)
{
this.action = fn;
return this;
}
public NadekoButtonActionInteraction Build(DiscordSocketClient client, ulong userId)
{
if (iData is null)
throw new InvalidOperationException("You have to specify the data before building the interaction");
if (action is null)
throw new InvalidOperationException("You have to specify the action before building the interaction");
return new(client, userId, iData, action);
}
}

View File

@@ -0,0 +1,20 @@
namespace NadekoBot;
public class NadekoInteractionService : INadekoInteractionService, INService
{
private readonly DiscordSocketClient _client;
public NadekoInteractionService(DiscordSocketClient client)
{
_client = client;
}
public NadekoInteraction Create<T>(
ulong userId,
SimpleInteraction<T> inter)
=> new NadekoInteraction(_client,
userId,
inter.Button,
inter.TriggerAsync,
onlyAuthor: true);
}

View File

@@ -1,15 +0,0 @@
namespace NadekoBot;
/// <summary>
/// Interaction which only the author can use
/// </summary>
public abstract class NadekoButtonOwnInteraction : NadekoButtonInteraction
{
protected readonly ulong _authorId;
protected NadekoButtonOwnInteraction(DiscordSocketClient client, ulong authorId) : base(client)
=> _authorId = authorId;
protected override ValueTask<bool> Validate(SocketMessageComponent smc)
=> new(smc.User.Id == _authorId);
}

View File

@@ -1,26 +0,0 @@
namespace NadekoBot.Common;
public abstract class NInteraction
{
private readonly DiscordSocketClient _client;
private readonly ulong _userId;
private readonly Func<SocketMessageComponent, Task> _action;
protected abstract NadekoInteractionData Data { get; }
public NInteraction(
DiscordSocketClient client,
ulong userId,
Func<SocketMessageComponent, Task> action)
{
_client = client;
_userId = userId;
_action = action;
}
public NadekoButtonInteraction GetInteraction()
=> new NadekoInteractionBuilder()
.WithData(Data)
.WithAction(_action)
.Build(_client, _userId);
}

View File

@@ -18,6 +18,7 @@ public abstract class NadekoModule : ModuleBase
public CommandHandler _cmdHandler { get; set; }
public ILocalization _localization { get; set; }
public IEmbedBuilderService _eb { get; set; }
public INadekoInteractionService _inter { get; set; }
protected string prefix
=> _cmdHandler.GetPrefix(ctx.Guild);
@@ -36,7 +37,7 @@ public abstract class NadekoModule : ModuleBase
string error,
string url = null,
string footer = null,
NadekoButtonInteraction inter = null)
NadekoInteraction inter = null)
=> ctx.Channel.SendErrorAsync(_eb, title, error, url, footer);
public Task<IUserMessage> SendConfirmAsync(
@@ -47,32 +48,32 @@ public abstract class NadekoModule : ModuleBase
=> ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer);
//
public Task<IUserMessage> SendErrorAsync(string text, NadekoButtonInteraction inter = null)
public Task<IUserMessage> SendErrorAsync(string text, NadekoInteraction inter = null)
=> ctx.Channel.SendAsync(_eb, text, MessageType.Error, inter);
public Task<IUserMessage> SendConfirmAsync(string text, NadekoButtonInteraction inter = null)
public Task<IUserMessage> SendConfirmAsync(string text, NadekoInteraction inter = null)
=> ctx.Channel.SendAsync(_eb, text, MessageType.Ok, inter);
public Task<IUserMessage> SendPendingAsync(string text, NadekoButtonInteraction inter = null)
public Task<IUserMessage> SendPendingAsync(string text, NadekoInteraction inter = null)
=> ctx.Channel.SendAsync(_eb, text, MessageType.Pending, inter);
// localized normal
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str, NadekoInteraction inter = null)
=> SendErrorAsync(GetText(str), inter);
public Task<IUserMessage> PendingLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
public Task<IUserMessage> PendingLocalizedAsync(LocStr str, NadekoInteraction inter = null)
=> SendPendingAsync(GetText(str), inter);
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str, NadekoInteraction inter = null)
=> SendConfirmAsync(GetText(str), inter);
// localized replies
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str, NadekoInteraction inter = null)
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}", inter);
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str, NadekoInteraction inter = null)
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}", inter);
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str, NadekoButtonInteraction inter = null)
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str, NadekoInteraction inter = null)
=> SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}", inter);
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)

View File

@@ -0,0 +1,94 @@
using System.Text.RegularExpressions;
using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Services;
using NCalc;
using OneOf;
namespace NadekoBot.Common.TypeReaders;
public class BaseShmartInputAmountReader
{
private static readonly Regex _percentRegex = new(@"^((?<num>100|\d{1,2})%)$", RegexOptions.Compiled);
protected readonly DbService _db;
protected readonly GamblingConfigService _gambling;
public BaseShmartInputAmountReader(DbService db, GamblingConfigService gambling)
{
_db = db;
_gambling = gambling;
}
public async ValueTask<OneOf<long, OneOf.Types.Error<string>>> ReadAsync(ICommandContext context, string input)
{
var i = input.Trim().ToUpperInvariant();
i = i.Replace("K", "000");
//can't add m because it will conflict with max atm
if (await TryHandlePercentage(context, i) is long num)
{
return num;
}
try
{
var expr = new Expression(i, EvaluateOptions.IgnoreCase);
expr.EvaluateParameter += (str, ev) => EvaluateParam(str, ev, context).GetAwaiter().GetResult();
return (long)decimal.Parse(expr.Evaluate().ToString()!);
}
catch (Exception)
{
return new OneOf.Types.Error<string>($"Invalid input: {input}");
}
}
private async Task EvaluateParam(string name, ParameterArgs args, ICommandContext ctx)
{
switch (name.ToUpperInvariant())
{
case "PI":
args.Result = Math.PI;
break;
case "E":
args.Result = Math.E;
break;
case "ALL":
case "ALLIN":
args.Result = await Cur(ctx);
break;
case "HALF":
args.Result = await Cur(ctx) / 2;
break;
case "MAX":
args.Result = await Max(ctx);
break;
}
}
protected virtual async Task<long> Cur(ICommandContext ctx)
{
await using var uow = _db.GetDbContext();
return await uow.DiscordUser.GetUserCurrencyAsync(ctx.User.Id);
}
protected virtual async Task<long> Max(ICommandContext ctx)
{
var settings = _gambling.Data;
var max = settings.MaxBet;
return max == 0 ? await Cur(ctx) : max;
}
private async Task<long?> TryHandlePercentage(ICommandContext ctx, string input)
{
var m = _percentRegex.Match(input);
if (m.Captures.Count == 0)
return null;
if (!long.TryParse(m.Groups["num"].ToString(), out var percent))
return null;
return (long)(await Cur(ctx) * (percent / 100.0f));
}
}

View File

@@ -0,0 +1,32 @@
#nullable disable
using NadekoBot.Modules.Gambling.Bank;
using NadekoBot.Modules.Gambling.Services;
namespace NadekoBot.Common.TypeReaders;
public sealed class ShmartBankAmountTypeReader : NadekoTypeReader<ShmartBankAmount>
{
private readonly IBankService _bank;
private readonly ShmartBankInputAmountReader _tr;
public ShmartBankAmountTypeReader(IBankService bank, DbService db, GamblingConfigService gambling)
{
_bank = bank;
_tr = new ShmartBankInputAmountReader(bank, db, gambling);
}
public override async ValueTask<TypeReaderResult<ShmartBankAmount>> ReadAsync(ICommandContext ctx, string input)
{
if (string.IsNullOrWhiteSpace(input))
return TypeReaderResult.FromError<ShmartBankAmount>(CommandError.ParseFailed, "Input is empty.");
var result = await _tr.ReadAsync(ctx, input);
if (result.TryPickT0(out var val, out var err))
{
return TypeReaderResult.FromSuccess<ShmartBankAmount>(new(val));
}
return TypeReaderResult.FromError<ShmartBankAmount>(CommandError.Unsuccessful, err.Value);
}
}

View File

@@ -0,0 +1,21 @@
using NadekoBot.Modules.Gambling.Bank;
using NadekoBot.Modules.Gambling.Services;
namespace NadekoBot.Common.TypeReaders;
public sealed class ShmartBankInputAmountReader : BaseShmartInputAmountReader
{
private readonly IBankService _bank;
public ShmartBankInputAmountReader(IBankService bank, DbService db, GamblingConfigService gambling)
: base(db, gambling)
{
_bank = bank;
}
protected override Task<long> Cur(ICommandContext ctx)
=> _bank.GetBalanceAsync(ctx.User.Id);
protected override Task<long> Max(ICommandContext ctx)
=> Cur(ctx);
}

View File

@@ -0,0 +1,29 @@
#nullable disable
using NadekoBot.Modules.Gambling.Services;
namespace NadekoBot.Common.TypeReaders;
public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
{
private readonly BaseShmartInputAmountReader _tr;
public ShmartNumberTypeReader(DbService db, GamblingConfigService gambling)
{
_tr = new BaseShmartInputAmountReader(db, gambling);
}
public override async ValueTask<TypeReaderResult<ShmartNumber>> ReadAsync(ICommandContext ctx, string input)
{
if (string.IsNullOrWhiteSpace(input))
return TypeReaderResult.FromError<ShmartNumber>(CommandError.ParseFailed, "Input is empty.");
var result = await _tr.ReadAsync(ctx, input);
if (result.TryPickT0(out var val, out var err))
{
return TypeReaderResult.FromSuccess<ShmartNumber>(new(val));
}
return TypeReaderResult.FromError<ShmartNumber>(CommandError.Unsuccessful, err.Value);
}
}

View File

@@ -1,100 +0,0 @@
#nullable disable
using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Services;
using NCalc;
using System.Text.RegularExpressions;
using Nadeko.Common;
namespace NadekoBot.Common.TypeReaders;
public sealed class ShmartNumberTypeReader : NadekoTypeReader<ShmartNumber>
{
private static readonly Regex _percentRegex = new(@"^((?<num>100|\d{1,2})%)$", RegexOptions.Compiled);
private readonly DbService _db;
private readonly GamblingConfigService _gambling;
public ShmartNumberTypeReader(DbService db, GamblingConfigService gambling)
{
_db = db;
_gambling = gambling;
}
public override ValueTask<TypeReaderResult<ShmartNumber>> ReadAsync(ICommandContext context, string input)
{
if (string.IsNullOrWhiteSpace(input))
return new(TypeReaderResult.FromError<ShmartNumber>(CommandError.ParseFailed, "Input is empty."));
var i = input.Trim().ToUpperInvariant();
i = i.Replace("K", "000");
//can't add m because it will conflict with max atm
if (TryHandlePercentage(context, i, out var num))
return new(TypeReaderResult.FromSuccess(new ShmartNumber(num, i)));
try
{
var expr = new Expression(i, EvaluateOptions.IgnoreCase);
expr.EvaluateParameter += (str, ev) => EvaluateParam(str, ev, context);
var lon = (long)decimal.Parse(expr.Evaluate().ToString());
return new(TypeReaderResult.FromSuccess(new ShmartNumber(lon, input)));
}
catch (Exception)
{
return ValueTask.FromResult(
TypeReaderResult.FromError<ShmartNumber>(CommandError.ParseFailed, $"Invalid input: {input}"));
}
}
private void EvaluateParam(string name, ParameterArgs args, ICommandContext ctx)
{
switch (name.ToUpperInvariant())
{
case "PI":
args.Result = Math.PI;
break;
case "E":
args.Result = Math.E;
break;
case "ALL":
case "ALLIN":
args.Result = Cur(ctx);
break;
case "HALF":
args.Result = Cur(ctx) / 2;
break;
case "MAX":
args.Result = Max(ctx);
break;
}
}
private long Cur(ICommandContext ctx)
{
using var uow = _db.GetDbContext();
return uow.DiscordUser.GetUserCurrency(ctx.User.Id);
}
private long Max(ICommandContext ctx)
{
var settings = _gambling.Data;
var max = settings.MaxBet;
return max == 0 ? Cur(ctx) : max;
}
private bool TryHandlePercentage(ICommandContext ctx, string input, out long num)
{
num = 0;
var m = _percentRegex.Match(input);
if (m.Captures.Count != 0)
{
if (!long.TryParse(m.Groups["num"].ToString(), out var percent))
return false;
num = (long)(Cur(ctx) * (percent / 100.0f));
return true;
}
return false;
}
}

View File

@@ -108,8 +108,8 @@ public static class DiscordUserExtensions
.Take(count)
.ToList();
public static long GetUserCurrency(this DbSet<DiscordUser> users, ulong userId)
=> users.AsNoTracking().FirstOrDefault(x => x.UserId == userId)?.CurrencyAmount ?? 0;
public static async Task<long> GetUserCurrencyAsync(this DbSet<DiscordUser> users, ulong userId)
=> (await users.FirstOrDefaultAsyncLinqToDB(x => x.UserId == userId))?.CurrencyAmount ?? 0;
public static void RemoveFromMany(this DbSet<DiscordUser> users, IEnumerable<ulong> ids)
{

View File

@@ -49,7 +49,8 @@ public enum PunishmentAction
ChatMute,
VoiceMute,
AddRole,
Warn
Warn,
TimeOut
}
public class AntiSpamIgnore : DbEntity

View File

@@ -17,8 +17,6 @@ public class DiscordUser : DbEntity
public bool IsClubAdmin { get; set; }
public long TotalXp { get; set; }
public DateTime LastLevelUp { get; set; } = DateTime.UtcNow;
public DateTime LastXpGain { get; set; } = DateTime.MinValue;
public XpNotificationLocation NotifyOnLevelUp { get; set; }
public long CurrencyAmount { get; set; }

View File

@@ -8,7 +8,6 @@ public class UserXpStats : DbEntity
public long Xp { get; set; }
public long AwardedXp { get; set; }
public XpNotificationLocation NotifyOnLevelUp { get; set; }
public DateTime LastLevelUp { get; set; } = DateTime.UtcNow;
}
public enum XpNotificationLocation { None, Dm, Channel }

View File

@@ -13,6 +13,6 @@ public class XpShopOwnedItem : DbEntity
public enum XpShopItemType
{
Background,
Frame,
Background = 0,
Frame = 1,
}

View File

@@ -10,10 +10,6 @@ public sealed class MysqlContext : NadekoContext
protected override string CurrencyTransactionOtherIdDefaultValue
=> "NULL";
protected override string DiscordUserLastXpGainDefaultValue
=> "(UTC_TIMESTAMP - INTERVAL 1 year)";
protected override string LastLevelUpDefaultValue
=> "(UTC_TIMESTAMP)";
public MysqlContext(string connStr = "Server=localhost", string version = "8.0")
{

View File

@@ -65,8 +65,6 @@ public abstract class NadekoContext : DbContext
#region Mandatory Provider-Specific Values
protected abstract string CurrencyTransactionOtherIdDefaultValue { get; }
protected abstract string DiscordUserLastXpGainDefaultValue { get; }
protected abstract string LastLevelUpDefaultValue { get; }
#endregion
@@ -166,12 +164,6 @@ public abstract class NadekoContext : DbContext
du.Property(x => x.NotifyOnLevelUp)
.HasDefaultValue(XpNotificationLocation.None);
du.Property(x => x.LastXpGain)
.HasDefaultValueSql(DiscordUserLastXpGainDefaultValue);
du.Property(x => x.LastLevelUp)
.HasDefaultValueSql(LastLevelUpDefaultValue);
du.Property(x => x.TotalXp)
.HasDefaultValue(0);
@@ -213,9 +205,6 @@ public abstract class NadekoContext : DbContext
})
.IsUnique();
xps.Property(x => x.LastLevelUp)
.HasDefaultValueSql(LastLevelUpDefaultValue);
xps.HasIndex(x => x.UserId);
xps.HasIndex(x => x.GuildId);
xps.HasIndex(x => x.Xp);

View File

@@ -8,10 +8,6 @@ public sealed class PostgreSqlContext : NadekoContext
protected override string CurrencyTransactionOtherIdDefaultValue
=> "NULL";
protected override string DiscordUserLastXpGainDefaultValue
=> "timezone('utc', now()) - interval '-1 year'";
protected override string LastLevelUpDefaultValue
=> "timezone('utc', now())";
public PostgreSqlContext(string connStr = "Host=localhost")
{
@@ -20,6 +16,8 @@ public sealed class PostgreSqlContext : NadekoContext
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
base.OnConfiguring(optionsBuilder);
optionsBuilder
.UseLowerCaseNamingConvention()

View File

@@ -9,10 +9,6 @@ public sealed class SqliteContext : NadekoContext
protected override string CurrencyTransactionOtherIdDefaultValue
=> "NULL";
protected override string DiscordUserLastXpGainDefaultValue
=> "datetime('now', '-1 years')";
protected override string LastLevelUpDefaultValue
=> "datetime('now')";
public SqliteContext(string connectionString = "Data Source=data/NadekoBot.db", int commandTimeout = 60)
{

View File

@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations;
@@ -6,11 +7,36 @@ public static class MigrationQueries
{
public static void MigrateRero(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
@"insert or ignore into reactionroles(guildid, channelid, messageid, emote, roleid, 'group', levelreq, dateadded)
if (migrationBuilder.IsMySql())
{
migrationBuilder.Sql(
@"INSERT IGNORE into reactionroles(guildid, channelid, messageid, emote, roleid, `group`, levelreq, dateadded)
select guildid, channelid, messageid, emotename, roleid, exclusive, 0, reactionrolemessage.dateadded
from reactionrole
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;");
}
else if (migrationBuilder.IsSqlite())
{
migrationBuilder.Sql(
@"insert or ignore into reactionroles(guildid, channelid, messageid, emote, roleid, 'group', levelreq, dateadded)
select guildid, channelid, messageid, emotename, roleid, exclusive, 0, reactionrolemessage.dateadded
from reactionrole
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;");
}
else if (migrationBuilder.IsNpgsql())
{
migrationBuilder.Sql(@"insert into reactionroles(guildid, channelid, messageid, emote, roleid, ""group"", levelreq, dateadded)
select guildid, channelid, messageid, emotename, roleid, exclusive::int, 0, reactionrolemessage.dateadded
from reactionrole
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id
ON CONFLICT DO NOTHING;");
}
else
{
throw new NotSupportedException("This database provider doesn't have an implementation for MigrateRero");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class removeobsoletexpcolumns : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "lastlevelup",
table: "userxpstats");
migrationBuilder.DropColumn(
name: "lastlevelup",
table: "discorduser");
migrationBuilder.DropColumn(
name: "lastxpgain",
table: "discorduser");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "lastlevelup",
table: "userxpstats",
type: "datetime(6)",
nullable: false,
defaultValueSql: "(UTC_TIMESTAMP)");
migrationBuilder.AddColumn<DateTime>(
name: "lastlevelup",
table: "discorduser",
type: "datetime(6)",
nullable: false,
defaultValueSql: "(UTC_TIMESTAMP)");
migrationBuilder.AddColumn<DateTime>(
name: "lastxpgain",
table: "discorduser",
type: "datetime(6)",
nullable: false,
defaultValueSql: "(UTC_TIMESTAMP - INTERVAL 1 year)");
}
}
}

View File

@@ -168,18 +168,6 @@ namespace NadekoBot.Migrations.Mysql
.HasDefaultValue(false)
.HasColumnName("isclubadmin");
b.Property<DateTime>("LastLevelUp")
.ValueGeneratedOnAdd()
.HasColumnType("datetime(6)")
.HasColumnName("lastlevelup")
.HasDefaultValueSql("(UTC_TIMESTAMP)");
b.Property<DateTime>("LastXpGain")
.ValueGeneratedOnAdd()
.HasColumnType("datetime(6)")
.HasColumnName("lastxpgain")
.HasDefaultValueSql("(UTC_TIMESTAMP - INTERVAL 1 year)");
b.Property<int>("NotifyOnLevelUp")
.ValueGeneratedOnAdd()
.HasColumnType("int")
@@ -2565,12 +2553,6 @@ namespace NadekoBot.Migrations.Mysql
.HasColumnType("bigint unsigned")
.HasColumnName("guildid");
b.Property<DateTime>("LastLevelUp")
.ValueGeneratedOnAdd()
.HasColumnType("datetime(6)")
.HasColumnName("lastlevelup")
.HasDefaultValueSql("(UTC_TIMESTAMP)");
b.Property<int>("NotifyOnLevelUp")
.HasColumnType("int")
.HasColumnName("notifyonlevelup");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("balance");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("UserId")
@@ -101,7 +101,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<string>("Description")
@@ -163,7 +163,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("currencyamount");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<string>("Discriminator")
@@ -176,18 +176,6 @@ namespace NadekoBot.Migrations.PostgreSql
.HasDefaultValue(false)
.HasColumnName("isclubadmin");
b.Property<DateTime>("LastLevelUp")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("lastlevelup")
.HasDefaultValueSql("timezone('utc', now())");
b.Property<DateTime>("LastXpGain")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("lastxpgain")
.HasDefaultValueSql("timezone('utc', now()) - interval '-1 year'");
b.Property<int>("NotifyOnLevelUp")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
@@ -243,7 +231,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -322,7 +310,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("amountcents");
b.Property<DateTime>("LastCharge")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("lastcharge");
b.Property<string>("UniquePlatformUserId")
@@ -330,7 +318,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("uniqueplatformuserid");
b.Property<DateTime>("ValidThru")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("validthru");
b.HasKey("UserId")
@@ -357,7 +345,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("MessageId")
@@ -388,7 +376,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<bool>("IsUsing")
@@ -471,7 +459,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("action");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int>("GuildConfigId")
@@ -518,7 +506,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.HasKey("Id")
@@ -544,7 +532,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("action");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int>("GuildConfigId")
@@ -595,7 +583,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("commandtext");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal?>("GuildId")
@@ -642,7 +630,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("GuildId")
@@ -676,7 +664,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<string>("Source")
@@ -710,7 +698,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("GuildId")
@@ -741,7 +729,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("ItemId")
@@ -768,7 +756,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -806,7 +794,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("commandname");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -840,7 +828,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("amount");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<string>("Extra")
@@ -890,7 +878,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -924,7 +912,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("command");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal?>("GuildId")
@@ -955,7 +943,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("ItemId")
@@ -993,7 +981,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int>("GuildConfigId")
@@ -1028,7 +1016,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -1054,7 +1042,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -1088,7 +1076,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -1118,7 +1106,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -1148,7 +1136,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -1174,7 +1162,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int>("GuildConfigId")
@@ -1253,7 +1241,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("cleverbotenabled");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<bool>("DeleteMessageOnCommand")
@@ -1381,7 +1369,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int>("ItemType")
@@ -1420,7 +1408,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("LogSettingId")
@@ -1450,7 +1438,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("GuildId")
@@ -1493,7 +1481,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelupdatedid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("GuildId")
@@ -1629,7 +1617,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("authorid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<string>("Name")
@@ -1652,7 +1640,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -1694,7 +1682,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("containsanywhere");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<bool>("DmResponse")
@@ -1733,7 +1721,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("GuildId")
@@ -1763,7 +1751,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -1825,7 +1813,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("GuildId")
@@ -1867,7 +1855,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("MusicPlaylistId")
@@ -1917,7 +1905,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("GuildId")
@@ -1948,7 +1936,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int>("Index")
@@ -1982,7 +1970,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("PollId")
@@ -2025,7 +2013,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("authorname");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("GuildId")
@@ -2068,7 +2056,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<string>("Emote")
@@ -2123,7 +2111,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<bool>("IsPrivate")
@@ -2143,7 +2131,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("userid");
b.Property<DateTime>("When")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("when");
b.HasKey("Id")
@@ -2169,7 +2157,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("channelid");
b.Property<DateTime>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("GuildId")
@@ -2216,11 +2204,11 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("amountrewardedthismonth");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<DateTime>("LastReward")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("lastreward");
b.Property<string>("PlatformUserId")
@@ -2251,7 +2239,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<string>("Status")
@@ -2278,7 +2266,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int>("Group")
@@ -2323,7 +2311,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("authorid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -2373,7 +2361,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("ShopEntryId")
@@ -2403,7 +2391,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -2433,7 +2421,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -2463,7 +2451,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("StreamRoleSettingsId")
@@ -2501,7 +2489,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("addroleid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<bool>("Enabled")
@@ -2540,7 +2528,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("StreamRoleSettingsId")
@@ -2574,7 +2562,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -2582,7 +2570,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("guildconfigid");
b.Property<DateTime>("UnbanAt")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("unbanat");
b.Property<decimal>("UserId")
@@ -2608,7 +2596,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -2616,7 +2604,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("guildconfigid");
b.Property<DateTime>("UnmuteAt")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("unmuteat");
b.Property<decimal>("UserId")
@@ -2642,7 +2630,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -2654,7 +2642,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("roleid");
b.Property<DateTime>("UnbanAt")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("unbanat");
b.Property<decimal>("UserId")
@@ -2684,19 +2672,13 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("awardedxp");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.Property<DateTime>("LastLevelUp")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("lastlevelup")
.HasDefaultValueSql("timezone('utc', now())");
b.Property<int>("NotifyOnLevelUp")
.HasColumnType("integer")
.HasColumnName("notifyonlevelup");
@@ -2741,7 +2723,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -2783,7 +2765,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("claimerid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<long>("Price")
@@ -2823,7 +2805,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<string>("ItemEmoji")
@@ -2857,7 +2839,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("NewId")
@@ -2901,7 +2883,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<bool>("Forgiven")
@@ -2963,7 +2945,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("count");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int?>("GuildConfigId")
@@ -3005,7 +2987,7 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnName("amount");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int>("Level")
@@ -3035,7 +3017,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int>("Level")
@@ -3074,7 +3056,7 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int>("GuildConfigId")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class removeobsoletexpcolumns : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "LastLevelUp",
table: "UserXpStats");
migrationBuilder.DropColumn(
name: "LastLevelUp",
table: "DiscordUser");
migrationBuilder.DropColumn(
name: "LastXpGain",
table: "DiscordUser");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "LastLevelUp",
table: "UserXpStats",
type: "TEXT",
nullable: false,
defaultValueSql: "datetime('now')");
migrationBuilder.AddColumn<DateTime>(
name: "LastLevelUp",
table: "DiscordUser",
type: "TEXT",
nullable: false,
defaultValueSql: "datetime('now')");
migrationBuilder.AddColumn<DateTime>(
name: "LastXpGain",
table: "DiscordUser",
type: "TEXT",
nullable: false,
defaultValueSql: "datetime('now', '-1 years')");
}
}
}

View File

@@ -134,16 +134,6 @@ namespace NadekoBot.Migrations
.HasColumnType("INTEGER")
.HasDefaultValue(false);
b.Property<DateTime>("LastLevelUp")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("datetime('now')");
b.Property<DateTime>("LastXpGain")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("datetime('now', '-1 years')");
b.Property<int>("NotifyOnLevelUp")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
@@ -2003,11 +1993,6 @@ namespace NadekoBot.Migrations
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastLevelUp")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("datetime('now')");
b.Property<int>("NotifyOnLevelUp")
.HasColumnType("INTEGER");

View File

@@ -344,4 +344,36 @@ public partial class Administration : NadekoModule<AdministrationService>
await ctx.OkAsync();
}
[Cmd]
[BotPerm(ChannelPermission.CreatePublicThreads)]
[UserPerm(ChannelPermission.CreatePublicThreads)]
public async Task ThreadCreate([Leftover] string name)
{
if (ctx.Channel is not SocketTextChannel stc)
return;
await stc.CreateThreadAsync(name, message: ctx.Message.ReferencedMessage);
await ctx.OkAsync();
}
[Cmd]
[BotPerm(ChannelPermission.ManageThreads)]
[UserPerm(ChannelPermission.ManageThreads)]
public async Task ThreadDelete([Leftover] string name)
{
if (ctx.Channel is not SocketTextChannel stc)
return;
var t = stc.Threads.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.InvariantCultureIgnoreCase));
if (t is null)
{
await ReplyErrorLocalizedAsync(strs.not_found);
return;
}
await t.DeleteAsync();
await ctx.OkAsync();
}
}

View File

@@ -38,10 +38,14 @@ public partial class Administration
if (minAgeMinutes < 1 || punishTimeMinutes < 0)
return;
var minutes = (int?)punishTime?.Time.TotalMinutes ?? 0;
if (action is PunishmentAction.TimeOut && minutes < 1)
minutes = 1;
await _service.StartAntiAltAsync(ctx.Guild.Id,
minAgeMinutes,
action,
(int?)punishTime?.Time.TotalMinutes ?? 0);
minutes);
await ctx.OkAsync();
}
@@ -56,6 +60,9 @@ public partial class Administration
if (minAgeMinutes < 1)
return;
if (action == PunishmentAction.TimeOut)
return;
await _service.StartAntiAltAsync(ctx.Guild.Id, minAgeMinutes, action, roleId: role.Id);
await ctx.OkAsync();
@@ -123,6 +130,9 @@ public partial class Administration
if (time is < 0 or > 60 * 24)
return;
if(action is PunishmentAction.TimeOut && time < 1)
return;
var stats = await _service.StartAntiRaidAsync(ctx.Guild.Id, userThreshold, seconds, action, time);
if (stats is null)
@@ -187,6 +197,9 @@ public partial class Administration
if (time is < 0 or > 60 * 24)
return;
if (action is PunishmentAction.TimeOut && time < 1)
return;
var stats = await _service.StartAntiSpamAsync(ctx.Guild.Id, messageCount, action, time, role?.Id);
await SendConfirmAsync(GetText(strs.prot_enable("Anti-Spam")),

View File

@@ -58,7 +58,7 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
}
private async Task<(IGuildUser, IRole)> GetUserAndRoleAsync(
SocketReaction r,
ulong userId,
ReactionRoleV2 rero)
{
var guild = _client.GetGuild(rero.GuildId);
@@ -67,8 +67,8 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
if (role is null)
return default;
var user = guild.GetUser(r.UserId) as IGuildUser
?? await _client.Rest.GetGuildUserAsync(guild.Id, r.UserId);
var user = guild.GetUser(userId) as IGuildUser
?? await _client.Rest.GetGuildUserAsync(guild.Id, userId);
if (user is null)
return default;
@@ -77,20 +77,23 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
}
private Task ClientOnReactionRemoved(
Cacheable<IUserMessage, ulong> msg,
Cacheable<IUserMessage, ulong> cmsg,
Cacheable<IMessageChannel, ulong> ch,
SocketReaction r)
{
if (!_cache.TryGetValue(msg.Id, out var reros))
if (!_cache.TryGetValue(cmsg.Id, out var reros))
return Task.CompletedTask;
_ = Task.Run(async () =>
{
var rero = reros.FirstOrDefault(x => x.Emote == r.Emote.Name || x.Emote == r.Emote.ToString());
var emote = await GetFixedEmoteAsync(cmsg, r.Emote);
var rero = reros.FirstOrDefault(x => x.Emote == emote.Name
|| x.Emote == emote.ToString());
if (rero is null)
return;
var (user, role) = await GetUserAndRoleAsync(r, rero);
var (user, role) = await GetUserAndRoleAsync(r.UserId, rero);
if (user.IsBot)
return;
@@ -112,6 +115,24 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
return Task.CompletedTask;
}
// had to add this because for some reason, reactionremoved event's reaction doesn't have IsAnimated set,
// causing the .ToString() to be wrong on animated custom emotes
private async Task<IEmote> GetFixedEmoteAsync(
Cacheable<IUserMessage, ulong> cmsg,
IEmote inputEmote)
{
// this should only run for emote
if (inputEmote is not Emote e)
return inputEmote;
// try to get the message and pull
var msg = await cmsg.GetOrDownloadAsync();
var emote = msg.Reactions.Keys.FirstOrDefault(x => e.Equals(x));
return emote ?? inputEmote;
}
private Task ClientOnReactionAdded(
Cacheable<IUserMessage, ulong> msg,
Cacheable<IMessageChannel, ulong> ch,
@@ -126,7 +147,7 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
if (rero is null)
return;
var (user, role) = await GetUserAndRoleAsync(r, rero);
var (user, role) = await GetUserAndRoleAsync(r.UserId, rero);
if (user.IsBot)
return;

View File

@@ -345,6 +345,10 @@ public partial class Administration
if (punish is PunishmentAction.AddRole or PunishmentAction.Warn)
return;
// you must specify the time for timeout
if (punish is PunishmentAction.TimeOut && time is null)
return;
var success = _service.WarnPunish(ctx.Guild.Id, number, punish, time);
if (!success)

View File

@@ -193,6 +193,9 @@ public class UserPunishService : INService, IReadyExecutor
case PunishmentAction.Warn:
await Warn(guild, user.Id, mod, 1, reason);
break;
case PunishmentAction.TimeOut:
await user.SetTimeOutAsync(TimeSpan.FromMinutes(minutes));
break;
}
}
@@ -224,6 +227,8 @@ public class UserPunishService : INService, IReadyExecutor
return botUser.GuildPermissions.MuteMembers;
case PunishmentAction.AddRole:
return botUser.GuildPermissions.ManageRoles;
case PunishmentAction.TimeOut:
return botUser.GuildPermissions.ModerateMembers;
default:
return true;
}
@@ -351,7 +356,7 @@ public class UserPunishService : INService, IReadyExecutor
await uow.Warnings.ForgiveAll(guildId, userId, moderator);
else
toReturn = uow.Warnings.Forgive(guildId, userId, moderator, index - 1);
uow.SaveChanges();
await uow.SaveChangesAsync();
return toReturn;
}
@@ -372,6 +377,9 @@ public class UserPunishService : INService, IReadyExecutor
if (punish is PunishmentAction.AddRole && role is null)
return false;
if (punish is PunishmentAction.TimeOut && time is null)
return false;
using var uow = _db.GetDbContext();
var ps = uow.GuildConfigsForId(guildId, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
var toDelete = ps.Where(x => x.Count == number);

View File

@@ -1,5 +1,4 @@
using Nadeko.Common;
using NadekoBot.Modules.Gambling.Bank;
using NadekoBot.Modules.Gambling.Bank;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Services;
@@ -12,10 +11,14 @@ public partial class Gambling
public partial class BankCommands : GamblingModule<IBankService>
{
private readonly IBankService _bank;
private readonly DiscordSocketClient _client;
public BankCommands(GamblingConfigService gcs, IBankService bank) : base(gcs)
public BankCommands(GamblingConfigService gcs,
IBankService bank,
DiscordSocketClient client) : base(gcs)
{
_bank = bank;
_client = client;
}
[Cmd]
@@ -35,7 +38,7 @@ public partial class Gambling
}
[Cmd]
public async Task BankWithdraw(ShmartNumber amount)
public async Task BankWithdraw(ShmartBankAmount amount)
{
if (amount <= 0)
return;
@@ -69,5 +72,50 @@ public partial class Gambling
await ReplyErrorLocalizedAsync(strs.cant_dm);
}
}
private async Task BankTakeInternalAsync(long amount, ulong userId)
{
if (await _bank.TakeAsync(userId, amount))
{
await ReplyErrorLocalizedAsync(strs.take_fail(N(amount),
_client.GetUser(userId)?.ToString()
?? userId.ToString(),
CurrencySign));
return;
}
await ctx.OkAsync();
}
private async Task BankAwardInternalAsync(long amount, ulong userId)
{
if (await _bank.AwardAsync(userId, amount))
{
await ReplyErrorLocalizedAsync(strs.take_fail(N(amount),
_client.GetUser(userId)?.ToString()
?? userId.ToString(),
CurrencySign));
return;
}
await ctx.OkAsync();
}
[Cmd]
[OwnerOnly]
[Priority(1)]
public async Task BankTake(long amount, [Leftover] IUser user)
=> await BankTakeInternalAsync(amount, user.Id);
[Cmd]
[OwnerOnly]
[Priority(0)]
public async Task BankTake(long amount, ulong userId)
=> await BankTakeInternalAsync(amount, userId);
[Cmd]
[OwnerOnly]
public async Task BankAward(long amount, [Leftover] IUser user)
=> await BankAwardInternalAsync(amount, user.Id);
}
}

View File

@@ -15,6 +15,48 @@ public sealed class BankService : IBankService, INService
_db = db;
}
public async Task<bool> AwardAsync(ulong userId, long amount)
{
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount));
await using var ctx = _db.GetDbContext();
await ctx.BankUsers
.ToLinqToDBTable()
.InsertOrUpdateAsync(() => new()
{
UserId = userId,
Balance = amount
},
(old) => new()
{
Balance = old.Balance + amount
},
() => new()
{
UserId = userId
});
return true;
}
public async Task<bool> TakeAsync(ulong userId, long amount)
{
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount));
await using var ctx = _db.GetDbContext();
var rows = await ctx.BankUsers
.ToLinqToDBTable()
.Where(x => x.UserId == userId && x.Balance >= amount)
.UpdateAsync((old) => new()
{
Balance = old.Balance - amount
});
return rows > 0;
}
public async Task<bool> DepositAsync(ulong userId, long amount)
{
if (amount <= 0)

View File

@@ -5,4 +5,6 @@ public interface IBankService
Task<bool> DepositAsync(ulong userId, long amount);
Task<bool> WithdrawAsync(ulong userId, long amount);
Task<long> GetBalanceAsync(ulong userId);
Task<bool> AwardAsync(ulong userId, long amount);
Task<bool> TakeAsync(ulong userId, long amount);
}

View File

@@ -1,13 +0,0 @@
#nullable disable
namespace NadekoBot.Modules.Gambling;
public class CashInteraction : NInteraction
{
protected override NadekoInteractionData Data
=> new NadekoInteractionData(new Emoji("🏦"), "cash:bank_show_balance");
public CashInteraction(DiscordSocketClient client, ulong userId, Func<SocketMessageComponent, Task> action)
: base(client, userId, action)
{
}
}

View File

@@ -99,39 +99,20 @@ public partial class Gambling : GamblingModule<GamblingService>
PrettyName = "Timely"
};
public class RemindMeInteraction : NInteraction
private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when)
{
public RemindMeInteraction(
[NotNull] DiscordSocketClient client,
ulong userId,
[NotNull] Func<SocketMessageComponent, Task> action)
: base(client, userId, action)
{
}
var tt = TimestampTag.FromDateTime(when, TimestampTagStyles.Relative);
protected override NadekoInteractionData Data
=> new NadekoInteractionData(
Emote: Emoji.Parse("⏰"),
CustomId: "timely:remind_me",
Text: "Remind me"
);
await _remind.AddReminderAsync(ctx.User.Id,
ctx.User.Id,
ctx.Guild.Id,
true,
when,
GetText(strs.timely_time));
await smc.RespondConfirmAsync(_eb, GetText(strs.remind_timely(tt)), ephemeral: true);
}
private Func<SocketMessageComponent, Task> RemindTimelyAction(DateTime when)
=> async smc =>
{
var tt = TimestampTag.FromDateTime(when, TimestampTagStyles.Relative);
await _remind.AddReminderAsync(ctx.User.Id,
ctx.User.Id,
ctx.Guild.Id,
true,
when,
GetText(strs.timely_time));
await smc.RespondConfirmAsync(_eb, GetText(strs.remind_timely(tt)), ephemeral: true);
};
[Cmd]
public async Task Timely()
{
@@ -147,7 +128,7 @@ public partial class Gambling : GamblingModule<GamblingService>
{
var now = DateTime.UtcNow;
var relativeTag = TimestampTag.FromDateTime(now.Add(rem), TimestampTagStyles.Relative);
await ReplyErrorLocalizedAsync(strs.timely_already_claimed(relativeTag));
await ReplyPendingLocalizedAsync(strs.timely_already_claimed(relativeTag));
return;
}
@@ -157,11 +138,17 @@ public partial class Gambling : GamblingModule<GamblingService>
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
var inter = new RemindMeInteraction(_client,
ctx.User.Id,
RemindTimelyAction(DateTime.UtcNow.Add(TimeSpan.FromHours(period))));
var inter = _inter
.Create(ctx.User.Id,
new SimpleInteraction<DateTime>(
new ButtonBuilder(
label: "Remind me",
emote: Emoji.Parse("⏰"),
customId: "timely:remind_me"),
RemindTimelyAction,
DateTime.UtcNow.Add(TimeSpan.FromHours(period))));
await ReplyConfirmLocalizedAsync(strs.timely(N(val), period), inter.GetInteraction());
await ReplyConfirmLocalizedAsync(strs.timely(N(val), period), inter);
}
[Cmd]
@@ -362,7 +349,7 @@ public partial class Gambling : GamblingModule<GamblingService>
await ReplyConfirmLocalizedAsync(strs.has(Format.Code(userId.ToString()), cur));
}
private async Task BankAction(SocketMessageComponent smc)
private async Task BankAction(SocketMessageComponent smc, object _)
{
var balance = await _bank.GetBalanceAsync(ctx.User.Id);
@@ -372,8 +359,12 @@ public partial class Gambling : GamblingModule<GamblingService>
.Pipe(text => smc.RespondConfirmAsync(_eb, text, ephemeral: true));
}
private NadekoButtonInteraction CreateCashInteraction()
=> new CashInteraction(_client, ctx.User.Id, BankAction).GetInteraction();
private NadekoInteraction CreateCashInteraction()
=> _inter.Create<object>(ctx.User.Id,
new(new(
customId: "cash:bank_show_balance",
emote: new Emoji("🏦")),
BankAction));
[Cmd]
[Priority(1)]
@@ -663,7 +654,6 @@ public partial class Gambling : GamblingModule<GamblingService>
if (win > 0)
{
str = GetText(strs.br_win(N(win), result.Threshold + (result.Roll == 100 ? " 👑" : "")));
await _cs.AddAsync(ctx.User, win, new("betroll", "win"));
}
else
{
@@ -876,11 +866,12 @@ public partial class Gambling : GamblingModule<GamblingService>
public enum GambleTestTarget
{
Slot,
Betroll,
Betflip,
BetflipT,
BetDraw,
BetDrawHL,
BetDrawRB,
Betflip,
BetflipT,
Lula,
Rps,
}
@@ -921,6 +912,7 @@ public partial class Gambling : GamblingModule<GamblingService>
GambleTestTarget.BetflipT => (await _gs.BetFlipAsync(ctx.User.Id, 0, 1)).AsT0.Multiplier,
GambleTestTarget.Lula => (await _gs.LulaAsync(ctx.User.Id, 0)).AsT0.Multiplier,
GambleTestTarget.Rps => (await _gs.RpsAsync(ctx.User.Id, 0, (byte)(i % 3))).AsT0.Multiplier,
GambleTestTarget.Betroll => (await _gs.BetRollAsync(ctx.User.Id, 0)).AsT0.Multiplier,
_ => throw new ArgumentOutOfRangeException(nameof(target))
};

View File

@@ -142,7 +142,7 @@ public class GamblingService : INService, IReadyExecutor
var onePercent = uow.DiscordUser.GetTopOnePercentCurrency(_client.CurrentUser.Id);
decimal planted = uow.PlantedCurrency.AsQueryable().Sum(x => x.Amount);
var waifus = uow.WaifuInfo.GetTotalValue();
var bot = uow.DiscordUser.GetUserCurrency(_client.CurrentUser.Id);
var bot = await uow.DiscordUser.GetUserCurrencyAsync(_client.CurrentUser.Id);
decimal bank = await uow.GetTable<BankUser>()
.SumAsyncLinqToDB(x => x.Balance);

View File

@@ -27,12 +27,6 @@ public partial class Gambling
private static decimal totalBet;
private static decimal totalPaidOut;
private static readonly ConcurrentHashSet<ulong> _runningUsers = new();
//here is a payout chart
//https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg
//thanks to judge for helping me with this
private readonly IImageCache _images;
private readonly FontProvider _fonts;
private readonly DbService _db;
@@ -73,16 +67,6 @@ public partial class Gambling
await ctx.Channel.EmbedAsync(embed);
}
public sealed class SlotInteraction : NInteraction
{
public SlotInteraction(DiscordSocketClient client, ulong userId, Func<SocketMessageComponent, Task> action) : base(client, userId, action)
{
}
protected override NadekoInteractionData Data { get; } = new(Emoji.Parse("🔁"),
"slot:again",
"Pull Again");
}
[Cmd]
public async Task Slot(ShmartNumber amount)
@@ -94,43 +78,34 @@ public partial class Gambling
await ctx.Channel.TriggerTypingAsync();
if (!_runningUsers.Add(ctx.User.Id))
if (await InternalSlotAsync(amount) is not SlotResult result)
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
try
{
if (await InternalSlotAsync(amount) is not SlotResult result)
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
var msg = GetSlotMessageInternal(result);
using var image = await GenerateSlotImageAsync(amount, result);
await using var imgStream = await image.ToStreamAsync();
var eb = _eb.Create(ctx)
.WithAuthor(ctx.User)
.WithDescription(Format.Bold(msg))
.WithImageUrl($"attachment://result.png")
.WithOkColor();
// var inter = slotInteraction.GetInteraction();
await ctx.Channel.SendFileAsync(imgStream,
"result.png",
embed: eb.Build()
// components: inter.CreateComponent()
);
// await inter.RunAsync(resMsg);
}
finally
{
await Task.Delay(1000);
_runningUsers.TryRemove(ctx.User.Id);
}
var text = GetSlotMessageTextInternal(result);
using var image = await GenerateSlotImageAsync(amount, result);
await using var imgStream = await image.ToStreamAsync();
var eb = _eb.Create(ctx)
.WithAuthor(ctx.User)
.WithDescription(Format.Bold(text))
.WithImageUrl($"attachment://result.png")
.WithOkColor();
var bb = new ButtonBuilder(emote: Emoji.Parse("🔁"), customId: "slot:again", label: "Pull Again");
var si = new SimpleInteraction<ShmartNumber>(bb, (_, amount) => Slot(amount), amount);
var inter = _inter.Create(ctx.User.Id, si);
var msg = await ctx.Channel.SendFileAsync(imgStream,
"result.png",
embed: eb.Build(),
components: inter.CreateComponent()
);
await inter.RunAsync(msg);
}
// private SlotInteraction CreateSlotInteractionIntenal(long amount)
@@ -181,7 +156,7 @@ public partial class Gambling
// });
// }
private string GetSlotMessageInternal(SlotResult result)
private string GetSlotMessageTextInternal(SlotResult result)
{
var multi = result.Multiplier.ToString("0.##");
var msg = result.WinType switch
@@ -201,7 +176,6 @@ public partial class Gambling
if (!maybeResult.TryPickT0(out var result, out var error))
{
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return null;
}
@@ -235,7 +209,7 @@ public partial class Gambling
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
WrappingLength = 140,
Origin = new(227, 92)
Origin = new(298, 100)
},
((long)result.Won).ToString(),
fontColor));
@@ -247,7 +221,7 @@ public partial class Gambling
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
WrappingLength = 135,
Origin = new(129, 472)
Origin = new(196, 480)
},
amount.ToString(),
fontColor));
@@ -256,8 +230,7 @@ public partial class Gambling
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
WrappingLength = 135,
Origin = new(325, 472)
Origin = new(393, 480)
},
ownedAmount.ToString(),
fontColor));

View File

@@ -57,123 +57,133 @@ public sealed class TriviaGame
// loop until game is stopped
// each iteration is one round
var firstRun = true;
while (!_isStopped)
try
{
if (errorCount >= 5)
while (!_isStopped)
{
Log.Warning("Trivia errored 5 times and will quit");
break;
}
// wait for 3 seconds before posting the next question
if (firstRun)
{
firstRun = false;
}
else
{
await Task.Delay(3000);
}
var maybeQuestion = await _questionPool.GetQuestionAsync();
if(!(maybeQuestion is TriviaQuestion question))
{
// if question is null (ran out of question, or other bugg ) - stop
break;
}
CurrentQuestion = question;
try
{
// clear out all of the past guesses
while (_inputs.Reader.TryRead(out _)) ;
await OnQuestion(this, question);
}
catch (Exception ex)
{
Log.Warning(ex, "Error executing OnQuestion: {Message}", ex.Message);
errorCount++;
continue;
}
// just keep looping through user inputs until someone guesses the answer
// or the timer expires
var halfGuessTimerTask = TimeOutFactory();
var hintSent = false;
var guessed = false;
while (true)
{
var readTask = _inputs.Reader.ReadAsync().AsTask();
// wait for either someone to attempt to guess
// or for timeout
var task = await Task.WhenAny(readTask, halfGuessTimerTask);
// if the task which completed is the timeout task
if (task == halfGuessTimerTask)
if (errorCount >= 5)
{
// if hint is already sent, means time expired
// break (end the round)
if (hintSent)
break;
Log.Warning("Trivia errored 5 times and will quit");
await OnEnded(this);
break;
}
// else, means half time passed, send a hint
hintSent = true;
// start a new countdown of the same length
halfGuessTimerTask = TimeOutFactory();
// send a hint out
await OnHint(this, question);
// wait for 3 seconds before posting the next question
if (firstRun)
{
firstRun = false;
}
else
{
await Task.Delay(3000);
}
var maybeQuestion = await _questionPool.GetQuestionAsync();
if (!(maybeQuestion is TriviaQuestion question))
{
// if question is null (ran out of question, or other bugg ) - stop
break;
}
CurrentQuestion = question;
try
{
// clear out all of the past guesses
while (_inputs.Reader.TryRead(out _))
;
await OnQuestion(this, question);
}
catch (Exception ex)
{
Log.Warning(ex, "Error executing OnQuestion: {Message}", ex.Message);
errorCount++;
continue;
}
// otherwise, read task is successful, and we're gonna
// get the user input data
var (user, input) = await readTask;
// check the guess
if (question.IsAnswerCorrect(input))
// just keep looping through user inputs until someone guesses the answer
// or the timer expires
var halfGuessTimerTask = TimeOutFactory();
var hintSent = false;
var guessed = false;
while (true)
{
// add 1 point to the user
var val = _users.AddOrUpdate(user.Id, 1, (_, points) => ++points);
guessed = true;
var readTask = _inputs.Reader.ReadAsync().AsTask();
// reset inactivity counter
inactivity = 0;
// wait for either someone to attempt to guess
// or for timeout
var task = await Task.WhenAny(readTask, halfGuessTimerTask);
var isWin = false;
// if user won the game, tell the game to stop
if (val >= _opts.WinRequirement)
// if the task which completed is the timeout task
if (task == halfGuessTimerTask)
{
_isStopped = true;
isWin = true;
// if hint is already sent, means time expired
// break (end the round)
if (hintSent)
break;
// else, means half time passed, send a hint
hintSent = true;
// start a new countdown of the same length
halfGuessTimerTask = TimeOutFactory();
// send a hint out
await OnHint(this, question);
continue;
}
// call onguess
await OnGuess(this, user, question, isWin);
break;
// otherwise, read task is successful, and we're gonna
// get the user input data
var (user, input) = await readTask;
// check the guess
if (question.IsAnswerCorrect(input))
{
// add 1 point to the user
var val = _users.AddOrUpdate(user.Id, 1, (_, points) => ++points);
guessed = true;
// reset inactivity counter
inactivity = 0;
var isWin = false;
// if user won the game, tell the game to stop
if (val >= _opts.WinRequirement)
{
_isStopped = true;
isWin = true;
}
// call onguess
await OnGuess(this, user, question, isWin);
break;
}
}
}
if (!guessed)
{
await OnTimeout(this, question);
if (_opts.Timeout != 0 && ++inactivity >= _opts.Timeout)
if (!guessed)
{
Log.Information("Trivia game is stopping due to inactivity");
break;
await OnTimeout(this, question);
if (_opts.Timeout != 0 && ++inactivity >= _opts.Timeout)
{
Log.Information("Trivia game is stopping due to inactivity");
break;
}
}
}
}
catch
{
// make sure game is set as ended
_isStopped = true;
await OnEnded(this);
}
finally
{
// make sure game is set as ended
_isStopped = true;
_ = OnEnded(this);
}
}
public IReadOnlyList<(ulong User, int points)> GetLeaderboard()

View File

@@ -424,7 +424,7 @@ public partial class Help : NadekoModule<HelpService>
}
catch (Exception)
{
Log.Information("No old version list found. Creating a new one.");
Log.Information("No old version list found. Creating a new one");
}
var versionList = JsonSerializer.Deserialize<List<string>>(versionListString);
@@ -469,7 +469,7 @@ public partial class Help : NadekoModule<HelpService>
"https://nadekobot.readthedocs.io/en/latest/"));
private Task SelfhostAction(SocketMessageComponent smc)
private Task SelfhostAction(SocketMessageComponent smc, object _)
=> smc.RespondConfirmAsync(_eb,
@"- In case you don't want or cannot Donate to NadekoBot project, but you
- NadekoBot is a completely free and fully [open source](https://gitlab.com/kwoth/nadekobot) project which means you can run your own ""selfhosted"" instance on your computer or server for free.
@@ -484,7 +484,13 @@ public partial class Help : NadekoModule<HelpService>
[OnlyPublicBot]
public async Task Donate()
{
var selfhostInter = new DonateSelfhostingInteraction(_client, ctx.User.Id, SelfhostAction);
// => new NadekoInteractionData(new Emoji("🖥️"), "donate:selfhosting", "Selfhosting");
var selfhostInter = _inter.Create(ctx.User.Id,
new SimpleInteraction<object>(new ButtonBuilder(
emote: new Emoji("🖥️"),
customId: "donate:selfhosting",
label: "Selfhosting"),
SelfhostAction));
var eb = _eb.Create(ctx)
.WithOkColor()
@@ -525,7 +531,7 @@ Nadeko will DM you the welcome instructions, and you may start using the patron-
try
{
await (await ctx.User.CreateDMChannelAsync()).EmbedAsync(eb, inter: selfhostInter.GetInteraction());
await (await ctx.User.CreateDMChannelAsync()).EmbedAsync(eb, inter: selfhostInter);
_ = ctx.OkAsync();
}
catch

View File

@@ -1,12 +0,0 @@
namespace NadekoBot.Modules.Help;
public class DonateSelfhostingInteraction : NInteraction
{
protected override NadekoInteractionData Data
=> new NadekoInteractionData(new Emoji("🖥️"), "donate:selfhosting", "Selfhosting");
public DonateSelfhostingInteraction(DiscordSocketClient client, ulong userId, Func<SocketMessageComponent, Task> action)
: base(client, userId, action)
{
}
}

View File

@@ -1,12 +0,0 @@
namespace NadekoBot.Modules.Help;
public class DonateTroubleshootInteraction : NInteraction
{
protected override NadekoInteractionData Data
=> new NadekoInteractionData(new Emoji("❓"), "donate:troubleshoot", "Troubleshoot");
public DonateTroubleshootInteraction(DiscordSocketClient client, ulong userId, Func<SocketMessageComponent, Task> action)
: base(client, userId, action)
{
}
}

View File

@@ -274,7 +274,7 @@ public sealed class YtdlYoutubeResolver : IYoutubeResolver
Log.Information("Resolving youtube song by search term: {YoutubeQuery}", query);
var cachedData = await _trackCacher.GetCachedDataByQueryAsync(query, MusicPlatform.Youtube);
if (cachedData is null)
if (cachedData is null || string.IsNullOrWhiteSpace(cachedData.Title))
{
var stringData = await _ytdlSearchOperation.GetDataAsync(query);
var trackData = ResolveYtdlData(stringData);

View File

@@ -98,6 +98,33 @@ public partial class Utility
await ctx.Channel.EmbedAsync(embed);
}
[Cmd]
[RequireUserPermission(GuildPermission.ManageRoles)]
public async Task RoleInfo([Leftover] SocketRole role)
{
if (role.IsEveryone)
return;
var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)
.AddMilliseconds(role.Id >> 22);
var usercount = role.Members.LongCount();
var embed = _eb.Create()
.WithTitle(role.Name.TrimTo(128))
.WithDescription(role.Permissions.ToList().Join(" | "))
.AddField(GetText(strs.id), role.Id.ToString(), true)
.AddField(GetText(strs.created_at), $"{createdAt:dd.MM.yyyy HH:mm}", true)
.AddField(GetText(strs.users), usercount.ToString(), true)
.AddField(GetText(strs.color), $"#{role.Color.R:X2}{role.Color.G:X2}{role.Color.B:X2}", true)
.AddField(GetText(strs.mentionable), role.IsMentionable.ToString(), true)
.AddField(GetText(strs.hoisted), role.IsHoisted.ToString(), true)
.WithOkColor();
if (!string.IsNullOrWhiteSpace(role.GetIconUrl()))
embed = embed.WithThumbnailUrl(role.GetIconUrl());
await ctx.Channel.EmbedAsync(embed);
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task UserInfo(IGuildUser usr = null)

View File

@@ -715,8 +715,8 @@ public sealed class PatronageService
return new()
{
Name = key.PrettyName,
Quota = defaultValue,
IsPatronLimit = false,
Quota = 0,
IsPatronLimit = true,
};
return new()

View File

@@ -367,6 +367,41 @@ public partial class Utility : NadekoModule
await ConfirmLocalizedAsync(strs.emoji_added(em.ToString()));
}
[Cmd]
[RequireContext(ContextType.Guild)]
[BotPerm(GuildPerm.ManageEmojisAndStickers)]
[UserPerm(GuildPerm.ManageEmojisAndStickers)]
[Priority(0)]
public async Task EmojiRemove(params Emote[] emotes)
{
if (emotes.Length == 0)
return;
var g = (SocketGuild)ctx.Guild;
var fails = new List<Emote>();
foreach (var emote in emotes)
{
var guildEmote = g.Emotes.FirstOrDefault(x => x.Id == emote.Id);
if (guildEmote is null)
{
fails.Add(emote);
}
else
{
await ctx.Guild.DeleteEmoteAsync(guildEmote);
}
}
if (fails.Count > 0)
{
await ReplyPendingLocalizedAsync(strs.emoji_not_removed(fails.Select(x => x.ToString()).Join(" ")));
return;
}
await ctx.OkAsync();
}
[Cmd]
[OwnerOnly]
public async Task ListServers(int page = 1)

View File

@@ -334,14 +334,15 @@ public partial class Xp : NadekoModule<XpService>
public enum XpShopInputType
{
F = 0,
Frs = 0,
Fs = 0,
Frames = 0,
B = 1,
Bg = 1,
Bgs = 1,
Backgrounds = 1
Backgrounds = 0,
B = 0,
Bg = 0,
Bgs = 0,
Frames = 1,
F = 1,
Fr = 1,
Frs = 1,
Fs = 1,
}
[Cmd]
@@ -353,8 +354,11 @@ public partial class Xp : NadekoModule<XpService>
return;
}
await SendConfirmAsync(GetText(strs.available_commands), $@"`{prefix}xpshop bgs`
`{prefix}xpshop frames`");
await SendConfirmAsync(GetText(strs.available_commands),
$@"`{prefix}xpshop bgs`
`{prefix}xpshop frames`
*{GetText(strs.xpshop_website)}*");
}
[Cmd]
@@ -394,13 +398,18 @@ public partial class Xp : NadekoModule<XpService>
.WithOkColor()
.WithTitle(item.Name)
.AddField(GetText(strs.price), Gambling.Gambling.N(item.Price, culture), true)
// .AddField(GetText(strs.buy), $"{prefix}xpbuy {key}", true)
.WithImageUrl(item.Url.ToString());
.WithImageUrl(string.IsNullOrWhiteSpace(item.Preview)
? item.Url
: item.Preview);
if (!string.IsNullOrWhiteSpace(item.Desc))
eb.WithDescription(item.Desc);
eb.AddField(GetText(strs.desc), item.Desc);
var tier = _service.GetXpShopTierRequirement();
if (key == "default")
eb.WithDescription(GetText(strs.xpshop_website));
var tier = _service.GetXpShopTierRequirement(type);
if (tier != PatronTier.None)
{
eb.WithFooter(GetText(strs.xp_shop_buy_required_tier(tier.ToString())));
@@ -423,8 +432,7 @@ public partial class Xp : NadekoModule<XpService>
var button = new ButtonBuilder(ownedItem.IsUsing
? GetText(strs.in_use)
: GetText(strs.use),
"XP_SHOP_USE",
ButtonStyle.Primary,
"xpshop:use",
emote: Emoji.Parse("👐"),
isDisabled: ownedItem.IsUsing);
@@ -438,8 +446,7 @@ public partial class Xp : NadekoModule<XpService>
else
{
var button = new ButtonBuilder(GetText(strs.buy),
"XP_SHOP_BUY",
ButtonStyle.Primary,
"xpshop:buy",
emote: Emoji.Parse("💰"));
var inter = new SimpleInteraction<(string key, XpShopItemType type)?>(
@@ -455,6 +462,53 @@ public partial class Xp : NadekoModule<XpService>
addPaginatedFooter: false);
}
[Cmd]
public async Task XpShopBuy(XpShopInputType type, string key)
{
var result = await _service.BuyShopItemAsync(ctx.User.Id, (XpShopItemType)type, key);
NadekoInteraction GetUseInteraction()
{
return _inter.Create(ctx.User.Id,
new SimpleInteraction<object>(
new ButtonBuilder(label: "Use", customId: "xpshop:use_item", emote: Emoji.Parse("👐")),
async (smc, _) => await XpShopUse(type, key)
));
}
if (result != BuyResult.Success)
{
var _ = result switch
{
BuyResult.XpShopDisabled => await ReplyErrorLocalizedAsync(strs.xp_shop_disabled),
BuyResult.InsufficientFunds => await ReplyErrorLocalizedAsync(strs.not_enough(_gss.Data.Currency.Sign)),
BuyResult.AlreadyOwned => await ReplyErrorLocalizedAsync(strs.xpshop_already_owned, GetUseInteraction()),
BuyResult.UnknownItem => await ReplyErrorLocalizedAsync(strs.xpshop_item_not_found),
BuyResult.InsufficientPatronTier => await ReplyErrorLocalizedAsync(strs.patron_insuff_tier),
_ => throw new ArgumentOutOfRangeException()
};
return;
}
await ReplyConfirmLocalizedAsync(strs.xpshop_buy_success(type.ToString().ToLowerInvariant(),
key.ToLowerInvariant()),
GetUseInteraction());
}
[Cmd]
public async Task XpShopUse(XpShopInputType type, string key)
{
var result = await _service.UseShopItemAsync(ctx.User.Id, (XpShopItemType)type, key);
if (!result)
{
await ReplyConfirmLocalizedAsync(strs.xp_shop_item_cant_use);
return;
}
await ctx.OkAsync();
}
private async Task OnShopUse(SocketMessageComponent smc, (string? key, XpShopItemType type)? maybeState)
{
if (maybeState is not { } state)

View File

@@ -10,7 +10,7 @@ namespace NadekoBot.Modules.Xp;
public sealed partial class XpConfig : ICloneable<XpConfig>
{
[Comment(@"DO NOT CHANGE")]
public int Version { get; set; } = 3;
public int Version { get; set; } = 5;
[Comment(@"How much XP will the users receive per message")]
public int XpPerMessage { get; set; } = 3;
@@ -37,9 +37,13 @@ True -> Users can access the xp shop using .xpshop command
False -> Users can't access the xp shop")]
public bool IsEnabled { get; set; } = false;
[Comment(@"Which patron tier do users need in order to use the .xpshop command
[Comment(@"Which patron tier do users need in order to use the .xpshop bgs command
Leave at 'None' if patron system is disabled or you don't want any restrictions")]
public PatronTier TierRequirement { get; set; } = PatronTier.None;
public PatronTier BgsTierRequirement { get; set; } = PatronTier.None;
[Comment(@"Which patron tier do users need in order to use the .xpshop frames command
Leave at 'None' if patron system is disabled or you don't want any restrictions")]
public PatronTier FramesTierRequirement { get; set; } = PatronTier.None;
[Comment(@"Frames available for sale. Keys are unique IDs.
Do not change keys as they are not publicly visible. Only change properties (name, price, id)
@@ -71,6 +75,9 @@ To remove an item from the shop, but keep previous purchases, set the price to -
[Comment(@"Direct url to the .png image which will be applied to the user's XP card")]
public string Url { get; set; }
[Comment(@"Optional preview url which will show instead of the real URL in the shop ")]
public string Preview { get; set; }
[Comment(@"Optional description of the item")]
public string Desc { get; set; }
}

View File

@@ -52,11 +52,11 @@ public sealed class XpConfigService : ConfigServiceBase<XpConfig>
});
}
if (data.Version < 3)
if (data.Version < 5)
{
ModifyConfig(c =>
{
c.Version = 3;
c.Version = 5;
});
}
}

View File

@@ -188,6 +188,13 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
},
(_, n) => n);
await ctx.Clubs
.Where(x => x.Members.Any(m => group.Contains(m.UserId)))
.UpdateAsync(old => new()
{
Xp = old.Xp + (group.Key * old.Members.Count(m => group.Contains(m.UserId)))
});
dus.AddRange(items);
}
@@ -207,6 +214,41 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
(_, n) => n);
gxps.AddRange(items);
var missingUserIds = group.Where(userId => !items.Any(x => x.UserId == userId)).ToArray();
foreach (var userId in missingUserIds)
{
await ctx
.UserXpStats
.ToLinqToDBTable()
.InsertOrUpdateAsync(() => new UserXpStats()
{
UserId = userId,
GuildId = guildId,
Xp = group.Key,
DateAdded = DateTime.UtcNow,
AwardedXp = 0,
NotifyOnLevelUp = XpNotificationLocation.None
},
_ => new()
{
},
() => new()
{
UserId = userId
});
}
if (missingUserIds.Length > 0)
{
var missingItems = await ctx.UserXpStats
.ToLinqToDBTable()
.Where(x => missingUserIds.Contains(x.UserId))
.ToArrayAsyncLinqToDB();
gxps.AddRange(missingItems);
}
}
}
}
@@ -593,12 +635,12 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
{
if (ShouldTrackVoiceChannel(channel))
{
foreach (var user in channel.Users)
foreach (var user in channel.ConnectedUsers)
await ScanUserForVoiceXp(user, channel);
}
else
{
foreach (var user in channel.Users)
foreach (var user in channel.ConnectedUsers)
await UserLeftVoiceChannel(user, channel);
}
}
@@ -617,13 +659,13 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
}
private bool ShouldTrackVoiceChannel(SocketVoiceChannel channel)
=> channel.Users.Where(UserParticipatingInVoiceChannel).Take(2).Count() >= 2;
=> channel.ConnectedUsers.Where(UserParticipatingInVoiceChannel).Take(2).Count() >= 2;
private bool UserParticipatingInVoiceChannel(SocketGuildUser user)
=> !user.IsDeafened && !user.IsMuted && !user.IsSelfDeafened && !user.IsSelfMuted;
private TypedKey<long> GetVoiceXpKey(ulong userId)
=> new($"xp:vc_join:{userId}");
=> new($"xp:{_client.CurrentUser.Id}:vc_join:{userId}");
private async Task UserJoinedVoiceChannel(SocketGuildUser user)
{
@@ -635,6 +677,18 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
overwrite: false);
}
// private void UserJoinedVoiceChannel(SocketGuildUser user)
// {
// var key = $"{_creds.RedisKey()}_user_xp_vc_join_{user.Id}";
// var value = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
//
// _cache.Redis.GetDatabase()
// .StringSet(key,
// value,
// TimeSpan.FromMinutes(_xpConfig.Data.VoiceMaxMinutes),
// when: When.NotExists);
// }
private async Task UserLeftVoiceChannel(SocketGuildUser user, SocketVoiceChannel channel)
{
var key = GetVoiceXpKey(user.Id);
@@ -654,6 +708,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
if (actualXp > 0)
{
Log.Information("Adding {Amount} voice xp to {User}", actualXp, user.ToString());
await _xpGainQueue.Writer.WriteAsync(new()
{
Guild = channel.Guild,
@@ -664,6 +719,38 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
}
}
/*
* private void UserLeftVoiceChannel(SocketGuildUser user, SocketVoiceChannel channel)
{
var key = $"{_creds.RedisKey()}_user_xp_vc_join_{user.Id}";
var value = _cache.Redis.GetDatabase().StringGet(key);
_cache.Redis.GetDatabase().KeyDelete(key);
// Allow for if this function gets called multiple times when a user leaves a channel.
if (value.IsNull)
return;
if (!value.TryParse(out long startUnixTime))
return;
var dateStart = DateTimeOffset.FromUnixTimeSeconds(startUnixTime);
var dateEnd = DateTimeOffset.UtcNow;
var minutes = (dateEnd - dateStart).TotalMinutes;
var xp = _xpConfig.Data.VoiceXpPerMinute * minutes;
var actualXp = (int)Math.Floor(xp);
if (actualXp > 0)
{
_addMessageXp.Enqueue(new()
{
Guild = channel.Guild,
User = user,
XpAmount = actualXp
});
}
}
*/
private bool ShouldTrackXp(SocketGuildUser user, ulong channelId)
{
if (_excludedChannels.TryGetValue(user.Guild.Id, out var chans) && chans.Contains(channelId))
@@ -767,8 +854,8 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
return Enumerable.Empty<ulong>();
}
private static TypedKey<bool> GetUserRewKey(ulong userId)
=> new($"xp:user_gain:{userId}");
private TypedKey<bool> GetUserRewKey(ulong userId)
=> new($"xp:{_client.CurrentUser.Id}:user_gain:{userId}");
private async Task<bool> SetUserRewardedAsync(ulong userId)
=> await _c.AddAsync(GetUserRewKey(userId),
@@ -887,6 +974,8 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
throw new ArgumentNullException(nameof(bgBytes));
}
var outlinePen = new Pen(Color.Black, 1f);
using var img = Image.Load<Rgba32>(bgBytes, out var imageFormat);
if (template.User.Name.Show)
{
@@ -909,7 +998,8 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
Origin = new(template.User.Name.Pos.X, template.User.Name.Pos.Y + 8)
},
"@" + username,
template.User.Name.Color);
Brushes.Solid(template.User.Name.Color),
outlinePen);
});
}
@@ -929,7 +1019,8 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
Origin = new(template.Club.Name.Pos.X + 50, template.Club.Name.Pos.Y - 8)
},
clubName,
template.Club.Name.Color));
Brushes.Solid(template.Club.Name.Color),
outlinePen));
}
Font GetTruncatedFont(
@@ -987,7 +1078,6 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
});
}
var pen = new Pen(Color.Black, 1.25f);
var global = stats.Global;
var guild = stats.Guild;
@@ -1012,7 +1102,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
},
$"{global.LevelXp}/{global.RequiredXp}",
Brushes.Solid(template.User.Xp.Global.Color),
pen));
outlinePen));
}
if (template.User.Xp.Guild.Show)
@@ -1026,7 +1116,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
},
$"{guild.LevelXp}/{guild.RequiredXp}",
Brushes.Solid(template.User.Xp.Guild.Color),
pen));
outlinePen));
}
if (stats.FullGuildStats.AwardedXp != 0 && template.User.Xp.Awarded.Show)
@@ -1038,10 +1128,11 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
img.Mutate(x => x.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})",
_fonts.NotoSans.CreateFont(template.User.Xp.Awarded.FontSize, FontStyle.Bold),
Brushes.Solid(template.User.Xp.Awarded.Color),
pen,
outlinePen,
new(awX, awY)));
}
var rankPen = new Pen(Color.White, 1);
//ranking
if (template.User.GlobalRank.Show)
{
@@ -1054,10 +1145,15 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
globalRankStr,
68);
img.Mutate(x => x.DrawText(globalRankStr,
globalRankFont,
template.User.GlobalRank.Color,
new(template.User.GlobalRank.Pos.X, template.User.GlobalRank.Pos.Y)));
img.Mutate(x => x.DrawText(
new TextOptions(globalRankFont)
{
Origin = new(template.User.GlobalRank.Pos.X, template.User.GlobalRank.Pos.Y)
},
globalRankStr,
Brushes.Solid(template.User.GlobalRank.Color),
rankPen
));
}
if (template.User.GuildRank.Show)
@@ -1071,10 +1167,15 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
guildRankStr,
43);
img.Mutate(x => x.DrawText(guildRankStr,
guildRankFont,
template.User.GuildRank.Color,
new(template.User.GuildRank.Pos.X, template.User.GuildRank.Pos.Y)));
img.Mutate(x => x.DrawText(
new TextOptions(guildRankFont)
{
Origin = new(template.User.GuildRank.Pos.X, template.User.GuildRank.Pos.Y)
},
guildRankStr,
Brushes.Solid(template.User.GuildRank.Color),
rankPen
));
}
//avatar
@@ -1335,13 +1436,17 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
var conf = _xpConfig.Data;
if (!conf.Shop.IsEnabled)
return BuyResult.UnknownItem;
return BuyResult.XpShopDisabled;
if (conf.Shop.TierRequirement != PatronTier.None)
var req = type == XpShopItemType.Background
? conf.Shop.BgsTierRequirement
: conf.Shop.FramesTierRequirement;
if (req != PatronTier.None && !_creds.IsOwner(userId))
{
var patron = await _ps.GetPatronAsync(userId);
if ((int)patron.Tier < (int)conf.Shop.TierRequirement)
if ((int)patron.Tier < (int)req)
return BuyResult.InsufficientPatronTier;
}
@@ -1468,8 +1573,12 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
return false;
}
public PatronTier GetXpShopTierRequirement()
=> _xpConfig.Data.Shop.TierRequirement;
public PatronTier GetXpShopTierRequirement(Xp.XpShopInputType type)
=> type switch
{
Xp.XpShopInputType.F => _xpConfig.Data.Shop.FramesTierRequirement,
_ => _xpConfig.Data.Shop.BgsTierRequirement,
};
public bool IsShopEnabled()
=> _xpConfig.Data.Shop.IsEnabled;
@@ -1478,6 +1587,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
public enum BuyResult
{
Success,
XpShopDisabled,
AlreadyOwned,
InsufficientFunds,
UnknownItem,

View File

@@ -27,4 +27,4 @@ if (args.Length > 0 && args[0] != "run")
LogSetup.SetupLogger(shardId);
Log.Information("Pid: {ProcessId}", pid);
await new Bot(shardId, totalShards).RunAndBlockAsync();
await new Bot(shardId, totalShards, Environment.GetEnvironmentVariable("NadekoBot__creds")).RunAndBlockAsync();

View File

@@ -67,6 +67,9 @@ public sealed class DiscordEmbedBuilderWrapper : IEmbedBuilder
_ => throw new ArgumentOutOfRangeException(nameof(color), "Unsupported EmbedColor type")
};
public IEmbedBuilder WithDiscordColor(Color color)
=> Wrap(embed.WithColor(color));
public Embed Build()
=> embed.Build();

View File

@@ -18,11 +18,9 @@ public sealed class BotCredsProvider : IBotCredsProvider
private const string CREDS_FILE_NAME = "creds.yml";
private const string CREDS_EXAMPLE_FILE_NAME = "creds_example.yml";
private string CredsPath
=> Path.Combine(Directory.GetCurrentDirectory(), CREDS_FILE_NAME);
private string CredsPath { get; }
private string CredsExamplePath
=> Path.Combine(Directory.GetCurrentDirectory(), CREDS_EXAMPLE_FILE_NAME);
private string CredsExamplePath { get; }
private readonly int? _totalShards;
@@ -34,9 +32,21 @@ public sealed class BotCredsProvider : IBotCredsProvider
private readonly object _reloadLock = new();
private readonly IDisposable _changeToken;
public BotCredsProvider(int? totalShards = null)
public BotCredsProvider(int? totalShards = null, string credPath = null)
{
_totalShards = totalShards;
if (!string.IsNullOrWhiteSpace(credPath))
{
CredsPath = credPath;
CredsExamplePath = Path.Combine(Path.GetDirectoryName(credPath), CREDS_EXAMPLE_FILE_NAME);
}
else
{
CredsPath = Path.Combine(Directory.GetCurrentDirectory(), CREDS_FILE_NAME);
CredsExamplePath = Path.Combine(Directory.GetCurrentDirectory(), CREDS_EXAMPLE_FILE_NAME);
}
try
{
if (!File.Exists(CredsExamplePath))

View File

@@ -7,7 +7,7 @@ namespace NadekoBot.Services;
public sealed class StatsService : IStatsService, IReadyExecutor, INService
{
public const string BOT_VERSION = "4.3.0";
public const string BOT_VERSION = "4.3.5";
public string Author
=> "Kwoth#2452";

View File

@@ -33,7 +33,7 @@ public static class MessageChannelExtensions
public static async Task<IUserMessage> SendAsync(
this IMessageChannel channel,
string? plainText,
NadekoButtonInteraction? inter,
NadekoInteraction? inter,
Embed? embed = null,
IReadOnlyCollection<Embed>? embeds = null,
bool sanitizeAll = false)
@@ -72,7 +72,7 @@ public static class MessageChannelExtensions
IEmbedBuilder? embed,
string plainText = "",
IReadOnlyCollection<IEmbedBuilder>? embeds = null,
NadekoButtonInteraction? inter = null)
NadekoInteraction? inter = null)
=> ch.SendAsync(plainText,
inter,
embed: embed?.Build(),
@@ -83,7 +83,7 @@ public static class MessageChannelExtensions
IEmbedBuilderService eb,
string text,
MessageType type,
NadekoButtonInteraction? inter = null)
NadekoInteraction? inter = null)
{
var builder = eb.Create().WithDescription(text);

View File

@@ -56,7 +56,7 @@ public static class SocketMessageComponentExtensions
IEmbedBuilder? embed,
string plainText = "",
IReadOnlyCollection<IEmbedBuilder>? embeds = null,
NadekoButtonInteraction? inter = null,
NadekoInteraction? inter = null,
bool ephemeral = false)
=> smc.RespondAsync(plainText,
embed: embed?.Build(),
@@ -69,7 +69,7 @@ public static class SocketMessageComponentExtensions
string text,
MessageType type,
bool ephemeral = false,
NadekoButtonInteraction? inter = null)
NadekoInteraction? inter = null)
{
var builder = eb.Create().WithDescription(text);

View File

@@ -174,6 +174,14 @@ settopic:
setchanlname:
- setchanlname
- schn
# thread stuff
threadcreate:
- threadcreate
- thcr
threaddelete:
- threaddelete
- thdel
- thrm
prune:
- prune
- clear
@@ -209,6 +217,9 @@ serverinfo:
channelinfo:
- channelinfo
- cinfo
roleinfo:
- roleinfo
- rinfo
userinfo:
- userinfo
- uinfo
@@ -374,6 +385,7 @@ award:
- award
take:
- take
- seize
betroll:
- betroll
- br
@@ -710,6 +722,13 @@ showemojis:
emojiadd:
- emojiadd
- ea
emojiremove:
- emojiremove
- emojirm
- er
- ed
- emojidel
- emojidelete
deckshuffle:
- deckshuffle
- dsh
@@ -1064,6 +1083,10 @@ xpadd:
- xpadd
xpshop:
- xpshop
xpshopbuy:
- xpshopbuy
xpshopuse:
- xpshopuse
clubcreate:
- clubcreate
clubtransfer:
@@ -1304,6 +1327,7 @@ medusalist:
medusainfo:
- medusainfo
- meinfo
# Bank stuff
bankdeposit:
- deposit
- d
@@ -1316,6 +1340,11 @@ bankbalance:
- balance
- b
- bal
banktake:
- take
- seize
bankaward:
- award
# Patron
patron:
- patron

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -423,6 +423,10 @@ channelinfo:
desc: "Shows info about the channel. If no channel is supplied, it defaults to current one."
args:
- "#some-channel"
roleinfo:
desc: "Shows info about the specified role."
args:
- "Gamers"
userinfo:
desc: "Shows info about the user. If no user is supplied, it defaults a user running the command."
args:
@@ -1217,6 +1221,10 @@ emojiadd:
- ":someonesCustomEmoji:"
- "MyEmojiName :someonesCustomEmoji:"
- "owoNice https://cdn.discordapp.com/emojis/587930873811173386.png?size=128"
emojiremove:
desc: "Removes the specified emoji or emojis from this server."
args:
- ":eagleWarrior: :plumedArcher:"
deckshuffle:
desc: "Reshuffles all cards back into the deck."
args:
@@ -2128,6 +2136,16 @@ xpshop:
- "bgs"
- "frames"
- "bgs 3"
xpshopbuy:
desc: "Buy an item from the xp shop by specifying the type and the key of the item."
args:
- "bg open_sea"
- "fr gold"
xpshopuse:
desc: "Use a previously purchased item from the xp shop by specifying the type and the key of the item."
args:
- "bg synth"
- "fr default"
bible:
desc: "Shows bible verse. You need to supply book name and chapter:verse"
args:
@@ -2227,6 +2245,14 @@ bankbalance:
desc: "Shows your current bank balance available for withdrawal."
args:
- ""
banktake:
desc: "Takes the specified amount of currency from a user's bank"
args:
- "500 @MoniLaunder"
bankaward:
desc: "Award the specified amount of currency to a user's bank"
args:
- "99999 @Bestie"
patron:
desc: "Check your patronage status and command usage quota. Bot owners can check targeted user's patronage status."
args:
@@ -2268,3 +2294,11 @@ bettest:
- ""
- "betflip 1000"
- "slot 2000"
threadcreate:
desc: "Create a public thread with the specified title. You may optionally reply to a message to have it as a starting point."
args:
- "Q&A"
threaddelete:
desc: "Delete a thread with the specified name in this channel. Case insensitive."
args:
- "Q&A"

View File

@@ -1,5 +1,5 @@
{
"api_key_missing": "Der API Key der für diesen Befehl benötigt wird ist nicht angegeben.",
"api_key_missing": "Der API Key fehlt.",
"quote_deleted": "Zitat gelöscht",
"redacted_too_long": "Wurde entfernt da es zu lang ist.",
"trigger": "Auslöser",
@@ -10,7 +10,7 @@
"banned_user": "Nutzer gebannt",
"byedel_off": "Die automatische Löschung der Verabschiedungs Nachrichten wurde deaktiviert.",
"byedel_on": "Verabschiedungs Nachrichten werden nach {0} Sekunden gelöscht.",
"byemsg_cur": "Derzeitige Verabschiedungs Nachricht",
"byemsg_cur": "Derzeitige Verabschiedungs Nachricht: {0}",
"byemsg_enable": "Beim schreiben von {0} werden die Verabschiedungs Nachrichten aktiviert.",
"byemsg_new": "Neue Verabschiedungs Nachricht gesetzt",
"bye_off": "Abschiedansagen ausgeschaltet.",
@@ -922,9 +922,9 @@
"pages": "Seiten",
"favorites": "Favoriten",
"tags": "Stichworte",
"invalid_emoji_link": "",
"emoji_add_error": "",
"emoji_added": "",
"invalid_emoji_link": "Der angegebene Link is entweder kein Bild, oder größer als 256KB.",
"emoji_add_error": "Fehler beim hinzufügen des Emoji. Es sind keine Emoji slots mehr vorhanden, oder die Bildgröße ist unangemessen.",
"emoji_added": "Neuen Emoji hinzugefügt: {0}",
"boost_on": "",
"boost_off": "",
"boostmsg_cur": "",
@@ -932,16 +932,16 @@
"boostmsg_new": "",
"boostdel_off": "",
"boostdel_on": "",
"log_ignored_channels": "",
"log_ignored_users": "",
"log_ignore_user": "",
"log_not_ignore_user": "",
"log_ignore_chan": "",
"log_not_ignore_chan": "",
"log_ignored_channels": "Ignorierte Kanäle",
"log_ignored_users": "Ignorierte User",
"log_ignore_user": "Logging wird User {0} ignorieren",
"log_not_ignore_user": "Logging wird nicht länger User {0} ignorieren",
"log_ignore_chan": "Logging wird Kanal {0} ignorieren",
"log_not_ignore_chan": "Logging wird nicht länger Kanal {0} ignorieren",
"streams_cleared": "",
"warn_weight": "",
"warn_count": "",
"mass_ban_in_progress": "",
"warn_weight": "Gewicht: {0}",
"warn_count": "{0} momentan, {1} insgesamt",
"mass_ban_in_progress": "Es werden {0} User gebannt...",
"mass_ban_completed": "",
"reminder_server_list": "",
"imageonly_enable": "",
@@ -975,5 +975,77 @@
"exprs_cleared": "",
"expr_reset": "",
"expr_set": "",
"expr_edited": ""
"expr_edited": "",
"stream_online_delete_enabled": "",
"stream_online_delete_disabled": "",
"club_create_error_name": "",
"club_desc_update": "",
"bank_accounts": "",
"module_description_medusa": "",
"list_of_medusae": "",
"list_of_unloaded": "",
"medusa_name_not_found": "",
"medusa_info": "",
"sneks_count": "",
"commands_count": "",
"no_medusa_loaded": "",
"no_medusa_available": "",
"loaded_medusae": "",
"medusa_not_loaded": "",
"medusa_possibly_cant_unload": "",
"medusa_loaded": "",
"medusa_unloaded": "",
"medusa_empty": "",
"medusa_already_loaded": "",
"medusa_invalid_not_found": "",
"bank_balance": "",
"bank_deposited": "",
"bank_withdrew": "",
"bank_withdraw_insuff": "",
"cmd_group_commands": "",
"limit_reached": "",
"feature_limit_reached_you": "",
"feature_limit_reached_owner": "",
"feature_limit_reached_either": "",
"tier": "",
"pledge": "",
"expires": "",
"commands": "",
"groups": "",
"modules": "",
"no_quota_found": "",
"patron_info": "",
"quotas": "",
"patron_not_enabled": "",
"results_in": "",
"patron_msg_sent": "",
"cards": "",
"hand_value": "",
"roll2": "",
"rolls": "",
"available_tests": "",
"test_results_for": "",
"multiplier": "",
"trivia_ended": "",
"card": "",
"guess": "",
"repeater_skip_next": "",
"repeater_dont_skip_next": "",
"remind_timely": "",
"xp_shop_disabled": "",
"timely_time": "",
"buy": "",
"use": "",
"in_use": "",
"xp_shop_item_cant_use": "",
"linkonly_enable": "",
"linkonly_disable": "",
"xp_shop_buy_required_tier": "",
"available_commands": "",
"xpadd_users": "",
"xpshop_buy_success": "",
"patron_insuff_tier": "",
"xpshop_already_owned": "",
"xpshop_item_not_found": "",
"xpshop_website": ""
}

View File

@@ -12,6 +12,7 @@
"invalid_emoji_link": "Specified link is either not an image or exceeds 256KB.",
"emoji_add_error": "Error adding emoji. You either ran out of emoji slots, or image size is inadequate.",
"emoji_added": "Added a new emoji: {0}",
"emoji_not_removed": "The following emojis were not removed: {0}",
"fw_cleared": "Removed all filtered words and filtered words channel settings.",
"aliases_cleared": "All {0} aliases on this server have been removed.",
"no_results": "No results found.",
@@ -141,6 +142,10 @@
"rar_err": "Failed to remove roles. I have insufficient permissions.",
"rc": "Color of {0} role has been changed.",
"rc_perms": "Error occurred due to invalid color or insufficient permissions.",
"color": "Color",
"icon": "Icon",
"hoisted": "Hoisted",
"mentionable": "Mentionable",
"remrole": "Successfully removed role {0} from user {1}",
"remrole_err": "Failed to remove role. I have insufficient permissions.",
"renrole": "Role renamed.",
@@ -1003,7 +1008,7 @@
"feature_limit_reached_you": "You've reached the limit of {0} for the {1} feature. You may be able to increase this limit by upgrading your patron tier.",
"feature_limit_reached_owner": "Server owner has reached the limit of {0} for the {1} feature. Server owner may be able to upgrade this limit by upgrading patron tier.",
"feature_limit_reached_either": "The limit of {0} for the {1} feature has been reached. Either you or the server owner may able to upgrade this limit by upgrading the patron tier.",
"xp_shop_buy_required_tier": "Buying items from the shop requires Patron Tier {0} or higher.",
"xp_shop_buy_required_tier": "Buying items from this shop requires Patron Tier {0} or higher.",
"available_commands": "Available Commands",
"tier": "Tier",
"pledge": "Pledge",
@@ -1017,5 +1022,10 @@
"patron_not_enabled": "Patron system is disabled.",
"results_in": "{0} results in {1}s",
"patron_msg_sent": "Done sending messages to patrons at and above tier {1}. {1} successfully sent and {2} failed.",
"xpadd_users": "Added {0} server XP to {1} users."
"xpadd_users": "Added {0} server XP to {1} users.",
"xpshop_buy_success": "Successfully purchased `{0}/{1}`",
"patron_insuff_tier": "Your Patron Tier insufficient to perform this action.",
"xpshop_already_owned": "You already own this item.",
"xpshop_item_not_found": "An item with that key doesn't exist.",
"xpshop_website": "You can see the list of all Xp Shop items here: <https://xpshop.nadeko.bot>"
}

View File

@@ -975,5 +975,77 @@
"exprs_cleared": "L'ensemble des {0} expressions du serveur ont été retirées.",
"expr_reset": "L'expression avec l'id {0} ne rajoutera plus de réactions.",
"expr_set": "L'expression avec l'id {0} ajoutera automatiquement ces réactions au message de réponse : {1}",
"expr_edited": "Expression éditée"
"expr_edited": "Expression éditée",
"stream_online_delete_enabled": "Aucune méduse de ce nom n'a été trouvée ou le fichier était invalide",
"stream_online_delete_disabled": "Les notifications de diffusions en ligne ne seront plus supprimées quand la diffusion sera hors ligne.",
"club_create_error_name": "Échec de la création du club. Un club existe du même nom existe déjà.",
"club_desc_update": "La description du club a été mise à jour",
"bank_accounts": "Comptes bancaires",
"module_description_medusa": "**Unique au propriétaire du bot.** Charger, décharger et maintenez des modules dynamiques. Découvrez en plus [ici](https://nadekobot.readthedocs.io/en/latest/medusa/creating-a-medusa/)",
"list_of_medusae": "Liste des méduses",
"list_of_unloaded": "Liste des méduses disponible",
"medusa_name_not_found": "Une méduse de ce nom n'existe pas ou n'est pas chargée.",
"medusa_info": "Information de la méduse",
"sneks_count": "Sneks ({0})",
"commands_count": "Commandes ({0})",
"no_medusa_loaded": "Aucunes méduses de chargées.",
"no_medusa_available": "Aucune méduse de disponible.",
"loaded_medusae": "Méduses chargées",
"medusa_not_loaded": "La méduse de ce nom n'est pas chargée.",
"medusa_possibly_cant_unload": "La méduse n'est probablement pas entièrement chargée. Redémarrez le bot si le problème persiste.",
"medusa_loaded": "La méduse {0} a été chargée.",
"medusa_unloaded": "La méduse {0} a été déchargée.",
"medusa_empty": "La méduse n'a pas été chargée puisqu'elle ne contient aucun Sneks.",
"medusa_already_loaded": "La méduse {0} est déjà chargée",
"medusa_invalid_not_found": "Aucune méduse de ce nom n'a été trouvée ou le fichier était invalide",
"bank_balance": "Vous disposez de {0} sur votre compte bancaire.",
"bank_deposited": "Vous avez déposé {0} sur votre compte bancaire.",
"bank_withdrew": "Vous avez retiré {0} de votre compte bancaire.",
"bank_withdraw_insuff": "Vous ne disposez pas suffisamment de {0} sur votre compte bancaire.",
"cmd_group_commands": "Groupe de commande '{0}'",
"limit_reached": "La limite des fonctionnalités de {0} a été atteinte.",
"feature_limit_reached_you": "Vous avez atteint la limite de {0} pour la fonctionnalité {1}. Vous pouvez augmenter cette limite en augmentant votre niveau de contribution.",
"feature_limit_reached_owner": "Le propriétaire du serveur a atteint la limite de {0} pour la fonctionnalité {1}. Ce dernier peut augmenter cette limite en augmentant son niveau de contribution.",
"feature_limit_reached_either": "La limite de {0} pour la fonctionnalité {1} a été atteinte. Soit vous ou le propriétaire du serveur peut augmenter cette limite en augmentant le niveau de contribution.",
"tier": "Niveau",
"pledge": "Promesse",
"expires": "Expire",
"commands": "Commandes",
"groups": "Groupes",
"modules": "Modules",
"no_quota_found": "Aucun quota de trouvé.",
"patron_info": "Info de la·e contributeur·rice",
"quotas": "<<< Quotas >>>",
"patron_not_enabled": "Le système de contribution est désactivé.",
"results_in": "{0} résulte en {1}s",
"patron_msg_sent": "L'envoi de messages aux contributeur·rice·s de niveau {1} et au-delà a été effectué. {1} ont été envoyés avec succès et {2} ont échoué.",
"cards": "Cartes",
"hand_value": "Valeur de la main",
"roll2": "Lancé",
"rolls": "Lancés",
"available_tests": "Tests disponibles",
"test_results_for": "Résultats du test pour {0}",
"multiplier": "Multiplier",
"trivia_ended": "Le jeu de culture générale est terminé",
"card": "Carte",
"guess": "Deviner",
"repeater_skip_next": "Le prochain rappel de ce répéteur sera ignoré.",
"repeater_dont_skip_next": "Le prochain rappel de ce répéteur ne sera pas ignoré.",
"remind_timely": "Je vous notifierai à propos de votre récompense opportune {0}",
"xp_shop_disabled": "Le magasin d'XP est désactivé par le propriétaire, ou il n'y a aucun item à vendre.",
"timely_time": "Il est l'heure de votre récompense opportune.",
"buy": "Acheter",
"use": "Utiliser",
"in_use": "En utilisation",
"xp_shop_item_cant_use": "Vous ne pouvez pas utiliser cet item car il ne vous appartient pas ou il n'existe pas.",
"linkonly_enable": "Ce channel est désormais limité aux liens.",
"linkonly_disable": "Ce channel n'est désormais plus limité aux liens.",
"xp_shop_buy_required_tier": "Acheter des items de ce magasin requiert d'être Tier Patron {0} ou au-delà.",
"available_commands": "Commandes disponibles",
"xpadd_users": "Ajout de {0} XP du serveur à l'utilisateur {1}.",
"xpshop_buy_success": "Vous avez acheté avec succès `{0}/{1}`",
"patron_insuff_tier": "Votre Tier Patron est insuffisant pour réaliser cette action.",
"xpshop_already_owned": "Vous disposez déjà de cet item.",
"xpshop_item_not_found": "Aucun item avec cette clé n'existe.",
"xpshop_website": "Vous pouvez voir la liste de tous les items du magasin d'XP ici: <https://xpshop.nadeko.bot>"
}

View File

@@ -1,5 +1,5 @@
# DO NOT CHANGE
version: 3
version: 5
# How much XP will the users receive per message
xpPerMessage: 3
# How often can the users receive XP in minutes
@@ -16,9 +16,12 @@ shop:
# True -> Users can access the xp shop using .xpshop command
# False -> Users can't access the xp shop
isEnabled: false
# Which patron tier do users need in order to use the .xpshop command
# Which patron tier do users need in order to use the .xpshop bgs command
# Leave at 'None' if patron system is disabled or you don't want any restrictions
tierRequirement: None
bgsTierRequirement: None
# Which patron tier do users need in order to use the .xpshop frames command
# Leave at 'None' if patron system is disabled or you don't want any restrictions
framesTierRequirement: None
# Frames available for sale. Keys are unique IDs.
# Do not change keys as they are not publicly visible. Only change properties (name, price, id)
# Removing a key which previously existed means that all previous purchases will also be unusable.
@@ -31,6 +34,8 @@ shop:
price: 0
# Direct url to the .png image which will be applied to the user's XP card
url: ''
# Optional preview url which will show instead of the real URL in the shop
preview:
# Optional description of the item
desc:
# Backgrounds available for sale. Keys are unique IDs.
@@ -45,5 +50,7 @@ shop:
price: 0
# Direct url to the .png image which will be applied to the user's XP card
url: ''
# Optional preview url which will show instead of the real URL in the shop
preview:
# Optional description of the item
desc:

View File

@@ -10,8 +10,8 @@
"Show": true,
"FontSize": 50,
"Pos": {
"X": 130,
"Y": 17
"X": 105,
"Y": 25
}
},
"Icon": {
@@ -65,7 +65,7 @@
"Bar": {
"Show": true,
"Global": {
"Color": "00000066",
"Color": "00000095",
"PointA": {
"X": 321,
"Y": 104
@@ -78,7 +78,7 @@
"Direction": 3
},
"Guild": {
"Color": "00000066",
"Color": "00000095",
"PointA": {
"X": 282,
"Y": 248
@@ -114,7 +114,7 @@
"Show": true,
"FontSize": 25,
"Pos": {
"X": 490,
"X": 450,
"Y": 345
}
}
@@ -138,7 +138,7 @@
"FontSize": 35,
"Pos": {
"X": 650,
"Y": 49
"Y": 55
}
}
}