Merge branch '4.3' into 'v4'

Base for 4.3 work.

See merge request Kwoth/nadekobot!259
This commit is contained in:
Kwoth
2022-07-27 03:47:47 +00:00
271 changed files with 25918 additions and 3173 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,7 @@
#Manually added files
src/NadekoBot/data/last_known_version.txt
# medusa stuff
!src/NadekoBot/data/medusae/medusa.yml
src/NadekoBot/data/medusae/**

View File

@@ -2,6 +2,62 @@
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
## [4.3.0] - 27.07.2022
### Added
- Added `.bettest` command which lets you test many gambling commands
- Better than .slottest
- Counts win/loss streaks too
- Doesn't count 1x returns as neither wins nor losses
- multipliers < 1 are considered losses, > 1 considered wins
- Added `.betdraw` command which lets you guess red/black and/or high/low for a random card
- 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 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>`
- 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`
### Changed
- `.t` Trivia code cleaned up, added ALL pokemon generations
- `.xpadd` will now work on roles too. It will add the specified xp to each user (visible to the bot) in the role
- Improved / cleaned up / modernized how most gambling commands look
- `.roll`
- `.rolluo`
- `.draw`
- `.flip`
- `.slot`
- `.betroll`
- `.betflip`
- Try them out!
- `.draw`, `.betdraw` and some other card commands (not all) will use the new, rewritten deck system
- Error will be printed to the console if there's a problem in `.plant`
- [dev] Split Nadeko.Common into a separate project
- [dev] It will contain classes/utilities which can be shared across different nadeko related projects
- [dev] Split Nadeko.Econ into a separate project
- [dev] It should be home for the backend any gambling/currency/economy feature
- [dev] It will contain most gambling games and any shared logic
- [dev] Compliation should take less time and RAM
- [dev] No longer using generator and partial methods for commands
### Fixed
- `.slot` will now show correct multipliers if they've been modified
- Fix patron errors showing up even with permissions disabling the command
- Fixed an issue with voice xp breaking xp gain.
### Removed
- Removed `.slottest`, replaced by `.bettest`
- Removed `.wof`, replaced by `.lula`
- [dev] Removed a lot of unused methods
- [dev] Removed several unused response strings
## [4.2.15] - 12.07.2022
### Fixed

View File

@@ -12,6 +12,7 @@ ProjectSection(SolutionItems) = preProject
README.md = README.md
.gitlab-ci.yml = .gitlab-ci.yml
Dockerfile = Dockerfile
NuGet.Config = NuGet.Config
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NadekoBot", "src\NadekoBot\NadekoBot.csproj", "{45EC1473-C678-4857-A544-07DFE0D0B478}"
@@ -30,6 +31,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.VotesApi", "src\N
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nadeko.Medusa", "src\Nadeko.Medusa\Nadeko.Medusa.csproj", "{E685977E-31A4-46F4-A5D7-4E3E39E82E43}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nadeko.Common", "src\Nadeko.Common\Nadeko.Common.csproj", "{A6022F5F-A764-4D3F-847B-36F0391FF659}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nadeko.Econ", "src\Nadeko.Econ\Nadeko.Econ.csproj", "{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -79,6 +84,18 @@ Global
{E685977E-31A4-46F4-A5D7-4E3E39E82E43}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
{E685977E-31A4-46F4-A5D7-4E3E39E82E43}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E685977E-31A4-46F4-A5D7-4E3E39E82E43}.Release|Any CPU.Build.0 = Release|Any CPU
{A6022F5F-A764-4D3F-847B-36F0391FF659}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6022F5F-A764-4D3F-847B-36F0391FF659}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6022F5F-A764-4D3F-847B-36F0391FF659}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
{A6022F5F-A764-4D3F-847B-36F0391FF659}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
{A6022F5F-A764-4D3F-847B-36F0391FF659}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6022F5F-A764-4D3F-847B-36F0391FF659}.Release|Any CPU.Build.0 = Release|Any CPU
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -92,6 +109,8 @@ Global
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{E685977E-31A4-46F4-A5D7-4E3E39E82E43} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{A6022F5F-A764-4D3F-847B-36F0391FF659} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
{4F4FBF7C-74F0-4AE4-B451-9E60BDCA9C37} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}

6
NuGet.Config Normal file
View File

@@ -0,0 +1,6 @@
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="nadeko.bot" value="https://www.myget.org/F/nadeko/api/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>

11
privacy-policy.md Normal file
View File

@@ -0,0 +1,11 @@
# Privacy Policy
## Profile Information
Nadeko stores userids, avatars, usernames, discriminators and nicknames of users who were targeted by or have used commands which require Xp, Clubs or Waifu features (not limited to these, as other features may be added over time).
## Other
Nadeko doesn't do analytics, doesn't store messages, doesn't track users, doesn't store their emails etc.
Nadeko only stores user settings and states as the result of executed commands or as the effect of administration tools (for example warnings or protection commands).
## Sensitive Information
Nadeko doesn't store sensitive information, and users are strongly discouraged from adding their passwords, keys, or other important information as quotes or expressions.

View File

@@ -1,7 +1,6 @@
#nullable disable
using System.Runtime.CompilerServices;
using System.Runtime.CompilerServices;
namespace NadekoBot.Common;
namespace Nadeko.Common;
public class AsyncLazy<T> : Lazy<Task<T>>
{

View File

@@ -1,14 +1,9 @@
#nullable enable
#pragma warning disable
// License MIT
// Source: https://github.com/i3arnon/ConcurrentHashSet
using System.Diagnostics;
using System.Diagnostics;
namespace System.Collections.Generic;
[DebuggerDisplay("{_backingStore.Count}")]
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T>
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T> where T : notnull
{
private readonly ConcurrentDictionary<T, bool> _backingStore;

View File

@@ -1,8 +1,11 @@
#nullable disable
using NadekoBot.Services.Database.Models;
using System.Collections;
using System.Collections;
namespace NadekoBot.Common.Collections;
namespace Nadeko.Common;
public interface IIndexed
{
int Index { get; set; }
}
public class IndexedCollection<T> : IList<T>
where T : class, IIndexed

View File

@@ -1,6 +1,4 @@
using System.Buffers;
namespace NadekoBot.Extensions;
namespace Nadeko.Common;
// made for expressions because they almost never get added
// and they get looped through constantly
@@ -32,6 +30,14 @@ public static class ArrayExtensions
public static TOut[] Map<TIn, TOut>(this TIn[] arr, Func<TIn, TOut> f)
=> Array.ConvertAll(arr, x => f(x));
/// <summary>
/// Creates a new array by applying the specified function to every element in the input array
/// </summary>
/// <param name="col">Array to modify</param>
/// <param name="f">Function to apply</param>
/// <typeparam name="TIn">Orignal type of the elements in the array</typeparam>
/// <typeparam name="TOut">Output type of the elements of the array</typeparam>
/// <returns>New array with updated elements</returns>
public static TOut[] Map<TIn, TOut>(this IReadOnlyCollection<TIn> col, Func<TIn, TOut> f)
{
var toReturn = new TOut[col.Count];

View File

@@ -1,8 +1,6 @@
using NadekoBot.Common.Collections;
using NadekoBot.Services.Database.Models;
using System.Security.Cryptography;
namespace NadekoBot.Extensions;
namespace Nadeko.Common;
public static class EnumerableExtensions
{

View File

@@ -0,0 +1,35 @@
using System.Net.Http.Headers;
namespace Nadeko.Common;
public static class HttpClientExtensions
{
public static HttpClient AddFakeHeaders(this HttpClient http)
{
AddFakeHeaders(http.DefaultRequestHeaders);
return http;
}
public static void AddFakeHeaders(this HttpHeaders dict)
{
dict.Clear();
dict.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
dict.Add("User-Agent",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1");
}
public static bool IsImage(this HttpResponseMessage msg)
=> IsImage(msg, out _);
public static bool IsImage(this HttpResponseMessage msg, out string? mimeType)
{
mimeType = msg.Content.Headers.ContentType?.MediaType;
if (mimeType is "image/png" or "image/jpeg" or "image/gif")
return true;
return false;
}
public static long GetContentLength(this HttpResponseMessage msg)
=> msg.Content.Headers.ContentLength ?? long.MaxValue;
}

View File

@@ -1,5 +1,4 @@
namespace NadekoBot.Extensions;
namespace Nadeko.Common;
public delegate TOut PipeFunc<TIn, out TOut>(in TIn a);
public delegate TOut PipeFunc<TIn1, TIn2, out TOut>(in TIn1 a, in TIn2 b);

View File

@@ -0,0 +1 @@
global using NonBlocking;

View File

@@ -1,9 +1,9 @@
#nullable disable
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes;
using System.Text;
using Serilog;
namespace NadekoBot.Services;
namespace Nadeko.Common;
public static class LogSetup
{

View File

@@ -1,5 +1,4 @@
#nullable disable
namespace NadekoBot.Services;
namespace Nadeko.Common;
public static class StandardConversions
{

View File

@@ -1,7 +1,6 @@
#nullable disable
using System.Runtime.CompilerServices;
using System.Runtime.CompilerServices;
namespace NadekoBot.Common;
namespace Nadeko.Common;
// needs proper invalid input check (character array input out of range)
// needs negative number support
@@ -90,7 +89,7 @@ public readonly struct kwum : IEquatable<kwum>
return new(chars);
}
public override bool Equals(object obj)
public override bool Equals(object? obj)
=> obj is kwum kw && kw == this;
public bool Equals(kwum other)

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NonBlocking" Version="2.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
</ItemGroup>
</Project>

View File

@@ -1,7 +1,7 @@
#nullable disable
using System.Security.Cryptography;
namespace NadekoBot.Common;
namespace Nadeko.Common;
public class NadekoRandom : Random
{
@@ -25,6 +25,20 @@ public class NadekoRandom : Random
_rng.GetBytes(bytes);
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)
{

View File

@@ -1,6 +1,7 @@
using System.Threading.Channels;
using Serilog;
namespace NadekoBot.Common;
namespace Nadeko.Common;
public sealed class QueueRunner
{

View File

@@ -1,14 +1,11 @@
#nullable disable
using System;
namespace Nadeko.Common;
namespace NadekoBot.Common;
public struct ShmartNumber : IEquatable<ShmartNumber>
public readonly struct ShmartNumber : IEquatable<ShmartNumber>
{
public long Value { get; }
public string Input { get; }
public string? Input { get; }
public ShmartNumber(long val, string input = null)
public ShmartNumber(long val, string? input = null)
{
Value = val;
Input = input;
@@ -26,14 +23,14 @@ public struct ShmartNumber : IEquatable<ShmartNumber>
public override string ToString()
=> Value.ToString();
public override bool Equals(object obj)
public override bool Equals(object? obj)
=> obj is ShmartNumber sn && Equals(sn);
public bool Equals(ShmartNumber other)
=> other.Value == Value;
public override int GetHashCode()
=> Value.GetHashCode() ^ Input.GetHashCode(StringComparison.InvariantCulture);
=> Value.GetHashCode();
public static bool operator ==(ShmartNumber left, ShmartNumber right)
=> left.Equals(right);

View File

@@ -1,5 +1,5 @@
#nullable disable
namespace NadekoBot.Modules.Gambling.Common;
namespace Nadeko.Econ;
public class Deck
{
@@ -272,7 +272,7 @@ public class Deck
public string GetValueText()
=> _cardNames[Number];
public override string ToString()
=> _cardNames[Number] + " Of " + Suit;

View File

@@ -0,0 +1,5 @@
namespace Nadeko.Econ;
public abstract record class NewCard<TSuit, TValue>(TSuit Suit, TValue Value)
where TSuit : struct, Enum
where TValue : struct, Enum;

View File

@@ -0,0 +1,54 @@
namespace Nadeko.Econ;
public abstract class NewDeck<TCard, TSuit, TValue>
where TCard: NewCard<TSuit, TValue>
where TSuit : struct, Enum
where TValue : struct, Enum
{
protected static readonly TSuit[] _suits = Enum.GetValues<TSuit>();
protected static readonly TValue[] _values = Enum.GetValues<TValue>();
public virtual int CurrentCount
=> _cards.Count;
public virtual int TotalCount { get; }
protected readonly LinkedList<TCard> _cards = new();
public NewDeck()
{
TotalCount = _suits.Length * _values.Length;
}
public virtual TCard? Draw()
{
var first = _cards.First;
if (first is not null)
{
_cards.RemoveFirst();
return first.Value;
}
return null;
}
public virtual TCard? Peek(int x = 0)
{
var card = _cards.First;
for (var i = 0; i < x; i++)
{
card = card?.Next;
}
return card?.Value;
}
public virtual void Shuffle()
{
var cards = _cards.ToList();
var newCards = cards.Shuffle();
_cards.Clear();
foreach (var card in newCards)
_cards.AddFirst(card);
}
}

View File

@@ -0,0 +1,28 @@
namespace Nadeko.Econ;
public class MultipleRegularDeck : NewDeck<RegularCard, RegularSuit, RegularValue>
{
private int Decks { get; }
public override int TotalCount { get; }
public MultipleRegularDeck(int decks = 1)
{
if (decks < 1)
throw new ArgumentOutOfRangeException(nameof(decks), "Has to be more than 0");
Decks = decks;
TotalCount = base.TotalCount * decks;
for (var i = 0; i < Decks; i++)
{
foreach (var suit in _suits)
{
foreach (var val in _values)
{
_cards.AddLast((RegularCard)Activator.CreateInstance(typeof(RegularCard), suit, val)!);
}
}
}
}
}

View File

@@ -0,0 +1,4 @@
namespace Nadeko.Econ;
public sealed record class RegularCard(RegularSuit Suit, RegularValue Value)
: NewCard<RegularSuit, RegularValue>(Suit, Value);

View File

@@ -0,0 +1,15 @@
namespace Nadeko.Econ;
public sealed class RegularDeck : NewDeck<RegularCard, RegularSuit, RegularValue>
{
public RegularDeck()
{
foreach (var suit in _suits)
{
foreach (var val in _values)
{
_cards.AddLast((RegularCard)Activator.CreateInstance(typeof(RegularCard), suit, val)!);
}
}
}
}

View File

@@ -0,0 +1,56 @@
namespace Nadeko.Econ;
public static class RegularDeckExtensions
{
public static string GetEmoji(this RegularSuit suit)
=> suit switch
{
RegularSuit.Hearts => "♥️",
RegularSuit.Spades => "♠️",
RegularSuit.Diamonds => "♦️",
_ => "♣️",
};
public static string GetEmoji(this RegularValue value)
=> value switch
{
RegularValue.Ace => "🇦",
RegularValue.Two => "2⃣",
RegularValue.Three => "3⃣",
RegularValue.Four => "4⃣",
RegularValue.Five => "5⃣",
RegularValue.Six => "6⃣",
RegularValue.Seven => "7⃣",
RegularValue.Eight => "8⃣",
RegularValue.Nine => "9⃣",
RegularValue.Ten => "🔟",
RegularValue.Jack => "🇯",
RegularValue.Queen => "🇶",
_ => "🇰",
};
public static string GetEmoji(this RegularCard card)
=> $"{card.Value.GetEmoji()} {card.Suit.GetEmoji()}";
public static string GetName(this RegularValue value)
=> value.ToString();
public static string GetName(this RegularSuit suit)
=> suit.ToString();
public static string GetName(this RegularCard card)
=> $"{card.Value.ToString()} of {card.Suit.GetName()}";
}

View File

@@ -0,0 +1,9 @@
namespace Nadeko.Econ;
public enum RegularSuit
{
Hearts,
Diamonds,
Clubs,
Spades
}

View File

@@ -0,0 +1,18 @@
namespace Nadeko.Econ;
public enum RegularValue
{
Ace = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9,
Ten = 10,
Jack = 12,
Queen = 13,
King = 14,
}

View File

@@ -0,0 +1,7 @@
namespace Nadeko.Econ.Gambling.Betdraw;
public enum BetdrawColorGuess
{
Red,
Black
}

View File

@@ -0,0 +1,86 @@
using Serilog;
namespace Nadeko.Econ.Gambling.Betdraw;
public sealed class BetdrawGame
{
private static readonly NadekoRandom _rng = new();
private readonly RegularDeck _deck;
private const decimal SINGLE_GUESS_MULTI = 2.075M;
private const decimal DOUBLE_GUESS_MULTI = 4.15M;
public BetdrawGame()
{
_deck = new RegularDeck();
}
public BetdrawResult Draw(BetdrawValueGuess? val, BetdrawColorGuess? col, decimal amount)
{
if (val is null && col is null)
throw new ArgumentNullException(nameof(val));
var card = _deck.Peek(_rng.Next(0, 52))!;
var realVal = (int)card.Value < 7
? BetdrawValueGuess.Low
: BetdrawValueGuess.High;
var realCol = card.Suit is RegularSuit.Diamonds or RegularSuit.Hearts
? BetdrawColorGuess.Red
: BetdrawColorGuess.Black;
// if card is 7, autoloss
if (card.Value == RegularValue.Seven)
{
return new()
{
Won = 0M,
Multiplier = 0M,
ResultType = BetdrawResultType.Lose,
Card = card,
};
}
byte win = 0;
if (val is BetdrawValueGuess valGuess)
{
if (realVal != valGuess)
return new()
{
Won = 0M,
Multiplier = 0M,
ResultType = BetdrawResultType.Lose,
Card = card
};
++win;
}
if (col is BetdrawColorGuess colGuess)
{
if (realCol != colGuess)
return new()
{
Won = 0M,
Multiplier = 0M,
ResultType = BetdrawResultType.Lose,
Card = card
};
++win;
}
var multi = win == 1
? SINGLE_GUESS_MULTI
: DOUBLE_GUESS_MULTI;
return new()
{
Won = amount * multi,
Multiplier = multi,
ResultType = BetdrawResultType.Win,
Card = card
};
}
}

View File

@@ -0,0 +1,9 @@
namespace Nadeko.Econ.Gambling.Betdraw;
public readonly struct BetdrawResult
{
public decimal Won { get; init; }
public decimal Multiplier { get; init; }
public BetdrawResultType ResultType { get; init; }
public RegularCard Card { get; init; }
}

View File

@@ -0,0 +1,7 @@
namespace Nadeko.Econ.Gambling.Betdraw;
public enum BetdrawResultType
{
Win,
Lose
}

View File

@@ -0,0 +1,7 @@
namespace Nadeko.Econ.Gambling.Betdraw;
public enum BetdrawValueGuess
{
High,
Low,
}

View File

@@ -0,0 +1,33 @@
namespace Nadeko.Econ.Gambling;
public sealed class BetflipGame
{
private readonly decimal _winMulti;
private static readonly NadekoRandom _rng = new NadekoRandom();
public BetflipGame(decimal winMulti)
{
_winMulti = winMulti;
}
public BetflipResult Flip(byte guess, decimal amount)
{
var side = _rng.Next(0, 2);
if (side == guess)
{
return new BetflipResult()
{
Side = side,
Won = amount * _winMulti,
Multiplier = _winMulti
};
}
return new BetflipResult()
{
Side = side,
Won = 0,
Multiplier = 0,
};
}
}

View File

@@ -0,0 +1,8 @@
namespace Nadeko.Econ.Gambling;
public readonly struct BetflipResult
{
public decimal Won { get; init; }
public byte Side { get; init; }
public decimal Multiplier { get; init; }
}

View File

@@ -0,0 +1,42 @@
namespace Nadeko.Econ.Gambling;
public sealed class BetrollGame
{
private readonly (int WhenAbove, decimal MultiplyBy)[] _thresholdPairs;
private readonly NadekoRandom _rng;
public BetrollGame(IReadOnlyList<(int WhenAbove, decimal MultiplyBy)> pairs)
{
_thresholdPairs = pairs.OrderByDescending(x => x.WhenAbove).ToArray();
_rng = new();
}
public BetrollResult Roll(decimal amount = 0)
{
var roll = _rng.Next(0, 101);
for (var i = 0; i < _thresholdPairs.Length; i++)
{
ref var pair = ref _thresholdPairs[i];
if (pair.WhenAbove < roll)
{
return new()
{
Multiplier = pair.MultiplyBy,
Roll = roll,
Threshold = pair.WhenAbove,
Won = amount * pair.MultiplyBy
};
}
}
return new()
{
Multiplier = 0,
Roll = roll,
Threshold = -1,
Won = 0,
};
}
}

View File

@@ -0,0 +1,9 @@
namespace Nadeko.Econ.Gambling;
public readonly struct BetrollResult
{
public int Roll { get; init; }
public decimal Multiplier { get; init; }
public decimal Threshold { get; init; }
public decimal Won { get; init; }
}

View File

@@ -0,0 +1,75 @@
namespace Nadeko.Econ.Gambling.Rps;
public sealed class RpsGame
{
private static readonly NadekoRandom _rng = new NadekoRandom();
const decimal WIN_MULTI = 1.95m;
const decimal DRAW_MULTI = 1m;
const decimal LOSE_MULTI = 0m;
public RpsGame()
{
}
public RpsResult Play(RpsPick pick, decimal amount)
{
var compPick = (RpsPick)_rng.Next(0, 3);
if (compPick == pick)
{
return new()
{
Won = amount * DRAW_MULTI,
Multiplier = DRAW_MULTI,
ComputerPick = compPick,
Result = RpsResultType.Draw,
};
}
if ((compPick == RpsPick.Paper && pick == RpsPick.Rock)
|| (compPick == RpsPick.Rock && pick == RpsPick.Scissors)
|| (compPick == RpsPick.Scissors && pick == RpsPick.Paper))
{
return new()
{
Won = amount * LOSE_MULTI,
Multiplier = LOSE_MULTI,
Result = RpsResultType.Lose,
ComputerPick = compPick,
};
}
return new()
{
Won = amount * WIN_MULTI,
Multiplier = WIN_MULTI,
Result = RpsResultType.Win,
ComputerPick = compPick,
};
}
}
public enum RpsPick : byte
{
Rock = 0,
Paper = 1,
Scissors = 2,
}
public enum RpsResultType : byte
{
Win,
Draw,
Lose
}
public readonly struct RpsResult
{
public decimal Won { get; init; }
public decimal Multiplier { get; init; }
public RpsResultType Result { get; init; }
public RpsPick ComputerPick { get; init; }
}

View File

@@ -0,0 +1,113 @@
namespace Nadeko.Econ.Gambling;
public class SlotGame
{
private static readonly NadekoRandom _rng = new NadekoRandom();
public SlotResult Spin(decimal bet)
{
var rolls = new[]
{
_rng.Next(0, 6),
_rng.Next(0, 6),
_rng.Next(0, 6)
};
ref var a = ref rolls[0];
ref var b = ref rolls[1];
ref var c = ref rolls[2];
var multi = 0;
var winType = SlotWinType.None;
if (a == b && b == c)
{
if (a == 5)
{
winType = SlotWinType.TrippleJoker;
multi = 30;
}
else
{
winType = SlotWinType.TrippleNormal;
multi = 10;
}
}
else if (a == 5 && (b == 5 || c == 5)
|| (b == 5 && c == 5))
{
winType = SlotWinType.DoubleJoker;
multi = 4;
}
else if (a == 5 || b == 5 || c == 5)
{
winType = SlotWinType.SingleJoker;
multi = 1;
}
return new()
{
Won = bet * multi,
WinType = winType,
Multiplier = multi,
Rolls = rolls,
};
}
}
public enum SlotWinType : byte
{
None,
SingleJoker,
DoubleJoker,
TrippleNormal,
TrippleJoker,
}
/*
var rolls = new[]
{
_rng.Next(default(byte), 6),
_rng.Next(default(byte), 6),
_rng.Next(default(byte), 6)
};
var multi = 0;
var winType = SlotWinType.None;
ref var a = ref rolls[0];
ref var b = ref rolls[1];
ref var c = ref rolls[2];
if (a == b && b == c)
{
if (a == 5)
{
winType = SlotWinType.TrippleJoker;
multi = 30;
}
else
{
winType = SlotWinType.TrippleNormal;
multi = 10;
}
}
else if (a == 5 && (b == 5 || c == 5)
|| (b == 5 && c == 5))
{
winType = SlotWinType.DoubleJoker;
multi = 4;
}
else if (rolls.Any(x => x == 5))
{
winType = SlotWinType.SingleJoker;
multi = 1;
}
return new()
{
Won = bet * multi,
WinType = winType,
Multiplier = multi,
Rolls = rolls,
};
}
*/

View File

@@ -0,0 +1,9 @@
namespace Nadeko.Econ.Gambling;
public readonly struct SlotResult
{
public decimal Multiplier { get; init; }
public byte[] Rolls { get; init; }
public decimal Won { get; init; }
public SlotWinType WinType { get; init; }
}

View File

@@ -0,0 +1,9 @@
namespace Nadeko.Econ.Gambling;
public readonly struct LuLaResult
{
public int Index { get; init; }
public decimal Multiplier { get; init; }
public decimal Won { get; init; }
public IReadOnlyList<decimal> Multipliers { get; init; }
}

View File

@@ -0,0 +1,34 @@
namespace Nadeko.Econ.Gambling;
public sealed class LulaGame
{
private static readonly IReadOnlyList<decimal> DEFAULT_MULTIPLIERS = new[] { 1.7M, 1.5M, 0.2M, 0.1M, 0.3M, 0.5M, 1.2M, 2.4M };
private readonly IReadOnlyList<decimal> _multipliers;
private static readonly NadekoRandom _rng = new();
public LulaGame(IReadOnlyList<decimal> multipliers)
{
_multipliers = multipliers;
}
public LulaGame() : this(DEFAULT_MULTIPLIERS)
{
}
public LuLaResult Spin(long bet)
{
var result = _rng.Next(0, _multipliers.Count);
var multi = _multipliers[result];
var amount = bet * multi;
return new()
{
Index = result,
Multiplier = multi,
Won = amount,
Multipliers = _multipliers.ToArray(),
};
}
}

View File

@@ -0,0 +1 @@
global using Nadeko.Common;

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Nadeko.Common\Nadeko.Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -12,7 +12,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Discord.Net.Core" Version="3.6.1" />
<PackageReference Include="Discord.Net.Core" Version="3.103.0" />
<PackageReference Include="Serilog" Version="2.11.0" />
<PackageReference Include="YamlDotNet" Version="11.2.1" />
</ItemGroup>

View File

@@ -9,7 +9,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.45.0" />
<PackageReference Include="Grpc.AspNetCore" Version="2.47.0" />
<PackageReference Include="Serilog" Version="2.11.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />

View File

@@ -1,336 +1,336 @@
#nullable enable
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace NadekoBot.Generators.Command;
[Generator]
public class CommandAttributesGenerator : IIncrementalGenerator
{
public const string ATTRIBUTE = @"// <AutoGenerated />
namespace NadekoBot.Common;
[System.AttributeUsage(System.AttributeTargets.Method)]
public class CmdAttribute : System.Attribute
{
}";
public class MethodModel
{
public string? Namespace { get; }
public IReadOnlyCollection<string> Classes { get; }
public string ReturnType { get; }
public string MethodName { get; }
public IEnumerable<string> Params { get; }
public MethodModel(string? ns, IReadOnlyCollection<string> classes, string returnType, string methodName, IEnumerable<string> @params)
{
Namespace = ns;
Classes = classes;
ReturnType = returnType;
MethodName = methodName;
Params = @params;
}
}
public class FileModel
{
public string? Namespace { get; }
public IReadOnlyCollection<string> ClassHierarchy { get; }
public IReadOnlyCollection<MethodModel> Methods { get; }
public FileModel(string? ns, IReadOnlyCollection<string> classHierarchy, IReadOnlyCollection<MethodModel> methods)
{
Namespace = ns;
ClassHierarchy = classHierarchy;
Methods = methods;
}
}
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// #if DEBUG
// if (!Debugger.IsAttached)
// Debugger.Launch();
// // SpinWait.SpinUntil(() => Debugger.IsAttached);
// #endif
context.RegisterPostInitializationOutput(static ctx => ctx.AddSource(
"CmdAttribute.g.cs",
SourceText.From(ATTRIBUTE, Encoding.UTF8)));
var methods = context.SyntaxProvider
.CreateSyntaxProvider(
static (node, _) => node is MethodDeclarationSyntax { AttributeLists.Count: > 0 },
static (ctx, cancel) => Transform(ctx, cancel))
.Where(static m => m is not null)
.Where(static m => m?.ChildTokens().Any(static x => x.IsKind(SyntaxKind.PublicKeyword)) ?? false);
var compilationMethods = context.CompilationProvider.Combine(methods.Collect());
context.RegisterSourceOutput(compilationMethods,
static (ctx, tuple) => RegisterAction(in ctx, tuple.Left, in tuple.Right));
}
private static void RegisterAction(in SourceProductionContext ctx,
Compilation comp,
in ImmutableArray<MethodDeclarationSyntax?> methods)
{
if (methods is { IsDefaultOrEmpty: true })
return;
var models = GetModels(comp, methods, ctx.CancellationToken);
foreach (var model in models)
{
var name = $"{model.Namespace}.{string.Join(".", model.ClassHierarchy)}.g.cs";
try
{
var source = GetSourceText(model);
ctx.AddSource(name, SourceText.From(source, Encoding.UTF8));
}
catch (Exception ex)
{
Console.WriteLine($"Error writing source file {name}\n" + ex);
}
}
}
private static string GetSourceText(FileModel model)
{
using var sw = new StringWriter();
using var tw = new IndentedTextWriter(sw);
tw.WriteLine("// <AutoGenerated />");
tw.WriteLine("#pragma warning disable CS1066");
if (model.Namespace is not null)
{
tw.WriteLine($"namespace {model.Namespace};");
tw.WriteLine();
}
foreach (var className in model.ClassHierarchy)
{
tw.WriteLine($"public partial class {className}");
tw.WriteLine("{");
tw.Indent ++;
}
foreach (var method in model.Methods)
{
tw.WriteLine("[NadekoCommand]");
tw.WriteLine("[NadekoDescription]");
tw.WriteLine("[Aliases]");
tw.WriteLine($"public partial {method.ReturnType} {method.MethodName}({string.Join(", ", method.Params)});");
}
foreach (var _ in model.ClassHierarchy)
{
tw.Indent --;
tw.WriteLine("}");
}
tw.Flush();
return sw.ToString();
}
private static IReadOnlyCollection<FileModel> GetModels(Compilation compilation,
in ImmutableArray<MethodDeclarationSyntax?> inputMethods,
CancellationToken cancel)
{
var models = new List<FileModel>();
var methods = inputMethods
.Where(static x => x is not null)
.Distinct();
var methodModels = methods
.Select(x => MethodDeclarationToMethodModel(compilation, x!))
.Where(static x => x is not null)
.Cast<MethodModel>();
var groups = methodModels
.GroupBy(static x => $"{x.Namespace}.{string.Join(".", x.Classes)}");
foreach (var group in groups)
{
if (cancel.IsCancellationRequested)
return new Collection<FileModel>();
if (group is null)
continue;
var elems = group.ToList();
if (elems.Count is 0)
continue;
var model = new FileModel(
methods: elems,
ns: elems[0].Namespace,
classHierarchy: elems![0].Classes
);
models.Add(model);
}
return models;
}
private static MethodModel? MethodDeclarationToMethodModel(Compilation comp, MethodDeclarationSyntax decl)
{
// SpinWait.SpinUntil(static () => Debugger.IsAttached);
SemanticModel semanticModel;
try
{
semanticModel = comp.GetSemanticModel(decl.SyntaxTree);
}
catch
{
// for some reason this method can throw "Not part of this compilation" argument exception
return null;
}
var methodModel = new MethodModel(
@params: decl.ParameterList.Parameters
.Where(p => p.Type is not null)
.Select(p =>
{
var prefix = p.Modifiers.Any(static x => x.IsKind(SyntaxKind.ParamsKeyword))
? "params "
: string.Empty;
var type = semanticModel
.GetTypeInfo(p.Type!)
.Type
?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var name = p.Identifier.Text;
var suffix = string.Empty;
if (p.Default is not null)
{
if (p.Default.Value is LiteralExpressionSyntax)
{
suffix = " = " + p.Default.Value;
}
else if (p.Default.Value is MemberAccessExpressionSyntax maes)
{
var maesSemModel = comp.GetSemanticModel(maes.SyntaxTree);
var sym = maesSemModel.GetSymbolInfo(maes.Name);
if (sym.Symbol is null)
{
suffix = " = " + p.Default.Value;
}
else
{
suffix = " = " + sym.Symbol.ToDisplayString();
}
}
}
return $"{prefix}{type} {name}{suffix}";
})
.ToList(),
methodName: decl.Identifier.Text,
returnType: decl.ReturnType.ToString(),
ns: GetNamespace(decl),
classes: GetClasses(decl)
);
return methodModel;
}
//https://github.com/andrewlock/NetEscapades.EnumGenerators/blob/main/src/NetEscapades.EnumGenerators/EnumGenerator.cs
static string? GetNamespace(MethodDeclarationSyntax declarationSyntax)
{
// determine the namespace the class is declared in, if any
string? nameSpace = null;
var parentOfInterest = declarationSyntax.Parent;
while (parentOfInterest is not null)
{
parentOfInterest = parentOfInterest.Parent;
if (parentOfInterest is BaseNamespaceDeclarationSyntax ns)
{
nameSpace = ns.Name.ToString();
while (true)
{
if (ns.Parent is not NamespaceDeclarationSyntax parent)
{
break;
}
ns = parent;
nameSpace = $"{ns.Name}.{nameSpace}";
}
return nameSpace;
}
}
return nameSpace;
}
static IReadOnlyCollection<string> GetClasses(MethodDeclarationSyntax declarationSyntax)
{
// determine the namespace the class is declared in, if any
var classes = new LinkedList<string>();
var parentOfInterest = declarationSyntax.Parent;
while (parentOfInterest is not null)
{
if (parentOfInterest is ClassDeclarationSyntax cds)
{
classes.AddFirst(cds.Identifier.ToString());
}
parentOfInterest = parentOfInterest.Parent;
}
Debug.WriteLine($"Method {declarationSyntax.Identifier.Text} has {classes.Count} classes");
return classes;
}
private static MethodDeclarationSyntax? Transform(GeneratorSyntaxContext ctx, CancellationToken cancel)
{
var methodDecl = ctx.Node as MethodDeclarationSyntax;
if (methodDecl is null)
return default;
foreach (var attListSyntax in methodDecl.AttributeLists)
{
foreach (var attSyntax in attListSyntax.Attributes)
{
if (cancel.IsCancellationRequested)
return default;
var symbol = ctx.SemanticModel.GetSymbolInfo(attSyntax).Symbol;
if (symbol is not IMethodSymbol attSymbol)
continue;
if (attSymbol.ContainingType.ToDisplayString() == "NadekoBot.Common.CmdAttribute")
return methodDecl;
}
}
return default;
}
}
// #nullable enable
// using System;
// using System.CodeDom.Compiler;
// using System.Collections.Generic;
// using System.Collections.Immutable;
// using System.Collections.ObjectModel;
// using System.Diagnostics;
// using System.IO;
// using System.Linq;
// using System.Text;
// using System.Threading;
// using Microsoft.CodeAnalysis;
// using Microsoft.CodeAnalysis.CSharp;
// using Microsoft.CodeAnalysis.CSharp.Syntax;
// using Microsoft.CodeAnalysis.Text;
//
// namespace NadekoBot.Generators.Command;
//
// [Generator]
// public class CommandAttributesGenerator : IIncrementalGenerator
// {
// public const string ATTRIBUTE = @"// <AutoGenerated />
//
// namespace NadekoBot.Common;
//
// [System.AttributeUsage(System.AttributeTargets.Method)]
// public class CmdAttribute : System.Attribute
// {
//
// }";
//
// public class MethodModel
// {
// public string? Namespace { get; }
// public IReadOnlyCollection<string> Classes { get; }
// public string ReturnType { get; }
// public string MethodName { get; }
// public IEnumerable<string> Params { get; }
//
// public MethodModel(string? ns, IReadOnlyCollection<string> classes, string returnType, string methodName, IEnumerable<string> @params)
// {
// Namespace = ns;
// Classes = classes;
// ReturnType = returnType;
// MethodName = methodName;
// Params = @params;
// }
// }
//
// public class FileModel
// {
// public string? Namespace { get; }
// public IReadOnlyCollection<string> ClassHierarchy { get; }
// public IReadOnlyCollection<MethodModel> Methods { get; }
//
// public FileModel(string? ns, IReadOnlyCollection<string> classHierarchy, IReadOnlyCollection<MethodModel> methods)
// {
// Namespace = ns;
// ClassHierarchy = classHierarchy;
// Methods = methods;
// }
// }
//
// public void Initialize(IncrementalGeneratorInitializationContext context)
// {
// // #if DEBUG
// // if (!Debugger.IsAttached)
// // Debugger.Launch();
// // // SpinWait.SpinUntil(() => Debugger.IsAttached);
// // #endif
// context.RegisterPostInitializationOutput(static ctx => ctx.AddSource(
// "CmdAttribute.g.cs",
// SourceText.From(ATTRIBUTE, Encoding.UTF8)));
//
// var methods = context.SyntaxProvider
// .CreateSyntaxProvider(
// static (node, _) => node is MethodDeclarationSyntax { AttributeLists.Count: > 0 },
// static (ctx, cancel) => Transform(ctx, cancel))
// .Where(static m => m is not null)
// .Where(static m => m?.ChildTokens().Any(static x => x.IsKind(SyntaxKind.PublicKeyword)) ?? false);
//
// var compilationMethods = context.CompilationProvider.Combine(methods.Collect());
//
// context.RegisterSourceOutput(compilationMethods,
// static (ctx, tuple) => RegisterAction(in ctx, tuple.Left, in tuple.Right));
// }
//
// private static void RegisterAction(in SourceProductionContext ctx,
// Compilation comp,
// in ImmutableArray<MethodDeclarationSyntax?> methods)
// {
// if (methods is { IsDefaultOrEmpty: true })
// return;
//
// var models = GetModels(comp, methods, ctx.CancellationToken);
//
// foreach (var model in models)
// {
// var name = $"{model.Namespace}.{string.Join(".", model.ClassHierarchy)}.g.cs";
// try
// {
// var source = GetSourceText(model);
// ctx.AddSource(name, SourceText.From(source, Encoding.UTF8));
// }
// catch (Exception ex)
// {
// Console.WriteLine($"Error writing source file {name}\n" + ex);
// }
// }
// }
//
// private static string GetSourceText(FileModel model)
// {
// using var sw = new StringWriter();
// using var tw = new IndentedTextWriter(sw);
//
// tw.WriteLine("// <AutoGenerated />");
// tw.WriteLine("#pragma warning disable CS1066");
//
// if (model.Namespace is not null)
// {
// tw.WriteLine($"namespace {model.Namespace};");
// tw.WriteLine();
// }
//
// foreach (var className in model.ClassHierarchy)
// {
// tw.WriteLine($"public partial class {className}");
// tw.WriteLine("{");
// tw.Indent ++;
// }
//
// foreach (var method in model.Methods)
// {
// tw.WriteLine("[NadekoCommand]");
// tw.WriteLine("[NadekoDescription]");
// tw.WriteLine("[Aliases]");
// tw.WriteLine($"public partial {method.ReturnType} {method.MethodName}({string.Join(", ", method.Params)});");
// }
//
// foreach (var _ in model.ClassHierarchy)
// {
// tw.Indent --;
// tw.WriteLine("}");
// }
//
// tw.Flush();
// return sw.ToString();
// }
//
// private static IReadOnlyCollection<FileModel> GetModels(Compilation compilation,
// in ImmutableArray<MethodDeclarationSyntax?> inputMethods,
// CancellationToken cancel)
// {
// var models = new List<FileModel>();
//
// var methods = inputMethods
// .Where(static x => x is not null)
// .Distinct();
//
// var methodModels = methods
// .Select(x => MethodDeclarationToMethodModel(compilation, x!))
// .Where(static x => x is not null)
// .Cast<MethodModel>();
//
// var groups = methodModels
// .GroupBy(static x => $"{x.Namespace}.{string.Join(".", x.Classes)}");
//
// foreach (var group in groups)
// {
// if (cancel.IsCancellationRequested)
// return new Collection<FileModel>();
//
// if (group is null)
// continue;
//
// var elems = group.ToList();
// if (elems.Count is 0)
// continue;
//
// var model = new FileModel(
// methods: elems,
// ns: elems[0].Namespace,
// classHierarchy: elems![0].Classes
// );
//
// models.Add(model);
// }
//
//
// return models;
// }
//
// private static MethodModel? MethodDeclarationToMethodModel(Compilation comp, MethodDeclarationSyntax decl)
// {
// // SpinWait.SpinUntil(static () => Debugger.IsAttached);
//
// SemanticModel semanticModel;
// try
// {
// semanticModel = comp.GetSemanticModel(decl.SyntaxTree);
// }
// catch
// {
// // for some reason this method can throw "Not part of this compilation" argument exception
// return null;
// }
//
// var methodModel = new MethodModel(
// @params: decl.ParameterList.Parameters
// .Where(p => p.Type is not null)
// .Select(p =>
// {
// var prefix = p.Modifiers.Any(static x => x.IsKind(SyntaxKind.ParamsKeyword))
// ? "params "
// : string.Empty;
//
// var type = semanticModel
// .GetTypeInfo(p.Type!)
// .Type
// ?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
//
//
// var name = p.Identifier.Text;
//
// var suffix = string.Empty;
// if (p.Default is not null)
// {
// if (p.Default.Value is LiteralExpressionSyntax)
// {
// suffix = " = " + p.Default.Value;
// }
// else if (p.Default.Value is MemberAccessExpressionSyntax maes)
// {
// var maesSemModel = comp.GetSemanticModel(maes.SyntaxTree);
// var sym = maesSemModel.GetSymbolInfo(maes.Name);
// if (sym.Symbol is null)
// {
// suffix = " = " + p.Default.Value;
// }
// else
// {
// suffix = " = " + sym.Symbol.ToDisplayString();
// }
// }
// }
//
// return $"{prefix}{type} {name}{suffix}";
// })
// .ToList(),
// methodName: decl.Identifier.Text,
// returnType: decl.ReturnType.ToString(),
// ns: GetNamespace(decl),
// classes: GetClasses(decl)
// );
//
// return methodModel;
// }
//
// //https://github.com/andrewlock/NetEscapades.EnumGenerators/blob/main/src/NetEscapades.EnumGenerators/EnumGenerator.cs
// static string? GetNamespace(MethodDeclarationSyntax declarationSyntax)
// {
// // determine the namespace the class is declared in, if any
// string? nameSpace = null;
// var parentOfInterest = declarationSyntax.Parent;
// while (parentOfInterest is not null)
// {
// parentOfInterest = parentOfInterest.Parent;
//
// if (parentOfInterest is BaseNamespaceDeclarationSyntax ns)
// {
// nameSpace = ns.Name.ToString();
// while (true)
// {
// if (ns.Parent is not NamespaceDeclarationSyntax parent)
// {
// break;
// }
//
// ns = parent;
// nameSpace = $"{ns.Name}.{nameSpace}";
// }
//
// return nameSpace;
// }
//
// }
//
// return nameSpace;
// }
//
// static IReadOnlyCollection<string> GetClasses(MethodDeclarationSyntax declarationSyntax)
// {
// // determine the namespace the class is declared in, if any
// var classes = new LinkedList<string>();
// var parentOfInterest = declarationSyntax.Parent;
// while (parentOfInterest is not null)
// {
// if (parentOfInterest is ClassDeclarationSyntax cds)
// {
// classes.AddFirst(cds.Identifier.ToString());
// }
//
// parentOfInterest = parentOfInterest.Parent;
// }
//
// Debug.WriteLine($"Method {declarationSyntax.Identifier.Text} has {classes.Count} classes");
//
// return classes;
// }
//
// private static MethodDeclarationSyntax? Transform(GeneratorSyntaxContext ctx, CancellationToken cancel)
// {
// var methodDecl = ctx.Node as MethodDeclarationSyntax;
// if (methodDecl is null)
// return default;
//
// foreach (var attListSyntax in methodDecl.AttributeLists)
// {
// foreach (var attSyntax in attListSyntax.Attributes)
// {
// if (cancel.IsCancellationRequested)
// return default;
//
// var symbol = ctx.SemanticModel.GetSymbolInfo(attSyntax).Symbol;
// if (symbol is not IMethodSymbol attSymbol)
// continue;
//
// if (attSymbol.ContainingType.ToDisplayString() == "NadekoBot.Common.CmdAttribute")
// return methodDecl;
// }
// }
//
// return default;
// }
// }

View File

@@ -47,7 +47,7 @@ namespace NadekoBot.Tests
|| !(type.GetCustomAttribute<GroupAttribute>(true) is null)) // or a submodule
.SelectMany(x => x.GetMethods()
.Where(mi => mi.CustomAttributes
.Any(ca => ca.AttributeType == typeof(NadekoCommandAttribute))))
.Any(ca => ca.AttributeType == typeof(CmdAttribute))))
.Select(x => x.Name.ToLowerInvariant())
.ToArray();

View File

@@ -1,5 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
using Nadeko.Common;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NUnit.Framework;

View File

@@ -1,4 +1,4 @@
using NadekoBot.Common.Collections;
using Nadeko.Common;
using NadekoBot.Services.Database.Models;
using NUnit.Framework;
using System;

View File

@@ -1,4 +1,5 @@
using NadekoBot.Common;
using Nadeko.Common;
using NadekoBot.Common;
using NUnit.Framework;
namespace NadekoBot.Tests

View File

@@ -14,6 +14,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Nadeko.Common\Nadeko.Common.csproj" />
<ProjectReference Include="..\Nadeko.Econ\Nadeko.Econ.csproj" />
<ProjectReference Include="..\NadekoBot\NadekoBot.csproj" />
</ItemGroup>

View File

@@ -0,0 +1,83 @@
using Nadeko.Econ;
using NUnit.Framework;
namespace NadekoBot.Tests;
public class NewDeckTests
{
private RegularDeck _deck;
[SetUp]
public void Setup()
{
_deck = new RegularDeck();
}
[Test]
public void TestCount()
{
Assert.AreEqual(52, _deck.TotalCount);
Assert.AreEqual(52, _deck.CurrentCount);
}
[Test]
public void TestDeckDraw()
{
var card = _deck.Draw();
Assert.IsNotNull(card);
Assert.AreEqual(card.Suit, RegularSuit.Hearts);
Assert.AreEqual(card.Value, RegularValue.Ace);
Assert.AreEqual(_deck.CurrentCount, _deck.TotalCount - 1);
}
[Test]
public void TestDeckSpent()
{
for (var i = 0; i < _deck.TotalCount - 1; ++i)
{
_deck.Draw();
}
var lastCard = _deck.Draw();
Assert.IsNotNull(lastCard);
Assert.AreEqual(new RegularCard(RegularSuit.Spades, RegularValue.King), lastCard);
var noCard = _deck.Draw();
Assert.IsNull(noCard);
}
[Test]
public void TestCardGetName()
{
var ace = _deck.Draw()!;
var two = _deck.Draw()!;
Assert.AreEqual("Ace of Hearts", ace.GetName());
Assert.AreEqual("Two of Hearts", two.GetName());
}
[Test]
public void TestPeek()
{
var ace = _deck.Peek()!;
var tenOfSpades = _deck.Peek(48);
Assert.AreEqual(new RegularCard(RegularSuit.Hearts, RegularValue.Ace), ace);
Assert.AreEqual(new RegularCard(RegularSuit.Spades, RegularValue.Ten), tenOfSpades);
}
[Test]
public void TestMultipleDeck()
{
var quadDeck = new MultipleRegularDeck(4);
var count = quadDeck.TotalCount;
Assert.AreEqual(52 * 4, count);
var card = quadDeck.Peek(54);
Assert.AreEqual(new RegularCard(RegularSuit.Hearts, RegularValue.Three), card);
}
}

View File

@@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="MorseCode.ITask" Version="2.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.2" />
</ItemGroup>
</Project>

View File

@@ -10,6 +10,7 @@ using System.Collections.Immutable;
using System.Diagnostics;
using System.Net;
using System.Reflection;
using Nadeko.Common;
using RunMode = Discord.Commands.RunMode;
namespace NadekoBot;

View File

@@ -3,11 +3,16 @@ using System.Runtime.CompilerServices;
namespace NadekoBot.Common.Attributes;
[AttributeUsage(AttributeTargets.Method)]
public sealed class NadekoCommandAttribute : CommandAttribute
public sealed class CmdAttribute : CommandAttribute
{
public string MethodName { get; }
public NadekoCommandAttribute([CallerMemberName] string memberName = "")
public CmdAttribute([CallerMemberName] string memberName = "")
: base(CommandNameLoadHelper.GetCommandNameFor(memberName))
=> MethodName = memberName.ToLowerInvariant();
{
MethodName = memberName.ToLowerInvariant();
Aliases = CommandNameLoadHelper.GetAliasesFor(memberName);
Remarks = memberName.ToLowerInvariant();
Summary = memberName.ToLowerInvariant();
}
}

View File

@@ -1,30 +0,0 @@
using System.Runtime.CompilerServices;
namespace NadekoBot.Common.Attributes;
[AttributeUsage(AttributeTargets.Class)]
internal sealed class NadekoModuleAttribute : GroupAttribute
{
public NadekoModuleAttribute(string moduleName)
: base(moduleName)
{
}
}
[AttributeUsage(AttributeTargets.Method)]
internal sealed class NadekoDescriptionAttribute : SummaryAttribute
{
public NadekoDescriptionAttribute([CallerMemberName] string name = "")
: base(name.ToLowerInvariant())
{
}
}
[AttributeUsage(AttributeTargets.Method)]
internal sealed class NadekoUsageAttribute : RemarksAttribute
{
public NadekoUsageAttribute([CallerMemberName] string name = "")
: base(name.ToLowerInvariant())
{
}
}

View File

@@ -12,7 +12,7 @@ namespace NadekoBot.Common.Configs;
public sealed partial class BotConfig : ICloneable<BotConfig>
{
[Comment(@"DO NOT CHANGE")]
public int Version { get; set; } = 3;
public int Version { get; set; } = 4;
[Comment(@"Most commands, when executed, have a small colored line
next to the response. The color depends whether the command
@@ -29,12 +29,8 @@ and copy the hex code fo your selected color (marked as #)")]
Allowed values: Simple, Normal, None")]
public ConsoleOutputType ConsoleOutputType { get; set; }
// [Comment(@"For what kind of updates will the bot check.
// Allowed values: Release, Commit, None")]
// public UpdateCheckType CheckForUpdates { get; set; }
// [Comment(@"How often will the bot check for updates, in hours")]
// public int CheckUpdateInterval { get; set; }
[Comment(@"Whether the bot will check for new releases every hour")]
public bool CheckForUpdates { get; set; } = true;
[Comment(@"Do you want any messages sent by users in Bot's DM to be forwarded to the owner(s)?")]
public bool ForwardMessages { get; set; }

View File

@@ -0,0 +1,20 @@
namespace NadekoBot;
public class SimpleInteraction<T>
{
public ButtonBuilder Button { get; }
private readonly Func<SocketMessageComponent, T, Task> _onClick;
private readonly T? _state;
public SimpleInteraction(ButtonBuilder button, Func<SocketMessageComponent, T?, Task> onClick, T? state = default)
{
Button = button;
_onClick = onClick;
_state = state;
}
public async Task TriggerAsync(SocketMessageComponent smc)
{
await _onClick(smc, _state!);
}
}

View File

@@ -1,47 +0,0 @@
#nullable disable
namespace NadekoBot.Common;
public class OldImageUrls
{
public int Version { get; set; } = 2;
public CoinData Coins { get; set; }
public Uri[] Currency { get; set; }
public Uri[] Dice { get; set; }
public RategirlData Rategirl { get; set; }
public XpData Xp { get; set; }
//new
public RipData Rip { get; set; }
public SlotData Slots { get; set; }
public class RipData
{
public Uri Bg { get; set; }
public Uri Overlay { get; set; }
}
public class SlotData
{
public Uri[] Emojis { get; set; }
public Uri[] Numbers { get; set; }
public Uri Bg { get; set; }
}
public class CoinData
{
public Uri[] Heads { get; set; }
public Uri[] Tails { get; set; }
}
public class RategirlData
{
public Uri Matrix { get; set; }
public Uri Dot { get; set; }
}
public class XpData
{
public Uri Bg { get; set; }
}
}

View File

@@ -1,25 +0,0 @@
#nullable disable
namespace NadekoBot.Common;
public static class PlatformHelper
{
private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 30000;
private static volatile int processorCount;
private static volatile int lastProcessorCountRefreshTicks;
public static int ProcessorCount
{
get
{
var now = Environment.TickCount;
if (processorCount == 0 || now - lastProcessorCountRefreshTicks >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS)
{
processorCount = Environment.ProcessorCount;
lastProcessorCountRefreshTicks = now;
}
return processorCount;
}
}
}

View File

@@ -1,5 +1,6 @@
#nullable disable
using System.Text.RegularExpressions;
using Nadeko.Common;
namespace NadekoBot.Common;

View File

@@ -1,4 +1,6 @@
#nullable disable
using Nadeko.Common;
namespace NadekoBot.Common.TypeReaders;
public sealed class KwumTypeReader : NadekoTypeReader<kwum>

View File

@@ -3,6 +3,7 @@ using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Services;
using NCalc;
using System.Text.RegularExpressions;
using Nadeko.Common;
namespace NadekoBot.Common.TypeReaders;

View File

@@ -1,5 +1,4 @@
#nullable disable
using NadekoBot.Common.Collections;
using NadekoBot.Db.Models;
namespace NadekoBot.Services.Database.Models;

View File

@@ -5,4 +5,11 @@ public class ImageOnlyChannel : DbEntity
{
public ulong GuildId { get; set; }
public ulong ChannelId { get; set; }
public OnlyChannelType Type { get; set; }
}
public enum OnlyChannelType
{
Image,
Link
}

View File

@@ -4,11 +4,6 @@ using System.Diagnostics;
namespace NadekoBot.Services.Database.Models;
public interface IIndexed
{
int Index { get; set; }
}
[DebuggerDisplay("{PrimaryTarget}{SecondaryTarget} {SecondaryTargetName} {State} {PrimaryTargetId}")]
public class Permissionv2 : DbEntity, IIndexed
{

View File

@@ -1,6 +1,4 @@
#nullable disable
using NadekoBot.Common.Collections;
namespace NadekoBot.Services.Database.Models;
public class Poll : DbEntity

View File

@@ -0,0 +1,18 @@
#nullable disable warnings
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Db.Models;
public class XpShopOwnedItem : DbEntity
{
public ulong UserId { get; set; }
public XpShopItemType ItemType { get; set; }
public bool IsUsing { get; set; }
public string ItemKey { get; set; }
}
public enum XpShopItemType
{
Background,
Frame,
}

View File

@@ -454,6 +454,23 @@ public abstract class NadekoContext : DbContext
});
#endregion
#region Xp Item Shop
modelBuilder.Entity<XpShopOwnedItem>(
x =>
{
// user can own only one of each item
x.HasIndex(model => new
{
model.UserId,
model.ItemType,
model.ItemKey
})
.IsUnique();
});
#endregion
}
#if DEBUG

View File

@@ -8,7 +8,8 @@ global using Humanizer;
// nadekobot
global using NadekoBot;
global using NadekoBot.Services;
global using NadekoBot.Common;
global using Nadeko.Common; // new project
global using NadekoBot.Common; // old + nadekobot specific things
global using NadekoBot.Common.Attributes;
global using NadekoBot.Extensions;
global using Nadeko.Snake;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class xpitemshop : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "xpshopowneditem",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
userid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
itemtype = table.Column<int>(type: "int", nullable: false),
isusing = table.Column<bool>(type: "tinyint(1)", nullable: false),
itemkey = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_xpshopowneditem", x => x.id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_xpshopowneditem_userid_itemtype_itemkey",
table: "xpshopowneditem",
columns: new[] { "userid", "itemtype", "itemkey" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "xpshopowneditem");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class linkonlychannels : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "type",
table: "imageonlychannels",
type: "int",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "type",
table: "imageonlychannels");
}
}
}

View File

@@ -16,7 +16,7 @@ namespace NadekoBot.Migrations.Mysql
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.6")
.HasAnnotation("ProductVersion", "6.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
@@ -366,6 +366,44 @@ namespace NadekoBot.Migrations.Mysql
b.ToTable("streamonlinemessages", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.XpShopOwnedItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<DateTime?>("DateAdded")
.HasColumnType("datetime(6)")
.HasColumnName("dateadded");
b.Property<bool>("IsUsing")
.HasColumnType("tinyint(1)")
.HasColumnName("isusing");
b.Property<string>("ItemKey")
.IsRequired()
.HasColumnType("varchar(255)")
.HasColumnName("itemkey");
b.Property<int>("ItemType")
.HasColumnType("int")
.HasColumnName("itemtype");
b.Property<ulong>("UserId")
.HasColumnType("bigint unsigned")
.HasColumnName("userid");
b.HasKey("Id")
.HasName("pk_xpshopowneditem");
b.HasIndex("UserId", "ItemType", "ItemKey")
.IsUnique()
.HasDatabaseName("ix_xpshopowneditem_userid_itemtype_itemkey");
b.ToTable("xpshopowneditem", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
{
b.Property<int>("Id")
@@ -1353,6 +1391,10 @@ namespace NadekoBot.Migrations.Mysql
.HasColumnType("bigint unsigned")
.HasColumnName("guildid");
b.Property<int>("Type")
.HasColumnType("int")
.HasColumnName("type");
b.HasKey("Id")
.HasName("pk_imageonlychannels");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
public partial class xpitemshop : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "xpshopowneditem",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
itemtype = table.Column<int>(type: "integer", nullable: false),
isusing = table.Column<bool>(type: "boolean", nullable: false),
itemkey = table.Column<string>(type: "text", nullable: false),
dateadded = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_xpshopowneditem", x => x.id);
});
migrationBuilder.CreateIndex(
name: "ix_xpshopowneditem_userid_itemtype_itemkey",
table: "xpshopowneditem",
columns: new[] { "userid", "itemtype", "itemkey" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "xpshopowneditem");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
public partial class linkonlychannels : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "type",
table: "imageonlychannels",
type: "integer",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "type",
table: "imageonlychannels");
}
}
}

View File

@@ -17,7 +17,7 @@ namespace NadekoBot.Migrations.PostgreSql
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.6")
.HasAnnotation("ProductVersion", "6.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@@ -378,6 +378,46 @@ namespace NadekoBot.Migrations.PostgreSql
b.ToTable("streamonlinemessages", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.XpShopOwnedItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp with time zone")
.HasColumnName("dateadded");
b.Property<bool>("IsUsing")
.HasColumnType("boolean")
.HasColumnName("isusing");
b.Property<string>("ItemKey")
.IsRequired()
.HasColumnType("text")
.HasColumnName("itemkey");
b.Property<int>("ItemType")
.HasColumnType("integer")
.HasColumnName("itemtype");
b.Property<decimal>("UserId")
.HasColumnType("numeric(20,0)")
.HasColumnName("userid");
b.HasKey("Id")
.HasName("pk_xpshopowneditem");
b.HasIndex("UserId", "ItemType", "ItemKey")
.IsUnique()
.HasDatabaseName("ix_xpshopowneditem_userid_itemtype_itemkey");
b.ToTable("xpshopowneditem", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
{
b.Property<int>("Id")
@@ -1417,6 +1457,10 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.Property<int>("Type")
.HasColumnType("integer")
.HasColumnName("type");
b.HasKey("Id")
.HasName("pk_imageonlychannels");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class xpitemshop : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "XpShopOwnedItem",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
ItemType = table.Column<int>(type: "INTEGER", nullable: false),
IsUsing = table.Column<bool>(type: "INTEGER", nullable: false),
ItemKey = table.Column<string>(type: "TEXT", nullable: false),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_XpShopOwnedItem", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_XpShopOwnedItem_UserId_ItemType_ItemKey",
table: "XpShopOwnedItem",
columns: new[] { "UserId", "ItemType", "ItemKey" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "XpShopOwnedItem");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class linkonlychannels : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Type",
table: "ImageOnlyChannels",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Type",
table: "ImageOnlyChannels");
}
}
}

View File

@@ -15,7 +15,7 @@ namespace NadekoBot.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.6");
modelBuilder.HasAnnotation("ProductVersion", "6.0.7");
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
{
@@ -288,6 +288,36 @@ namespace NadekoBot.Migrations
b.ToTable("StreamOnlineMessages");
});
modelBuilder.Entity("NadekoBot.Db.Models.XpShopOwnedItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<bool>("IsUsing")
.HasColumnType("INTEGER");
b.Property<string>("ItemKey")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("ItemType")
.HasColumnType("INTEGER");
b.Property<ulong>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId", "ItemType", "ItemKey")
.IsUnique();
b.ToTable("XpShopOwnedItem");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiAltSetting", b =>
{
b.Property<int>("Id")
@@ -1058,6 +1088,9 @@ namespace NadekoBot.Migrations
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ChannelId")

View File

@@ -32,29 +32,42 @@ public partial class Administration : NadekoModule<AdministrationService>
Inherit
}
private readonly ImageOnlyChannelService _imageOnly;
private readonly SomethingOnlyChannelService _somethingOnly;
public Administration(ImageOnlyChannelService imageOnly)
=> _imageOnly = imageOnly;
public Administration(SomethingOnlyChannelService somethingOnly)
=> _somethingOnly = somethingOnly;
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageGuild)]
public async partial Task ImageOnlyChannel(StoopidTime time = null)
public async Task ImageOnlyChannel(StoopidTime time = null)
{
var newValue = _imageOnly.ToggleImageOnlyChannel(ctx.Guild.Id, ctx.Channel.Id);
var newValue = await _somethingOnly.ToggleImageOnlyChannelAsync(ctx.Guild.Id, ctx.Channel.Id);
if (newValue)
await ReplyConfirmLocalizedAsync(strs.imageonly_enable);
else
await ReplyPendingLocalizedAsync(strs.imageonly_disable);
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageGuild)]
public async Task LinkOnlyChannel(StoopidTime time = null)
{
var newValue = await _somethingOnly.ToggleLinkOnlyChannelAsync(ctx.Guild.Id, ctx.Channel.Id);
if (newValue)
await ReplyConfirmLocalizedAsync(strs.linkonly_enable);
else
await ReplyPendingLocalizedAsync(strs.linkonly_disable);
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageChannels)]
[BotPerm(ChannelPerm.ManageChannels)]
public async partial Task Slowmode(StoopidTime time = null)
public async Task Slowmode(StoopidTime time = null)
{
var seconds = (int?)time?.Time.TotalSeconds ?? 0;
if (time is not null && (time.Time < TimeSpan.FromSeconds(0) || time.Time > TimeSpan.FromHours(6)))
@@ -73,7 +86,7 @@ public partial class Administration : NadekoModule<AdministrationService>
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageMessages)]
[Priority(2)]
public async partial Task Delmsgoncmd(List _)
public async Task Delmsgoncmd(List _)
{
var guild = (SocketGuild)ctx.Guild;
var (enabled, channels) = _service.GetDelMsgOnCmdData(ctx.Guild.Id);
@@ -104,7 +117,7 @@ public partial class Administration : NadekoModule<AdministrationService>
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageMessages)]
[Priority(1)]
public async partial Task Delmsgoncmd(Server _ = Server.Server)
public async Task Delmsgoncmd(Server _ = Server.Server)
{
if (_service.ToggleDeleteMessageOnCommand(ctx.Guild.Id))
{
@@ -123,7 +136,7 @@ public partial class Administration : NadekoModule<AdministrationService>
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageMessages)]
[Priority(0)]
public partial Task Delmsgoncmd(Channel _, State s, ITextChannel ch)
public Task Delmsgoncmd(Channel _, State s, ITextChannel ch)
=> Delmsgoncmd(_, s, ch.Id);
[Cmd]
@@ -131,7 +144,7 @@ public partial class Administration : NadekoModule<AdministrationService>
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageMessages)]
[Priority(1)]
public async partial Task Delmsgoncmd(Channel _, State s, ulong? chId = null)
public async Task Delmsgoncmd(Channel _, State s, ulong? chId = null)
{
var actualChId = chId ?? ctx.Channel.Id;
await _service.SetDelMsgOnCmdState(ctx.Guild.Id, actualChId, s);
@@ -148,7 +161,7 @@ public partial class Administration : NadekoModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.DeafenMembers)]
[BotPerm(GuildPerm.DeafenMembers)]
public async partial Task Deafen(params IGuildUser[] users)
public async Task Deafen(params IGuildUser[] users)
{
await _service.DeafenUsers(true, users);
await ReplyConfirmLocalizedAsync(strs.deafen);
@@ -158,7 +171,7 @@ public partial class Administration : NadekoModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.DeafenMembers)]
[BotPerm(GuildPerm.DeafenMembers)]
public async partial Task UnDeafen(params IGuildUser[] users)
public async Task UnDeafen(params IGuildUser[] users)
{
await _service.DeafenUsers(false, users);
await ReplyConfirmLocalizedAsync(strs.undeafen);
@@ -168,7 +181,7 @@ public partial class Administration : NadekoModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async partial Task DelVoiChanl([Leftover] IVoiceChannel voiceChannel)
public async Task DelVoiChanl([Leftover] IVoiceChannel voiceChannel)
{
await voiceChannel.DeleteAsync();
await ReplyConfirmLocalizedAsync(strs.delvoich(Format.Bold(voiceChannel.Name)));
@@ -178,7 +191,7 @@ public partial class Administration : NadekoModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async partial Task CreatVoiChanl([Leftover] string channelName)
public async Task CreatVoiChanl([Leftover] string channelName)
{
var ch = await ctx.Guild.CreateVoiceChannelAsync(channelName);
await ReplyConfirmLocalizedAsync(strs.createvoich(Format.Bold(ch.Name)));
@@ -188,7 +201,7 @@ public partial class Administration : NadekoModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async partial Task DelTxtChanl([Leftover] ITextChannel toDelete)
public async Task DelTxtChanl([Leftover] ITextChannel toDelete)
{
await toDelete.DeleteAsync();
await ReplyConfirmLocalizedAsync(strs.deltextchan(Format.Bold(toDelete.Name)));
@@ -198,9 +211,9 @@ public partial class Administration : NadekoModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async partial Task CreaTxtChanl([Leftover] string channelName)
public async Task CreaTxtChanl([Leftover] string channelName)
{
var txtCh = await ctx.Guild.CreateTextChannelAsync(channelName);
var txtCh = await ctx.Guild.CreateTextChannelAsync(channelName);
await ReplyConfirmLocalizedAsync(strs.createtextchan(Format.Bold(txtCh.Name)));
}
@@ -208,7 +221,7 @@ public partial class Administration : NadekoModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async partial Task SetTopic([Leftover] string topic = null)
public async Task SetTopic([Leftover] string topic = null)
{
var channel = (ITextChannel)ctx.Channel;
topic ??= "";
@@ -220,7 +233,7 @@ public partial class Administration : NadekoModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async partial Task SetChanlName([Leftover] string name)
public async Task SetChanlName([Leftover] string name)
{
var channel = (ITextChannel)ctx.Channel;
await channel.ModifyAsync(c => c.Name = name);
@@ -231,7 +244,7 @@ public partial class Administration : NadekoModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageChannels)]
[BotPerm(GuildPerm.ManageChannels)]
public async partial Task NsfwToggle()
public async Task NsfwToggle()
{
var channel = (ITextChannel)ctx.Channel;
var isEnabled = channel.IsNsfw;
@@ -248,13 +261,13 @@ public partial class Administration : NadekoModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[Priority(0)]
public partial Task Edit(ulong messageId, [Leftover] string text)
public Task Edit(ulong messageId, [Leftover] string text)
=> Edit((ITextChannel)ctx.Channel, messageId, text);
[Cmd]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public async partial Task Edit(ITextChannel channel, ulong messageId, [Leftover] string text)
public async Task Edit(ITextChannel channel, ulong messageId, [Leftover] string text)
{
var userPerms = ((SocketGuildUser)ctx.User).GetPermissions(channel);
var botPerms = ((SocketGuild)ctx.Guild).CurrentUser.GetPermissions(channel);
@@ -277,12 +290,12 @@ public partial class Administration : NadekoModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[BotPerm(ChannelPerm.ManageMessages)]
public partial Task Delete(ulong messageId, StoopidTime time = null)
public Task Delete(ulong messageId, StoopidTime time = null)
=> Delete((ITextChannel)ctx.Channel, messageId, time);
[Cmd]
[RequireContext(ContextType.Guild)]
public async partial Task Delete(ITextChannel channel, ulong messageId, StoopidTime time = null)
public async Task Delete(ITextChannel channel, ulong messageId, StoopidTime time = null)
=> await InternalMessageAction(channel, messageId, time, msg => msg.DeleteAsync());
private async Task InternalMessageAction(

View File

@@ -1,5 +1,6 @@
#nullable disable
using Microsoft.EntityFrameworkCore;
using Nadeko.Common;
using NadekoBot.Db;
using NadekoBot.Services.Database.Models;
@@ -41,11 +42,11 @@ public class AdministrationService : INService
private Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd)
{
if (msg.Channel is not ITextChannel channel)
return Task.CompletedTask;
_ = Task.Run(async () =>
{
if (msg.Channel is not SocketTextChannel channel)
return;
//wat ?!
if (DeleteMessagesOnCommandChannels.TryGetValue(channel.Id, out var state))
{

View File

@@ -1,4 +1,5 @@
#nullable disable
using Nadeko.Common;
using NadekoBot.Modules.Administration.Services;
namespace NadekoBot.Modules.Administration;
@@ -12,7 +13,7 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async partial Task AutoAssignRole([Leftover] IRole role)
public async Task AutoAssignRole([Leftover] IRole role)
{
var guser = (IGuildUser)ctx.User;
if (role.Id == ctx.Guild.EveryoneRole.Id)
@@ -38,7 +39,7 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async partial Task AutoAssignRole()
public async Task AutoAssignRole()
{
if (!_service.TryGetRoles(ctx.Guild.Id, out var roles))
{

View File

@@ -5,6 +5,7 @@ using NadekoBot.Db;
using NadekoBot.Services.Database.Models;
using System.Net;
using System.Threading.Channels;
using Nadeko.Common;
namespace NadekoBot.Modules.Administration.Services;

View File

@@ -32,7 +32,7 @@ namespace NadekoBot.Modules.Administration
[Cmd]
[OwnerOnly]
public partial Task SqlSelect([Leftover] string sql)
public Task SqlSelect([Leftover] string sql)
{
var result = _service.SelectSql(sql);
@@ -56,7 +56,7 @@ namespace NadekoBot.Modules.Administration
[Cmd]
[OwnerOnly]
public async partial Task SqlExec([Leftover] string sql)
public async Task SqlExec([Leftover] string sql)
{
try
{
@@ -78,37 +78,37 @@ namespace NadekoBot.Modules.Administration
[Cmd]
[OwnerOnly]
public partial Task DeleteWaifus()
public Task DeleteWaifus()
=> ConfirmActionInternalAsync("Delete Waifus", () => _service.DeleteWaifus());
[Cmd]
[OwnerOnly]
public async partial Task DeleteWaifu(IUser user)
public async Task DeleteWaifu(IUser user)
=> await DeleteWaifu(user.Id);
[Cmd]
[OwnerOnly]
public partial Task DeleteWaifu(ulong userId)
public Task DeleteWaifu(ulong userId)
=> ConfirmActionInternalAsync($"Delete Waifu {userId}", () => _service.DeleteWaifu(userId));
[Cmd]
[OwnerOnly]
public partial Task DeleteCurrency()
public Task DeleteCurrency()
=> ConfirmActionInternalAsync("Delete Currency", () => _service.DeleteCurrency());
[Cmd]
[OwnerOnly]
public partial Task DeletePlaylists()
public Task DeletePlaylists()
=> ConfirmActionInternalAsync("Delete Playlists", () => _service.DeletePlaylists());
[Cmd]
[OwnerOnly]
public partial Task DeleteXp()
public Task DeleteXp()
=> ConfirmActionInternalAsync("Delete Xp", () => _service.DeleteXp());
[Cmd]
[OwnerOnly]
public async partial Task PurgeUser(ulong userId)
public async Task PurgeUser(ulong userId)
{
var embed = _eb.Create()
.WithDescription(GetText(strs.purge_user_confirm(Format.Bold(userId.ToString()))));
@@ -122,7 +122,7 @@ namespace NadekoBot.Modules.Administration
[Cmd]
[OwnerOnly]
public partial Task PurgeUser([Leftover] IUser user)
public Task PurgeUser([Leftover] IUser user)
=> PurgeUser(user.Id);
}
}

View File

@@ -12,7 +12,7 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.MoveMembers)]
public async partial Task GameVoiceChannel()
public async Task GameVoiceChannel()
{
var vch = ((IGuildUser)ctx.User).VoiceChannel;

View File

@@ -8,7 +8,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async partial Task Boost()
public async Task Boost()
{
var enabled = await _service.ToggleBoost(ctx.Guild.Id, ctx.Channel.Id);
@@ -21,7 +21,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async partial Task BoostDel(int timer = 30)
public async Task BoostDel(int timer = 30)
{
if (timer is < 0 or > 600)
return;
@@ -37,7 +37,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async partial Task BoostMsg([Leftover] string? text = null)
public async Task BoostMsg([Leftover] string? text = null)
{
if (string.IsNullOrWhiteSpace(text))
{
@@ -56,7 +56,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async partial Task GreetDel(int timer = 30)
public async Task GreetDel(int timer = 30)
{
if (timer is < 0 or > 600)
return;
@@ -72,7 +72,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async partial Task Greet()
public async Task Greet()
{
var enabled = await _service.SetGreet(ctx.Guild.Id, ctx.Channel.Id);
@@ -85,7 +85,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async partial Task GreetMsg([Leftover] string? text = null)
public async Task GreetMsg([Leftover] string? text = null)
{
if (string.IsNullOrWhiteSpace(text))
{
@@ -105,7 +105,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async partial Task GreetDm()
public async Task GreetDm()
{
var enabled = await _service.SetGreetDm(ctx.Guild.Id);
@@ -118,7 +118,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async partial Task GreetDmMsg([Leftover] string? text = null)
public async Task GreetDmMsg([Leftover] string? text = null)
{
if (string.IsNullOrWhiteSpace(text))
{
@@ -137,7 +137,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async partial Task Bye()
public async Task Bye()
{
var enabled = await _service.SetBye(ctx.Guild.Id, ctx.Channel.Id);
@@ -150,7 +150,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async partial Task ByeMsg([Leftover] string? text = null)
public async Task ByeMsg([Leftover] string? text = null)
{
if (string.IsNullOrWhiteSpace(text))
{
@@ -169,7 +169,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
public async partial Task ByeDel(int timer = 30)
public async Task ByeDel(int timer = 30)
{
await _service.SetByeDel(ctx.Guild.Id, timer);
@@ -184,7 +184,7 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async partial Task ByeTest([Leftover] IGuildUser? user = null)
public async Task ByeTest([Leftover] IGuildUser? user = null)
{
user ??= (IGuildUser)ctx.User;
@@ -198,7 +198,7 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async partial Task GreetTest([Leftover] IGuildUser? user = null)
public async Task GreetTest([Leftover] IGuildUser? user = null)
{
user ??= (IGuildUser)ctx.User;
@@ -212,7 +212,7 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageGuild)]
[Ratelimit(5)]
public async partial Task GreetDmTest([Leftover] IGuildUser? user = null)
public async Task GreetDmTest([Leftover] IGuildUser? user = null)
{
user ??= (IGuildUser)ctx.User;

View File

@@ -191,6 +191,11 @@ public class GreetService : INService, IReadyExecutor
if (conf.AutoDeleteByeMessagesTimer > 0)
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
}
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions)
{
Log.Warning(ex, "Missing permissions to send a bye message, the bye message will be disabled on server: {GuildId}", channel.GuildId);
await SetBye(channel.GuildId, channel.Id, false);
}
catch (Exception ex)
{
Log.Warning(ex, "Error embeding bye message");
@@ -219,6 +224,11 @@ public class GreetService : INService, IReadyExecutor
if (conf.AutoDeleteGreetMessagesTimer > 0)
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
}
catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions)
{
Log.Warning(ex, "Missing permissions to send a bye message, the greet message will be disabled on server: {GuildId}", channel.GuildId);
await SetGreet(channel.GuildId, channel.Id, false);
}
catch (Exception ex)
{
Log.Warning(ex, "Error embeding greet message");

View File

@@ -4,16 +4,18 @@ using Microsoft.Extensions.Caching.Memory;
using NadekoBot.Common.ModuleBehaviors;
using System.Net;
using System.Threading.Channels;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Administration.Services;
public sealed class ImageOnlyChannelService : IExecOnMessage
public sealed class SomethingOnlyChannelService : IExecOnMessage
{
public int Priority { get; } = 0;
private readonly IMemoryCache _ticketCache;
private readonly DiscordSocketClient _client;
private readonly DbService _db;
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _enabledOn;
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _imageOnly;
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _linkOnly;
private readonly Channel<IUserMessage> _deleteQueue = Channel.CreateBounded<IUserMessage>(
new BoundedChannelOptions(100)
@@ -24,32 +26,39 @@ public sealed class ImageOnlyChannelService : IExecOnMessage
});
public ImageOnlyChannelService(IMemoryCache ticketCache, DiscordSocketClient client, DbService db)
public SomethingOnlyChannelService(IMemoryCache ticketCache, DiscordSocketClient client, DbService db)
{
_ticketCache = ticketCache;
_client = client;
_db = db;
using var uow = _db.GetDbContext();
_enabledOn = uow.ImageOnlyChannels.ToList()
.GroupBy(x => x.GuildId)
.ToDictionary(x => x.Key, x => new ConcurrentHashSet<ulong>(x.Select(y => y.ChannelId)))
.ToConcurrent();
_imageOnly = uow.ImageOnlyChannels
.Where(x => x.Type == OnlyChannelType.Image)
.ToList()
.GroupBy(x => x.GuildId)
.ToDictionary(x => x.Key, x => new ConcurrentHashSet<ulong>(x.Select(y => y.ChannelId)))
.ToConcurrent();
_linkOnly = uow.ImageOnlyChannels
.Where(x => x.Type == OnlyChannelType.Link)
.ToList()
.GroupBy(x => x.GuildId)
.ToDictionary(x => x.Key, x => new ConcurrentHashSet<ulong>(x.Select(y => y.ChannelId)))
.ToConcurrent();
_ = Task.Run(DeleteQueueRunner);
_client.ChannelDestroyed += ClientOnChannelDestroyed;
}
private Task ClientOnChannelDestroyed(SocketChannel ch)
private async Task ClientOnChannelDestroyed(SocketChannel ch)
{
if (ch is not IGuildChannel gch)
return Task.CompletedTask;
return;
if (_enabledOn.TryGetValue(gch.GuildId, out var channels) && channels.TryRemove(ch.Id))
ToggleImageOnlyChannel(gch.GuildId, ch.Id, true);
return Task.CompletedTask;
if (_imageOnly.TryGetValue(gch.GuildId, out var channels) && channels.TryRemove(ch.Id))
await ToggleImageOnlyChannelAsync(gch.GuildId, ch.Id, true);
}
private async Task DeleteQueueRunner()
@@ -65,31 +74,68 @@ public sealed class ImageOnlyChannelService : IExecOnMessage
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
{
// disable if bot can't delete messages in the channel
ToggleImageOnlyChannel(((ITextChannel)toDelete.Channel).GuildId, toDelete.Channel.Id, true);
await ToggleImageOnlyChannelAsync(((ITextChannel)toDelete.Channel).GuildId, toDelete.Channel.Id, true);
}
}
}
public bool ToggleImageOnlyChannel(ulong guildId, ulong channelId, bool forceDisable = false)
public async Task<bool> ToggleImageOnlyChannelAsync(ulong guildId, ulong channelId, bool forceDisable = false)
{
var newState = false;
using var uow = _db.GetDbContext();
if (forceDisable || (_enabledOn.TryGetValue(guildId, out var channels) && channels.TryRemove(channelId)))
uow.ImageOnlyChannels.Delete(x => x.ChannelId == channelId);
await using var uow = _db.GetDbContext();
if (forceDisable || (_imageOnly.TryGetValue(guildId, out var channels) && channels.TryRemove(channelId)))
{
await uow.ImageOnlyChannels.DeleteAsync(x => x.ChannelId == channelId && x.Type == OnlyChannelType.Image);
}
else
{
await uow.ImageOnlyChannels.DeleteAsync(x => x.ChannelId == channelId);
uow.ImageOnlyChannels.Add(new()
{
GuildId = guildId,
ChannelId = channelId
ChannelId = channelId,
Type = OnlyChannelType.Image
});
channels = _enabledOn.GetOrAdd(guildId, new ConcurrentHashSet<ulong>());
if (_linkOnly.TryGetValue(guildId, out var chs))
chs.TryRemove(channelId);
channels = _imageOnly.GetOrAdd(guildId, new ConcurrentHashSet<ulong>());
channels.Add(channelId);
newState = true;
}
uow.SaveChanges();
await uow.SaveChangesAsync();
return newState;
}
public async Task<bool> ToggleLinkOnlyChannelAsync(ulong guildId, ulong channelId, bool forceDisable = false)
{
var newState = false;
await using var uow = _db.GetDbContext();
if (forceDisable || (_linkOnly.TryGetValue(guildId, out var channels) && channels.TryRemove(channelId)))
{
await uow.ImageOnlyChannels.DeleteAsync(x => x.ChannelId == channelId && x.Type == OnlyChannelType.Link);
}
else
{
await uow.ImageOnlyChannels.DeleteAsync(x => x.ChannelId == channelId);
uow.ImageOnlyChannels.Add(new()
{
GuildId = guildId,
ChannelId = channelId,
Type = OnlyChannelType.Link
});
if (_imageOnly.TryGetValue(guildId, out var chs))
chs.TryRemove(channelId);
channels = _linkOnly.GetOrAdd(guildId, new ConcurrentHashSet<ulong>());
channels.Add(channelId);
newState = true;
}
await uow.SaveChangesAsync();
return newState;
}
@@ -98,12 +144,28 @@ public sealed class ImageOnlyChannelService : IExecOnMessage
if (msg.Channel is not ITextChannel tch)
return false;
if (msg.Attachments.Any(x => x is { Height: > 0, Width: > 0 }))
return false;
if (_imageOnly.TryGetValue(tch.GuildId, out var chs) && chs.Contains(msg.Channel.Id))
return await HandleOnlyChannel(tch, msg, OnlyChannelType.Image);
if (_linkOnly.TryGetValue(tch.GuildId, out chs) && chs.Contains(msg.Channel.Id))
return await HandleOnlyChannel(tch, msg, OnlyChannelType.Link);
if (!_enabledOn.TryGetValue(tch.GuildId, out var chs) || !chs.Contains(msg.Channel.Id))
return false;
return false;
}
private async Task<bool> HandleOnlyChannel(ITextChannel tch, IUserMessage msg, OnlyChannelType type)
{
if (type == OnlyChannelType.Image)
{
if (msg.Attachments.Any(x => x is { Height: > 0, Width: > 0 }))
return false;
}
else
{
if (msg.Content.TryGetUrlPath(out _))
return false;
}
var user = await tch.Guild.GetUserAsync(msg.Author.Id)
?? await _client.Rest.GetGuildUserAsync(tch.GuildId, msg.Author.Id);
@@ -113,7 +175,7 @@ public sealed class ImageOnlyChannelService : IExecOnMessage
// ignore owner and admin
if (user.Id == tch.Guild.OwnerId || user.GuildPermissions.Administrator)
{
Log.Information("Image-Only: Ignoring owner od admin ({ChannelId})", msg.Channel.Id);
Log.Information("{Type}-Only Channel: Ignoring owner od admin ({ChannelId})", type, msg.Channel.Id);
return false;
}
@@ -124,7 +186,11 @@ public sealed class ImageOnlyChannelService : IExecOnMessage
if (!botUser.GetPermissions(tch).ManageChannel)
{
ToggleImageOnlyChannel(tch.GuildId, tch.Id, true);
if(type == OnlyChannelType.Image)
await ToggleImageOnlyChannelAsync(tch.GuildId, tch.Id, true);
else
await ToggleImageOnlyChannelAsync(tch.GuildId, tch.Id, true);
return false;
}
@@ -132,7 +198,8 @@ public sealed class ImageOnlyChannelService : IExecOnMessage
if (shouldLock)
{
await tch.AddPermissionOverwriteAsync(msg.Author, new(sendMessages: PermValue.Deny));
Log.Warning("Image-Only: User {User} [{UserId}] has been banned from typing in the channel [{ChannelId}]",
Log.Warning("{Type}-Only Channel: User {User} [{UserId}] has been banned from typing in the channel [{ChannelId}]",
type,
msg.Author,
msg.Author.Id,
msg.Channel.Id);

View File

@@ -41,7 +41,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public async partial Task LanguageSet()
public async Task LanguageSet()
=> await ReplyConfirmLocalizedAsync(strs.lang_set_show(Format.Bold(Culture.ToString()),
Format.Bold(Culture.NativeName)));
@@ -49,7 +49,7 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(1)]
public async partial Task LanguageSet(string name)
public async Task LanguageSet(string name)
{
try
{
@@ -74,7 +74,7 @@ public partial class Administration
}
[Cmd]
public async partial Task LanguageSetDefault()
public async Task LanguageSetDefault()
{
var cul = _localization.DefaultCultureInfo;
await ReplyErrorLocalizedAsync(strs.lang_set_bot_show(cul, cul.NativeName));
@@ -82,7 +82,7 @@ public partial class Administration
[Cmd]
[OwnerOnly]
public async partial Task LanguageSetDefault(string name)
public async Task LanguageSetDefault(string name)
{
try
{
@@ -108,7 +108,7 @@ public partial class Administration
}
[Cmd]
public async partial Task LanguagesList()
public async Task LanguagesList()
=> await ctx.Channel.EmbedAsync(_eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.lang_list))

View File

@@ -26,7 +26,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
public async partial Task MuteRole([Leftover] IRole role = null)
public async Task MuteRole([Leftover] IRole role = null)
{
if (role is null)
{
@@ -51,7 +51,7 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
[Priority(0)]
public async partial Task Mute(IGuildUser target, [Leftover] string reason = "")
public async Task Mute(IGuildUser target, [Leftover] string reason = "")
{
try
{
@@ -72,7 +72,7 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
[Priority(1)]
public async partial Task Mute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
public async Task Mute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
{
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
return;
@@ -95,7 +95,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
public async partial Task Unmute(IGuildUser user, [Leftover] string reason = "")
public async Task Unmute(IGuildUser user, [Leftover] string reason = "")
{
try
{
@@ -112,7 +112,7 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async partial Task ChatMute(IGuildUser user, [Leftover] string reason = "")
public async Task ChatMute(IGuildUser user, [Leftover] string reason = "")
{
try
{
@@ -133,7 +133,7 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[Priority(1)]
public async partial Task ChatMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
public async Task ChatMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
{
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
return;
@@ -156,7 +156,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
public async partial Task ChatUnmute(IGuildUser user, [Leftover] string reason = "")
public async Task ChatUnmute(IGuildUser user, [Leftover] string reason = "")
{
try
{
@@ -173,7 +173,7 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.MuteMembers)]
[Priority(0)]
public async partial Task VoiceMute(IGuildUser user, [Leftover] string reason = "")
public async Task VoiceMute(IGuildUser user, [Leftover] string reason = "")
{
try
{
@@ -193,7 +193,7 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.MuteMembers)]
[Priority(1)]
public async partial Task VoiceMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
public async Task VoiceMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
{
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
return;
@@ -215,7 +215,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.MuteMembers)]
public async partial Task VoiceUnmute(IGuildUser user, [Leftover] string reason = "")
public async Task VoiceUnmute(IGuildUser user, [Leftover] string reason = "")
{
try
{

View File

@@ -1,5 +1,6 @@
#nullable disable
using Microsoft.EntityFrameworkCore;
using Nadeko.Common;
using NadekoBot.Db;
using NadekoBot.Services.Database.Models;

View File

@@ -1,4 +1,5 @@
#nullable disable
using Nadeko.Common;
using NadekoBot.Common.TypeReaders;
using NadekoBot.Modules.Administration.Services;
@@ -14,7 +15,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async partial Task DiscordPermOverride(CommandOrExprInfo cmd, params GuildPerm[] perms)
public async Task DiscordPermOverride(CommandOrExprInfo cmd, params GuildPerm[] perms)
{
if (perms is null || perms.Length == 0)
{
@@ -33,7 +34,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async partial Task DiscordPermOverrideReset()
public async Task DiscordPermOverrideReset()
{
var result = await PromptUserConfirmAsync(_eb.Create()
.WithOkColor()
@@ -50,7 +51,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async partial Task DiscordPermOverrideList(int page = 1)
public async Task DiscordPermOverrideList(int page = 1)
{
if (--page < 0)
return;

Some files were not shown because too many files have changed in this diff Show More