Compare commits

...

58 Commits

Author SHA1 Message Date
Kwoth
7bff20cc70 Upped version to 3.0.11 2021-12-17 23:41:31 +01:00
Kwoth
29f5dcc359 Merge branch 'v3' of https://gitlab.com/kwoth/nadekobot into v3 2021-12-17 23:39:08 +01:00
Kwoth
14f2851072 - you should be able to update your .atl now without disabling it
- capitalization of language input in .atl should no longer matter
2021-12-17 23:38:06 +01:00
Kwoth
a2b25f8246 atl no longer pings if in nodelete mode 2021-12-17 16:32:35 +01:00
Kwoth
a38951b5ad atl will no longer post the translation if it's equivalent to the input message
updated packages
2021-12-17 16:26:14 +01:00
Kwoth
4c1b911cb7 Inrole string fix 2021-12-17 15:59:18 +01:00
Kwoth
6c9f231453 Update responses.nl-NL.json (POEditor.com) 2021-12-15 19:09:59 +00:00
Kwoth
83daf3c30f Made .showembed always output lowercase field names and no null values 2021-12-14 19:41:07 +01:00
Kwoth
9be8140d4d Added .showembed <msgid> and .showembed #channel <msgid> which will show you embed json from the specified message 2021-12-13 20:29:45 +01:00
Kwoth
96c9b699aa Merge branch 'v3' of https://gitlab.com/kwoth/nadekobot into v3 2021-12-13 19:28:34 +01:00
Kwoth
3c0768a372 .atl / .at reworked 2021-12-13 19:28:22 +01:00
Kwoth
58b22e3d9e Merge branch 'award-patch' into 'v3'
Award no longer misinterprets IDs

See merge request Kwoth/nadekobot!195
2021-12-13 13:11:56 +00:00
Alan Beatty
0474551e2f Award no longer misinterprets IDs 2021-12-13 13:11:56 +00:00
Kwoth
c85bdec396 Merge branch 'qdel-patch' into 'v3'
Clarity on qdel

See merge request Kwoth/nadekobot!197
2021-12-13 13:07:00 +00:00
Alan Beatty
5fa39eaa9f Clarity on qdel 2021-12-13 13:07:00 +00:00
Kwoth
8eaaa35c7a Merge branch 'perm-patch' into 'v3'
Replace `RequireUserPemission` with `UserPerm`

See merge request Kwoth/nadekobot!196
2021-12-13 13:06:28 +00:00
Alan Beatty
1c24f95efa Replace RequireUserPemission with UserPerm 2021-12-13 13:06:28 +00:00
Kwoth
fcc49dbbdb Made .sinfo slightly better 2021-12-11 16:13:52 +01:00
Kwoth
3a317590b4 Merge branch 'v3' of https://gitlab.com/kwoth/nadekobot into v3 2021-12-11 15:27:37 +01:00
Kwoth
36c013fbb5 Added .qimport and .qexport commands, fixed some response strings 2021-12-11 15:27:32 +01:00
Kwoth
a9ec8049f6 Update responses.id-ID.json (POEditor.com) 2021-12-11 01:04:37 +00:00
Kwoth
ced0d97e3a Update responses.pt-BR.json (POEditor.com) 2021-12-11 00:37:34 +00:00
Kwoth
24d0f57dc3 Update responses.pl-PL.json (POEditor.com) 2021-12-11 00:37:33 +00:00
Kwoth
514ecd6be8 Update responses.fr-FR.json (POEditor.com) 2021-12-11 00:37:32 +00:00
Kwoth
02eb6e172b added slots.currencyFontColor to gambling.yml 2021-12-09 20:54:26 +01:00
Kwoth
d22c579875 Merge branch 'v3' of https://gitlab.com/kwoth/nadekobot into v3 2021-12-09 02:07:12 +01:00
Kwoth
f70e49fc6a Removed unwanted command 2021-12-09 02:07:03 +01:00
Kwoth
8b410561f9 Update responses.uk-UA.json (POEditor.com) 2021-12-09 01:04:48 +00:00
Kwoth
3c79fd1d6f Update responses.es-ES.json (POEditor.com) 2021-12-09 01:04:46 +00:00
Kwoth
e9f1a9b1dd Update responses.ru-RU.json (POEditor.com) 2021-12-09 01:04:45 +00:00
Kwoth
3c293ae6db Update responses.pt-BR.json (POEditor.com) 2021-12-09 01:04:43 +00:00
Kwoth
a5d9e7de66 Update responses.de-DE.json (POEditor.com) 2021-12-09 01:04:42 +00:00
Kwoth
4d9e48cd41 Update responses.fr-FR.json (POEditor.com) 2021-12-09 01:04:42 +00:00
Kwoth
b7ead22e09 .remindl and .remindrm commands now supports optional 'server' parameter for Administrators which allows them to delete any reminder created on the server 2021-12-09 01:48:39 +01:00
Kwoth
9f219cddbb Updated osx guide with dotnet 5 installation instructions 2021-12-09 01:11:12 +01:00
Kwoth
cf9792f24a Fixed .rule34, closes #320 2021-12-07 23:44:57 +01:00
Kwoth
0187dd57ac Fixed an exception in coordinator preventing some shards from restarting 2021-12-05 08:47:33 +01:00
Kwoth
2d2e54e31e Bot will now check for permissions when trying to apply punishments 2021-12-05 08:47:10 +01:00
Kwoth
cb7c5e48fd Fixed gambling vote reward getting overwritten and reset to 100 every time 2021-12-05 08:20:30 +01:00
Kwoth
771c2745dc .crypto now supports top 5k coins. closes #138 2021-12-04 15:45:04 +01:00
Kwoth
59c0f2f4b3 Added graceful option to die (kill coordinator without killing shards) 2021-12-01 09:47:41 +01:00
Kwoth
219ca39cd1 Added .coordreload which will reload coord.yml when using NadekoBot.Coordinator
- bots with more than 1 shard will now use redis strings provider
2021-12-01 09:41:23 +01:00
Kwoth
1e6d0806d7 Fixed some console log spam which was incorrectly done by streamrole feature, upped version to 3.0.10 in stats 2021-12-01 07:02:01 +01:00
Kwoth
71f1e43272 .xprewsreset now has correct permissions 2021-12-01 05:41:03 +01:00
Kwoth
8499e1da70 Updated changelog, upped version in the stats to 3.0.9 2021-11-24 01:51:34 +01:00
Kwoth
a2ea806bed Removed slot.numbers from images.yml as they're no longer used anywhere, thx ala 2021-11-24 01:49:03 +01:00
Kwoth
732b5dfeed Merge branch 'v3' of https://gitlab.com/kwoth/nadekobot into v3 2021-11-21 03:01:18 +01:00
Kwoth
d4dcdc761a .economy should not overflow so easily anymore, and big numbers look nicer 2021-11-21 03:01:04 +01:00
Kwoth
57996ba290 Merge branch 'take-award-patch' into 'v3'
Change award and take to not use ShmartNumber

See merge request Kwoth/nadekobot!192
2021-11-21 01:37:30 +00:00
Alan Beatty
4b29b3a239 Change award and take to not use ShmartNumber 2021-11-21 01:37:30 +00:00
Kwoth
54ac955395 Merge branch 'xpservicepatch' into 'v3'
Remove deprecated method from XpService.

See merge request Kwoth/nadekobot!191
2021-11-21 01:09:45 +00:00
Kwoth
f4fa298866 Merge branch 'plantpatch' into 'v3'
ShmartNumber for .plant

See merge request Kwoth/nadekobot!193
2021-11-21 01:09:14 +00:00
Kwoth
b2fafc964f Merge branch 'v3' into 'v3'
Move plant/pick where they belong

See merge request Kwoth/nadekobot!178
2021-11-21 01:08:46 +00:00
Kwoth
22b452e449 Added .warn weights, improved .warnlog 2021-11-21 02:01:21 +01:00
Alan Beatty
fda385a5e4 ShmartNumber for .plant 2021-11-20 18:36:05 -06:00
Alan Beatty
c28f7cfa07 Remove deprecated method from XpService. 2021-11-20 17:55:33 -06:00
Kwoth
0a029a7847 Added image attachment support for .ea if you omit imageUrl 2021-11-21 00:22:10 +01:00
Yuno Gasai
717543f6c2 Move plant/pick where they belong 2021-10-19 09:28:55 -04:00
72 changed files with 9690 additions and 3164 deletions

View File

@@ -4,8 +4,48 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
## Unreleased
-
## [3.0.11] - 17.12.2021
### Added
- `.remindl` and `.remindrm` commands now supports optional 'server' parameter for Administrators which allows them to delete any reminder created on the server
- Added slots.currencyFontColor to gambling.yml
- Added `.qexport` and `.qimport` commands which allow you to export and import quotes just like `.crsexport`
- Added `.showembed <msgid>` and `.showembed #channel <msgid>` which will show you embed json from the specified message
### Changed
- `.at` and `.atl` commands reworked
- Persist restarts
- Will now only translate non-commands
- You can switch between `.at del` and `.at` without clearing the user language registrations
- Disabling `.at` will clear all user language registrations on that channel
- Users can't register languages if the `.at` is not enabled
- Looks much nicer
- Bot will now reply to user messages with a translation if `del` is disabled
- Bot will make an embed with original and translated text with user avatar and name if `del` is enabled
- If the bot is unable to delete messages while having `del` enabled, it will reset back to the no-del behavior for the current session
### Fixed
- `.crypto` now supports top 5000 coins
## [3.0.10] - 01.12.2021
### Changed
- `.warn` now supports weighted warnings
- `.warnlog` will now show current amount and total amount of warnings
### Fixed
- `.xprewsreset` now has correct permissions
### Removed
- Removed slot.numbers from `images.yml` as they're no longer used
## [3.0.9] - 21.11.2021
### Changed
- `.ea` will now use an image attachments if you omit imageUrl
### Added
- Added `.emojiadd` with 3 overloads
- `.ea :customEmoji:` which copies another server's emoji

View File

@@ -2,14 +2,33 @@
Open Terminal (if you don't know how to, click on the magnifying glass on the top right corner of your screen and type **Terminal** on the window that pops up) and navigate to the location where you want to install the bot (for example `cd ~`)
##### Installing Homebrew and wget
##### Installing Homebrew, wget and dotnet
###### 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)"`
- Install wget
- `brew install wget`
###### Dotnet
- Download [.net5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0)
- Open the `.pkg` file you've downloaded and install it.
- Run this command in Terminal. There might be output. If there is, disregard it. (copy-paste the entire block)
```bash
sudo mkdir /usr/local/bin
sudo mkdir /usr/local/lib
```
- Run this command in Terminal. There won't be any output. (copy-paste the entire block):
```bash
sudo ln -s /usr/local/share/dotnet/dotnet /usr/local/bin
sudo ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/
sudo ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/
```
##### Installation Instructions
1. Download and run the **new** installer script `cd ~ && wget -N https://gitlab.com/Kwoth/nadeko-bash-installer/-/raw/master/linuxAIO.sh && bash linuxAIO.sh`

View File

@@ -9,9 +9,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.38.0" />
<PackageReference Include="Grpc.AspNetCore" Version="2.41.0" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="YamlDotNet" Version="11.2.1" />
</ItemGroup>

View File

@@ -171,7 +171,7 @@ namespace NadekoBot.Coordinator
}
}
status.Process?.Dispose();
try { status.Process?.Dispose(); } catch { }
var proc = StartShardProcess(shardId);
_shardStatuses[shardId] = status with

View File

@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
</ItemGroup>

View File

@@ -19,6 +19,7 @@ using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Common.Configs;
using NadekoBot.Db;
using NadekoBot.Modules.Administration.Services;
using NadekoBot.Modules.Searches;
using Serilog;
namespace NadekoBot
@@ -105,7 +106,7 @@ namespace NadekoBot
.AddSingleton<ISeria, JsonSeria>()
.AddSingleton<IPubSub, RedisPubSub>()
.AddSingleton<IConfigSeria, YamlSeria>()
.AddBotStringsServices()
.AddBotStringsServices(_creds.TotalShards)
.AddConfigServices()
.AddConfigMigrators()
.AddMemoryCache()

View File

@@ -14,10 +14,14 @@ namespace NadekoBot.Extensions
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddBotStringsServices(this IServiceCollection services)
=> services
public static IServiceCollection AddBotStringsServices(this IServiceCollection services, int totalShards)
=> totalShards <= 1
? services
.AddSingleton<IStringsSource, LocalFileStringsSource>()
.AddSingleton<IBotStringsProvider, LocalBotStringsProvider>()
.AddSingleton<IBotStrings, BotStrings>()
: services.AddSingleton<IStringsSource, LocalFileStringsSource>()
.AddSingleton<IBotStringsProvider, RedisBotStringsProvider>()
.AddSingleton<IBotStrings, BotStrings>();
public static IServiceCollection AddConfigServices(this IServiceCollection services)

View File

@@ -6,7 +6,7 @@ namespace NadekoBot.Common
public class ImageUrls
{
[Comment("DO NOT CHANGE")]
public int Version { get; set; } = 2;
public int Version { get; set; } = 3;
public CoinData Coins { get; set; }
public Uri[] Currency { get; set; }
@@ -27,7 +27,6 @@ namespace NadekoBot.Common
public class SlotData
{
public Uri[] Emojis { get; set; }
public Uri[] Numbers { get; set; }
public Uri Bg { get; set; }
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Linq;
using Discord;
using NadekoBot.Extensions;
using NadekoBot.Services;
@@ -29,6 +30,47 @@ namespace NadekoBot
(Footer != null && (!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl))) ||
(Fields != null && Fields.Length > 0);
public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
{
var set = new SmartEmbedText();
set.PlainText = plainText;
set.Title = eb.Title;
set.Description = eb.Description;
set.Url = eb.Url;
set.Thumbnail = eb.Thumbnail?.Url;
set.Image = eb.Image?.Url;
set.Author = eb.Author is EmbedAuthor ea
? new()
{
Name = ea.Name,
Url = ea.Url,
IconUrl = ea.IconUrl
}
: null;
set.Footer = eb.Footer is EmbedFooter ef
? new()
{
Text = ef.Text,
IconUrl = ef.IconUrl
}
: null;
if (eb.Fields.Length > 0)
set.Fields = eb
.Fields
.Select(field => new SmartTextEmbedField()
{
Inline = field.Inline,
Name = field.Name,
Value = field.Value,
})
.ToArray();
set.Color = eb.Color?.RawValue ?? 0;
return set;
}
public EmbedBuilder GetEmbed()
{
var embed = new EmbedBuilder()

View File

@@ -1,4 +1,5 @@
using NadekoBot.Db.Models;
using System;
using NadekoBot.Db.Models;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Discord;
@@ -168,7 +169,7 @@ VALUES ({userId}, {name}, {discrim}, {avatarId}, {amount}, 0);
public static decimal GetTotalCurrency(this DbSet<DiscordUser> users)
{
return users
.Sum(x => x.CurrencyAmount);
.Sum((Func<DiscordUser, decimal>)(x => x.CurrencyAmount));
}
public static decimal GetTopOnePercentCurrency(this DbSet<DiscordUser> users, ulong botId)

View File

@@ -9,6 +9,11 @@ namespace NadekoBot.Db
{
public static class QuoteExtensions
{
public static IEnumerable<Quote> GetForGuild(this DbSet<Quote> quotes, ulong guildId)
{
return quotes.AsQueryable().Where(x => x.GuildId == guildId);
}
public static IEnumerable<Quote> GetGroup(this DbSet<Quote> quotes, ulong guildId, int page, OrderType order)
{
var q = quotes.AsQueryable().Where(x => x.GuildId == guildId);

View File

@@ -18,5 +18,12 @@ namespace NadekoBot.Db
.OrderBy(x => x.DateAdded)
.Skip(page * 10)
.Take(10);
public static IEnumerable<Reminder> RemindersForServer(this DbSet<Reminder> reminders, ulong serverId, int page)
=> reminders.AsQueryable()
.Where(x => x.ServerId == serverId)
.OrderBy(x => x.DateAdded)
.Skip(page * 10)
.Take(10);
}
}

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace NadekoBot.Services.Database.Models
{
public class AutoTranslateChannel : DbEntity
{
public ulong GuildId { get; set; }
public ulong ChannelId { get; set; }
public bool AutoDelete { get; set; }
public IList<AutoTranslateUser> Users { get; set; } = new List<AutoTranslateUser>();
}
}

View File

@@ -0,0 +1,11 @@
namespace NadekoBot.Services.Database.Models
{
public class AutoTranslateUser : DbEntity
{
public int ChannelId { get; set; }
public AutoTranslateChannel Channel { get; set; }
public ulong UserId { get; set; }
public string Source { get; set; }
public string Target { get; set; }
}
}

View File

@@ -8,5 +8,6 @@
public bool Forgiven { get; set; }
public string ForgivenBy { get; set; }
public string Moderator { get; set; }
public int Weight { get; set; }
}
}

View File

@@ -60,6 +60,8 @@ namespace NadekoBot.Services.Database
public DbSet<WaifuInfo> WaifuInfo { get; set; }
public DbSet<ImageOnlyChannel> ImageOnlyChannels { get; set; }
public DbSet<NsfwBlacklistedTag> NsfwBlacklistedTags { get; set; }
public DbSet<AutoTranslateChannel> AutoTranslateChannels { get; set; }
public DbSet<AutoTranslateUser> AutoTranslateUsers { get; set; }
public NadekoContext(DbContextOptions<NadekoContext> options) : base(options)
{
@@ -196,10 +198,16 @@ namespace NadekoBot.Services.Database
#endregion
#region Warnings
var warn = modelBuilder.Entity<Warning>();
modelBuilder.Entity<Warning>(warn =>
{
warn.HasIndex(x => x.GuildId);
warn.HasIndex(x => x.UserId);
warn.HasIndex(x => x.DateAdded);
warn.Property(x => x.Weight)
.HasDefaultValue(1);
});
#endregion
#region PatreonRewards
@@ -362,6 +370,21 @@ namespace NadekoBot.Services.Database
modelBuilder.Entity<NsfwBlacklistedTag>(nbt => nbt
.HasIndex(x => x.GuildId)
.IsUnique(false));
var atch = modelBuilder.Entity<AutoTranslateChannel>();
atch.HasIndex(x => x.GuildId)
.IsUnique(false);
atch.HasIndex(x => x.ChannelId)
.IsUnique();
atch
.HasMany(x => x.Users)
.WithOne(x => x.Channel)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<AutoTranslateUser>(atu => atu
.HasAlternateKey(x => new { x.ChannelId, x.UserId }));
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,71 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class atlrework : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AutoTranslateChannels",
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),
AutoDelete = table.Column<bool>(type: "INTEGER", nullable: false),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AutoTranslateChannels", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AutoTranslateUsers",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ChannelId = table.Column<int>(type: "INTEGER", nullable: false),
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
Source = table.Column<string>(type: "TEXT", nullable: true),
Target = table.Column<string>(type: "TEXT", nullable: true),
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AutoTranslateUsers", x => x.Id);
table.UniqueConstraint("AK_AutoTranslateUsers_ChannelId_UserId", x => new { x.ChannelId, x.UserId });
table.ForeignKey(
name: "FK_AutoTranslateUsers_AutoTranslateChannels_ChannelId",
column: x => x.ChannelId,
principalTable: "AutoTranslateChannels",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AutoTranslateChannels_ChannelId",
table: "AutoTranslateChannels",
column: "ChannelId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AutoTranslateChannels_GuildId",
table: "AutoTranslateChannels",
column: "GuildId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AutoTranslateUsers");
migrationBuilder.DropTable(
name: "AutoTranslateChannels");
}
}
}

View File

@@ -340,6 +340,62 @@ namespace NadekoBot.Migrations
b.ToTable("AutoCommands");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("AutoDelete")
.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("ChannelId")
.IsUnique();
b.HasIndex("GuildId");
b.ToTable("AutoTranslateChannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ChannelId")
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<string>("Source")
.HasColumnType("TEXT");
b.Property<string>("Target")
.HasColumnType("TEXT");
b.Property<ulong>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasAlternateKey("ChannelId", "UserId");
b.ToTable("AutoTranslateUsers");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.BanTemplate", b =>
{
b.Property<int>("Id")
@@ -1967,6 +2023,11 @@ namespace NadekoBot.Migrations
b.Property<ulong>("UserId")
.HasColumnType("INTEGER");
b.Property<int>("Weight")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(1);
b.HasKey("Id");
b.HasIndex("DateAdded");
@@ -2189,6 +2250,17 @@ namespace NadekoBot.Migrations
b.Navigation("GuildConfig");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateUser", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.AutoTranslateChannel", "Channel")
.WithMany("Users")
.HasForeignKey("ChannelId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Channel");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandAlias", b =>
{
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
@@ -2536,6 +2608,11 @@ namespace NadekoBot.Migrations
b.Navigation("IgnoredChannels");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.AutoTranslateChannel", b =>
{
b.Navigation("Users");
});
modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b =>
{
b.Navigation("AntiAltSetting");

View File

@@ -318,7 +318,7 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task Die()
public async Task Die(bool graceful = false)
{
try
{
@@ -329,7 +329,7 @@ namespace NadekoBot.Modules.Administration
// ignored
}
await Task.Delay(2000).ConfigureAwait(false);
_coord.Die();
_coord.Die(graceful);
}
[NadekoCommand, Aliases]
@@ -507,6 +507,14 @@ namespace NadekoBot.Modules.Administration
await ReplyConfirmLocalizedAsync(strs.bot_strings_reloaded).ConfigureAwait(false);
}
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task CoordReload()
{
await _coord.Reload();
await ctx.OkAsync();
}
private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus)
{
switch (sus)

View File

@@ -41,8 +41,11 @@ namespace NadekoBot.Modules.Administration.Services
}, null, TimeSpan.FromSeconds(0), TimeSpan.FromHours(12));
}
public async Task<WarningPunishment> Warn(IGuild guild, ulong userId, IUser mod, string reason)
public async Task<WarningPunishment> Warn(IGuild guild, ulong userId, IUser mod, int weight, string reason)
{
if (weight <= 0)
throw new ArgumentOutOfRangeException(nameof(weight));
var modName = mod.ToString();
if (string.IsNullOrWhiteSpace(reason))
@@ -57,6 +60,7 @@ namespace NadekoBot.Modules.Administration.Services
Forgiven = false,
Reason = reason,
Moderator = modName,
Weight = weight,
};
int warnings = 1;
@@ -70,7 +74,7 @@ namespace NadekoBot.Modules.Administration.Services
.Warnings
.ForId(guildId, userId)
.Where(w => !w.Forgiven && w.UserId == userId)
.Count();
.Sum(x => x.Weight);
uow.Warnings.Add(warn);
@@ -95,6 +99,10 @@ namespace NadekoBot.Modules.Administration.Services
public async Task ApplyPunishment(IGuild guild, IGuildUser user, IUser mod, PunishmentAction p, int minutes,
ulong? roleId, string reason)
{
if (!await CheckPermission(guild, p))
return;
switch (p)
{
case PunishmentAction.Mute:
@@ -167,6 +175,40 @@ namespace NadekoBot.Modules.Administration.Services
}
}
/// <summary>
/// Used to prevent the bot from hitting 403's when it needs to
/// apply punishments with insufficient permissions
/// </summary>
/// <param name="guild">Guild the punishment is applied in</param>
/// <param name="punish">Punishment to apply</param>
/// <returns>Whether the bot has sufficient permissions</returns>
private async Task<bool> CheckPermission(IGuild guild, PunishmentAction punish)
{
var botUser = await guild.GetCurrentUserAsync();
switch (punish)
{
case PunishmentAction.Mute:
return botUser.GuildPermissions.MuteMembers && botUser.GuildPermissions.ManageRoles;
case PunishmentAction.Kick:
return botUser.GuildPermissions.KickMembers;
case PunishmentAction.Ban:
return botUser.GuildPermissions.BanMembers;
case PunishmentAction.Softban:
return botUser.GuildPermissions.BanMembers; // ban + unban
case PunishmentAction.RemoveRoles:
return botUser.GuildPermissions.ManageRoles;
case PunishmentAction.ChatMute:
return botUser.GuildPermissions.ManageRoles; // adds nadeko-mute role
case PunishmentAction.VoiceMute:
return botUser.GuildPermissions.MuteMembers;
case PunishmentAction.AddRole:
return botUser.GuildPermissions.ManageRoles;
default:
return true;
}
}
public async Task CheckAllWarnExpiresAsync()
{
using (var uow = _db.GetDbContext())

View File

@@ -54,8 +54,17 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
public async Task Warn(IGuildUser user, [Leftover] string reason = null)
public Task Warn(IGuildUser user, [Leftover] string reason = null)
=> Warn(1, user, reason);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
public async Task Warn(int weight, IGuildUser user, [Leftover] string reason = null)
{
if (weight <= 0)
return;
if (!await CheckRoleHierarchy(user))
return;
@@ -76,7 +85,7 @@ namespace NadekoBot.Modules.Administration
WarningPunishment punishment;
try
{
punishment = await _service.Warn(ctx.Guild, user.Id, ctx.User, reason).ConfigureAwait(false);
punishment = await _service.Warn(ctx.Guild, user.Id, ctx.User, weight, reason).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -230,19 +239,29 @@ namespace NadekoBot.Modules.Administration
}
else
{
var descText = GetText(strs.warn_count(
Format.Bold(warnings.Where(x => !x.Forgiven).Sum(x => x.Weight).ToString()),
Format.Bold(warnings.Sum(x => x.Weight).ToString())));
embed.WithDescription(descText);
var i = page * 9;
foreach (var w in warnings)
{
i++;
var name = GetText(strs.warned_on_by(
w.DateAdded.Value.ToString("dd.MM.yyy"),
w.DateAdded.Value.ToString("HH:mm"),
w.DateAdded?.ToString("dd.MM.yyy"),
w.DateAdded?.ToString("HH:mm"),
w.Moderator));
if (w.Forgiven)
name = $"{Format.Strikethrough(name)} {GetText(strs.warn_cleared_by(w.ForgivenBy))}";
embed.AddField($"#`{i}` " + name, w.Reason.TrimTo(1020));
embed.AddField($"#`{i}` " + name,
Format.Code(GetText(strs.warn_weight(w.Weight))) +
'\n' +
w.Reason.TrimTo(1000));
}
}

View File

@@ -310,7 +310,7 @@ namespace NadekoBot.Modules.CustomReactions
_ = ctx.Channel.TriggerTypingAsync();
var serialized = _service.ExportCrs(ctx.Guild?.Id);
using var stream = await serialized.ToStream();
await using var stream = await serialized.ToStream();
await ctx.Channel.SendFileAsync(stream, "crs-export.yml", text: null);
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Cloneable;
using NadekoBot.Common;
using NadekoBot.Common.Yml;
using SixLabors.ImageSharp.PixelFormats;
using YamlDotNet.Serialization;
namespace NadekoBot.Modules.Gambling.Common
@@ -20,6 +21,7 @@ namespace NadekoBot.Modules.Gambling.Common
Generation = new GenerationConfig();
Timely = new TimelyConfig();
Decay = new DecayConfig();
Slots = new SlotsConfig();
}
[Comment(@"DO NOT CHANGE")]
@@ -64,6 +66,9 @@ Set 0 for unlimited")]
[Comment(@"Currency reward per vote.
This will work only if you've set up VotesApi and correct credentials for topgg and/or discords voting")]
public long VoteReward { get; set; } = 100;
[Comment(@"Slot config")]
public SlotsConfig Slots { get; set; }
}
public class CurrencyConfig
@@ -273,6 +278,12 @@ Example: If a waifu is worth 1000, and she receives a negative gift worth 100, h
public decimal NegativeGiftEffect { get; set; } = 0.50M;
}
public sealed partial class SlotsConfig
{
[Comment(@"Hex value of the color which the numbers on the slot image will have.")]
public Rgba32 CurrencyFontColor { get; set; } = SixLabors.ImageSharp.Color.Red;
}
[Cloneable]
public sealed partial class WaifuItemModel
{

View File

@@ -66,11 +66,11 @@ namespace NadekoBot.Modules.Gambling
}
var embed = _eb.Create()
.WithTitle(GetText(strs.economy_state))
.AddField(GetText(strs.currency_owned), ((BigInteger)(ec.Cash - ec.Bot)) + CurrencySign)
.AddField(GetText(strs.currency_owned), ((BigInteger)(ec.Cash - ec.Bot)).ToString("N", _enUsCulture) + CurrencySign)
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
.AddField(GetText(strs.currency_planted), ((BigInteger)ec.Planted) + CurrencySign)
.AddField(GetText(strs.owned_waifus_total), ((BigInteger)ec.Waifus) + CurrencySign)
.AddField(GetText(strs.bot_currency), ec.Bot + CurrencySign)
.AddField(GetText(strs.bot_currency), ec.Bot.ToString("N", _enUsCulture) + CurrencySign)
.AddField(GetText(strs.total), ((BigInteger)(ec.Cash + ec.Planted + ec.Waifus)).ToString("N", _enUsCulture) + CurrencySign)
.WithOkColor();
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
@@ -247,25 +247,33 @@ namespace NadekoBot.Modules.Gambling
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(0)]
public Task Award(ShmartNumber amount, IGuildUser usr, [Leftover] string msg) =>
public Task Award(long amount, IGuildUser usr, [Leftover] string msg) =>
Award(amount, usr.Id, msg);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(1)]
public Task Award(ShmartNumber amount, [Leftover] IGuildUser usr) =>
public Task Award(long amount, [Leftover] IGuildUser usr) =>
Award(amount, usr.Id);
[NadekoCommand, Aliases]
[OwnerOnly]
[Priority(2)]
public async Task Award(ShmartNumber amount, ulong usrId, [Leftover] string msg = null)
public async Task Award(long amount, ulong usrId, [Leftover] string msg = null)
{
if (amount <= 0)
return;
await _cs.AddAsync(usrId,
var usr = await ((DiscordSocketClient)Context.Client).Rest.GetUserAsync(usrId);
if(usr is null)
{
await ReplyErrorLocalizedAsync(strs.user_not_found).ConfigureAwait(false);
return;
}
await _cs.AddAsync(usr,
$"Awarded by bot owner. ({ctx.User.Username}/{ctx.User.Id}) {(msg ?? "")}",
amount,
gamble: (ctx.Client.CurrentUser.Id != usrId)).ConfigureAwait(false);
@@ -275,8 +283,8 @@ namespace NadekoBot.Modules.Gambling
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(2)]
public async Task Award(ShmartNumber amount, [Leftover] IRole role)
[Priority(3)]
public async Task Award(long amount, [Leftover] IRole role)
{
var users = (await ctx.Guild.GetUsersAsync().ConfigureAwait(false))
.Where(u => u.GetRoles().Contains(role))
@@ -284,7 +292,7 @@ namespace NadekoBot.Modules.Gambling
await _cs.AddBulkAsync(users.Select(x => x.Id),
users.Select(x => $"Awarded by bot owner to **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"),
users.Select(x => amount.Value),
users.Select(x => amount),
gamble: true)
.ConfigureAwait(false);
@@ -298,13 +306,13 @@ namespace NadekoBot.Modules.Gambling
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(0)]
public async Task Take(ShmartNumber amount, [Leftover] IRole role)
public async Task Take(long amount, [Leftover] IRole role)
{
var users = (await role.GetMembersAsync()).ToList();
await _cs.RemoveBulkAsync(users.Select(x => x.Id),
users.Select(x => $"Taken by bot owner from **{role.Name}** role. ({ctx.User.Username}/{ctx.User.Id})"),
users.Select(x => amount.Value),
users.Select(x => amount),
gamble: true)
.ConfigureAwait(false);
@@ -318,7 +326,7 @@ namespace NadekoBot.Modules.Gambling
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(1)]
public async Task Take(ShmartNumber amount, [Leftover] IGuildUser user)
public async Task Take(long amount, [Leftover] IGuildUser user)
{
if (amount <= 0)
return;
@@ -333,7 +341,7 @@ namespace NadekoBot.Modules.Gambling
[NadekoCommand, Aliases]
[OwnerOnly]
public async Task Take(ShmartNumber amount, [Leftover] ulong usrId)
public async Task Take(long amount, [Leftover] ulong usrId)
{
if (amount <= 0)
return;

View File

@@ -8,10 +8,11 @@ using NadekoBot.Modules.Gambling.Services;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Common;
namespace NadekoBot.Modules.Games
namespace NadekoBot.Modules.Gambling
{
public partial class Games
public partial class Gambling
{
[Group]
public class PlantPickCommands : GamblingSubmodule<PlantPickService>
@@ -53,7 +54,7 @@ namespace NadekoBot.Modules.Games
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Plant(int amount = 1, string pass = null)
public async Task Plant(ShmartNumber amount, string pass = null)
{
if (amount < 1)
return;
@@ -63,18 +64,17 @@ namespace NadekoBot.Modules.Games
return;
}
var success = await _service.PlantAsync(ctx.Guild.Id, ctx.Channel, ctx.User.Id, ctx.User.ToString(), amount, pass);
if (!success)
{
await ReplyErrorLocalizedAsync(strs.not_enough( CurrencySign));
return;
}
if (((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
{
logService.AddDeleteIgnore(ctx.Message.Id);
await ctx.Message.DeleteAsync().ConfigureAwait(false);
}
var success = await _service.PlantAsync(ctx.Guild.Id, ctx.Channel, ctx.User.Id, ctx.User.ToString(), amount, pass);
if (!success)
{
await ReplyErrorLocalizedAsync(strs.not_enough( CurrencySign));
}
}
[NadekoCommand, Aliases]

View File

@@ -68,9 +68,18 @@ namespace NadekoBot.Modules.Gambling.Services
{
ModifyConfig(c =>
{
c.Version = 3;
c.VoteReward = 100;
});
}
if (_data.Version < 4)
{
ModifyConfig(c =>
{
c.Version = 4;
});
}
}
}
}

View File

@@ -19,6 +19,7 @@ using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using Color = SixLabors.ImageSharp.Color;
namespace NadekoBot.Modules.Gambling
{
@@ -186,6 +187,8 @@ namespace NadekoBot.Modules.Gambling
var numbers = new int[3];
result.Rolls.CopyTo(numbers, 0);
Color fontColor = _config.Slots.CurrencyFontColor;
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
{
TextOptions = new TextOptions()
@@ -194,9 +197,11 @@ namespace NadekoBot.Modules.Gambling
VerticalAlignment = VerticalAlignment.Center,
WrapTextWidth = 140,
}
}, result.Won.ToString(), _fonts.DottyFont.CreateFont(65), SixLabors.ImageSharp.Color.Red,
}, result.Won.ToString(), _fonts.DottyFont.CreateFont(65), fontColor,
new PointF(227, 92)));
var bottomFont = _fonts.DottyFont.CreateFont(50);
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
{
TextOptions = new TextOptions()
@@ -205,7 +210,7 @@ namespace NadekoBot.Modules.Gambling
VerticalAlignment = VerticalAlignment.Center,
WrapTextWidth = 135,
}
}, amount.ToString(), _fonts.DottyFont.CreateFont(50), SixLabors.ImageSharp.Color.Red,
}, amount.ToString(), bottomFont, fontColor,
new PointF(129, 472)));
bgImage.Mutate(x => x.DrawText(new TextGraphicsOptions
@@ -216,7 +221,7 @@ namespace NadekoBot.Modules.Gambling
VerticalAlignment = VerticalAlignment.Center,
WrapTextWidth = 135,
}
}, ownedAmount.ToString(), _fonts.DottyFont.CreateFont(50), SixLabors.ImageSharp.Color.Red,
}, ownedAmount.ToString(), bottomFont, fontColor,
new PointF(325, 472)));
//sw.PrintLap("drew red text");

View File

@@ -763,7 +763,7 @@ namespace NadekoBot.Modules.Music
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
[UserPerm(GuildPerm.Administrator)]
public async Task MusicQuality()
{
var quality = await _service.GetMusicQualityAsync(ctx.Guild.Id);
@@ -772,7 +772,7 @@ namespace NadekoBot.Modules.Music
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
[UserPerm(GuildPerm.Administrator)]
public async Task MusicQuality(QualityPreset preset)
{
await _service.SetMusicQualityAsync(ctx.Guild.Id, preset);

View File

@@ -24,7 +24,7 @@ namespace NadekoBot.Modules.Nsfw.Common
return new();
return images
.Where(img => !string.IsNullOrWhiteSpace(img.Directory) && !string.IsNullOrWhiteSpace(img.Image))
.Where(img => !string.IsNullOrWhiteSpace(img.Image))
.ToList();
}
}

View File

@@ -3,7 +3,7 @@
public class Rule34Object : IImageData
{
public string Image { get; init; }
public string Directory { get; init; }
public int Directory { get; init; }
public string Tags { get; init; }
public int Score { get; init; }

View File

@@ -0,0 +1,12 @@
using System.Text.Json;
namespace SystemTextJsonSamples
{
public class LowerCaseNamingPolicy : JsonNamingPolicy
{
public static LowerCaseNamingPolicy Default = new LowerCaseNamingPolicy();
public override string ConvertName(string name) =>
name.ToLower();
}
}

View File

@@ -20,7 +20,7 @@ namespace NadekoBot.Modules.Searches
var (crypto, nearest) = await _service.GetCryptoData(name).ConfigureAwait(false);
if (nearest != null)
if (nearest is not null)
{
var embed = _eb.Create()
.WithTitle(GetText(strs.crypto_not_found))

View File

@@ -0,0 +1,16 @@
using System.Linq;
using System.Threading.Tasks;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Searches
{
public static class AtlExtensions
{
public static Task<AutoTranslateChannel> GetByChannelId(this IQueryable<AutoTranslateChannel> set, ulong channelId)
=> set
.Include(x => x.Users)
.FirstOrDefaultAsyncEF(x => x.ChannelId == channelId);
}
}

View File

@@ -35,6 +35,9 @@ namespace NadekoBot.Modules.Searches.Services
name = name.ToUpperInvariant();
var cryptos = await CryptoData().ConfigureAwait(false);
if (cryptos is null)
return (null, null);
var crypto = cryptos
?.FirstOrDefault(x => x.Id.ToUpperInvariant() == name || x.Name.ToUpperInvariant() == name
|| x.Symbol.ToUpperInvariant() == name);
@@ -42,7 +45,8 @@ namespace NadekoBot.Modules.Searches.Services
(CryptoResponseData Elem, int Distance)? nearest = null;
if (crypto is null)
{
nearest = cryptos.Select(x => (x, Distance: x.Name.ToUpperInvariant().LevenshteinDistance(name)))
nearest = cryptos
.Select(x => (x, Distance: x.Name.ToUpperInvariant().LevenshteinDistance(name)))
.OrderBy(x => x.Distance)
.Where(x => x.Distance <= 2)
.FirstOrDefault();
@@ -68,19 +72,18 @@ namespace NadekoBot.Modules.Searches.Services
{
try
{
using (var _http = _httpFactory.CreateClient())
{
var strData = await _http.GetStringAsync(new Uri($"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?" +
using var _http = _httpFactory.CreateClient();
var strData = await _http.GetStringAsync(
$"https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?" +
$"CMC_PRO_API_KEY={_creds.CoinmarketcapApiKey}" +
$"&start=1" +
$"&limit=500" +
$"&convert=USD"));
$"&limit=5000" +
$"&convert=USD");
JsonConvert.DeserializeObject<CryptoResponse>(strData); // just to see if its' valid
return strData;
}
}
catch (Exception ex)
{
Log.Error(ex, "Error getting crypto data: {Message}", ex.Message);

View File

@@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Searches
{
public interface ITranslateService
{
public Task<string> Translate(string source, string target, string text = null);
Task<bool> ToggleAtl(ulong guildId, ulong channelId, bool autoDelete);
IEnumerable<string> GetLanguages();
Task<bool?> RegisterUserAsync(ulong userId, ulong channelId, string @from, string to);
Task<bool> UnregisterUser(ulong channelId, ulong userId);
}
}

View File

@@ -1,10 +1,6 @@
using Discord;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common;
using NadekoBot.Common;
using NadekoBot.Modules.Searches.Common;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Extensions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -13,18 +9,14 @@ using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;
using NadekoBot.Db;
using NadekoBot.Modules.Administration;
using Serilog;
using HorizontalAlignment = SixLabors.Fonts.HorizontalAlignment;
using Image = SixLabors.ImageSharp.Image;
@@ -34,71 +26,31 @@ namespace NadekoBot.Modules.Searches.Services
public class SearchesService : INService
{
private readonly IHttpClientFactory _httpFactory;
private readonly DiscordSocketClient _client;
private readonly IGoogleApiService _google;
private readonly DbService _db;
private readonly IImageCache _imgs;
private readonly IDataCache _cache;
private readonly FontProvider _fonts;
private readonly IBotCredentials _creds;
private readonly IEmbedBuilderService _eb;
private readonly NadekoRandom _rng;
public ConcurrentDictionary<ulong, bool> TranslatedChannels { get; } = new ConcurrentDictionary<ulong, bool>();
// (userId, channelId)
public ConcurrentDictionary<(ulong UserId, ulong ChannelId), string> UserLanguages { get; } = new ConcurrentDictionary<(ulong, ulong), string>();
public List<WoWJoke> WowJokes { get; } = new List<WoWJoke>();
public List<MagicItem> MagicItems { get; } = new List<MagicItem>();
private readonly List<string> _yomamaJokes;
public SearchesService(DiscordSocketClient client, IGoogleApiService google,
DbService db, Bot bot, IDataCache cache, IHttpClientFactory factory,
FontProvider fonts, IBotCredentials creds, IEmbedBuilderService eb)
public SearchesService(IGoogleApiService google,
IDataCache cache,
IHttpClientFactory factory,
FontProvider fonts,
IBotCredentials creds)
{
_httpFactory = factory;
_client = client;
_google = google;
_db = db;
_imgs = cache.LocalImages;
_cache = cache;
_fonts = fonts;
_creds = creds;
_eb = eb;
_rng = new NadekoRandom();
//translate commands
_client.MessageReceived += (msg) =>
{
var _ = Task.Run(async () =>
{
try
{
if (!(msg is SocketUserMessage umsg))
return;
if (!TranslatedChannels.TryGetValue(umsg.Channel.Id, out var autoDelete))
return;
var key = (umsg.Author.Id, umsg.Channel.Id);
if (!UserLanguages.TryGetValue(key, out string langs))
return;
var text = await Translate(langs, umsg.Resolve(TagHandling.Ignore))
.ConfigureAwait(false);
if (autoDelete)
try { await umsg.DeleteAsync().ConfigureAwait(false); } catch { }
await umsg.Channel.SendConfirmAsync(_eb, $"{umsg.Author.Mention} `:` "
+ text.Replace("<@ ", "<@", StringComparison.InvariantCulture)
.Replace("<@! ", "<@!", StringComparison.InvariantCulture)).ConfigureAwait(false);
}
catch { }
});
return Task.CompletedTask;
};
//joke commands
if (File.Exists("data/wowjokes.json"))
{
@@ -340,19 +292,6 @@ namespace NadekoBot.Modules.Searches.Services
_rng.Next(1, max).ToString("000") + ".png";
}
public async Task<string> Translate(string langs, string text = null)
{
if (string.IsNullOrWhiteSpace(text))
throw new ArgumentException("Text is empty or null", nameof(text));
var langarr = langs.ToLowerInvariant().Split('>');
if (langarr.Length != 2)
throw new ArgumentException("Langs does not have 2 parts separated by a >", nameof(langs));
var from = langarr[0];
var to = langarr[1];
text = text?.Trim();
return (await _google.Translate(text, from, to).ConfigureAwait(false)).SanitizeMentions(true);
}
private readonly object yomamaLock = new object();
private int yomamaJokeIndex = 0;
public Task<string> GetYomamaJoke()

View File

@@ -0,0 +1,237 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using AngleSharp.Common;
using Discord;
using Discord.Net;
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
namespace NadekoBot.Modules.Searches
{
public sealed class TranslateService : ITranslateService, ILateExecutor, IReadyExecutor, INService
{
private readonly IGoogleApiService _google;
private readonly DbService _db;
private readonly IEmbedBuilderService _eb;
private readonly Bot _bot;
private readonly ConcurrentDictionary<ulong, bool> _atcs = new();
private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, (string From, string To)>> _users = new();
public TranslateService(IGoogleApiService google,
DbService db,
IEmbedBuilderService eb,
Bot bot)
{
_google = google;
_db = db;
_eb = eb;
_bot = bot;
}
public async Task OnReadyAsync()
{
var ctx = _db.GetDbContext();
var guilds = _bot.AllGuildConfigs.Select(x => x.GuildId).ToList();
var cs = await ctx.AutoTranslateChannels
.Include(x => x.Users)
.Where(x => guilds.Contains(x.GuildId))
.ToListAsyncEF();
foreach (var c in cs)
{
_atcs[c.ChannelId] = c.AutoDelete;
_users[c.ChannelId] = new(c.Users.ToDictionary(x => x.UserId, x => (x.Source.ToLower(), x.Target.ToLower())));
}
}
public async Task LateExecute(IGuild guild, IUserMessage msg)
{
if (string.IsNullOrWhiteSpace(msg.Content))
return;
if (msg is IUserMessage { Channel: ITextChannel tch } um)
{
if (!_atcs.TryGetValue(tch.Id, out var autoDelete))
return;
if (!_users.TryGetValue(tch.Id, out var users)
|| !users.TryGetValue(um.Author.Id, out var langs))
return;
var output = await _google.Translate(msg.Content, langs.From, langs.To);
if (string.IsNullOrWhiteSpace(output)
|| msg.Content.Equals(output, StringComparison.InvariantCultureIgnoreCase))
return;
var embed = _eb.Create()
.WithOkColor();
if (autoDelete)
{
embed
.WithAuthor(um.Author.ToString(), um.Author.GetAvatarUrl())
.AddField(langs.From, um.Content)
.AddField(langs.To, output);
await tch.EmbedAsync(embed);
try
{
await um.DeleteAsync();
}
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
{
_atcs.TryUpdate(tch.Id, false, true);
}
return;
}
await um.ReplyAsync(embed: embed
.AddField(langs.To, output)
.Build(),
allowedMentions: AllowedMentions.None);
}
}
public async Task<string> Translate(string source, string target, string text = null)
{
if (string.IsNullOrWhiteSpace(text))
throw new ArgumentException("Text is empty or null", nameof(text));
var res = await _google.Translate(text, source, target).ConfigureAwait(false);
return res.SanitizeMentions(true);
}
public async Task<bool> ToggleAtl(ulong guildId, ulong channelId, bool autoDelete)
{
var ctx = _db.GetDbContext();
var old = await ctx.AutoTranslateChannels
.ToLinqToDBTable()
.FirstOrDefaultAsyncLinqToDB(x => x.ChannelId == channelId);
if (old is null)
{
ctx.AutoTranslateChannels
.Add(new()
{
GuildId = guildId,
ChannelId = channelId,
AutoDelete = autoDelete,
});
await ctx.SaveChangesAsync();
_atcs[channelId] = autoDelete;
_users[channelId] = new();
return true;
}
// if autodelete value is different, update the autodelete value
// instead of disabling
if (old.AutoDelete != autoDelete)
{
old.AutoDelete = autoDelete;
await ctx.SaveChangesAsync();
_atcs[channelId] = autoDelete;
return true;
}
await ctx.AutoTranslateChannels
.ToLinqToDBTable()
.DeleteAsync(x => x.ChannelId == channelId);
await ctx.SaveChangesAsync();
_atcs.TryRemove(channelId, out _);
_users.TryRemove(channelId, out _);
return false;
}
private void UpdateUser(ulong channelId, ulong userId, string from, string to)
{
var dict = _users.GetOrAdd(channelId, new ConcurrentDictionary<ulong, (string, string)>());
dict[userId] = (from, to);
}
public async Task<bool?> RegisterUserAsync(ulong userId, ulong channelId, string from, string to)
{
if (!_google.Languages.ContainsKey(from) || !_google.Languages.ContainsKey(to))
return null;
var ctx = _db.GetDbContext();
var ch = await ctx.AutoTranslateChannels
.GetByChannelId(channelId);
if (ch is null)
return null;
var user = ch.Users
.FirstOrDefault(x => x.UserId == userId);
if (user is null)
{
ch.Users.Add(user = new()
{
Source = from,
Target = to,
UserId = userId,
});
await ctx.SaveChangesAsync();
UpdateUser(channelId, userId, from, to);
return true;
}
// if it's different from old settings, update
if (user.Source != from || user.Target != to)
{
user.Source = from;
user.Target = to;
await ctx.SaveChangesAsync();
UpdateUser(channelId, userId, from, to);
return true;
}
return await UnregisterUser(channelId, userId);
}
public async Task<bool> UnregisterUser(ulong channelId, ulong userId)
{
var ctx = _db.GetDbContext();
var rows = await ctx.AutoTranslateUsers
.ToLinqToDBTable()
.DeleteAsync(x => x.UserId == userId &&
x.Channel.ChannelId == channelId);
if (_users.TryGetValue(channelId, out var inner))
inner.TryRemove(userId, out _);
await ctx.SaveChangesAsync();
return rows > 0;
}
public IEnumerable<string> GetLanguages() => _google.Languages.Select(x => x.Key);
}
}

View File

@@ -2,35 +2,29 @@
using Discord.Commands;
using NadekoBot.Extensions;
using System.Threading.Tasks;
using System.Linq;
using NadekoBot.Common.Attributes;
using NadekoBot.Services;
using NadekoBot.Modules.Searches.Services;
namespace NadekoBot.Modules.Searches
{
public partial class Searches
{
[Group]
public class TranslateCommands : NadekoSubmodule
public class TranslateCommands : NadekoSubmodule<ITranslateService>
{
private readonly SearchesService _searches;
private readonly IGoogleApiService _google;
public TranslateCommands(SearchesService searches, IGoogleApiService google)
{
_searches = searches;
_google = google;
}
[NadekoCommand, Aliases]
public async Task Translate(string langs, [Leftover] string text = null)
public async Task Translate(string from, string to, [Leftover] string text = null)
{
try
{
await ctx.Channel.TriggerTypingAsync().ConfigureAwait(false);
var translation = await _searches.Translate(langs, text).ConfigureAwait(false);
await SendConfirmAsync(GetText(strs.translation) + " " + langs, translation).ConfigureAwait(false);
var translation = await _service.Translate(from, to, text).ConfigureAwait(false);
var embed = _eb.Create(ctx)
.WithOkColor()
.AddField(from, text, false)
.AddField(to, translation, false);
await ctx.Channel.EmbedAsync(embed);
}
catch
{
@@ -38,27 +32,6 @@ namespace NadekoBot.Modules.Searches
}
}
//[NadekoCommand, Usage, Description, Aliases]
//[OwnerOnly]
//public async Task Obfuscate([Leftover] string txt)
//{
// var lastItem = "en";
// foreach (var item in _google.Languages.Except(new[] { "en" }).Where(x => x.Length < 4))
// {
// var txt2 = await _searches.Translate(lastItem + ">" + item, txt);
// await ctx.Channel.EmbedAsync(_eb.Create()
// .WithOkColor()
// .WithTitle(lastItem + ">" + item)
// .AddField("Input", txt)
// .AddField("Output", txt2));
// txt = txt2;
// await Task.Delay(500);
// lastItem = item;
// }
// txt = await _searches.Translate(lastItem + ">en", txt);
// await SendConfirmAsync("Final output:\n\n" + txt);
//}
public enum AutoDeleteAutoTranslate
{
Del,
@@ -68,56 +41,52 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(ChannelPerm.ManageMessages)]
[OwnerOnly]
public async Task AutoTranslate(AutoDeleteAutoTranslate autoDelete = AutoDeleteAutoTranslate.Nodel)
{
var channel = (ITextChannel)ctx.Channel;
if (autoDelete == AutoDeleteAutoTranslate.Del)
{
_searches.TranslatedChannels.AddOrUpdate(channel.Id, true, (key, val) => true);
await ReplyConfirmLocalizedAsync(strs.atl_ad_started).ConfigureAwait(false);
return;
}
if (_searches.TranslatedChannels.TryRemove(channel.Id, out _))
{
await ReplyConfirmLocalizedAsync(strs.atl_stopped).ConfigureAwait(false);
return;
}
if (_searches.TranslatedChannels.TryAdd(channel.Id, autoDelete == AutoDeleteAutoTranslate.Del))
var toggle = await _service.ToggleAtl(ctx.Guild.Id, ctx.Channel.Id, autoDelete == AutoDeleteAutoTranslate.Del);
if (toggle)
{
await ReplyConfirmLocalizedAsync(strs.atl_started).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalizedAsync(strs.atl_stopped).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AutoTransLang([Leftover] string langs = null)
public async Task AutoTransLang()
{
var ucp = (ctx.User.Id, ctx.Channel.Id);
if (string.IsNullOrWhiteSpace(langs))
if (await _service.UnregisterUser(ctx.Channel.Id, ctx.User.Id))
{
if (_searches.UserLanguages.TryRemove(ucp, out langs))
await ReplyConfirmLocalizedAsync(strs.atl_removed).ConfigureAwait(false);
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AutoTransLang(string from, string to)
{
var succ = await _service.RegisterUserAsync(ctx.User.Id,
ctx.Channel.Id,
from.ToLower(),
to.ToLower());
if (succ is null)
{
await ReplyErrorLocalizedAsync(strs.atl_not_enabled);
return;
}
var langarr = langs.ToLowerInvariant().Split('>');
if (langarr.Length != 2)
return;
var from = langarr[0];
var to = langarr[1];
if (!_google.Languages.Contains(from) || !_google.Languages.Contains(to))
if (succ is false)
{
await ReplyErrorLocalizedAsync(strs.invalid_lang).ConfigureAwait(false);
return;
}
_searches.UserLanguages.AddOrUpdate(ucp, langs, (key, val) => langs);
await ReplyConfirmLocalizedAsync(strs.atl_set(from, to));
}
@@ -125,7 +94,7 @@ namespace NadekoBot.Modules.Searches
[RequireContext(ContextType.Guild)]
public async Task Translangs()
{
await ctx.Channel.SendTableAsync(_google.Languages, str => $"{str,-15}", 3).ConfigureAwait(false);
await ctx.Channel.SendTableAsync(_service.GetLanguages(), str => $"{str,-15}", 3).ConfigureAwait(false);
}
}

View File

@@ -32,35 +32,40 @@ namespace NadekoBot.Modules.Utility
var channel = (ITextChannel)ctx.Channel;
guildName = guildName?.ToUpperInvariant();
SocketGuild guild;
if (string.IsNullOrWhiteSpace(guildName))
guild = (SocketGuild)channel.Guild;
else
guild = _client.Guilds.FirstOrDefault(g => g.Name.ToUpperInvariant() == guildName.ToUpperInvariant());
if (guild is null)
return;
var ownername = guild.GetUser(guild.OwnerId);
var textchn = guild.TextChannels.Count();
var voicechn = guild.VoiceChannels.Count();
var ownername = guild.GetUser(guild.OwnerId);
var textchn = guild.TextChannels.Count;
var voicechn = guild.VoiceChannels.Count;
var channels = $@"{GetText(strs.text_channels(textchn))}
{GetText(strs.voice_channels(voicechn))}";
var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(guild.Id >> 22);
var features = string.Join("\n", guild.Features);
var features = string.Join(", ", guild.Features);
if (string.IsNullOrWhiteSpace(features))
features = "-";
var embed = _eb.Create()
.WithAuthor(GetText(strs.server_info))
.WithTitle(guild.Name)
.AddField(GetText(strs.id), guild.Id.ToString(), true)
.AddField(GetText(strs.owner), ownername.ToString(), true)
.AddField(GetText(strs.members), guild.MemberCount.ToString(), true)
.AddField(GetText(strs.text_channels), textchn.ToString(), true)
.AddField(GetText(strs.voice_channels), voicechn.ToString(), true)
.AddField(GetText(strs.channels), channels, true)
.AddField(GetText(strs.created_at), $"{createdAt:dd.MM.yyyy HH:mm}", true)
.AddField(GetText(strs.region), guild.VoiceRegionId.ToString(), true)
.AddField(GetText(strs.roles), (guild.Roles.Count - 1).ToString(), true)
.AddField(GetText(strs.features), features, true)
.AddField(GetText(strs.features), features)
.WithOkColor();
if (Uri.IsWellFormedUriString(guild.IconUrl, UriKind.Absolute))
embed.WithThumbnailUrl(guild.IconUrl);
if (guild.Emotes.Any())
{
embed.AddField(GetText(strs.custom_emojis) + $"({guild.Emotes.Count})",

View File

@@ -7,10 +7,13 @@ using NadekoBot.Db.Models;
using NadekoBot.Extensions;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using NadekoBot.Common.Yml;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Db;
using YamlDotNet.Serialization;
namespace NadekoBot.Modules.Utility
{
@@ -20,10 +23,12 @@ namespace NadekoBot.Modules.Utility
public class QuoteCommands : NadekoSubmodule
{
private readonly DbService _db;
private readonly IHttpClientFactory _http;
public QuoteCommands(DbService db)
public QuoteCommands(DbService db, IHttpClientFactory http)
{
_db = db;
_http = http;
}
[NadekoCommand, Aliases]
@@ -203,7 +208,7 @@ namespace NadekoBot.Modules.Utility
[RequireContext(ContextType.Guild)]
public async Task QuoteDelete(int id)
{
var isAdmin = ((IGuildUser)ctx.Message.Author).GuildPermissions.ManageMessages;
var hasManageMessages = ((IGuildUser)ctx.Message.Author).GuildPermissions.ManageMessages;
var success = false;
string response;
@@ -211,7 +216,7 @@ namespace NadekoBot.Modules.Utility
{
var q = uow.Quotes.GetById(id);
if ((q?.GuildId != ctx.Guild.Id) || (!isAdmin && q.AuthorId != ctx.Message.Author.Id))
if ((q?.GuildId != ctx.Guild.Id) || (!hasManageMessages && q.AuthorId != ctx.Message.Author.Id))
{
response = GetText(strs.quotes_remove_none);
}
@@ -248,6 +253,140 @@ namespace NadekoBot.Modules.Utility
await ReplyConfirmLocalizedAsync(strs.quotes_deleted(Format.Bold(keyword.SanitizeAllMentions()))).ConfigureAwait(false);
}
public class ExportedQuote
{
public static ExportedQuote FromModel(Quote quote)
=> new ExportedQuote()
{
Id = ((kwum)quote.Id).ToString(),
An = quote.AuthorName,
Aid = quote.AuthorId,
Txt = quote.Text
};
public string Id { get; set; }
public string An { get; set; }
public ulong Aid { get; set; }
public string Txt { get; set; }
}
private const string _prependExport =
@"# Keys are keywords, Each key has a LIST of quotes in the following format:
# - id: Alphanumeric id used for commands related to the quote. (Note, when using .quotesimport, a new id will be generated.)
# an: Author name
# aid: Author id
# txt: Quote text
";
private static readonly ISerializer _exportSerializer = new SerializerBuilder()
.WithEventEmitter(args => new MultilineScalarFlowStyleEmitter(args))
.WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance)
.WithIndentedSequences()
.ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults)
.DisableAliases()
.Build();
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task QuotesExport()
{
IEnumerable<Quote> quotes;
using (var uow = _db.GetDbContext())
{
quotes = uow.Quotes
.GetForGuild(ctx.Guild.Id)
.ToList();
}
var crsDict = quotes
.GroupBy(x => x.Keyword)
.ToDictionary(x => x.Key, x => x.Select(ExportedQuote.FromModel));
var text = _prependExport + _exportSerializer
.Serialize(crsDict)
.UnescapeUnicodeCodePoints();
await using var stream = await text.ToStream();
await ctx.Channel.SendFileAsync(stream, "quote-export.yml", text: null);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Ratelimit(300)]
#if GLOBAL_NADEKO
[OwnerOnly]
#endif
public async Task QuotesImport([Leftover]string input = null)
{
input = input?.Trim();
_ = ctx.Channel.TriggerTypingAsync();
if (input is null)
{
var attachment = ctx.Message.Attachments.FirstOrDefault();
if (attachment is null)
{
await ReplyErrorLocalizedAsync(strs.expr_import_no_input);
return;
}
using var client = _http.CreateClient();
input = await client.GetStringAsync(attachment.Url);
if (string.IsNullOrWhiteSpace(input))
{
await ReplyErrorLocalizedAsync(strs.expr_import_no_input);
return;
}
}
var succ = await ImportCrsAsync(ctx.Guild.Id, input);
if (!succ)
{
await ReplyErrorLocalizedAsync(strs.expr_import_invalid_data);
return;
}
await ctx.OkAsync();
}
public async Task<bool> ImportCrsAsync(ulong guildId, string input)
{
Dictionary<string, List<ExportedQuote>> data;
try
{
data = Yaml.Deserializer.Deserialize<Dictionary<string, List<ExportedQuote>>>(input);
if (data.Sum(x => x.Value.Count) == 0)
return false;
}
catch
{
return false;
}
await using var uow = _db.GetDbContext();
foreach (var entry in data)
{
var keyword = entry.Key;
await uow.Quotes
.AddRangeAsync(entry.Value
.Where(quote => !string.IsNullOrWhiteSpace(quote.Txt))
.Select(quote => new Quote()
{
GuildId = guildId,
Keyword = keyword,
Text = quote.Txt,
AuthorId = quote.Aid,
AuthorName = quote.An,
}));
}
await uow.SaveChangesAsync();
return true;
}
}
}
}

View File

@@ -80,22 +80,51 @@ namespace NadekoBot.Modules.Utility
}
}
public enum Server
{
Server = int.MinValue,
Srvr = int.MinValue,
Serv = int.MinValue,
S = int.MinValue,
}
[NadekoCommand, Aliases]
public async Task RemindList(int page = 1)
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(0)]
public Task RemindList(Server _, int page = 1)
=> RemindList(page, true);
[NadekoCommand, Aliases]
[Priority(1)]
public Task RemindList(int page = 1)
=> RemindList(page, false);
private async Task RemindList(int page, bool isServer)
{
if (--page < 0)
return;
var embed = _eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.reminder_list));
.WithTitle(GetText(isServer ? strs.reminder_server_list : strs.reminder_list));
List<Reminder> rems;
using (var uow = _db.GetDbContext())
{
rems = uow.Reminders.RemindersFor(ctx.User.Id, page)
if (isServer)
{
rems = uow.Reminders
.RemindersForServer(ctx.Guild.Id, page)
.ToList();
}
else
{
rems = uow.Reminders
.RemindersFor(ctx.User.Id, page)
.ToList();
}
}
if (rems.Any())
{
@@ -121,18 +150,33 @@ namespace NadekoBot.Modules.Utility
}
[NadekoCommand, Aliases]
public async Task RemindDelete(int index)
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(0)]
public Task RemindDelete(Server _, int index)
=> RemindDelete(index, true);
[NadekoCommand, Aliases]
[Priority(1)]
public Task RemindDelete(int index)
=> RemindDelete(index, false);
private async Task RemindDelete(int index, bool isServer)
{
if (--index < 0)
return;
var embed = _eb.Create();
Reminder rem = null;
using (var uow = _db.GetDbContext())
{
var rems = uow.Reminders.RemindersFor(ctx.User.Id, index / 10)
var rems = isServer
? uow.Reminders
.RemindersForServer(ctx.Guild.Id, index / 10)
.ToList()
: uow.Reminders
.RemindersFor(ctx.User.Id, index / 10)
.ToList();
var pageIndex = index % 10;
if (rems.Count > pageIndex)
{

View File

@@ -227,6 +227,9 @@ namespace NadekoBot.Modules.Utility.Services
private async Task RescanUser(IGuildUser user, StreamRoleSettings setting, IRole addRole = null)
{
if (user.IsBot)
return;
var g = (StreamingGame)user.Activities
.FirstOrDefault(a => a is StreamingGame &&
(string.IsNullOrWhiteSpace(setting.Keyword)
@@ -240,7 +243,7 @@ namespace NadekoBot.Modules.Utility.Services
{
try
{
addRole = addRole ?? user.Guild.GetRole(setting.AddRoleId);
addRole ??= user.Guild.GetRole(setting.AddRoleId);
if (addRole is null)
{
await StopStreamRole(user.Guild).ConfigureAwait(false);
@@ -249,9 +252,12 @@ namespace NadekoBot.Modules.Utility.Services
}
//check if he doesn't have addrole already, to avoid errors
if (!user.RoleIds.Contains(setting.AddRoleId))
if (!user.RoleIds.Contains(addRole.Id))
{
await user.AddRoleAsync(addRole).ConfigureAwait(false);
Log.Information("Added stream role to user {0} in {1} server", user.ToString(), user.Guild.ToString());
Log.Information("Added stream role to user {0} in {1} server", user.ToString(),
user.Guild.ToString());
}
}
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
{

View File

@@ -11,11 +11,14 @@ using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using NadekoBot.Common.Replacements;
using NadekoBot.Services;
using Serilog;
using SystemTextJsonSamples;
using JsonSerializer = Newtonsoft.Json.JsonSerializer;
namespace NadekoBot.Modules.Utility
{
@@ -121,7 +124,7 @@ namespace NadekoBot.Modules.Utility
return _eb.Create().WithOkColor().WithDescription(GetText(strs.no_user_on_this_page));
return _eb.Create().WithOkColor()
.WithTitle(GetText(strs.inrole_list(Format.Bold(role?.Name ?? "No Role") + $" - {roleUsers.Length}")))
.WithTitle(GetText(strs.inrole_list(Format.Bold(role?.Name ?? "No Role"), roleUsers.Length)))
.WithDescription(string.Join("\n", pageUsers));
}, roleUsers.Length, 20).ConfigureAwait(false);
}
@@ -284,26 +287,34 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[RequireBotPermission(GuildPermission.ManageEmojis)]
[RequireUserPermission(GuildPermission.ManageEmojis)]
[Priority(0)]
[BotPerm(GuildPerm.ManageEmojis)]
[UserPerm(GuildPerm.ManageEmojis)]
[Priority(2)]
public Task EmojiAdd(string name, Emote emote)
=> EmojiAdd(name, emote.Url);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[RequireBotPermission(GuildPermission.ManageEmojis)]
[RequireUserPermission(GuildPermission.ManageEmojis)]
[BotPerm(GuildPerm.ManageEmojis)]
[UserPerm(GuildPerm.ManageEmojis)]
[Priority(1)]
public Task EmojiAdd(Emote emote)
=> EmojiAdd(emote.Name, emote.Url);
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[RequireBotPermission(GuildPermission.ManageEmojis)]
[RequireUserPermission(GuildPermission.ManageEmojis)]
public async Task EmojiAdd(string name, string url)
[BotPerm(GuildPerm.ManageEmojis)]
[UserPerm(GuildPerm.ManageEmojis)]
[Priority(0)]
public async Task EmojiAdd(string name, string url = null)
{
name = name.Trim(':');
url ??= ctx.Message.Attachments.FirstOrDefault()?.Url;
if (url is null)
return;
using var http = _httpFactory.CreateClient();
var res = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
if (!res.IsImage() || res.GetImageSize() is null or > 262_144)
@@ -355,6 +366,47 @@ namespace NadekoBot.Modules.Utility
await ctx.Channel.EmbedAsync(embed);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public Task ShowEmbed(ulong messageId)
=> ShowEmbed((ITextChannel)ctx.Channel, messageId);
private static readonly JsonSerializerOptions _showEmbedSerializerOptions = new JsonSerializerOptions()
{
WriteIndented = true,
IgnoreNullValues = true,
PropertyNamingPolicy = LowerCaseNamingPolicy.Default
};
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ShowEmbed(ITextChannel ch, ulong messageId)
{
var user = (IGuildUser)ctx.User;
var perms = user.GetPermissions(ch);
if (!perms.ReadMessageHistory || !perms.ViewChannel)
{
await ReplyErrorLocalizedAsync(strs.insuf_perms_u);
return;
}
var msg = await ch.GetMessageAsync(messageId);
if (msg is null)
{
await ReplyErrorLocalizedAsync(strs.msg_not_found);
return;
}
var embed = msg.Embeds.FirstOrDefault();
if (embed is null)
{
await ReplyErrorLocalizedAsync(strs.not_found);
return;
}
var json = SmartEmbedText.FromEmbed(embed, msg.Content).ToJson(_showEmbedSerializerOptions);
await SendConfirmAsync(Format.Sanitize(json).Replace("](", "]\\("));
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
@@ -425,6 +477,8 @@ namespace NadekoBot.Modules.Utility
New
}
// [NadekoCommand, Usage, Description, Aliases]
// [RequireContext(ContextType.Guild)]
// public async Task CreateMyInvite(CreateInviteType type = CreateInviteType.Any)

View File

@@ -746,27 +746,6 @@ namespace NadekoBot.Modules.Xp.Services
guildRank);
}
public static (int Level, int LevelXp, int LevelRequiredXp) GetLevelData(UserXpStats stats)
{
var baseXp = XpService.XP_REQUIRED_LVL_1;
var required = baseXp;
var totalXp = 0;
var lvl = 1;
while (true)
{
required = (int) (baseXp + baseXp / 4.0 * (lvl - 1));
if (required + totalXp > stats.Xp)
break;
totalXp += required;
lvl++;
}
return (lvl - 1, stats.Xp - totalXp, required);
}
public bool ToggleExcludeServer(ulong id)
{
using (var uow = _db.GetDbContext())

View File

@@ -39,6 +39,8 @@ namespace NadekoBot.Modules.Xp
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task XpRewsReset()
{
var reply = await PromptUserConfirmAsync(_eb.Create()

View File

@@ -9,29 +9,29 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AngleSharp" Version="0.16.0" />
<PackageReference Include="AWSSDK.S3" Version="3.7.1.19" />
<PackageReference Include="AngleSharp" Version="0.16.1" />
<PackageReference Include="AWSSDK.S3" Version="3.7.7.3" />
<PackageReference Include="Cloneable" Version="1.3.0" />
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.1" />
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.2" />
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Discord.Net" Version="2.4.0" />
<PackageReference Include="CoreCLR-NCalc" Version="2.2.92" />
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.41.1.138" />
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.53.0.2378" />
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.55.0.2449" />
<PackageReference Include="Google.Apis.Customsearch.v1" Version="1.49.0.2084" />
<PackageReference Include="Google.Protobuf" Version="3.17.3" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.38.0" />
<PackageReference Include="Grpc.Tools" Version="2.38.1">
<PackageReference Include="Google.Protobuf" Version="3.19.1" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.41.0" />
<PackageReference Include="Grpc.Tools" Version="2.42.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Html2Markdown" Version="4.0.0.427" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.8">
<PackageReference Include="Html2Markdown" Version="5.0.0.468" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.13" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.13">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.13" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
@@ -44,15 +44,13 @@
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Scrutor" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.Seq" Version="5.0.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="5.1.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta0010" />
<PackageReference Include="StackExchange.Redis" Version="2.2.62" />
<PackageReference Include="VideoLibrary" Version="3.1.2" />
<PackageReference Include="StackExchange.Redis" Version="2.2.88" />
<PackageReference Include="YamlDotNet" Version="11.2.1" />
<PackageReference Include="YoutubeExplode" Version="6.0.5" />
<PackageReference Include="linq2db.EntityFrameworkCore" Version="5.4.0" />
<PackageReference Include="linq2db.EntityFrameworkCore" Version="5.9.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,15 +1,17 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace NadekoBot.Services
{
public interface ICoordinator
{
bool RestartBot();
void Die();
void Die(bool graceful);
bool RestartShard(int shardId);
IList<ShardStatus> GetAllShardStatuses();
int GetGuildCount();
Task Reload();
}
public class ShardStatus

View File

@@ -7,7 +7,7 @@ namespace NadekoBot.Services
{
public interface IGoogleApiService : INService
{
IEnumerable<string> Languages { get; }
IReadOnlyDictionary<string, string> Languages { get; }
Task<IEnumerable<string>> GetVideoLinksByKeywordAsync(string keywords, int count = 1);
Task<IEnumerable<(string Name, string Id, string Url)>> GetVideoInfosByKeywordAsync(string keywords, int count = 1);

View File

@@ -14,7 +14,6 @@ namespace NadekoBot.Services
IReadOnlyList<byte[]> Dice { get; }
IReadOnlyList<byte[]> SlotEmojis { get; }
IReadOnlyList<byte[]> SlotNumbers { get; }
IReadOnlyList<byte[]> Currency { get; }
byte[] SlotBackground { get; }

View File

@@ -226,8 +226,7 @@ namespace NadekoBot.Services
return new ImageResult(search.Items[0].Image, search.Items[0].Link);
}
public IEnumerable<string> Languages => _languageDictionary.Keys.OrderBy(x => x);
private readonly Dictionary<string, string> _languageDictionary = new Dictionary<string, string>() {
public IReadOnlyDictionary<string, string> Languages { get; } = new Dictionary<string, string>() {
{ "afrikaans", "af"},
{ "albanian", "sq"},
{ "arabic", "ar"},
@@ -365,8 +364,8 @@ namespace NadekoBot.Services
await Task.Yield();
string text;
if (!_languageDictionary.ContainsKey(sourceLanguage) ||
!_languageDictionary.ContainsKey(targetLanguage))
if (!Languages.ContainsKey(sourceLanguage) ||
!Languages.ContainsKey(targetLanguage))
throw new ArgumentException(nameof(sourceLanguage) + "/" + nameof(targetLanguage));
@@ -385,7 +384,7 @@ namespace NadekoBot.Services
private string ConvertToLanguageCode(string language)
{
_languageDictionary.TryGetValue(language, out var mode);
Languages.TryGetValue(language, out var mode);
return mode;
}
}

View File

@@ -36,7 +36,6 @@ namespace NadekoBot.Services
Dice,
SlotBg,
SlotEmojis,
SlotNumbers,
Currency,
RategirlMatrix,
RategirlDot,
@@ -57,9 +56,6 @@ namespace NadekoBot.Services
public IReadOnlyList<byte[]> SlotEmojis
=> GetByteArrayData(ImageKeys.SlotEmojis);
public IReadOnlyList<byte[]> SlotNumbers
=> GetByteArrayData(ImageKeys.SlotNumbers);
public IReadOnlyList<byte[]> Currency
=> GetByteArrayData(ImageKeys.Currency);
@@ -157,20 +153,7 @@ namespace NadekoBot.Services
"https://cdn.nadeko.bot/slots/3.png",
"https://cdn.nadeko.bot/slots/4.png",
"https://cdn.nadeko.bot/slots/5.png"
}.Map(x => new Uri(x)),
Numbers = new[]
{
"https://cdn.nadeko.bot/other/slots/numbers/0.png",
"https://cdn.nadeko.bot/other/slots/numbers/1.png",
"https://cdn.nadeko.bot/other/slots/numbers/2.png",
"https://cdn.nadeko.bot/other/slots/numbers/3.png",
"https://cdn.nadeko.bot/other/slots/numbers/4.png",
"https://cdn.nadeko.bot/other/slots/numbers/5.png",
"https://cdn.nadeko.bot/other/slots/numbers/6.png",
"https://cdn.nadeko.bot/other/slots/numbers/7.png",
"https://cdn.nadeko.bot/other/slots/numbers/8.png",
"https://cdn.nadeko.bot/other/slots/numbers/9.png"
}.Map(x => new Uri(x)),
}.Map(x => new Uri(x))
},
Xp = new ImageUrls.XpData()
{
@@ -183,6 +166,14 @@ namespace NadekoBot.Services
File.WriteAllText(_imagesPath, Yaml.Serializer.Serialize(newData));
}
}
// removed numbers from slots
var localImageUrls = Yaml.Deserializer.Deserialize<ImageUrls>(File.ReadAllText(_imagesPath));
if (localImageUrls.Version == 2)
{
localImageUrls.Version = 3;
File.WriteAllText(_imagesPath, Yaml.Serializer.Serialize(localImageUrls));
}
}
public async Task Reload()
@@ -207,9 +198,6 @@ namespace NadekoBot.Services
case ImageKeys.SlotEmojis:
await Load(key, ImageUrls.Slots.Emojis);
break;
case ImageKeys.SlotNumbers:
await Load(key, ImageUrls.Slots.Numbers);
break;
case ImageKeys.Currency:
await Load(key, ImageUrls.Currency);
break;

View File

@@ -39,11 +39,11 @@ namespace NadekoBot.Services
return true;
}
public void Die()
public void Die(bool graceful)
{
_coordClient.Die(new DieRequest()
{
Graceful = false
Graceful = graceful
});
}
@@ -79,6 +79,11 @@ namespace NadekoBot.Services
return res.Statuses.Sum(x => x.GuildCount);
}
public async Task Reload()
{
await _coordClient.ReloadAsync(new());
}
public Task OnReadyAsync()
{
Task.Run(async () =>

View File

@@ -36,7 +36,7 @@ namespace NadekoBot.Services
return true;
}
public void Die()
public void Die(bool graceful = false)
{
Environment.Exit(5);
}
@@ -64,5 +64,10 @@ namespace NadekoBot.Services
{
return _client.Guilds.Count;
}
public Task Reload()
{
return Task.CompletedTask;
}
}
}

View File

@@ -20,7 +20,7 @@ namespace NadekoBot.Services
private readonly IBotCredentials _creds;
private readonly DateTime _started;
public const string BotVersion = "3.0.8";
public const string BotVersion = "3.0.11";
public string Author => "Kwoth#2452";
public string Library => "Discord.Net";
public double MessagesPerSecond => MessageCounter / GetUptime().TotalSeconds;

View File

@@ -22,10 +22,12 @@ using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using NadekoBot.Common.Attributes;
using Color = Discord.Color;
using JsonSerializer = System.Text.Json.JsonSerializer;
namespace NadekoBot.Extensions
{
@@ -286,8 +288,8 @@ namespace NadekoBot.Extensions
public static async Task<IEnumerable<IGuildUser>> GetMembersAsync(this IRole role) =>
(await role.Guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false)).Where(u => u.RoleIds.Contains(role.Id)) ?? Enumerable.Empty<IGuildUser>();
public static string ToJson<T>(this T any, Formatting formatting = Formatting.Indented) =>
JsonConvert.SerializeObject(any, formatting);
public static string ToJson<T>(this T any, JsonSerializerOptions options = null) =>
JsonSerializer.Serialize(any, options);
/// <summary>
/// Adds fallback fonts to <see cref="TextOptions"/>

View File

@@ -1263,3 +1263,13 @@ imageonlychannel:
- imageonlychannel
- imageonly
- imagesonly
coordreload:
- coordreload
quotesexport:
- quotesexport
- qexport
quotesimport:
- quotesimport
- qimport
showembed:
- showembed

View File

@@ -1,5 +1,5 @@
# DO NOT CHANGE
version: 2
version: 4
# Currency settings
currency:
# What is the emoji/character which represents the currency
@@ -240,3 +240,7 @@ patreonCurrencyPerCent: 1
# Currency reward per vote.
# This will work only if you've set up VotesApi and correct credentials for topgg and/or discords voting
voteReward: 100
# Slot config
slots:
# Hex value of the color which the numbers on the slot image will have.
currencyFontColor: ff0000

View File

@@ -1,5 +1,5 @@
# DO NOT CHANGE
version: 2
version: 3
coins:
heads:
- https://cdn.nadeko.bot/coins/heads3.png
@@ -36,15 +36,4 @@ slots:
- https://cdn.nadeko.bot/slots/3.png
- https://cdn.nadeko.bot/slots/4.png
- https://cdn.nadeko.bot/slots/5.png
numbers:
- https://cdn.nadeko.bot/other/slots/numbers/0.png
- https://cdn.nadeko.bot/other/slots/numbers/1.png
- https://cdn.nadeko.bot/other/slots/numbers/2.png
- https://cdn.nadeko.bot/other/slots/numbers/3.png
- https://cdn.nadeko.bot/other/slots/numbers/4.png
- https://cdn.nadeko.bot/other/slots/numbers/5.png
- https://cdn.nadeko.bot/other/slots/numbers/6.png
- https://cdn.nadeko.bot/other/slots/numbers/7.png
- https://cdn.nadeko.bot/other/slots/numbers/8.png
- https://cdn.nadeko.bot/other/slots/numbers/9.png
bg: https://cdn.nadeko.bot/slots/slots_bg.png

View File

@@ -406,13 +406,15 @@ remind:
- "me 1d5h Do something"
- "#general 1m Start now!"
reminddelete:
desc: "Deletes a reminder on the specified index."
desc: "Deletes a reminder on the specified index. You can specify 'server' option if you're an Administrator, and you want to delete a reminder on this server created by someone else. "
args:
- "3"
- "server 2"
remindlist:
desc: "Lists all reminders you created. Paginated."
desc: "Lists all reminders you created. You can specify 'server' option if you're an Administrator to list all reminders created on this server. Paginated."
args:
- "1"
- "server 2"
serverinfo:
desc: "Shows info about the server the bot is on. If no server is supplied, it defaults to current one."
args:
@@ -609,7 +611,7 @@ quoteid:
args:
- "123456"
quotedelete:
desc: "Deletes a quote with the specified ID. You have to be either server Administrator or the creator of the quote to delete it."
desc: "Deletes a quote with the specified ID. You have to either have the Manage Messages permission or be the creator of the quote to delete it."
args:
- "123456"
draw:
@@ -1200,6 +1202,7 @@ emojiadd:
Adds the specified emoji to this server.
You can specify a name before the emoji to add it under a different name.
You can specify a name followed by an image link to add a new emoji from an image.
You can omit imageUrl and instead upload the image as an attachment.
Image size has to be below 256KB.
args:
- ":someonesCustomEmoji:"
@@ -1303,7 +1306,7 @@ poll:
autotranslang:
desc: "Sets your source and target language to be used with `{0}at`. Specify no parameters to remove previously set value."
args:
- "en>fr"
- "en fr"
autotranslate:
desc: "Starts automatic translation of all messages by users who set their `{0}atl` in this channel. You can set \"del\" parameter to automatically delete all translated user messages."
args:
@@ -1575,6 +1578,14 @@ crsexport:
desc: "Exports custom reactions from the current server (or global custom reactions in DMs) into a .yml file"
args:
- ""
quotesimport:
desc: "Upload the file or send the raw .yml data with this command to import all quotes from the specified string or file into the current server."
args:
- "<upload .yml file>"
quotesexport:
desc: "Exports quotes from the current server into a .yml file"
args:
- ""
aliaslist:
desc: "Shows the list of currently set aliases. Paginated."
args:
@@ -1595,9 +1606,12 @@ warnlogall:
- ""
- "2"
warn:
desc: "Warns a user."
desc: |-
Warns a user with an optional reason.
You can specify a warning weight integer before the user. For example, 3 would mean that this warning counts as 3 warnings.
args:
- "@Someone Very rude person"
- "3 @Someone Very rude person"
scadd:
desc: "Adds a command to the list of commands which will be executed automatically in the current channel, in the order they were added in, by the bot when it startups up."
args:
@@ -2062,7 +2076,7 @@ rollduel:
- "50 @Someone"
- "@Challenger"
reactionroles:
desc: "Specify role names and server emojis with which they're represented, the bot will then add those emojis to the previous message in the channel, and users will be able to get the roles by clicking on the emoji. You can set 'excl' as the parameter before the reactions and roles to make them exclusive. You can have up to 5 of these enabled on one server at a time. Optionally you can specify target message if you don't want it to be the previous one."
desc: "Specify role names and server emojis with which they're represented, the bot will then add those emojis to the previous message in the channel, and users will be able to get the roles by clicking on the emoji. You can set 'excl' as the parameter before the reactions and roles to make them exclusive. You can have up to 10 of these enabled on one server at a time. Optionally you can specify target message if you don't want it to be the previous one."
args:
- "Gamer :SomeServerEmoji: Streamer :Other: Watcher :Other2:"
- "excl Horde :Horde: Alliance :Alliance:"
@@ -2135,3 +2149,12 @@ imageonlychannel:
Users who send more than a few non-image messages will be banned from using the channel.
args:
- ""
coordreload:
desc: "Reloads coordinator config"
args:
- ""
showembed:
desc: "Prints the json equivalent of the embed of the message specified by its Id."
args:
- "820022733172121600"
- "#some-channel 820022733172121600"

View File

@@ -593,8 +593,6 @@
"log_all": "Alle Events werden nun in diesem Channel geloggt.",
"log_disabled": "Protokollierung ausgeschaltet.",
"log_events": "Protokoll Ereignisse die du Abonnieren kannst:",
"log_ignore": "Protokollierung wird {0} ignorieren",
"log_not_ignore": "Protokollierung wird {0} nicht ignorieren",
"log_stop": "Protokollierung des {0} Ereignisses wurde gestoppt.",
"msg_not_found": "Nachricht wurde nicht gefunden.",
"time_too_long": "Die spezifizierte Zeit ist zu lang.",
@@ -953,5 +951,29 @@
"empty_page": "Diese Seite ist leer.",
"pages": "Seiten",
"favorites": "Favoriten",
"tags": "Stichworte"
"tags": "Stichworte",
"invalid_emoji_link": "",
"emoji_add_error": "",
"emoji_added": "",
"boost_on": "",
"boost_off": "",
"boostmsg_cur": "",
"boostmsg_enable": "",
"boostmsg_new": "",
"boostdel_off": "",
"boostdel_on": "",
"log_ignored_channels": "",
"log_ignored_users": "",
"log_ignore_user": "",
"log_not_ignore_user": "",
"log_ignore_chan": "",
"log_not_ignore_chan": "",
"streams_cleared": "",
"warn_weight": "",
"warn_count": "",
"mass_ban_in_progress": "",
"mass_ban_completed": "",
"reminder_server_list": "",
"imageonly_enable": "",
"imageonly_disable": ""
}

View File

@@ -450,6 +450,7 @@
"atl_set": "Your auto-translate language has been set to {0}>{1}",
"atl_started": "Started automatic translation of messages on this channel.",
"atl_stopped": "Stopped automatic translation of messages on this channel.",
"atl_not_enabled": "Automatic translation is not enabled on this channel or you've provided an invalid language.",
"bad_input_format": "Bad input format, or something went wrong.",
"card_not_found": "Couldn't find that card.",
"catfact": "fact",
@@ -555,7 +556,7 @@
"error": "Error",
"features": "Features",
"index_out_of_range": "Index out of range.",
"inrole_list": "List of users in {0} role",
"inrole_list": "List of users in {0} role ({1})",
"joined_discord": "Joined Discord",
"joined_server": "Joined server",
"listservers": "ID: {0}\nMembers: {1}\nOwner ID: {2}",
@@ -605,12 +606,13 @@
"shard": "Shard",
"showemojis": "**Name:** {0} **Link:** {1}",
"showemojis_none": "No special emojis found.",
"text_channels": "Text channels",
"channels": "Channels",
"text_channels": "Text: {0}",
"voice_channels": "Voice: {0}",
"uptime": "Uptime",
"userid": "{0} of the user {1} is {2}",
"roleid": "{0} of the role {1} is {2}",
"users": "Users",
"voice_channels": "Voice channels",
"current_poll_results": "Current poll results",
"poll_already_running": "Poll is already running on this server.",
"poll_created": "📃 {0} has created a poll",
@@ -665,6 +667,8 @@
"warning_clear_fail": "Warning not cleared. Either the warning at that index doesn't exist, or it has already been cleared.",
"warning_cleared": "Warning {0} has been cleared for {1}.",
"warnings_none": "No warning on this page.",
"warn_weight": "Weight: {0}",
"warn_count": "{0} current, {1} total",
"warnlog_for": "Warnlog for {0}",
"warnpl_none": "No punishments set.",
"warn_expire_set_delete": "Warnings will be deleted after {0} days.",
@@ -680,7 +684,7 @@
"clpa_obsolete": ":tada: **Patreon currency rewards are now automatic!** :tada:\nThis command is now obsolete.\nIf you did not receive your reward for this month's pledge, below are some of the reasons as to why that might be.",
"clpa_fail_already": "Maybe you've already received your reward for this month. You can receive rewards only once a month unless you increase your pledge.\nYou can check it by using `.curtrs` command.",
"clpa_fail_already_title": "Already rewarded",
"clpa_fail_conn": "Your discord account might not be connected to Patreon. If you are unsure what that means, or don't know how to connect it - you have to go to [Patreon account settings page](https://patreon.com/settings/account) and click 'Connect to discord' button.",
"clpa_fail_conn": "Your discord account might not be connected to Patreon. If you are unsure what that means, or don't know how to connect it - you have to go to [Patreon account settings page](https://www.patreon.com/settings/apps) and click 'Connect to discord' button.",
"clpa_fail_conn_title": "Discord account not connected",
"clpa_fail_sup": "In order to be eligible for the reward, you must support the project on patreon. You can use {0} command to get the link.",
"clpa_fail_sup_title": "Not supporting",
@@ -917,6 +921,7 @@
"reaction_role_removed": "Removed ReactionRole message #{0}",
"reaction_roles_full": "You've reached the limit on ReactionRole messages. You have to delete some.",
"reminder_list": "List of reminders",
"reminder_server_list": "List of server reminders",
"reminder_deleted": "Reminder #{0} was deleted.",
"reminder_not_exist": "Reminder at that index does not exist.",
"reminders_none": "No reminder on this page.",

View File

@@ -177,7 +177,7 @@
"cleverbot_enabled": "Cleverbot activado en este canal.",
"curgen_disabled": "La generación de moneda ha sido desactivada en este canal.",
"curgen_enabled": "La generación de moneda ha sido habilitada en este canal.",
"curgen_pl": "¡{0} {1} han aparecido!",
"curgen_pl": "¡{0} {1} ha aparecido!",
"curgen_sn": "¡Ha aparecido {0}!",
"game_started": "Juego iniciado",
"hangman_game_started": "Juego del ahorcado iniciado",
@@ -593,8 +593,6 @@
"log_all": "Registrando todos los eventos en este canal.",
"log_disabled": "Registros desactivados.",
"log_events": "Registrar los eventos que puedes seguir en:",
"log_ignore": "Los registros ignorarán {0}",
"log_not_ignore": "Los registros no ignorarán {0}",
"log_stop": "Se detuvo el registro del evento {0}.",
"msg_not_found": "Mensaje no encontrado.",
"time_too_long": "El tiempo especificado es mucho.",
@@ -805,7 +803,7 @@
"warn_expire_set_clear": "Las advertencias se reiniciarán cada {0} días.",
"warn_expire_reset": "Las advertencias ya no expirarán.",
"warn_punish_set_timed": "Aplicaré el castigo {0} por {2} a los usuarios con {1} advertencias.",
"clpa_obsolete": ":tada: **¡Las recompensas de Patreon ahora son automáticas!** :tada:\nEste comando está obsoleto.\nSi no recibiste tu recompensa de este mes, abajo tienes alguna de las posibles razones.",
"clpa_obsolete": ":tada: **¡Las recompensas de Patreon ahora son automáticas!** :tada:\nEste comando está obsoleto.\nSi no recibiste tu recompensa de este mes, abajo tienes algunas de las posibles razones.",
"time_new": "Tiempo",
"timezone_db_api_key": "Necesitas activar tu clave de API de TimezoneDB. Puedes hacerlo haciendo click en el link que recibiste en tu e-mail con tu clave de API.",
"rolehoist_enabled": "El rol {0} ahora se muestra separado de los miembros en línea.",
@@ -934,7 +932,7 @@
"module_page_empty": "No hay módulos en esta pagina",
"module_description_help": "Obtén ayuda con los comandos, descripciones y ejemplos de uso.",
"module_description_gambling": "Apuesta a las tiradas de dados, al blackjack, a las tragamonedas, al lanzamiento de monedas y a otros\n",
"module_description_games": "Juega a la trivia, al nunchi, al colgado, conecta4 y otros juegos.",
"module_description_games": "Juega al trivial, al nunchi, al colgado, conecta4 y a otros juegos.",
"module_description_nsfw": "Comandos NSFW",
"module_description_music": "Reproduce música de YouTube, archivos locales, SoundCloud y radios.",
"module_description_utility": "Administra citas personalizadas, repeticiones de mensajes y revisa datos sobre el servidor",
@@ -953,5 +951,29 @@
"empty_page": "Esta página está vacía.",
"pages": "Páginas",
"favorites": "Favoritos",
"tags": "Etiquetas"
"tags": "Etiquetas",
"invalid_emoji_link": "",
"emoji_add_error": "",
"emoji_added": "",
"boost_on": "",
"boost_off": "",
"boostmsg_cur": "",
"boostmsg_enable": "",
"boostmsg_new": "",
"boostdel_off": "",
"boostdel_on": "",
"log_ignored_channels": "",
"log_ignored_users": "",
"log_ignore_user": "",
"log_not_ignore_user": "",
"log_ignore_chan": "",
"log_not_ignore_chan": "",
"streams_cleared": "",
"warn_weight": "",
"warn_count": "",
"mass_ban_in_progress": "",
"mass_ban_completed": "",
"reminder_server_list": "",
"imageonly_enable": "",
"imageonly_disable": ""
}

View File

@@ -590,11 +590,9 @@
"lang_set_fail": "Échec de la définition des paramètres régionaux. Revoyez l'aide de cette commande.",
"lang_set_show": "La langue de ce serveur est défini sur {0} - {1}",
"log": "Journalisation des évènements de {0} dans ce salon.",
"log_all": "Journalisation de tout les évènements dans ce salon.",
"log_all": "Journalisation de tous les évènements dans ce salon.",
"log_disabled": "Journalisation désactivée.",
"log_events": "Evènements de journalisation auxquels vous pouvez vous abonner :",
"log_ignore": "La journalisation ignorera {0}",
"log_not_ignore": "La journalisation n'ignorera pas {0}",
"log_stop": "La journalisation de l'évènement {0} arrêtée.",
"msg_not_found": "Message introuvable.",
"time_too_long": "Le temps spécifié est trop long.",
@@ -953,5 +951,29 @@
"empty_page": "Cette page est vide.",
"pages": "Pages",
"favorites": "Favoris",
"tags": "Tags"
"tags": "Tags",
"invalid_emoji_link": "Le lien spécifié n'est pas une image ou excède 256KB.",
"emoji_add_error": "Erreur lors de l'ajout d'emoji. Soit vous n'avez plus d'emplacements pour emoji, soit la taille de l'image est inadéquate.",
"emoji_added": "Nouveau emoji ajouté: {0}",
"boost_on": "Annonces Boost activées dans ce salon.",
"boost_off": "Annonces Boost désactivées.",
"boostmsg_cur": "Message Boost actuel: {0}",
"boostmsg_enable": "Activez les messages Boost en tapant {0}",
"boostmsg_new": "Nouveau message Boost défini.",
"boostdel_off": "La suppression automatique des messages Boost a été désactivée.",
"boostdel_on": "Les messages Boost seront supprimés après {0} secondes.",
"log_ignored_channels": "Salons ignorés",
"log_ignored_users": "Utilisateurs ignorés",
"log_ignore_user": "La journalisation ignorera le membre {0}",
"log_not_ignore_user": "La journalisation n'ignorera plus le membre {0}",
"log_ignore_chan": "La journalisation ignorera le salon {0}",
"log_not_ignore_chan": "La journalisation n'ignorera plus le salon {0}",
"streams_cleared": "Tous les streams suivis sur ce server ont été supprimés.",
"warn_weight": "Poids: {0}",
"warn_count": "{0} actuel(s), {1} total",
"mass_ban_in_progress": "En train de bannir {0} membres...",
"mass_ban_completed": "{0} membres bannis.",
"reminder_server_list": "Liste des rappels du server.",
"imageonly_enable": "Ce salon est maintenant exclusivement pour les images.",
"imageonly_disable": "Ce salon n'est plus exclusivement pour les images."
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -593,8 +593,6 @@
"log_all": "Registrando todos os eventos nesse canal.",
"log_disabled": "Registros desabilitado.",
"log_events": "Eventos de registro que você pode assinar:",
"log_ignore": "Registros vai ignorar {0}",
"log_not_ignore": "Registros não irá ignorar {0}",
"log_stop": "Parando de registrar evento {0}.",
"msg_not_found": "Mensagem não encontrada.",
"time_too_long": "O tempo que você especificou é muito longo.",
@@ -847,7 +845,7 @@
"stopped": "Repost encerrado.",
"restart_fail": "Você precisa configurar o RestartCommand no arquivo creds.yml",
"restarting": "Reiniciando.",
"edit_fail": "Não existe reação personalizada com essa ID.",
"edit_fail": "Não existe reação personalizada com esse ID.",
"streaming": "Transmitindo",
"rafflecur": "Rifa {0}",
"rafflecur_joined": "O usuário {0} entrou no sorteio",
@@ -953,5 +951,29 @@
"empty_page": "Essa página esta vazia.",
"pages": "Páginas",
"favorites": "Favoritos",
"tags": "Etiquetas"
"tags": "Etiquetas",
"invalid_emoji_link": "O link especificado não retorna para uma imagem ou excede 256KB.",
"emoji_add_error": "Erro ao adicionar emoji. O servidor não possui mais slots de emoji disponíveis ou o tamanho da imagem é incompatível.",
"emoji_added": "Novo emoji adicionado: {0}",
"boost_on": "Os anúncios de impulsionamento foram ativados neste canal.",
"boost_off": "Os anúncios de impulsionamento foram desativados.",
"boostmsg_cur": "Mensagem atual de impulsionamento: {0}",
"boostmsg_enable": "Ative os anúncios de impulsionamento digitando {0}",
"boostmsg_new": "Nova mensagem de impulsionamento definida.",
"boostdel_off": "A deleção automática dos anúncios de impulsionamento foi desativada.",
"boostdel_on": "Anúncios de impulsionamento serão deletados após {0} segundos.",
"log_ignored_channels": "Canais ignorados",
"log_ignored_users": "Usuários ignorados",
"log_ignore_user": "O registro de logs passará a ignorar o usuário {0}",
"log_not_ignore_user": "O registro de logs não vai mais ignorar o usuário {0}",
"log_ignore_chan": "O registro de logs passará a ignorar o canal {0}",
"log_not_ignore_chan": "O registro de logs não vai mais ignorar o canal {0}",
"streams_cleared": "Todas as transmissões seguidas neste servidor foram removidas.",
"warn_weight": "Peso: {0}",
"warn_count": "{0} atual, {1} total",
"mass_ban_in_progress": "Banindo {0} usuários...",
"mass_ban_completed": "{0} usuários banidos.",
"reminder_server_list": "Lista de lembretes do servidor",
"imageonly_enable": "Agora este canal é exclusivo para imagens.",
"imageonly_disable": "Este canal não é mais exclusivo para imagens."
}

View File

@@ -593,8 +593,6 @@
"log_all": "Регистрация всех событий в этом канале.",
"log_disabled": "Регистрация событий отключена.",
"log_events": "Журнал событий, на которые вы можете подписаться:",
"log_ignore": "Ведение журнала игнорирует {0}",
"log_not_ignore": "Ведение журнала не игнорирует {0}",
"log_stop": "Прекращено ведение журнала события {0}.",
"msg_not_found": "Сообщение не найдено.",
"time_too_long": "Вы указали слишком много времени.",
@@ -953,5 +951,29 @@
"empty_page": "Эта страница пуста.",
"pages": "Страницы",
"favorites": "Любимое",
"tags": "Теги"
"tags": "Теги",
"invalid_emoji_link": "",
"emoji_add_error": "",
"emoji_added": "",
"boost_on": "",
"boost_off": "",
"boostmsg_cur": "",
"boostmsg_enable": "",
"boostmsg_new": "",
"boostdel_off": "",
"boostdel_on": "",
"log_ignored_channels": "",
"log_ignored_users": "",
"log_ignore_user": "",
"log_not_ignore_user": "",
"log_ignore_chan": "",
"log_not_ignore_chan": "",
"streams_cleared": "",
"warn_weight": "",
"warn_count": "",
"mass_ban_in_progress": "",
"mass_ban_completed": "",
"reminder_server_list": "",
"imageonly_enable": "",
"imageonly_disable": ""
}

View File

@@ -593,8 +593,6 @@
"log_all": "Запис усіх подій на цьому каналі.",
"log_disabled": "Запис подій вимкнено.",
"log_events": "Журнал подій, на які можна підписатися:",
"log_ignore": "Запис подій ігноруватиметься {0}",
"log_not_ignore": "Запис подій не ігноруватиметься {0}",
"log_stop": "Зупинено запис подій {0}.",
"msg_not_found": "Повідомлення не знайдено.",
"time_too_long": "Вказаний Вами час надто довгий.",
@@ -953,5 +951,29 @@
"empty_page": "Ця сторінка пуста.",
"pages": "Сторінки",
"favorites": "Улюблене",
"tags": "Теги"
"tags": "Теги",
"invalid_emoji_link": "",
"emoji_add_error": "",
"emoji_added": "",
"boost_on": "",
"boost_off": "",
"boostmsg_cur": "",
"boostmsg_enable": "",
"boostmsg_new": "",
"boostdel_off": "",
"boostdel_on": "",
"log_ignored_channels": "",
"log_ignored_users": "",
"log_ignore_user": "",
"log_not_ignore_user": "",
"log_ignore_chan": "",
"log_not_ignore_chan": "",
"streams_cleared": "",
"warn_weight": "",
"warn_count": "",
"mass_ban_in_progress": "",
"mass_ban_completed": "",
"reminder_server_list": "",
"imageonly_enable": "",
"imageonly_disable": ""
}