Compare commits

...

52 Commits

Author SHA1 Message Date
Kwoth
e49e3eec69 Version upped to 4.3.10, CHANGELOG.md updated 2022-11-10 22:42:34 +01:00
Kwoth
3992ae392b Fixed nullref in xploop 2022-11-10 22:32:27 +01:00
Kwoth
8f0c5fab47 Fixed nullref in cmdcd 2022-11-08 07:51:29 +01:00
Kwoth
780a260b88 If cleverbot is disabled through permissions, let other things execute 2022-11-08 07:51:20 +01:00
Kwoth
25692b9585 Fixed .feedadd for real 2022-11-04 17:21:05 +01:00
Kwoth
ed3ce52865 Fixed .feedadd, closes #388 2022-11-04 05:08:22 +01:00
Kwoth
f5f0f1e250 Fixed .prune @Target not working bug 2022-10-25 02:07:05 +02:00
Kwoth
9d9e61fdfb Fixed command cooldown calculation. Closes #387 2022-10-25 01:57:58 +02:00
Kwoth
e68e948a80 Aliases now support %target% placeholder. For example 'alias .bft .bf %target% t' 2022-10-22 23:33:42 +02:00
Kwoth
cb98f4aa15 Added .exprtoggleglobal / .extg which can be used to toggle usage of global expressions on the server 2022-10-21 21:31:59 +02:00
Kwoth
bfec0cbcbf OwnerId will be autofilled in creds.yml if it's missing at startup 2022-10-19 23:43:44 +02:00
Kwoth
3e1268f3bb You can now specify time+date (time is optional) in remind instead of relative time, in the format HH:mm dd.MM.YYYY 2022-10-18 23:31:34 +02:00
Kwoth
c28f458972 Updated help text for .antispam and .antiraid 2022-10-18 01:45:00 +02:00
Kwoth
27ac948463 Added .forwardtochannel which will forward messages to the current channel. It has lower priority than fwtoall 2022-10-17 22:20:48 +02:00
Kwoth
3f9a3c4c18 Merge branch 'hokutochen-v4-patch-12507' into 'v4'
upped medusa version and updated brew command

See merge request Kwoth/nadekobot!271
2022-10-17 11:11:37 +00:00
Hokuto Chen
9a5545a951 docs: upped medusa version and updated brew command 2022-10-17 11:11:37 +00:00
Kwoth
584193db18 Added .filterlist command 2022-10-16 22:57:36 +02:00
Kwoth
1a132fd234 Added a missing return 2022-10-15 13:17:30 +02:00
Kwoth
fd6a51ac82 Added appropriate error messages for every club related command. Removed obsolete error messages. 2022-10-15 13:13:31 +02:00
Kwoth
eb1fabb2b7 Prepared several more enums to clarify club related action results 2022-10-14 23:02:43 +02:00
Kwoth
d079e684bd Clubleave errors clarified. Moved enum results to their own files 2022-10-13 22:27:12 +02:00
Kwoth
bf817a1436 Medusa modules (sneks) should now inherit medusa description when listed in .mdls command 2022-10-12 23:29:48 +02:00
Kwoth
78f1624aaf .meload and .meunload are now case sensitive. Previously loaded medusae may need to be reloaded or data/medusae/medusa.yml may need to be edited manually 2022-10-12 20:59:04 +02:00
Kwoth
793a49fc64 Updated version to 4.3.9 2022-10-12 20:20:05 +02:00
Kwoth
8b6be656b3 Updated CHANGELOG.md 2022-10-12 20:19:33 +02:00
Kwoth
89a88304dc If version is not specified, Nadeko.Medusa will default to 5.0.0 to allow for full compatibility for from-source-selfhosters 2022-10-12 20:10:55 +02:00
Kwoth
a7fe9ae08f .betstats now looks uwu 2022-10-11 19:59:58 +02:00
Kwoth
0469705037 Clarified .feedadd errors 2022-10-11 19:14:16 +02:00
Kwoth
dc568fe0e2 Removed a duplicate string key 2022-10-11 18:29:53 +02:00
Kwoth
eb01bb6c08 Removed ambiguity in clubban and clubunban error messages 2022-10-11 18:21:45 +02:00
Kwoth
71a3539d0e Possible fix for Remind .timely button 2022-10-10 18:39:49 +02:00
Kwoth
c896a0cdb8 Added better error messages to .clubapply 2022-10-09 16:25:28 +02:00
Kwoth
8effe817ad Fixed a bug with postgresql and mysql migration folders as well as compilation warning related to it 2022-10-08 09:05:31 +02:00
Kwoth
eedf6998b6 Fixed a bug in cmdcd not modifying the database. Added total stats to .betstats 2022-10-07 11:01:26 +02:00
Kwoth
e6b7c31a72 Gambling Tracker will only track successful removes 2022-10-06 10:55:12 +02:00
Kwoth
2f77fd57b0 Improved .betstats formatting 2022-10-04 16:27:19 +02:00
Kwoth
fda3d92134 Added gambling stats migrations for postgres and mysql 2022-10-03 19:58:08 +02:00
Kwoth
fe6f28143b Updated changelog 2022-10-03 13:52:17 +02:00
Kwoth
df3909fc55 Added .betstats command, shows statistics for multiple gambling commands, .slotstats removed as it is obsolete 2022-10-03 13:49:52 +02:00
Kwoth
9ffa701742 Updated changelog.md 2022-10-02 15:45:28 +02:00
Kwoth
a4f7df8aee Merge branch 'cmdcd-and-cleanup' into 'v4'
Cmdcd and cleanup

See merge request Kwoth/nadekobot!269
2022-10-02 13:38:51 +00:00
Kwoth
15e6cff14a * Rewrote cmdcd service, prettified and paginated .cmdcds
* Cleaned up/improved some command handler code
* Fixed .yun when channel id has an underscore
2022-10-02 13:38:50 +00:00
Kwoth
e63ac07a52 Fixed typo 2022-10-01 08:54:11 +02:00
Kwoth
a294e3bf8f Fixed -c GlobalNadeko compilation 2022-10-01 08:52:29 +02:00
Kwoth
a5b2fac69c .curtrs may? run a little faster. Uses async, clarified GamblingConfig transactionLifetime field 2022-09-28 07:09:37 +02:00
Kwoth
a8e06a5ae4 Clarified .remind permission requirement 2022-09-26 14:20:26 +02:00
Kwoth
761bdd8610 Merge branch 'hokutochen-v4-patch-16984' into 'v4'
Added VPS hosting requirements

See merge request Kwoth/nadekobot!268
2022-09-24 09:50:20 +00:00
Hokuto Chen
b5904889b0 Added VPS hosting requirements 2022-09-24 09:50:20 +00:00
Kwoth
5b4517cf5c Updated changelog, removed the ability to bet 0 on betdraw 2022-09-23 23:01:11 +02:00
Kwoth
72158c0a2c Added -a / --after <messageid> option to .prune, converted the argument to proper options object 2022-09-17 13:51:03 +02:00
Kwoth
1ca6f6dc5c Added .autopublish command 2022-09-16 21:49:50 +02:00
Kwoth
005fd7b8c6 Possible fix for missing IGuildUser when using guild commands, closes #383 2022-09-15 17:02:53 +02:00
139 changed files with 25587 additions and 625 deletions

View File

@@ -104,7 +104,7 @@ publish-medusa-package:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_TAG
script:
- LAST_TAG=$(git describe --tags --abbrev=0)
- if [ $CI_COMMIT_TAG ];then MEDUSA_VERSION="$CI_COMMIT_TAG"; else MEDUSA_VERSION="$LAST_TAG-$CI_COMMIT_SHA"; fi
- if [ $CI_COMMIT_TAG ];then MEDUSA_VERSION="$CI_COMMIT_TAG"; else MEDUSA_VERSION="$LAST_TAG-$CI_COMMIT_SHORT_SHA"; fi
- cd src/Nadeko.Medusa/
- dotnet pack -c Release /p:Version=$MEDUSA_VERSION -o bin/Release/packed
- dotnet nuget push bin/Release/packed/ --source https://www.myget.org/F/nadeko/api/v2/package --api-key "$MYGET_API_KEY"

View File

@@ -2,7 +2,77 @@
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
## [4.3.7]
## [4.3.10] - 10.11.2022
### Added
- `.filterlist` / `.fl` command which lists link and invite filtering channels and status
- Added support for `%target%` placeholder in `.alias` command
- Added .forwardtochannel which will forward messages to the current channel. It has lower priority than fwtoall
- Added .exprtoggleglobal / .extg which can be used to toggle usage of global expressions on the server
### Changed
- .meload and .meunload are now case sensitive. Previously loaded medusae may need to be reloaded or data/medusae/medusa.yml may need to be edited manually
- Several club related command have their error messages improved
- Updated help text for .antispam and .antiraid
- You can now specify time and date (time is optional) in `.remind` command instead of relative time, in the format `HH:mm dd.MM.YYYY`
- OwnerId will be automatically added to `creds.yml` at bot startup if it's missing
### Fixed
- Fixed `.cmdcd` console error
- Fixed an error when currency is add per xp
- Fixed an issue preventing execution of expressions starting with @Bot when cleverbot is enabled on the server
- Fixed `.feedadd`
- Fixed `.prune @target` not working
- Medusa modules (sneks) should now inherit medusa description when listed in .mdls command
- Fixed command cooldown calculation
## [4.3.9] - 12.10.2022
### Added
- `.betstats` shows sum of all bets, payouts and the payout rate in %. Updates once an hour
### Changed
- `.betstats` looks way better (except on Mac)
- `.feedadd` errors clarified and separated in individual error messages for each issue.
- `.clubban` and `.clubunban` errors clarified and separated in individual error messages for each issue.
- `.clubapply` better error messages
### Fixed
- `.timely` 'Remind' button fixed in DMs
- `.cmdcd` database bugs fixed
- Fixed bugged mysql and postgresql migrations
- Fixed issues with lodaing medusae due to strict versioning
### Removed
- `.slotstats` Superseded by `.betstats`
## [4.3.8] - 02.10.2022
### Added
- Added `.autopublish` command which will automatically publish messages posted in the channel.
- Added `--after <messageid>` option to prune which will make prune only delete messages after the specified message id.
### Changed
- `.prune` options `--after` and `--safe` are now proper command options, and will show in .h help
- `.cmdcd` code mostly rewritten, slight QoL improvements.
- Clarified `.remind` permission requirements in help text
- `.cmdcds` looks a little better, and is paginated
### Fixed
- Fixed trivia bugs
- Fixed `.yun` not working with channels with underscore in the name
## [4.3.7] - 14.09.2022
### Added

View File

@@ -17,7 +17,7 @@ It is recommended that you use **Ubuntu 20.04**, as there have been nearly no pr
##### Compatible operating systems:
- Ubuntu: 16.04, 18.04, 20.04, 21.04, 21.10 22.04
- Ubuntu: 16.04, 18.04, 20.04, 21.04, 21.10, 22.04
- Mint: 19, 20
- Debian: 9, 10
- CentOS: 7
@@ -316,6 +316,26 @@ This method is similar to the one above, but requires one extra step, with the a
If you want Nadeko to play music for you 24/7 without having to hosting it on your PC and want to keep it cheap, reliable and convenient as possible, you can try Nadeko on Linux Digital Ocean Droplet using the link [DigitalOcean](http://m.do.co/c/46b4d3d44795/) (by using this link, you will get **$10 credit** and also support Nadeko)
To set up the VPS, please select the options below
```
These are the min requirements you must follow:
OS: Any between Ubuntu, Fedora, and Debian
Plan: Basic
CPU options: regular with SSD
1 GB / 1 CPU
25 GB SSD Disk
1000 GB transfer
Note: You can select the cheapest option with 512 MB /1 CPU but this has been a hit or miss.
Datacenter region: Choose one depending on where you are located.
Authentication: Password or SSH
(Select SSH if you know what you are doing, otherwise choose password)
```
**Setting up NadekoBot**
Assuming you have followed the link above to setup an account and a Droplet with a 64-bit operational system on Digital Ocean and got the `IP address and root password (in your e-mail)` to login, it's time to get started.

View File

@@ -7,7 +7,7 @@ Open Terminal (if you don't know how to, click on the magnifying glass on the to
###### Homebrew/wget
*Skip this step if you already have homebrew installed*
- Copy and paste this command, then press Enter:
- `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
- `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
- Install wget
- `brew install wget`

View File

@@ -155,7 +155,7 @@ This section will guide you through how to create a simple custom medusa. You ca
<ItemGroup>
<!-- Base medusa package. You MUST reference this in order to have a working medusa -->
<!-- Also, this package comes from MyGet, which requires you to have a NuGet.Config file next to your .csproj -->
<PackageReference Include="Nadeko.Medusa" Version="1.0.1">
<PackageReference Include="Nadeko.Medusa" Version="4.3.9">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
@@ -248,4 +248,4 @@ hello:
- Unload it
- `.meunload example_medusa`
- Congrats! You've just made your first medusa!
- Congrats! You've just made your first medusa!

View File

@@ -17,4 +17,7 @@
<PackageReference Include="YamlDotNet" Version="11.2.1" />
</ItemGroup>
<PropertyGroup Condition=" '$(Version)' == '' ">
<Version>5.0.0</Version>
</PropertyGroup>
</Project>

View File

@@ -116,10 +116,6 @@ public sealed class Bot
// cache
.AddCache(_creds);
// admin
#if GLOBAL_NADEKO
svcs.AddSingleton<ILogCommandService, DummyLogCommandService>();
#endif
svcs.AddHttpClient();
svcs.AddHttpClient("memelist")
@@ -317,10 +313,29 @@ public sealed class Bot
await _commandService.AddModulesAsync(typeof(Bot).Assembly, Services);
// await _interactionService.AddModulesAsync(typeof(Bot).Assembly, Services);
IsReady = true;
await EnsureBotOwnershipAsync();
_ = Task.Run(ExecuteReadySubscriptions);
Log.Information("Shard {ShardId} ready", Client.ShardId);
}
private async ValueTask EnsureBotOwnershipAsync()
{
try
{
if (_creds.OwnerIds.Count != 0)
return;
Log.Information("Initializing Owner Id...");
var info = await Client.GetApplicationInfoAsync();
_credsProvider.ModifyCredsFile(x => x.OwnerIds = new[] { info.Owner.Id });
}
catch (Exception ex)
{
Log.Warning("Getting application info failed: {ErrorMessage}", ex.Message);
}
}
private Task ExecuteReadySubscriptions()
{
var readyExecutors = Services.GetServices<IReadyExecutor>();

View File

@@ -12,7 +12,7 @@ namespace NadekoBot.Common.Configs;
public sealed partial class BotConfig : ICloneable<BotConfig>
{
[Comment(@"DO NOT CHANGE")]
public int Version { get; set; } = 4;
public int Version { get; set; } = 5;
[Comment(@"Most commands, when executed, have a small colored line
next to the response. The color depends whether the command
@@ -39,6 +39,10 @@ Allowed values: Simple, Normal, None")]
@"Do you want the message to be forwarded only to the first owner specified in the list of owners (in creds.yml),
or all owners? (this might cause the bot to lag if there's a lot of owners specified)")]
public bool ForwardToAllOwners { get; set; }
[Comment(@"Any messages sent by users in Bot's DM to be forwarded to the specified channel.
This option will only work when ForwardToAllOwners is set to false")]
public ulong? ForwardToChannel { get; set; }
[Comment(@"When a user DMs the bot with a message which is not a command
they will receive this message. Leave empty for no response. The string which will be sent whenever someone DMs the bot.

View File

@@ -186,7 +186,6 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
return MedusaLoadResult.AlreadyLoaded;
var safeName = Uri.EscapeDataString(name);
name = name.ToLowerInvariant();
await _lock.WaitAsync();
try
@@ -525,7 +524,6 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
[MethodImpl(MethodImplOptions.NoInlining)]
private async Task<MedusaUnloadResult> InternalUnloadAsync(string name)
{
name = name.ToLowerInvariant();
if (!_resolved.Remove(name, out var lsi))
return MedusaUnloadResult.NotLoaded;

View File

@@ -1,15 +1,14 @@
#nullable disable
using CommandLine;
using CommandLine;
namespace NadekoBot.Common;
public static class OptionsParser
{
public static T ParseFrom<T>(string[] args)
public static T ParseFrom<T>(string[]? args)
where T : INadekoCommandOptions, new()
=> ParseFrom(new T(), args).Item1;
public static (T, bool) ParseFrom<T>(T options, string[] args)
public static (T, bool) ParseFrom<T>(T options, string[]? args)
where T : INadekoCommandOptions
{
using var p = new Parser(x =>

View File

@@ -0,0 +1,33 @@
namespace NadekoBot.Common.TypeReaders;
public sealed class GuildUserTypeReader : NadekoTypeReader<IGuildUser>
{
public override async ValueTask<TypeReaderResult<IGuildUser>> ReadAsync(ICommandContext ctx, string input)
{
if (ctx.Guild is null)
return TypeReaderResult.FromError<IGuildUser>(CommandError.Unsuccessful, "Must be in a guild.");
input = input.Trim();
IGuildUser? user = null;
if (MentionUtils.TryParseUser(input, out var id))
user = await ctx.Guild.GetUserAsync(id, CacheMode.AllowDownload);
if (ulong.TryParse(input, out id))
user = await ctx.Guild.GetUserAsync(id, CacheMode.AllowDownload);
if (user is null)
{
var users = await ctx.Guild.GetUsersAsync(CacheMode.CacheOnly);
user = users.FirstOrDefault(x => x.Username == input)
?? users.FirstOrDefault(x =>
string.Equals(x.ToString(), input, StringComparison.InvariantCultureIgnoreCase))
?? users.FirstOrDefault(x =>
string.Equals(x.Username, input, StringComparison.InvariantCultureIgnoreCase));
}
if (user is null)
return TypeReaderResult.FromError<IGuildUser>(CommandError.ObjectNotFound, "User not found.");
return TypeReaderResult.FromSuccess(user);
}
}

View File

@@ -1,4 +1,5 @@
#nullable disable
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Services.Database.Models;
@@ -6,12 +7,14 @@ namespace NadekoBot.Db;
public static class CurrencyTransactionExtensions
{
public static List<CurrencyTransaction> GetPageFor(this DbSet<CurrencyTransaction> set, ulong userId, int page)
=> set.AsQueryable()
.AsNoTracking()
.Where(x => x.UserId == userId)
.OrderByDescending(x => x.DateAdded)
.Skip(15 * page)
.Take(15)
.ToList();
public static Task<List<CurrencyTransaction>> GetPageFor(
this DbSet<CurrencyTransaction> set,
ulong userId,
int page)
=> set.ToLinqToDBTable()
.Where(x => x.UserId == userId)
.OrderByDescending(x => x.DateAdded)
.Skip(15 * page)
.Take(15)
.ToListAsyncLinqToDB();
}

View File

@@ -0,0 +1,9 @@
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Db.Models;
public class AutoPublishChannel : DbEntity
{
public ulong GuildId { get; set; }
public ulong ChannelId { get; set; }
}

View File

@@ -0,0 +1,9 @@
#nullable disable
namespace NadekoBot.Services.Database.Models;
public class GamblingStats : DbEntity
{
public string Feature { get; set; }
public decimal Bet { get; set; }
public decimal PaidOut { get; set; }
}

View File

@@ -95,6 +95,8 @@ public class GuildConfig : DbEntity
public int WarnExpireHours { get; set; }
public WarnExpireAction WarnExpireAction { get; set; } = WarnExpireAction.Clear;
public bool DisableGlobalExpressions { get; set; } = false;
#region Boost Message
public bool SendBoostMessage { get; set; }

View File

@@ -464,6 +464,22 @@ public abstract class NadekoContext : DbContext
});
#endregion
#region AutoPublish
modelBuilder.Entity<AutoPublishChannel>(apc => apc
.HasIndex(x => x.GuildId)
.IsUnique());
#endregion
#region GamblingStats
modelBuilder.Entity<GamblingStats>(gs => gs
.HasIndex(x => x.Feature)
.IsUnique());
#endregion
}
#if DEBUG

View File

@@ -2,6 +2,7 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using NadekoBot.Services.Database;
@@ -10,15 +11,45 @@ using NadekoBot.Services.Database;
namespace NadekoBot.Migrations.Mysql
{
[DbContext(typeof(MysqlContext))]
partial class MysqlContextModelSnapshot : ModelSnapshot
[Migration("20220916194514_autopub")]
partial class autopub
{
protected override void BuildModel(ModelBuilder modelBuilder)
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("NadekoBot.Db.Models.AutoPublishChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<ulong>("ChannelId")
.HasColumnType("bigint unsigned")
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("datetime(6)")
.HasColumnName("dateadded");
b.Property<ulong>("GuildId")
.HasColumnType("bigint unsigned")
.HasColumnName("guildid");
b.HasKey("Id")
.HasName("pk_autopublishchannel");
b.HasIndex("GuildId")
.IsUnique()
.HasDatabaseName("ix_autopublishchannel_guildid");
b.ToTable("autopublishchannel", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
{
b.Property<int>("Id")
@@ -2236,6 +2267,10 @@ namespace NadekoBot.Migrations.Mysql
.HasColumnType("longtext")
.HasColumnName("rolename");
b.Property<ulong?>("RoleRequirement")
.HasColumnType("bigint unsigned")
.HasColumnName("rolerequirement");
b.Property<int>("Type")
.HasColumnType("int")
.HasColumnName("type");

View File

@@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class autopub : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "autopublishchannel",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
guildid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_autopublishchannel", x => x.id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_autopublishchannel_guildid",
table: "autopublishchannel",
column: "guildid",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "autopublishchannel");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
public partial class gamblingstats : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "gamblingstats",
columns: table => new
{
id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
feature = table.Column<string>(type: "varchar(255)", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
bet = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
paidout = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_gamblingstats", x => x.id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "ix_gamblingstats_feature",
table: "gamblingstats",
column: "feature",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "gamblingstats");
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -19,6 +19,35 @@ namespace NadekoBot.Migrations.Mysql
.HasAnnotation("ProductVersion", "6.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("NadekoBot.Db.Models.AutoPublishChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<ulong>("ChannelId")
.HasColumnType("bigint unsigned")
.HasColumnName("channelid");
b.Property<DateTime?>("DateAdded")
.HasColumnType("datetime(6)")
.HasColumnName("dateadded");
b.Property<ulong>("GuildId")
.HasColumnType("bigint unsigned")
.HasColumnName("guildid");
b.HasKey("Id")
.HasName("pk_autopublishchannel");
b.HasIndex("GuildId")
.IsUnique()
.HasDatabaseName("ix_autopublishchannel_guildid");
b.ToTable("autopublishchannel", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
{
b.Property<int>("Id")
@@ -1072,6 +1101,39 @@ namespace NadekoBot.Migrations.Mysql
b.ToTable("filterwordschannelid", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasColumnName("id");
b.Property<decimal>("Bet")
.HasColumnType("decimal(65,30)")
.HasColumnName("bet");
b.Property<DateTime?>("DateAdded")
.HasColumnType("datetime(6)")
.HasColumnName("dateadded");
b.Property<string>("Feature")
.HasColumnType("varchar(255)")
.HasColumnName("feature");
b.Property<decimal>("PaidOut")
.HasColumnType("decimal(65,30)")
.HasColumnName("paidout");
b.HasKey("Id")
.HasName("pk_gamblingstats");
b.HasIndex("Feature")
.IsUnique()
.HasDatabaseName("ix_gamblingstats_feature");
b.ToTable("gamblingstats", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
{
b.Property<int>("Id")
@@ -1196,6 +1258,10 @@ namespace NadekoBot.Migrations.Mysql
.HasColumnType("tinyint(1)")
.HasColumnName("deletestreamonlinemessage");
b.Property<bool>("DisableGlobalExpressions")
.HasColumnType("tinyint(1)")
.HasColumnName("disableglobalexpressions");
b.Property<string>("DmGreetMessageText")
.HasColumnType("longtext")
.HasColumnName("dmgreetmessagetext");

View File

@@ -2,6 +2,7 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using NadekoBot.Services.Database;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
@@ -11,9 +12,10 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace NadekoBot.Migrations.PostgreSql
{
[DbContext(typeof(PostgreSqlContext))]
partial class PostgreSqlContextModelSnapshot : ModelSnapshot
[Migration("20220916194523_autopub")]
partial class autopub
{
protected override void BuildModel(ModelBuilder modelBuilder)
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
@@ -22,6 +24,37 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("NadekoBot.Db.Models.AutoPublishChannel", 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_autopublishchannel");
b.HasIndex("GuildId")
.IsUnique()
.HasDatabaseName("ix_autopublishchannel_guildid");
b.ToTable("autopublishchannel", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
{
b.Property<int>("Id")
@@ -2342,6 +2375,10 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("text")
.HasColumnName("rolename");
b.Property<decimal?>("RoleRequirement")
.HasColumnType("numeric(20,0)")
.HasColumnName("rolerequirement");
b.Property<int>("Type")
.HasColumnType("integer")
.HasColumnName("type");

View File

@@ -0,0 +1,51 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
public partial class autopub : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<decimal>(
name: "rolerequirement",
table: "shopentry",
type: "numeric(20,0)",
nullable: true);
migrationBuilder.CreateTable(
name: "autopublishchannel",
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_autopublishchannel", x => x.id);
});
migrationBuilder.CreateIndex(
name: "ix_autopublishchannel_guildid",
table: "autopublishchannel",
column: "guildid",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "autopublishchannel");
migrationBuilder.DropColumn(
name: "rolerequirement",
table: "shopentry");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
public partial class gamblingstats : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "gamblingstats",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
feature = table.Column<string>(type: "text", nullable: true),
bet = table.Column<decimal>(type: "numeric", nullable: false),
paidout = table.Column<decimal>(type: "numeric", nullable: false),
dateadded = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_gamblingstats", x => x.id);
});
migrationBuilder.CreateIndex(
name: "ix_gamblingstats_feature",
table: "gamblingstats",
column: "feature",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "gamblingstats");
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -22,6 +22,37 @@ namespace NadekoBot.Migrations.PostgreSql
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("NadekoBot.Db.Models.AutoPublishChannel", 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_autopublishchannel");
b.HasIndex("GuildId")
.IsUnique()
.HasDatabaseName("ix_autopublishchannel_guildid");
b.ToTable("autopublishchannel", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
{
b.Property<int>("Id")
@@ -1126,6 +1157,41 @@ namespace NadekoBot.Migrations.PostgreSql
b.ToTable("filterwordschannelid", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("Bet")
.HasColumnType("numeric")
.HasColumnName("bet");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<string>("Feature")
.HasColumnType("text")
.HasColumnName("feature");
b.Property<decimal>("PaidOut")
.HasColumnType("numeric")
.HasColumnName("paidout");
b.HasKey("Id")
.HasName("pk_gamblingstats");
b.HasIndex("Feature")
.IsUnique()
.HasDatabaseName("ix_gamblingstats_feature");
b.ToTable("gamblingstats", (string)null);
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
{
b.Property<int>("Id")
@@ -1256,6 +1322,10 @@ namespace NadekoBot.Migrations.PostgreSql
.HasColumnType("boolean")
.HasColumnName("deletestreamonlinemessage");
b.Property<bool>("DisableGlobalExpressions")
.HasColumnType("boolean")
.HasColumnName("disableglobalexpressions");
b.Property<string>("DmGreetMessageText")
.HasColumnType("text")
.HasColumnName("dmgreetmessagetext");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class autopub : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AutoPublishChannel",
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_AutoPublishChannel", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_AutoPublishChannel_GuildId",
table: "AutoPublishChannel",
column: "GuildId",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AutoPublishChannel");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class gamblingstats : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "GamblingStats",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Feature = table.Column<string>(type: "TEXT", nullable: true),
Bet = table.Column<decimal>(type: "TEXT", nullable: false),
PaidOut = table.Column<decimal>(type: "TEXT", nullable: false),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_GamblingStats", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_GamblingStats_Feature",
table: "GamblingStats",
column: "Feature",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "GamblingStats");
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -17,6 +17,29 @@ namespace NadekoBot.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.7");
modelBuilder.Entity("NadekoBot.Db.Models.AutoPublishChannel", 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")
.IsUnique();
b.ToTable("AutoPublishChannel");
});
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
{
b.Property<int>("Id")
@@ -841,6 +864,32 @@ namespace NadekoBot.Migrations
b.ToTable("FilterWordsChannelId");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GamblingStats", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<decimal>("Bet")
.HasColumnType("TEXT");
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<string>("Feature")
.HasColumnType("TEXT");
b.Property<decimal>("PaidOut")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Feature")
.IsUnique();
b.ToTable("GamblingStats");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b =>
{
b.Property<int>("Id")
@@ -937,6 +986,9 @@ namespace NadekoBot.Migrations
b.Property<bool>("DeleteStreamOnlineMessage")
.HasColumnType("INTEGER");
b.Property<bool>("DisableGlobalExpressions")
.HasColumnType("INTEGER");
b.Property<string>("DmGreetMessageText")
.HasColumnType("TEXT");

View File

@@ -33,9 +33,13 @@ public partial class Administration : NadekoModule<AdministrationService>
}
private readonly SomethingOnlyChannelService _somethingOnly;
private readonly AutoPublishService _autoPubService;
public Administration(SomethingOnlyChannelService somethingOnly)
=> _somethingOnly = somethingOnly;
public Administration(SomethingOnlyChannelService somethingOnly, AutoPublishService autoPubService)
{
_somethingOnly = somethingOnly;
_autoPubService = autoPubService;
}
[Cmd]
[RequireContext(ContextType.Guild)]
@@ -376,4 +380,26 @@ public partial class Administration : NadekoModule<AdministrationService>
await t.DeleteAsync();
await ctx.OkAsync();
}
[Cmd]
[UserPerm(ChannelPerm.ManageMessages)]
public async Task AutoPublish()
{
if (ctx.Channel.GetChannelType() != ChannelType.News)
{
await ReplyErrorLocalizedAsync(strs.req_announcement_channel);
return;
}
var newState = await _autoPubService.ToggleAutoPublish(ctx.Guild.Id, ctx.Channel.Id);
if (newState)
{
await ReplyConfirmLocalizedAsync(strs.autopublish_enable);
}
else
{
await ReplyConfirmLocalizedAsync(strs.autopublish_disable);
}
}
}

View File

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

View File

@@ -0,0 +1,87 @@
#nullable disable
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db.Models;
namespace NadekoBot.Modules.Administration.Services;
public class AutoPublishService : IExecNoCommand, IReadyExecutor, INService
{
private readonly DbService _db;
private readonly DiscordSocketClient _client;
private readonly IBotCredsProvider _creds;
private ConcurrentDictionary<ulong, ulong> _enabled;
public AutoPublishService(DbService db, DiscordSocketClient client, IBotCredsProvider creds)
{
_db = db;
_client = client;
_creds = creds;
}
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
{
if (guild is null)
return;
if (msg.Channel.GetChannelType() != ChannelType.News)
return;
if (!_enabled.TryGetValue(guild.Id, out var cid) || cid != msg.Channel.Id)
return;
await msg.CrosspostAsync(new RequestOptions()
{
RetryMode = RetryMode.AlwaysFail
});
}
public async Task OnReadyAsync()
{
var creds = _creds.GetCreds();
await using var ctx = _db.GetDbContext();
var items = await ctx.GetTable<AutoPublishChannel>()
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId, creds.TotalShards, _client.ShardId))
.ToListAsyncLinqToDB();
_enabled = items
.ToDictionary(x => x.GuildId, x => x.ChannelId)
.ToConcurrent();
}
public async Task<bool> ToggleAutoPublish(ulong guildId, ulong channelId)
{
await using var ctx = _db.GetDbContext();
var deleted = await ctx.GetTable<AutoPublishChannel>()
.DeleteAsync(x => x.GuildId == guildId && x.ChannelId == channelId);
if (deleted != 0)
{
_enabled.TryRemove(guildId, out _);
return false;
}
await ctx.GetTable<AutoPublishChannel>()
.InsertOrUpdateAsync(() => new()
{
GuildId = guildId,
ChannelId = channelId,
DateAdded = DateTime.UtcNow,
},
old => new()
{
ChannelId = channelId,
DateAdded = DateTime.UtcNow,
},
() => new()
{
GuildId = guildId
});
_enabled[guildId] = channelId;
return true;
}
}

View File

@@ -1,4 +1,5 @@
#nullable disable
using CommandLine;
using NadekoBot.Modules.Administration.Services;
namespace NadekoBot.Modules.Administration;
@@ -10,17 +11,34 @@ public partial class Administration
{
private static readonly TimeSpan _twoWeeks = TimeSpan.FromDays(14);
//delets her own messages, no perm required
public sealed class PruneOptions : INadekoCommandOptions
{
[Option(shortName: 's', longName: "safe", Default = false, HelpText = "Whether pinned messages should be deleted.", Required = false)]
public bool Safe { get; set; }
[Option(shortName: 'a', longName: "after", Default = null, HelpText = "Prune only messages after the specified message ID.", Required = false)]
public ulong? After { get; set; }
public void NormalizeOptions()
{
}
}
//deletes her own messages, no perm required
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Prune(string parameter = null)
[NadekoOptions(typeof(PruneOptions))]
public async Task Prune(params string[] args)
{
var (opts, _) = OptionsParser.ParseFrom<PruneOptions>(new PruneOptions(), args);
var user = await ctx.Guild.GetCurrentUserAsync();
if (parameter is "-s" or "--safe")
await _service.PruneWhere((ITextChannel)ctx.Channel, 100, x => x.Author.Id == user.Id && !x.IsPinned);
if (opts.Safe)
await _service.PruneWhere((ITextChannel)ctx.Channel, 100, x => x.Author.Id == user.Id && !x.IsPinned, opts.After);
else
await _service.PruneWhere((ITextChannel)ctx.Channel, 100, x => x.Author.Id == user.Id);
await _service.PruneWhere((ITextChannel)ctx.Channel, 100, x => x.Author.Id == user.Id, opts.After);
ctx.Message.DeleteAfter(3);
}
@@ -29,19 +47,22 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[BotPerm(ChannelPerm.ManageMessages)]
[NadekoOptions(typeof(PruneOptions))]
[Priority(1)]
public async Task Prune(int count, string parameter = null)
public async Task Prune(int count, params string[] args)
{
count++;
if (count < 1)
return;
if (count > 1000)
count = 1000;
var (opts, _) = OptionsParser.ParseFrom<PruneOptions>(new PruneOptions(), args);
if (parameter is "-s" or "--safe")
await _service.PruneWhere((ITextChannel)ctx.Channel, count, x => !x.IsPinned);
if (opts.Safe)
await _service.PruneWhere((ITextChannel)ctx.Channel, count, x => !x.IsPinned, opts.After);
else
await _service.PruneWhere((ITextChannel)ctx.Channel, count, _ => true);
await _service.PruneWhere((ITextChannel)ctx.Channel, count, _ => true, opts.After);
}
//prune @user [x]
@@ -49,17 +70,19 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[BotPerm(ChannelPerm.ManageMessages)]
[NadekoOptions(typeof(PruneOptions))]
[Priority(0)]
public Task Prune(IGuildUser user, int count = 100, string parameter = null)
=> Prune(user.Id, count, parameter);
public Task Prune(IGuildUser user, int count = 100, params string[] args)
=> Prune(user.Id, count, args);
//prune userid [x]
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[BotPerm(ChannelPerm.ManageMessages)]
[NadekoOptions(typeof(PruneOptions))]
[Priority(0)]
public async Task Prune(ulong userId, int count = 100, string parameter = null)
public async Task Prune(ulong userId, int count = 100, params string[] args)
{
if (userId == ctx.User.Id)
count++;
@@ -70,17 +93,21 @@ public partial class Administration
if (count > 1000)
count = 1000;
if (parameter is "-s" or "--safe")
var (opts, _) = OptionsParser.ParseFrom<PruneOptions>(new PruneOptions(), args);
if (opts.Safe)
{
await _service.PruneWhere((ITextChannel)ctx.Channel,
count,
m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < _twoWeeks && !m.IsPinned);
m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < _twoWeeks && !m.IsPinned,
opts.After);
}
else
{
await _service.PruneWhere((ITextChannel)ctx.Channel,
count,
m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < _twoWeeks);
m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < _twoWeeks,
opts.After);
}
}
}

View File

@@ -1,6 +1,4 @@
#nullable disable
using Nadeko.Common;
namespace NadekoBot.Modules.Administration.Services;
public class PruneService : INService
@@ -13,7 +11,7 @@ public class PruneService : INService
public PruneService(ILogCommandService logService)
=> _logService = logService;
public async Task PruneWhere(ITextChannel channel, int amount, Func<IMessage, bool> predicate)
public async Task PruneWhere(ITextChannel channel, int amount, Func<IMessage, bool> predicate, ulong? after = null)
{
ArgumentNullException.ThrowIfNull(channel, nameof(channel));
@@ -28,7 +26,14 @@ public class PruneService : INService
var now = DateTime.UtcNow;
IMessage[] msgs;
IMessage lastMessage = null;
msgs = (await channel.GetMessagesAsync(50).FlattenAsync()).Where(predicate).Take(amount).ToArray();
var dled = await channel.GetMessagesAsync(50).FlattenAsync();
msgs = dled
.Where(predicate)
.Where(x => after is ulong a ? x.Id > a : true)
.Take(amount)
.ToArray();
while (amount > 0 && msgs.Any())
{
lastMessage = msgs[^1];
@@ -62,10 +67,13 @@ public class PruneService : INService
amount -= 50;
if (amount > 0)
{
msgs = (await channel.GetMessagesAsync(lastMessage, Direction.Before, 50).FlattenAsync())
.Where(predicate)
.Take(amount)
.ToArray();
dled = await channel.GetMessagesAsync(lastMessage, Direction.Before, 50).FlattenAsync();
msgs = dled
.Where(predicate)
.Where(x => after is ulong a ? x.Id > a : true)
.Take(amount)
.ToArray();
}
}
}

View File

@@ -230,6 +230,19 @@ public partial class Administration
await ReplyPendingLocalizedAsync(strs.fwall_stop);
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task ForwardToChannel()
{
var enabled = _service.ForwardToChannel(ctx.Channel.Id);
if (enabled)
await ReplyConfirmLocalizedAsync(strs.fwch_start);
else
await ReplyPendingLocalizedAsync(strs.fwch_stop);
}
[Cmd]
public async Task ShardStats(int page = 1)
{

View File

@@ -85,12 +85,12 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
await using var uow = _db.GetDbContext();
autoCommands = uow.AutoCommands.AsNoTracking()
.Where(x => x.Interval >= 5)
.AsEnumerable()
.GroupBy(x => x.GuildId)
.ToDictionary(x => x.Key,
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent())
.ToConcurrent();
.Where(x => x.Interval >= 5)
.AsEnumerable()
.GroupBy(x => x.GuildId)
.ToDictionary(x => x.Key,
y => y.ToDictionary(x => x.Id, TimerFromAutoCommand).ToConcurrent())
.ToConcurrent();
var startupCommands = uow.AutoCommands.AsNoTracking().Where(x => x.Interval == 0);
foreach (var cmd in startupCommands)
@@ -169,18 +169,18 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
private async Task LoadOwnerChannels()
{
var channels = await _creds.OwnerIds.Select(id =>
{
var user = _client.GetUser(id);
if (user is null)
return Task.FromResult<IDMChannel>(null);
{
var user = _client.GetUser(id);
if (user is null)
return Task.FromResult<IDMChannel>(null);
return user.CreateDMChannelAsync();
})
.WhenAll();
return user.CreateDMChannelAsync();
})
.WhenAll();
ownerChannels = channels.Where(x => x is not null)
.ToDictionary(x => x.Recipient.Id, x => x)
.ToImmutableDictionary();
.ToDictionary(x => x.Recipient.Id, x => x)
.ToImmutableDictionary();
if (!ownerChannels.Any())
{
@@ -202,7 +202,7 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
{
var bs = _bss.Data;
if (msg.Channel is IDMChannel && bs.ForwardMessages && ownerChannels.Any())
if (msg.Channel is IDMChannel && bs.ForwardMessages && (ownerChannels.Any() || bs.ForwardToChannel is not null))
{
var title = _strings.GetText(strs.dm_from) + $" [{msg.Author}]({msg.Author.Id})";
@@ -232,6 +232,18 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
}
}
}
else if (bs.ForwardToChannel is ulong cid)
{
try
{
if (_client.GetChannel(cid) is ITextChannel ch)
await ch.SendConfirmAsync(_eb, title, toSend);
}
catch
{
Log.Warning("Error forwarding message to the channel");
}
}
else
{
var firstOwnerChannel = ownerChannels.Values.First();
@@ -333,6 +345,20 @@ public sealed class SelfService : IExecNoCommand, IReadyExecutor, INService
return isToAll;
}
public bool ForwardToChannel(ulong? channelId)
{
using var uow = _db.GetDbContext();
_bss.ModifyConfig(config =>
{
config.ForwardToChannel = channelId == config.ForwardToChannel
? null
: channelId;
});
return channelId is not null;
}
private void HandleStatusChanges()
=> _pubSub.Sub(_activitySetKey,
async data =>

View File

@@ -3,6 +3,9 @@
namespace NadekoBot.Modules.Administration;
public sealed class DummyLogCommandService : ILogCommandService
#if GLOBAL_NADEKO
, INService
#endif
{
public void AddDeleteIgnore(ulong xId)
{

View File

@@ -41,6 +41,17 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
}
[Cmd]
[UserPerm(GuildPerm.Administrator)]
public async Task ExprToggleGlobal()
{
var result = await _service.ToggleGlobalExpressionsAsync(ctx.Guild.Id);
if (result)
await ReplyConfirmLocalizedAsync(strs.expr_global_disabled);
else
await ReplyConfirmLocalizedAsync(strs.expr_global_enabled);
}
[Cmd]
[UserPerm(GuildPerm.Administrator)]
public async Task ExprAddServer(string key, [Leftover] string message)

View File

@@ -7,6 +7,7 @@ using NadekoBot.Modules.Permissions.Common;
using NadekoBot.Modules.Permissions.Services;
using NadekoBot.Services.Database.Models;
using System.Runtime.CompilerServices;
using LinqToDB.EntityFrameworkCore;
using Nadeko.Common;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
@@ -56,8 +57,8 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
// 1. expressions are almost never added (compared to how many times they are being looped through)
// 2. only need write locks for this as we'll rebuild+replace the array on every edit
// 3. there's never many of them (at most a thousand, usually < 100)
private NadekoExpression[] globalReactions;
private ConcurrentDictionary<ulong, NadekoExpression[]> newGuildReactions;
private NadekoExpression[] globalExpressions;
private ConcurrentDictionary<ulong, NadekoExpression[]> newguildExpressions;
private readonly DbService _db;
private readonly DiscordSocketClient _client;
@@ -72,6 +73,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
private readonly Random _rng;
private bool ready;
private ConcurrentHashSet<ulong> _disabledGlobalExpressionGuilds;
public NadekoExpressionsService(
PermissionService perms,
@@ -113,7 +115,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
.Where(x => allGuildIds.Contains(x.GuildId.Value))
.ToListAsync();
newGuildReactions = guildItems.GroupBy(k => k.GuildId!.Value)
newguildExpressions = guildItems.GroupBy(k => k.GuildId!.Value)
.ToDictionary(g => g.Key,
g => g.Select(x =>
{
@@ -123,6 +125,11 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
.ToArray())
.ToConcurrent();
_disabledGlobalExpressionGuilds = new (await uow.GuildConfigs
.Where(x => x.DisableGlobalExpressions)
.Select(x => x.GuildId)
.ToListAsyncLinqToDB());
lock (_gexprWriteLock)
{
var globalItems = uow.Expressions.AsNoTracking()
@@ -135,7 +142,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
})
.ToArray();
globalReactions = globalItems;
globalExpressions = globalItems;
}
ready = true;
@@ -151,14 +158,17 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
var content = umsg.Content.Trim().ToLowerInvariant();
if (newGuildReactions.TryGetValue(channel.Guild.Id, out var reactions) && reactions.Length > 0)
if (newguildExpressions.TryGetValue(channel.Guild.Id, out var expressions) && expressions.Length > 0)
{
var expr = MatchExpressions(content, reactions);
var expr = MatchExpressions(content, expressions);
if (expr is not null)
return expr;
}
var localGrs = globalReactions;
if (_disabledGlobalExpressionGuilds.Contains(channel.Guild.Id))
return null;
var localGrs = globalExpressions;
return MatchExpressions(content, localGrs);
}
@@ -345,7 +355,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
if (maybeGuildId is { } guildId)
{
newGuildReactions.AddOrUpdate(guildId,
newguildExpressions.AddOrUpdate(guildId,
new[] { expr },
(_, old) =>
{
@@ -363,7 +373,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
lock (_gexprWriteLock)
{
var exprs = globalReactions;
var exprs = globalExpressions;
for (var i = 0; i < exprs.Length; i++)
{
if (exprs[i].Id == expr.Id)
@@ -379,7 +389,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
expr.Trigger = expr.Trigger.Replace(MENTION_PH, _client.CurrentUser.Mention);
if (maybeGuildId is { } guildId)
newGuildReactions.AddOrUpdate(guildId, new[] { expr }, (_, old) => old.With(expr));
newguildExpressions.AddOrUpdate(guildId, new[] { expr }, (_, old) => old.With(expr));
else
return _pubSub.Pub(_gexprAddedKey, expr);
@@ -390,7 +400,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
if (maybeGuildId is { } guildId)
{
newGuildReactions.AddOrUpdate(guildId,
newguildExpressions.AddOrUpdate(guildId,
Array.Empty<NadekoExpression>(),
(key, old) => DeleteInternal(old, id, out _));
@@ -399,7 +409,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
lock (_gexprWriteLock)
{
var expr = Array.Find(globalReactions, item => item.Id == id);
var expr = Array.Find(globalExpressions, item => item.Id == id);
if (expr is not null)
return _pubSub.Pub(_gexprDeletedkey, expr.Id);
}
@@ -492,7 +502,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
var count = uow.Expressions.ClearFromGuild(guildId);
uow.SaveChanges();
newGuildReactions.TryRemove(guildId, out _);
newguildExpressions.TryRemove(guildId, out _);
return count;
}
@@ -562,10 +572,10 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
lock (_gexprWriteLock)
{
var newGlobalReactions = new NadekoExpression[globalReactions.Length + 1];
Array.Copy(globalReactions, newGlobalReactions, globalReactions.Length);
newGlobalReactions[globalReactions.Length] = c;
globalReactions = newGlobalReactions;
var newGlobalReactions = new NadekoExpression[globalExpressions.Length + 1];
Array.Copy(globalExpressions, newGlobalReactions, globalExpressions.Length);
newGlobalReactions[globalExpressions.Length] = c;
globalExpressions = newGlobalReactions;
}
return default;
@@ -575,11 +585,11 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
lock (_gexprWriteLock)
{
for (var i = 0; i < globalReactions.Length; i++)
for (var i = 0; i < globalExpressions.Length; i++)
{
if (globalReactions[i].Id == c.Id)
if (globalExpressions[i].Id == c.Id)
{
globalReactions[i] = c;
globalExpressions[i] = c;
return default;
}
}
@@ -596,8 +606,8 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
{
lock (_gexprWriteLock)
{
var newGlobalReactions = DeleteInternal(globalReactions, id, out _);
globalReactions = newGlobalReactions;
var newGlobalReactions = DeleteInternal(globalExpressions, id, out _);
globalExpressions = newGlobalReactions;
}
return default;
@@ -612,7 +622,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
private Task OnLeftGuild(SocketGuild arg)
{
newGuildReactions.TryRemove(arg.Id, out _);
newguildExpressions.TryRemove(arg.Id, out _);
return Task.CompletedTask;
}
@@ -622,7 +632,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
await using var uow = _db.GetDbContext();
var exprs = await uow.Expressions.AsNoTracking().Where(x => x.GuildId == gc.GuildId).ToArrayAsync();
newGuildReactions[gc.GuildId] = exprs;
newguildExpressions[gc.GuildId] = exprs;
}
#endregion
@@ -702,10 +712,25 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
public NadekoExpression[] GetExpressionsFor(ulong? maybeGuildId)
{
if (maybeGuildId is { } guildId)
return newGuildReactions.TryGetValue(guildId, out var exprs) ? exprs : Array.Empty<NadekoExpression>();
return newguildExpressions.TryGetValue(guildId, out var exprs) ? exprs : Array.Empty<NadekoExpression>();
return globalReactions;
return globalExpressions;
}
#endregion
public async Task<bool> ToggleGlobalExpressionsAsync(ulong guildId)
{
await using var ctx = _db.GetDbContext();
var gc = ctx.GuildConfigsForId(guildId, set => set);
var toReturn = gc.DisableGlobalExpressions = !gc.DisableGlobalExpressions;
await ctx.SaveChangesAsync();
if (toReturn)
_disabledGlobalExpressionGuilds.Add(guildId);
else
_disabledGlobalExpressionGuilds.TryRemove(guildId);
return toReturn;
}
}

View File

@@ -145,6 +145,9 @@ public partial class Gambling
public async Task BetDrawInternal(long amount, InputValueGuess? val, InputColorGuess? col)
{
if (amount <= 0)
return;
var res = await _service.BetDrawAsync(ctx.User.Id,
amount,
(byte?)val,

View File

@@ -29,6 +29,7 @@ public partial class Gambling : GamblingModule<GamblingService>
private readonly IBankService _bank;
private readonly IPatronageService _ps;
private readonly RemindService _remind;
private readonly GamblingTxTracker _gamblingTxTracker;
private IUserMessage rdMsg;
@@ -41,7 +42,8 @@ public partial class Gambling : GamblingModule<GamblingService>
GamblingConfigService configService,
IBankService bank,
IPatronageService ps,
RemindService remind)
RemindService remind,
GamblingTxTracker gamblingTxTracker)
: base(configService)
{
_gs = gs;
@@ -51,6 +53,7 @@ public partial class Gambling : GamblingModule<GamblingService>
_bank = bank;
_ps = ps;
_remind = remind;
_gamblingTxTracker = gamblingTxTracker;
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
_enUsCulture.NumberDecimalDigits = 0;
@@ -65,6 +68,43 @@ public partial class Gambling : GamblingModule<GamblingService>
return N(bal);
}
[Cmd]
public async Task BetStats()
{
var stats = await _gamblingTxTracker.GetAllAsync();
var eb = _eb.Create(ctx)
.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 ctx.Channel.EmbedAsync(eb);
}
[Cmd]
public async Task Economy()
{
@@ -79,15 +119,15 @@ public partial class Gambling : GamblingModule<GamblingService>
// [21:03] Bob Page: Kinda remids me of US economy
var embed = _eb.Create()
.WithTitle(GetText(strs.economy_state))
.AddField(GetText(strs.currency_owned), N(ec.Cash - ec.Bot))
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
.AddField(GetText(strs.currency_planted), N(ec.Planted))
.AddField(GetText(strs.owned_waifus_total), N(ec.Waifus))
.AddField(GetText(strs.bot_currency), N(ec.Bot))
.AddField(GetText(strs.bank_accounts), N(ec.Bank))
.AddField(GetText(strs.total), N(ec.Cash + ec.Planted + ec.Waifus + ec.Bank))
.WithOkColor();
.WithTitle(GetText(strs.economy_state))
.AddField(GetText(strs.currency_owned), N(ec.Cash - ec.Bot))
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
.AddField(GetText(strs.currency_planted), N(ec.Planted))
.AddField(GetText(strs.owned_waifus_total), N(ec.Waifus))
.AddField(GetText(strs.bot_currency), N(ec.Bot))
.AddField(GetText(strs.bank_accounts), N(ec.Bank))
.AddField(GetText(strs.total), N(ec.Cash + ec.Planted + ec.Waifus + ec.Bank))
.WithOkColor();
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
await ctx.Channel.EmbedAsync(embed);
@@ -105,7 +145,7 @@ public partial class Gambling : GamblingModule<GamblingService>
await _remind.AddReminderAsync(ctx.User.Id,
ctx.User.Id,
ctx.Guild.Id,
ctx.Guild?.Id,
true,
when,
GetText(strs.timely_time));
@@ -249,13 +289,13 @@ public partial class Gambling : GamblingModule<GamblingService>
List<CurrencyTransaction> trs;
await using (var uow = _db.GetDbContext())
{
trs = uow.CurrencyTransactions.GetPageFor(userId, page);
trs = await uow.CurrencyTransactions.GetPageFor(userId, page);
}
var embed = _eb.Create()
.WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
?? $"{userId}")))
.WithOkColor();
.WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
?? $"{userId}")))
.WithOkColor();
var sb = new StringBuilder();
foreach (var tr in trs)
@@ -292,8 +332,8 @@ public partial class Gambling : GamblingModule<GamblingService>
await using var uow = _db.GetDbContext();
var tr = await uow.CurrencyTransactions.ToLinqToDBTable()
.Where(x => x.Id == intId && x.UserId == ctx.User.Id)
.FirstOrDefaultAsync();
.Where(x => x.Id == intId && x.UserId == ctx.User.Id)
.FirstOrDefaultAsync();
if (tr is null)
{
@@ -340,7 +380,7 @@ public partial class Gambling : GamblingModule<GamblingService>
(_, _, ulong userId) => $"{type.Titleize()} - {subType.Titleize()} | [{userId}]",
_ => $"{type.Titleize()} - {subType.Titleize()}"
};
[Cmd]
[Priority(0)]
public async Task Cash(ulong userId)
@@ -354,15 +394,15 @@ public partial class Gambling : GamblingModule<GamblingService>
var balance = await _bank.GetBalanceAsync(ctx.User.Id);
await N(balance)
.Pipe(strs.bank_balance)
.Pipe(GetText)
.Pipe(text => smc.RespondConfirmAsync(_eb, text, ephemeral: true));
.Pipe(strs.bank_balance)
.Pipe(GetText)
.Pipe(text => smc.RespondConfirmAsync(_eb, text, ephemeral: true));
}
private NadekoInteraction CreateCashInteraction()
=> _inter.Create<object>(ctx.User.Id,
new(new(
customId: "cash:bank_show_balance",
customId: "cash:bank_show_balance",
emote: new Emoji("🏦")),
BankAction));
@@ -372,7 +412,7 @@ public partial class Gambling : GamblingModule<GamblingService>
{
user ??= ctx.User;
var cur = await GetBalanceStringAsync(user.Id);
var inter = user == ctx.User
? CreateCashInteraction()
: null;
@@ -648,7 +688,7 @@ public partial class Gambling : GamblingModule<GamblingService>
return;
}
var win = (long)result.Won;
string str;
if (win > 0)
@@ -762,7 +802,7 @@ public partial class Gambling : GamblingModule<GamblingService>
S = 2,
Scissors = 2
}
[Cmd]
public async Task Rps(InputRpsPick pick, ShmartNumber amount = default)
{
@@ -778,7 +818,7 @@ public partial class Gambling : GamblingModule<GamblingService>
return "✂️";
}
}
if (!await CheckBetOptional(amount) || amount == 1)
return;
@@ -789,9 +829,9 @@ public partial class Gambling : GamblingModule<GamblingService>
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
return;
}
var embed = _eb.Create();
string msg;
if (result.Result == RpsResultType.Draw)
{
@@ -799,8 +839,8 @@ public partial class Gambling : GamblingModule<GamblingService>
}
else if (result.Result == RpsResultType.Win)
{
if((long)result.Won > 0)
embed.AddField(GetText(strs.won), N(amount.Value));
if ((long)result.Won > 0)
embed.AddField(GetText(strs.won), N(amount.Value));
msg = GetText(strs.rps_win(ctx.User.Mention,
GetRpsPick(pick),
@@ -819,7 +859,7 @@ public partial class Gambling : GamblingModule<GamblingService>
await ctx.Channel.EmbedAsync(embed);
}
private static readonly ImmutableArray<string> _emojis =
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
@@ -847,7 +887,7 @@ public partial class Gambling : GamblingModule<GamblingService>
sb.Append($"{Format.Bold($"x{multi:0.##}")} ⬅️");
else
sb.Append($"||x{multi:0.##}||");
sb.AppendLine();
}
@@ -861,101 +901,100 @@ public partial class Gambling : GamblingModule<GamblingService>
await ctx.Channel.EmbedAsync(eb);
}
public enum GambleTestTarget
{
Slot,
Betroll,
Betflip,
BetflipT,
BetDraw,
BetDrawHL,
BetDrawRB,
Lula,
Rps,
}
[Cmd]
[OwnerOnly]
public async Task BetTest()
{
await SendConfirmAsync(GetText(strs.available_tests),
Enum.GetValues<GambleTestTarget>()
.Select(x => $"`{x}`")
.Join(", "));
}
public enum GambleTestTarget
{
Slot,
Betroll,
Betflip,
BetflipT,
BetDraw,
BetDrawHL,
BetDrawRB,
Lula,
Rps,
}
[Cmd]
[OwnerOnly]
public async Task BetTest(GambleTestTarget target, int tests = 1000)
[Cmd]
[OwnerOnly]
public async Task BetTest()
{
await SendConfirmAsync(GetText(strs.available_tests),
Enum.GetValues<GambleTestTarget>()
.Select(x => $"`{x}`")
.Join(", "));
}
[Cmd]
[OwnerOnly]
public async Task BetTest(GambleTestTarget target, int tests = 1000)
{
if (tests <= 0)
return;
await ctx.Channel.TriggerTypingAsync();
var streak = 0;
var maxW = 0;
var maxL = 0;
var dict = new Dictionary<decimal, int>();
for (var i = 0; i < tests; i++)
{
if (tests <= 0)
return;
await ctx.Channel.TriggerTypingAsync();
var streak = 0;
var maxW = 0;
var maxL = 0;
var dict = new Dictionary<decimal, int>();
for (var i = 0; i < tests; i++)
var multi = target switch
{
var multi = target switch
{
GambleTestTarget.BetDraw => (await _gs.BetDrawAsync(ctx.User.Id, 0, 1, 0)).AsT0.Multiplier,
GambleTestTarget.BetDrawRB => (await _gs.BetDrawAsync(ctx.User.Id, 0, null, 1)).AsT0.Multiplier,
GambleTestTarget.BetDrawHL => (await _gs.BetDrawAsync(ctx.User.Id, 0, 0, null)).AsT0.Multiplier,
GambleTestTarget.Slot => (await _gs.SlotAsync(ctx.User.Id, 0)).AsT0.Multiplier,
GambleTestTarget.Betflip => (await _gs.BetFlipAsync(ctx.User.Id, 0, 0)).AsT0.Multiplier,
GambleTestTarget.BetflipT => (await _gs.BetFlipAsync(ctx.User.Id, 0, 1)).AsT0.Multiplier,
GambleTestTarget.Lula => (await _gs.LulaAsync(ctx.User.Id, 0)).AsT0.Multiplier,
GambleTestTarget.Rps => (await _gs.RpsAsync(ctx.User.Id, 0, (byte)(i % 3))).AsT0.Multiplier,
GambleTestTarget.Betroll => (await _gs.BetRollAsync(ctx.User.Id, 0)).AsT0.Multiplier,
_ => throw new ArgumentOutOfRangeException(nameof(target))
};
if (dict.ContainsKey(multi))
dict[multi] += 1;
GambleTestTarget.BetDraw => (await _gs.BetDrawAsync(ctx.User.Id, 0, 1, 0)).AsT0.Multiplier,
GambleTestTarget.BetDrawRB => (await _gs.BetDrawAsync(ctx.User.Id, 0, null, 1)).AsT0.Multiplier,
GambleTestTarget.BetDrawHL => (await _gs.BetDrawAsync(ctx.User.Id, 0, 0, null)).AsT0.Multiplier,
GambleTestTarget.Slot => (await _gs.SlotAsync(ctx.User.Id, 0)).AsT0.Multiplier,
GambleTestTarget.Betflip => (await _gs.BetFlipAsync(ctx.User.Id, 0, 0)).AsT0.Multiplier,
GambleTestTarget.BetflipT => (await _gs.BetFlipAsync(ctx.User.Id, 0, 1)).AsT0.Multiplier,
GambleTestTarget.Lula => (await _gs.LulaAsync(ctx.User.Id, 0)).AsT0.Multiplier,
GambleTestTarget.Rps => (await _gs.RpsAsync(ctx.User.Id, 0, (byte)(i % 3))).AsT0.Multiplier,
GambleTestTarget.Betroll => (await _gs.BetRollAsync(ctx.User.Id, 0)).AsT0.Multiplier,
_ => throw new ArgumentOutOfRangeException(nameof(target))
};
if (dict.ContainsKey(multi))
dict[multi] += 1;
else
dict.Add(multi, 1);
if (multi < 1)
{
if (streak <= 0)
--streak;
else
dict.Add(multi, 1);
streak = -1;
if (multi < 1)
{
if (streak <= 0)
--streak;
else
streak = -1;
maxL = Math.Max(maxL, -streak);
}
else if (multi > 1)
{
if (streak >= 0)
++streak;
else
streak = 1;
maxW = Math.Max(maxW, streak);
}
maxL = Math.Max(maxL, -streak);
}
var sb = new StringBuilder();
decimal payout = 0;
foreach (var key in dict.Keys.OrderByDescending(x => x))
else if (multi > 1)
{
sb.AppendLine($"x**{key}** occured `{dict[key]}` times. {dict[key] * 1.0f / tests * 100}%");
payout += key * dict[key];
}
if (streak >= 0)
++streak;
else
streak = 1;
sb.AppendLine();
sb.AppendLine($"Longest win streak: `{maxW}`");
sb.AppendLine($"Longest lose streak: `{maxL}`");
await SendConfirmAsync(GetText(strs.test_results_for(target)),
sb.ToString(),
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%");
maxW = Math.Max(maxW, streak);
}
}
var sb = new StringBuilder();
decimal payout = 0;
foreach (var key in dict.Keys.OrderByDescending(x => x))
{
sb.AppendLine($"x**{key}** occured `{dict[key]}` times. {dict[key] * 1.0f / tests * 100}%");
payout += key * dict[key];
}
sb.AppendLine();
sb.AppendLine($"Longest win streak: `{maxW}`");
sb.AppendLine($"Longest lose streak: `{maxL}`");
await SendConfirmAsync(GetText(strs.test_results_for(target)),
sb.ToString(),
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%");
}
}

View File

@@ -78,7 +78,7 @@ public class CurrencyConfig
[Comment(@"What is the name of the currency")]
public string Name { get; set; } = "Nadeko Flower";
[Comment(@"For how long will the transactions be kept in the database (curtrs)
[Comment(@"For how long (in days) will the transactions be kept in the database (curtrs)
Set 0 to disable cleanup (keep transactions forever)")]
public int TransactionsLifetime { get; set; } = 0;
}

View File

@@ -47,27 +47,6 @@ public partial class Gambling
public Task Test()
=> Task.CompletedTask;
[Cmd]
[OwnerOnly]
public async Task SlotStats()
{
//i remembered to not be a moron
var paid = totalPaidOut;
var bet = totalBet;
if (bet <= 0)
bet = 1;
var embed = _eb.Create()
.WithOkColor()
.WithTitle("Slot Stats")
.AddField("Total Bet", N(bet), true)
.AddField("Paid Out", N(paid), true)
.WithFooter($"Payout Rate: {paid * 1.0M / bet * 100:f4}%");
await ctx.Channel.EmbedAsync(embed);
}
[Cmd]
public async Task Slot(ShmartNumber amount)
{

View File

@@ -126,7 +126,7 @@ public class ChatterBotService : IExecOnMessage
Log.Information("{PermissionMessage}", returnMsg);
}
return true;
return false;
}
if (await _ccs.TryBlock(sg, usrMsg.Author, CleverBotResponseStr.CLEVERBOT_RESPONSE))

View File

@@ -112,25 +112,25 @@ public partial class Games
private void RegisterEvents(TriviaGame trivia)
{
trivia.OnQuestion += OnTriviaOnOnQuestion;
trivia.OnHint += OnTriviaOnOnHint;
trivia.OnGuess += OnTriviaOnOnGuess;
trivia.OnEnded += OnTriviaOnOnEnded;
trivia.OnStats += OnTriviaOnOnStats;
trivia.OnTimeout += OnTriviaOnOnTimeout;
trivia.OnQuestion += OnTriviaQuestion;
trivia.OnHint += OnTriviaHint;
trivia.OnGuess += OnTriviaGuess;
trivia.OnEnded += OnTriviaEnded;
trivia.OnStats += OnTriviaStats;
trivia.OnTimeout += OnTriviaTimeout;
}
private void UnregisterEvents(TriviaGame trivia)
{
trivia.OnQuestion -= OnTriviaOnOnQuestion;
trivia.OnHint -= OnTriviaOnOnHint;
trivia.OnGuess -= OnTriviaOnOnGuess;
trivia.OnEnded -= OnTriviaOnOnEnded;
trivia.OnStats -= OnTriviaOnOnStats;
trivia.OnTimeout -= OnTriviaOnOnTimeout;
trivia.OnQuestion -= OnTriviaQuestion;
trivia.OnHint -= OnTriviaHint;
trivia.OnGuess -= OnTriviaGuess;
trivia.OnEnded -= OnTriviaEnded;
trivia.OnStats -= OnTriviaStats;
trivia.OnTimeout -= OnTriviaTimeout;
}
private async Task OnTriviaOnOnHint(TriviaGame game, TriviaQuestion question)
private async Task OnTriviaHint(TriviaGame game, TriviaQuestion question)
{
try
{
@@ -150,11 +150,11 @@ public partial class Games
}
catch (Exception ex)
{
Log.Warning(ex, "Error editing triva message");
Log.Warning(ex, "Error editing trivia message");
}
}
private async Task OnTriviaOnOnQuestion(TriviaGame game, TriviaQuestion question)
private async Task OnTriviaQuestion(TriviaGame game, TriviaQuestion question)
{
try
{
@@ -173,19 +173,16 @@ public partial class Games
questionMessage = await ctx.Channel.EmbedAsync(questionEmbed);
}
catch (HttpException ex) when (ex.HttpCode is HttpStatusCode.NotFound or HttpStatusCode.Forbidden or HttpStatusCode.BadRequest)
catch (HttpException ex) when (ex.HttpCode is HttpStatusCode.NotFound or HttpStatusCode.Forbidden
or HttpStatusCode.BadRequest)
{
Log.Warning("Unable to send trivia questions. Stopping immediately");
game.Stop();
}
catch (Exception ex)
{
Log.Warning(ex, "Error sending trivia embed");
await Task.Delay(2000);
throw;
}
}
private async Task OnTriviaOnOnTimeout(TriviaGame _, TriviaQuestion question)
private async Task OnTriviaTimeout(TriviaGame _, TriviaQuestion question)
{
try
{
@@ -205,7 +202,7 @@ public partial class Games
}
}
private async Task OnTriviaOnOnStats(TriviaGame game)
private async Task OnTriviaStats(TriviaGame game)
{
try
{
@@ -217,7 +214,7 @@ public partial class Games
}
}
private async Task OnTriviaOnOnEnded(TriviaGame game)
private async Task OnTriviaEnded(TriviaGame game)
{
try
{
@@ -237,10 +234,9 @@ public partial class Games
}
UnregisterEvents(game);
await Task.Delay(1000);
}
private async Task OnTriviaOnOnGuess(TriviaGame _, TriviaUser user, TriviaQuestion question, bool isWin)
private async Task OnTriviaGuess(TriviaGame _, TriviaUser user, TriviaQuestion question, bool isWin)
{
try
{

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