Compare commits

...

58 Commits

Author SHA1 Message Date
Kwoth
e036a2d3c9 fix: fixed .sclr again, again, and fixed .iamn 2024-11-27 03:26:57 +00:00
Kwoth
62a16f3faf fix: fixed .iam
fix: fixed .sclr not being respected on many different commands
change: .rps now also has the amount bet
2024-11-27 02:41:05 +00:00
Kwoth
231451f978 fix: fixed .sclr again 2024-11-27 01:28:17 +00:00
Kwoth
2bed4e7eac fix: Fixed many issues with 5.2.0 2024-11-27 01:23:38 +00:00
Kwoth
22eea3fa7b docs: updated changelog 2024-11-26 23:49:32 +00:00
Kwoth
8b1767078e fix: fixed sar migration 2024-11-26 23:31:27 +00:00
Kwoth
6654ccdad5 docs: updated commandlist 2024-11-26 20:56:49 +00:00
Kwoth
99827b0fa1 change: MessageXpCooldown is not in seconds 2024-11-26 19:58:20 +00:00
Kwoth
0451551ddf change: .gc will now remove previous plants with the same pw length from the channel and add them to itself. This is to avoid missed gcs. The amount text will be wrong however, as it will only show how much flowers spawned now. The user will get full amount of all gcs previously 2024-11-26 17:02:09 +00:00
Kwoth
75d0eb631d change: gambling commands now show amount bet. Slightly changed the layout. Updated some gambling strings
add: added .btr excl
2024-11-26 04:50:56 +00:00
Kwoth
96ce7e23bb Merge branch '520' into v5 2024-11-21 22:28:08 +00:00
Kwoth
aac475be62 fix: fixed time conversion 2024-11-21 19:02:22 +00:00
Kwoth
55fae53f6d fix: fixed time conversion 2024-11-21 19:00:36 +00:00
Kwoth
879f196f8b dev: new method in rotating service 2024-11-21 18:59:31 +00:00
Kwoth
796086538a .btr and .sclr added, cleanup 2024-11-20 17:16:41 +00:00
Kwoth
3178074828 fix: fixed .sinfo for guilds on other shards 2024-11-19 18:03:12 +00:00
Kwoth
0a9d53d380 change: sar rework, improved 2024-11-17 23:36:11 +00:00
Kwoth
d7fbf44d53 fix: re-added a missing extension method 2024-11-17 19:32:41 +00:00
Kwoth
cdd07d0559 fix: fixed accidental rename in previous patch 2024-11-17 19:17:04 +00:00
Kwoth
80a41c1f38 fix: fixed nullref in blacklist 2024-11-17 19:14:40 +00:00
Kwoth
1631394638 change: .bsreset price reduced 10x 2024-11-12 10:48:03 +00:00
Kwoth
7eb4895570 fix: fixed update usernames query 2024-11-12 01:09:06 +00:00
Kwoth
9b55028ba6 fix: fixed command description for betroll 2024-11-10 23:03:04 +00:00
Kwoth
53363d2840 fix: fixed newline missing in .timely 2024-11-10 22:31:48 +00:00
Kwoth
ca5a870bf8 docs: updated changelog 2024-11-10 16:08:26 +00:00
Kwoth
7f5e065c4d docs: updated commandlist, version upped to 5.1.20 2024-11-10 15:56:43 +00:00
Kwoth
86b214163a fix: typo 2024-11-10 12:28:06 +00:00
Kwoth
9dbb08d85f add: added .snipe command
add: added .gsreset and .bsreset commands
change: improved .timely rewards for patrons
dev: Improved how blacklist works under the hood
2024-11-08 18:24:50 +00:00
Kwoth
89ca56c77c fix: rich logging fix for userids, updated commandlist
add: added bot.date and changed bot.time placeholder. They use timestamp tags now.
fix: fixed double log on server leave
2024-11-07 05:38:22 +00:00
Kwoth
9e96679099 change: patreon reward bonuses increased slightly 2024-11-06 07:10:12 +00:00
Kwoth
1280d2b397 change: .divorce no longer has a cooldown
add: Added .waifuclaims / .claims command which lists your waifus (name, price and ids)
change: Timely now shows patreon multiplier bonus if there is any, (alongside boost)
2024-11-06 05:20:04 +00:00
Kwoth
c731127607 fix: missing example in commands 2024-11-05 09:40:01 +00:00
Kwoth
c15930306a fix: fix pipeline, missing aliases 2024-11-05 09:34:01 +00:00
Kwoth
701501d678 add: added .rakeback to get a part of the house edge back. Rakeback is accumulated by betting (not winning or losing in particular). All games have manually specified rakeback values
add: slot now has 1 more icon (wheat!), and multipliers have been modified to even out the gains
change: betroll is improved (around 2% better payout), as 66 is now a winning number, not a losing one
2024-11-05 08:24:21 +00:00
Kwoth
fa12fcea58 change: .race will now have 82-94% payout rate based on the number of players playign (1-12, x0.01 per player). Any player over 12 won't increase payout 2024-11-05 04:27:27 +00:00
Kwoth
274219c40b docs: Upped version to 5.1.19, updated changelog
fix: Fixed timely on different shards
2024-11-05 02:57:01 +00:00
Kwoth
96c9b47da2 add: timely now has an option in gambling whether to use no protection, captcha, or button.
fix: grpc api fix for dashy
2024-11-04 14:35:59 +00:00
Kwoth
b5d1469df1 Merge branch 'v5' of https://gitlab.com/kwoth/nadekobot into v5 2024-11-04 12:28:52 +00:00
Kwoth
d7747bd25a fix: timely fixes 2024-11-04 12:28:42 +00:00
Kwoth
7d162d1f04 fix: timely fixes 2024-11-04 12:28:01 +00:00
Kwoth
704d061d46 fix: fixed pipeline, added missing strings 2024-11-04 10:58:44 +00:00
Kwoth
c39c9061fd add: added timely boost bonus to gambling.yml
change: .betstats renamed to .gamblestats/.gs
add: added .betstats, .betstats <game> and .betstats <user> <game?> command which shows you your stats for gambling commands
2024-11-04 10:42:05 +00:00
Kwoth
619ddba4f8 fix: fixed pagination numbers in xplb and xpglb 2024-11-04 02:03:53 +00:00
Kwoth
3acef04b32 change: strikeout slightly thinner to make password easier to read on plants 2024-11-03 13:45:12 +00:00
Kwoth
83a1d959b1 fix: Added nordic and ugro finnic languages to flag translate 2024-11-03 12:05:51 +00:00
Kwoth
a1632722bc fix: fix timely 2024-11-03 09:32:43 +00:00
Kwoth
ee0a28afab fix: revert patron migration temporarily as ef core is bugging out hard 2024-11-03 08:39:37 +00:00
Kwoth
2b301c0aab fix: possible fix for patron table 2024-11-03 08:31:23 +00:00
Kwoth
b6b6b4e19e fix: Fixed UserId patron table error
fix: Added au and kz countries as en and kz languages respectively
fix: Strikeout is thinner now on plants
2024-11-03 08:24:00 +00:00
Kwoth
32fc8b6e03 docs: Upped version to 5.1.18, updated changelog 2024-11-03 03:44:33 +00:00
Kwoth
297e2fde0e change: timely 'password' is now a button 2024-11-03 03:41:34 +00:00
Kwoth
729f26caab button for timely 2024-11-03 02:48:39 +00:00
Kwoth
4b12e4e923 dev: Removed discrim from the database
add: .translateflags command
add: captcha to timely, configurable in .conf gambling
change: change bonuses for patreon rewards
fix: nunchi message color fix
2024-11-02 16:23:58 +00:00
Kwoth
12f4ce7f2a change: animal race will update more frequently, but animals will move slightly slower. Overall everything will be slightly faster 2024-11-01 04:53:20 +00:00
Kwoth
00944e08c3 fix: .ncs will now show an error if setting a pixel fails 2024-11-01 04:52:29 +00:00
Kwoth
569abd7194 api: work on server xp api 2024-10-31 11:48:31 +00:00
Kwoth
474a1db41d add: timely now has a 3 letter password by default. Configurable via .conf gamb 2024-10-31 11:48:09 +00:00
Kwoth
0f6255947e fix: fixed ubl pagination 2024-10-30 13:16:04 +00:00
197 changed files with 42013 additions and 2009 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +0,0 @@
dotnet ef migrations remove -c SqliteContext -f -p src/NadekoBot/NadekoBot.csproj
dotnet ef migrations remove -c PostgreSqlContext -f -p src/NadekoBot/NadekoBot.csproj

View File

@@ -0,0 +1,120 @@
syntax = "proto3";
option csharp_namespace = "NadekoBot.GrpcApi";
package xp;
service GrpcXp {
rpc GetXpLb(GetXpLbRequest) returns (GetXpLbReply);
rpc ResetUserXp(ResetUserXpRequest) returns (ResetUserXpReply);
rpc GetXpSettings(GetXpSettingsRequest) returns (GetXpSettingsReply);
rpc AddExclusion(AddExclusionRequest) returns (AddExclusionReply);
rpc DeleteExclusion(DeleteExclusionRequest) returns (DeleteExclusionReply);
rpc AddReward(AddRewardRequest) returns (AddRewardReply);
rpc DeleteReward(DeleteRewardRequest) returns (DeleteRewardReply);
rpc SetServerExclusion(SetServerExclusionRequest) returns (SetServerExclusionReply);
}
message SetServerExclusionRequest {
uint64 guildId = 1;
bool serverExcluded = 2;
}
message SetServerExclusionReply {
bool success = 1;
}
message GetXpLbRequest {
uint64 guildId = 1;
int32 page = 2;
}
message GetXpLbReply {
repeated XpLbUserReply users = 1;
int32 total = 2;
}
message XpLbUserReply {
uint64 userId = 1;
string username = 2;
int64 xp = 3;
int64 level = 4;
int64 levelPercent = 5;
string avatar = 6;
}
message ResetUserXpRequest {
uint64 guildId = 1;
uint64 userId = 2;
}
message ResetUserXpReply {
bool success = 1;
}
message GetXpSettingsReply {
repeated ExclItemReply exclusions = 1;
repeated RewItemReply rewards = 2;
bool serverExcluded = 3;
}
message GetXpSettingsRequest {
uint64 guildId = 1;
}
message ExclItemReply {
string type = 1;
uint64 id = 2;
string name = 3;
}
message RewItemReply {
int32 level = 1;
string type = 2;
string value = 3;
}
message AddExclusionRequest {
uint64 guildId = 1;
string type = 2;
uint64 id = 3;
}
message AddExclusionReply {
bool success = 1;
}
message DeleteExclusionRequest {
uint64 guildId = 1;
string type = 2;
uint64 id = 3;
}
message DeleteExclusionReply {
bool success = 1;
}
message AddRewardRequest {
uint64 guildId = 1;
int32 level = 2;
string type = 3;
string value = 4;
}
message AddRewardReply {
bool success = 1;
}
message DeleteRewardRequest {
uint64 guildId = 1;
int32 level = 2;
string type = 3;
}
message DeleteRewardReply {
bool success = 1;
}

View File

@@ -25,7 +25,6 @@ public static class DiscordUserExtensions
{
UserId = userId,
Username = username,
Discriminator = discrim,
AvatarId = avatarId,
TotalXp = 0,
CurrencyAmount = 0
@@ -33,7 +32,6 @@ public static class DiscordUserExtensions
old => new()
{
Username = username,
Discriminator = discrim,
AvatarId = avatarId
},
() => new()
@@ -49,8 +47,7 @@ public static class DiscordUserExtensions
() => new()
{
UserId = userId,
Username = "Unknown",
Discriminator = "????",
Username = "??Unknown",
AvatarId = string.Empty,
TotalXp = 0,
CurrencyAmount = 0

View File

@@ -1,22 +0,0 @@
#nullable disable
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db.Models;
namespace NadekoBot.Db;
public static class SelfAssignableRolesExtensions
{
public static bool DeleteByGuildAndRoleId(this DbSet<SelfAssignedRole> roles, ulong guildId, ulong roleId)
{
var role = roles.FirstOrDefault(s => s.GuildId == guildId && s.RoleId == roleId);
if (role is null)
return false;
roles.Remove(role);
return true;
}
public static IReadOnlyCollection<SelfAssignedRole> GetFromGuild(this DbSet<SelfAssignedRole> roles, ulong guildId)
=> roles.AsQueryable().Where(s => s.GuildId == guildId).ToArray();
}

View File

@@ -1,5 +1,4 @@
#nullable disable
using LinqToDB;
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Db.Models;
@@ -8,6 +7,9 @@ namespace NadekoBot.Db;
public static class UserXpExtensions
{
public static async Task<UserXpStats?> GetGuildUserXp(this ITable<UserXpStats> table, ulong guildId, ulong userId)
=> await table.FirstOrDefaultAsyncLinqToDB(x => x.GuildId == guildId && x.UserId == userId);
public static UserXpStats GetOrCreateUserXpStats(this DbContext ctx, ulong guildId, ulong userId)
{
var usr = ctx.Set<UserXpStats>().FirstOrDefault(x => x.UserId == userId && x.GuildId == guildId);
@@ -44,9 +46,6 @@ public static class UserXpExtensions
.CountAsyncLinqToDB()
+ 1;
public static void ResetGuildUserXp(this DbSet<UserXpStats> xps, ulong userId, ulong guildId)
=> xps.Delete(x => x.UserId == userId && x.GuildId == guildId);
public static void ResetGuildXp(this DbSet<UserXpStats> xps, ulong guildId)
=> xps.Delete(x => x.GuildId == guildId);

View File

@@ -7,7 +7,7 @@ public class DiscordUser : DbEntity
{
public ulong UserId { get; set; }
public string Username { get; set; }
public string Discriminator { get; set; }
// public string Discriminator { get; set; }
public string AvatarId { get; set; }
public int? ClubId { get; set; }
@@ -27,9 +27,6 @@ public class DiscordUser : DbEntity
public override string ToString()
{
if (string.IsNullOrWhiteSpace(Discriminator) || Discriminator == "0000")
return Username;
return Username + "#" + Discriminator;
return Username;
}
}

View File

@@ -0,0 +1,8 @@
#nullable disable
namespace NadekoBot.Db.Models;
public class FlagTranslateChannel : DbEntity
{
public ulong GuildId { get; set; }
public ulong ChannelId { get; set; }
}

View File

@@ -9,7 +9,8 @@ public class FollowedStream : DbEntity
Picarto = 3,
Youtube = 4,
Facebook = 5,
Trovo = 6
Trovo = 6,
Kick = 7,
}
public ulong GuildId { get; set; }

View File

@@ -1,11 +0,0 @@
#nullable disable
namespace NadekoBot.Db.Models;
public class GroupName : DbEntity
{
public int GuildConfigId { get; set; }
public GuildConfig GuildConfig { get; set; }
public int Number { get; set; }
public string Name { get; set; }
}

View File

@@ -5,14 +5,15 @@ namespace NadekoBot.Db.Models;
public class GuildColors
{
[Key]
public int Id { get; set; }
public ulong GuildId { get; set; }
[Length(0, 9)]
[MaxLength(9)]
public string? OkColor { get; set; }
[Length(0, 9)]
[MaxLength(9)]
public string? ErrorColor { get; set; }
[Length(0, 9)]
[MaxLength(9)]
public string? PendingColor { get; set; }
}

View File

@@ -13,28 +13,11 @@ public class GuildConfig : DbEntity
public string AutoAssignRoleIds { get; set; }
// //greet stuff
// public int AutoDeleteGreetMessagesTimer { get; set; } = 30;
// public int AutoDeleteByeMessagesTimer { get; set; } = 30;
//
// public ulong GreetMessageChannelId { get; set; }
// public ulong ByeMessageChannelId { get; set; }
//
// public bool SendDmGreetMessage { get; set; }
// public string DmGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!";
//
// public bool SendChannelGreetMessage { get; set; }
// public string ChannelGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!";
//
// public bool SendChannelByeMessage { get; set; }
// public string ChannelByeMessageText { get; set; } = "%user% has left!";
// public bool SendBoostMessage { get; set; }
// pulic int BoostMessageDeleteAfter { get; set; }
//self assignable roles
//todo FUTURE: DELETE, UNUSED
public bool ExclusiveSelfAssignedRoles { get; set; }
public bool AutoDeleteSelfAssignedRoleMessages { get; set; }
//stream notifications
public HashSet<FollowedStream> FollowedStreams { get; set; } = new();
@@ -91,15 +74,10 @@ public class GuildConfig : DbEntity
public List<FeedSub> FeedSubs { get; set; } = new();
public bool NotifyStreamOffline { get; set; }
public bool DeleteStreamOnlineMessage { get; set; }
public List<GroupName> SelfAssignableRoleGroupNames { get; set; }
public int WarnExpireHours { get; set; }
public WarnExpireAction WarnExpireAction { get; set; } = WarnExpireAction.Clear;
public bool DisableGlobalExpressions { get; set; } = false;
#region Boost Message
public bool StickyRoles { get; set; }
#endregion
}

View File

@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace NadekoBot.Db.Models;
public sealed class SarGroup
{
[Key]
public int Id { get; set; }
public int GroupNumber { get; set; }
public ulong GuildId { get; set; }
public ulong? RoleReq { get; set; }
public ICollection<Sar> Roles { get; set; } = [];
public bool IsExclusive { get; set; }
[MaxLength(100)]
public string? Name { get; set; }
}

View File

@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
namespace NadekoBot.Db.Models;
public sealed class ButtonRole
{
[Key]
public int Id { get; set; }
[MaxLength(200)]
public string ButtonId { get; set; } = string.Empty;
public ulong GuildId { get; set; }
public ulong ChannelId { get; set; }
public ulong MessageId { get; set; }
public int Position { get; set; }
public ulong RoleId { get; set; }
[MaxLength(100)]
public string Emote { get; set; } = string.Empty;
[MaxLength(50)]
public string Label { get; set; } = string.Empty;
public bool Exclusive { get; set; }
}

View File

@@ -1,11 +1,24 @@
#nullable disable
using System.ComponentModel.DataAnnotations;
namespace NadekoBot.Db.Models;
public class SelfAssignedRole : DbEntity
public sealed class Sar
{
[Key]
public int Id { get; set; }
public ulong GuildId { get; set; }
public ulong RoleId { get; set; }
public int Group { get; set; }
public int LevelRequirement { get; set; }
public int SarGroupId { get; set; }
public int LevelReq { get; set; }
}
public sealed class SarAutoDelete
{
[Key]
public int Id { get; set; }
public ulong GuildId { get; set; }
public bool IsEnabled { get; set; } = false;
}

View File

@@ -1,8 +1,12 @@
#nullable disable
using System.ComponentModel.DataAnnotations;
namespace NadekoBot.Db.Models;
public class PatronUser
{
// [Key]
// public int Id { get; set; }
public string UniquePlatformUserId { get; set; }
public ulong UserId { get; set; }
public int AmountCents { get; set; }

View File

@@ -2,6 +2,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using NadekoBot.Db.Models;
using NadekoBot.Modules.Administration.Services;
// ReSharper disable UnusedAutoPropertyAccessor.Global
@@ -14,7 +15,6 @@ public abstract class NadekoContext : DbContext
public DbSet<Quote> Quotes { get; set; }
public DbSet<Reminder> Reminders { get; set; }
public DbSet<SelfAssignedRole> SelfAssignableRoles { get; set; }
public DbSet<MusicPlaylist> MusicPlaylists { get; set; }
public DbSet<NadekoExpression> Expressions { get; set; }
public DbSet<CurrencyTransaction> CurrencyTransactions { get; set; }
@@ -62,6 +62,7 @@ public abstract class NadekoContext : DbContext
public DbSet<ArchivedTodoListModel> TodosArchive { get; set; }
public DbSet<HoneypotChannel> HoneyPotChannels { get; set; }
// public DbSet<GuildColors> GuildColors { get; set; }
@@ -73,6 +74,89 @@ public abstract class NadekoContext : DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region GuildColors
modelBuilder.Entity<GuildColors>()
.HasIndex(x => x.GuildId)
.IsUnique(true);
#endregion
#region Button Roles
modelBuilder.Entity<ButtonRole>(br =>
{
br.HasIndex(x => x.GuildId)
.IsUnique(false);
br.HasAlternateKey(x => new
{
x.RoleId,
x.MessageId,
});
});
#endregion
#region New Sar
modelBuilder.Entity<SarGroup>(sg =>
{
sg.HasAlternateKey(x => new
{
x.GuildId,
x.GroupNumber
});
sg.HasMany(x => x.Roles)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<Sar>()
.HasAlternateKey(x => new
{
x.GuildId,
x.RoleId
});
modelBuilder.Entity<SarAutoDelete>()
.HasIndex(x => x.GuildId)
.IsUnique();
#endregion
#region Rakeback
modelBuilder.Entity<Rakeback>()
.HasKey(x => x.UserId);
#endregion
#region UserBetStats
modelBuilder.Entity<UserBetStats>()
.HasIndex(x => new
{
x.UserId,
x.Game
})
.IsUnique();
#endregion
#region Flag Translate
modelBuilder.Entity<FlagTranslateChannel>()
.HasIndex(x => new
{
x.GuildId,
x.ChannelId
})
.IsUnique();
#endregion
#region NCanvas
modelBuilder.Entity<NCPixel>()
@@ -261,11 +345,6 @@ public abstract class NadekoContext : DbContext
.HasForeignKey(x => x.GuildConfigId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<GuildConfig>()
.HasMany(x => x.SelfAssignableRoleGroupNames)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<FeedSub>()
.HasAlternateKey(x => new
{
@@ -294,21 +373,6 @@ public abstract class NadekoContext : DbContext
#endregion
#region Self Assignable Roles
var selfassignableRolesEntity = modelBuilder.Entity<SelfAssignedRole>();
selfassignableRolesEntity.HasIndex(s => new
{
s.GuildId,
s.RoleId
})
.IsUnique();
selfassignableRolesEntity.Property(x => x.Group).HasDefaultValue(0);
#endregion
#region MusicPlaylists
var musicPlaylistEntity = modelBuilder.Entity<MusicPlaylist>();
@@ -491,23 +555,6 @@ public abstract class NadekoContext : DbContext
#endregion
#region GroupName
modelBuilder.Entity<GroupName>()
.HasIndex(x => new
{
x.GuildConfigId,
x.Number
})
.IsUnique();
modelBuilder.Entity<GroupName>()
.HasOne(x => x.GuildConfig)
.WithMany(x => x.SelfAssignableRoleGroupNames)
.IsRequired();
#endregion
#region BanTemplate
modelBuilder.Entity<BanTemplate>().HasIndex(x => x.GuildId).IsUnique();

View File

@@ -5,6 +5,41 @@ namespace NadekoBot.Migrations;
public static class MigrationQueries
{
public static void MigrateSar(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("""
INSERT INTO GroupName (Number, GuildConfigId)
SELECT DISTINCT "Group", GC.Id
FROM SelfAssignableRoles as SAR
INNER JOIN GuildConfigs as GC
ON SAR.GuildId = GC.GuildId
WHERE SAR.GuildId not in (SELECT GuildConfigs.GuildId from GroupName LEFT JOIN GuildConfigs ON GroupName.GuildConfigId = GuildConfigs.Id);
INSERT INTO SarGroup (Id, GroupNumber, Name, IsExclusive, GuildId)
SELECT GN.Id, GN.Number, GN.Name, GC.ExclusiveSelfAssignedRoles, GC.GuildId
FROM GroupName as GN
INNER JOIN GuildConfigs as GC ON GN.GuildConfigId = GC.Id;
INSERT INTO Sar (GuildId, RoleId, SarGroupId, LevelReq)
SELECT SAR.GuildId, SAR.RoleId, (SELECT Id FROM SarGroup WHERE SG.Number = SarGroup.GroupNumber AND SG.GuildId = SarGroup.GuildId), MIN(SAR.LevelRequirement)
FROM SelfAssignableRoles as SAR
INNER JOIN (SELECT GuildId, gn.Number FROM GroupName as gn
INNER JOIN GuildConfigs as gc ON gn.GuildConfigId =gc.Id
) as SG
ON SG.GuildId = SAR.GuildId
WHERE SG.Number IN (SELECT GroupNumber FROM SarGroup WHERE Sar.GuildId = SarGroup.GuildId)
GROUP BY SAR.GuildId, SAR.RoleId;
INSERT INTO SarAutoDelete (GuildId, IsEnabled)
SELECT GuildId, AutoDeleteSelfAssignedRoleMessages FROM GuildConfigs WHERE AutoDeleteSelfAssignedRoleMessages = TRUE;
""");
}
public static void UpdateUsernames(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("UPDATE DiscordUser SET Username = '??' || Username WHERE Discriminator = '????';");
}
public static void MigrateRero(MigrationBuilder migrationBuilder)
{
if (migrationBuilder.IsSqlite())
@@ -73,7 +108,7 @@ left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;")
DELETE FROM WarningPunishment WHERE GuildConfigId IS NULL OR GuildConfigId NOT IN (SELECT Id FROM GuildConfigs);
UPDATE WarningPunishment
SET GuildId = (SELECT GuildId FROM GuildConfigs WHERE Id = GuildConfigId);
DELETE FROM WarningPunishment as wp
WHERE (wp.Count, wp.GuildConfigId) in (
SELECT wp2.Count, wp2.GuildConfigId FROM WarningPunishment as wp2

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
/// <inheritdoc />
public partial class nodiscrimandflagtranslate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "discriminator",
table: "discorduser");
migrationBuilder.CreateTable(
name: "flagtranslatechannel",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_flagtranslatechannel", x => x.id);
});
migrationBuilder.CreateIndex(
name: "ix_flagtranslatechannel_guildid_channelid",
table: "flagtranslatechannel",
columns: new[] { "guildid", "channelid" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "flagtranslatechannel");
migrationBuilder.AddColumn<string>(
name: "discriminator",
table: "discorduser",
type: "text",
nullable: true);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
/// <inheritdoc />
public partial class betstats : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "userbetstats",
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),
game = table.Column<int>(type: "integer", nullable: false),
wincount = table.Column<long>(type: "bigint", nullable: false),
losecount = table.Column<long>(type: "bigint", nullable: false),
totalbet = table.Column<decimal>(type: "numeric", nullable: false),
paidout = table.Column<decimal>(type: "numeric", nullable: false),
maxwin = table.Column<long>(type: "bigint", nullable: false),
maxbet = table.Column<long>(type: "bigint", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_userbetstats", x => x.id);
});
migrationBuilder.CreateIndex(
name: "ix_userbetstats_userid_game",
table: "userbetstats",
columns: new[] { "userid", "game" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "userbetstats");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
/// <inheritdoc />
public partial class rakeback : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "rakeback",
columns: table => new
{
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
amount = table.Column<decimal>(type: "numeric", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_rakeback", x => x.userid);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "rakeback");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,170 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
/// <inheritdoc />
public partial class sarrework : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "sarautodelete",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy",
NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
isenabled = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_sarautodelete", x => x.id);
});
migrationBuilder.CreateTable(
name: "sargroup",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy",
NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
groupnumber = table.Column<int>(type: "integer", nullable: false),
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
rolereq = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
isexclusive = table.Column<bool>(type: "boolean", nullable: false),
name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_sargroup", x => x.id);
table.UniqueConstraint("ak_sargroup_guildid_groupnumber",
x => new
{
x.guildid,
x.groupnumber
});
});
migrationBuilder.CreateTable(
name: "sar",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy",
NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
sargroupid = table.Column<int>(type: "integer", nullable: false),
levelreq = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_sar", x => x.id);
table.UniqueConstraint("ak_sar_guildid_roleid",
x => new
{
x.guildid,
x.roleid
});
table.ForeignKey(
name: "fk_sar_sargroup_sargroupid",
column: x => x.sargroupid,
principalTable: "sargroup",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "ix_sar_sargroupid",
table: "sar",
column: "sargroupid");
migrationBuilder.CreateIndex(
name: "ix_sarautodelete_guildid",
table: "sarautodelete",
column: "guildid",
unique: true);
MigrationQueries.MigrateSar(migrationBuilder);
migrationBuilder.DropTable(
name: "groupname");
migrationBuilder.DropTable(
name: "selfassignableroles");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "sar");
migrationBuilder.DropTable(
name: "sarautodelete");
migrationBuilder.DropTable(
name: "sargroup");
migrationBuilder.CreateTable(
name: "groupname",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy",
NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
guildconfigid = table.Column<int>(type: "integer", nullable: false),
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
name = table.Column<string>(type: "text", nullable: true),
number = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_groupname", x => x.id);
table.ForeignKey(
name: "fk_groupname_guildconfigs_guildconfigid",
column: x => x.guildconfigid,
principalTable: "guildconfigs",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "selfassignableroles",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy",
NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
group = table.Column<int>(type: "integer", nullable: false, defaultValue: 0),
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
levelrequirement = table.Column<int>(type: "integer", nullable: false),
roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_selfassignableroles", x => x.id);
});
migrationBuilder.CreateIndex(
name: "ix_groupname_guildconfigid_number",
table: "groupname",
columns: new[] { "guildconfigid", "number" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_selfassignableroles_guildid_roleid",
table: "selfassignableroles",
columns: new[] { "guildid", "roleid" },
unique: true);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
/// <inheritdoc />
public partial class btnroles_guildcolors : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "buttonrole",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
buttonid = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
messageid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
position = table.Column<int>(type: "integer", nullable: false),
roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
emote = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
label = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
exclusive = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_buttonrole", x => x.id);
table.UniqueConstraint("ak_buttonrole_roleid_messageid", x => new { x.roleid, x.messageid });
});
migrationBuilder.CreateTable(
name: "guildcolors",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
okcolor = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: true),
errorcolor = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: true),
pendingcolor = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_guildcolors", x => x.id);
});
migrationBuilder.CreateIndex(
name: "ix_buttonrole_guildid",
table: "buttonrole",
column: "guildid");
migrationBuilder.CreateIndex(
name: "ix_guildcolors_guildid",
table: "guildcolors",
column: "guildid",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "buttonrole");
migrationBuilder.DropTable(
name: "guildcolors");
}
}
}

View File

@@ -451,6 +451,69 @@ namespace NadekoBot.Migrations.PostgreSql
b.ToTable("blacklist", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.ButtonRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ButtonId")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("buttonid");
b.Property<decimal>("ChannelId")
.HasColumnType("numeric(20,0)")
.HasColumnName("channelid");
b.Property<string>("Emote")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("emote");
b.Property<bool>("Exclusive")
.HasColumnType("boolean")
.HasColumnName("exclusive");
b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.Property<string>("Label")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("label");
b.Property<decimal>("MessageId")
.HasColumnType("numeric(20,0)")
.HasColumnName("messageid");
b.Property<int>("Position")
.HasColumnType("integer")
.HasColumnName("position");
b.Property<decimal>("RoleId")
.HasColumnType("numeric(20,0)")
.HasColumnName("roleid");
b.HasKey("Id")
.HasName("pk_buttonrole");
b.HasAlternateKey("RoleId", "MessageId")
.HasName("ak_buttonrole_roleid_messageid");
b.HasIndex("GuildId")
.HasDatabaseName("ix_buttonrole_guildid");
b.ToTable("buttonrole", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>
{
b.Property<int>("ClubId")
@@ -751,10 +814,6 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<string>("Discriminator")
.HasColumnType("text")
.HasColumnName("discriminator");
b.Property<bool>("IsClubAdmin")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
@@ -998,6 +1057,37 @@ namespace NadekoBot.Migrations.PostgreSql
b.ToTable("filteredword", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.FlagTranslateChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("ChannelId")
.HasColumnType("numeric(20,0)")
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.HasKey("Id")
.HasName("pk_flagtranslatechannel");
b.HasIndex("GuildId", "ChannelId")
.IsUnique()
.HasDatabaseName("ix_flagtranslatechannel_guildid_channelid");
b.ToTable("flagtranslatechannel", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b =>
{
b.Property<int>("Id")
@@ -1175,7 +1265,7 @@ namespace NadekoBot.Migrations.PostgreSql
b.ToTable("giveawayuser", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.GroupName", b =>
modelBuilder.Entity("NadekoBot.Db.Models.GuildColors", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
@@ -1184,30 +1274,33 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<string>("ErrorColor")
.HasMaxLength(9)
.HasColumnType("character varying(9)")
.HasColumnName("errorcolor");
b.Property<int>("GuildConfigId")
.HasColumnType("integer")
.HasColumnName("guildconfigid");
b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.Property<string>("Name")
.HasColumnType("text")
.HasColumnName("name");
b.Property<string>("OkColor")
.HasMaxLength(9)
.HasColumnType("character varying(9)")
.HasColumnName("okcolor");
b.Property<int>("Number")
.HasColumnType("integer")
.HasColumnName("number");
b.Property<string>("PendingColor")
.HasMaxLength(9)
.HasColumnType("character varying(9)")
.HasColumnName("pendingcolor");
b.HasKey("Id")
.HasName("pk_groupname");
.HasName("pk_guildcolors");
b.HasIndex("GuildConfigId", "Number")
b.HasIndex("GuildId")
.IsUnique()
.HasDatabaseName("ix_groupname_guildconfigid_number");
.HasDatabaseName("ix_guildcolors_guildid");
b.ToTable("groupname", (string)null);
b.ToTable("guildcolors", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.GuildConfig", b =>
@@ -2173,7 +2266,7 @@ namespace NadekoBot.Migrations.PostgreSql
b.ToTable("rotatingstatus", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.SelfAssignedRole", b =>
modelBuilder.Entity("NadekoBot.Db.Models.Sar", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
@@ -2182,36 +2275,98 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int>("Group")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(0)
.HasColumnName("group");
b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.Property<int>("LevelRequirement")
b.Property<int>("LevelReq")
.HasColumnType("integer")
.HasColumnName("levelrequirement");
.HasColumnName("levelreq");
b.Property<decimal>("RoleId")
.HasColumnType("numeric(20,0)")
.HasColumnName("roleid");
b.Property<int>("SarGroupId")
.HasColumnType("integer")
.HasColumnName("sargroupid");
b.HasKey("Id")
.HasName("pk_selfassignableroles");
.HasName("pk_sar");
b.HasIndex("GuildId", "RoleId")
b.HasAlternateKey("GuildId", "RoleId")
.HasName("ak_sar_guildid_roleid");
b.HasIndex("SarGroupId")
.HasDatabaseName("ix_sar_sargroupid");
b.ToTable("sar", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.SarAutoDelete", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.Property<bool>("IsEnabled")
.HasColumnType("boolean")
.HasColumnName("isenabled");
b.HasKey("Id")
.HasName("pk_sarautodelete");
b.HasIndex("GuildId")
.IsUnique()
.HasDatabaseName("ix_selfassignableroles_guildid_roleid");
.HasDatabaseName("ix_sarautodelete_guildid");
b.ToTable("selfassignableroles", (string)null);
b.ToTable("sarautodelete", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.SarGroup", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("GroupNumber")
.HasColumnType("integer")
.HasColumnName("groupnumber");
b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.Property<bool>("IsExclusive")
.HasColumnType("boolean")
.HasColumnName("isexclusive");
b.Property<string>("Name")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("name");
b.Property<decimal?>("RoleReq")
.HasColumnType("numeric(20,0)")
.HasColumnName("rolereq");
b.HasKey("Id")
.HasName("pk_sargroup");
b.HasAlternateKey("GuildId", "GroupNumber")
.HasName("ak_sargroup_guildid_groupnumber");
b.ToTable("sargroup", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.ShopEntry", b =>
@@ -3200,6 +3355,74 @@ namespace NadekoBot.Migrations.PostgreSql
b.ToTable("greetsettings", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Rakeback", b =>
{
b.Property<decimal>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("numeric(20,0)")
.HasColumnName("userid");
b.Property<decimal>("Amount")
.HasColumnType("numeric")
.HasColumnName("amount");
b.HasKey("UserId")
.HasName("pk_rakeback");
b.ToTable("rakeback", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.UserBetStats", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("Game")
.HasColumnType("integer")
.HasColumnName("game");
b.Property<long>("LoseCount")
.HasColumnType("bigint")
.HasColumnName("losecount");
b.Property<long>("MaxBet")
.HasColumnType("bigint")
.HasColumnName("maxbet");
b.Property<long>("MaxWin")
.HasColumnType("bigint")
.HasColumnName("maxwin");
b.Property<decimal>("PaidOut")
.HasColumnType("numeric")
.HasColumnName("paidout");
b.Property<decimal>("TotalBet")
.HasColumnType("numeric")
.HasColumnName("totalbet");
b.Property<decimal>("UserId")
.HasColumnType("numeric(20,0)")
.HasColumnName("userid");
b.Property<long>("WinCount")
.HasColumnType("bigint")
.HasColumnName("wincount");
b.HasKey("Id")
.HasName("pk_userbetstats");
b.HasIndex("UserId", "Game")
.IsUnique()
.HasDatabaseName("ix_userbetstats_userid_game");
b.ToTable("userbetstats", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.AntiAltSetting", b =>
{
b.HasOne("NadekoBot.Db.Models.GuildConfig", null)
@@ -3432,18 +3655,6 @@ namespace NadekoBot.Migrations.PostgreSql
.HasConstraintName("fk_giveawayuser_giveawaymodel_giveawayid");
});
modelBuilder.Entity("NadekoBot.Db.Models.GroupName", b =>
{
b.HasOne("NadekoBot.Db.Models.GuildConfig", "GuildConfig")
.WithMany("SelfAssignableRoleGroupNames")
.HasForeignKey("GuildConfigId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_groupname_guildconfigs_guildconfigid");
b.Navigation("GuildConfig");
});
modelBuilder.Entity("NadekoBot.Db.Models.IgnoredLogItem", b =>
{
b.HasOne("NadekoBot.Db.Models.LogSetting", "LogSetting")
@@ -3483,6 +3694,16 @@ namespace NadekoBot.Migrations.PostgreSql
.HasConstraintName("fk_playlistsong_musicplaylists_musicplaylistid");
});
modelBuilder.Entity("NadekoBot.Db.Models.Sar", b =>
{
b.HasOne("NadekoBot.Db.Models.SarGroup", null)
.WithMany("Roles")
.HasForeignKey("SarGroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_sar_sargroup_sargroupid");
});
modelBuilder.Entity("NadekoBot.Db.Models.ShopEntry", b =>
{
b.HasOne("NadekoBot.Db.Models.GuildConfig", null)
@@ -3759,8 +3980,6 @@ namespace NadekoBot.Migrations.PostgreSql
b.Navigation("Permissions");
b.Navigation("SelfAssignableRoleGroupNames");
b.Navigation("ShopEntries");
b.Navigation("SlowmodeIgnoredRoles");
@@ -3790,6 +4009,11 @@ namespace NadekoBot.Migrations.PostgreSql
b.Navigation("Songs");
});
modelBuilder.Entity("NadekoBot.Db.Models.SarGroup", b =>
{
b.Navigation("Roles");
});
modelBuilder.Entity("NadekoBot.Db.Models.ShopEntry", b =>
{
b.Navigation("Items");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
/// <inheritdoc />
public partial class nodiscrimandflagtranslate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Discriminator",
table: "DiscordUser");
migrationBuilder.CreateTable(
name: "FlagTranslateChannel",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_FlagTranslateChannel", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_FlagTranslateChannel_GuildId_ChannelId",
table: "FlagTranslateChannel",
columns: new[] { "GuildId", "ChannelId" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "FlagTranslateChannel");
migrationBuilder.AddColumn<string>(
name: "Discriminator",
table: "DiscordUser",
type: "TEXT",
nullable: true);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
/// <inheritdoc />
public partial class betstats : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "UserBetStats",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
Game = table.Column<int>(type: "INTEGER", nullable: false),
WinCount = table.Column<long>(type: "INTEGER", nullable: false),
LoseCount = table.Column<long>(type: "INTEGER", nullable: false),
TotalBet = table.Column<decimal>(type: "TEXT", nullable: false),
PaidOut = table.Column<decimal>(type: "TEXT", nullable: false),
MaxWin = table.Column<long>(type: "INTEGER", nullable: false),
MaxBet = table.Column<long>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserBetStats", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_UserBetStats_UserId_Game",
table: "UserBetStats",
columns: new[] { "UserId", "Game" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "UserBetStats");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
/// <inheritdoc />
public partial class rakeback : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Rakeback",
columns: table => new
{
UserId = table.Column<ulong>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Amount = table.Column<decimal>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Rakeback", x => x.UserId);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Rakeback");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,163 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
/// <inheritdoc />
public partial class sarrework : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "SarAutoDelete",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SarAutoDelete", x => x.Id);
});
migrationBuilder.CreateTable(
name: "SarGroup",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
GroupNumber = table.Column<int>(type: "INTEGER", nullable: false),
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
RoleReq = table.Column<ulong>(type: "INTEGER", nullable: true),
IsExclusive = table.Column<bool>(type: "INTEGER", nullable: false),
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_SarGroup", x => x.Id);
table.UniqueConstraint("AK_SarGroup_GuildId_GroupNumber",
x => new
{
x.GuildId,
x.GroupNumber
});
});
migrationBuilder.CreateTable(
name: "Sar",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false),
SarGroupId = table.Column<int>(type: "INTEGER", nullable: false),
LevelReq = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Sar", x => x.Id);
table.UniqueConstraint("AK_Sar_GuildId_RoleId",
x => new
{
x.GuildId,
x.RoleId
});
table.ForeignKey(
name: "FK_Sar_SarGroup_SarGroupId",
column: x => x.SarGroupId,
principalTable: "SarGroup",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Sar_SarGroupId",
table: "Sar",
column: "SarGroupId");
migrationBuilder.CreateIndex(
name: "IX_SarAutoDelete_GuildId",
table: "SarAutoDelete",
column: "GuildId",
unique: true);
MigrationQueries.MigrateSar(migrationBuilder);
migrationBuilder.DropTable(
name: "GroupName");
migrationBuilder.DropTable(
name: "SelfAssignableRoles");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Sar");
migrationBuilder.DropTable(
name: "SarAutoDelete");
migrationBuilder.DropTable(
name: "SarGroup");
migrationBuilder.CreateTable(
name: "GroupName",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
GuildConfigId = table.Column<int>(type: "INTEGER", nullable: false),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
Name = table.Column<string>(type: "TEXT", nullable: true),
Number = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GroupName", x => x.Id);
table.ForeignKey(
name: "FK_GroupName_GuildConfigs_GuildConfigId",
column: x => x.GuildConfigId,
principalTable: "GuildConfigs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "SelfAssignableRoles",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
Group = table.Column<int>(type: "INTEGER", nullable: false, defaultValue: 0),
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
LevelRequirement = table.Column<int>(type: "INTEGER", nullable: false),
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SelfAssignableRoles", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_GroupName_GuildConfigId_Number",
table: "GroupName",
columns: new[] { "GuildConfigId", "Number" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_SelfAssignableRoles_GuildId_RoleId",
table: "SelfAssignableRoles",
columns: new[] { "GuildId", "RoleId" },
unique: true);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,73 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
/// <inheritdoc />
public partial class btnroles_guildcolors : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ButtonRole",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ButtonId = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
MessageId = table.Column<ulong>(type: "INTEGER", nullable: false),
Position = table.Column<int>(type: "INTEGER", nullable: false),
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false),
Emote = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
Label = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
Exclusive = table.Column<bool>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ButtonRole", x => x.Id);
table.UniqueConstraint("AK_ButtonRole_RoleId_MessageId", x => new { x.RoleId, x.MessageId });
});
migrationBuilder.CreateTable(
name: "GuildColors",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
OkColor = table.Column<string>(type: "TEXT", maxLength: 9, nullable: true),
ErrorColor = table.Column<string>(type: "TEXT", maxLength: 9, nullable: true),
PendingColor = table.Column<string>(type: "TEXT", maxLength: 9, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_GuildColors", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_ButtonRole_GuildId",
table: "ButtonRole",
column: "GuildId");
migrationBuilder.CreateIndex(
name: "IX_GuildColors_GuildId",
table: "GuildColors",
column: "GuildId",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ButtonRole");
migrationBuilder.DropTable(
name: "GuildColors");
}
}
}

View File

@@ -335,6 +335,54 @@ namespace NadekoBot.Migrations
b.ToTable("Blacklist");
});
modelBuilder.Entity("NadekoBot.Db.Models.ButtonRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ButtonId")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<ulong>("ChannelId")
.HasColumnType("INTEGER");
b.Property<string>("Emote")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<bool>("Exclusive")
.HasColumnType("INTEGER");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<string>("Label")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<ulong>("MessageId")
.HasColumnType("INTEGER");
b.Property<int>("Position")
.HasColumnType("INTEGER");
b.Property<ulong>("RoleId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasAlternateKey("RoleId", "MessageId");
b.HasIndex("GuildId");
b.ToTable("ButtonRole");
});
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>
{
b.Property<int>("ClubId")
@@ -560,9 +608,6 @@ namespace NadekoBot.Migrations
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<string>("Discriminator")
.HasColumnType("TEXT");
b.Property<bool>("IsClubAdmin")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
@@ -743,6 +788,29 @@ namespace NadekoBot.Migrations
b.ToTable("FilteredWord");
});
modelBuilder.Entity("NadekoBot.Db.Models.FlagTranslateChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ulong>("ChannelId")
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("GuildId", "ChannelId")
.IsUnique();
b.ToTable("FlagTranslateChannel");
});
modelBuilder.Entity("NadekoBot.Db.Models.FollowedStream", b =>
{
b.Property<int>("Id")
@@ -874,30 +942,33 @@ namespace NadekoBot.Migrations
b.ToTable("GiveawayUser");
});
modelBuilder.Entity("NadekoBot.Db.Models.GroupName", b =>
modelBuilder.Entity("NadekoBot.Db.Models.GuildColors", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateAdded")
b.Property<string>("ErrorColor")
.HasMaxLength(9)
.HasColumnType("TEXT");
b.Property<int>("GuildConfigId")
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
b.Property<string>("OkColor")
.HasMaxLength(9)
.HasColumnType("TEXT");
b.Property<int>("Number")
.HasColumnType("INTEGER");
b.Property<string>("PendingColor")
.HasMaxLength(9)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("GuildConfigId", "Number")
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("GroupName");
b.ToTable("GuildColors");
});
modelBuilder.Entity("NadekoBot.Db.Models.GuildConfig", b =>
@@ -1620,35 +1691,80 @@ namespace NadekoBot.Migrations
b.ToTable("RotatingStatus");
});
modelBuilder.Entity("NadekoBot.Db.Models.SelfAssignedRole", b =>
modelBuilder.Entity("NadekoBot.Db.Models.Sar", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<int>("Group")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<int>("LevelRequirement")
b.Property<int>("LevelReq")
.HasColumnType("INTEGER");
b.Property<ulong>("RoleId")
.HasColumnType("INTEGER");
b.Property<int>("SarGroupId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("GuildId", "RoleId")
b.HasAlternateKey("GuildId", "RoleId");
b.HasIndex("SarGroupId");
b.ToTable("Sar");
});
modelBuilder.Entity("NadekoBot.Db.Models.SarAutoDelete", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("GuildId")
.IsUnique();
b.ToTable("SelfAssignableRoles");
b.ToTable("SarAutoDelete");
});
modelBuilder.Entity("NadekoBot.Db.Models.SarGroup", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("GroupNumber")
.HasColumnType("INTEGER");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<bool>("IsExclusive")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<ulong?>("RoleReq")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasAlternateKey("GuildId", "GroupNumber");
b.ToTable("SarGroup");
});
modelBuilder.Entity("NadekoBot.Db.Models.ShopEntry", b =>
@@ -2379,6 +2495,58 @@ namespace NadekoBot.Migrations
b.ToTable("GreetSettings");
});
modelBuilder.Entity("NadekoBot.Services.Rakeback", b =>
{
b.Property<ulong>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<decimal>("Amount")
.HasColumnType("TEXT");
b.HasKey("UserId");
b.ToTable("Rakeback");
});
modelBuilder.Entity("NadekoBot.Services.UserBetStats", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Game")
.HasColumnType("INTEGER");
b.Property<long>("LoseCount")
.HasColumnType("INTEGER");
b.Property<long>("MaxBet")
.HasColumnType("INTEGER");
b.Property<long>("MaxWin")
.HasColumnType("INTEGER");
b.Property<decimal>("PaidOut")
.HasColumnType("TEXT");
b.Property<decimal>("TotalBet")
.HasColumnType("TEXT");
b.Property<ulong>("UserId")
.HasColumnType("INTEGER");
b.Property<long>("WinCount")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId", "Game")
.IsUnique();
b.ToTable("UserBetStats");
});
modelBuilder.Entity("NadekoBot.Db.Models.AntiAltSetting", b =>
{
b.HasOne("NadekoBot.Db.Models.GuildConfig", null)
@@ -2588,17 +2756,6 @@ namespace NadekoBot.Migrations
.IsRequired();
});
modelBuilder.Entity("NadekoBot.Db.Models.GroupName", b =>
{
b.HasOne("NadekoBot.Db.Models.GuildConfig", "GuildConfig")
.WithMany("SelfAssignableRoleGroupNames")
.HasForeignKey("GuildConfigId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("GuildConfig");
});
modelBuilder.Entity("NadekoBot.Db.Models.IgnoredLogItem", b =>
{
b.HasOne("NadekoBot.Db.Models.LogSetting", "LogSetting")
@@ -2634,6 +2791,15 @@ namespace NadekoBot.Migrations
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("NadekoBot.Db.Models.Sar", b =>
{
b.HasOne("NadekoBot.Db.Models.SarGroup", null)
.WithMany("Roles")
.HasForeignKey("SarGroupId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("NadekoBot.Db.Models.ShopEntry", b =>
{
b.HasOne("NadekoBot.Db.Models.GuildConfig", null)
@@ -2888,8 +3054,6 @@ namespace NadekoBot.Migrations
b.Navigation("Permissions");
b.Navigation("SelfAssignableRoleGroupNames");
b.Navigation("ShopEntries");
b.Navigation("SlowmodeIgnoredRoles");
@@ -2919,6 +3083,11 @@ namespace NadekoBot.Migrations
b.Navigation("Songs");
});
modelBuilder.Entity("NadekoBot.Db.Models.SarGroup", b =>
{
b.Navigation("Roles");
});
modelBuilder.Entity("NadekoBot.Db.Models.ShopEntry", b =>
{
b.Navigation("Items");

View File

@@ -96,7 +96,7 @@ public partial class Administration : NadekoModule<AdministrationService>
var guild = (SocketGuild)ctx.Guild;
var (enabled, channels) = _service.GetDelMsgOnCmdData(ctx.Guild.Id);
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.server_delmsgoncmd))
.WithDescription(enabled ? "✅" : "❌");

View File

@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore;
using NadekoBot.Db.Models;
using NadekoBot.Modules.Administration._common.results;
namespace NadekoBot.Modules.Administration.Services;
namespace NadekoBot.Modules.Administration;
public class AdministrationService : INService
{

View File

@@ -136,7 +136,7 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
await using var linqCtx = ctx.CreateLinqToDBContext();
await using var tempTable = linqCtx.CreateTempTable<CleanupId>();
foreach (var chunk in allIds.Chunk(20000))
foreach (var chunk in allIds.Chunk(10000))
{
await tempTable.BulkCopyAsync(chunk.Select(x => new CleanupId()
{
@@ -187,13 +187,6 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
.Contains(x.GuildId))
.DeleteAsync();
// delete ignored users
await ctx.GetTable<DiscordPermOverride>()
.Where(x => x.GuildId != null
&& !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId.Value))
.DeleteAsync();
// delete perm overrides
await ctx.GetTable<DiscordPermOverride>()
.Where(x => x.GuildId != null
@@ -218,7 +211,55 @@ public sealed class CleanupService : ICleanupService, IReadyExecutor, INService
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete sar
await ctx.GetTable<SarGroup>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete warnings
await ctx.GetTable<Warning>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete warn punishments
await ctx.GetTable<WarningPunishment>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete sticky roles
await ctx.GetTable<StickyRole>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete at channels
await ctx.GetTable<AutoTranslateChannel>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete ban templates
await ctx.GetTable<BanTemplate>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
// delete reminders
await ctx.GetTable<Reminder>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.ServerId))
.DeleteAsync();
// delete button roles
await ctx.GetTable<ButtonRole>()
.Where(x => !tempTable.Select(x => x.GuildId)
.Contains(x.GuildId))
.DeleteAsync();
return new()
{
GuildCount = guildIds.Keys.Count,

View File

@@ -42,9 +42,9 @@ public partial class Administration
.Page((items, _) =>
{
if (!items.Any())
return _sender.CreateEmbed().WithErrorColor().WithFooter(sql).WithDescription("-");
return CreateEmbed().WithErrorColor().WithFooter(sql).WithDescription("-");
return _sender.CreateEmbed()
return CreateEmbed()
.WithOkColor()
.WithFooter(sql)
.WithTitle(string.Join(" ║ ", result.ColumnNames))
@@ -99,7 +99,7 @@ public partial class Administration
{
try
{
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithTitle(GetText(strs.sql_confirm_exec))
.WithDescription(Format.Code(sql));
@@ -119,7 +119,7 @@ public partial class Administration
[OwnerOnly]
public async Task PurgeUser(ulong userId)
{
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithDescription(GetText(strs.purge_user_confirm(Format.Bold(userId.ToString()))));
if (!await PromptUserConfirmAsync(embed))

View File

@@ -123,7 +123,7 @@ public partial class Administration
[Cmd]
public async Task LanguagesList()
=> await Response().Embed(_sender.CreateEmbed()
=> await Response().Embed(CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.lang_list))
.WithDescription(string.Join("\n",

View File

@@ -122,7 +122,7 @@ public class MuteService : INService
return;
_ = Task.Run(() => _sender.Response(user)
.Embed(_sender.CreateEmbed()
.Embed(_sender.CreateEmbed(user?.GuildId)
.WithDescription($"You've been muted in {user.Guild} server")
.AddField("Mute Type", type.ToString())
.AddField("Moderator", mod.ToString())
@@ -140,7 +140,7 @@ public class MuteService : INService
return;
_ = Task.Run(() => _sender.Response(user)
.Embed(_sender.CreateEmbed()
.Embed(_sender.CreateEmbed(user.GuildId)
.WithDescription($"You've been unmuted in {user.Guild} server")
.AddField("Unmute Type", type.ToString())
.AddField("Moderator", mod.ToString())

View File

@@ -36,7 +36,7 @@ public partial class Administration
[UserPerm(GuildPerm.Administrator)]
public async Task DiscordPermOverrideReset()
{
var result = await PromptUserConfirmAsync(_sender.CreateEmbed()
var result = await PromptUserConfirmAsync(CreateEmbed()
.WithOkColor()
.WithDescription(GetText(strs.perm_override_all_confirm)));
@@ -65,7 +65,7 @@ public partial class Administration
.CurrentPage(page)
.Page((items, _) =>
{
var eb = _sender.CreateEmbed().WithTitle(GetText(strs.perm_overrides)).WithOkColor();
var eb = CreateEmbed().WithTitle(GetText(strs.perm_overrides)).WithOkColor();
if (items.Count == 0)
eb.WithDescription(GetText(strs.perm_override_page_none));

View File

@@ -9,7 +9,9 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
{
private readonly BotConfigService _bss;
private readonly SelfService _selfService;
private readonly IReplacementService _repService;
// private readonly Replacer _rep;
private readonly DbService _db;
private readonly DiscordSocketClient _client;
@@ -27,14 +29,13 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
_selfService = selfService;
_repService = repService;
_client = client;
}
public async Task OnReadyAsync()
{
if (_client.ShardId != 0)
return;
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
var index = 0;
while (await timer.WaitForNextTickAsync())
@@ -57,7 +58,7 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
? rotatingStatuses[index = 0]
: rotatingStatuses[index++];
var statusText = await _repService.ReplaceAsync(playingStatus.Status, new (client: _client));
var statusText = await _repService.ReplaceAsync(playingStatus.Status, new(client: _client));
await _selfService.SetActivityAsync(statusText, (ActivityType)playingStatus.Type);
}
catch (Exception ex)
@@ -72,7 +73,11 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
ArgumentOutOfRangeException.ThrowIfNegative(index);
await using var uow = _db.GetDbContext();
var toRemove = await uow.Set<RotatingPlayingStatus>().AsQueryable().AsNoTracking().Skip(index).FirstOrDefaultAsync();
var toRemove = await uow.Set<RotatingPlayingStatus>()
.AsQueryable()
.AsNoTracking()
.Skip(index)
.FirstOrDefaultAsync();
if (toRemove is null)
return null;
@@ -94,6 +99,11 @@ public sealed class PlayingRotateService : INService, IReadyExecutor
await uow.SaveChangesAsync();
}
public void DisableRotatePlaying()
{
_bss.ModifyConfig(bs => { bs.RotateStatuses = false; });
}
public bool ToggleRotatePlaying()
{
var enabled = false;

View File

@@ -241,7 +241,7 @@ public partial class Administration
return;
}
var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.prot_active));
var embed = CreateEmbed().WithOkColor().WithTitle(GetText(strs.prot_active));
if (spam is not null)
embed.AddField("Anti-Spam", GetAntiSpamString(spam).TrimTo(1024), true);

View File

@@ -113,7 +113,7 @@ public partial class Administration
{
await progressMsg.ModifyAsync(props =>
{
props.Embed = _sender.CreateEmbed()
props.Embed = CreateEmbed()
.WithPendingColor()
.WithDescription(GetText(strs.prune_progress(deleted, total)))
.Build();

View File

@@ -0,0 +1,302 @@
using NadekoBot.Common.TypeReaders.Models;
using NadekoBot.Db.Models;
using NadekoBot.Modules.Administration.Services;
using System.Text;
using ContextType = Discord.Commands.ContextType;
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
[Group("btr")]
public partial class ButtonRoleCommands : NadekoModule<ButtonRolesService>
{
private List<ActionRowBuilder> GetActionRows(IReadOnlyList<ButtonRole> roles)
{
var rows = roles.Select((x, i) => (Index: i, ButtonRole: x))
.GroupBy(x => x.Index / 5)
.Select(x => x.Select(y => y.ButtonRole))
.Select(x =>
{
var ab = new ActionRowBuilder()
.WithComponents(x.Select(y =>
{
var curRole = ctx.Guild.GetRole(y.RoleId);
var label = string.IsNullOrWhiteSpace(y.Label)
? curRole?.ToString() ?? "?missing " + y.RoleId
: y.Label;
var btnEmote = EmoteTypeReader.TryParse(y.Emote, out var e)
? e
: null;
return new ButtonBuilder()
.WithCustomId(y.ButtonId)
.WithEmote(btnEmote)
.WithLabel(label)
.WithStyle(ButtonStyle.Secondary)
.Build() as IMessageComponent;
})
.ToList());
return ab;
})
.ToList();
return rows;
}
private async Task<MessageLink?> CreateMessageLinkAsync(ulong messageId)
{
var msg = await ctx.Channel.GetMessageAsync(messageId);
if (msg is null)
return null;
return new MessageLink(ctx.Guild, ctx.Channel, msg);
}
[Cmd]
[RequireContext(ContextType.Guild)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireUserPermission(GuildPerm.ManageRoles)]
public async Task BtnRoleAdd(ulong messageId, IEmote emote, [Leftover] IRole role)
{
var link = await CreateMessageLinkAsync(messageId);
if (link is null)
{
await Response().Error(strs.invalid_message_id).SendAsync();
return;
}
await BtnRoleAdd(link, emote, role);
}
[Cmd]
[RequireContext(ContextType.Guild)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireUserPermission(GuildPerm.ManageRoles)]
public async Task BtnRoleAdd(MessageLink link, IEmote emote, [Leftover] IRole role)
{
if (link.Message is not IUserMessage msg || !msg.IsAuthor(ctx.Client))
{
await Response().Error(strs.invalid_message_link).SendAsync();
return;
}
if (!await CheckRoleHierarchy(role))
{
await Response().Error(strs.hierarchy).SendAsync();
return;
}
var success = await _service.AddButtonRole(ctx.Guild.Id, link.Channel.Id, role.Id, link.Message.Id, emote);
if (!success)
{
await Response().Error(strs.btnrole_message_max).SendAsync();
return;
}
var roles = await _service.GetButtonRoles(ctx.Guild.Id, link.Message.Id);
var rows = GetActionRows(roles);
await msg.ModifyAsync(x => x.Components = new(new ComponentBuilder().WithRows(rows).Build()));
await ctx.OkAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireUserPermission(GuildPerm.ManageRoles)]
public Task BtnRoleRemove(ulong messageId, IRole role)
=> BtnRoleRemove(messageId, role.Id);
[Cmd]
[RequireContext(ContextType.Guild)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireUserPermission(GuildPerm.ManageRoles)]
public Task BtnRoleRemove(MessageLink link, IRole role)
=> BtnRoleRemove(link.Message.Id, role.Id);
[Cmd]
[RequireContext(ContextType.Guild)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireUserPermission(GuildPerm.ManageRoles)]
public Task BtnRoleRemove(MessageLink link, ulong roleId)
=> BtnRoleRemove(link.Message.Id, roleId);
[Cmd]
[RequireContext(ContextType.Guild)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireUserPermission(GuildPerm.ManageRoles)]
public async Task BtnRoleRemove(ulong messageId, ulong roleId)
{
var removed = await _service.RemoveButtonRole(ctx.Guild.Id, messageId, roleId);
if (removed is null)
{
await Response().Error(strs.btnrole_not_found).SendAsync();
return;
}
var roles = await _service.GetButtonRoles(ctx.Guild.Id, messageId);
var ch = await ctx.Guild.GetTextChannelAsync(removed.ChannelId);
if (ch is null)
{
await Response().Error(strs.btnrole_removeall_not_found).SendAsync();
return;
}
var msg = await ch.GetMessageAsync(removed.MessageId) as IUserMessage;
if (msg is null)
{
await Response().Error(strs.btnrole_removeall_not_found).SendAsync();
return;
}
var rows = GetActionRows(roles);
await msg.ModifyAsync(x => x.Components = new(new ComponentBuilder().WithRows(rows).Build()));
await Response().Confirm(strs.btnrole_removed).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireUserPermission(GuildPerm.ManageRoles)]
public Task BtnRoleRemoveAll(MessageLink link)
=> BtnRoleRemoveAll(link.Message.Id);
[Cmd]
[RequireContext(ContextType.Guild)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireUserPermission(GuildPerm.ManageRoles)]
public async Task BtnRoleRemoveAll(ulong messageId)
{
var succ = await _service.RemoveButtonRoles(ctx.Guild.Id, messageId);
if (succ.Count == 0)
{
await Response().Error(strs.btnrole_not_found).SendAsync();
return;
}
var info = succ[0];
var ch = await ctx.Guild.GetTextChannelAsync(info.ChannelId);
if (ch is null)
{
await Response().Pending(strs.btnrole_removeall_not_found).SendAsync();
return;
}
var msg = await ch.GetMessageAsync(info.MessageId) as IUserMessage;
if (msg is null)
{
await Response().Pending(strs.btnrole_removeall_not_found).SendAsync();
return;
}
await msg.ModifyAsync(x => x.Components = new(new ComponentBuilder().Build()));
await ctx.OkAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireUserPermission(GuildPerm.ManageRoles)]
public async Task BtnRoleList()
{
var btnRoles = await _service.GetButtonRoles(ctx.Guild.Id, null);
var groups = btnRoles
.GroupBy(x => (x.ChannelId, x.MessageId))
.ToList();
await Response()
.Paginated()
.Items(groups)
.PageSize(1)
.AddFooter(false)
.Page(async (items, page) =>
{
var eb = CreateEmbed()
.WithOkColor();
var item = items.FirstOrDefault();
if (item == default)
{
eb.WithPendingColor()
.WithDescription(GetText(strs.btnrole_none));
return eb;
}
var (cid, msgId) = item.Key;
var str = new StringBuilder();
var ch = await ctx.Client.GetChannelAsync(cid) as IMessageChannel;
str.AppendLine($"Channel: {ch?.ToString() ?? cid.ToString()}");
str.AppendLine($"Message: {msgId}");
if (ch is not null)
{
var msg = await ch.GetMessageAsync(msgId);
if (msg is not null)
{
str.AppendLine(new MessageLink(ctx.Guild, ch, msg).ToString());
}
}
str.AppendLine("---");
foreach (var x in item.AsEnumerable())
{
var role = ctx.Guild.GetRole(x.RoleId);
str.AppendLine($"{x.Emote} {(role?.ToString() ?? x.RoleId.ToString())}");
}
eb.WithDescription(str.ToString());
return eb;
})
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireUserPermission(GuildPerm.ManageRoles)]
public Task BtnRoleExclusive(MessageLink link, PermissionAction exclusive)
=> BtnRoleExclusive(link.Message.Id, exclusive);
[Cmd]
[RequireContext(ContextType.Guild)]
[BotPerm(GuildPerm.ManageRoles)]
[RequireUserPermission(GuildPerm.ManageRoles)]
public async Task BtnRoleExclusive(ulong messageId, PermissionAction exclusive)
{
var res = await _service.SetExclusiveButtonRoles(ctx.Guild.Id, messageId, exclusive.Value);
if (!res)
{
await Response().Error(strs.btnrole_not_found).SendAsync();
return;
}
if (exclusive.Value)
{
await Response().Confirm(strs.btnrole_exclusive).SendAsync();
}
else
{
await Response().Confirm(strs.btnrole_multiple).SendAsync();
}
}
}
}

View File

@@ -0,0 +1,187 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using LinqToDB.SqlQuery;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db.Models;
using NCalc;
namespace NadekoBot.Modules.Administration.Services;
public sealed class ButtonRolesService : INService, IReadyExecutor
{
private const string BTN_PREFIX = "n:btnrole:";
private readonly DbService _db;
private readonly DiscordSocketClient _client;
private readonly IBotCreds _creds;
public ButtonRolesService(IBotCreds creds, DiscordSocketClient client, DbService db)
{
_creds = creds;
_client = client;
_db = db;
}
public Task OnReadyAsync()
{
_client.InteractionCreated += OnInteraction;
return Task.CompletedTask;
}
private async Task OnInteraction(SocketInteraction inter)
{
if (inter is not SocketMessageComponent smc)
return;
if (!smc.Data.CustomId.StartsWith(BTN_PREFIX))
return;
await inter.DeferAsync();
_ = Task.Run(async () =>
{
try
{
await using var uow = _db.GetDbContext();
var buttonRole = await uow.GetTable<ButtonRole>()
.Where(x => x.ButtonId == smc.Data.CustomId && x.MessageId == smc.Message.Id)
.FirstOrDefaultAsyncLinqToDB();
if (buttonRole is null)
return;
var guild = _client.GetGuild(buttonRole.GuildId);
if (guild is null)
return;
var role = guild.GetRole(buttonRole.RoleId);
if (role is null)
return;
if (smc.User is not IGuildUser user)
return;
if (user.GetRoles().Any(x => x.Id == role.Id))
{
await user.RemoveRoleAsync(role.Id);
return;
}
if (buttonRole.Exclusive)
{
var otherRoles = await uow.GetTable<ButtonRole>()
.Where(x => x.GuildId == smc.GuildId && x.MessageId == smc.Message.Id)
.Select(x => x.RoleId)
.ToListAsyncLinqToDB();
await user.RemoveRolesAsync(otherRoles);
}
await user.AddRoleAsync(role.Id);
}
catch (Exception ex)
{
Log.Warning(ex, "Unable to handle button role interaction for user {UserId}", inter.User.Id);
}
});
}
public async Task<bool> AddButtonRole(
ulong guildId,
ulong channelId,
ulong roleId,
ulong messageId,
IEmote emote
)
{
await using var uow = _db.GetDbContext();
// up to 25 per message
if (await uow.GetTable<ButtonRole>()
.Where(x => x.MessageId == messageId)
.CountAsyncLinqToDB()
>= 25)
return false;
var emoteStr = emote.ToString()!;
var guid = Guid.NewGuid();
await uow.GetTable<ButtonRole>()
.InsertOrUpdateAsync(() => new ButtonRole()
{
GuildId = guildId,
ChannelId = channelId,
RoleId = roleId,
MessageId = messageId,
Position =
uow
.GetTable<ButtonRole>()
.Any(x => x.MessageId == messageId)
? uow.GetTable<ButtonRole>()
.Where(x => x.MessageId == messageId)
.Max(x => x.Position)
: 1,
Emote = emoteStr,
Label = string.Empty,
ButtonId = $"{BTN_PREFIX}:{guildId}:{guid}",
Exclusive = uow.GetTable<ButtonRole>()
.Any(x => x.GuildId == guildId && x.MessageId == messageId)
&& uow.GetTable<ButtonRole>()
.Where(x => x.GuildId == guildId && x.MessageId == messageId)
.All(x => x.Exclusive)
},
_ => new()
{
Emote = emoteStr,
Label = string.Empty,
ButtonId = $"{BTN_PREFIX}:{guildId}:{guid}"
},
() => new()
{
RoleId = roleId,
MessageId = messageId,
});
return true;
}
public async Task<IReadOnlyList<ButtonRole>> RemoveButtonRoles(ulong guildId, ulong messageId)
{
await using var uow = _db.GetDbContext();
return await uow.GetTable<ButtonRole>()
.Where(x => x.GuildId == guildId && x.MessageId == messageId)
.DeleteWithOutputAsync();
}
public async Task<ButtonRole?> RemoveButtonRole(ulong guildId, ulong messageId, ulong roleId)
{
await using var uow = _db.GetDbContext();
var deleted = await uow.GetTable<ButtonRole>()
.Where(x => x.GuildId == guildId && x.MessageId == messageId && x.RoleId == roleId)
.DeleteWithOutputAsync();
return deleted.FirstOrDefault();
}
public async Task<IReadOnlyList<ButtonRole>> GetButtonRoles(ulong guildId, ulong? messageId)
{
await using var uow = _db.GetDbContext();
return await uow.GetTable<ButtonRole>()
.Where(x => x.GuildId == guildId && (messageId == null || x.MessageId == messageId))
.OrderBy(x => x.Id)
.ToListAsyncLinqToDB();
}
public async Task<bool> SetExclusiveButtonRoles(ulong guildId, ulong messageId, bool exclusive)
{
await using var uow = _db.GetDbContext();
return await uow.GetTable<ButtonRole>()
.Where(x => x.GuildId == guildId && x.MessageId == messageId)
.UpdateAsync((_) => new()
{
Exclusive = exclusive
}) > 0;
}
}

View File

@@ -80,7 +80,7 @@ public partial class Administration
.CurrentPage(page)
.Page((items, _) =>
{
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithOkColor();
var content = string.Empty;

View File

@@ -1,8 +1,6 @@
#nullable disable
using LinqToDB;
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Modules.Patronage;
using NadekoBot.Db.Models;
using OneOf.Types;
using OneOf;
@@ -18,18 +16,15 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
private ConcurrentDictionary<ulong, List<ReactionRoleV2>> _cache;
private readonly object _cacheLock = new();
private readonly SemaphoreSlim _assignementLock = new(1, 1);
private readonly IPatronageService _ps;
public ReactionRolesService(
DiscordSocketClient client,
IPatronageService ps,
DbService db,
IBotCreds creds)
{
_db = db;
_client = client;
_creds = creds;
_ps = ps;
_cache = new();
}
@@ -57,7 +52,7 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
var guild = _client.GetGuild(rero.GuildId);
var role = guild?.GetRole(rero.RoleId);
if (role is null)
if (guild is null || role is null)
return default;
var user = guild.GetUser(userId) as IGuildUser
@@ -80,8 +75,8 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
_ = Task.Run(async () =>
{
var emote = await GetFixedEmoteAsync(cmsg, r.Emote);
var rero = reros.FirstOrDefault(x => x.Emote == emote.Name
var rero = reros.FirstOrDefault(x => x.Emote == emote.Name
|| x.Emote == emote.ToString());
if (rero is null)
return;
@@ -96,10 +91,11 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
{
if (user.RoleIds.Contains(role.Id))
{
await user.RemoveRoleAsync(role.Id, new RequestOptions()
{
AuditLogReason = $"Reaction role"
});
await user.RemoveRoleAsync(role.Id,
new RequestOptions()
{
AuditLogReason = $"Reaction role"
});
}
}
finally
@@ -111,7 +107,7 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
return Task.CompletedTask;
}
// had to add this because for some reason, reactionremoved event's reaction doesn't have IsAnimated set,
// causing the .ToString() to be wrong on animated custom emotes
private async Task<IEmote> GetFixedEmoteAsync(
@@ -210,10 +206,11 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
}
}
await user.AddRoleAsync(role.Id, new()
{
AuditLogReason = "Reaction role"
});
await user.AddRoleAsync(role.Id,
new()
{
AuditLogReason = "Reaction role"
});
}
}
finally
@@ -244,23 +241,10 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
int levelReq = 0)
{
ArgumentOutOfRangeException.ThrowIfNegative(group);
ArgumentOutOfRangeException.ThrowIfNegative(levelReq);
await using var ctx = _db.GetDbContext();
await using var tran = await ctx.Database.BeginTransactionAsync();
var activeReactionRoles = await ctx.GetTable<ReactionRoleV2>()
.Where(x => x.GuildId == guild.Id)
.CountAsync();
var limit = await _ps.GetUserLimit(LimitedFeatureName.ReactionRole, guild.OwnerId);
if (!_creds.IsOwner(guild.OwnerId) && (activeReactionRoles >= limit.Quota && limit.Quota >= 0))
{
return new Error();
}
await ctx.GetTable<ReactionRoleV2>()
.InsertOrUpdateAsync(() => new()
{
@@ -286,8 +270,6 @@ public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionR
Emote = emote,
});
await tran.CommitAsync();
var obj = new ReactionRoleV2()
{
GuildId = guild.Id,

View File

@@ -174,12 +174,11 @@ public partial class Administration
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task RoleColor(Color color, [Leftover] IRole role)
public async Task RoleColor(Rgba32 color, [Leftover] IRole role)
{
try
{
var rgba32 = color.ToPixel<Rgba32>();
await role.ModifyAsync(r => r.Color = new Discord.Color(rgba32.R, rgba32.G, rgba32.B));
await role.ModifyAsync(r => r.Color = new Discord.Color(color.R, color.G, color.B));
await Response().Confirm(strs.rc(Format.Bold(role.Name))).SendAsync();
}
catch (Exception)

View File

@@ -62,7 +62,7 @@ public partial class Administration
var (added, updated) = await _service.RefreshUsersAsync(users);
await message.ModifyAsync(x =>
x.Embed = _sender.CreateEmbed()
x.Embed = CreateEmbed()
.WithDescription(GetText(strs.cache_users_done(added, updated)))
.WithOkColor()
.Build()
@@ -115,7 +115,7 @@ public partial class Administration
_service.AddNewAutoCommand(cmd);
await Response()
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.scadd))
.AddField(GetText(strs.server),
@@ -343,7 +343,7 @@ public partial class Administration
if (string.IsNullOrWhiteSpace(str))
str = GetText(strs.no_shards_on_page);
return _sender.CreateEmbed().WithOkColor().WithDescription($"{status}\n\n{str}");
return CreateEmbed().WithOkColor().WithDescription($"{status}\n\n{str}");
})
.SendAsync();
}

View File

@@ -71,7 +71,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
if (server.OwnerId != _client.CurrentUser.Id)
{
await server.LeaveAsync();
Log.Information("Left server {Name} [{Id}]", server.Name, server.Id);
}
else
{
@@ -453,7 +452,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
{
x.UserId,
x.Username,
x.Discriminator
})
.Where(x => users.Select(y => y.Id).Contains(x.UserId))
.ToArrayAsyncEF();
@@ -465,12 +463,11 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
UserId = x.Id,
AvatarId = x.AvatarId,
Username = x.Username,
Discriminator = x.Discriminator
});
var added = (await ctx.BulkCopyAsync(usersToAdd)).RowsCopied;
var toUpdateUserIds = presentDbUsers
.Where(x => x.Username == "Unknown" && x.Discriminator == "????")
.Where(x => x.Username.StartsWith("??"))
.Select(x => x.UserId)
.ToArray();
@@ -481,7 +478,6 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
.UpdateAsync(x => new DiscordUser()
{
Username = user.Username,
Discriminator = user.Discriminator,
// .award tends to set AvatarId and DateAdded to NULL, so account for that.
AvatarId = user.AvatarId,

View File

@@ -6,16 +6,136 @@ namespace NadekoBot.Modules.Administration;
public partial class Administration
{
[Group]
public partial class SelfAssignedRolesHelpers : NadekoModule<SelfAssignedRolesService>
{
private readonly SarAssignerService _sas;
public SelfAssignedRolesHelpers(SarAssignerService sas)
{
_sas = sas;
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Iam([Leftover] IRole role)
{
var guildUser = (IGuildUser)ctx.User;
var group = await _service.GetRoleGroup(ctx.Guild.Id, role.Id);
IUserMessage msg = null;
try
{
if (group is null)
{
msg = await Response().Error(strs.self_assign_not).SendAsync();
return;
}
var tcs = new TaskCompletionSource<SarAssignResult>(TaskCreationOptions.RunContinuationsAsynchronously);
await _sas.Add(new()
{
Group = group,
RoleId = role.Id,
User = guildUser,
CompletionTask = tcs
});
var res = await tcs.Task;
if (res.TryPickT0(out _, out var error))
{
msg = await Response()
.Confirm(strs.self_assign_success(Format.Bold(role.Name)))
.SendAsync();
}
else
{
var resStr = error.Match(
_ => strs.error_occured,
lvlReq => strs.self_assign_not_level(Format.Bold(lvlReq.Level.ToString())),
roleRq => strs.self_assign_role_req(Format.Bold(ctx.Guild.GetRole(roleRq.RoleId).ToString()
?? "missing role " + roleRq.RoleId),
group.Name),
_ => strs.self_assign_already(Format.Bold(role.Name)),
_ => strs.self_assign_perms);
msg = await Response().Error(resStr).SendAsync();
}
}
finally
{
var ad = _service.GetAutoDelete(ctx.Guild.Id);
if (ad)
{
msg?.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
}
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Iamnot([Leftover] IRole role)
{
var guildUser = (IGuildUser)ctx.User;
IUserMessage msg = null;
try
{
if (!guildUser.RoleIds.Contains(role.Id))
{
msg = await Response().Error(strs.self_assign_not_have(Format.Bold(role.Name))).SendAsync();
return;
}
var group = await _service.GetRoleGroup(ctx.Guild.Id, role.Id);
if (group is null || group.Roles.All(x => x.RoleId != role.Id))
{
msg = await Response().Error(strs.self_assign_not).SendAsync();
return;
}
if (role.Position >= ((SocketGuild)ctx.Guild).CurrentUser.Roles.Max(x => x.Position))
{
msg = await Response().Error(strs.self_assign_perms).SendAsync();
return;
}
await guildUser.RemoveRoleAsync(role);
msg = await Response().Confirm(strs.self_assign_remove(Format.Bold(role.Name))).SendAsync();
}
finally
{
var ad = _service.GetAutoDelete(ctx.Guild.Id);
if (ad)
{
msg?.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
}
}
}
}
[Group("sar")]
public partial class SelfAssignedRolesCommands : NadekoModule<SelfAssignedRolesService>
{
private readonly SarAssignerService _sas;
public SelfAssignedRolesCommands(SarAssignerService sas)
{
_sas = sas;
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
[BotPerm(GuildPerm.ManageMessages)]
public async Task AdSarm()
public async Task SarAutoDelete()
{
var newVal = _service.ToggleAdSarm(ctx.Guild.Id);
var newVal = await _service.ToggleAutoDelete(ctx.Guild.Id);
if (newVal)
await Response().Confirm(strs.adsarm_enable(prefix)).SendAsync();
@@ -28,30 +148,24 @@ public partial class Administration
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(1)]
public Task Asar([Leftover] IRole role)
=> Asar(0, role);
public Task SarAdd([Leftover] IRole role)
=> SarAdd(0, role);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task Asar(int group, [Leftover] IRole role)
public async Task SarAdd(int group, [Leftover] IRole role)
{
var guser = (IGuildUser)ctx.User;
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
if (!await CheckRoleHierarchy(role))
return;
var succ = _service.AddNew(ctx.Guild.Id, role, group);
await _service.AddAsync(ctx.Guild.Id, role.Id, group);
if (succ)
{
await Response()
.Confirm(strs.role_added(Format.Bold(role.Name), Format.Bold(group.ToString())))
.SendAsync();
}
else
await Response().Error(strs.role_in_list(Format.Bold(role.Name))).SendAsync();
await Response()
.Confirm(strs.role_added(Format.Bold(role.Name), Format.Bold(group.ToString())))
.SendAsync();
}
[Cmd]
@@ -59,9 +173,9 @@ public partial class Administration
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
[Priority(0)]
public async Task Sargn(int group, [Leftover] string name = null)
public async Task SarGroupName(int group, [Leftover] string name = null)
{
var set = await _service.SetNameAsync(ctx.Guild.Id, group, name);
var set = await _service.SetGroupNameAsync(ctx.Guild.Id, group, name);
if (set)
{
@@ -70,19 +184,19 @@ public partial class Administration
.SendAsync();
}
else
{
await Response().Confirm(strs.group_name_removed(Format.Bold(group.ToString()))).SendAsync();
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
public async Task Rsar([Leftover] IRole role)
public async Task SarRemove([Leftover] IRole role)
{
var guser = (IGuildUser)ctx.User;
if (ctx.User.Id != guser.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= role.Position)
return;
var success = _service.RemoveSar(role.Guild.Id, role.Id);
var success = await _service.RemoveAsync(role.Guild.Id, role.Id);
if (!success)
await Response().Error(strs.self_assign_not).SendAsync();
else
@@ -91,59 +205,81 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Lsar(int page = 1)
public async Task SarList(int page = 1)
{
if (--page < 0)
return;
var (exclusive, roles, groups) = _service.GetRoles(ctx.Guild);
var groups = await _service.GetSarsAsync(ctx.Guild.Id);
var gDict = groups.ToDictionary(x => x.Id, x => x);
await Response()
.Paginated()
.Items(roles.OrderBy(x => x.Model.Group).ToList())
.Items(groups.SelectMany(x => x.Roles).ToList())
.PageSize(20)
.CurrentPage(page)
.Page((items, _) =>
.Page(async (items, _) =>
{
var rolesStr = new StringBuilder();
var roleGroups = items
.GroupBy(x => x.Model.Group)
.GroupBy(x => x.SarGroupId)
.OrderBy(x => x.Key);
var eb = CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.self_assign_list(groups.Sum(x => x.Roles.Count))));
foreach (var kvp in roleGroups)
{
string groupNameText;
if (!groups.TryGetValue(kvp.Key, out var name))
groupNameText = Format.Bold(GetText(strs.self_assign_group(kvp.Key)));
else
groupNameText = Format.Bold($"{kvp.Key} - {name.TrimTo(25, true)}");
var group = gDict[kvp.Key];
rolesStr.AppendLine("\t\t\t\t ⟪" + groupNameText + "⟫");
foreach (var (model, role) in kvp.AsEnumerable())
var groupNameText = "";
if (!string.IsNullOrWhiteSpace(group.Name))
groupNameText += $" **{group.Name}**";
groupNameText = $"`{group.GroupNumber}` {groupNameText}";
var rolesStr = new StringBuilder();
if (group.IsExclusive)
{
if (role is null)
rolesStr.AppendLine(Format.Italics(GetText(strs.choose_one)));
}
if (group.RoleReq is ulong rrId)
{
var rr = ctx.Guild.GetRole(rrId);
if (rr is null)
{
await _service.SetGroupRoleReq(group.GuildId, group.GroupNumber, null);
}
else
{
// first character is invisible space
if (model.LevelRequirement == 0)
rolesStr.AppendLine(" " + role.Name);
else
rolesStr.AppendLine(" " + role.Name + $" (lvl {model.LevelRequirement}+)");
rolesStr.AppendLine(
Format.Italics(GetText(strs.requires_role(Format.Bold(rr.Name)))));
}
}
rolesStr.AppendLine();
foreach (var sar in kvp)
{
var roleName = (ctx.Guild.GetRole(sar.RoleId)?.Name ?? (sar.RoleId + " (deleted)"));
rolesStr.Append("- " + Format.Code(roleName));
if (sar.LevelReq > 0)
{
rolesStr.Append($" *[lvl {sar.LevelReq}+]*");
}
rolesStr.AppendLine();
}
eb.AddField(groupNameText, rolesStr, false);
}
return _sender.CreateEmbed()
.WithOkColor()
.WithTitle(Format.Bold(GetText(strs.self_assign_list(roles.Count()))))
.WithDescription(rolesStr.ToString())
.WithFooter(exclusive
? GetText(strs.self_assign_are_exclusive)
: GetText(strs.self_assign_are_not_exclusive));
return eb;
})
.SendAsync();
}
@@ -152,25 +288,36 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task Togglexclsar()
public async Task SarExclusive(int groupNumber)
{
var areExclusive = _service.ToggleEsar(ctx.Guild.Id);
if (areExclusive)
var areExclusive = await _service.SetGroupExclusivityAsync(ctx.Guild.Id, groupNumber);
if (areExclusive is null)
{
await Response().Error(strs.sar_group_not_found).SendAsync();
return;
}
if (areExclusive is true)
{
await Response().Confirm(strs.self_assign_excl).SendAsync();
}
else
{
await Response().Confirm(strs.self_assign_no_excl).SendAsync();
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task RoleLevelReq(int level, [Leftover] IRole role)
public async Task SarRoleLevelReq(int level, [Leftover] IRole role)
{
if (level < 0)
return;
var succ = _service.SetLevelReq(ctx.Guild.Id, role, level);
var succ = await _service.SetRoleLevelReq(ctx.Guild.Id, role.Id, level);
if (!succ)
{
@@ -186,54 +333,35 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Iam([Leftover] IRole role)
[UserPerm(GuildPerm.ManageRoles)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task SarGroupRoleReq(int groupNumber, [Leftover] IRole role)
{
var guildUser = (IGuildUser)ctx.User;
var succ = await _service.SetGroupRoleReq(ctx.Guild.Id, groupNumber, role.Id);
var (result, autoDelete, extra) = await _service.Assign(guildUser, role);
IUserMessage msg;
if (result == SelfAssignedRolesService.AssignResult.ErrNotAssignable)
msg = await Response().Error(strs.self_assign_not).SendAsync();
else if (result == SelfAssignedRolesService.AssignResult.ErrLvlReq)
msg = await Response().Error(strs.self_assign_not_level(Format.Bold(extra.ToString()))).SendAsync();
else if (result == SelfAssignedRolesService.AssignResult.ErrAlreadyHave)
msg = await Response().Error(strs.self_assign_already(Format.Bold(role.Name))).SendAsync();
else if (result == SelfAssignedRolesService.AssignResult.ErrNotPerms)
msg = await Response().Error(strs.self_assign_perms).SendAsync();
else
msg = await Response().Confirm(strs.self_assign_success(Format.Bold(role.Name))).SendAsync();
if (autoDelete)
if (!succ)
{
msg.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
await Response().Error(strs.sar_group_not_found).SendAsync();
return;
}
await Response()
.Confirm(strs.self_assign_group_role_req(
Format.Bold(groupNumber.ToString()),
Format.Bold(role.Name)))
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Iamnot([Leftover] IRole role)
[UserPerm(GuildPerm.ManageRoles)]
public async Task SarGroupDelete(int groupNumber)
{
var guildUser = (IGuildUser)ctx.User;
var (result, autoDelete) = await _service.Remove(guildUser, role);
IUserMessage msg;
if (result == SelfAssignedRolesService.RemoveResult.ErrNotAssignable)
msg = await Response().Error(strs.self_assign_not).SendAsync();
else if (result == SelfAssignedRolesService.RemoveResult.ErrNotHave)
msg = await Response().Error(strs.self_assign_not_have(Format.Bold(role.Name))).SendAsync();
else if (result == SelfAssignedRolesService.RemoveResult.ErrNotPerms)
msg = await Response().Error(strs.self_assign_perms).SendAsync();
var succ = await _service.DeleteRoleGroup(ctx.Guild.Id, groupNumber);
if (succ)
await Response().Confirm(strs.sar_group_deleted(Format.Bold(groupNumber.ToString()))).SendAsync();
else
msg = await Response().Confirm(strs.self_assign_remove(Format.Bold(role.Name))).SendAsync();
if (autoDelete)
{
msg.DeleteAfter(3);
ctx.Message.DeleteAfter(3);
}
await Response().Error(strs.sar_group_not_found).SendAsync();
}
}
}

View File

@@ -1,233 +1,326 @@
#nullable disable
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db.Models;
using NadekoBot.Modules.Xp.Services;
using OneOf;
using OneOf.Types;
using System.ComponentModel.DataAnnotations;
using System.Threading.Channels;
namespace NadekoBot.Modules.Administration.Services;
public class SelfAssignedRolesService : INService
public class SelfAssignedRolesService : INService, IReadyExecutor
{
public enum AssignResult
private readonly DbService _db;
private readonly DiscordSocketClient _client;
private readonly IBotCreds _creds;
private ConcurrentHashSet<ulong> _sarAds = new();
public SelfAssignedRolesService(DbService db, DiscordSocketClient client, IBotCreds creds)
{
Assigned, // successfully removed
ErrNotAssignable, // not assignable (error)
ErrAlreadyHave, // you already have that role (error)
ErrNotPerms, // bot doesn't have perms (error)
ErrLvlReq // you are not required level (error)
_db = db;
_client = client;
_creds = creds;
}
public enum RemoveResult
public async Task AddAsync(ulong guildId, ulong roleId, int groupNumber)
{
Removed, // successfully removed
ErrNotAssignable, // not assignable (error)
ErrNotHave, // you don't have a role you want to remove (error)
ErrNotPerms // bot doesn't have perms (error)
await using var ctx = _db.GetDbContext();
await ctx.GetTable<SarGroup>()
.InsertOrUpdateAsync(() => new()
{
GuildId = guildId,
GroupNumber = groupNumber,
IsExclusive = false
},
_ => new()
{
},
() => new()
{
GuildId = guildId,
GroupNumber = groupNumber
});
await ctx.GetTable<Sar>()
.InsertOrUpdateAsync(() => new()
{
RoleId = roleId,
LevelReq = 0,
GuildId = guildId,
SarGroupId = ctx.GetTable<SarGroup>()
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
.Select(x => x.Id)
.First()
},
_ => new()
{
},
() => new()
{
RoleId = roleId,
});
}
public async Task<bool> RemoveAsync(ulong guildId, ulong roleId)
{
await using var ctx = _db.GetDbContext();
var deleted = await ctx.GetTable<Sar>()
.Where(x => x.RoleId == roleId && x.GuildId == guildId)
.DeleteAsync();
return deleted > 0;
}
public async Task<bool> SetGroupNameAsync(ulong guildId, int groupNumber, string? name)
{
await using var ctx = _db.GetDbContext();
var changes = await ctx.GetTable<SarGroup>()
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
.UpdateAsync(x => new()
{
Name = name
});
return changes > 0;
}
public async Task<IReadOnlyCollection<SarGroup>> GetSarsAsync(ulong guildId)
{
await using var ctx = _db.GetDbContext();
var sgs = await ctx.GetTable<SarGroup>()
.Where(x => x.GuildId == guildId)
.LoadWith(x => x.Roles)
.ToListAsyncLinqToDB();
return sgs;
}
public async Task<bool> SetRoleLevelReq(ulong guildId, ulong roleId, int levelReq)
{
await using var ctx = _db.GetDbContext();
var changes = await ctx.GetTable<Sar>()
.Where(x => x.GuildId == guildId && x.RoleId == roleId)
.UpdateAsync(_ => new()
{
LevelReq = levelReq,
});
return changes > 0;
}
public async Task<bool> SetGroupRoleReq(ulong guildId, int groupNumber, ulong? roleId)
{
await using var ctx = _db.GetDbContext();
var changes = await ctx.GetTable<SarGroup>()
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
.UpdateAsync(_ => new()
{
RoleReq = roleId
});
return changes > 0;
}
public async Task<bool?> SetGroupExclusivityAsync(ulong guildId, int groupNumber)
{
await using var ctx = _db.GetDbContext();
var changes = await ctx.GetTable<SarGroup>()
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
.UpdateWithOutputAsync(old => new()
{
IsExclusive = !old.IsExclusive
},
(o, n) => n.IsExclusive);
if (changes.Length == 0)
{
return null;
}
return changes[0];
}
public async Task<SarGroup?> GetRoleGroup(ulong guildId, ulong roleId)
{
await using var ctx = _db.GetDbContext();
var group = await ctx.GetTable<SarGroup>()
.Where(x => x.GuildId == guildId && x.Roles.Any(x => x.RoleId == roleId))
.LoadWith(x => x.Roles)
.FirstOrDefaultAsyncLinqToDB();
return group;
}
public async Task<bool> DeleteRoleGroup(ulong guildId, int groupNumber)
{
await using var ctx = _db.GetDbContext();
var deleted = await ctx.GetTable<SarGroup>()
.Where(x => x.GuildId == guildId && x.GroupNumber == groupNumber)
.DeleteAsync();
return deleted > 0;
}
public async Task<bool> ToggleAutoDelete(ulong guildId)
{
await using var ctx = _db.GetDbContext();
var delted = await ctx.GetTable<SarAutoDelete>()
.DeleteAsync(x => x.GuildId == guildId);
if (delted > 0)
{
_sarAds.TryRemove(guildId);
return false;
}
await ctx.GetTable<SarAutoDelete>()
.InsertOrUpdateAsync(() => new()
{
IsEnabled = true,
GuildId = guildId,
},
(_) => new()
{
IsEnabled = true
},
() => new()
{
GuildId = guildId
});
_sarAds.Add(guildId);
return true;
}
public bool GetAutoDelete(ulong guildId)
=> _sarAds.Contains(guildId);
public async Task OnReadyAsync()
{
await using var uow = _db.GetDbContext();
var guilds = await uow.GetTable<SarAutoDelete>()
.Where(x => x.IsEnabled && Linq2DbExpressions.GuildOnShard(x.GuildId, _creds.TotalShards, _client.ShardId))
.Select(x => x.GuildId)
.ToListAsyncLinqToDB();
_sarAds = new(guilds);
}
}
public sealed class SarAssignerService : INService, IReadyExecutor
{
private readonly XpService _xp;
private readonly DbService _db;
public SelfAssignedRolesService(DbService db)
=> _db = db;
private readonly Channel<SarAssignerDataItem> _channel =
Channel.CreateBounded<SarAssignerDataItem>(100);
public bool AddNew(ulong guildId, IRole role, int group)
public SarAssignerService(XpService xp, DbService db)
{
using var uow = _db.GetDbContext();
var roles = uow.Set<SelfAssignedRole>().GetFromGuild(guildId);
if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.Guild.Id))
return false;
uow.Set<SelfAssignedRole>().Add(new()
{
Group = group,
RoleId = role.Id,
GuildId = role.Guild.Id
});
uow.SaveChanges();
return true;
_xp = xp;
_db = db;
}
public bool ToggleAdSarm(ulong guildId)
public async Task OnReadyAsync()
{
bool newval;
using var uow = _db.GetDbContext();
var config = uow.GuildConfigsForId(guildId, set => set);
newval = config.AutoDeleteSelfAssignedRoleMessages = !config.AutoDeleteSelfAssignedRoleMessages;
uow.SaveChanges();
return newval;
}
public async Task<(AssignResult Result, bool AutoDelete, object extra)> Assign(IGuildUser guildUser, IRole role)
{
LevelStats userLevelData;
await using (var uow = _db.GetDbContext())
var reader = _channel.Reader;
while (true)
{
var stats = uow.GetOrCreateUserXpStats(guildUser.Guild.Id, guildUser.Id);
userLevelData = new(stats.Xp + stats.AwardedXp);
}
var item = await reader.ReadAsync();
var (autoDelete, exclusive, roles) = GetAdAndRoles(guildUser.Guild.Id);
var theRoleYouWant = roles.FirstOrDefault(r => r.RoleId == role.Id);
if (theRoleYouWant is null)
return (AssignResult.ErrNotAssignable, autoDelete, null);
if (theRoleYouWant.LevelRequirement > userLevelData.Level)
return (AssignResult.ErrLvlReq, autoDelete, theRoleYouWant.LevelRequirement);
if (guildUser.RoleIds.Contains(role.Id))
return (AssignResult.ErrAlreadyHave, autoDelete, null);
var roleIds = roles.Where(x => x.Group == theRoleYouWant.Group).Select(x => x.RoleId).ToArray();
if (exclusive)
{
var sameRoles = guildUser.RoleIds.Where(r => roleIds.Contains(r));
foreach (var roleId in sameRoles)
try
{
var sameRole = guildUser.Guild.GetRole(roleId);
if (sameRole is not null)
var sar = item.Group.Roles.First(x => x.RoleId == item.RoleId);
if (item.User.RoleIds.Contains(item.RoleId))
{
try
{
await guildUser.RemoveRoleAsync(sameRole);
await Task.Delay(300);
}
catch
{
// ignored
}
item.CompletionTask.TrySetResult(new SarAlreadyHasRole());
continue;
}
if (item.Group.RoleReq is { } rid)
{
if (!item.User.RoleIds.Contains(rid))
{
item.CompletionTask.TrySetResult(new SarRoleRequirement(rid));
continue;
}
// passed
}
// check level requirement
if (sar.LevelReq > 0)
{
await using var ctx = _db.GetDbContext();
var xpStats = await ctx.GetTable<UserXpStats>().GetGuildUserXp(sar.GuildId, item.User.Id);
var lvlData = new LevelStats(xpStats?.Xp ?? 0);
if (lvlData.Level < sar.LevelReq)
{
item.CompletionTask.TrySetResult(new SarLevelRequirement(sar.LevelReq));
continue;
}
// passed
}
if (item.Group.IsExclusive)
{
var rolesToRemove = item.Group.Roles.Select(x => x.RoleId);
await item.User.RemoveRolesAsync(rolesToRemove);
}
await item.User.AddRoleAsync(item.RoleId);
item.CompletionTask.TrySetResult(new Success());
}
catch (Exception ex)
{
Log.Error(ex, "Unknown error ocurred in SAR runner: {Error}", ex.Message);
item.CompletionTask.TrySetResult(new Error());
}
}
try
{
await guildUser.AddRoleAsync(role);
}
catch (Exception ex)
{
return (AssignResult.ErrNotPerms, autoDelete, ex);
}
return (AssignResult.Assigned, autoDelete, null);
}
public async Task<bool> SetNameAsync(ulong guildId, int group, string name)
public async Task Add(SarAssignerDataItem item)
{
var set = false;
await using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, y => y.Include(x => x.SelfAssignableRoleGroupNames));
var toUpdate = gc.SelfAssignableRoleGroupNames.FirstOrDefault(x => x.Number == group);
if (string.IsNullOrWhiteSpace(name))
{
if (toUpdate is not null)
gc.SelfAssignableRoleGroupNames.Remove(toUpdate);
}
else if (toUpdate is null)
{
gc.SelfAssignableRoleGroupNames.Add(new()
{
Name = name,
Number = group
});
set = true;
}
else
{
toUpdate.Name = name;
set = true;
}
await uow.SaveChangesAsync();
return set;
await _channel.Writer.WriteAsync(item);
}
public async Task<(RemoveResult Result, bool AutoDelete)> Remove(IGuildUser guildUser, IRole role)
{
var (autoDelete, _, roles) = GetAdAndRoles(guildUser.Guild.Id);
}
if (roles.FirstOrDefault(r => r.RoleId == role.Id) is null)
return (RemoveResult.ErrNotAssignable, autoDelete);
if (!guildUser.RoleIds.Contains(role.Id))
return (RemoveResult.ErrNotHave, autoDelete);
try
{
await guildUser.RemoveRoleAsync(role);
}
catch (Exception)
{
return (RemoveResult.ErrNotPerms, autoDelete);
}
public sealed class SarAssignerDataItem
{
public required SarGroup Group { get; init; }
public required IGuildUser User { get; init; }
public required ulong RoleId { get; init; }
public required TaskCompletionSource<SarAssignResult> CompletionTask { get; init; }
}
return (RemoveResult.Removed, autoDelete);
}
[GenerateOneOf]
public sealed partial class SarAssignResult
: OneOfBase<Success, Error, SarLevelRequirement, SarRoleRequirement, SarAlreadyHasRole, SarInsuffPerms>
{
}
public bool RemoveSar(ulong guildId, ulong roleId)
{
bool success;
using var uow = _db.GetDbContext();
success = uow.Set<SelfAssignedRole>().DeleteByGuildAndRoleId(guildId, roleId);
uow.SaveChanges();
return success;
}
public record class SarLevelRequirement(int Level);
public (bool AutoDelete, bool Exclusive, IReadOnlyCollection<SelfAssignedRole>) GetAdAndRoles(ulong guildId)
{
using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, set => set);
var autoDelete = gc.AutoDeleteSelfAssignedRoleMessages;
var exclusive = gc.ExclusiveSelfAssignedRoles;
var roles = uow.Set<SelfAssignedRole>().GetFromGuild(guildId);
public record class SarRoleRequirement(ulong RoleId);
return (autoDelete, exclusive, roles);
}
public record class SarAlreadyHasRole();
public bool SetLevelReq(ulong guildId, IRole role, int level)
{
using var uow = _db.GetDbContext();
var roles = uow.Set<SelfAssignedRole>().GetFromGuild(guildId);
var sar = roles.FirstOrDefault(x => x.RoleId == role.Id);
if (sar is not null)
{
sar.LevelRequirement = level;
uow.SaveChanges();
}
else
return false;
return true;
}
public bool ToggleEsar(ulong guildId)
{
bool areExclusive;
using var uow = _db.GetDbContext();
var config = uow.GuildConfigsForId(guildId, set => set);
areExclusive = config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles;
uow.SaveChanges();
return areExclusive;
}
public (bool Exclusive, IReadOnlyCollection<(SelfAssignedRole Model, IRole Role)> Roles, IDictionary<int, string>
GroupNames
) GetRoles(IGuild guild)
{
var exclusive = false;
IReadOnlyCollection<(SelfAssignedRole Model, IRole Role)> roles;
IDictionary<int, string> groupNames;
using (var uow = _db.GetDbContext())
{
var gc = uow.GuildConfigsForId(guild.Id, set => set.Include(x => x.SelfAssignableRoleGroupNames));
exclusive = gc.ExclusiveSelfAssignedRoles;
groupNames = gc.SelfAssignableRoleGroupNames.ToDictionary(x => x.Number, x => x.Name);
var roleModels = uow.Set<SelfAssignedRole>().GetFromGuild(guild.Id);
roles = roleModels.Select(x => (Model: x, Role: guild.GetRole(x.RoleId)))
.ToList();
uow.Set<SelfAssignedRole>().RemoveRange(roles.Where(x => x.Role is null).Select(x => x.Model).ToArray());
uow.SaveChanges();
}
return (exclusive, roles.Where(x => x.Role is not null).ToList(), groupNames);
}
}
public record class SarInsuffPerms();

View File

@@ -35,7 +35,7 @@ public partial class Administration
var usrs = settings?.LogIgnores.Where(x => x.ItemType == IgnoredItemType.User).ToList()
?? new List<IgnoredLogItem>();
var eb = _sender.CreateEmbed()
var eb = CreateEmbed()
.WithOkColor()
.AddField(GetText(strs.log_ignored_channels),
chs.Count == 0

View File

@@ -42,7 +42,7 @@ public partial class Administration
.Items(timezoneStrings)
.PageSize(timezonesPerPage)
.CurrentPage(page)
.Page((items, _) => _sender.CreateEmbed()
.Page((items, _) => CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.timezones_available))
.WithDescription(string.Join("\n", items)))

View File

@@ -23,27 +23,6 @@ public partial class Administration
_mute = mute;
}
private async Task<bool> CheckRoleHierarchy(IGuildUser target)
{
var curUser = ((SocketGuild)ctx.Guild).CurrentUser;
var ownerId = ctx.Guild.OwnerId;
var modMaxRole = ((IGuildUser)ctx.User).GetRoles().Max(r => r.Position);
var targetMaxRole = target.GetRoles().Max(r => r.Position);
var botMaxRole = curUser.GetRoles().Max(r => r.Position);
// bot can't punish a user who is higher in the hierarchy. Discord will return 403
// moderator can be owner, in which case role hierarchy doesn't matter
// otherwise, moderator has to have a higher role
if (botMaxRole <= targetMaxRole
|| (ctx.User.Id != ownerId && targetMaxRole >= modMaxRole)
|| target.Id == ownerId)
{
await Response().Error(strs.hierarchy).SendAsync();
return false;
}
return true;
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
@@ -65,7 +44,7 @@ public partial class Administration
try
{
await _sender.Response(user)
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithErrorColor()
.WithDescription(GetText(strs.warned_on(ctx.Guild.ToString())))
.AddField(GetText(strs.moderator), ctx.User.ToString())
@@ -85,7 +64,7 @@ public partial class Administration
catch (Exception ex)
{
Log.Warning(ex, "Exception occured while warning a user");
var errorEmbed = _sender.CreateEmbed()
var errorEmbed = CreateEmbed()
.WithErrorColor()
.WithDescription(GetText(strs.cant_apply_punishment));
@@ -96,7 +75,7 @@ public partial class Administration
return;
}
var embed = _sender.CreateEmbed().WithOkColor();
var embed = CreateEmbed().WithOkColor();
if (punishment is null)
embed.WithDescription(GetText(strs.user_warned(Format.Bold(user.ToString()))));
else
@@ -205,7 +184,7 @@ public partial class Administration
.Page((warnings, page) =>
{
var user = (ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString();
var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.warnlog_for(user)));
var embed = CreateEmbed().WithOkColor().WithTitle(GetText(strs.warnlog_for(user)));
if (!warnings.Any())
embed.WithDescription(GetText(strs.warnings_none));
@@ -266,7 +245,7 @@ public partial class Administration
+ $" | {total} ({all} - {forgiven})";
});
return _sender.CreateEmbed()
return CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.warnings_list))
.WithDescription(string.Join("\n", ws));
@@ -478,7 +457,7 @@ public partial class Administration
var user = await ctx.Client.GetUserAsync(userId);
var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
await _mute.TimedBan(ctx.Guild, userId, time.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune);
var toSend = _sender.CreateEmbed()
var toSend = CreateEmbed()
.WithOkColor()
.WithTitle("⛔️ " + GetText(strs.banned_user))
.AddField(GetText(strs.username), user?.ToString() ?? userId.ToString(), true)
@@ -507,7 +486,7 @@ public partial class Administration
await ctx.Guild.AddBanAsync(userId, banPrune, (ctx.User + " | " + msg).TrimTo(512));
await Response()
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithOkColor()
.WithTitle("⛔️ " + GetText(strs.banned_user))
.AddField("ID", userId.ToString(), true))
@@ -544,7 +523,7 @@ public partial class Administration
var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
await ctx.Guild.AddBanAsync(user, banPrune, (ctx.User + " | " + msg).TrimTo(512));
var toSend = _sender.CreateEmbed()
var toSend = CreateEmbed()
.WithOkColor()
.WithTitle("⛔️ " + GetText(strs.banned_user))
.AddField(GetText(strs.username), user.ToString(), true)
@@ -739,7 +718,7 @@ public partial class Administration
try { await ctx.Guild.RemoveBanAsync(user); }
catch { await ctx.Guild.RemoveBanAsync(user); }
var toSend = _sender.CreateEmbed()
var toSend = CreateEmbed()
.WithOkColor()
.WithTitle("☣ " + GetText(strs.sb_user))
.AddField(GetText(strs.username), user.ToString(), true)
@@ -794,7 +773,7 @@ public partial class Administration
await user.KickAsync((ctx.User + " | " + msg).TrimTo(512));
var toSend = _sender.CreateEmbed()
var toSend = CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.kicked_user))
.AddField(GetText(strs.username), user.ToString(), true)
@@ -827,7 +806,7 @@ public partial class Administration
{
var dmMessage = GetText(strs.timeoutdm(Format.Bold(ctx.Guild.Name), msg));
await _sender.Response(user)
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithPendingColor()
.WithDescription(dmMessage))
.SendAsync();
@@ -839,7 +818,7 @@ public partial class Administration
await user.SetTimeOutAsync(time.Time);
var toSend = _sender.CreateEmbed()
var toSend = CreateEmbed()
.WithOkColor()
.WithTitle("⏳ " + GetText(strs.timedout_user))
.AddField(GetText(strs.username), user.ToString(), true)
@@ -900,7 +879,7 @@ public partial class Administration
if (string.IsNullOrWhiteSpace(missStr))
missStr = "-";
var toSend = _sender.CreateEmbed()
var toSend = CreateEmbed()
.WithDescription(GetText(strs.mass_ban_in_progress(banning.Count)))
.AddField(GetText(strs.invalid(missing.Count)), missStr)
.WithPendingColor();
@@ -920,7 +899,7 @@ public partial class Administration
}
}
await banningMessage.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
await banningMessage.ModifyAsync(x => x.Embed = CreateEmbed()
.WithDescription(
GetText(strs.mass_ban_completed(
banning.Count())))
@@ -948,7 +927,7 @@ public partial class Administration
//send a message but don't wait for it
var banningMessageTask = Response()
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithDescription(
GetText(strs.mass_kill_in_progress(bans.Count())))
.AddField(GetText(strs.invalid(missing)), missStr)
@@ -969,7 +948,7 @@ public partial class Administration
//wait for the message and edit it
var banningMessage = await banningMessageTask;
await banningMessage.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
await banningMessage.ModifyAsync(x => x.Embed = CreateEmbed()
.WithDescription(
GetText(strs.mass_kill_completed(bans.Count())))
.AddField(GetText(strs.invalid(missing)), missStr)

View File

@@ -68,7 +68,7 @@ public partial class Administration
else
text = GetText(strs.no_vcroles);
await Response().Embed(_sender.CreateEmbed()
await Response().Embed(CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.vc_role_list))
.WithDescription(text)).SendAsync();

View File

@@ -35,7 +35,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
var ex = await _service.AddAsync(ctx.Guild?.Id, key, message);
await Response()
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.expr_new))
.WithDescription($"#{new kwum(ex.Id)}")
@@ -105,7 +105,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
if (ex is not null)
{
await Response()
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.expr_edited))
.WithDescription($"#{id}")
@@ -160,7 +160,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
: " // " + string.Join(" ", ex.GetReactions())))
.Join('\n');
return _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.expressions)).WithDescription(desc);
return CreateEmbed().WithOkColor().WithTitle(GetText(strs.expressions)).WithDescription(desc);
})
.SendAsync();
}
@@ -180,7 +180,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
await Response()
.Interaction(IsValidExprEditor() ? inter : null)
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithOkColor()
.WithDescription($"#{id}")
.AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
@@ -225,7 +225,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
if (ex is not null)
{
await Response()
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.expr_deleted))
.WithDescription($"#{id}")
@@ -376,7 +376,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
[UserPerm(GuildPerm.Administrator)]
public async Task ExprClear()
{
if (await PromptUserConfirmAsync(_sender.CreateEmbed()
if (await PromptUserConfirmAsync(CreateEmbed()
.WithTitle("Expression clear")
.WithDescription("This will delete all expressions on this server.")))
{

View File

@@ -6,6 +6,10 @@ namespace NadekoBot.Modules.Gambling.Common.AnimalRacing;
public sealed class AnimalRace : IDisposable
{
public const double BASE_MULTIPLIER = 0.87;
public const double MAX_MULTIPLIER = 0.94;
public const double MULTI_PER_USER = 0.005;
public enum Phase
{
WaitingForPlayers,
@@ -100,7 +104,7 @@ public sealed class AnimalRace : IDisposable
foreach (var user in _users)
{
if (user.Bet > 0)
await _currency.AddAsync(user.UserId, user.Bet, new("animalrace", "refund"));
await _currency.AddAsync(user.UserId, (long)(user.Bet * BASE_MULTIPLIER), new("animalrace", "refund"));
}
_ = OnStartingFailed?.Invoke(this);
@@ -116,7 +120,7 @@ public sealed class AnimalRace : IDisposable
{
foreach (var user in _users)
{
user.Progress += rng.Next(1, 11);
user.Progress += rng.Next(1, 10);
if (user.Progress >= 60)
user.Progress = 60;
}
@@ -126,13 +130,15 @@ public sealed class AnimalRace : IDisposable
FinishedUsers.AddRange(finished);
_ = OnStateUpdate?.Invoke(this);
await Task.Delay(2500);
await Task.Delay(1750);
}
if (FinishedUsers[0].Bet > 0)
{
Multi = FinishedUsers.Count
* Math.Min(MAX_MULTIPLIER, BASE_MULTIPLIER + (MULTI_PER_USER * FinishedUsers.Count));
await _currency.AddAsync(FinishedUsers[0].UserId,
FinishedUsers[0].Bet * (_users.Count - 1),
(long)(FinishedUsers[0].Bet * Multi),
new("animalrace", "win"));
}
@@ -140,6 +146,8 @@ public sealed class AnimalRace : IDisposable
});
}
public double Multi { get; set; } = BASE_MULTIPLIER;
public void Dispose()
{
CurrentPhase = Phase.Ended;

View File

@@ -12,7 +12,7 @@ namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
[Group]
public partial class AnimalRacingCommands : GamblingSubmodule<AnimalRaceService>
public partial class AnimalRacingCommands : GamblingModule<AnimalRaceService>
{
private readonly ICurrencyService _cs;
private readonly DiscordSocketClient _client;
@@ -74,10 +74,14 @@ public partial class Gambling
if (race.FinishedUsers[0].Bet > 0)
{
return Response()
.Confirm(GetText(strs.animal_race),
GetText(strs.animal_race_won_money(Format.Bold(winner.Username),
winner.Animal.Icon,
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)))
.Embed(CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.animal_race))
.WithDescription(GetText(strs.animal_race_won_money(
Format.Bold(winner.Username),
winner.Animal.Icon,
N(race.FinishedUsers[0].Bet * race.Multi))))
.WithFooter($"x{race.Multi:F2}"))
.SendAsync();
}
@@ -113,14 +117,14 @@ public partial class Gambling
private async Task Ar_OnStateUpdate(AnimalRace race)
{
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
var text = $@"|🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁🔚|
{string.Join("\n", race.Users.Select(p =>
{
var index = race.FinishedUsers.IndexOf(p);
var extra = index == -1 ? "" : $"#{index + 1} {(index == 0 ? "🏆" : "")}";
return $"{(int)(p.Progress / 60f * 100),-2}%|{new string('‣', p.Progress) + p.Animal.Icon + extra}";
}))}
|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|";
|🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🏁 🔚|";
var msg = raceMessage;
@@ -128,11 +132,11 @@ public partial class Gambling
raceMessage = await Response().Confirm(text).SendAsync();
else
{
await msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
.WithTitle(GetText(strs.animal_race))
.WithDescription(text)
.WithOkColor()
.Build());
await msg.ModifyAsync(x => x.Embed = CreateEmbed()
.WithTitle(GetText(strs.animal_race))
.WithDescription(text)
.WithOkColor()
.Build());
}
}

View File

@@ -59,7 +59,7 @@ public partial class Gambling
{
var bal = await _bank.GetBalanceAsync(ctx.User.Id);
var eb = _sender.CreateEmbed()
var eb = CreateEmbed()
.WithOkColor()
.WithDescription(GetText(strs.bank_balance(N(bal))));
@@ -80,7 +80,7 @@ public partial class Gambling
{
var bal = await _bank.GetBalanceAsync(user.Id);
var eb = _sender.CreateEmbed()
var eb = CreateEmbed()
.WithOkColor()
.WithDescription(GetText(strs.bank_balance_other(user.ToString(), N(bal))));

View File

@@ -0,0 +1,175 @@
#nullable disable
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Modules.Gambling.Services;
namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
[Group]
public sealed class BetStatsCommands : GamblingModule<UserBetStatsService>
{
private readonly GamblingTxTracker _gamblingTxTracker;
public BetStatsCommands(
GamblingTxTracker gamblingTxTracker,
GamblingConfigService gcs)
: base(gcs)
{
_gamblingTxTracker = gamblingTxTracker;
}
[Cmd]
public async Task BetStatsReset(GamblingGame? game = null)
{
var price = await _service.GetResetStatsPriceAsync(ctx.User.Id, game);
var result = await PromptUserConfirmAsync(CreateEmbed()
.WithDescription(
$"""
Are you sure you want to reset your bet stats for **{GetGameName(game)}**?
It will cost you {N(price)}
"""));
if (!result)
return;
var success = await _service.ResetStatsAsync(ctx.User.Id, game);
if (success)
{
await ctx.OkAsync();
}
else
{
await Response()
.Error(strs.not_enough(CurrencySign))
.SendAsync();
}
}
private string GetGameName(GamblingGame? game)
{
if (game is null)
return "all games";
return game.ToString();
}
[Cmd]
[Priority(3)]
public async Task BetStats()
=> await BetStats(ctx.User, null);
[Cmd]
[Priority(2)]
public async Task BetStats(GamblingGame game)
=> await BetStats(ctx.User, game);
[Cmd]
[Priority(1)]
public async Task BetStats([Leftover] IUser user)
=> await BetStats(user, null);
[Cmd]
[Priority(0)]
public async Task BetStats(IUser user, GamblingGame? game)
{
var stats = await _gamblingTxTracker.GetUserStatsAsync(user.Id, game);
if (stats.Count == 0)
stats = new()
{
new()
{
TotalBet = 1
}
};
var eb = CreateEmbed()
.WithOkColor()
.WithAuthor(user)
.AddField("Total Won", N(stats.Sum(x => x.PaidOut)), true)
.AddField("Biggest Win", N(stats.Max(x => x.MaxWin)), true)
.AddField("Biggest Bet", N(stats.Max(x => x.MaxBet)), true)
.AddField("# Bets", stats.Sum(x => x.WinCount + x.LoseCount), true)
.AddField("Payout",
(stats.Sum(x => x.PaidOut) / stats.Sum(x => x.TotalBet)).ToString("P2", Culture),
true);
if (game == null)
{
var favGame = stats.MaxBy(x => x.WinCount + x.LoseCount);
eb.AddField("Favorite Game",
favGame.Game + "\n" + Format.Italics((favGame.WinCount + favGame.LoseCount) + " plays"),
true);
}
else
{
eb.WithDescription(game.ToString())
.AddField("# Wins", stats.Sum(x => x.WinCount), true);
}
await Response()
.Embed(eb)
.SendAsync();
}
[Cmd]
public async Task GambleStats()
{
var stats = await _gamblingTxTracker.GetAllAsync();
var eb = CreateEmbed()
.WithOkColor();
var str = "` Feature `` Bet ``Paid Out`` RoI `\n";
str += "――――――――――――――――――――\n";
foreach (var stat in stats)
{
var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture);
str += $"`{stat.Feature.PadBoth(9)}`"
+ $"`{stat.Bet.ToString("N0").PadLeft(8, '')}`"
+ $"`{stat.PaidOut.ToString("N0").PadLeft(8, '')}`"
+ $"`{perc.PadLeft(6, '')}`\n";
}
var bet = stats.Sum(x => x.Bet);
var paidOut = stats.Sum(x => x.PaidOut);
if (bet == 0)
bet = 1;
var tPerc = (paidOut / bet).ToString("P2", Culture);
str += "――――――――――――――――――――\n";
str += $"` {("TOTAL").PadBoth(7)}` "
+ $"**{N(bet).PadLeft(8, '')}**"
+ $"**{N(paidOut).PadLeft(8, '')}**"
+ $"`{tPerc.PadLeft(6, '')}`";
eb.WithDescription(str);
await Response().Embed(eb).SendAsync();
}
[Cmd]
[OwnerOnly]
public async Task GambleStatsReset()
{
if (!await PromptUserConfirmAsync(CreateEmbed()
.WithDescription(
"""
Are you sure?
This will completely reset Gambling Stats.
This action is irreversible.
""")))
return;
await GambleStats();
await _service.ResetGamblingStatsAsync();
await ctx.OkAsync();
}
}
}

View File

@@ -8,7 +8,7 @@ namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
public partial class BlackJackCommands : GamblingSubmodule<BlackJackService>
public partial class BlackJackCommands : GamblingModule<BlackJackService>
{
public enum BjAction
{
@@ -95,7 +95,7 @@ public partial class Gambling
var cStr = string.Concat(c.Select(x => x[..^1] + " "));
cStr += "\n" + string.Concat(c.Select(x => x.Last() + " "));
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithOkColor()
.WithTitle("BlackJack")
.AddField($"{dealerIcon} Dealer's Hand | Value: {bj.Dealer.GetHandValue()}", cStr);

View File

@@ -9,7 +9,7 @@ namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
[Group]
public partial class Connect4Commands : GamblingSubmodule<GamblingService>
public partial class Connect4Commands : GamblingModule<GamblingService>
{
private static readonly string[] _numbers =
[
@@ -132,7 +132,7 @@ public partial class Gambling
else
title = GetText(strs.connect4_draw);
return msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
return msg.ModifyAsync(x => x.Embed = CreateEmbed()
.WithTitle(title)
.WithDescription(GetGameStateText(game))
.WithOkColor()
@@ -142,7 +142,7 @@ public partial class Gambling
private async Task Game_OnGameStateUpdated(Connect4Game game)
{
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
.WithDescription(GetGameStateText(game))
.WithOkColor();

View File

@@ -38,7 +38,7 @@ public partial class Gambling
var fileName = $"dice.{format.FileExtensions.First()}";
var eb = _sender.CreateEmbed()
var eb = CreateEmbed()
.WithOkColor()
.WithAuthor(ctx.User)
.AddField(GetText(strs.roll2), gen)
@@ -115,7 +115,7 @@ public partial class Gambling
d.Dispose();
var imageName = $"dice.{format.FileExtensions.First()}";
var eb = _sender.CreateEmbed()
var eb = CreateEmbed()
.WithOkColor()
.WithAuthor(ctx.User)
.AddField(GetText(strs.rolls), values.Select(x => Format.Code(x.ToString())).Join(' '), true)
@@ -141,7 +141,7 @@ public partial class Gambling
for (var i = 0; i < n1; i++)
rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithOkColor()
.WithAuthor(ctx.User)
.WithDescription(GetText(strs.dice_rolled_num(Format.Bold(n1.ToString()))))
@@ -170,7 +170,7 @@ public partial class Gambling
arr[i] = rng.Next(1, n2 + 1);
var sum = arr.Sum();
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithOkColor()
.WithAuthor(ctx.User)
.WithDescription(GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`")))

View File

@@ -12,7 +12,7 @@ namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
[Group]
public partial class DrawCommands : GamblingSubmodule<IGamblingService>
public partial class DrawCommands : GamblingModule<IGamblingService>
{
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new();
private readonly IImageCache _images;
@@ -56,8 +56,8 @@ public partial class Gambling
foreach (var i in images)
i.Dispose();
var eb = _sender.CreateEmbed()
.WithOkColor();
var eb = CreateEmbed()
.WithOkColor();
var toSend = string.Empty;
if (cardObjects.Count == 5)
@@ -171,14 +171,15 @@ public partial class Gambling
return;
}
var eb = _sender.CreateEmbed()
.WithOkColor()
.WithAuthor(ctx.User)
.WithDescription(result.Card.GetEmoji())
.AddField(GetText(strs.guess), GetGuessInfo(val, col), true)
.AddField(GetText(strs.card), GetCardInfo(result.Card), true)
.AddField(GetText(strs.won), N((long)result.Won), false)
.WithImageUrl("attachment://card.png");
var eb = CreateEmbed()
.WithOkColor()
.WithAuthor(ctx.User)
.WithDescription(result.Card.GetEmoji())
.AddField(GetText(strs.guess), GetGuessInfo(val, col), true)
.AddField(GetText(strs.card), GetCardInfo(result.Card), false)
.AddField(GetText(strs.bet), N(amount), true)
.AddField(GetText(strs.won), N((long)result.Won), true)
.WithImageUrl("attachment://card.png");
using var img = await GetCardImageAsync(result.Card);
await using var imgStream = await img.ToStreamAsync();

View File

@@ -9,7 +9,7 @@ namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
[Group]
public partial class CurrencyEventsCommands : GamblingSubmodule<CurrencyEventsService>
public partial class CurrencyEventsCommands : GamblingModule<CurrencyEventsService>
{
public CurrencyEventsCommands(GamblingConfigService gamblingConf)
: base(gamblingConf)
@@ -30,12 +30,12 @@ public partial class Gambling
private EmbedBuilder GetEmbed(CurrencyEvent.Type type, EventOptions opts, long currentPot)
=> type switch
{
CurrencyEvent.Type.Reaction => _sender.CreateEmbed()
CurrencyEvent.Type.Reaction => CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.event_title(type.ToString())))
.WithDescription(GetReactionDescription(opts.Amount, currentPot))
.WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
CurrencyEvent.Type.GameStatus => _sender.CreateEmbed()
CurrencyEvent.Type.GameStatus => CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.event_title(type.ToString())))
.WithDescription(GetGameStatusDescription(opts.Amount, currentPot))

View File

@@ -11,7 +11,7 @@ namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
[Group]
public partial class FlipCoinCommands : GamblingSubmodule<IGamblingService>
public partial class FlipCoinCommands : GamblingModule<IGamblingService>
{
public enum BetFlipGuess : byte
{
@@ -56,7 +56,7 @@ public partial class Gambling
var tailsArr = await _images.GetTailsImageAsync();
var result = await _service.FlipAsync(count);
for (var i = 0; i < result.Length; i++)
{
if (result[i].Side == 0)
@@ -77,18 +77,18 @@ public partial class Gambling
i.Dispose();
var imgName = $"coins.{format.FileExtensions.First()}";
var msg = count != 1
? Format.Bold(GetText(strs.flip_results(count, headCount, tailCount)))
: GetText(strs.flipped(headCount > 0
? Format.Bold(GetText(strs.heads))
: Format.Bold(GetText(strs.tails))));
var eb = _sender.CreateEmbed()
.WithOkColor()
.WithAuthor(ctx.User)
.WithDescription(msg)
.WithImageUrl($"attachment://{imgName}");
var eb = CreateEmbed()
.WithOkColor()
.WithAuthor(ctx.User)
.WithDescription(msg)
.WithImageUrl($"attachment://{imgName}");
await ctx.Channel.SendFileAsync(stream,
imgName,
@@ -123,18 +123,22 @@ public partial class Gambling
var won = (long)result.Won;
if (won > 0)
{
str = Format.Bold(GetText(strs.flip_guess(N(won))));
str = Format.Bold(GetText(strs.betflip_guess));
}
else
{
str = Format.Bold(GetText(strs.better_luck));
}
await Response().Embed(_sender.CreateEmbed()
.WithAuthor(ctx.User)
.WithDescription(str)
.WithOkColor()
.WithImageUrl(imageToSend.ToString())).SendAsync();
await Response()
.Embed(CreateEmbed()
.WithAuthor(ctx.User)
.WithDescription(str)
.AddField(GetText(strs.bet), N(amount), true)
.AddField(GetText(strs.won), N((long)result.Won), true)
.WithOkColor()
.WithImageUrl(imageToSend.ToString()))
.SendAsync();
}
}
}

View File

@@ -14,6 +14,13 @@ using System.Text;
using NadekoBot.Modules.Gambling.Rps;
using NadekoBot.Common.TypeReaders;
using NadekoBot.Modules.Patronage;
using SixLabors.Fonts;
using SixLabors.Fonts.Unicode;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Color = SixLabors.ImageSharp.Color;
namespace NadekoBot.Modules.Gambling;
@@ -26,10 +33,13 @@ public partial class Gambling : GamblingModule<GamblingService>
private readonly NumberFormatInfo _enUsCulture;
private readonly DownloadTracker _tracker;
private readonly GamblingConfigService _configService;
private readonly FontProvider _fonts;
private readonly IBankService _bank;
private readonly IRemindService _remind;
private readonly GamblingTxTracker _gamblingTxTracker;
private readonly IPatronageService _ps;
private readonly RakebackService _rb;
private readonly IBotCache _cache;
public Gambling(
IGamblingService gs,
@@ -38,10 +48,13 @@ public partial class Gambling : GamblingModule<GamblingService>
DiscordSocketClient client,
DownloadTracker tracker,
GamblingConfigService configService,
FontProvider fonts,
IBankService bank,
IRemindService remind,
IPatronageService patronage,
GamblingTxTracker gamblingTxTracker)
GamblingTxTracker gamblingTxTracker,
RakebackService rb,
IBotCache cache)
: base(configService)
{
_gs = gs;
@@ -51,13 +64,17 @@ public partial class Gambling : GamblingModule<GamblingService>
_bank = bank;
_remind = remind;
_gamblingTxTracker = gamblingTxTracker;
_rb = rb;
_cache = cache;
_ps = patronage;
_rng = new NadekoRandom();
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
_enUsCulture.NumberDecimalDigits = 0;
_enUsCulture.NumberGroupSeparator = "";
_tracker = tracker;
_configService = configService;
_fonts = fonts;
}
public async Task<string> GetBalanceStringAsync(ulong userId)
@@ -66,42 +83,6 @@ public partial class Gambling : GamblingModule<GamblingService>
return N(bal);
}
[Cmd]
public async Task BetStats()
{
var stats = await _gamblingTxTracker.GetAllAsync();
var eb = _sender.CreateEmbed()
.WithOkColor();
var str = "` Feature `` Bet ``Paid Out`` RoI `\n";
str += "――――――――――――――――――――\n";
foreach (var stat in stats)
{
var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture);
str += $"`{stat.Feature.PadBoth(9)}`"
+ $"`{stat.Bet.ToString("N0").PadLeft(8, '')}`"
+ $"`{stat.PaidOut.ToString("N0").PadLeft(8, '')}`"
+ $"`{perc.PadLeft(6, '')}`\n";
}
var bet = stats.Sum(x => x.Bet);
var paidOut = stats.Sum(x => x.PaidOut);
if (bet == 0)
bet = 1;
var tPerc = (paidOut / bet).ToString("P2", Culture);
str += "――――――――――――――――――――\n";
str += $"` {("TOTAL").PadBoth(7)}` "
+ $"**{N(bet).PadLeft(8, '')}**"
+ $"**{N(paidOut).PadLeft(8, '')}**"
+ $"`{tPerc.PadLeft(6, '')}`";
eb.WithDescription(str);
await Response().Embed(eb).SendAsync();
}
private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when)
{
@@ -140,7 +121,21 @@ public partial class Gambling : GamblingModule<GamblingService>
(smc) => RemindTimelyAction(smc, DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(ms)))
);
private NadekoInteractionBase CreateTimelyInteraction()
=> _inter
.Create(ctx.User.Id,
new ButtonBuilder(
label: "Timely",
emote: Emoji.Parse("💰"),
customId: "timely:" + _rng.Next(123456, 999999)),
async (smc) =>
{
await smc.DeferAsync();
await ClaimTimely();
});
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Timely()
{
var val = Config.Timely.Amount;
@@ -151,6 +146,94 @@ public partial class Gambling : GamblingModule<GamblingService>
return;
}
if (Config.Timely.ProtType == TimelyProt.Button)
{
var interaction = CreateTimelyInteraction();
var msg = await Response().Pending(strs.timely_button).Interaction(interaction).SendAsync();
await msg.DeleteAsync();
return;
}
else if (Config.Timely.ProtType == TimelyProt.Captcha)
{
var password = await GetUserTimelyPassword(ctx.User.Id);
var img = GetPasswordImage(password);
using var stream = await img.ToStreamAsync();
var captcha = await Response()
.File(stream, "timely.png")
.SendAsync();
try
{
var userInput = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id);
if (userInput?.ToLowerInvariant() != password?.ToLowerInvariant())
{
return;
}
await ClearUserTimelyPassword(ctx.User.Id);
}
finally
{
_ = captcha.DeleteAsync();
}
}
await ClaimTimely();
}
private static TypedKey<string> TimelyPasswordKey(ulong userId)
=> new($"timely_password:{userId}");
private async Task<string> GetUserTimelyPassword(ulong userId)
{
var pw = await _cache.GetOrAddAsync(TimelyPasswordKey(userId),
() =>
{
var password = _service.GeneratePassword();
return Task.FromResult(password);
});
return pw;
}
private ValueTask<bool> ClearUserTimelyPassword(ulong userId)
=> _cache.RemoveAsync(TimelyPasswordKey(userId));
private Image<Rgba32> GetPasswordImage(string password)
{
var img = new Image<Rgba32>(50, 24);
var font = _fonts.NotoSans.CreateFont(22);
var outlinePen = new SolidPen(Color.Black, 0.5f);
var strikeoutRun = new RichTextRun
{
Start = 0,
End = password.GetGraphemeCount(),
Font = font,
StrikeoutPen = new SolidPen(Color.White, 4),
TextDecorations = TextDecorations.Strikeout
};
// draw password on the image
img.Mutate(x =>
{
x.DrawText(new RichTextOptions(font)
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
FallbackFontFamilies = _fonts.FallBackFonts,
Origin = new(25, 12),
TextRuns = [strikeoutRun]
},
password,
Brushes.Solid(Color.White),
outlinePen);
});
return img;
}
private async Task ClaimTimely()
{
var period = Config.Timely.Cooldown;
if (await _service.ClaimTimelyAsync(ctx.User.Id, period) is { } remainder)
{
// Get correct time form remainder
@@ -169,6 +252,30 @@ public partial class Gambling : GamblingModule<GamblingService>
}
var val = Config.Timely.Amount;
var boostGuilds = Config.BoostBonus.GuildIds ?? new();
var guildUsers = await boostGuilds
.Select(async gid =>
{
try
{
var guild = await _client.Rest.GetGuildAsync(gid, false);
var user = await _client.Rest.GetGuildUserAsync(gid, ctx.User.Id);
return (guild, user);
}
catch
{
return default;
}
})
.WhenAll();
var userInfo = guildUsers.FirstOrDefault(x => x.user?.PremiumSince is not null);
var booster = userInfo != default;
if (booster)
val += Config.BoostBonus.BaseTimelyBonus;
var patron = await _ps.GetPatronAsync(ctx.User.Id);
var percentBonus = (_ps.PercentBonus(patron) / 100f);
@@ -179,7 +286,21 @@ public partial class Gambling : GamblingModule<GamblingService>
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
await Response().Confirm(strs.timely(N(val), period)).Interaction(inter).SendAsync();
var msg = GetText(strs.timely(N(val), period));
if (booster || percentBonus > float.Epsilon)
{
msg += "\n\n";
if (booster)
msg += $"*+{N(Config.BoostBonus.BaseTimelyBonus)} bonus for boosting {userInfo.guild}!*\n";
if (percentBonus > float.Epsilon)
msg +=
$"*+{percentBonus:P0} bonus for the [Patreon](https://patreon.com/nadekobot) pledge! <:hart:746995901758832712>*";
await Response().Confirm(msg).Interaction(inter).SendAsync();
}
else
await Response().Confirm(strs.timely(N(val), period)).Interaction(inter).SendAsync();
}
[Cmd]
@@ -270,6 +391,12 @@ public partial class Gambling : GamblingModule<GamblingService>
public Task CurrencyTransactions([Leftover] IUser usr)
=> InternalCurrencyTransactions(usr.Id, 1);
[Cmd]
[OwnerOnly]
[Priority(-1)]
public Task CurrencyTransactions([Leftover] ulong userId)
=> InternalCurrencyTransactions(userId, 1);
[Cmd]
[OwnerOnly]
[Priority(1)]
@@ -289,10 +416,11 @@ public partial class Gambling : GamblingModule<GamblingService>
trs = await uow.Set<CurrencyTransaction>().GetPageFor(userId, page);
}
var embed = _sender.CreateEmbed()
.WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
?? $"{userId}")))
.WithOkColor();
var embed = CreateEmbed()
.WithTitle(GetText(strs.transactions(
((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
?? $"{userId}")))
.WithOkColor();
var sb = new StringBuilder();
foreach (var tr in trs)
@@ -339,7 +467,7 @@ public partial class Gambling : GamblingModule<GamblingService>
return;
}
var eb = _sender.CreateEmbed().WithOkColor();
var eb = CreateEmbed().WithOkColor();
eb.WithAuthor(ctx.User);
eb.WithTitle(GetText(strs.transaction));
@@ -547,7 +675,9 @@ public partial class Gambling : GamblingModule<GamblingService>
}
else
{
await Response().Error(strs.take_fail(N(amount), Format.Bold(user.ToString()), CurrencySign)).SendAsync();
await Response()
.Error(strs.take_fail(N(amount), Format.Bold(user.ToString()), CurrencySign))
.SendAsync();
}
}
@@ -568,7 +698,9 @@ public partial class Gambling : GamblingModule<GamblingService>
}
else
{
await Response().Error(strs.take_fail(N(amount), Format.Code(usrId.ToString()), CurrencySign)).SendAsync();
await Response()
.Error(strs.take_fail(N(amount), Format.Code(usrId.ToString()), CurrencySign))
.SendAsync();
}
}
@@ -592,18 +724,20 @@ public partial class Gambling : GamblingModule<GamblingService>
string str;
if (win > 0)
{
str = GetText(strs.br_win(N(win), result.Threshold + (result.Roll == 100 ? " 👑" : "")));
str = GetText(strs.betroll_win(result.Threshold + (result.Roll == 100 ? " 👑" : "")));
}
else
{
str = GetText(strs.better_luck);
}
var eb = _sender.CreateEmbed()
.WithAuthor(ctx.User)
.WithDescription(Format.Bold(str))
.AddField(GetText(strs.roll2), result.Roll.ToString(CultureInfo.InvariantCulture))
.WithOkColor();
var eb = CreateEmbed()
.WithAuthor(ctx.User)
.WithDescription(Format.Bold(str))
.AddField(GetText(strs.roll2), result.Roll.ToString(CultureInfo.InvariantCulture), true)
.AddField(GetText(strs.bet), N(amount), true)
.AddField(GetText(strs.won), N((long)result.Won), true)
.WithOkColor();
await Response().Embed(eb).SendAsync();
}
@@ -666,9 +800,9 @@ public partial class Gambling : GamblingModule<GamblingService>
.CurrentPage(page)
.Page((toSend, curPage) =>
{
var embed = _sender.CreateEmbed()
.WithOkColor()
.WithTitle(CurrencySign + " " + GetText(strs.leaderboard));
var embed = CreateEmbed()
.WithOkColor()
.WithTitle(CurrencySign + " " + GetText(strs.leaderboard));
if (!toSend.Any())
{
@@ -729,7 +863,7 @@ public partial class Gambling : GamblingModule<GamblingService>
return;
}
var embed = _sender.CreateEmbed();
var embed = CreateEmbed();
string msg;
if (result.Result == RpsResultType.Draw)
@@ -738,9 +872,6 @@ public partial class Gambling : GamblingModule<GamblingService>
}
else if (result.Result == RpsResultType.Win)
{
if ((long)result.Won > 0)
embed.AddField(GetText(strs.won), N((long)result.Won));
msg = GetText(strs.rps_win(ctx.User.Mention,
GetRpsPick(pick),
GetRpsPick((InputRpsPick)result.ComputerPick)));
@@ -756,12 +887,21 @@ public partial class Gambling : GamblingModule<GamblingService>
.WithOkColor()
.WithDescription(msg);
if (amount > 0)
{
embed
.AddField(GetText(strs.bet), N(amount), true)
.AddField(GetText(strs.won), $"{N((long)result.Won)}", true);
}
await Response().Embed(embed).SendAsync();
}
private static readonly ImmutableArray<string> _emojis =
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
private readonly NadekoRandom _rng;
[Cmd]
public async Task LuckyLadder([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
@@ -791,12 +931,12 @@ public partial class Gambling : GamblingModule<GamblingService>
sb.AppendLine();
}
var eb = _sender.CreateEmbed()
.WithOkColor()
.WithDescription(sb.ToString())
.AddField(GetText(strs.multiplier), $"{result.Multiplier:0.##}x", true)
.AddField(GetText(strs.won), $"{(long)result.Won}", true)
.WithAuthor(ctx.User);
var eb = CreateEmbed()
.WithOkColor()
.WithDescription(sb.ToString())
.AddField(GetText(strs.bet), N(amount), true)
.AddField(GetText(strs.won), $"{N((long)result.Won)}", true)
.WithAuthor(ctx.User);
await Response().Embed(eb).SendAsync();
@@ -900,4 +1040,45 @@ public partial class Gambling : GamblingModule<GamblingService>
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%")
.SendAsync();
}
private NadekoInteractionBase CreateRakebackInteraction()
=> _inter.Create(ctx.User.Id,
new ButtonBuilder(
customId: "cash:rakeback",
emote: new Emoji("💸")),
RakebackAction);
private async Task RakebackAction(SocketMessageComponent arg)
{
var rb = await _rb.ClaimRakebackAsync(ctx.User.Id);
if (rb == 0)
{
await arg.DeferAsync();
return;
}
await arg.RespondAsync(_sender, GetText(strs.rakeback_claimed(N(rb))), MsgType.Ok);
}
[Cmd]
public async Task Rakeback()
{
var rb = await _rb.GetRakebackAsync(ctx.User.Id);
if (rb < 1)
{
await Response()
.Error(strs.rakeback_none)
.SendAsync();
return;
}
var inter = CreateRakebackInteraction();
await Response()
.Pending(strs.rakeback_available(N(rb)))
.Interaction(inter)
.SendAsync();
}
}

View File

@@ -11,7 +11,7 @@ namespace NadekoBot.Modules.Gambling.Common;
public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
{
[Comment("""DO NOT CHANGE""")]
public int Version { get; set; } = 8;
public int Version { get; set; } = 12;
[Comment("""Currency settings""")]
public CurrencyConfig Currency { get; set; }
@@ -67,6 +67,11 @@ public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
[Comment("""Slot config""")]
public SlotsConfig Slots { get; set; }
[Comment("""
Bonus config for server boosts
""")]
public BoostBonusConfig BoostBonus { get; set; }
public GamblingConfig()
{
BetRoll = new();
@@ -79,6 +84,7 @@ public sealed partial class GamblingConfig : ICloneable<GamblingConfig>
Slots = new();
LuckyLadder = new();
BotCuts = new();
BoostBonus = new();
}
}
@@ -104,13 +110,26 @@ public partial class TimelyConfig
How much currency will the users get every time they run .timely command
setting to 0 or less will disable this feature
""")]
public int Amount { get; set; } = 0;
public long Amount { get; set; } = 0;
[Comment("""
How often (in hours) can users claim currency with .timely command
setting to 0 or less will disable this feature
""")]
public int Cooldown { get; set; } = 24;
[Comment("""
How will timely be protected?
None, Button (users have to click the button) or Captcha (users have to type the captcha from an image)
""")]
public TimelyProt ProtType { get; set; } = TimelyProt.Button;
}
public enum TimelyProt
{
None,
Button,
Captcha
}
[Cloneable]
@@ -145,7 +164,7 @@ public partial class BetRollConfig
},
new()
{
WhenAbove = 66,
WhenAbove = 65,
MultiplyBy = 2
}
];
@@ -207,7 +226,7 @@ public partial class LuckyLadderSettings
public decimal[] Multipliers { get; set; }
public LuckyLadderSettings()
=> Multipliers = [2.4M, 1.7M, 1.5M, 1.2M, 0.5M, 0.3M, 0.2M, 0.1M];
=> Multipliers = [2.4M, 1.7M, 1.5M, 1.1M, 0.5M, 0.3M, 0.2M, 0.1M];
}
[Cloneable]
@@ -408,4 +427,15 @@ public sealed partial class BotCutConfig
Default 0.1 (10%).
""")]
public decimal ShopSaleCut { get; set; } = 0.1m;
}
[Cloneable]
public sealed partial class BoostBonusConfig
{
[Comment("Users will receive a bonus if they boost any of these servers")]
public List<ulong> GuildIds { get; set; } = new();
[Comment("This bonus will be added before any other multiplier is applied to the .timely command")]
public long BaseTimelyBonus { get; set; } = 50;
}

View File

@@ -144,6 +144,11 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
ConfigPrinters.ToString,
val => val >= 0);
AddParsedProp("timely.prot",
gs => gs.Timely.ProtType,
ConfigParsers.InsensitiveEnum,
ConfigPrinters.ToString);
Migrate();
}
@@ -167,22 +172,6 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
});
}
if (data.Version < 5)
{
ModifyConfig(c =>
{
c.Version = 5;
});
}
if (data.Version < 6)
{
ModifyConfig(c =>
{
c.Version = 6;
});
}
if (data.Version < 7)
{
ModifyConfig(c =>
@@ -199,5 +188,18 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
c.Waifu.Decay.UnclaimedDecayPercent = 0;
});
}
if (data.Version < 12)
{
ModifyConfig(c =>
{
c.Version = 12;
if (c.BetRoll.Pairs.Length == 3 && c.BetRoll.Pairs[2].WhenAbove == 66)
{
c.BetRoll.Pairs[2].WhenAbove = 65;
}
});
}
}
}

View File

@@ -16,6 +16,7 @@ public class GamblingService : INService, IReadyExecutor
private readonly DiscordSocketClient _client;
private readonly IBotCache _cache;
private readonly GamblingConfigService _gss;
private readonly NadekoRandom _rng;
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
@@ -29,11 +30,19 @@ public class GamblingService : INService, IReadyExecutor
_client = client;
_cache = cache;
_gss = gss;
_rng = new NadekoRandom();
}
public Task OnReadyAsync()
=> Task.WhenAll(CurrencyDecayLoopAsync(), TransactionClearLoopAsync());
public string GeneratePassword()
{
var num = _rng.Next((int)Math.Pow(31, 2), (int)Math.Pow(32, 3));
return new kwum(num).ToString();
}
private async Task TransactionClearLoopAsync()
{
if (_client.ShardId != 0)
@@ -52,7 +61,7 @@ public class GamblingService : INService, IReadyExecutor
var days = TimeSpan.FromDays(lifetime);
await using var uow = _db.GetDbContext();
await uow.Set<CurrencyTransaction>()
.DeleteAsync(ct => ct.DateAdded == null || now - ct.DateAdded < days);
.DeleteAsync(ct => ct.DateAdded == null || now - ct.DateAdded < days);
}
catch (Exception ex)
{
@@ -90,11 +99,11 @@ public class GamblingService : INService, IReadyExecutor
}
Log.Information("""
--- Decaying users' currency ---
| decay: {ConfigDecayPercent}%
| max: {MaxDecay}
| threshold: {DecayMinTreshold}
""",
--- Decaying users' currency ---
| decay: {ConfigDecayPercent}%
| max: {MaxDecay}
| threshold: {DecayMinTreshold}
""",
config.Decay.Percent * 100,
maxDecay,
config.Decay.MinThreshold);
@@ -104,14 +113,14 @@ public class GamblingService : INService, IReadyExecutor
var decay = (double)config.Decay.Percent;
await uow.Set<DiscordUser>()
.Where(x => x.CurrencyAmount > config.Decay.MinThreshold && x.UserId != _client.CurrentUser.Id)
.UpdateAsync(old => new()
{
CurrencyAmount =
maxDecay > Sql.Round((old.CurrencyAmount * decay) - 0.5)
? (long)(old.CurrencyAmount - Sql.Round((old.CurrencyAmount * decay) - 0.5))
: old.CurrencyAmount - maxDecay
});
.Where(x => x.CurrencyAmount > config.Decay.MinThreshold && x.UserId != _client.CurrentUser.Id)
.UpdateAsync(old => new()
{
CurrencyAmount =
maxDecay > Sql.Round((old.CurrencyAmount * decay) - 0.5)
? (long)(old.CurrencyAmount - Sql.Round((old.CurrencyAmount * decay) - 0.5))
: old.CurrencyAmount - maxDecay
});
await uow.SaveChangesAsync();
@@ -133,6 +142,7 @@ public class GamblingService : INService, IReadyExecutor
private static TypedKey<Dictionary<ulong, long>> _timelyKey
= new("timely:claims");
public async Task<TimeSpan?> ClaimTimelyAsync(ulong userId, int period)
{
if (period == 0)
@@ -178,9 +188,10 @@ public class GamblingService : INService, IReadyExecutor
public bool UserHasTimelyReminder(ulong userId)
{
var db = _db.GetDbContext();
return db.GetTable<Reminder>().Any(x => x.UserId == userId
&& x.Type == ReminderType.Timely);
}
return db.GetTable<Reminder>()
.Any(x => x.UserId == userId
&& x.Type == ReminderType.Timely);
}
public async Task RemoveAllTimelyClaimsAsync()
=> await _cache.RemoveAsync(_timelyKey);

View File

@@ -57,12 +57,4 @@ public abstract class GamblingModule<TService> : NadekoModule<TService>
return Task.FromResult(true);
return InternalCheckBet(amount);
}
}
public abstract class GamblingSubmodule<TService> : GamblingModule<TService>
{
protected GamblingSubmodule(GamblingConfigService gamblingConfService)
: base(gamblingConfService)
{
}
}

View File

@@ -0,0 +1,92 @@
// namespace NadekoBot.Modules.Gambling;
// public sealed class Loan
// {
// public int Id { get; set; }
// public ulong LenderId { get; set; }
// public string LenderName { get; set; }
// public ulong BorrowerId { get; set; }
// public string BorrowerName { get; set; }
// public long Amount { get; set; }
// public decimal Interest { get; set; }
// public DateTime DueDate { get; set; }
// public bool Repaid { get; set; }
// }
//
// public sealed class LoanService : INService
// {
// public async Task<IReadOnlyList<Loan>> GetLoans(ulong userId)
// {
// }
//
// public async Task<object> RepayAsync(object loandId)
// {
// }
// }
//
// public partial class Gambling
// {
// public partial class LoanCommands : NadekoModule<LoanService>
// {
// [Cmd]
// public async Task Loan(
// IUser lender,
// long amount,
// decimal interest = 0,
// TimeSpan dueIn = default)
// {
// var eb = CreateEmbed()
// .WithOkColor()
// .WithDescription("User 0 Requests a loan from User {1}")
// .AddField("Amount", amount, true)
// .AddField("Interest", (interest * 0.01m).ToString("P2"), true);
// }
//
// public Task Loans()
// => Loans(ctx.User);
//
// public async Task Loans([Leftover] IUser user)
// {
// var loans = await _service.GetLoans(user.Id);
//
// Response()
// .Paginated()
// .PageItems(loans)
// .Page((items, page) =>
// {
// var eb = CreateEmbed()
// .WithOkColor()
// .WithDescription("Current Loans");
//
// foreach (var item in items)
// {
// eb.AddField(new kwum(item.id).ToString(),
// $"""
// To: {item.LenderName}
// Amount: {}
// """,
// true);
// }
//
// return eb;
// });
// }
//
// [Cmd]
// public async Task Repay(kwum loanId)
// {
// var res = await _service.RepayAsync(loandId);
//
// if (res.TryPickT0(out var _, out var err))
// {
// }
// else
// {
// var errStr = err.Match(
// _ => "Not enough funds",
// _ => "Loan not found");
//
// await Response().Error(errStr).SendAsync();
// }
// }
// }
// }

View File

@@ -8,7 +8,7 @@ namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
[Group]
public partial class PlantPickCommands : GamblingSubmodule<PlantPickService>
public partial class PlantPickCommands : GamblingModule<PlantPickService>
{
private readonly ILogCommandService _logService;
@@ -59,7 +59,7 @@ public partial class Gambling
}
var success = await _service.PlantAsync(ctx.Guild.Id,
ctx.Channel,
(ITextChannel)ctx.Channel,
ctx.User.Id,
ctx.User.ToString(),
amount,
@@ -103,9 +103,9 @@ public partial class Gambling
.Page((items, _) =>
{
if (!items.Any())
return _sender.CreateEmbed().WithErrorColor().WithDescription("-");
return CreateEmbed().WithErrorColor().WithDescription("-");
return items.Aggregate(_sender.CreateEmbed().WithOkColor(),
return items.Aggregate(CreateEmbed().WithOkColor(),
(eb, i) => eb.AddField(i.GuildId.ToString(), i.ChannelId));
})
.SendAsync();

View File

@@ -1,8 +1,11 @@
#nullable disable
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db.Models;
using SixLabors.Fonts;
using SixLabors.Fonts.Unicode;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
@@ -25,6 +28,7 @@ public class PlantPickService : INService, IExecNoCommand
private readonly NadekoRandom _rng;
private readonly DiscordSocketClient _client;
private readonly GamblingConfigService _gss;
private readonly GamblingService _gs;
private readonly ConcurrentHashSet<ulong> _generationChannels;
private readonly SemaphoreSlim _pickLock = new(1, 1);
@@ -37,7 +41,8 @@ public class PlantPickService : INService, IExecNoCommand
ICurrencyService cs,
CommandHandler cmdHandler,
DiscordSocketClient client,
GamblingConfigService gss)
GamblingConfigService gss,
GamblingService gs)
{
_db = db;
_strings = strings;
@@ -48,6 +53,7 @@ public class PlantPickService : INService, IExecNoCommand
_rng = new();
_client = client;
_gss = gss;
_gs = gs;
using var uow = db.GetDbContext();
var guildIds = client.Guilds.Select(x => x.Id).ToList();
@@ -87,6 +93,7 @@ public class PlantPickService : INService, IExecNoCommand
var toDelete = guildConfig.GenerateCurrencyChannelIds.FirstOrDefault(x => x.Equals(toAdd));
if (toDelete is not null)
uow.Remove(toDelete);
_generationChannels.TryRemove(cid);
enabled = false;
}
@@ -140,7 +147,7 @@ public class PlantPickService : INService, IExecNoCommand
pass = pass.TrimTo(10, true).ToLowerInvariant();
using var img = Image.Load<Rgba32>(curImg);
// choose font size based on the image height, so that it's visible
var font = _fonts.NotoSans.CreateFont(img.Height / 12.0f, FontStyle.Bold);
var font = _fonts.NotoSans.CreateFont(img.Height / 11.0f, FontStyle.Bold);
img.Mutate(x =>
{
// measure the size of the text to be drawing
@@ -152,13 +159,31 @@ public class PlantPickService : INService, IExecNoCommand
// fill the background with black, add 5 pixels on each side to make it look better
x.FillPolygon(Color.ParseHex("00000080"),
new PointF(0, 0),
new PointF(1, 1),
new PointF(size.Width + 5, 0),
new PointF(size.Width + 5, size.Height + 10),
new PointF(0, size.Height + 10));
var strikeoutRun = new RichTextRun
{
Start = 0,
End = pass.GetGraphemeCount(),
Font = font,
StrikeoutPen = new SolidPen(Color.White, 2),
TextDecorations = TextDecorations.Strikeout
};
// draw the password over the background
x.DrawText(pass, font, Color.White, new(0, 0));
x.DrawText(new RichTextOptions(font)
{
Origin = new(0, 0),
TextRuns =
[
strikeoutRun
]
},
pass,
new SolidBrush(Color.White));
});
// return image as a stream for easy sending
var format = img.Metadata.DecodedImageFormat;
@@ -208,7 +233,7 @@ public class PlantPickService : INService, IExecNoCommand
+ " "
+ GetText(channel.GuildId, strs.pick_pl(prefix));
var pw = config.Generation.HasPassword ? GenerateCurrencyPassword().ToUpperInvariant() : null;
var pw = config.Generation.HasPassword ? _gs.GeneratePassword().ToUpperInvariant() : null;
IUserMessage sent;
var (stream, ext) = await GetRandomCurrencyImageAsync(pw);
@@ -216,12 +241,18 @@ public class PlantPickService : INService, IExecNoCommand
await using (stream)
sent = await channel.SendFileAsync(stream, $"currency_image.{ext}", toSend);
await AddPlantToDatabase(channel.GuildId,
var res = await AddPlantToDatabase(channel.GuildId,
channel.Id,
_client.CurrentUser.Id,
sent.Id,
dropAmount,
pw);
pw,
true);
if (res.toDelete.Length > 0)
{
await channel.DeleteMessagesAsync(res.toDelete);
}
}
}
}
@@ -232,67 +263,44 @@ public class PlantPickService : INService, IExecNoCommand
return Task.CompletedTask;
}
/// <summary>
/// Generate a hexadecimal string from 1000 to ffff.
/// </summary>
/// <returns>A hexadecimal string from 1000 to ffff</returns>
private string GenerateCurrencyPassword()
{
// generate a number from 1000 to ffff
var num = _rng.Next(4096, 65536);
// convert it to hexadecimal
return num.ToString("x4");
}
public async Task<long> PickAsync(
ulong gid,
ITextChannel ch,
ulong uid,
string pass)
{
await _pickLock.WaitAsync();
long amount;
ulong[] ids;
await using (var uow = _db.GetDbContext())
{
// this method will sum all plants with that password,
// remove them, and get messageids of the removed plants
pass = pass?.Trim().TrimTo(10, true)?.ToUpperInvariant();
// gets all plants in this channel with the same password
var entries = await uow.GetTable<PlantedCurrency>()
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
.DeleteWithOutputAsync();
if (!entries.Any())
return 0;
amount = entries.Sum(x => x.Amount);
ids = entries.Select(x => x.MessageId).ToArray();
}
if (amount > 0)
await _cs.AddAsync(uid, amount, new("currency", "collect"));
try
{
long amount;
ulong[] ids;
await using (var uow = _db.GetDbContext())
{
// this method will sum all plants with that password,
// remove them, and get messageids of the removed plants
pass = pass?.Trim().TrimTo(10, true).ToUpperInvariant();
// gets all plants in this channel with the same password
var entries = uow.Set<PlantedCurrency>()
.AsQueryable()
.Where(x => x.ChannelId == ch.Id && pass == x.Password)
.ToList();
// sum how much currency that is, and get all of the message ids (so that i can delete them)
amount = entries.Sum(x => x.Amount);
ids = entries.Select(x => x.MessageId).ToArray();
// remove them from the database
uow.RemoveRange(entries);
if (amount > 0)
// give the picked currency to the user
await _cs.AddAsync(uid, amount, new("currency", "collect"));
await uow.SaveChangesAsync();
}
try
{
// delete all of the plant messages which have just been picked
_ = ch.DeleteMessagesAsync(ids);
}
catch { }
// return the amount of currency the user picked
return amount;
}
finally
{
_pickLock.Release();
_ = ch.DeleteMessagesAsync(ids);
}
catch { }
// return the amount of currency the user picked
return amount;
}
public async Task<ulong?> SendPlantMessageAsync(
@@ -333,7 +341,7 @@ public class PlantPickService : INService, IExecNoCommand
public async Task<bool> PlantAsync(
ulong gid,
IMessageChannel ch,
ITextChannel ch,
ulong uid,
string user,
long amount,
@@ -359,6 +367,7 @@ public class PlantPickService : INService, IExecNoCommand
// if it doesn't fail, put the plant in the database for other people to pick
await AddPlantToDatabase(gid, ch.Id, uid, msgId.Value, amount, pass);
return true;
}
@@ -366,25 +375,41 @@ public class PlantPickService : INService, IExecNoCommand
return false;
}
private async Task AddPlantToDatabase(
private async Task<(long totalAmount, ulong[] toDelete)> AddPlantToDatabase(
ulong gid,
ulong cid,
ulong uid,
ulong mid,
long amount,
string pass)
string pass,
bool auto = false)
{
await using var uow = _db.GetDbContext();
uow.Set<PlantedCurrency>()
.Add(new()
{
Amount = amount,
GuildId = gid,
ChannelId = cid,
Password = pass,
UserId = uid,
MessageId = mid
});
await uow.SaveChangesAsync();
PlantedCurrency[] deleted = [];
if (!string.IsNullOrWhiteSpace(pass) && auto)
{
deleted = await uow.GetTable<PlantedCurrency>()
.Where(x => x.GuildId == gid
&& x.ChannelId == cid
&& x.Password != null
&& x.Password.Length == pass.Length)
.DeleteWithOutputAsync();
}
var totalDeletedAmount = deleted.Length == 0 ? 0 : deleted.Sum(x => x.Amount);
await uow.GetTable<PlantedCurrency>()
.InsertAsync(() => new()
{
Amount = totalDeletedAmount + amount,
GuildId = gid,
ChannelId = cid,
Password = pass,
UserId = uid,
MessageId = mid,
});
return (totalDeletedAmount + amount, deleted.Select(x => x.MessageId).ToArray());
}
}

View File

@@ -10,7 +10,7 @@ namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
[Group]
public partial class ShopCommands : GamblingSubmodule<IShopService>
public partial class ShopCommands : GamblingModule<IShopService>
{
public enum List
{
@@ -56,8 +56,8 @@ public partial class Gambling
.Page((items, curPage) =>
{
if (!items.Any())
return _sender.CreateEmbed().WithErrorColor().WithDescription(GetText(strs.shop_none));
var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.shop));
return CreateEmbed().WithErrorColor().WithDescription(GetText(strs.shop_none));
var embed = CreateEmbed().WithOkColor().WithTitle(GetText(strs.shop));
for (var i = 0; i < items.Count; i++)
{
@@ -188,7 +188,7 @@ public partial class Gambling
{
await Response()
.User(ctx.User)
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.shop_purchase(ctx.Guild.Name)))
.AddField(GetText(strs.item), item.Text)
@@ -254,7 +254,7 @@ public partial class Gambling
.Replace("%you.name%", buyer.GlobalName ?? buyer.Username)
.Replace("%you.nick%", buyer.DisplayName);
var eb = _sender.CreateEmbed()
var eb = CreateEmbed()
.WithPendingColor()
.WithTitle("Executing shop command")
.WithDescription(cmd);
@@ -541,7 +541,7 @@ public partial class Gambling
public EmbedBuilder EntryToEmbed(ShopEntry entry)
{
var embed = _sender.CreateEmbed().WithOkColor();
var embed = CreateEmbed().WithOkColor();
if (entry.Type == ShopEntryType.Role)
{

View File

@@ -21,7 +21,7 @@ public enum GamblingError
public partial class Gambling
{
[Group]
public partial class SlotCommands : GamblingSubmodule<IGamblingService>
public partial class SlotCommands : GamblingModule<IGamblingService>
{
private readonly IImageCache _images;
private readonly FontProvider _fonts;
@@ -65,11 +65,11 @@ public partial class Gambling
await using var imgStream = await image.ToStreamAsync();
var eb = _sender.CreateEmbed()
.WithAuthor(ctx.User)
.WithDescription(Format.Bold(text))
.WithImageUrl($"attachment://result.png")
.WithOkColor();
var eb = CreateEmbed()
.WithAuthor(ctx.User)
.WithDescription(Format.Bold(text))
.WithImageUrl($"attachment://result.png")
.WithOkColor();
var bb = new ButtonBuilder(emote: Emoji.Parse("🔁"), customId: "slot:again", label: "Pull Again");
var inter = _inter.Create(ctx.User.Id,
@@ -168,7 +168,8 @@ public partial class Gambling
await using (var uow = _db.GetDbContext())
{
ownedAmount = uow.Set<DiscordUser>()
.FirstOrDefault(x => x.UserId == ctx.User.Id)?.CurrencyAmount
.FirstOrDefault(x => x.UserId == ctx.User.Id)
?.CurrencyAmount
?? 0;
}

View File

@@ -0,0 +1,55 @@
#nullable disable
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using NadekoBot.Db.Models;
namespace NadekoBot.Modules.Gambling.Services;
public sealed class UserBetStatsService : INService
{
private const long RESET_MIN_PRICE = 1000;
private const decimal RESET_TOTAL_MULTIPLIER = 0.002m;
private readonly DbService _db;
private readonly ICurrencyService _cs;
public UserBetStatsService(DbService db, ICurrencyService cs)
{
_db = db;
_cs = cs;
}
public async Task<long> GetResetStatsPriceAsync(ulong userId, GamblingGame? game)
{
await using var ctx = _db.GetDbContext();
var totalBet = await ctx.GetTable<UserBetStats>()
.Where(x => x.UserId == userId && (game == null || x.Game == game))
.SumAsyncLinqToDB(x => x.TotalBet);
return Math.Max(RESET_MIN_PRICE, (long)Math.Ceiling(totalBet * RESET_TOTAL_MULTIPLIER));
}
public async Task<bool> ResetStatsAsync(ulong userId, GamblingGame? game)
{
var price = await GetResetStatsPriceAsync(userId, game);
if (!await _cs.RemoveAsync(userId, price, new("betstats", "reset")))
{
return false;
}
await using var ctx = _db.GetDbContext();
await ctx.GetTable<UserBetStats>()
.DeleteAsync(x => x.UserId == userId && (game == null || x.Game == game));
return true;
}
public async Task ResetGamblingStatsAsync()
{
await using var ctx = _db.GetDbContext();
await ctx.GetTable<GamblingStats>()
.DeleteAsync();
}
}

View File

@@ -10,7 +10,7 @@ namespace NadekoBot.Modules.Gambling;
public partial class Gambling
{
[Group]
public partial class WaifuClaimCommands : GamblingSubmodule<WaifuService>
public partial class WaifuClaimCommands : GamblingModule<WaifuService>
{
public WaifuClaimCommands(GamblingConfigService gamblingConfService)
: base(gamblingConfService)
@@ -21,7 +21,7 @@ public partial class Gambling
public async Task WaifuReset()
{
var price = _service.GetResetPrice(ctx.User);
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithTitle(GetText(strs.waifu_reset_confirm))
.WithDescription(GetText(strs.waifu_reset_price(Format.Bold(N(price)))));
@@ -37,6 +37,45 @@ public partial class Gambling
await Response().Error(strs.waifu_reset_fail).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task WaifuClaims()
{
await Response()
.Paginated()
.PageItems(async (page) => await _service.GetClaimsAsync(ctx.User.Id, page))
.Page((items, page) =>
{
var eb = CreateEmbed()
.WithOkColor()
.WithTitle("Waifus");
if (items.Count == 0)
{
eb
.WithPendingColor()
.WithDescription(GetText(strs.empty_page));
return eb;
}
for (var i = 0; i < items.Count; i++)
{
var item = items[i];
eb.AddField($"`#{(page * 9) + 1 + i}` {N(item.Price)}",
$"""
{item.Username}
||{item.UserId}||
""",
true
);
}
return eb;
})
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task WaifuClaim(long amount, [Leftover] IUser target)
@@ -74,7 +113,7 @@ public partial class Gambling
Format.Bold(ctx.User.ToString()),
Format.Bold(target.ToString()),
N(amount)));
if (w.Affinity?.UserId == ctx.User.Id)
msg += "\n" + GetText(strs.waifu_fulfilled(target, N(w.Price)));
else
@@ -144,7 +183,7 @@ public partial class Gambling
if (targetId == ctx.User.Id)
return;
var (w, result, amount, remaining) = await _service.DivorceWaifuAsync(ctx.User, targetId);
var (w, result, amount) = await _service.DivorceWaifuAsync(ctx.User, targetId);
if (result == DivorceResult.SucessWithPenalty)
{
@@ -157,14 +196,6 @@ public partial class Gambling
await Response().Confirm(strs.waifu_divorced_notlike(N(amount))).SendAsync();
else if (result == DivorceResult.NotYourWife)
await Response().Error(strs.waifu_not_yours).SendAsync();
else if (remaining is { } rem)
{
await Response()
.Error(strs.waifu_recent_divorce(
Format.Bold(((int)rem.TotalHours).ToString()),
Format.Bold(rem.Minutes.ToString())))
.SendAsync();
}
}
[Cmd]
@@ -235,7 +266,7 @@ public partial class Gambling
return;
}
var embed = _sender.CreateEmbed().WithTitle(GetText(strs.waifus_top_waifus)).WithOkColor();
var embed = CreateEmbed().WithTitle(GetText(strs.waifus_top_waifus)).WithOkColor();
var i = 0;
foreach (var w in waifus)
@@ -319,7 +350,7 @@ public partial class Gambling
if (string.IsNullOrWhiteSpace(fansStr))
fansStr = "-";
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.waifu)
+ " "
@@ -362,7 +393,7 @@ public partial class Gambling
.CurrentPage(page)
.Page((items, _) =>
{
var embed = _sender.CreateEmbed().WithTitle(GetText(strs.waifu_gift_shop)).WithOkColor();
var embed = CreateEmbed().WithTitle(GetText(strs.waifu_gift_shop)).WithOkColor();
items
.ToList()

View File

@@ -318,25 +318,20 @@ public class WaifuService : INService, IReadyExecutor
private static TypedKey<long> GetAffinityKey(ulong userId)
=> new($"waifu:affinity:{userId}");
public async Task<(WaifuInfo, DivorceResult, long, TimeSpan?)> DivorceWaifuAsync(IUser user, ulong targetId)
public async Task<(WaifuInfo, DivorceResult, long)> DivorceWaifuAsync(IUser user, ulong targetId)
{
DivorceResult result;
TimeSpan? remaining = null;
long amount = 0;
WaifuInfo w;
await using (var uow = _db.GetDbContext())
{
w = uow.Set<WaifuInfo>().ByWaifuUserId(targetId);
if (w?.Claimer is null || w.Claimer.UserId != user.Id)
{
result = DivorceResult.NotYourWife;
}
else
{
remaining = await _cache.GetRatelimitAsync(GetDivorceKey(user.Id), 6.Hours());
if (remaining is TimeSpan rem)
{
result = DivorceResult.Cooldown;
return (w, result, amount, rem);
}
amount = w.Price / 2;
@@ -369,7 +364,7 @@ public class WaifuService : INService, IReadyExecutor
await uow.SaveChangesAsync();
}
return (w, result, amount, remaining);
return (w, result, amount);
}
public async Task<bool> GiftWaifuAsync(
@@ -603,7 +598,7 @@ public class WaifuService : INService, IReadyExecutor
.Where(wi => wi.ClaimerId == waifuId)
.Select(wi => wi.WaifuId)
.Contains(x.Id))
.Select(x => $"{x.Username}#{x.Discriminator}")
.Select(x => x.Username)
.ToListAsyncEF();
}
@@ -615,7 +610,7 @@ public class WaifuService : INService, IReadyExecutor
.Where(wi => wi.AffinityId == waifuId)
.Select(wi => wi.WaifuId)
.Contains(x.Id))
.Select(x => $"{x.Username}#{x.Discriminator}")
.Select(x => x.Username)
.ToListAsyncEF();
}
@@ -630,4 +625,38 @@ public class WaifuService : INService, IReadyExecutor
.FirstOrDefault())
.ToListAsyncEF();
}
public async Task<IReadOnlyCollection<WaifuClaimsResult>> GetClaimsAsync(ulong userId, int page)
{
await using var ctx = _db.GetDbContext();
var wid = ctx.GetTable<DiscordUser>()
.Where(x => x.UserId == userId)
.Select(x => x.Id)
.FirstOrDefault();
if (wid == 0)
return [];
return await ctx.GetTable<WaifuInfo>()
.Where(x => x.ClaimerId == wid)
.LeftJoin(ctx.GetTable<DiscordUser>(),
(wi, du) => wi.WaifuId == du.Id,
(wi, du) => new WaifuClaimsResult(
du.Username,
du.UserId,
wi.Price
))
.OrderByDescending(x => x.Price)
.Skip(page * 9)
.Take(9)
.ToListAsyncLinqToDB();
}
}
public sealed class WaifuClaimsResult(string username, ulong userId, long price)
{
public string Username { get; } = username;
public ulong UserId { get; } = userId;
public long Price { get; } = price;
}

View File

@@ -42,15 +42,12 @@ public static class WaifuExtensions
{
Affinity = x.Affinity == null
? null
: x.Affinity.Username
+ (x.Affinity.Discriminator != "0000" ? "#" + x.Affinity.Discriminator : ""),
: x.Affinity.Username,
ClaimerName =
x.Claimer == null
? null
: x.Claimer.Username
+ (x.Claimer.Discriminator != "0000" ? "#" + x.Claimer.Discriminator : ""),
WaifuName = x.Waifu.Username
+ (x.Waifu.Discriminator != "0000" ? "#" + x.Waifu.Discriminator : ""),
: x.Claimer.Username,
WaifuName = x.Waifu.Username,
Price = x.Price
})
.ToListAsyncEF();
@@ -62,7 +59,7 @@ public static class WaifuExtensions
public static ulong GetWaifuUserId(this DbSet<WaifuInfo> waifus, ulong ownerId, string name)
=> waifus.AsQueryable()
.AsNoTracking()
.Where(x => x.Claimer.UserId == ownerId && x.Waifu.Username + "#" + x.Waifu.Discriminator == name)
.Where(x => x.Claimer.UserId == ownerId && x.Waifu.Username == name)
.Select(x => x.Waifu.UserId)
.FirstOrDefault();
@@ -100,7 +97,7 @@ public static class WaifuExtensions
ctx.Set<DiscordUser>()
.AsQueryable()
.Where(u => u.UserId == userId)
.Select(u => u.Username + "#" + u.Discriminator)
.Select(u => u.Username)
.FirstOrDefault(),
AffinityCount =
ctx.Set<WaifuUpdate>()
@@ -112,14 +109,14 @@ public static class WaifuExtensions
ctx.Set<DiscordUser>()
.AsQueryable()
.Where(u => u.Id == w.AffinityId)
.Select(u => u.Username + "#" + u.Discriminator)
.Select(u => u.Username)
.FirstOrDefault(),
ClaimCount = ctx.Set<WaifuInfo>().AsQueryable().Count(x => x.ClaimerId == w.WaifuId),
ClaimerName =
ctx.Set<DiscordUser>()
.AsQueryable()
.Where(u => u.Id == w.ClaimerId)
.Select(u => u.Username + "#" + u.Discriminator)
.Select(u => u.Username)
.FirstOrDefault(),
DivorceCount =
ctx.Set<WaifuUpdate>()

View File

@@ -13,5 +13,10 @@ public interface IGamblingService
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
Task<FlipResult[]> FlipAsync(int count);
Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick);
Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? maybeGuessValue, byte? maybeGuessColor);
Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(
ulong userId,
long amount,
byte? maybeGuessValue,
byte? maybeGuessColor);
}

View File

@@ -1,4 +1,6 @@
#nullable disable
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using NadekoBot.Modules.Gambling.Betdraw;
using NadekoBot.Modules.Gambling.Rps;
using NadekoBot.Modules.Gambling.Services;
@@ -8,15 +10,15 @@ namespace NadekoBot.Modules.Gambling;
public sealed class NewGamblingService : IGamblingService, INService
{
private readonly GamblingConfigService _bcs;
private readonly GamblingConfigService _gcs;
private readonly ICurrencyService _cs;
public NewGamblingService(GamblingConfigService bcs, ICurrencyService cs)
public NewGamblingService(GamblingConfigService gcs, ICurrencyService cs)
{
_bcs = bcs;
_gcs = gcs;
_cs = cs;
}
public async Task<OneOf<LuLaResult, GamblingError>> LulaAsync(ulong userId, long amount)
{
ArgumentOutOfRangeException.ThrowIfNegative(amount);
@@ -31,13 +33,13 @@ public sealed class NewGamblingService : IGamblingService, INService
}
}
var game = new LulaGame(_bcs.Data.LuckyLadder.Multipliers);
var game = new LulaGame(_gcs.Data.LuckyLadder.Multipliers);
var result = game.Spin(amount);
var won = (long)result.Won;
if (won > 0)
{
await _cs.AddAsync(userId, won, new("lula", "win"));
await _cs.AddAsync(userId, won, new("lula", result.Multiplier >= 1 ? "win" : "lose"));
}
return result;
@@ -57,9 +59,9 @@ public sealed class NewGamblingService : IGamblingService, INService
}
}
var game = new BetrollGame(_bcs.Data.BetRoll.Pairs
.Select(x => (x.WhenAbove, (decimal)x.MultiplyBy))
.ToList());
var game = new BetrollGame(_gcs.Data.BetRoll.Pairs
.Select(x => (x.WhenAbove, (decimal)x.MultiplyBy))
.ToList());
var result = game.Roll(amount);
@@ -88,19 +90,23 @@ public sealed class NewGamblingService : IGamblingService, INService
}
}
var game = new BetflipGame(_bcs.Data.BetFlip.Multiplier);
var game = new BetflipGame(_gcs.Data.BetFlip.Multiplier);
var result = game.Flip(guess, amount);
var won = (long)result.Won;
if (won > 0)
{
await _cs.AddAsync(userId, won, new("betflip", "win"));
}
return result;
}
public async Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? maybeGuessValue, byte? maybeGuessColor)
public async Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(
ulong userId,
long amount,
byte? maybeGuessValue,
byte? maybeGuessColor)
{
ArgumentOutOfRangeException.ThrowIfNegative(amount);
@@ -109,7 +115,7 @@ public sealed class NewGamblingService : IGamblingService, INService
if (maybeGuessColor > 1)
throw new ArgumentOutOfRangeException(nameof(maybeGuessColor));
if (maybeGuessValue > 1)
throw new ArgumentOutOfRangeException(nameof(maybeGuessValue));
@@ -125,13 +131,13 @@ public sealed class NewGamblingService : IGamblingService, INService
var game = new BetdrawGame();
var result = game.Draw((BetdrawValueGuess?)maybeGuessValue, (BetdrawColorGuess?)maybeGuessColor, amount);
var won = (long)result.Won;
if (won > 0)
{
await _cs.AddAsync(userId, won, new("betdraw", "win"));
}
return result;
}
@@ -155,7 +161,7 @@ public sealed class NewGamblingService : IGamblingService, INService
var won = (long)result.Won;
if (won > 0)
{
await _cs.AddAsync(userId, won, new("slot", "won"));
await _cs.AddAsync(userId, won, new("slot", "win"));
}
return result;
@@ -178,7 +184,7 @@ public sealed class NewGamblingService : IGamblingService, INService
return Task.FromResult(results);
}
//
//
// private readonly ConcurrentDictionary<ulong, Deck> _decks = new ConcurrentDictionary<ulong, Deck>();
@@ -236,7 +242,7 @@ public sealed class NewGamblingService : IGamblingService, INService
{
ArgumentOutOfRangeException.ThrowIfNegative(amount);
ArgumentOutOfRangeException.ThrowIfGreaterThan(pick, 2);
if (amount > 0)
{
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("rps", "bet"));
@@ -249,7 +255,7 @@ public sealed class NewGamblingService : IGamblingService, INService
var rps = new RpsGame();
var result = rps.Play((RpsPick)pick, amount);
var won = (long)result.Won;
if (won > 0)
{
@@ -265,4 +271,46 @@ public sealed class NewGamblingService : IGamblingService, INService
return result;
}
}
public sealed class RakebackService: INService
{
private readonly DbService _db;
private readonly ICurrencyService _cs;
public RakebackService(DbService db, ICurrencyService cs)
{
_db = db;
_cs = cs;
}
public async Task<long> GetRakebackAsync(ulong userId)
{
await using var uow = _db.GetDbContext();
var rb = uow.GetTable<Rakeback>()
.Where(x => x.UserId == userId)
.Select(x => x.Amount)
.FirstOrDefault();
return (long)rb;
}
public async Task<long> ClaimRakebackAsync(ulong userId)
{
await using var uow = _db.GetDbContext();
var rbs = await uow.GetTable<Rakeback>()
.Where(x => x.UserId == userId)
.DeleteWithOutputAsync((x) => x.Amount);
if(rbs.Length == 0)
return 0;
var rb = (long)rbs[0];
await _cs.AddAsync(userId, rb, new("rakeback", "claim"));
return rb;
}
}

View File

@@ -67,7 +67,7 @@ public partial class Games
private Task Game_OnStarted(AcrophobiaGame game)
{
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.acrophobia))
.WithDescription(
@@ -92,7 +92,7 @@ public partial class Games
if (submissions.Length == 1)
{
await Response().Embed(_sender.CreateEmbed()
await Response().Embed(CreateEmbed()
.WithOkColor()
.WithDescription(GetText(
strs.acro_winner_only(
@@ -103,7 +103,7 @@ public partial class Games
var i = 0;
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.acrophobia) + " - " + GetText(strs.submissions_closed))
.WithDescription(GetText(strs.acro_nym_was(
@@ -127,7 +127,7 @@ public partial class Games
var table = votes.OrderByDescending(v => v.Value);
var winner = table.First();
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.acrophobia))
.WithDescription(GetText(strs.acro_winner(Format.Bold(winner.Key.UserName),

View File

@@ -38,7 +38,7 @@ public partial class Games : NadekoModule<GamesService>
return;
var res = _service.GetEightballResponse(ctx.User.Id, question);
await Response().Embed(_sender.CreateEmbed()
await Response().Embed(CreateEmbed()
.WithOkColor()
.WithDescription(ctx.User.ToString())
.AddField("❓ " + GetText(strs.question), question)

View File

@@ -53,7 +53,7 @@ public partial class Games
var hint = GetText(strs.nc_hint(prefix, _service.GetWidth(), _service.GetHeight()));
await Response()
.File(stream, "ncanvas.png")
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithOkColor()
#if GLOBAL_NADEKO
.WithDescription("https://dashy.nadeko.bot/ncanvas")
@@ -164,20 +164,36 @@ public partial class Games
Culture,
_gcs.Data.Currency.Sign))));
if (!await PromptUserConfirmAsync(_sender.CreateEmbed()
if (!await PromptUserConfirmAsync(CreateEmbed()
.WithPendingColor()
.WithDescription(prompt)))
{
return;
}
await _service.SetPixel(position, clr.PackedValue, text, ctx.User.Id, pixel.Price);
var result = await _service.SetPixel(position, clr.PackedValue, text, ctx.User.Id, pixel.Price);
if (result == SetPixelResult.NotEnoughMoney)
{
await Response().Error(strs.not_enough(_gcs.Data.Currency.Sign)).SendAsync();
return;
}
else if (result == SetPixelResult.InsufficientPayment)
{
await Response().Error(strs.nc_insuff_payment).SendAsync();
return;
}
else if (result == SetPixelResult.InvalidInput)
{
await Response().Error(strs.invalid_input).SendAsync();
return;
}
using var img = await GetZoomImage(position);
await using var stream = await img.ToStreamAsync();
await Response()
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithOkColor()
.WithDescription(GetText(strs.nc_pixel_set(Format.Code(position.ToString()))))
.WithImageUrl($"attachment://zoom_{position}.png"))
@@ -215,7 +231,7 @@ public partial class Games
var pos = new kwum(pixel.Position);
await Response()
.File(stream, $"{pixel.Position}.png")
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithOkColor()
.WithDescription(string.IsNullOrWhiteSpace(pixel.Text) ? string.Empty : pixel.Text)
.WithTitle(GetText(strs.nc_pixel(pos)))
@@ -247,7 +263,7 @@ public partial class Games
return;
}
if (!await PromptUserConfirmAsync(_sender.CreateEmbed()
if (!await PromptUserConfirmAsync(CreateEmbed()
.WithDescription(
"This will reset the canvas to the specified image. All prices, text and colors will be reset.\n\n"
+ "Are you sure you want to continue?")))
@@ -277,7 +293,7 @@ public partial class Games
{
await _service.ResetAsync();
if (!await PromptUserConfirmAsync(_sender.CreateEmbed()
if (!await PromptUserConfirmAsync(CreateEmbed()
.WithDescription(
"This will delete all pixels and reset the canvas.\n\n"
+ "Are you sure you want to continue?")))

View File

@@ -29,7 +29,7 @@ public partial class Games
if (!await nunchi.Join(ctx.User.Id, ctx.User.ToString()))
return;
await Response().Error(strs.nunchi_joined(nunchi.ParticipantCount)).SendAsync();
await Response().Confirm(strs.nunchi_joined(nunchi.ParticipantCount)).SendAsync();
return;
}

View File

@@ -94,7 +94,7 @@ public partial class Games
if (removed is null)
return;
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithTitle($"Removed typing article #{index + 1}")
.WithDescription(removed.Text.TrimTo(50))
.WithOkColor();

View File

@@ -160,7 +160,7 @@ public partial class Games
{
try
{
questionEmbed = _sender.CreateEmbed()
questionEmbed = CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.trivia_game))
.AddField(GetText(strs.category), question.Category)
@@ -189,7 +189,7 @@ public partial class Games
{
try
{
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithErrorColor()
.WithTitle(GetText(strs.trivia_game))
.WithDescription(GetText(strs.trivia_times_up(Format.Bold(question.Answer))));
@@ -221,7 +221,7 @@ public partial class Games
{
try
{
await Response().Embed(_sender.CreateEmbed()
await Response().Embed(CreateEmbed()
.WithOkColor()
.WithAuthor(GetText(strs.trivia_ended))
.WithTitle(GetText(strs.leaderboard))
@@ -247,7 +247,7 @@ public partial class Games
{
try
{
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.trivia_game))
.WithDescription(GetText(strs.trivia_win(user.Name,

View File

@@ -114,7 +114,7 @@ public sealed partial class Help : NadekoModule<HelpService>
.AddFooter(false)
.Page((items, _) =>
{
var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.list_of_modules));
var embed = CreateEmbed().WithOkColor().WithTitle(GetText(strs.list_of_modules));
if (!items.Any())
{
@@ -315,7 +315,7 @@ public sealed partial class Help : NadekoModule<HelpService>
.WithPlaceholder("Select a submodule to see detailed commands");
var groups = cmdsWithGroup.ToArray();
var embed = _sender.CreateEmbed().WithOkColor();
var embed = CreateEmbed().WithOkColor();
foreach (var g in groups)
{
sb.AddOption(g.Key, g.Key);
@@ -383,7 +383,7 @@ public sealed partial class Help : NadekoModule<HelpService>
.Interaction(inter)
.Page((items, _) =>
{
var eb = _sender.CreateEmbed()
var eb = CreateEmbed()
.WithTitle(GetText(strs.cmd_group_commands(group.Name)))
.WithOkColor();
@@ -520,7 +520,7 @@ public sealed partial class Help : NadekoModule<HelpService>
[OnlyPublicBot]
public async Task Donate()
{
var eb = _sender.CreateEmbed()
var eb = CreateEmbed()
.WithOkColor()
.WithTitle("Thank you for considering to donate to the NadekoBot project!");

View File

@@ -40,7 +40,7 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
.PageSize(10)
.Page((items, _) =>
{
return _sender.CreateEmbed()
return CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.list_of_unloaded))
.WithDescription(items.Join('\n'));
@@ -81,7 +81,7 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
}
await Response()
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.loaded_medusae))
.WithDescription(loaded.Select(x => x.Name)
@@ -136,7 +136,7 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
.Paginated()
.Items(output)
.PageSize(10)
.Page((items, _) => _sender.CreateEmbed()
.Page((items, _) => CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.list_of_medusae))
.WithDescription(items.Join('\n')))
@@ -168,7 +168,7 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
: $"{x.Prefix} {x.Name}"))
.Join("\n");
var eb = _sender.CreateEmbed()
var eb = CreateEmbed()
.WithOkColor()
.WithAuthor(GetText(strs.medusa_info))
.WithTitle(found.Name)
@@ -201,7 +201,7 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
.CurrentPage(0)
.Page((items, _) =>
{
var eb = _sender.CreateEmbed()
var eb = CreateEmbed()
.WithOkColor();
foreach (var medusa in items)
@@ -224,7 +224,7 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
[OwnerOnly]
public async Task MedusaSearch()
{
var eb = _sender.CreateEmbed()
var eb = CreateEmbed()
.WithTitle(GetText(strs.list_of_medusae))
.WithOkColor();

View File

@@ -109,7 +109,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
try
{
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithOkColor()
.WithAuthor(GetText(strs.queued_track) + " #" + (index + 1), MUSIC_ICON_URL)
.WithDescription($"{trackInfo.PrettyName()}\n{GetText(strs.queue)} ")
@@ -314,7 +314,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
if (!string.IsNullOrWhiteSpace(add))
desc = add + "\n" + desc;
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithAuthor(
GetText(strs.player_queue(curPage + 1, (tracks.Count / LQ_ITEMS_PER_PAGE) + 1)),
MUSIC_ICON_URL)
@@ -352,7 +352,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
}
var embeds = videos.Select((x, i) => _sender.CreateEmbed()
var embeds = videos.Select((x, i) => CreateEmbed()
.WithOkColor()
.WithThumbnailUrl(x.Thumbnail)
.WithDescription($"`{i + 1}.` {Format.Bold(x.Title)}\n\t{x.Url}"))
@@ -424,7 +424,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
return;
}
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithAuthor(GetText(strs.removed_track) + " #" + index, MUSIC_ICON_URL)
.WithDescription(track.PrettyName())
.WithFooter(track.PrettyInfo())
@@ -592,7 +592,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
return;
}
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithTitle(track.Title.TrimTo(65))
.WithAuthor(GetText(strs.track_moved), MUSIC_ICON_URL)
.AddField(GetText(strs.from_position), $"#{from + 1}", true)
@@ -651,7 +651,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
if (currentTrack is null)
return;
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithOkColor()
.WithAuthor(GetText(strs.now_playing), MUSIC_ICON_URL)
.WithDescription(currentTrack.PrettyName())
@@ -752,4 +752,20 @@ public sealed partial class Music : NadekoModule<IMusicService>
else
await Response().Error(strs.no_player).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task WrongSong()
{
var removed = await _service.RemoveLastQueuedTrackAsync(ctx.Guild.Id);
if (removed is null)
{
await Response().Error(strs.no_last_queued_found).SendAsync();
}
else
{
await Response().Confirm(strs.wrongsong_success(removed.Title.TrimTo(30))).SendAsync();
}
}
}

View File

@@ -49,7 +49,7 @@ public sealed partial class Music
playlists = uow.Set<MusicPlaylist>().GetPlaylistsOnPage(num);
}
var embed = _sender.CreateEmbed()
var embed = CreateEmbed()
.WithAuthor(GetText(strs.playlists_page(num)), MUSIC_ICON_URL)
.WithDescription(string.Join("\n",
playlists.Select(r => GetText(strs.playlists(r.Id, r.Name, r.Author, r.Songs.Count)))))
@@ -113,7 +113,7 @@ public sealed partial class Music
var str = string.Join("\n",
items
.Select(x => $"`{++i}.` [{x.Title.TrimTo(45)}]({x.Query}) `{x.Provider}`"));
return _sender.CreateEmbed().WithTitle($"\"{mpl.Name}\" by {mpl.Author}")
return CreateEmbed().WithTitle($"\"{mpl.Name}\" by {mpl.Author}")
.WithOkColor()
.WithDescription(str);
})
@@ -155,7 +155,7 @@ public sealed partial class Music
}
await Response()
.Embed(_sender.CreateEmbed()
.Embed(CreateEmbed()
.WithOkColor()
.WithTitle(GetText(strs.playlist_saved))
.AddField(GetText(strs.name), name)

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