Merge pull request #5 from Kwoth/dev

ups
This commit is contained in:
samvaio 2017-03-02 15:15:25 +05:30 committed by GitHub
commit ad5916fc19
95 changed files with 25828 additions and 1904 deletions

View File

@ -1,14 +1,12 @@
![img](https://ci.appveyor.com/api/projects/status/gmu6b3ltc80hr3k9?svg=true)
[![Discord](https://discordapp.com/api/guilds/117523346618318850/widget.png)](https://discord.gg/nadekobot)
[![Documentation Status](https://readthedocs.org/projects/nadekobot/badge/?version=latest)](http://nadekobot.readthedocs.io/en/latest/?badge=latest)
# NadekoBot
[![nadeko1](https://cdn.discordapp.com/attachments/155726317222887425/252095170676391936/A1.jpg)](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303)
[![nadeko2](https://cdn.discordapp.com/attachments/155726317222887425/252095207514832896/A2.jpg)](http://nadekobot.readthedocs.io/en/latest/Commands%20List/)
##For Update, Help and Guidlines
`Follow me on twitter for updates. | Join my Discord server if you need help. | Read the Docs for hosting guides.`
[![twitter](https://cdn.discordapp.com/attachments/155726317222887425/252192520094613504/twiter_banner.JPG)](https://twitter.com/TheNadekoBot) [![discord](https://cdn.discordapp.com/attachments/155726317222887425/252192415673221122/discord_banner.JPG)](https://discord.gg/nadekobot) [![Wiki](https://cdn.discordapp.com/attachments/155726317222887425/252192472849973250/read_the_docs_banner.JPG)](http://nadekobot.readthedocs.io/en/latest/)
[![nadeko0](https://cdn.discordapp.com/attachments/266240393639755778/281920716809699328/part1.png)](http://nadekobot.xyz)
[![nadeko1](https://cdn.discordapp.com/attachments/266240393639755778/281920134967328768/part2.png)](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303)
[![nadeko2](https://cdn.discordapp.com/attachments/266240393639755778/281920161311883264/part3.png)](http://nadekobot.readthedocs.io/en/latest/Commands%20List/)
##For Update, Help and Guidelines
| [![twitter](https://cdn.discordapp.com/attachments/155726317222887425/252192520094613504/twiter_banner.JPG)](https://twitter.com/TheNadekoBot) | [![discord](https://cdn.discordapp.com/attachments/266240393639755778/281920766490968064/discord.png)](https://discord.gg/nadekobot) | [![Wiki](https://cdn.discordapp.com/attachments/266240393639755778/281920793330581506/datcord.png)](http://nadekobot.readthedocs.io/en/latest/)
| --- | --- | --- |
| Follow me on Twitter for updates. | Join my Discord server if you need help. | Read the Docs for hosting guides. |

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.DataStructures
{
public class AsyncLazy<T> : Lazy<Task<T>>
{
public AsyncLazy(Func<T> valueFactory) :
base(() => Task.Factory.StartNew(valueFactory))
{ }
public AsyncLazy(Func<Task<T>> taskFactory) :
base(() => Task.Factory.StartNew(taskFactory).Unwrap())
{ }
public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); }
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,357 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace NadekoBot.Migrations
{
public partial class dateadded : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "WaifuUpdates",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "WaifuInfo",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "PokeGame",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "SelfAssignableRoles",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "Reminders",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "RaceAnimals",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "Quotes",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "PlaylistSong",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "PlayingStatus",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "Permission",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "MutedUserId",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "MusicPlaylists",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "ModulePrefixes",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "LogSettings",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "IgnoredVoicePresenceCHannels",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "IgnoredLogChannels",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "GuildRepeater",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "GuildConfigs",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "GCChannelId",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "FollowedStream",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "FilteredWord",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "FilterChannelId",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "EightBallResponses",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "Donators",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "DiscordUser",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "CustomReactions",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "CurrencyTransactions",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "Currency",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "ConversionUnits",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "CommandPrice",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "CommandCooldown",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "ClashOfClans",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "ClashCallers",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "BotConfig",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "BlacklistItem",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "AntiSpamSetting",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "AntiSpamIgnore",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "AntiRaidSetting",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DateAdded",
table: "WaifuUpdates");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "WaifuInfo");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "PokeGame");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "SelfAssignableRoles");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "Reminders");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "RaceAnimals");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "Quotes");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "PlaylistSong");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "PlayingStatus");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "Permission");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "MutedUserId");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "MusicPlaylists");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "ModulePrefixes");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "LogSettings");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "IgnoredVoicePresenceCHannels");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "IgnoredLogChannels");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "GuildRepeater");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "GuildConfigs");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "GCChannelId");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "FollowedStream");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "FilteredWord");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "FilterChannelId");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "EightBallResponses");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "Donators");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "DiscordUser");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "CustomReactions");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "CurrencyTransactions");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "Currency");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "ConversionUnits");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "CommandPrice");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "CommandCooldown");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "ClashOfClans");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "ClashCallers");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "BotConfig");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "BlacklistItem");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "AntiSpamSetting");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "AntiSpamIgnore");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "AntiRaidSetting");
}
}
}

View File

@ -24,6 +24,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Action");
b.Property<DateTime?>("DateAdded");
b.Property<int>("GuildConfigId");
b.Property<int>("Seconds");
@ -47,6 +49,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.HasKey("Id");
b.HasIndex("AntiSpamSettingId");
@ -61,6 +65,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Action");
b.Property<DateTime?>("DateAdded");
b.Property<int>("GuildConfigId");
b.Property<int>("MessageThreshold");
@ -80,6 +86,8 @@ namespace NadekoBot.Migrations
b.Property<int?>("BotConfigId");
b.Property<DateTime?>("DateAdded");
b.Property<ulong>("ItemId");
b.Property<int>("Type");
@ -120,6 +128,8 @@ namespace NadekoBot.Migrations
b.Property<string>("DMHelpString");
b.Property<DateTime?>("DateAdded");
b.Property<string>("ErrorColor");
b.Property<bool>("ForwardMessages");
@ -158,6 +168,8 @@ namespace NadekoBot.Migrations
b.Property<int>("ClashWarId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("SequenceNumber");
b.Property<int>("Stars");
@ -178,6 +190,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("EnemyClan");
b.Property<ulong>("GuildId");
@ -200,6 +214,8 @@ namespace NadekoBot.Migrations
b.Property<string>("CommandName");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<int>("Seconds");
@ -220,6 +236,8 @@ namespace NadekoBot.Migrations
b.Property<string>("CommandName");
b.Property<DateTime?>("DateAdded");
b.Property<int>("Price");
b.HasKey("Id");
@ -237,6 +255,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<string>("InternalTrigger");
b.Property<decimal>("Modifier");
@ -255,6 +275,8 @@ namespace NadekoBot.Migrations
b.Property<long>("Amount");
b.Property<DateTime?>("DateAdded");
b.Property<ulong>("UserId");
b.HasKey("Id");
@ -272,6 +294,8 @@ namespace NadekoBot.Migrations
b.Property<long>("Amount");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Reason");
b.Property<ulong>("UserId");
@ -286,6 +310,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<ulong?>("GuildId");
b.Property<bool>("IsRegex");
@ -308,6 +334,8 @@ namespace NadekoBot.Migrations
b.Property<string>("AvatarId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Discriminator");
b.Property<ulong>("UserId");
@ -328,6 +356,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Amount");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Name");
b.Property<ulong>("UserId");
@ -347,6 +377,8 @@ namespace NadekoBot.Migrations
b.Property<int?>("BotConfigId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Text");
b.HasKey("Id");
@ -363,6 +395,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<int?>("GuildConfigId1");
@ -381,6 +415,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<string>("Word");
@ -399,6 +435,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("GuildId");
@ -421,6 +459,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.HasKey("Id");
@ -455,6 +495,8 @@ namespace NadekoBot.Migrations
b.Property<bool>("CleverbotEnabled");
b.Property<DateTime?>("DateAdded");
b.Property<float>("DefaultMusicVolume");
b.Property<bool>("DeleteMessageOnCommand");
@ -512,6 +554,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("GuildId");
@ -534,6 +578,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("LogSettingId");
b.HasKey("Id");
@ -550,6 +596,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<int?>("LogSettingId");
b.HasKey("Id");
@ -578,6 +626,8 @@ namespace NadekoBot.Migrations
b.Property<ulong?>("ChannelUpdatedId");
b.Property<DateTime?>("DateAdded");
b.Property<bool>("IsLogging");
b.Property<ulong?>("LogOtherId");
@ -638,6 +688,8 @@ namespace NadekoBot.Migrations
b.Property<int?>("BotConfigId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("ModuleName");
b.Property<string>("Prefix");
@ -658,6 +710,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("AuthorId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Name");
b.HasKey("Id");
@ -670,6 +724,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("GuildConfigId");
b.Property<ulong>("UserId");
@ -686,6 +742,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("NextId");
b.Property<int>("PrimaryTarget");
@ -713,6 +771,8 @@ namespace NadekoBot.Migrations
b.Property<int?>("BotConfigId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Status");
b.HasKey("Id");
@ -727,6 +787,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("MusicPlaylistId");
b.Property<string>("Provider");
@ -756,6 +818,8 @@ namespace NadekoBot.Migrations
b.Property<string>("AuthorName")
.IsRequired();
b.Property<DateTime?>("DateAdded");
b.Property<ulong>("GuildId");
b.Property<string>("Keyword")
@ -776,6 +840,8 @@ namespace NadekoBot.Migrations
b.Property<int?>("BotConfigId");
b.Property<DateTime?>("DateAdded");
b.Property<string>("Icon");
b.Property<string>("Name");
@ -794,6 +860,8 @@ namespace NadekoBot.Migrations
b.Property<ulong>("ChannelId");
b.Property<DateTime?>("DateAdded");
b.Property<bool>("IsPrivate");
b.Property<string>("Message");
@ -814,6 +882,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<ulong>("GuildId");
b.Property<ulong>("RoleId");
@ -831,6 +901,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<ulong>("UserId");
b.Property<string>("type");
@ -852,6 +924,8 @@ namespace NadekoBot.Migrations
b.Property<int?>("ClaimerId");
b.Property<DateTime?>("DateAdded");
b.Property<int>("Price");
b.Property<int>("WaifuId");
@ -873,6 +947,8 @@ namespace NadekoBot.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime?>("DateAdded");
b.Property<int?>("NewId");
b.Property<int?>("OldId");

View File

@ -16,7 +16,7 @@ using NLog;
namespace NadekoBot.Modules.Administration
{
[NadekoModule("Administration", ".")]
public partial class Administration : NadekoModule
public partial class Administration : NadekoTopLevelModule
{
private static ConcurrentHashSet<ulong> deleteMessagesOnCommand { get; }
@ -440,6 +440,7 @@ namespace NadekoBot.Modules.Administration
var enumerable = (await Context.Channel.GetMessagesAsync().Flatten()).AsEnumerable();
enumerable = enumerable.Where(x => x.Author.Id == user.Id);
await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false);
Context.Message.DeleteAfter(3);
}
// prune x
@ -451,7 +452,6 @@ namespace NadekoBot.Modules.Administration
{
if (count < 1)
return;
count += 1;
await Context.Message.DeleteAsync().ConfigureAwait(false);
int limit = (count < 100) ? count : 100;
var enumerable = (await Context.Channel.GetMessagesAsync(limit: limit).Flatten().ConfigureAwait(false));
@ -474,6 +474,8 @@ namespace NadekoBot.Modules.Administration
int limit = (count < 100) ? count : 100;
var enumerable = (await Context.Channel.GetMessagesAsync(limit: limit).Flatten()).Where(m => m.Author == user);
await Context.Channel.DeleteMessagesAsync(enumerable).ConfigureAwait(false);
Context.Message.DeleteAfter(3);
}
[NadekoCommand, Usage, Description, Aliases]

View File

@ -19,7 +19,13 @@ namespace NadekoBot.Modules.Administration
private ImmutableDictionary<string, string> supportedLocales { get; } = new Dictionary<string, string>()
{
{"en-US", "English, United States"},
{"sr-cyrl-rs", "Serbian, Cyrillic" }
{"fr-FR", "French, France"},
{"ru-RU", "Russian, Russia"},
{"de-DE", "German, Germany"},
//{"nl-NL", "Dutch, Netherlands"},
//{"ja-JP", "Japanese, Japan"},
{"pt-BR", "Portuguese, Brazil"},
//{"sr-Cyrl-RS", "Serbian, Serbia - Cyrillic"}
}.ToImmutableDictionary();
[NadekoCommand, Usage, Description, Aliases]
@ -94,9 +100,10 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public async Task LanguagesList()
{
await ReplyConfirmLocalized("lang_list",
string.Join("\n", supportedLocales.Select(x => $"{Format.Code(x.Key)} => {x.Value}")))
.ConfigureAwait(false);
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("lang_list", ""))
.WithDescription(string.Join("\n",
supportedLocales.Select(x => $"{Format.Code(x.Key), -10} => {x.Value}"))));
}
}
}

View File

@ -126,7 +126,6 @@ namespace NadekoBot.Modules.Administration
{
embed.WithTitle("👥" + g.GetLogText("avatar_changed"))
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
.WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}")
.WithThumbnailUrl(before.AvatarUrl)
.WithImageUrl(after.AvatarUrl)
.WithFooter(fb => fb.WithText(currentTime))
@ -530,7 +529,7 @@ namespace NadekoBot.Modules.Administration
else if (afterVch == null)
{
str = "🎙" + Format.Code(prettyCurrentTime) + logChannel.Guild.GetLogText("user_vleft",
"👤" + Format.Code(prettyCurrentTime), "👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
"👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
Format.Bold(beforeVch.Name ?? ""));
}
if (str != null)
@ -705,17 +704,18 @@ namespace NadekoBot.Modules.Administration
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle("🗑 " + logChannel.Guild.GetLogText("msg_del", ((ITextChannel)msg.Channel).Name))
.WithDescription($"{msg.Author}")
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("content")).WithValue(msg.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.WithDescription(msg.Author.ToString())
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("content")).WithValue(string.IsNullOrWhiteSpace(msg.Content) ? "-" : msg.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName("Id").WithValue(msg.Id.ToString()).WithIsInline(false))
.WithFooter(efb => efb.WithText(currentTime));
if (msg.Attachments.Any())
embed.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("attachments")).WithValue(string.Join(", ", msg.Attachments.Select(a => a.ProxyUrl))).WithIsInline(false));
embed.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("attachments")).WithValue(string.Join(", ", msg.Attachments.Select(a => a.Url))).WithIsInline(false));
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch
catch (Exception ex)
{
_log.Warn(ex);
// ignored
}
}
@ -753,8 +753,8 @@ namespace NadekoBot.Modules.Administration
.WithOkColor()
.WithTitle("📝 " + logChannel.Guild.GetLogText("msg_update", ((ITextChannel)after.Channel).Name))
.WithDescription(after.Author.ToString())
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("old_msg")).WithValue(before.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("new_msg")).WithValue(after.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("old_msg")).WithValue(string.IsNullOrWhiteSpace(before.Content) ? "-" : before.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName(logChannel.Guild.GetLogText("new_msg")).WithValue(string.IsNullOrWhiteSpace(after.Content) ? "-" : after.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName("Id").WithValue(after.Id.ToString()).WithIsInline(false))
.WithFooter(efb => efb.WithText(currentTime));
@ -988,7 +988,9 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public async Task LogEvents()
{
await ReplyConfirmLocalized("log_events", string.Join(", ", Enum.GetNames(typeof(LogType)).Cast<string>())).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(GetText("log_events") + "\n" +
string.Join(", ", Enum.GetNames(typeof(LogType)).Cast<string>()))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -1066,7 +1068,7 @@ namespace NadekoBot.Modules.Administration
public static class GuildExtensions
{
public static string GetLogText(this IGuild guild, string key, params object[] replacements)
=> NadekoModule.GetTextStatic(key,
=> NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(guild),
typeof(Administration).Name.ToLowerInvariant(),
replacements);

View File

@ -72,19 +72,37 @@ namespace NadekoBot.Modules.Administration
{
if (_forwardDMs && ownerChannels.Any())
{
var title =
GetTextStatic("dm_from", NadekoBot.Localization.DefaultCultureInfo,
typeof(Administration).Name.ToLowerInvariant()) + $" [{msg.Author}]({msg.Author.Id})";
var title = GetTextStatic("dm_from",
NadekoBot.Localization.DefaultCultureInfo,
typeof(Administration).Name.ToLowerInvariant()) +
$" [{msg.Author}]({msg.Author.Id})";
var attachamentsTxt = GetTextStatic("attachments",
NadekoBot.Localization.DefaultCultureInfo,
typeof(Administration).Name.ToLowerInvariant());
var toSend = msg.Content;
if (msg.Attachments.Count > 0)
{
toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n" +
string.Join("\n", msg.Attachments.Select(a => a.ProxyUrl));
}
if (_forwardDMsToAllOwners)
{
await Task.WhenAll(ownerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id)
.Select(ch => ch.SendConfirmAsync(title, msg.Content))).ConfigureAwait(false);
.Select(ch => ch.SendConfirmAsync(title, toSend))).ConfigureAwait(false);
}
else
{
var firstOwnerChannel = ownerChannels.First();
if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
try { await firstOwnerChannel.SendConfirmAsync(title, msg.Content).ConfigureAwait(false); }
{
try
{
await firstOwnerChannel.SendConfirmAsync(title, toSend).ConfigureAwait(false);
}
catch
{
// ignored
@ -92,6 +110,7 @@ namespace NadekoBot.Modules.Administration
}
}
}
}
[NadekoCommand, Usage, Description, Aliases]

View File

@ -11,15 +11,13 @@ using NadekoBot.Services.Database.Models;
using System.Linq;
using NadekoBot.Extensions;
using System.Threading;
using System.Diagnostics;
using NLog;
namespace NadekoBot.Modules.ClashOfClans
{
[NadekoModule("ClashOfClans", ",")]
public class ClashOfClans : NadekoModule
public class ClashOfClans : NadekoTopLevelModule
{
public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; } = new ConcurrentDictionary<ulong, List<ClashWar>>();
public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; }
private static Timer checkWarTimer { get; }
@ -82,11 +80,9 @@ namespace NadekoBot.Modules.ClashOfClans
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task CreateWar(int size, [Remainder] string enemyClan = null)
{
if (!(Context.User as IGuildUser).GuildPermissions.ManageChannels)
return;
if (string.IsNullOrWhiteSpace(enemyClan))
return;

View File

@ -135,7 +135,7 @@ namespace NadekoBot.Modules.ClashOfClans
public static string Localize(this ClashWar cw, string key)
{
return NadekoModule.GetTextStatic(key,
return NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(cw.Channel?.GuildId),
typeof(ClashOfClans).Name.ToLowerInvariant());
}

View File

@ -17,7 +17,7 @@ using NadekoBot.DataStructures;
namespace NadekoBot.Modules.CustomReactions
{
[NadekoModule("CustomReactions", ".")]
public class CustomReactions : NadekoModule
public class CustomReactions : NadekoTopLevelModule
{
private static CustomReaction[] _globalReactions = new CustomReaction[] { };
public static CustomReaction[] GlobalReactions => _globalReactions;

View File

@ -28,7 +28,7 @@ namespace NadekoBot.Modules.Gambling
var ar = new AnimalRace(Context.Guild.Id, (ITextChannel)Context.Channel, Prefix);
if (ar.Fail)
await Context.Channel.SendErrorAsync("🏁 `Failed starting a race. Another race is probably running.`").ConfigureAwait(false);
await ReplyErrorLocalized("race_failed_starting").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -43,7 +43,7 @@ namespace NadekoBot.Modules.Gambling
AnimalRace ar;
if (!AnimalRaces.TryGetValue(Context.Guild.Id, out ar))
{
await Context.Channel.SendErrorAsync("No race exists on this server").ConfigureAwait(false);
await ReplyErrorLocalized("race_not_exist").ConfigureAwait(false);
return;
}
await ar.JoinRace(Context.User as IGuildUser, amount);
@ -56,22 +56,22 @@ namespace NadekoBot.Modules.Gambling
public bool Fail { get; set; }
public List<Participant> participants = new List<Participant>();
private ulong serverId;
private int messagesSinceGameStarted = 0;
private readonly List<Participant> _participants = new List<Participant>();
private readonly ulong _serverId;
private int _messagesSinceGameStarted;
private readonly string _prefix;
private Logger _log { get; }
private readonly Logger _log;
public ITextChannel raceChannel { get; set; }
public bool Started { get; private set; } = false;
private readonly ITextChannel _raceChannel;
public bool Started { get; private set; }
public AnimalRace(ulong serverId, ITextChannel ch, string prefix)
{
this._prefix = prefix;
this._log = LogManager.GetCurrentClassLogger();
this.serverId = serverId;
this.raceChannel = ch;
_prefix = prefix;
_log = LogManager.GetCurrentClassLogger();
_serverId = serverId;
_raceChannel = ch;
if (!AnimalRaces.TryAdd(serverId, this))
{
Fail = true;
@ -90,8 +90,8 @@ namespace NadekoBot.Modules.Gambling
{
try
{
await raceChannel.SendConfirmAsync("Animal Race", $"Starting in 20 seconds or when the room is full.",
footer: $"Type {_prefix}jr to join the race.");
await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting"),
footer: GetText("animal_race_join_instr", _prefix));
}
catch (Exception ex)
{
@ -102,16 +102,16 @@ namespace NadekoBot.Modules.Gambling
cancelSource.Cancel();
if (t == fullgame)
{
try { await raceChannel.SendConfirmAsync("Animal Race", "Full! Starting immediately."); } catch (Exception ex) { _log.Warn(ex); }
try { await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_full") ); } catch (Exception ex) { _log.Warn(ex); }
}
else if (participants.Count > 1)
else if (_participants.Count > 1)
{
try { await raceChannel.SendConfirmAsync("Animal Race", "Starting with " + participants.Count + " participants."); } catch (Exception ex) { _log.Warn(ex); }
try { await _raceChannel.SendConfirmAsync(GetText("animal_race"), GetText("animal_race_starting_with_x", _participants.Count)); } catch (Exception ex) { _log.Warn(ex); }
}
else
{
try { await raceChannel.SendErrorAsync("Animal Race", "Failed to start since there was not enough participants."); } catch (Exception ex) { _log.Warn(ex); }
var p = participants.FirstOrDefault();
try { await _raceChannel.SendErrorAsync(GetText("animal_race"), GetText("animal_race_failed")); } catch (Exception ex) { _log.Warn(ex); }
var p = _participants.FirstOrDefault();
if (p != null && p.AmountBet > 0)
await CurrencyHandler.AddCurrencyAsync(p.User, "BetRace", p.AmountBet, false).ConfigureAwait(false);
@ -128,7 +128,7 @@ namespace NadekoBot.Modules.Gambling
private void End()
{
AnimalRace throwaway;
AnimalRaces.TryRemove(serverId, out throwaway);
AnimalRaces.TryRemove(_serverId, out throwaway);
}
private async Task StartRace()
@ -136,21 +136,21 @@ namespace NadekoBot.Modules.Gambling
var rng = new NadekoRandom();
Participant winner = null;
IUserMessage msg = null;
int place = 1;
var place = 1;
try
{
NadekoBot.Client.MessageReceived += Client_MessageReceived;
while (!participants.All(p => p.Total >= 60))
while (!_participants.All(p => p.Total >= 60))
{
//update the state
participants.ForEach(p =>
_participants.ForEach(p =>
{
p.Total += 1 + rng.Next(0, 10);
});
participants
_participants
.OrderByDescending(p => p.Total)
.ForEach(p =>
{
@ -170,14 +170,14 @@ namespace NadekoBot.Modules.Gambling
//draw the state
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
{String.Join("\n", participants.Select(p => $"{(int)(p.Total / 60f * 100),-2}%|{p.ToString()}"))}
{String.Join("\n", _participants.Select(p => $"{(int)(p.Total / 60f * 100),-2}%|{p.ToString()}"))}
|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|";
if (msg == null || messagesSinceGameStarted >= 10) // also resend the message if channel was spammed
if (msg == null || _messagesSinceGameStarted >= 10) // also resend the message if channel was spammed
{
if (msg != null)
try { await msg.DeleteAsync(); } catch { }
messagesSinceGameStarted = 0;
try { msg = await raceChannel.SendMessageAsync(text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
_messagesSinceGameStarted = 0;
try { msg = await _raceChannel.SendMessageAsync(text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
}
else
{
@ -187,22 +187,33 @@ namespace NadekoBot.Modules.Gambling
await Task.Delay(2500);
}
}
catch { }
catch
{
// ignored
}
finally
{
NadekoBot.Client.MessageReceived -= Client_MessageReceived;
}
if (winner != null)
{
if (winner.AmountBet > 0)
{
var wonAmount = winner.AmountBet * (participants.Count - 1);
var wonAmount = winner.AmountBet * (_participants.Count - 1);
await CurrencyHandler.AddCurrencyAsync(winner.User, "Won a Race", wonAmount, true).ConfigureAwait(false);
await raceChannel.SendConfirmAsync("Animal Race", $"{winner.User.Mention} as {winner.Animal} **Won the race and {wonAmount}{CurrencySign}!**").ConfigureAwait(false);
await CurrencyHandler.AddCurrencyAsync(winner.User, "Won a Race", wonAmount, true)
.ConfigureAwait(false);
await _raceChannel.SendConfirmAsync(GetText("animal_race"),
Format.Bold(GetText("animal_race_won_money", winner.User.Mention,
winner.Animal, wonAmount + CurrencySign)))
.ConfigureAwait(false);
}
else
{
await raceChannel.SendConfirmAsync("Animal Race", $"{winner.User.Mention} as {winner.Animal} **Won the race!**").ConfigureAwait(false);
await _raceChannel.SendConfirmAsync(GetText("animal_race"),
Format.Bold(GetText("animal_race_won", winner.User.Mention, winner.Animal))).ConfigureAwait(false);
}
}
}
@ -212,9 +223,9 @@ namespace NadekoBot.Modules.Gambling
var msg = imsg as SocketUserMessage;
if (msg == null)
return Task.CompletedTask;
if (msg.IsAuthor() || !(imsg.Channel is ITextChannel) || imsg.Channel != raceChannel)
if (msg.IsAuthor() || !(imsg.Channel is ITextChannel) || imsg.Channel != _raceChannel)
return Task.CompletedTask;
messagesSinceGameStarted++;
_messagesSinceGameStarted++;
return Task.CompletedTask;
}
@ -228,51 +239,66 @@ namespace NadekoBot.Modules.Gambling
public async Task JoinRace(IGuildUser u, int amount = 0)
{
var animal = "";
string animal;
if (!animals.TryDequeue(out animal))
{
await raceChannel.SendErrorAsync($"{u.Mention} `There is no running race on this server.`").ConfigureAwait(false);
await _raceChannel.SendErrorAsync(GetText("animal_race_no_race")).ConfigureAwait(false);
return;
}
var p = new Participant(u, animal, amount);
if (participants.Contains(p))
if (_participants.Contains(p))
{
await raceChannel.SendErrorAsync($"{u.Mention} `You already joined this race.`").ConfigureAwait(false);
await _raceChannel.SendErrorAsync(GetText("animal_race_already_in")).ConfigureAwait(false);
return;
}
if (Started)
{
await raceChannel.SendErrorAsync($"{u.Mention} `Race is already started`").ConfigureAwait(false);
await _raceChannel.SendErrorAsync(GetText("animal_race_already_started")).ConfigureAwait(false);
return;
}
if (amount > 0)
if (!await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)u, "BetRace", amount, false).ConfigureAwait(false))
if (!await CurrencyHandler.RemoveCurrencyAsync(u, "BetRace", amount, false).ConfigureAwait(false))
{
try { await raceChannel.SendErrorAsync($"{u.Mention} You don't have enough {NadekoBot.BotConfig.CurrencyPluralName}.").ConfigureAwait(false); } catch { }
await _raceChannel.SendErrorAsync(GetText("not_enough", CurrencySign)).ConfigureAwait(false);
return;
}
participants.Add(p);
await raceChannel.SendConfirmAsync("Animal Race", $"{u.Mention} **joined as a {p.Animal}" + (amount > 0 ? $" and bet {amount} {CurrencySign}!**" : "**"))
.ConfigureAwait(false);
_participants.Add(p);
string confStr;
if (amount > 0)
confStr = GetText("animal_race_join_bet", u.Mention, p.Animal, amount + CurrencySign);
else
confStr = GetText("animal_race_join", u.Mention, p.Animal);
await _raceChannel.SendConfirmAsync(GetText("animal_race"), Format.Bold(confStr)).ConfigureAwait(false);
}
private string GetText(string text)
=> NadekoTopLevelModule.GetTextStatic(text,
NadekoBot.Localization.GetCultureInfo(_raceChannel.Guild),
typeof(Gambling).Name.ToLowerInvariant());
private string GetText(string text, params object[] replacements)
=> NadekoTopLevelModule.GetTextStatic(text,
NadekoBot.Localization.GetCultureInfo(_raceChannel.Guild),
typeof(Gambling).Name.ToLowerInvariant(),
replacements);
}
public class Participant
{
public IGuildUser User { get; set; }
public string Animal { get; set; }
public int AmountBet { get; set; }
public IGuildUser User { get; }
public string Animal { get; }
public int AmountBet { get; }
public float Coeff { get; set; }
public int Total { get; set; }
public int Place { get; set; } = 0;
public int Place { get; set; }
public Participant(IGuildUser u, string a, int amount)
{
this.User = u;
this.Animal = a;
this.AmountBet = amount;
User = u;
Animal = a;
AmountBet = amount;
}
public override int GetHashCode() => User.GetHashCode();
@ -288,23 +314,13 @@ namespace NadekoBot.Modules.Gambling
var str = new string('‣', Total) + Animal;
if (Place == 0)
return str;
if (Place == 1)
{
return str + "🏆";
}
else if (Place == 2)
{
return str + "`2nd`";
}
else if (Place == 3)
{
return str + "`3rd`";
}
else
{
return str + $"`{Place}th`";
}
str += $"`#{Place}`";
if (Place == 1)
str += "🏆";
return str;
}
}
}

View File

@ -43,7 +43,7 @@ namespace NadekoBot.Modules.Gambling
switch (e)
{
case CurrencyEvent.FlowerReaction:
await FlowerReactionEvent(Context).ConfigureAwait(false);
await FlowerReactionEvent(Context, arg).ConfigureAwait(false);
break;
case CurrencyEvent.SneakyGameStatus:
await SneakyGameStatusEvent(Context, arg).ConfigureAwait(false);
@ -115,23 +115,26 @@ namespace NadekoBot.Modules.Gambling
return Task.Delay(0);
}
public async Task FlowerReactionEvent(CommandContext context)
public async Task FlowerReactionEvent(CommandContext context, int amount)
{
if (amount <= 0)
amount = 100;
var title = GetText("flowerreaction_title");
var desc = GetText("flowerreaction_desc", "🌸", Format.Bold(100.ToString()) + CurrencySign);
var desc = GetText("flowerreaction_desc", "🌸", Format.Bold(amount.ToString()) + CurrencySign);
var footer = GetText("flowerreaction_footer", 24);
var msg = await context.Channel.SendConfirmAsync(title,
desc, footer: footer)
.ConfigureAwait(false);
await new FlowerReactionEvent().Start(msg, context);
await new FlowerReactionEvent().Start(msg, context, amount);
}
}
}
public abstract class CurrencyEvent
{
public abstract Task Start(IUserMessage msg, CommandContext channel);
public abstract Task Start(IUserMessage msg, CommandContext channel, int amount);
}
public class FlowerReactionEvent : CurrencyEvent
@ -172,7 +175,7 @@ namespace NadekoBot.Modules.Gambling
return Task.CompletedTask;
}
public override async Task Start(IUserMessage umsg, CommandContext context)
public override async Task Start(IUserMessage umsg, CommandContext context, int amount)
{
msg = umsg;
NadekoBot.Client.MessageDeleted += MessageDeletedEventHandler;
@ -193,7 +196,7 @@ namespace NadekoBot.Modules.Gambling
{
if (r.Emoji.Name == "🌸" && r.User.IsSpecified && ((DateTime.UtcNow - r.User.Value.CreatedAt).TotalDays > 5) && _flowerReactionAwardedUsers.Add(r.User.Value.Id))
{
await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", 100, false)
await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", amount, false)
.ConfigureAwait(false);
}
}

View File

@ -10,6 +10,7 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ImageSharp.Formats;
using Image = ImageSharp.Image;
namespace NadekoBot.Modules.Gambling
@ -22,7 +23,7 @@ namespace NadekoBot.Modules.Gambling
private Regex dndRegex { get; } = new Regex(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$", RegexOptions.Compiled);
private Regex fudgeRegex { get; } = new Regex(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);
private readonly char[] fateRolls = new[] { '-', ' ', '+' };
private readonly char[] _fateRolls = { '-', ' ', '+' };
[NadekoCommand, Usage, Description, Aliases]
public async Task Roll()
@ -35,12 +36,14 @@ namespace NadekoBot.Modules.Gambling
var imageStream = await Task.Run(() =>
{
var ms = new MemoryStream();
new[] { GetDice(num1), GetDice(num2) }.Merge().SaveAsPng(ms);
new[] { GetDice(num1), GetDice(num2) }.Merge().Save(ms);
ms.Position = 0;
return ms;
}).ConfigureAwait(false);
await Context.Channel.SendFileAsync(imageStream, "dice.png", $"{Context.User.Mention} rolled " + Format.Code(gen.ToString())).ConfigureAwait(false);
await Context.Channel.SendFileAsync(imageStream,
"dice.png",
Context.User.Mention + " " + GetText("dice_rolled", Format.Code(gen.ToString()))).ConfigureAwait(false);
}
public enum RollOrderType
@ -82,7 +85,7 @@ namespace NadekoBot.Modules.Gambling
{
if (num < 1 || num > 30)
{
await Context.Channel.SendErrorAsync("Invalid number specified. You can roll up to 1-30 dice at a time.").ConfigureAwait(false);
await ReplyErrorLocalized("dice_invalid_number", 1, 30).ConfigureAwait(false);
return;
}
@ -118,9 +121,14 @@ namespace NadekoBot.Modules.Gambling
var bitmap = dice.Merge();
var ms = new MemoryStream();
bitmap.SaveAsPng(ms);
bitmap.Save(ms);
ms.Position = 0;
await Context.Channel.SendFileAsync(ms, "dice.png", $"{Context.User.Mention} rolled {values.Count} {(values.Count == 1 ? "die" : "dice")}. Total: **{values.Sum()}** Average: **{(values.Sum() / (1.0f * values.Count)).ToString("N2")}**").ConfigureAwait(false);
await Context.Channel.SendFileAsync(ms, "dice.png",
Context.User.Mention + " " +
GetText("dice_rolled_num", Format.Bold(values.Count.ToString())) +
" " + GetText("total_average",
Format.Bold(values.Sum().ToString()),
Format.Bold((values.Sum() / (1.0f * values.Count)).ToString("N2")))).ConfigureAwait(false);
}
private async Task InternallDndRoll(string arg, bool ordered)
@ -138,9 +146,9 @@ namespace NadekoBot.Modules.Gambling
for (int i = 0; i < n1; i++)
{
rolls.Add(fateRolls[rng.Next(0, fateRolls.Length)]);
rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
}
var embed = new EmbedBuilder().WithOkColor().WithDescription($"{Context.User.Mention} rolled {n1} fate {(n1 == 1 ? "die" : "dice")}.")
var embed = new EmbedBuilder().WithOkColor().WithDescription(Context.User.Mention + " " + GetText("dice_rolled_num", Format.Bold(n1.ToString())))
.AddField(efb => efb.WithName(Format.Bold("Result"))
.WithValue(string.Join(" ", rolls.Select(c => Format.Code($"[{c}]")))));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
@ -164,7 +172,7 @@ namespace NadekoBot.Modules.Gambling
}
var sum = arr.Sum();
var embed = new EmbedBuilder().WithOkColor().WithDescription($"{Context.User.Mention} rolled {n1} {(n1 == 1 ? "die" : "dice")} `1 to {n2}`")
var embed = new EmbedBuilder().WithOkColor().WithDescription(Context.User.Mention + " " +GetText("dice_rolled_num", n1) + $"`1 - {n2}`")
.AddField(efb => efb.WithName(Format.Bold("Rolls"))
.WithValue(string.Join(" ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => Format.Code(x.ToString())))))
.AddField(efb => efb.WithName(Format.Bold("Sum"))
@ -176,8 +184,6 @@ namespace NadekoBot.Modules.Gambling
[NadekoCommand, Usage, Description, Aliases]
public async Task NRoll([Remainder] string range)
{
try
{
int rolled;
if (range.Contains("-"))
@ -187,7 +193,10 @@ namespace NadekoBot.Modules.Gambling
.Select(int.Parse)
.ToArray();
if (arr[0] > arr[1])
throw new ArgumentException("Second argument must be larger than the first one.");
{
await ReplyErrorLocalized("second_larger_than_first").ConfigureAwait(false);
return;
}
rolled = new NadekoRandom().Next(arr[0], arr[1] + 1);
}
else
@ -195,12 +204,7 @@ namespace NadekoBot.Modules.Gambling
rolled = new NadekoRandom().Next(0, int.Parse(range) + 1);
}
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} rolled **{rolled}**.").ConfigureAwait(false);
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync($":anger: {ex.Message}").ConfigureAwait(false);
}
await ReplyConfirmLocalized("dice_rolled", Format.Bold(rolled.ToString())).ConfigureAwait(false);
}
private Image GetDice(int num)

View File

@ -49,7 +49,7 @@ namespace NadekoBot.Modules.Gambling
images.Add(new Image(stream));
}
MemoryStream bitmapStream = new MemoryStream();
images.Merge().SaveAsPng(bitmapStream);
images.Merge().Save(bitmapStream);
bitmapStream.Position = 0;
var toSend = $"{Context.User.Mention}";
if (cardObjects.Count == 5)

View File

@ -52,18 +52,23 @@ namespace NadekoBot.Modules.Gambling
return;
}
var imgs = new Image[count];
for (var i = 0; i < count; i++)
{
using (var heads = _images.Heads.ToStream())
using (var tails = _images.Tails.ToStream())
{
for (var i = 0; i < count; i++)
if (rng.Next(0, 10) < 5)
{
imgs[i] = rng.Next(0, 10) < 5 ?
new Image(heads) :
new Image(tails);
imgs[i] = new Image(heads);
}
else
{
imgs[i] = new Image(tails);
}
}
}
await Context.Channel.SendFileAsync(imgs.Merge().ToStream(), $"{count} coins.png").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Betflip(int amount, string guess)
@ -105,7 +110,7 @@ namespace NadekoBot.Modules.Gambling
{
var toWin = (int)Math.Round(amount * NadekoBot.BotConfig.BetflipMultiplier);
str = Context.User.Mention + " " + GetText("flip_guess", toWin + CurrencySign);
await CurrencyHandler.AddCurrencyAsync(Context.User, GetText("betflip_gamble"), toWin, false).ConfigureAwait(false);
await CurrencyHandler.AddCurrencyAsync(Context.User, "Betflip Gamble", toWin, false).ConfigureAwait(false);
}
else
{

View File

@ -1,5 +1,6 @@
using Discord;
using Discord.Commands;
using ImageSharp;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
@ -163,28 +164,13 @@ namespace NadekoBot.Modules.Gambling
var result = SlotMachine.Pull();
int[] numbers = result.Numbers;
using (var bgPixels = bgImage.Lock())
{
for (int i = 0; i < 3; i++)
{
using (var file = _images.SlotEmojis[numbers[i]].ToStream())
using (var randomImage = new ImageSharp.Image(file))
{
var randomImage = new ImageSharp.Image(file);
using (var toAdd = randomImage.Lock())
{
for (int j = 0; j < toAdd.Width; j++)
{
for (int k = 0; k < toAdd.Height; k++)
{
var x = 95 + 142 * i + j;
int y = 330 + k;
var toSet = toAdd[j, k];
if (toSet.A < _alphaCutOut)
continue;
bgPixels[x, y] = toAdd[j, k];
}
}
}
bgImage.DrawImage(randomImage, 100, default(Size), new Point(95 + 142 * i, 330));
}
}
@ -195,21 +181,9 @@ namespace NadekoBot.Modules.Gambling
{
var digit = printWon % 10;
using (var fs = NadekoBot.Images.SlotNumbers[digit].ToStream())
using (var img = new ImageSharp.Image(fs))
{
var img = new ImageSharp.Image(fs);
using (var pixels = img.Lock())
{
for (int i = 0; i < pixels.Width; i++)
{
for (int j = 0; j < pixels.Height; j++)
{
if (pixels[i, j].A < _alphaCutOut)
continue;
var x = 230 - n * 16 + i;
bgPixels[x, 462 + j] = pixels[i, j];
}
}
}
bgImage.DrawImage(img, 100, default(Size), new Point(230 - n * 16, 462));
}
n++;
} while ((printWon /= 10) != 0);
@ -220,25 +194,12 @@ namespace NadekoBot.Modules.Gambling
{
var digit = printAmount % 10;
using (var fs = _images.SlotNumbers[digit].ToStream())
using (var img = new ImageSharp.Image(fs))
{
var img = new ImageSharp.Image(fs);
using (var pixels = img.Lock())
{
for (int i = 0; i < pixels.Width; i++)
{
for (int j = 0; j < pixels.Height; j++)
{
if (pixels[i, j].A < _alphaCutOut)
continue;
var x = 395 - n * 16 + i;
bgPixels[x, 462 + j] = pixels[i, j];
}
}
}
bgImage.DrawImage(img, 100, default(Size), new Point(395 - n * 16, 462));
}
n++;
} while ((printAmount /= 10) != 0);
}
var msg = GetText("better_luck");
if (result.Multiplier != 0)

View File

@ -3,13 +3,11 @@ using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling
@ -49,8 +47,8 @@ namespace NadekoBot.Modules.Gambling
[Group]
public class WaifuClaimCommands : NadekoSubmodule
{
private static ConcurrentDictionary<ulong, DateTime> _divorceCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
private static ConcurrentDictionary<ulong, DateTime> _affinityCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
private static ConcurrentDictionary<ulong, DateTime> divorceCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
private static ConcurrentDictionary<ulong, DateTime> affinityCooldowns { get; } = new ConcurrentDictionary<ulong, DateTime>();
enum WaifuClaimResult
{
@ -65,20 +63,19 @@ namespace NadekoBot.Modules.Gambling
{
if (amount < 50)
{
await Context.Channel.SendErrorAsync($"{Context.User.Mention} No waifu is that cheap. You must pay at least 50{NadekoBot.BotConfig.CurrencySign} to get a waifu, even if their actual value is lower.").ConfigureAwait(false);
await ReplyErrorLocalized("waifu_isnt_cheap", 50 + CurrencySign).ConfigureAwait(false);
return;
}
if (target.Id == Context.User.Id)
{
await Context.Channel.SendErrorAsync(Context.User.Mention + " You can't claim yourself.").ConfigureAwait(false);
await ReplyErrorLocalized("waifu_not_yourself").ConfigureAwait(false);
return;
}
WaifuClaimResult result = WaifuClaimResult.NotEnoughFunds;
int? oldPrice = null;
WaifuClaimResult result;
WaifuInfo w;
var isAffinity = false;
bool isAffinity;
using (var uow = DbHandler.UnitOfWork())
{
w = uow.Waifus.ByWaifuUserId(target.Id);
@ -120,7 +117,6 @@ namespace NadekoBot.Modules.Gambling
{
var oldClaimer = w.Claimer;
w.Claimer = uow.DiscordUsers.GetOrCreate(Context.User);
oldPrice = w.Price;
w.Price = amount + (amount / 4);
result = WaifuClaimResult.Success;
@ -143,7 +139,6 @@ namespace NadekoBot.Modules.Gambling
{
var oldClaimer = w.Claimer;
w.Claimer = uow.DiscordUsers.GetOrCreate(Context.User);
oldPrice = w.Price;
w.Price = amount;
result = WaifuClaimResult.Success;
@ -165,22 +160,20 @@ namespace NadekoBot.Modules.Gambling
if (result == WaifuClaimResult.InsufficientAmount)
{
await Context.Channel.SendErrorAsync($"{Context.User.Mention} You must pay {Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f))} or more to claim that waifu!").ConfigureAwait(false);
await ReplyErrorLocalized("waifu_not_enough", Math.Ceiling(w.Price * (isAffinity ? 0.88f : 1.1f))).ConfigureAwait(false);
return;
}
else if (result == WaifuClaimResult.NotEnoughFunds)
if (result == WaifuClaimResult.NotEnoughFunds)
{
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} you don't have {amount}{NadekoBot.BotConfig.CurrencySign}!")
.ConfigureAwait(false);
await ReplyErrorLocalized("not_enough", CurrencySign).ConfigureAwait(false);
return;
}
else
{
var msg = $"{Context.User.Mention} claimed {target.Mention} as their waifu for {amount}{NadekoBot.BotConfig.CurrencySign}!";
var msg = GetText("waifu_claimed",
Format.Bold(target.ToString()),
amount + CurrencySign);
if (w.Affinity?.UserId == Context.User.Id)
msg += $"\n🎉 Their love is fulfilled! 🎉\n**{target}'s** new value is {w.Price}{NadekoBot.BotConfig.CurrencySign}!";
await Context.Channel.SendConfirmAsync(msg)
.ConfigureAwait(false);
}
msg += "\n" + GetText("waifu_fulfilled", target, w.Price + CurrencySign);
await Context.Channel.SendConfirmAsync(Context.User.Mention + msg).ConfigureAwait(false);
}
public enum DivorceResult
@ -192,7 +185,7 @@ namespace NadekoBot.Modules.Gambling
}
private static readonly TimeSpan DivorceLimit = TimeSpan.FromHours(6);
private static readonly TimeSpan _divorceLimit = TimeSpan.FromHours(6);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Divorce([Remainder]IUser target)
@ -200,19 +193,19 @@ namespace NadekoBot.Modules.Gambling
if (target.Id == Context.User.Id)
return;
var result = DivorceResult.NotYourWife;
TimeSpan difference = TimeSpan.Zero;
DivorceResult result;
var difference = TimeSpan.Zero;
var amount = 0;
WaifuInfo w = null;
using (var uow = DbHandler.UnitOfWork())
{
w = uow.Waifus.ByWaifuUserId(target.Id);
var now = DateTime.UtcNow;
if (w == null || w.Claimer == null || w.Claimer.UserId != Context.User.Id)
if (w?.Claimer == null || w.Claimer.UserId != Context.User.Id)
result = DivorceResult.NotYourWife;
else if (_divorceCooldowns.AddOrUpdate(Context.User.Id,
else if (divorceCooldowns.AddOrUpdate(Context.User.Id,
now,
(key, old) => ((difference = now.Subtract(old)) > DivorceLimit) ? now : old) != now)
(key, old) => ((difference = now.Subtract(old)) > _divorceLimit) ? now : old) != now)
{
result = DivorceResult.Cooldown;
}
@ -249,37 +242,39 @@ namespace NadekoBot.Modules.Gambling
if (result == DivorceResult.SucessWithPenalty)
{
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} You have divorced a waifu who likes you. You heartless monster.\n{w.Waifu} received {amount}{NadekoBot.BotConfig.CurrencySign} as a compensation.").ConfigureAwait(false);
await ReplyConfirmLocalized("waifu_divorced_like", Format.Bold(w.Waifu.ToString()), amount + CurrencySign).ConfigureAwait(false);
}
else if (result == DivorceResult.Success)
{
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} You have divorced a waifu who doesn't like you. You received {amount}{NadekoBot.BotConfig.CurrencySign} back.").ConfigureAwait(false);
await ReplyConfirmLocalized("waifu_divorced_notlike", amount + CurrencySign).ConfigureAwait(false);
}
else if (result == DivorceResult.NotYourWife)
{
await Context.Channel.SendErrorAsync($"{Context.User.Mention} That waifu is not yours.").ConfigureAwait(false);
await ReplyErrorLocalized("waifu_not_yours").ConfigureAwait(false);
}
else
{
var remaining = DivorceLimit.Subtract(difference);
await Context.Channel.SendErrorAsync($"{Context.User.Mention} You divorced recently. You must wait **{remaining.Hours} hours and {remaining.Minutes} minutes** to divorce again.").ConfigureAwait(false);
var remaining = _divorceLimit.Subtract(difference);
await ReplyErrorLocalized("waifu_recent_divorce",
Format.Bold(((int)remaining.TotalHours).ToString()),
Format.Bold(remaining.Minutes.ToString())).ConfigureAwait(false);
}
}
private static readonly TimeSpan AffinityLimit = TimeSpan.FromMinutes(30);
private static readonly TimeSpan _affinityLimit = TimeSpan.FromMinutes(30);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WaifuClaimerAffinity([Remainder]IUser u = null)
{
if (u?.Id == Context.User.Id)
{
await Context.Channel.SendErrorAsync($"{Context.User.Mention} you can't set affinity to yourself, you egomaniac.").ConfigureAwait(false);
await ReplyErrorLocalized("waifu_egomaniac").ConfigureAwait(false);
return;
}
DiscordUser oldAff = null;
var sucess = false;
var cooldown = false;
TimeSpan difference = TimeSpan.Zero;
var difference = TimeSpan.Zero;
using (var uow = DbHandler.UnitOfWork())
{
var w = uow.Waifus.ByWaifuUserId(Context.User.Id);
@ -287,13 +282,11 @@ namespace NadekoBot.Modules.Gambling
var now = DateTime.UtcNow;
if (w?.Affinity?.UserId == u?.Id)
{
sucess = false;
}
else if (_affinityCooldowns.AddOrUpdate(Context.User.Id,
else if (affinityCooldowns.AddOrUpdate(Context.User.Id,
now,
(key, old) => ((difference = now.Subtract(old)) > AffinityLimit) ? now : old) != now)
(key, old) => ((difference = now.Subtract(old)) > _affinityLimit) ? now : old) != now)
{
sucess = false;
cooldown = true;
}
else if (w == null)
@ -338,19 +331,29 @@ namespace NadekoBot.Modules.Gambling
{
if (cooldown)
{
var remaining = AffinityLimit.Subtract(difference);
await Context.Channel.SendErrorAsync($"{Context.User.Mention} You must wait **{remaining.Hours} hours and {remaining.Minutes} minutes** in order to change your affinity again.").ConfigureAwait(false);
var remaining = _affinityLimit.Subtract(difference);
await ReplyErrorLocalized("waifu_affinity_cooldown",
Format.Bold(((int)remaining.TotalHours).ToString()),
Format.Bold(remaining.Minutes.ToString())).ConfigureAwait(false);
}
else
await Context.Channel.SendErrorAsync($"{Context.User.Mention} your affinity is already set to that waifu or you're trying to remove your affinity while not having one.").ConfigureAwait(false);
{
await ReplyErrorLocalized("waifu_affinity_already").ConfigureAwait(false);
}
return;
}
if (u == null)
await Context.Channel.SendConfirmAsync("Affinity Reset", $"{Context.User.Mention} Your affinity is reset. You no longer have a person you like.").ConfigureAwait(false);
{
await ReplyConfirmLocalized("waifu_affinity_reset").ConfigureAwait(false);
}
else if (oldAff == null)
await Context.Channel.SendConfirmAsync("Affinity Set", $"{Context.User.Mention} wants to be {u.Mention}'s waifu. Aww <3").ConfigureAwait(false);
{
await ReplyConfirmLocalized("waifu_affinity_set", Format.Bold(u.ToString())).ConfigureAwait(false);
}
else
await Context.Channel.SendConfirmAsync("Affinity Changed", $"{Context.User.Mention} changed their affinity from {oldAff} to {u.Mention}.\n\n*This is morally questionable.*🤔").ConfigureAwait(false);
{
await ReplyConfirmLocalized("waifu_affinity_changed", Format.Bold(oldAff.ToString()), Format.Bold(u.ToString())).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -365,19 +368,20 @@ namespace NadekoBot.Modules.Gambling
if (waifus.Count == 0)
{
await Context.Channel.SendConfirmAsync("No waifus have been claimed yet.").ConfigureAwait(false);
await ReplyConfirmLocalized("waifus_none").ConfigureAwait(false);
return;
}
var embed = new EmbedBuilder()
.WithTitle("Top Waifus")
.WithTitle(GetText("waifus_top_waifus"))
.WithOkColor();
for (int i = 0; i < waifus.Count; i++)
for (var i = 0; i < waifus.Count; i++)
{
var w = waifus[i];
embed.AddField(efb => efb.WithName("#" + (i + 1) + " - " + w.Price + NadekoBot.BotConfig.CurrencySign).WithValue(w.ToString()).WithIsInline(false));
var j = i;
embed.AddField(efb => efb.WithName("#" + (j + 1) + " - " + w.Price + NadekoBot.BotConfig.CurrencySign).WithValue(w.ToString()).WithIsInline(false));
}
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
@ -419,15 +423,18 @@ namespace NadekoBot.Modules.Gambling
var claimInfo = GetClaimTitle(target.Id);
var affInfo = GetAffinityTitle(target.Id);
var rng = new NadekoRandom();
var nobody = GetText("nobody");
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle("Waifu " + w.Waifu + " - \"the " + claimInfo.Title + "\"")
.AddField(efb => efb.WithName("Price").WithValue(w.Price.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("Claimed by").WithValue(w.Claimer?.ToString() ?? "No one").WithIsInline(true))
.AddField(efb => efb.WithName("Likes").WithValue(w.Affinity?.ToString() ?? "Nobody").WithIsInline(true))
.AddField(efb => efb.WithName("Changes Of Heart").WithValue($"{affInfo.Count} - \"the {affInfo.Title}\"").WithIsInline(true))
.AddField(efb => efb.WithName("Divorces").WithValue(divorces.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName($"Waifus ({claims.Count})").WithValue(claims.Count == 0 ? "Nobody" : string.Join("\n", claims.Select(x => x.Waifu))).WithIsInline(true));
.AddField(efb => efb.WithName(GetText("price")).WithValue(w.Price.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("claimed_by")).WithValue(w.Claimer?.ToString() ?? nobody).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("likes")).WithValue(w.Affinity?.ToString() ?? nobody).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("changes_of_heart")).WithValue($"{affInfo.Count} - \"the {affInfo.Title}\"").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("divorces")).WithValue(divorces.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName($"Waifus ({claims.Count})").WithValue(claims.Count == 0 ? nobody : string.Join("\n", claims.OrderBy(x => rng.Next()).Take(40).Select(x => x.Waifu))).WithIsInline(true));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
@ -447,13 +454,13 @@ namespace NadekoBot.Modules.Gambling
private static WaifuProfileTitle GetClaimTitle(ulong userId)
{
int count = 0;
int count;
using (var uow = DbHandler.UnitOfWork())
{
count = uow.Waifus.ByClaimerUserId(userId).Count;
}
ClaimTitles title = ClaimTitles.Lonely;
ClaimTitles title;
if (count == 0)
title = ClaimTitles.Lonely;
else if (count == 1)
@ -484,13 +491,13 @@ namespace NadekoBot.Modules.Gambling
private static WaifuProfileTitle GetAffinityTitle(ulong userId)
{
int count = 0;
int count;
using (var uow = DbHandler.UnitOfWork())
{
count = uow._context.WaifuUpdates.Count(w => w.User.UserId == userId && w.UpdateType == WaifuUpdateType.AffinityChanged);
}
AffinityTitles title = AffinityTitles.Pure;
AffinityTitles title;
if (count < 1)
title = AffinityTitles.Pure;
else if (count < 2)

View File

@ -12,7 +12,7 @@ using System.Collections.Generic;
namespace NadekoBot.Modules.Gambling
{
[NadekoModule("Gambling", "$")]
public partial class Gambling : NadekoModule
public partial class Gambling : NadekoTopLevelModule
{
public static string CurrencyName { get; set; }
public static string CurrencyPluralName { get; set; }
@ -49,7 +49,9 @@ namespace NadekoBot.Modules.Gambling
[Priority(0)]
public async Task Cash([Remainder] IUser user = null)
{
user = user ?? Context.User;
if(user == null)
await ConfirmLocalized("has", Format.Bold(Context.User.ToString()), $"{GetCurrency(Context.User.Id)} {CurrencySign}").ConfigureAwait(false);
else
await ReplyConfirmLocalized("has", Format.Bold(user.ToString()), $"{GetCurrency(user.Id)} {CurrencySign}").ConfigureAwait(false);
}
@ -238,9 +240,7 @@ namespace NadekoBot.Modules.Gambling
(int) (amount * NadekoBot.BotConfig.Betroll100Multiplier), false).ConfigureAwait(false);
}
}
Console.WriteLine("started sending");
await Context.Channel.SendConfirmAsync(str).ConfigureAwait(false);
Console.WriteLine("done sending");
}
[NadekoCommand, Usage, Description, Aliases]

View File

@ -19,7 +19,7 @@ namespace NadekoBot.Modules.Games
public partial class Games
{
[Group]
public class Acropobia : ModuleBase
public class Acropobia : NadekoSubmodule
{
//channelId, game
public static ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>();
@ -28,6 +28,8 @@ namespace NadekoBot.Modules.Games
[RequireContext(ContextType.Guild)]
public async Task Acro(int time = 60)
{
if (time < 10 || time > 120)
return;
var channel = (ITextChannel)Context.Channel;
var game = new AcrophobiaGame(channel, time);
@ -45,7 +47,7 @@ namespace NadekoBot.Modules.Games
}
else
{
await channel.SendErrorAsync("Acrophobia game is already running in this channel.").ConfigureAwait(false);
await ReplyErrorLocalized("acro_running").ConfigureAwait(false);
}
}
}
@ -59,44 +61,44 @@ namespace NadekoBot.Modules.Games
public class AcrophobiaGame
{
private readonly ITextChannel channel;
private readonly int time;
private readonly NadekoRandom rng;
private readonly ImmutableArray<char> startingLetters;
private readonly CancellationTokenSource source;
private readonly ITextChannel _channel;
private readonly int _time;
private readonly NadekoRandom _rng;
private readonly ImmutableArray<char> _startingLetters;
private readonly CancellationTokenSource _source;
private AcroPhase phase { get; set; } = AcroPhase.Submitting;
private readonly ConcurrentDictionary<string, IGuildUser> submissions = new ConcurrentDictionary<string, IGuildUser>();
public IReadOnlyDictionary<string, IGuildUser> Submissions => submissions;
private readonly ConcurrentDictionary<string, IGuildUser> _submissions = new ConcurrentDictionary<string, IGuildUser>();
public IReadOnlyDictionary<string, IGuildUser> Submissions => _submissions;
private readonly ConcurrentHashSet<ulong> usersWhoSubmitted = new ConcurrentHashSet<ulong>();
private readonly ConcurrentHashSet<ulong> usersWhoVoted = new ConcurrentHashSet<ulong>();
private readonly ConcurrentHashSet<ulong> _usersWhoSubmitted = new ConcurrentHashSet<ulong>();
private readonly ConcurrentHashSet<ulong> _usersWhoVoted = new ConcurrentHashSet<ulong>();
private int spamCount = 0;
private int _spamCount;
//text, votes
private readonly ConcurrentDictionary<string, int> votes = new ConcurrentDictionary<string, int>();
private readonly ConcurrentDictionary<string, int> _votes = new ConcurrentDictionary<string, int>();
private readonly Logger _log;
public AcrophobiaGame(ITextChannel channel, int time)
{
this._log = LogManager.GetCurrentClassLogger();
_log = LogManager.GetCurrentClassLogger();
this.channel = channel;
this.time = time;
this.source = new CancellationTokenSource();
_channel = channel;
_time = time;
_source = new CancellationTokenSource();
this.rng = new NadekoRandom();
var wordCount = rng.Next(3, 6);
_rng = new NadekoRandom();
var wordCount = _rng.Next(3, 6);
var lettersArr = new char[wordCount];
for (int i = 0; i < wordCount; i++)
{
var randChar = (char)rng.Next(65, 91);
lettersArr[i] = randChar == 'X' ? (char)rng.Next(65, 88) : randChar;
var randChar = (char)_rng.Next(65, 91);
lettersArr[i] = randChar == 'X' ? (char)_rng.Next(65, 88) : randChar;
}
startingLetters = lettersArr.ToImmutableArray();
_startingLetters = lettersArr.ToImmutableArray();
}
private EmbedBuilder GetEmbed()
@ -105,18 +107,18 @@ namespace NadekoBot.Modules.Games
return phase == AcroPhase.Submitting
? new EmbedBuilder().WithOkColor()
.WithTitle("Acrophobia")
.WithDescription($"Game started. Create a sentence with the following acronym: **{string.Join(".", startingLetters)}.**\n")
.WithFooter(efb => efb.WithText("You have " + this.time + " seconds to make a submission."))
.WithTitle(GetText("acrophobia"))
.WithDescription(GetText("acro_started", Format.Bold(string.Join(".", _startingLetters))))
.WithFooter(efb => efb.WithText(GetText("acro_started_footer", _time)))
: new EmbedBuilder()
.WithOkColor()
.WithTitle("Acrophobia - Submissions Closed")
.WithDescription($@"Acronym was **{string.Join(".", startingLetters)}.**
--
{this.submissions.Aggregate("", (agg, cur) => agg + $"`{++i}.` **{cur.Key.ToLowerInvariant().ToTitleCase()}**\n")}
--")
.WithFooter(efb => efb.WithText("Vote by typing a number of the submission"));
.WithTitle(GetText("acrophobia") + " - " + GetText("submissions_closed"))
.WithDescription(GetText("acro_nym_was", Format.Bold(string.Join(".", _startingLetters)) + "\n" +
$@"--
{_submissions.Aggregate("",(agg, cur) => agg + $"`{++i}.` **{cur.Key.ToLowerInvariant().ToTitleCase()}**\n")}
--"))
.WithFooter(efb => efb.WithText(GetText("acro_vote")));
}
public async Task Run()
@ -125,10 +127,10 @@ namespace NadekoBot.Modules.Games
var embed = GetEmbed();
//SUBMISSIONS PHASE
await channel.EmbedAsync(embed).ConfigureAwait(false);
await _channel.EmbedAsync(embed).ConfigureAwait(false);
try
{
await Task.Delay(time * 1000, source.Token).ConfigureAwait(false);
await Task.Delay(_time * 1000, _source.Token).ConfigureAwait(false);
phase = AcroPhase.Idle;
}
catch (OperationCanceledException)
@ -137,30 +139,32 @@ namespace NadekoBot.Modules.Games
}
//var i = 0;
if (submissions.Count == 0)
if (_submissions.Count == 0)
{
await channel.SendErrorAsync("Acrophobia", "Game ended with no submissions.");
await _channel.SendErrorAsync(GetText("acrophobia"), GetText("acro_ended_no_sub"));
return;
}
else if (submissions.Count == 1)
if (_submissions.Count == 1)
{
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription($"{submissions.First().Value.Mention} is the winner for being the only user who made a submission!")
.WithFooter(efb => efb.WithText(submissions.First().Key.ToLowerInvariant().ToTitleCase())))
await _channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription(
GetText("acro_winner_only",
Format.Bold(_submissions.First().Value.ToString())))
.WithFooter(efb => efb.WithText(_submissions.First().Key.ToLowerInvariant().ToTitleCase())))
.ConfigureAwait(false);
return;
}
var submissionClosedEmbed = GetEmbed();
await channel.EmbedAsync(submissionClosedEmbed).ConfigureAwait(false);
await _channel.EmbedAsync(submissionClosedEmbed).ConfigureAwait(false);
//VOTING PHASE
this.phase = AcroPhase.Voting;
phase = AcroPhase.Voting;
try
{
//30 secondds for voting
await Task.Delay(30000, source.Token).ConfigureAwait(false);
this.phase = AcroPhase.Idle;
await Task.Delay(30000, _source.Token).ConfigureAwait(false);
phase = AcroPhase.Idle;
}
catch (OperationCanceledException)
{
@ -174,10 +178,10 @@ namespace NadekoBot.Modules.Games
try
{
var msg = arg as SocketUserMessage;
if (msg == null || msg.Author.IsBot || msg.Channel.Id != channel.Id)
if (msg == null || msg.Author.IsBot || msg.Channel.Id != _channel.Id)
return;
++spamCount;
++_spamCount;
var guildUser = (IGuildUser)msg.Author;
@ -185,37 +189,39 @@ namespace NadekoBot.Modules.Games
if (phase == AcroPhase.Submitting)
{
if (spamCount > 10)
if (_spamCount > 10)
{
spamCount = 0;
try { await channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); }
_spamCount = 0;
try { await _channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); }
catch { }
}
var inputWords = input.Split(' '); //get all words
if (inputWords.Length != startingLetters.Length) // number of words must be the same as the number of the starting letters
if (inputWords.Length != _startingLetters.Length) // number of words must be the same as the number of the starting letters
return;
for (int i = 0; i < startingLetters.Length; i++)
for (int i = 0; i < _startingLetters.Length; i++)
{
var letter = startingLetters[i];
var letter = _startingLetters[i];
if (!inputWords[i].StartsWith(letter.ToString())) // all first letters must match
return;
}
if (!usersWhoSubmitted.Add(guildUser.Id))
if (!_usersWhoSubmitted.Add(guildUser.Id))
return;
//try adding it to the list of answers
if (!submissions.TryAdd(input, guildUser))
if (!_submissions.TryAdd(input, guildUser))
{
usersWhoSubmitted.TryRemove(guildUser.Id);
_usersWhoSubmitted.TryRemove(guildUser.Id);
return;
}
// all good. valid input. answer recorded
await channel.SendConfirmAsync("Acrophobia", $"{guildUser.Mention} submitted their sentence. ({submissions.Count} total)");
await _channel.SendConfirmAsync(GetText("acrophobia"),
GetText("acro_submit", guildUser.Mention,
_submissions.Count));
try
{
await msg.DeleteAsync();
@ -227,14 +233,13 @@ namespace NadekoBot.Modules.Games
}
else if (phase == AcroPhase.Voting)
{
if (spamCount > 10)
if (_spamCount > 10)
{
spamCount = 0;
try { await channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); }
_spamCount = 0;
try { await _channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); }
catch { }
}
IGuildUser usr;
//if (submissions.TryGetValue(input, out usr) && usr.Id != guildUser.Id)
//{
// if (!usersWhoVoted.Add(guildUser.Id))
@ -246,17 +251,17 @@ namespace NadekoBot.Modules.Games
//}
int num;
if (int.TryParse(input, out num) && num > 0 && num <= submissions.Count)
if (int.TryParse(input, out num) && num > 0 && num <= _submissions.Count)
{
var kvp = submissions.Skip(num - 1).First();
usr = kvp.Value;
var kvp = _submissions.Skip(num - 1).First();
var usr = kvp.Value;
//can't vote for yourself, can't vote multiple times
if (usr.Id == guildUser.Id || !usersWhoVoted.Add(guildUser.Id))
if (usr.Id == guildUser.Id || !_usersWhoVoted.Add(guildUser.Id))
return;
votes.AddOrUpdate(kvp.Key, 1, (key, old) => ++old);
await channel.SendConfirmAsync("Acrophobia", $"{guildUser.Mention} cast their vote!").ConfigureAwait(false);
_votes.AddOrUpdate(kvp.Key, 1, (key, old) => ++old);
await _channel.SendConfirmAsync(GetText("acrophobia"),
GetText("acro_vote_cast", Format.Bold(guildUser.ToString()))).ConfigureAwait(false);
await msg.DeleteAsync().ConfigureAwait(false);
return;
}
}
@ -269,27 +274,34 @@ namespace NadekoBot.Modules.Games
public async Task End()
{
if (!votes.Any())
if (!_votes.Any())
{
await channel.SendErrorAsync("Acrophobia", "No votes cast. Game ended with no winner.").ConfigureAwait(false);
await _channel.SendErrorAsync(GetText("acrophobia"), GetText("acro_no_votes_cast")).ConfigureAwait(false);
return;
}
var table = votes.OrderByDescending(v => v.Value);
var table = _votes.OrderByDescending(v => v.Value);
var winner = table.First();
var embed = new EmbedBuilder().WithOkColor()
.WithTitle("Acrophobia")
.WithDescription($"Winner is {submissions[winner.Key].Mention} with {winner.Value} points.\n")
.WithTitle(GetText("acrophobia"))
.WithDescription(GetText("acro_winner", Format.Bold(_submissions[winner.Key].ToString()),
Format.Bold(winner.Value.ToString())))
.WithFooter(efb => efb.WithText(winner.Key.ToLowerInvariant().ToTitleCase()));
await channel.EmbedAsync(embed).ConfigureAwait(false);
await _channel.EmbedAsync(embed).ConfigureAwait(false);
}
public void EnsureStopped()
{
NadekoBot.Client.MessageReceived -= PotentialAcro;
if (!source.IsCancellationRequested)
source.Cancel();
}
if (!_source.IsCancellationRequested)
_source.Cancel();
}
private string GetText(string key, params object[] replacements)
=> GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(_channel.Guild),
typeof(Games).Name.ToLowerInvariant(),
replacements);
}
}
}

View File

@ -17,11 +17,11 @@ namespace NadekoBot.Modules.Games
public partial class Games
{
[Group]
public class CleverBotCommands : ModuleBase
public class CleverBotCommands : NadekoSubmodule
{
private static Logger _log { get; }
private new static Logger _log { get; }
public static ConcurrentDictionary<ulong, Lazy<ChatterBotSession>> CleverbotGuilds { get; } = new ConcurrentDictionary<ulong, Lazy<ChatterBotSession>>();
public static ConcurrentDictionary<ulong, Lazy<ChatterBotSession>> CleverbotGuilds { get; }
static CleverBotCommands()
{
@ -96,7 +96,7 @@ namespace NadekoBot.Modules.Games
uow.GuildConfigs.SetCleverbotEnabled(Context.Guild.Id, false);
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} Disabled cleverbot on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("cleverbot_disabled").ConfigureAwait(false);
return;
}
@ -110,7 +110,7 @@ namespace NadekoBot.Modules.Games
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} Enabled cleverbot on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("cleverbot_enabled").ConfigureAwait(false);
}
}
}

View File

@ -9,8 +9,9 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Modules.Games.Commands.Hangman;
namespace NadekoBot.Modules.Games.Commands.Hangman
namespace NadekoBot.Modules.Games.Hangman
{
public class HangmanTermPool
{
@ -70,7 +71,7 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
return $" {c}";
c = char.ToUpperInvariant(c);
return Guesses.Contains(c) ? $" {c}" : " _";
return Guesses.Contains(c) ? $" {c}" : " ";
})) + "`";
public bool GuessedAll => Guesses.IsSupersetOf(Term.Word.ToUpperInvariant()
@ -145,7 +146,7 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
MessagesSinceLastPost = 0;
++Errors;
if (Errors < MaxErrors)
await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author.Mention} Letter `{guess}` has already been used.\n" + ScrambledWord + "\n" + GetHangman(),
await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author} Letter `{guess}` has already been used.\n" + ScrambledWord + "\n" + GetHangman(),
footer: string.Join(" ", Guesses)).ConfigureAwait(false);
else
await End().ConfigureAwait(false);
@ -158,7 +159,7 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
{
if (GuessedAll)
{
try { await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author.Mention} guessed a letter `{guess}`!").ConfigureAwait(false); } catch { }
try { await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author} guessed a letter `{guess}`!").ConfigureAwait(false); } catch { }
await End().ConfigureAwait(false);
return;
@ -166,7 +167,7 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
MessagesSinceLastPost = 0;
try
{
await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author.Mention} guessed a letter `{guess}`!\n" + ScrambledWord + "\n" + GetHangman(),
await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author} guessed a letter `{guess}`!\n" + ScrambledWord + "\n" + GetHangman(),
footer: string.Join(" ", Guesses)).ConfigureAwait(false);
}
catch { }
@ -177,7 +178,7 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
MessagesSinceLastPost = 0;
++Errors;
if (Errors < MaxErrors)
await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author.Mention} Letter `{guess}` does not exist.\n" + ScrambledWord + "\n" + GetHangman(),
await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author} Letter `{guess}` does not exist.\n" + ScrambledWord + "\n" + GetHangman(),
footer: string.Join(" ", Guesses)).ConfigureAwait(false);
else
await End().ConfigureAwait(false);

View File

@ -1,36 +1,25 @@
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Commands.Hangman;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using NadekoBot.Modules.Games.Hangman;
using Discord;
namespace NadekoBot.Modules.Games
{
public partial class Games
{
[Group]
public class HangmanCommands : ModuleBase
public class HangmanCommands : NadekoSubmodule
{
private static Logger _log { get; }
//channelId, game
public static ConcurrentDictionary<ulong, HangmanGame> HangmanGames { get; } = new ConcurrentDictionary<ulong, HangmanGame>();
private static string typesStr { get; } = "";
static HangmanCommands()
{
_log = LogManager.GetCurrentClassLogger();
typesStr =
string.Format("`List of \"{0}hangman\" term types:`\n", NadekoBot.ModulePrefixes[typeof(Games).Name]) + String.Join(", ", HangmanTermPool.data.Keys);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Hangmanlist()
{
await Context.Channel.SendConfirmAsync(typesStr);
await Context.Channel.SendConfirmAsync(Format.Code(GetText("hangman_types", Prefix)) + "\n" + string.Join(", ", HangmanTermPool.data.Keys));
}
[NadekoCommand, Usage, Description, Aliases]
@ -40,11 +29,11 @@ namespace NadekoBot.Modules.Games
if (!HangmanGames.TryAdd(Context.Channel.Id, hm))
{
await Context.Channel.SendErrorAsync("Hangman game already running on this channel.").ConfigureAwait(false);
await ReplyErrorLocalized("hangman_running").ConfigureAwait(false);
return;
}
hm.OnEnded += (g) =>
hm.OnEnded += g =>
{
HangmanGame throwaway;
HangmanGames.TryRemove(g.GameChannel.Id, out throwaway);
@ -55,14 +44,14 @@ namespace NadekoBot.Modules.Games
}
catch (Exception ex)
{
try { await Context.Channel.SendErrorAsync($"Starting errored: {ex.Message}").ConfigureAwait(false); } catch { }
try { await Context.Channel.SendErrorAsync(GetText("hangman_start_errored") + " " + ex.Message).ConfigureAwait(false); } catch { }
HangmanGame throwaway;
HangmanGames.TryRemove(Context.Channel.Id, out throwaway);
throwaway.Dispose();
return;
}
await Context.Channel.SendConfirmAsync("Hangman game started", hm.ScrambledWord + "\n" + hm.GetHangman());
await Context.Channel.SendConfirmAsync(GetText("hangman_game_started"), hm.ScrambledWord + "\n" + hm.GetHangman());
}
}
}

View File

@ -11,10 +11,7 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games
@ -31,7 +28,7 @@ namespace NadekoBot.Modules.Games
[Group]
public class PlantPickCommands : NadekoSubmodule
{
private static ConcurrentHashSet<ulong> generationChannels { get; } = new ConcurrentHashSet<ulong>();
private static ConcurrentHashSet<ulong> generationChannels { get; }
//channelid/message
private static ConcurrentDictionary<ulong, List<IUserMessage>> plantedFlowers { get; } = new ConcurrentDictionary<ulong, List<IUserMessage>>();
//channelId/last generation
@ -82,25 +79,17 @@ namespace NadekoBot.Modules.Games
if (dropAmount > 0)
{
var msgs = new IUserMessage[dropAmount];
string firstPart;
if (dropAmount == 1)
{
firstPart = $"A random { NadekoBot.BotConfig.CurrencyName } appeared!";
}
else
{
firstPart = $"{dropAmount} random { NadekoBot.BotConfig.CurrencyPluralName } appeared!";
}
var prefix = NadekoBot.ModulePrefixes[typeof(Games).Name];
var toSend = dropAmount == 1
? GetLocalText(channel, "curgen_sn", NadekoBot.BotConfig.CurrencySign, prefix)
: GetLocalText(channel, "curgen_pl", dropAmount, NadekoBot.BotConfig.CurrencySign, prefix);
var file = GetRandomCurrencyImage();
using (var fileStream = file.Value.ToStream())
{
var sent = await channel.SendFileAsync(
fileStream,
file.Key,
string.Format("❗ {0} Pick it up by typing `{1}pick`", firstPart,
NadekoBot.ModulePrefixes[typeof(Games).Name]))
.ConfigureAwait(false);
toSend).ConfigureAwait(false);
msgs[0] = sent;
}
@ -117,6 +106,12 @@ namespace NadekoBot.Modules.Games
return Task.CompletedTask;
}
public static string GetLocalText(ITextChannel channel, string key, params object[] replacements) =>
NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(channel.GuildId),
typeof(Games).Name.ToLowerInvariant(),
replacements);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Pick()
@ -135,7 +130,8 @@ namespace NadekoBot.Modules.Games
await Task.WhenAll(msgs.Where(m => m != null).Select(toDelete => toDelete.DeleteAsync())).ConfigureAwait(false);
await CurrencyHandler.AddCurrencyAsync((IGuildUser)Context.User, $"Picked {NadekoBot.BotConfig.CurrencyPluralName}", msgs.Count, false).ConfigureAwait(false);
var msg = await channel.SendConfirmAsync($"**{Context.User}** picked {msgs.Count}{NadekoBot.BotConfig.CurrencySign}!").ConfigureAwait(false);
var msg = await ReplyConfirmLocalized("picked", msgs.Count + NadekoBot.BotConfig.CurrencySign)
.ConfigureAwait(false);
msg.DeleteAfter(10);
}
@ -149,14 +145,19 @@ namespace NadekoBot.Modules.Games
var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Planted a {NadekoBot.BotConfig.CurrencyName}", amount, false).ConfigureAwait(false);
if (!removed)
{
await Context.Channel.SendErrorAsync($"You don't have enough {NadekoBot.BotConfig.CurrencyPluralName}.").ConfigureAwait(false);
await ReplyErrorLocalized("not_enough", NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false);
return;
}
var imgData = GetRandomCurrencyImage();
var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.BotConfig.CurrencyName[0]);
var msgToSend = $"Oh how Nice! **{Context.User.Username}** planted {(amount == 1 ? (vowelFirst ? "an" : "a") : amount.ToString())} {(amount > 1 ? NadekoBot.BotConfig.CurrencyPluralName : NadekoBot.BotConfig.CurrencyName)}. Pick it using {Prefix}pick";
//todo upload all currency images to transfer.sh and use that one as cdn
//and then
var msgToSend = GetText("planted",
Format.Bold(Context.User.ToString()),
amount + NadekoBot.BotConfig.CurrencySign,
Prefix);
IUserMessage msg;
using (var toSend = imgData.Value.ToStream())
@ -203,11 +204,11 @@ namespace NadekoBot.Modules.Games
}
if (enabled)
{
await channel.SendConfirmAsync("Currency generation enabled on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("curgen_enabled").ConfigureAwait(false);
}
else
{
await channel.SendConfirmAsync("Currency generation disabled on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("curgen_disabled").ConfigureAwait(false);
}
}

View File

@ -3,12 +3,10 @@ using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games
@ -16,7 +14,7 @@ namespace NadekoBot.Modules.Games
public partial class Games
{
[Group]
public class PollCommands : ModuleBase
public class PollCommands : NadekoSubmodule
{
public static ConcurrentDictionary<ulong, Poll> ActivePolls = new ConcurrentDictionary<ulong, Poll>();
@ -24,20 +22,20 @@ namespace NadekoBot.Modules.Games
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)]
public Task Poll([Remainder] string arg = null)
=> InternalStartPoll(arg, isPublic: false);
=> InternalStartPoll(arg, false);
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)]
public Task PublicPoll([Remainder] string arg = null)
=> InternalStartPoll(arg, isPublic: true);
=> InternalStartPoll(arg, true);
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)]
public async Task PollStats()
{
Games.Poll poll;
Poll poll;
if (!ActivePolls.TryGetValue(Context.Guild.Id, out poll))
return;
@ -48,8 +46,6 @@ namespace NadekoBot.Modules.Games
{
var channel = (ITextChannel)Context.Channel;
if (!(Context.User as IGuildUser).GuildPermissions.ManageChannels)
return;
if (string.IsNullOrWhiteSpace(arg) || !arg.Contains(";"))
return;
var data = arg.Split(';');
@ -80,27 +76,25 @@ namespace NadekoBot.Modules.Games
public class Poll
{
private readonly IUserMessage originalMessage;
private readonly IGuild guild;
private string[] Answers { get; }
private ConcurrentDictionary<ulong, int> participants = new ConcurrentDictionary<ulong, int>();
private readonly string question;
private DateTime started;
private CancellationTokenSource pollCancellationSource = new CancellationTokenSource();
private readonly IUserMessage _originalMessage;
private readonly IGuild _guild;
private string[] answers { get; }
private readonly ConcurrentDictionary<ulong, int> _participants = new ConcurrentDictionary<ulong, int>();
private readonly string _question;
public bool IsPublic { get; }
public Poll(IUserMessage umsg, string question, IEnumerable<string> enumerable, bool isPublic = false)
{
this.originalMessage = umsg;
this.guild = ((ITextChannel)umsg.Channel).Guild;
this.question = question;
this.Answers = enumerable as string[] ?? enumerable.ToArray();
this.IsPublic = isPublic;
_originalMessage = umsg;
_guild = ((ITextChannel)umsg.Channel).Guild;
_question = question;
answers = enumerable as string[] ?? enumerable.ToArray();
IsPublic = isPublic;
}
public EmbedBuilder GetStats(string title)
{
var results = participants.GroupBy(kvp => kvp.Value)
var results = _participants.GroupBy(kvp => kvp.Value)
.ToDictionary(x => x.Key, x => x.Sum(kvp => 1))
.OrderByDescending(kvp => kvp.Value)
.ToArray();
@ -108,7 +102,7 @@ namespace NadekoBot.Modules.Games
var eb = new EmbedBuilder().WithTitle(title);
var sb = new StringBuilder()
.AppendLine(Format.Bold(question))
.AppendLine(Format.Bold(_question))
.AppendLine();
var totalVotesCast = 0;
@ -121,7 +115,7 @@ namespace NadekoBot.Modules.Games
for (int i = 0; i < results.Length; i++)
{
var result = results[i];
sb.AppendLine($"`{i + 1}.` {Format.Bold(Answers[result.Key - 1])} with {Format.Bold(result.Value.ToString())} votes.");
sb.AppendLine($"`{i + 1}.` {Format.Bold(answers[result.Key - 1])} with {Format.Bold(result.Value.ToString())} votes.");
totalVotesCast += result.Value;
}
}
@ -135,22 +129,21 @@ namespace NadekoBot.Modules.Games
public async Task StartPoll()
{
started = DateTime.Now;
NadekoBot.Client.MessageReceived += Vote;
var msgToSend = $"📃**{originalMessage.Author.Username}** has created a poll which requires your attention:\n\n**{question}**\n";
var msgToSend = $"📃**{_originalMessage.Author.Username}** has created a poll which requires your attention:\n\n**{_question}**\n";
var num = 1;
msgToSend = Answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n");
msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n");
if (!IsPublic)
msgToSend += "\n**Private Message me with the corresponding number of the answer.**";
else
msgToSend += "\n**Send a Message here with the corresponding number of the answer.**";
await originalMessage.Channel.SendConfirmAsync(msgToSend).ConfigureAwait(false);
await _originalMessage.Channel.SendConfirmAsync(msgToSend).ConfigureAwait(false);
}
public async Task StopPoll()
{
NadekoBot.Client.MessageReceived -= Vote;
await originalMessage.Channel.EmbedAsync(GetStats("POLL CLOSED")).ConfigureAwait(false);
await _originalMessage.Channel.EmbedAsync(GetStats("POLL CLOSED")).ConfigureAwait(false);
}
private async Task Vote(SocketMessage imsg)
@ -166,14 +159,14 @@ namespace NadekoBot.Modules.Games
int vote;
if (!int.TryParse(imsg.Content, out vote))
return;
if (vote < 1 || vote > Answers.Length)
if (vote < 1 || vote > answers.Length)
return;
IMessageChannel ch;
if (IsPublic)
{
//if public, channel must be the same the poll started in
if (originalMessage.Channel.Id != imsg.Channel.Id)
if (_originalMessage.Channel.Id != imsg.Channel.Id)
return;
ch = imsg.Channel;
}
@ -184,13 +177,13 @@ namespace NadekoBot.Modules.Games
return;
// user must be a member of the guild this poll is in
var guildUsers = await guild.GetUsersAsync().ConfigureAwait(false);
if (!guildUsers.Any(u => u.Id == imsg.Author.Id))
var guildUsers = await _guild.GetUsersAsync().ConfigureAwait(false);
if (guildUsers.All(u => u.Id != imsg.Author.Id))
return;
}
//user can vote only once
if (participants.TryAdd(msg.Author.Id, vote))
if (_participants.TryAdd(msg.Author.Id, vote))
{
if (!IsPublic)
{

View File

@ -147,7 +147,7 @@ namespace NadekoBot.Modules.Games
}
[Group]
public class SpeedTypingCommands : ModuleBase
public class SpeedTypingCommands : NadekoSubmodule
{
public static List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>();

View File

@ -4,9 +4,7 @@ using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -15,21 +13,13 @@ namespace NadekoBot.Modules.Games
{
public partial class Games
{
//todo timeout
[Group]
public class TicTacToeCommands : ModuleBase
public class TicTacToeCommands : NadekoSubmodule
{
//channelId/game
private static readonly Dictionary<ulong, TicTacToe> _games = new Dictionary<ulong, TicTacToe>();
private readonly Logger _log;
public TicTacToeCommands()
{
_log = LogManager.GetCurrentClassLogger();
}
private readonly SemaphoreSlim sem = new SemaphoreSlim(1, 1);
private readonly object tttLockObj = new object();
private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
@ -37,7 +27,7 @@ namespace NadekoBot.Modules.Games
{
var channel = (ITextChannel)Context.Channel;
await sem.WaitAsync(1000);
await _sem.WaitAsync(1000);
try
{
TicTacToe game;
@ -51,7 +41,7 @@ namespace NadekoBot.Modules.Games
}
game = new TicTacToe(channel, (IGuildUser)Context.User);
_games.Add(channel.Id, game);
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} Created a TicTacToe game.").ConfigureAwait(false);
await ReplyConfirmLocalized("ttt_created").ConfigureAwait(false);
game.OnEnded += (g) =>
{
@ -60,7 +50,7 @@ namespace NadekoBot.Modules.Games
}
finally
{
sem.Release();
_sem.Release();
}
}
}
@ -75,46 +65,49 @@ namespace NadekoBot.Modules.Games
}
private readonly ITextChannel _channel;
private readonly Logger _log;
private readonly IGuildUser[] _users;
private readonly int?[,] _state;
private Phase _phase;
int curUserIndex = 0;
private readonly SemaphoreSlim moveLock;
private int _curUserIndex;
private readonly SemaphoreSlim _moveLock;
private IGuildUser _winner = null;
private IGuildUser _winner;
private readonly string[] numbers = { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:" };
private readonly string[] _numbers = { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:" };
public Action<TicTacToe> OnEnded;
private IUserMessage previousMessage = null;
private Timer timeoutTimer;
private IUserMessage _previousMessage;
private Timer _timeoutTimer;
public TicTacToe(ITextChannel channel, IGuildUser firstUser)
{
_channel = channel;
_users = new IGuildUser[2] { firstUser, null };
_state = new int?[3, 3] {
_users = new[] { firstUser, null };
_state = new int?[,] {
{ null, null, null },
{ null, null, null },
{ null, null, null },
};
_log = LogManager.GetCurrentClassLogger();
_log.Warn($"User {firstUser} created a TicTacToe game.");
_phase = Phase.Starting;
moveLock = new SemaphoreSlim(1, 1);
_moveLock = new SemaphoreSlim(1, 1);
}
private string GetText(string key, params object[] replacements) =>
NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(_channel.GuildId),
typeof(Games).Name.ToLowerInvariant(),
replacements);
public string GetState()
{
var sb = new StringBuilder();
for (int i = 0; i < _state.GetLength(0); i++)
for (var i = 0; i < _state.GetLength(0); i++)
{
for (int j = 0; j < _state.GetLength(1); j++)
for (var j = 0; j < _state.GetLength(1); j++)
{
sb.Append(_state[i, j] == null ? numbers[i * 3 + j] : GetIcon(_state[i, j]));
sb.Append(_state[i, j] == null ? _numbers[i * 3 + j] : GetIcon(_state[i, j]));
if (j < _state.GetLength(1) - 1)
sb.Append("┃");
}
@ -130,7 +123,7 @@ namespace NadekoBot.Modules.Games
var embed = new EmbedBuilder()
.WithOkColor()
.WithDescription(Environment.NewLine + GetState())
.WithAuthor(eab => eab.WithName($"{_users[0]} vs {_users[1]}"));
.WithAuthor(eab => eab.WithName(GetText("vs", _users[0], _users[1])));
if (!string.IsNullOrWhiteSpace(title))
embed.WithTitle(title);
@ -138,12 +131,12 @@ namespace NadekoBot.Modules.Games
if (_winner == null)
{
if (_phase == Phase.Ended)
embed.WithFooter(efb => efb.WithText($"No moves left!"));
embed.WithFooter(efb => efb.WithText(GetText("ttt_no_moves")));
else
embed.WithFooter(efb => efb.WithText($"{_users[curUserIndex]}'s move"));
embed.WithFooter(efb => efb.WithText(GetText("ttt_users_move", _users[_curUserIndex])));
}
else
embed.WithFooter(efb => efb.WithText($"{_winner} Won!"));
embed.WithFooter(efb => efb.WithText(GetText("ttt_has_won", _winner)));
return embed;
}
@ -169,23 +162,22 @@ namespace NadekoBot.Modules.Games
{
if (_phase == Phase.Started || _phase == Phase.Ended)
{
await _channel.SendErrorAsync(user.Mention + " TicTacToe Game is already running in this channel.").ConfigureAwait(false);
await _channel.SendErrorAsync(user.Mention + GetText("ttt_already_running")).ConfigureAwait(false);
return;
}
else if (_users[0] == user)
{
await _channel.SendErrorAsync(user.Mention + " You can't play against yourself.").ConfigureAwait(false);
await _channel.SendErrorAsync(user.Mention + GetText("ttt_against_yourself")).ConfigureAwait(false);
return;
}
_users[1] = user;
_log.Warn($"User {user} joined a TicTacToe game.");
_phase = Phase.Started;
timeoutTimer = new Timer(async (_) =>
_timeoutTimer = new Timer(async (_) =>
{
await moveLock.WaitAsync();
await _moveLock.WaitAsync();
try
{
if (_phase == Phase.Ended)
@ -194,11 +186,12 @@ namespace NadekoBot.Modules.Games
_phase = Phase.Ended;
if (_users[1] != null)
{
_winner = _users[curUserIndex ^= 1];
var del = previousMessage?.DeleteAsync();
_winner = _users[_curUserIndex ^= 1];
var del = _previousMessage?.DeleteAsync();
try
{
await _channel.EmbedAsync(GetEmbed("Time Expired!")).ConfigureAwait(false);
await _channel.EmbedAsync(GetEmbed(GetText("ttt_time_expired"))).ConfigureAwait(false);
if (del != null)
await del.ConfigureAwait(false);
}
catch { }
@ -209,21 +202,21 @@ namespace NadekoBot.Modules.Games
catch { }
finally
{
moveLock.Release();
_moveLock.Release();
}
}, null, 15000, Timeout.Infinite);
NadekoBot.Client.MessageReceived += Client_MessageReceived;
previousMessage = await _channel.EmbedAsync(GetEmbed("Game Started")).ConfigureAwait(false);
_previousMessage = await _channel.EmbedAsync(GetEmbed(GetText("game_started"))).ConfigureAwait(false);
}
private bool IsDraw()
{
for (int i = 0; i < 3; i++)
for (var i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
for (var j = 0; j < 3; j++)
{
if (_state[i, j] == null)
return false;
@ -236,10 +229,10 @@ namespace NadekoBot.Modules.Games
{
var _ = Task.Run(async () =>
{
await moveLock.WaitAsync().ConfigureAwait(false);
await _moveLock.WaitAsync().ConfigureAwait(false);
try
{
var curUser = _users[curUserIndex];
var curUser = _users[_curUserIndex];
if (_phase == Phase.Ended || msg.Author?.Id != curUser.Id)
return;
@ -249,53 +242,53 @@ namespace NadekoBot.Modules.Games
index <= 9 &&
_state[index / 3, index % 3] == null)
{
_state[index / 3, index % 3] = curUserIndex;
_state[index / 3, index % 3] = _curUserIndex;
// i'm lazy
if (_state[index / 3, 0] == _state[index / 3, 1] && _state[index / 3, 1] == _state[index / 3, 2])
{
_state[index / 3, 0] = curUserIndex + 2;
_state[index / 3, 1] = curUserIndex + 2;
_state[index / 3, 2] = curUserIndex + 2;
_state[index / 3, 0] = _curUserIndex + 2;
_state[index / 3, 1] = _curUserIndex + 2;
_state[index / 3, 2] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (_state[0, index % 3] == _state[1, index % 3] && _state[1, index % 3] == _state[2, index % 3])
{
_state[0, index % 3] = curUserIndex + 2;
_state[1, index % 3] = curUserIndex + 2;
_state[2, index % 3] = curUserIndex + 2;
_state[0, index % 3] = _curUserIndex + 2;
_state[1, index % 3] = _curUserIndex + 2;
_state[2, index % 3] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (curUserIndex == _state[0, 0] && _state[0, 0] == _state[1, 1] && _state[1, 1] == _state[2, 2])
else if (_curUserIndex == _state[0, 0] && _state[0, 0] == _state[1, 1] && _state[1, 1] == _state[2, 2])
{
_state[0, 0] = curUserIndex + 2;
_state[1, 1] = curUserIndex + 2;
_state[2, 2] = curUserIndex + 2;
_state[0, 0] = _curUserIndex + 2;
_state[1, 1] = _curUserIndex + 2;
_state[2, 2] = _curUserIndex + 2;
_phase = Phase.Ended;
}
else if (curUserIndex == _state[0, 2] && _state[0, 2] == _state[1, 1] && _state[1, 1] == _state[2, 0])
else if (_curUserIndex == _state[0, 2] && _state[0, 2] == _state[1, 1] && _state[1, 1] == _state[2, 0])
{
_state[0, 2] = curUserIndex + 2;
_state[1, 1] = curUserIndex + 2;
_state[2, 0] = curUserIndex + 2;
_state[0, 2] = _curUserIndex + 2;
_state[1, 1] = _curUserIndex + 2;
_state[2, 0] = _curUserIndex + 2;
_phase = Phase.Ended;
}
string reason = "";
var reason = "";
if (_phase == Phase.Ended) // if user won, stop receiving moves
{
reason = "Matched three!";
_winner = _users[curUserIndex];
reason = GetText("ttt_matched_three");
_winner = _users[_curUserIndex];
NadekoBot.Client.MessageReceived -= Client_MessageReceived;
OnEnded?.Invoke(this);
}
else if (IsDraw())
{
reason = "A draw!";
reason = GetText("ttt_a_draw");
_phase = Phase.Ended;
NadekoBot.Client.MessageReceived -= Client_MessageReceived;
OnEnded?.Invoke(this);
@ -304,19 +297,19 @@ namespace NadekoBot.Modules.Games
var sendstate = Task.Run(async () =>
{
var del1 = msg.DeleteAsync();
var del2 = previousMessage?.DeleteAsync();
try { previousMessage = await _channel.EmbedAsync(GetEmbed(reason)); } catch { }
var del2 = _previousMessage?.DeleteAsync();
try { _previousMessage = await _channel.EmbedAsync(GetEmbed(reason)); } catch { }
try { await del1; } catch { }
try { if (del2 != null) await del2; } catch { }
});
curUserIndex ^= 1;
_curUserIndex ^= 1;
timeoutTimer.Change(15000, Timeout.Infinite);
_timeoutTimer.Change(15000, Timeout.Infinite);
}
}
finally
{
moveLock.Release();
_moveLock.Release();
}
});

View File

@ -17,36 +17,42 @@ namespace NadekoBot.Modules.Games.Trivia
public class TriviaGame
{
private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1, 1);
private Logger _log { get; }
private readonly Logger _log;
public IGuild guild { get; }
public ITextChannel channel { get; }
public IGuild Guild { get; }
public ITextChannel Channel { get; }
private int QuestionDurationMiliseconds { get; } = 30000;
private int HintTimeoutMiliseconds { get; } = 6000;
public bool ShowHints { get; } = true;
private int questionDurationMiliseconds { get; } = 30000;
private int hintTimeoutMiliseconds { get; } = 6000;
public bool ShowHints { get; }
private CancellationTokenSource triviaCancelSource { get; set; }
public TriviaQuestion CurrentQuestion { get; private set; }
public HashSet<TriviaQuestion> oldQuestions { get; } = new HashSet<TriviaQuestion>();
public HashSet<TriviaQuestion> OldQuestions { get; } = new HashSet<TriviaQuestion>();
public ConcurrentDictionary<IGuildUser, int> Users { get; } = new ConcurrentDictionary<IGuildUser, int>();
public bool GameActive { get; private set; } = false;
public bool GameActive { get; private set; }
public bool ShouldStopGame { get; private set; }
public int WinRequirement { get; } = 10;
public int WinRequirement { get; }
public TriviaGame(IGuild guild, ITextChannel channel, bool showHints, int winReq)
{
this._log = LogManager.GetCurrentClassLogger();
_log = LogManager.GetCurrentClassLogger();
this.ShowHints = showHints;
this.guild = guild;
this.channel = channel;
this.WinRequirement = winReq;
ShowHints = showHints;
Guild = guild;
Channel = channel;
WinRequirement = winReq;
}
private string GetText(string key, params object[] replacements) =>
NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(Channel.GuildId),
typeof(Games).Name.ToLowerInvariant(),
replacements);
public async Task StartGame()
{
while (!ShouldStopGame)
@ -55,26 +61,24 @@ namespace NadekoBot.Modules.Games.Trivia
triviaCancelSource = new CancellationTokenSource();
// load question
CurrentQuestion = TriviaQuestionPool.Instance.GetRandomQuestion(oldQuestions);
if (CurrentQuestion == null ||
string.IsNullOrWhiteSpace(CurrentQuestion.Answer) ||
string.IsNullOrWhiteSpace(CurrentQuestion.Question))
CurrentQuestion = TriviaQuestionPool.Instance.GetRandomQuestion(OldQuestions);
if (string.IsNullOrWhiteSpace(CurrentQuestion?.Answer) || string.IsNullOrWhiteSpace(CurrentQuestion.Question))
{
await channel.SendErrorAsync("Trivia Game", "Failed loading a question.").ConfigureAwait(false);
await Channel.SendErrorAsync(GetText("trivia_game"), GetText("failed_loading_question")).ConfigureAwait(false);
return;
}
oldQuestions.Add(CurrentQuestion); //add it to exclusion list so it doesn't show up again
OldQuestions.Add(CurrentQuestion); //add it to exclusion list so it doesn't show up again
EmbedBuilder questionEmbed;
IUserMessage questionMessage;
try
{
questionEmbed = new EmbedBuilder().WithOkColor()
.WithTitle("Trivia Game")
.AddField(eab => eab.WithName("Category").WithValue(CurrentQuestion.Category))
.AddField(eab => eab.WithName("Question").WithValue(CurrentQuestion.Question));
.WithTitle(GetText("trivia_game"))
.AddField(eab => eab.WithName(GetText("category")).WithValue(CurrentQuestion.Category))
.AddField(eab => eab.WithName(GetText("question")).WithValue(CurrentQuestion.Question));
questionMessage = await channel.EmbedAsync(questionEmbed).ConfigureAwait(false);
questionMessage = await Channel.EmbedAsync(questionEmbed).ConfigureAwait(false);
}
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound ||
ex.HttpCode == System.Net.HttpStatusCode.Forbidden ||
@ -99,7 +103,7 @@ namespace NadekoBot.Modules.Games.Trivia
try
{
//hint
await Task.Delay(HintTimeoutMiliseconds, triviaCancelSource.Token).ConfigureAwait(false);
await Task.Delay(hintTimeoutMiliseconds, triviaCancelSource.Token).ConfigureAwait(false);
if (ShowHints)
try
{
@ -113,7 +117,7 @@ namespace NadekoBot.Modules.Games.Trivia
catch (Exception ex) { _log.Warn(ex); }
//timeout
await Task.Delay(QuestionDurationMiliseconds - HintTimeoutMiliseconds, triviaCancelSource.Token).ConfigureAwait(false);
await Task.Delay(questionDurationMiliseconds - hintTimeoutMiliseconds, triviaCancelSource.Token).ConfigureAwait(false);
}
catch (TaskCanceledException) { } //means someone guessed the answer
@ -124,7 +128,7 @@ namespace NadekoBot.Modules.Games.Trivia
NadekoBot.Client.MessageReceived -= PotentialGuess;
}
if (!triviaCancelSource.IsCancellationRequested)
try { await channel.SendErrorAsync("Trivia Game", $"**Time's up!** The correct answer was **{CurrentQuestion.Answer}**").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
try { await Channel.SendErrorAsync(GetText("trivia_game"), GetText("trivia_times_up", Format.Bold(CurrentQuestion.Answer))).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
await Task.Delay(2000).ConfigureAwait(false);
}
}
@ -133,7 +137,7 @@ namespace NadekoBot.Modules.Games.Trivia
{
ShouldStopGame = true;
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
await Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName("Trivia Game Ended"))
.WithTitle("Final Results")
.WithDescription(GetLeaderboard())).ConfigureAwait(false);
@ -144,7 +148,7 @@ namespace NadekoBot.Modules.Games.Trivia
var old = ShouldStopGame;
ShouldStopGame = true;
if (!old)
try { await channel.SendConfirmAsync("Trivia Game", "Stopping after this question.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
try { await Channel.SendConfirmAsync(GetText("trivia_game"), GetText("trivia_stopping")).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
}
private async Task PotentialGuess(SocketMessage imsg)
@ -155,11 +159,9 @@ namespace NadekoBot.Modules.Games.Trivia
return;
var umsg = imsg as SocketUserMessage;
if (umsg == null)
return;
var textChannel = umsg.Channel as ITextChannel;
if (textChannel == null || textChannel.Guild != guild)
var textChannel = umsg?.Channel as ITextChannel;
if (textChannel == null || textChannel.Guild != Guild)
return;
var guildUser = (IGuildUser)umsg.Author;
@ -182,13 +184,24 @@ namespace NadekoBot.Modules.Games.Trivia
if (Users[guildUser] == WinRequirement)
{
ShouldStopGame = true;
try { await channel.SendConfirmAsync("Trivia Game", $"{guildUser.Mention} guessed it and WON the game! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false); } catch { }
try
{
await Channel.SendConfirmAsync(GetText("trivia_game"),
GetText("trivia_win",
guildUser.Mention,
Format.Bold(CurrentQuestion.Answer))).ConfigureAwait(false);
}
catch
{
// ignored
}
var reward = NadekoBot.BotConfig.TriviaCurrencyReward;
if (reward > 0)
await CurrencyHandler.AddCurrencyAsync(guildUser, "Won trivia", reward, true).ConfigureAwait(false);
return;
}
await channel.SendConfirmAsync("Trivia Game", $"{guildUser.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false);
await Channel.SendConfirmAsync(GetText("trivia_game"),
GetText("trivia_guess", guildUser.Mention, Format.Bold(CurrentQuestion.Answer))).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
@ -197,13 +210,13 @@ namespace NadekoBot.Modules.Games.Trivia
public string GetLeaderboard()
{
if (Users.Count == 0)
return "No results.";
return GetText("no_results");
var sb = new StringBuilder();
foreach (var kvp in Users.OrderByDescending(kvp => kvp.Value))
{
sb.AppendLine($"**{kvp.Key.Username}** has {kvp.Value} points".ToString().SnPl(kvp.Value));
sb.AppendLine(GetText("trivia_points", Format.Bold(kvp.Key.ToString()), kvp.Value).SnPl(kvp.Value));
}
return sb.ToString();

View File

@ -3,9 +3,7 @@ using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Trivia;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
@ -14,7 +12,7 @@ namespace NadekoBot.Modules.Games
public partial class Games
{
[Group]
public class TriviaCommands : ModuleBase
public class TriviaCommands : NadekoSubmodule
{
public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>();
@ -31,7 +29,7 @@ namespace NadekoBot.Modules.Games
var showHints = !additionalArgs.Contains("nohint");
TriviaGame trivia = new TriviaGame(channel.Guild, channel, showHints, winReq);
var trivia = new TriviaGame(channel.Guild, channel, showHints, winReq);
if (RunningTrivias.TryAdd(channel.Guild.Id, trivia))
{
try
@ -45,8 +43,9 @@ namespace NadekoBot.Modules.Games
}
return;
}
else
await Context.Channel.SendErrorAsync("Trivia game is already running on this server.\n" + trivia.CurrentQuestion).ConfigureAwait(false);
await Context.Channel.SendErrorAsync(GetText("trivia_already_running") + "\n" + trivia.CurrentQuestion)
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -58,11 +57,11 @@ namespace NadekoBot.Modules.Games
TriviaGame trivia;
if (RunningTrivias.TryGetValue(channel.Guild.Id, out trivia))
{
await channel.SendConfirmAsync("Leaderboard", trivia.GetLeaderboard()).ConfigureAwait(false);
await channel.SendConfirmAsync(GetText("leaderboard"), trivia.GetLeaderboard()).ConfigureAwait(false);
return;
}
await channel.SendErrorAsync("No trivia is running on this server.").ConfigureAwait(false);
await ReplyErrorLocalized("trivia_none").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -78,7 +77,7 @@ namespace NadekoBot.Modules.Games
return;
}
await channel.SendErrorAsync("No trivia is running on this server.").ConfigureAwait(false);
await ReplyErrorLocalized("trivia_none").ConfigureAwait(false);
}
}
}

View File

@ -4,16 +4,29 @@ using NadekoBot.Services;
using System.Threading.Tasks;
using NadekoBot.Attributes;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Threading;
using NadekoBot.Extensions;
using System.Net.Http;
using ImageSharp;
using NadekoBot.DataStructures;
using NLog;
namespace NadekoBot.Modules.Games
{
[NadekoModule("Games", ">")]
public partial class Games : NadekoModule
public partial class Games : NadekoTopLevelModule
{
private static string[] _8BallResponses { get; } = NadekoBot.BotConfig.EightBallResponses.Select(ebr => ebr.Text).ToArray();
private static readonly ImmutableArray<string> _8BallResponses = NadekoBot.BotConfig.EightBallResponses.Select(ebr => ebr.Text).ToImmutableArray();
private static readonly Timer _t = new Timer((_) =>
{
_girlRatings.Clear();
}, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1));
[NadekoCommand, Usage, Description, Aliases]
public async Task Choose([Remainder] string list = null)
@ -21,7 +34,7 @@ namespace NadekoBot.Modules.Games
if (string.IsNullOrWhiteSpace(list))
return;
var listArr = list.Split(';');
if (listArr.Count() < 2)
if (listArr.Length < 2)
return;
var rng = new NadekoRandom();
await Context.Channel.SendConfirmAsync("🤔", listArr[rng.Next(0, listArr.Length)]).ConfigureAwait(false);
@ -34,21 +47,24 @@ namespace NadekoBot.Modules.Games
return;
await Context.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
.AddField(efb => efb.WithName("❓ Question").WithValue(question).WithIsInline(false))
.AddField(efb => efb.WithName("🎱 8Ball").WithValue(_8BallResponses[new NadekoRandom().Next(0, _8BallResponses.Length)]).WithIsInline(false)));
.AddField(efb => efb.WithName("❓ " + GetText("question") ).WithValue(question).WithIsInline(false))
.AddField(efb => efb.WithName("🎱 " + GetText("8ball")).WithValue(_8BallResponses[new NadekoRandom().Next(0, _8BallResponses.Length)]).WithIsInline(false)));
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Rps(string input)
{
Func<int,string> GetRPSPick = (p) =>
Func<int,string> getRpsPick = (p) =>
{
if (p == 0)
switch (p)
{
case 0:
return "🚀";
else if (p == 1)
case 1:
return "📎";
else
default:
return "✂️";
}
};
int pick;
@ -72,19 +88,176 @@ namespace NadekoBot.Modules.Games
return;
}
var nadekoPick = new NadekoRandom().Next(0, 3);
var msg = "";
string msg;
if (pick == nadekoPick)
msg = $"It's a draw! Both picked {GetRPSPick(pick)}";
msg = GetText("rps_draw", getRpsPick(pick));
else if ((pick == 0 && nadekoPick == 1) ||
(pick == 1 && nadekoPick == 2) ||
(pick == 2 && nadekoPick == 0))
msg = $"{NadekoBot.Client.CurrentUser.Mention} won! {GetRPSPick(nadekoPick)} beats {GetRPSPick(pick)}";
msg = GetText("rps_win", NadekoBot.Client.CurrentUser.Mention,
getRpsPick(nadekoPick), getRpsPick(pick));
else
msg = $"{Context.User.Mention} won! {GetRPSPick(pick)} beats {GetRPSPick(nadekoPick)}";
msg = GetText("rps_win", Context.User.Mention, getRpsPick(pick),
getRpsPick(nadekoPick));
await Context.Channel.SendConfirmAsync(msg).ConfigureAwait(false);
}
private static readonly ConcurrentDictionary<ulong, GirlRating> _girlRatings = new ConcurrentDictionary<ulong, GirlRating>();
public class GirlRating
{
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
public double Crazy { get; }
public double Hot { get; }
public int Roll { get; }
public string Advice { get; }
public AsyncLazy<string> Url { get; }
public GirlRating(double crazy, double hot, int roll, string advice)
{
Crazy = crazy;
Hot = hot;
Roll = roll;
Advice = advice; // convenient to have it here, even though atm there are only few different ones.
Url = new AsyncLazy<string>(async () =>
{
try
{
using (var ms = new MemoryStream(NadekoBot.Images.WifeMatrix.ToArray(), false))
using (var img = new ImageSharp.Image(ms))
{
const int minx = 35;
const int miny = 385;
const int length = 345;
var pointx = (int)(minx + length * (Hot / 10));
var pointy = (int)(miny - length * ((Crazy - 4) / 6));
using (var pointMs = new MemoryStream(NadekoBot.Images.RategirlDot.ToArray(), false))
using (var pointImg = new ImageSharp.Image(pointMs))
{
img.DrawImage(pointImg, 100, default(Size), new Point(pointx - 10, pointy - 10));
}
string url;
using (var http = new HttpClient())
using (var imgStream = new MemoryStream())
{
img.Save(imgStream);
var byteContent = new ByteArrayContent(imgStream.ToArray());
http.AddFakeHeaders();
var reponse = await http.PutAsync("https://transfer.sh/img.png", byteContent);
url = await reponse.Content.ReadAsStringAsync();
}
return url;
}
}
catch (Exception ex)
{
_log.Warn(ex);
return null;
}
});
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task RateGirl(IGuildUser usr)
{
var gr = _girlRatings.GetOrAdd(usr.Id, GetGirl);
var img = await gr.Url;
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle("Girl Rating For " + usr)
.AddField(efb => efb.WithName("Hot").WithValue(gr.Hot.ToString("F2")).WithIsInline(true))
.AddField(efb => efb.WithName("Crazy").WithValue(gr.Crazy.ToString("F2")).WithIsInline(true))
.AddField(efb => efb.WithName("Advice").WithValue(gr.Advice).WithIsInline(false))
.WithImageUrl(img)).ConfigureAwait(false);
}
private double NextDouble(double x, double y)
{
var rng = new Random();
return rng.NextDouble() * (y - x) + x;
}
private GirlRating GetGirl(ulong uid)
{
var rng = new NadekoRandom();
var roll = rng.Next(1, 1001);
if ((uid == 185968432783687681 ||
uid == 265642040950390784) && roll >= 900)
roll = 1000;
double hot;
double crazy;
string advice;
if (roll < 500)
{
hot = NextDouble(0, 5);
crazy = NextDouble(4, 10);
advice =
"This is your NO-GO ZONE. We do not hang around, and date, and marry women who are atleast, in our mind, a 5. " +
"So, this is your no-go zone. You don't go here. You just rule this out. Life is better this way, that's the way it is.";
}
else if (roll < 750)
{
hot = NextDouble(5, 8);
crazy = NextDouble(4, .6 * hot + 4);
advice = "Above a 5, and to about an 8, and below the crazy line - this is your FUN ZONE. You can " +
"hang around here, and meet these girls and spend time with them. Keep in mind, while you're " +
"in the fun zone, you want to move OUT of the fun zone to a more permanent location. " +
"These girls are most of the time not crazy.";
}
else if (roll < 900)
{
hot = NextDouble(5, 10);
crazy = NextDouble(.61 * hot + 4, 10);
advice = "Above the crazy line - it's the DANGER ZONE. This is redheads, strippers, anyone named Tiffany, " +
"hairdressers... This is where your car gets keyed, you get bunny in the pot, your tires get slashed, " +
"and you wind up in jail.";
}
else if (roll < 951)
{
hot = NextDouble(8, 10);
crazy = NextDouble(7, .6 * hot + 4);
advice = "Below the crazy line, above an 8 hot, but still about 7 crazy. This is your DATE ZONE. " +
"You can stay in the date zone indefinitely. These are the girls you introduce to your friends and your family. " +
"They're good looking, and they're reasonably not crazy most of the time. You can stay here indefinitely.";
}
else if (roll < 990)
{
hot = NextDouble(8, 10);
crazy = NextDouble(5, 7);
advice = "Above an 8 hot, and between about 7 and a 5 crazy - this is WIFE ZONE. You you meet this girl, you should consider long-term " +
"relationship. Rare.";
}
else if (roll < 999)
{
hot = NextDouble(8, 10);
crazy = NextDouble(2, 3.99d);
advice = "You've met a girl she's above 8 hot, and not crazy at all (below 4)... totally cool?" +
" You should be careful. That's a dude. It's a tranny.";
}
else
{
hot = NextDouble(8, 10);
crazy = NextDouble(4, 5);
advice = "Below 5 crazy, and above 8 hot, this is the UNICORN ZONE, these things don't exist." +
"If you find a unicorn, please capture it safely, keep it alive, we'd like to study it, " +
"and maybe look at how to replicate that.";
}
return new GirlRating(crazy, hot, roll, advice);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Linux(string guhnoo, string loonix)
{

View File

@ -13,7 +13,7 @@ using System.Collections.Generic;
namespace NadekoBot.Modules.Help
{
[NadekoModule("Help", "-")]
public class Help : NadekoModule
public class Help : NadekoTopLevelModule
{
private static string helpString { get; } = NadekoBot.BotConfig.HelpString;
public static string HelpString => String.Format(helpString, NadekoBot.Credentials.ClientId, NadekoBot.ModulePrefixes[typeof(Help).Name]);

View File

@ -77,10 +77,10 @@ namespace NadekoBot.Modules.Music.Classes
public IVoiceChannel PlaybackVoiceChannel { get; private set; }
public ITextChannel OutputTextChannel { get; set; }
private bool destroyed { get; set; } = false;
public bool RepeatSong { get; private set; } = false;
public bool RepeatPlaylist { get; private set; } = false;
public bool Autoplay { get; set; } = false;
private bool destroyed { get; set; }
public bool RepeatSong { get; private set; }
public bool RepeatPlaylist { get; private set; }
public bool Autoplay { get; set; }
public uint MaxQueueSize { get; set; } = 0;
private ConcurrentQueue<Action> actionQueue { get; } = new ConcurrentQueue<Action>();

View File

@ -5,12 +5,9 @@ using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using VideoLibrary;
using System.Net;
namespace NadekoBot.Modules.Music.Classes
@ -32,22 +29,22 @@ namespace NadekoBot.Modules.Music.Classes
public string QueuerName { get; set; }
public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
public TimeSpan CurrentTime => TimeSpan.FromSeconds(bytesSent / frameBytes / (1000 / milliseconds));
public TimeSpan CurrentTime => TimeSpan.FromSeconds(bytesSent / (float)_frameBytes / (1000 / (float)_milliseconds));
const int milliseconds = 20;
const int samplesPerFrame = (48000 / 1000) * milliseconds;
const int frameBytes = 3840; //16-bit, 2 channels
private const int _milliseconds = 20;
private const int _samplesPerFrame = (48000 / 1000) * _milliseconds;
private const int _frameBytes = 3840; //16-bit, 2 channels
private ulong bytesSent { get; set; } = 0;
private ulong bytesSent { get; set; }
//pwetty
public string PrettyProvider =>
$"{(SongInfo.Provider ?? "No Provider")}";
$"{(SongInfo.Provider ?? "???")}";
public string PrettyFullTime => PrettyCurrentTime + " / " + PrettyTotalTime;
public string PrettyName => $"**[{SongInfo.Title.TrimTo(65)}]({songUrl})**";
public string PrettyName => $"**[{SongInfo.Title.TrimTo(65)}]({SongUrl})**";
public string PrettyInfo => $"{MusicPlayer.PrettyVolume} | {PrettyTotalTime} | {PrettyProvider} | {QueuerName}";
@ -65,37 +62,34 @@ namespace NadekoBot.Modules.Music.Classes
}
}
private string PrettyTotalTime {
get {
public string PrettyTotalTime {
get
{
if (TotalTime == TimeSpan.Zero)
return "(?)";
else if (TotalTime == TimeSpan.MaxValue)
if (TotalTime == TimeSpan.MaxValue)
return "∞";
else
{
var time = TotalTime.ToString(@"mm\:ss");
var hrs = (int)TotalTime.TotalHours;
if (hrs > 0)
return hrs + ":" + time;
else
return time;
}
}
}
public string Thumbnail {
get {
switch (SongInfo.ProviderType)
{
case MusicType.Radio:
return $"https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png"; //test links
return "https://cdn.discordapp.com/attachments/155726317222887425/261850925063340032/1482522097_radio.png"; //test links
case MusicType.Normal:
//todo have videoid in songinfo from the start
var videoId = Regex.Match(SongInfo.Query, "<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+");
return $"https://img.youtube.com/vi/{ videoId }/0.jpg";
case MusicType.Local:
return $"https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png"; //test links
return "https://cdn.discordapp.com/attachments/155726317222887425/261850914783100928/1482522077_music.png"; //test links
case MusicType.Soundcloud:
return SongInfo.AlbumArt;
default:
@ -104,7 +98,7 @@ namespace NadekoBot.Modules.Music.Classes
}
}
private string songUrl {
public string SongUrl {
get {
switch (SongInfo.ProviderType)
{
@ -122,36 +116,32 @@ namespace NadekoBot.Modules.Music.Classes
}
}
private int skipTo = 0;
public int SkipTo {
get { return skipTo; }
set {
skipTo = value;
bytesSent = (ulong)skipTo * 3840 * 50;
}
}
public int SkipTo { get; set; }
private readonly Logger _log;
public Song(SongInfo songInfo)
{
this.SongInfo = songInfo;
this._log = LogManager.GetCurrentClassLogger();
SongInfo = songInfo;
_log = LogManager.GetCurrentClassLogger();
}
public Song Clone()
{
var s = new Song(SongInfo);
s.MusicPlayer = MusicPlayer;
s.QueuerName = QueuerName;
var s = new Song(SongInfo)
{
MusicPlayer = MusicPlayer,
QueuerName = QueuerName
};
return s;
}
public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
{
bytesSent = (ulong) SkipTo * 3840 * 50;
var filename = Path.Combine(Music.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
SongBuffer inStream = new SongBuffer(MusicPlayer, filename, SongInfo, skipTo, frameBytes * 100);
var inStream = new SongBuffer(MusicPlayer, filename, SongInfo, SkipTo, _frameBytes * 100);
var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false);
try
@ -200,22 +190,22 @@ namespace NadekoBot.Modules.Music.Classes
var outStream = voiceClient.CreatePCMStream(960);
int nextTime = Environment.TickCount + milliseconds;
int nextTime = Environment.TickCount + _milliseconds;
byte[] buffer = new byte[frameBytes];
byte[] buffer = new byte[_frameBytes];
while (!cancelToken.IsCancellationRequested && //song canceled for whatever reason
!(MusicPlayer.MaxPlaytimeSeconds != 0 && CurrentTime.TotalSeconds >= MusicPlayer.MaxPlaytimeSeconds)) // or exceedded max playtime
{
//Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------");
var read = await inStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
//await inStream.CopyToAsync(voiceClient.OutputStream);
if (read < frameBytes)
if (read < _frameBytes)
_log.Debug("read {0}", read);
unchecked
{
bytesSent += (ulong)read;
}
if (read < frameBytes)
if (read < _frameBytes)
{
if (read == 0)
{
@ -231,12 +221,12 @@ namespace NadekoBot.Modules.Music.Classes
_log.Warn("Slow connection has disrupted music, waiting a bit for buffer");
await Task.Delay(1000, cancelToken).ConfigureAwait(false);
nextTime = Environment.TickCount + milliseconds;
nextTime = Environment.TickCount + _milliseconds;
}
else
{
await Task.Delay(100, cancelToken).ConfigureAwait(false);
nextTime = Environment.TickCount + milliseconds;
nextTime = Environment.TickCount + _milliseconds;
}
}
else
@ -245,16 +235,16 @@ namespace NadekoBot.Modules.Music.Classes
else
attempt = 0;
while (this.MusicPlayer.Paused)
while (MusicPlayer.Paused)
{
await Task.Delay(200, cancelToken).ConfigureAwait(false);
nextTime = Environment.TickCount + milliseconds;
nextTime = Environment.TickCount + _milliseconds;
}
buffer = AdjustVolume(buffer, MusicPlayer.Volume);
if (read != frameBytes) continue;
nextTime = unchecked(nextTime + milliseconds);
if (read != _frameBytes) continue;
nextTime = unchecked(nextTime + _milliseconds);
int delayMillis = unchecked(nextTime - Environment.TickCount);
if (delayMillis > 0)
await Task.Delay(delayMillis, cancelToken).ConfigureAwait(false);
@ -264,7 +254,6 @@ namespace NadekoBot.Modules.Music.Classes
finally
{
await bufferTask;
if (inStream != null)
inStream.Dispose();
}
}
@ -279,25 +268,20 @@ namespace NadekoBot.Modules.Music.Classes
}
//aidiakapi ftw
public unsafe static byte[] AdjustVolume(byte[] audioSamples, float volume)
public static unsafe byte[] AdjustVolume(byte[] audioSamples, float volume)
{
Contract.Requires(audioSamples != null);
Contract.Requires(audioSamples.Length % 2 == 0);
Contract.Requires(volume >= 0f && volume <= 1f);
Contract.Assert(BitConverter.IsLittleEndian);
if (Math.Abs(volume - 1f) < 0.0001f) return audioSamples;
// 16-bit precision for the multiplication
int volumeFixed = (int)Math.Round(volume * 65536d);
var volumeFixed = (int)Math.Round(volume * 65536d);
int count = audioSamples.Length / 2;
var count = audioSamples.Length / 2;
fixed (byte* srcBytes = audioSamples)
{
short* src = (short*)srcBytes;
var src = (short*)srcBytes;
for (int i = count; i != 0; i--, src++)
for (var i = count; i != 0; i--, src++)
*src = (short)(((*src) * volumeFixed) >> 16);
}

View File

@ -6,12 +6,14 @@ using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using NLog;
using VideoLibrary;
namespace NadekoBot.Modules.Music.Classes
{
public static class SongHandler
{
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
public static async Task<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal)
{
if (string.IsNullOrWhiteSpace(query))
@ -106,7 +108,8 @@ namespace NadekoBot.Modules.Music.Classes
}
catch (Exception ex)
{
Console.WriteLine($"Failed resolving the link.{ex.Message}");
_log.Warn($"Failed resolving the link.{ex.Message}");
_log.Warn(ex);
return null;
}
}
@ -137,7 +140,7 @@ namespace NadekoBot.Modules.Music.Classes
}
catch
{
Console.WriteLine($"Failed reading .pls:\n{file}");
_log.Warn($"Failed reading .pls:\n{file}");
return null;
}
}
@ -156,7 +159,7 @@ namespace NadekoBot.Modules.Music.Classes
}
catch
{
Console.WriteLine($"Failed reading .m3u:\n{file}");
_log.Warn($"Failed reading .m3u:\n{file}");
return null;
}
@ -172,7 +175,7 @@ namespace NadekoBot.Modules.Music.Classes
}
catch
{
Console.WriteLine($"Failed reading .asx:\n{file}");
_log.Warn($"Failed reading .asx:\n{file}");
return null;
}
}
@ -192,7 +195,7 @@ namespace NadekoBot.Modules.Music.Classes
}
catch
{
Console.WriteLine($"Failed reading .xspf:\n{file}");
_log.Warn($"Failed reading .xspf:\n{file}");
return null;
}
}

View File

@ -14,14 +14,13 @@ using System.Net.Http;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using NadekoBot.Services.Database.Models;
using System.Text.RegularExpressions;
using System.Threading;
namespace NadekoBot.Modules.Music
{
[NadekoModule("Music", "!!")]
[DontAutoLoad]
public partial class Music : NadekoModule
public class Music : NadekoTopLevelModule
{
public static ConcurrentDictionary<ulong, MusicPlayer> MusicPlayers { get; } = new ConcurrentDictionary<ulong, MusicPlayer>();
@ -78,7 +77,10 @@ namespace NadekoBot.Modules.Music
}
}
catch { }
catch
{
// ignored
}
return Task.CompletedTask;
}
@ -153,7 +155,14 @@ namespace NadekoBot.Modules.Music
return;
var val = musicPlayer.FairPlay = !musicPlayer.FairPlay;
await channel.SendConfirmAsync("Fair play " + (val ? "enabled" : "disabled") + ".").ConfigureAwait(false);
if (val)
{
await ReplyConfirmLocalized("fp_enabled").ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("fp_disabled").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -182,57 +191,56 @@ namespace NadekoBot.Modules.Music
[RequireContext(ContextType.Guild)]
public async Task ListQueue(int page = 1)
{
Song currentSong;
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer) ||
(currentSong = musicPlayer?.CurrentSong) == null)
{
await Context.Channel.SendErrorAsync("🎵 No active music player.").ConfigureAwait(false);
await ReplyErrorLocalized("no_player").ConfigureAwait(false);
return;
}
if (page <= 0)
return;
var currentSong = musicPlayer.CurrentSong;
if (currentSong == null)
{
await Context.Channel.SendErrorAsync("🎵 No active music player.").ConfigureAwait(false);
return;
}
try { await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { }
const int itemsPerPage = 10;
var total = musicPlayer.TotalPlaytime;
var totalStr = total == TimeSpan.MaxValue ? "∞" : $"{(int)total.TotalHours}h {total.Minutes}m {total.Seconds}s";
var totalStr = total == TimeSpan.MaxValue ? "∞" : GetText("time_format",
(int) total.TotalHours,
total.Minutes,
total.Seconds);
var maxPlaytime = musicPlayer.MaxPlaytimeSeconds;
var lastPage = musicPlayer.Playlist.Count / itemsPerPage;
Func<int, EmbedBuilder> printAction = (curPage) =>
Func<int, EmbedBuilder> printAction = curPage =>
{
int startAt = itemsPerPage * (curPage - 1);
var startAt = itemsPerPage * (curPage - 1);
var number = 0 + startAt;
var desc = string.Join("\n", musicPlayer.Playlist
.Skip(startAt)
.Take(itemsPerPage)
.Select(v => $"`{++number}.` {v.PrettyFullName}"));
if (currentSong != null)
desc = $"`🔊` {currentSong.PrettyFullName}\n\n" + desc;
if (musicPlayer.RepeatSong)
desc = "🔂 Repeating Current Song\n\n" + desc;
desc = "🔂 " + GetText("repeating_cur_song") +"\n\n" + desc;
else if (musicPlayer.RepeatPlaylist)
desc = "🔁 Repeating Playlist\n\n" + desc;
desc = "🔁 " + GetText("repeating_playlist")+"\n\n" + desc;
var embed = new EmbedBuilder()
.WithAuthor(eab => eab.WithName($"Player Queue - Page {curPage}/{lastPage + 1}")
.WithAuthor(eab => eab.WithName(GetText("player_queue", curPage, lastPage + 1))
.WithMusicIcon())
.WithDescription(desc)
.WithFooter(ef => ef.WithText($"{musicPlayer.PrettyVolume} | {musicPlayer.Playlist.Count} " +
$"{("tracks".SnPl(musicPlayer.Playlist.Count))} | {totalStr} | " +
(musicPlayer.FairPlay ? "✔fairplay" : "✖fairplay") + $" | " + (maxPlaytime == 0 ? "unlimited" : $"{maxPlaytime}s limit")))
(musicPlayer.FairPlay
? "✔️" + GetText("fairplay")
: "✖️" + GetText("fairplay")) + " | " +
(maxPlaytime == 0 ? "unlimited" : GetText("play_limit", maxPlaytime))))
.WithOkColor();
return embed;
@ -253,7 +261,7 @@ namespace NadekoBot.Modules.Music
try { await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { }
var embed = new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName("Now Playing").WithMusicIcon())
.WithAuthor(eab => eab.WithName(GetText("now_playing")).WithMusicIcon())
.WithDescription(currentSong.PrettyName)
.WithThumbnailUrl(currentSong.Thumbnail)
.WithFooter(ef => ef.WithText(musicPlayer.PrettyVolume + " | " + currentSong.PrettyFullTime + $" | {currentSong.PrettyProvider} | {currentSong.QueuerName}"));
@ -270,21 +278,22 @@ namespace NadekoBot.Modules.Music
return;
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return;
if (val < 0)
if (val < 0 || val > 100)
{
await ReplyErrorLocalized("volume_input_invalid").ConfigureAwait(false);
return;
}
var volume = musicPlayer.SetVolume(val);
await Context.Channel.SendConfirmAsync($"🎵 Volume set to {volume}%").ConfigureAwait(false);
await ReplyConfirmLocalized("volume_set", volume).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Defvol([Remainder] int val)
{
if (val < 0 || val > 100)
{
await Context.Channel.SendErrorAsync("Volume number invalid. Must be between 0 and 100").ConfigureAwait(false);
await ReplyErrorLocalized("volume_input_invalid").ConfigureAwait(false);
return;
}
using (var uow = DbHandler.UnitOfWork())
@ -292,7 +301,7 @@ namespace NadekoBot.Modules.Music
uow.GuildConfigs.For(Context.Guild.Id, set => set).DefaultMusicVolume = val / 100.0f;
uow.Complete();
}
await Context.Channel.SendConfirmAsync($"🎵 Default volume set to {val}%").ConfigureAwait(false);
await ReplyConfirmLocalized("defvol_set", val).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -306,13 +315,10 @@ namespace NadekoBot.Modules.Music
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return;
if (musicPlayer.Playlist.Count < 2)
{
await Context.Channel.SendErrorAsync("💢 Not enough songs in order to perform the shuffle.").ConfigureAwait(false);
return;
}
musicPlayer.Shuffle();
await Context.Channel.SendConfirmAsync("🎵 Songs shuffled.").ConfigureAwait(false);
await ReplyConfirmLocalized("songs_shuffled").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -325,29 +331,31 @@ namespace NadekoBot.Modules.Music
return;
if (((IGuildUser)Context.User).VoiceChannel?.Guild != Context.Guild)
{
await Context.Channel.SendErrorAsync($"💢 You need to be in a **voice channel** on this server.").ConfigureAwait(false);
await ReplyErrorLocalized("must_be_in_voice").ConfigureAwait(false);
return;
}
var plId = (await NadekoBot.Google.GetPlaylistIdsByKeywordsAsync(arg).ConfigureAwait(false)).FirstOrDefault();
if (plId == null)
{
await Context.Channel.SendErrorAsync("No search results for that query.");
await ReplyErrorLocalized("no_search_results").ConfigureAwait(false);
return;
}
var ids = await NadekoBot.Google.GetPlaylistTracksAsync(plId, 500).ConfigureAwait(false);
if (!ids.Any())
{
await Context.Channel.SendErrorAsync($"🎵 Failed to find any songs.").ConfigureAwait(false);
await ReplyErrorLocalized("no_search_results").ConfigureAwait(false);
return;
}
var count = ids.Count();
var msg = await Context.Channel.SendMessageAsync($"🎵 Attempting to queue **{count}** songs".SnPl(count) + "...").ConfigureAwait(false);
var msg = await Context.Channel.SendMessageAsync(GetText("attempting_to_queue",
Format.Bold(count.ToString())))
.ConfigureAwait(false);
var cancelSource = new CancellationTokenSource();
var gusr = (IGuildUser)Context.User;
//todo use grouping
while (ids.Any() && !cancelSource.IsCancellationRequested)
{
var tasks = Task.WhenAll(ids.Take(5).Select(async id =>
@ -366,7 +374,7 @@ namespace NadekoBot.Modules.Music
ids = ids.Skip(5);
}
await msg.ModifyAsync(m => m.Content = "✅ Playlist queue complete.").ConfigureAwait(false);
await msg.ModifyAsync(m => m.Content = GetText("playlist_queue_complete")).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -392,7 +400,7 @@ namespace NadekoBot.Modules.Music
{
try
{
mp.AddSong(new Song(new Classes.SongInfo
mp.AddSong(new Song(new SongInfo
{
Title = svideo.FullName,
Provider = "SoundCloud",
@ -434,20 +442,20 @@ namespace NadekoBot.Modules.Music
// ignored
}
}
await Context.Channel.SendConfirmAsync("🎵 Directory queue complete.").ConfigureAwait(false);
await ReplyConfirmLocalized("dir_queue_complete").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Radio(string radio_link)
public async Task Radio(string radioLink)
{
if (((IGuildUser)Context.User).VoiceChannel?.Guild != Context.Guild)
{
await Context.Channel.SendErrorAsync("💢 You need to be in a voice channel on this server.\n If you are already in a voice (ITextChannel)Context.Channel, try rejoining it.").ConfigureAwait(false);
await ReplyErrorLocalized("must_be_in_voice").ConfigureAwait(false);
return;
}
await QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, radio_link, musicType: MusicType.Radio).ConfigureAwait(false);
await QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, radioLink, musicType: MusicType.Radio).ConfigureAwait(false);
if ((await Context.Guild.GetCurrentUserAsync()).GetPermissions((IGuildChannel)Context.Channel).ManageMessages)
{
Context.Message.DeleteAfter(10);
@ -504,20 +512,20 @@ namespace NadekoBot.Modules.Music
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer)) return;
musicPlayer.ClearQueue();
await Context.Channel.SendConfirmAsync($"🎵 Queue cleared!").ConfigureAwait(false);
return;
await ReplyConfirmLocalized("queue_cleared").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task MoveSong([Remainder] string fromto)
{
if (string.IsNullOrWhiteSpace(fromto))
return;
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
{
return;
}
fromto = fromto?.Trim();
var fromtoArr = fromto.Split('>');
@ -530,7 +538,7 @@ namespace NadekoBot.Modules.Music
!int.TryParse(fromtoArr[1], out n2) || n1 < 1 || n2 < 1 || n1 == n2 ||
n1 > playlist.Count || n2 > playlist.Count)
{
await Context.Channel.SendErrorAsync("Invalid input.").ConfigureAwait(false);
await ReplyConfirmLocalized("invalid_input").ConfigureAwait(false);
return;
}
@ -541,10 +549,10 @@ namespace NadekoBot.Modules.Music
var embed = new EmbedBuilder()
.WithTitle($"{s.SongInfo.Title.TrimTo(70)}")
.WithUrl($"{s.SongInfo.Query}")
.WithAuthor(eab => eab.WithName("Song Moved").WithIconUrl("https://cdn.discordapp.com/attachments/155726317222887425/258605269972549642/music1.png"))
.AddField(fb => fb.WithName("**From Position**").WithValue($"#{n1}").WithIsInline(true))
.AddField(fb => fb.WithName("**To Position**").WithValue($"#{n2}").WithIsInline(true))
.WithUrl(s.SongUrl)
.WithAuthor(eab => eab.WithName(GetText("song_moved")).WithIconUrl("https://cdn.discordapp.com/attachments/155726317222887425/258605269972549642/music1.png"))
.AddField(fb => fb.WithName(GetText("from_position")).WithValue($"#{n1}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("to_position")).WithValue($"#{n2}").WithIsInline(true))
.WithColor(NadekoBot.OkColor);
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
@ -562,7 +570,11 @@ namespace NadekoBot.Modules.Music
return;
musicPlayer.MaxQueueSize = size;
await Context.Channel.SendConfirmAsync($"🎵 Max queue set to {(size == 0 ? ("unlimited") : size + " tracks")}.");
if(size == 0)
await ReplyConfirmLocalized("max_queue_unlimited").ConfigureAwait(false);
else
await ReplyConfirmLocalized("max_queue_x", size).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -578,9 +590,9 @@ namespace NadekoBot.Modules.Music
return;
musicPlayer.MaxPlaytimeSeconds = seconds;
if (seconds == 0)
await channel.SendConfirmAsync($"🎵 Max playtime has no limit now.");
await ReplyConfirmLocalized("max_playtime_none").ConfigureAwait(false);
else
await channel.SendConfirmAsync($"🎵 Max playtime set to {seconds} seconds.");
await ReplyConfirmLocalized("max_playtime_set", seconds).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -599,11 +611,11 @@ namespace NadekoBot.Modules.Music
if (currentValue)
await Context.Channel.EmbedAsync(new EmbedBuilder()
.WithOkColor()
.WithAuthor(eab => eab.WithMusicIcon().WithName("🔂 Repeating track"))
.WithAuthor(eab => eab.WithMusicIcon().WithName("🔂 " + GetText("repeating_track")))
.WithDescription(currentSong.PrettyName)
.WithFooter(ef => ef.WithText(currentSong.PrettyInfo))).ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync($"🔂 Current track repeat stopped.")
await Context.Channel.SendConfirmAsync("🔂 " + GetText("repeating_track_stopped"))
.ConfigureAwait(false);
}
@ -616,7 +628,10 @@ namespace NadekoBot.Modules.Music
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
return;
var currentValue = musicPlayer.ToggleRepeatPlaylist();
await Context.Channel.SendConfirmAsync($"🔁 Repeat playlist {(currentValue ? "**enabled**." : "**disabled**.")}").ConfigureAwait(false);
if(currentValue)
await ReplyConfirmLocalized("rpl_enabled").ConfigureAwait(false);
else
await ReplyConfirmLocalized("rpl_disabled").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -653,7 +668,10 @@ namespace NadekoBot.Modules.Music
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync(($"🎵 Saved playlist as **{name}**, ID: {playlist.Id}.")).ConfigureAwait(false);
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("playlist_saved"))
.AddField(efb => efb.WithName(GetText("name")).WithValue(name))
.AddField(efb => efb.WithName(GetText("id")).WithValue(playlist.Id.ToString())));
}
[NadekoCommand, Usage, Description, Aliases]
@ -668,11 +686,11 @@ namespace NadekoBot.Modules.Music
if (mpl == null)
{
await Context.Channel.SendErrorAsync("Can't find playlist with that ID.").ConfigureAwait(false);
await ReplyErrorLocalized("playlist_id_not_found").ConfigureAwait(false);
return;
}
IUserMessage msg = null;
try { msg = await Context.Channel.SendMessageAsync($"🎶 Attempting to load **{mpl.Songs.Count}** songs...").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
try { msg = await Context.Channel.SendMessageAsync(GetText("attempting_to_queue", Format.Bold(mpl.Songs.Count.ToString()))).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
foreach (var item in mpl.Songs)
{
var usr = (IGuildUser)Context.User;
@ -684,15 +702,13 @@ namespace NadekoBot.Modules.Music
catch { break; }
}
if (msg != null)
await msg.ModifyAsync(m => m.Content = $"✅ Done loading playlist **{mpl.Name}**.").ConfigureAwait(false);
await msg.ModifyAsync(m => m.Content = GetText("playlist_queue_complete")).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Playlists([Remainder] int num = 1)
{
if (num <= 0)
return;
@ -704,8 +720,9 @@ namespace NadekoBot.Modules.Music
}
var embed = new EmbedBuilder()
.WithAuthor(eab => eab.WithName($"Page {num} of Saved Playlists").WithMusicIcon())
.WithDescription(string.Join("\n", playlists.Select(r => $"`#{r.Id}` - **{r.Name}** by *{r.Author}* ({r.Songs.Count} songs)")))
.WithAuthor(eab => eab.WithName(GetText("playlists_page", num)).WithMusicIcon())
.WithDescription(string.Join("\n", playlists.Select(r =>
GetText("playlists", "#" + r.Id, r.Name, r.Author, r.Songs.Count))))
.WithOkColor();
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
@ -715,13 +732,12 @@ namespace NadekoBot.Modules.Music
[RequireContext(ContextType.Guild)]
public async Task DeletePlaylist([Remainder] int id)
{
bool success = false;
MusicPlaylist pl = null;
var success = false;
try
{
using (var uow = DbHandler.UnitOfWork())
{
pl = uow.MusicPlaylists.Get(id);
var pl = uow.MusicPlaylists.Get(id);
if (pl != null)
{
@ -731,15 +747,13 @@ namespace NadekoBot.Modules.Music
await uow.CompleteAsync().ConfigureAwait(false);
success = true;
}
else
success = false;
}
}
if (!success)
await Context.Channel.SendErrorAsync("Failed to delete that playlist. It either doesn't exist, or you are not its author.").ConfigureAwait(false);
await ReplyErrorLocalized("playlist_delete_fail").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync("🗑 Playlist successfully **deleted**.").ConfigureAwait(false);
await ReplyConfirmLocalized("playlist_deleted").ConfigureAwait(false);
}
catch (Exception ex)
{
@ -779,7 +793,7 @@ namespace NadekoBot.Modules.Music
if (seconds.Length == 1)
seconds = "0" + seconds;
await Context.Channel.SendConfirmAsync($"Skipped to `{minutes}:{seconds}`").ConfigureAwait(false);
await ReplyConfirmLocalized("skipped_to", minutes, seconds).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -791,9 +805,9 @@ namespace NadekoBot.Modules.Music
return;
if (!musicPlayer.ToggleAutoplay())
await Context.Channel.SendConfirmAsync("❌ Autoplay disabled.").ConfigureAwait(false);
await ReplyConfirmLocalized("autoplay_disabled").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync("✅ Autoplay enabled.").ConfigureAwait(false);
await ReplyConfirmLocalized("autoplay_enabled").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -804,29 +818,29 @@ namespace NadekoBot.Modules.Music
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(Context.Guild.Id, out musicPlayer))
{
await Context.Channel.SendErrorAsync("Music must be playing before you set an ouput channel.").ConfigureAwait(false);
await ReplyErrorLocalized("no_player").ConfigureAwait(false);
return;
}
musicPlayer.OutputTextChannel = (ITextChannel)Context.Channel;
await Context.Channel.SendConfirmAsync("I will now output playing, finished, paused and removed songs in this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("set_music_channel").ConfigureAwait(false);
}
public static async Task QueueSong(IGuildUser queuer, ITextChannel textCh, IVoiceChannel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal)
public async Task QueueSong(IGuildUser queuer, ITextChannel textCh, IVoiceChannel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal)
{
if (voiceCh == null || voiceCh.Guild != textCh.Guild)
{
if (!silent)
await textCh.SendErrorAsync($"💢 You need to be in a voice channel on this server.").ConfigureAwait(false);
await textCh.SendErrorAsync(GetText("must_be_in_voice")).ConfigureAwait(false);
throw new ArgumentNullException(nameof(voiceCh));
}
if (string.IsNullOrWhiteSpace(query) || query.Length < 3)
throw new ArgumentException("💢 Invalid query for queue song.", nameof(query));
throw new ArgumentException("Invalid song query.", nameof(query));
var musicPlayer = MusicPlayers.GetOrAdd(textCh.Guild.Id, server =>
{
float vol = 1;// SpecificConfigurations.Default.Of(server.Id).DefaultMusicVolume;
float vol;// SpecificConfigurations.Default.Of(server.Id).DefaultMusicVolume;
using (var uow = DbHandler.UnitOfWork())
{
vol = uow.GuildConfigs.For(textCh.Guild.Id, set => set).DefaultMusicVolume;
@ -843,7 +857,7 @@ namespace NadekoBot.Modules.Music
try
{
lastFinishedMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName("Finished Song").WithMusicIcon())
.WithAuthor(eab => eab.WithName(GetText("finished_song")).WithMusicIcon())
.WithDescription(song.PrettyName)
.WithFooter(ef => ef.WithText(song.PrettyInfo)))
.ConfigureAwait(false);
@ -861,17 +875,23 @@ namespace NadekoBot.Modules.Music
textCh,
voiceCh,
relatedVideos[new NadekoRandom().Next(0, relatedVideos.Count)],
true,
musicType).ConfigureAwait(false);
true).ConfigureAwait(false);
}
}
catch { }
catch
{
// ignored
}
};
mp.OnStarted += async (player, song) =>
{
try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { }
var sender = player as MusicPlayer;
try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); }
catch
{
// ignored
}
var sender = player;
if (sender == null)
return;
try
@ -879,12 +899,15 @@ namespace NadekoBot.Modules.Music
playingMessage?.DeleteAfter(0);
playingMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName("Playing Song").WithMusicIcon())
.WithAuthor(eab => eab.WithName(GetText("playing_song")).WithMusicIcon())
.WithDescription(song.PrettyName)
.WithFooter(ef => ef.WithText(song.PrettyInfo)))
.ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
};
mp.OnPauseChanged += async (paused) =>
{
@ -892,13 +915,16 @@ namespace NadekoBot.Modules.Music
{
IUserMessage msg;
if (paused)
msg = await mp.OutputTextChannel.SendConfirmAsync("🎵 Music playback **paused**.").ConfigureAwait(false);
msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("paused")).ConfigureAwait(false);
else
msg = await mp.OutputTextChannel.SendConfirmAsync("🎵 Music playback **resumed**.").ConfigureAwait(false);
msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("resumed")).ConfigureAwait(false);
msg?.DeleteAfter(10);
}
catch { }
catch
{
// ignored
}
};
mp.SongRemoved += async (song, index) =>
@ -906,7 +932,7 @@ namespace NadekoBot.Modules.Music
try
{
var embed = new EmbedBuilder()
.WithAuthor(eab => eab.WithName("Removed song #" + (index + 1)).WithMusicIcon())
.WithAuthor(eab => eab.WithName(GetText("removed_song") + " #" + (index + 1)).WithMusicIcon())
.WithDescription(song.PrettyName)
.WithFooter(ef => ef.WithText(song.PrettyInfo))
.WithErrorColor();
@ -914,7 +940,10 @@ namespace NadekoBot.Modules.Music
await mp.OutputTextChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch { }
catch
{
// ignored
}
};
return mp;
});
@ -931,7 +960,14 @@ namespace NadekoBot.Modules.Music
}
catch (PlaylistFullException)
{
try { await textCh.SendConfirmAsync($"🎵 Queue is full at **{musicPlayer.MaxQueueSize}/{musicPlayer.MaxQueueSize}**."); } catch { }
try
{
await textCh.SendConfirmAsync(GetText("queue_full", musicPlayer.MaxQueueSize));
}
catch
{
// ignored
}
throw;
}
if (!silent)
@ -940,15 +976,17 @@ namespace NadekoBot.Modules.Music
{
//var queuedMessage = await textCh.SendConfirmAsync($"🎵 Queued **{resolvedSong.SongInfo.Title}** at `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false);
var queuedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName("Queued Song #" + (musicPlayer.Playlist.Count + 1)).WithMusicIcon())
.WithDescription($"{resolvedSong.PrettyName}\nQueue ")
.WithAuthor(eab => eab.WithName(GetText("queued_song") + " #" + (musicPlayer.Playlist.Count + 1)).WithMusicIcon())
.WithDescription($"{resolvedSong.PrettyName}\n{GetText("queue")} ")
.WithThumbnailUrl(resolvedSong.Thumbnail)
.WithFooter(ef => ef.WithText(resolvedSong.PrettyProvider)))
.ConfigureAwait(false);
if (queuedMessage != null)
queuedMessage.DeleteAfter(10);
queuedMessage?.DeleteAfter(10);
}
catch { } // if queued message sending fails, don't attempt to delete it
catch
{
// ignored
} // if queued message sending fails, don't attempt to delete it
}
}
}

View File

@ -7,8 +7,6 @@ using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Services;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using NadekoBot.Extensions;
using System.Xml;
using System.Threading;
@ -17,11 +15,11 @@ using System.Collections.Concurrent;
namespace NadekoBot.Modules.NSFW
{
[NadekoModule("NSFW", "~")]
public class NSFW : NadekoModule
public class NSFW : NadekoTopLevelModule
{
private static readonly ConcurrentDictionary<ulong, Timer> AutoHentaiTimers = new ConcurrentDictionary<ulong, Timer>();
private static readonly ConcurrentHashSet<ulong> HentaiBombBlacklist = new ConcurrentHashSet<ulong>();
private static readonly ConcurrentDictionary<ulong, Timer> _autoHentaiTimers = new ConcurrentDictionary<ulong, Timer>();
private static readonly ConcurrentHashSet<ulong> _hentaiBombBlacklist = new ConcurrentHashSet<ulong>();
private async Task InternalHentai(IMessageChannel channel, string tag, bool noError)
{
@ -72,7 +70,7 @@ namespace NadekoBot.Modules.NSFW
if (interval == 0)
{
if (!AutoHentaiTimers.TryRemove(Context.Channel.Id, out t)) return;
if (!_autoHentaiTimers.TryRemove(Context.Channel.Id, out t)) return;
t.Change(Timeout.Infinite, Timeout.Infinite); //proper way to disable the timer
await ReplyConfirmLocalized("autohentai_stopped").ConfigureAwait(false);
@ -99,7 +97,7 @@ namespace NadekoBot.Modules.NSFW
}
}, null, interval * 1000, interval * 1000);
AutoHentaiTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) =>
_autoHentaiTimers.AddOrUpdate(Context.Channel.Id, t, (key, old) =>
{
old.Change(Timeout.Infinite, Timeout.Infinite);
return t;
@ -114,7 +112,7 @@ namespace NadekoBot.Modules.NSFW
[NadekoCommand, Usage, Description, Aliases]
public async Task HentaiBomb([Remainder] string tag = null)
{
if (!HentaiBombBlacklist.Add(Context.User.Id))
if (!_hentaiBombBlacklist.Add(Context.User.Id))
return;
try
{
@ -138,17 +136,17 @@ namespace NadekoBot.Modules.NSFW
finally
{
await Task.Delay(5000).ConfigureAwait(false);
HentaiBombBlacklist.TryRemove(Context.User.Id);
_hentaiBombBlacklist.TryRemove(Context.User.Id);
}
}
#endif
[NadekoCommand, Usage, Description, Aliases]
public Task Yandere([Remainder] string tag = null)
=> Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Yandere);
=> InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Yandere);
[NadekoCommand, Usage, Description, Aliases]
public Task Konachan([Remainder] string tag = null)
=> Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Konachan);
=> InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Konachan);
[NadekoCommand, Usage, Description, Aliases]
public async Task E621([Remainder] string tag = null)
@ -169,7 +167,7 @@ namespace NadekoBot.Modules.NSFW
[NadekoCommand, Usage, Description, Aliases]
public Task Rule34([Remainder] string tag = null)
=> Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Rule34);
=> InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Rule34);
[NadekoCommand, Usage, Description, Aliases]
public async Task Danbooru([Remainder] string tag = null)
@ -212,7 +210,7 @@ namespace NadekoBot.Modules.NSFW
[NadekoCommand, Usage, Description, Aliases]
public Task Gelbooru([Remainder] string tag = null)
=> Searches.Searches.InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Gelbooru);
=> InternalDapiCommand(Context.Message, tag, Searches.Searches.DapiSearchType.Gelbooru);
[NadekoCommand, Usage, Description, Aliases]
public async Task Cp()
@ -289,5 +287,22 @@ namespace NadekoBot.Modules.NSFW
public static Task<string> GetGelbooruImageLink(string tag) =>
Searches.Searches.InternalDapiSearch(tag, Searches.Searches.DapiSearchType.Gelbooru);
public async Task InternalDapiCommand(IUserMessage umsg, string tag, Searches.Searches.DapiSearchType type)
{
var channel = umsg.Channel;
tag = tag?.Trim() ?? "";
var url = await Searches.Searches.InternalDapiSearch(tag, type).ConfigureAwait(false);
if (url == null)
await channel.SendErrorAsync(umsg.Author.Mention + " " + GetText("no_results"));
else
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription(umsg.Author.Mention + " " + tag)
.WithImageUrl(url)
.WithFooter(efb => efb.WithText(type.ToString()))).ConfigureAwait(false);
}
}
}

View File

@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace NadekoBot.Modules
{
public abstract class NadekoModule : ModuleBase
public abstract class NadekoTopLevelModule : ModuleBase
{
protected readonly Logger _log;
protected CultureInfo _cultureInfo;
@ -17,7 +17,7 @@ namespace NadekoBot.Modules
public readonly string ModuleTypeName;
public readonly string LowerModuleTypeName;
protected NadekoModule(bool isTopLevelModule = true)
protected NadekoTopLevelModule(bool isTopLevelModule = true)
{
//if it's top level module
ModuleTypeName = isTopLevelModule ? this.GetType().Name : this.GetType().DeclaringType.Name;
@ -69,7 +69,7 @@ namespace NadekoBot.Modules
LogManager.GetCurrentClassLogger().Warn(lowerModuleTypeName + "_" + key + " key is missing from " + cultureInfo + " response strings. PLEASE REPORT THIS.");
text = NadekoBot.ResponsesResourceManager.GetString(lowerModuleTypeName + "_" + key, _usCultureInfo) ?? $"Error: dkey {lowerModuleTypeName + "_" + key} not found!";
if (string.IsNullOrWhiteSpace(text))
return "I cant tell if you command is executed, because there was an error printing out the response. Key '" +
return "I can't tell you is the command executed, because there was an error printing out the response. Key '" +
lowerModuleTypeName + "_" + key + "' " + "is missing from resources. Please report this.";
}
return text;
@ -120,7 +120,7 @@ namespace NadekoBot.Modules
}
}
public abstract class NadekoSubmodule : NadekoModule
public abstract class NadekoSubmodule : NadekoTopLevelModule
{
protected NadekoSubmodule() : base(false)
{

View File

@ -21,11 +21,11 @@ namespace NadekoBot.Modules.Permissions
}
[Group]
public class BlacklistCommands : ModuleBase
public class BlacklistCommands : NadekoSubmodule
{
public static ConcurrentHashSet<ulong> BlacklistedUsers { get; set; } = new ConcurrentHashSet<ulong>();
public static ConcurrentHashSet<ulong> BlacklistedGuilds { get; set; } = new ConcurrentHashSet<ulong>();
public static ConcurrentHashSet<ulong> BlacklistedChannels { get; set; } = new ConcurrentHashSet<ulong>();
public static ConcurrentHashSet<ulong> BlacklistedUsers { get; set; }
public static ConcurrentHashSet<ulong> BlacklistedGuilds { get; set; }
public static ConcurrentHashSet<ulong> BlacklistedChannels { get; set; }
static BlacklistCommands()
{
@ -115,7 +115,7 @@ namespace NadekoBot.Modules.Permissions
}
break;
case BlacklistType.Channel:
var item = Games.Games.TriviaCommands.RunningTrivias.FirstOrDefault(kvp => kvp.Value.channel.Id == id);
var item = Games.Games.TriviaCommands.RunningTrivias.FirstOrDefault(kvp => kvp.Value.Channel.Id == id);
Games.Games.TriviaCommands.RunningTrivias.TryRemove(item.Key, out tg);
if (tg != null)
{
@ -124,16 +124,14 @@ namespace NadekoBot.Modules.Permissions
break;
case BlacklistType.User:
break;
default:
break;
}
}
if(action == AddRemove.Add)
await Context.Channel.SendConfirmAsync($"Blacklisted a `{type}` with id `{id}`").ConfigureAwait(false);
await ReplyConfirmLocalized("blacklisted", Format.Code(type.ToString()), Format.Code(id.ToString())).ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync($"Unblacklisted a `{type}` with id `{id}`").ConfigureAwait(false);
await ReplyConfirmLocalized("unblacklisted", Format.Code(type.ToString()), Format.Code(id.ToString())).ConfigureAwait(false);
}
}
}

View File

@ -21,15 +21,15 @@ namespace NadekoBot.Modules.Permissions
}
[Group]
public class CmdCdsCommands : ModuleBase
public class CmdCdsCommands : NadekoSubmodule
{
public static ConcurrentDictionary<ulong, ConcurrentHashSet<CommandCooldown>> commandCooldowns { get; }
public static ConcurrentDictionary<ulong, ConcurrentHashSet<CommandCooldown>> CommandCooldowns { get; }
private static ConcurrentDictionary<ulong, ConcurrentHashSet<ActiveCooldown>> activeCooldowns { get; } = new ConcurrentDictionary<ulong, ConcurrentHashSet<ActiveCooldown>>();
static CmdCdsCommands()
{
var configs = NadekoBot.AllGuildConfigs;
commandCooldowns = new ConcurrentDictionary<ulong, ConcurrentHashSet<CommandCooldown>>(configs.ToDictionary(k => k.GuildId, v => new ConcurrentHashSet<CommandCooldown>(v.CommandCooldowns)));
CommandCooldowns = new ConcurrentDictionary<ulong, ConcurrentHashSet<CommandCooldown>>(configs.ToDictionary(k => k.GuildId, v => new ConcurrentHashSet<CommandCooldown>(v.CommandCooldowns)));
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
@ -38,14 +38,14 @@ namespace NadekoBot.Modules.Permissions
var channel = (ITextChannel)Context.Channel;
if (secs < 0 || secs > 3600)
{
await channel.SendErrorAsync("Invalid second parameter. (Must be a number between 0 and 3600)").ConfigureAwait(false);
await ReplyErrorLocalized("invalid_second_param_between", 0, 3600).ConfigureAwait(false);
return;
}
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.CommandCooldowns));
var localSet = commandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CommandCooldown>());
var localSet = CommandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CommandCooldown>());
config.CommandCooldowns.RemoveWhere(cc => cc.CommandName == command.Aliases.First().ToLowerInvariant());
localSet.RemoveWhere(cc => cc.CommandName == command.Aliases.First().ToLowerInvariant());
@ -65,13 +65,14 @@ namespace NadekoBot.Modules.Permissions
{
var activeCds = activeCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<ActiveCooldown>());
activeCds.RemoveWhere(ac => ac.Command == command.Aliases.First().ToLowerInvariant());
await channel.SendConfirmAsync($"🚮 Command **{command.Aliases.First()}** has no coooldown now and all existing cooldowns have been cleared.")
.ConfigureAwait(false);
await ReplyConfirmLocalized("cmdcd_cleared",
Format.Bold(command.Aliases.First())).ConfigureAwait(false);
}
else
{
await channel.SendConfirmAsync($"✅ Command **{command.Aliases.First()}** now has a **{secs} {"seconds".SnPl(secs)}** cooldown.")
.ConfigureAwait(false);
await ReplyConfirmLocalized("cmdcd_add",
Format.Bold(command.Aliases.First()),
Format.Bold(secs.ToString())).ConfigureAwait(false);
}
}
@ -80,19 +81,19 @@ namespace NadekoBot.Modules.Permissions
public async Task AllCmdCooldowns()
{
var channel = (ITextChannel)Context.Channel;
var localSet = commandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CommandCooldown>());
var localSet = CommandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<CommandCooldown>());
if (!localSet.Any())
await channel.SendConfirmAsync(" `No command cooldowns set.`").ConfigureAwait(false);
await ReplyConfirmLocalized("cmdcd_none").ConfigureAwait(false);
else
await channel.SendTableAsync("", localSet.Select(c => c.CommandName + ": " + c.Seconds + " secs"), s => $"{s,-30}", 2).ConfigureAwait(false);
await channel.SendTableAsync("", localSet.Select(c => c.CommandName + ": " + c.Seconds + GetText("sec")), s => $"{s,-30}", 2).ConfigureAwait(false);
}
public static bool HasCooldown(CommandInfo cmd, IGuild guild, IUser user)
{
if (guild == null)
return false;
var cmdcds = CmdCdsCommands.commandCooldowns.GetOrAdd(guild.Id, new ConcurrentHashSet<CommandCooldown>());
var cmdcds = CmdCdsCommands.CommandCooldowns.GetOrAdd(guild.Id, new ConcurrentHashSet<CommandCooldown>());
CommandCooldown cdRule;
if ((cdRule = cmdcds.FirstOrDefault(cc => cc.CommandName == cmd.Aliases.First().ToLowerInvariant())) != null)
{

View File

@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Permissions
public partial class Permissions
{
[Group]
public class CommandCostCommands : ModuleBase
public class CommandCostCommands : NadekoSubmodule
{
private static readonly ConcurrentDictionary<string, int> _commandCosts = new ConcurrentDictionary<string, int>();
public static IReadOnlyDictionary<string, int> CommandCosts => _commandCosts;
@ -29,29 +29,29 @@ namespace NadekoBot.Modules.Permissions
// x => x.Cost));
}
[NadekoCommand, Usage, Description, Aliases]
public async Task CmdCosts(int page = 1)
{
var prices = _commandCosts.ToList();
//[NadekoCommand, Usage, Description, Aliases]
//public async Task CmdCosts(int page = 1)
//{
// var prices = _commandCosts.ToList();
if (!prices.Any())
{
await Context.Channel.SendConfirmAsync("No costs set.").ConfigureAwait(false);
return;
}
// if (!prices.Any())
// {
// await Context.Channel.SendConfirmAsync(GetText("no_costs")).ConfigureAwait(false);
// return;
// }
await Context.Channel.SendPaginatedConfirmAsync(page, (curPage) => {
var embed = new EmbedBuilder().WithOkColor()
.WithTitle("Command Costs");
var current = prices.Skip((curPage - 1) * 9)
.Take(9);
foreach (var price in current)
{
embed.AddField(efb => efb.WithName(price.Key).WithValue(price.Value.ToString()).WithIsInline(true));
}
return embed;
}, prices.Count / 9).ConfigureAwait(false);
}
// await Context.Channel.SendPaginatedConfirmAsync(page, (curPage) => {
// var embed = new EmbedBuilder().WithOkColor()
// .WithTitle(GetText("command_costs"));
// var current = prices.Skip((curPage - 1) * 9)
// .Take(9);
// foreach (var price in current)
// {
// embed.AddField(efb => efb.WithName(price.Key).WithValue(price.Value.ToString()).WithIsInline(true));
// }
// return embed;
// }, prices.Count / 9).ConfigureAwait(false);
//}
//[NadekoCommand, Usage, Description, Aliases]
//public async Task CommandCost(int cost, CommandInfo cmd)

View File

@ -13,13 +13,13 @@ namespace NadekoBot.Modules.Permissions
public partial class Permissions
{
[Group]
public class FilterCommands : ModuleBase
public class FilterCommands : NadekoSubmodule
{
public static ConcurrentHashSet<ulong> InviteFilteringChannels { get; }
public static ConcurrentHashSet<ulong> InviteFilteringServers { get; }
//serverid, filteredwords
private static ConcurrentDictionary<ulong, ConcurrentHashSet<string>> ServerFilteredWords { get; }
private static ConcurrentDictionary<ulong, ConcurrentHashSet<string>> serverFilteredWords { get; }
public static ConcurrentHashSet<ulong> WordFilteringChannels { get; }
public static ConcurrentHashSet<ulong> WordFilteringServers { get; }
@ -28,7 +28,7 @@ namespace NadekoBot.Modules.Permissions
{
ConcurrentHashSet<string> words = new ConcurrentHashSet<string>();
if(WordFilteringChannels.Contains(channelId))
ServerFilteredWords.TryGetValue(guildId, out words);
serverFilteredWords.TryGetValue(guildId, out words);
return words;
}
@ -36,7 +36,7 @@ namespace NadekoBot.Modules.Permissions
{
var words = new ConcurrentHashSet<string>();
if(WordFilteringServers.Contains(guildId))
ServerFilteredWords.TryGetValue(guildId, out words);
serverFilteredWords.TryGetValue(guildId, out words);
return words;
}
@ -49,7 +49,7 @@ namespace NadekoBot.Modules.Permissions
var dict = guildConfigs.ToDictionary(gc => gc.GuildId, gc => new ConcurrentHashSet<string>(gc.FilteredWords.Select(fw => fw.Word)));
ServerFilteredWords = new ConcurrentDictionary<ulong, ConcurrentHashSet<string>>(dict);
serverFilteredWords = new ConcurrentDictionary<ulong, ConcurrentHashSet<string>>(dict);
var serverFiltering = guildConfigs.Where(gc => gc.FilterWords);
WordFilteringServers = new ConcurrentHashSet<ulong>(serverFiltering.Select(gc => gc.GuildId));
@ -74,12 +74,12 @@ namespace NadekoBot.Modules.Permissions
if (enabled)
{
InviteFilteringServers.Add(channel.Guild.Id);
await channel.SendConfirmAsync("Invite filtering enabled on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("invite_filter_server_on").ConfigureAwait(false);
}
else
{
InviteFilteringServers.TryRemove(channel.Guild.Id);
await channel.SendConfirmAsync("Invite filtering disabled on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("invite_filter_server_off").ConfigureAwait(false);
}
}
@ -107,12 +107,11 @@ namespace NadekoBot.Modules.Permissions
if (removed == 0)
{
InviteFilteringChannels.Add(channel.Id);
await channel.SendConfirmAsync("Invite filtering enabled on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("invite_filter_channel_on").ConfigureAwait(false);
}
else
{
InviteFilteringChannels.TryRemove(channel.Id);
await channel.SendConfirmAsync("Invite filtering disabled on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("invite_filter_channel_off").ConfigureAwait(false);
}
}
@ -133,12 +132,12 @@ namespace NadekoBot.Modules.Permissions
if (enabled)
{
WordFilteringServers.Add(channel.Guild.Id);
await channel.SendConfirmAsync("Word filtering enabled on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("word_filter_server_on").ConfigureAwait(false);
}
else
{
WordFilteringServers.TryRemove(channel.Guild.Id);
await channel.SendConfirmAsync("Word filtering disabled on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("word_filter_server_off").ConfigureAwait(false);
}
}
@ -166,12 +165,12 @@ namespace NadekoBot.Modules.Permissions
if (removed == 0)
{
WordFilteringChannels.Add(channel.Id);
await channel.SendConfirmAsync("Word filtering enabled on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("word_filter_channel_on").ConfigureAwait(false);
}
else
{
WordFilteringChannels.TryRemove(channel.Id);
await channel.SendConfirmAsync("Word filtering disabled on this channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("word_filter_channel_off").ConfigureAwait(false);
}
}
@ -199,19 +198,17 @@ namespace NadekoBot.Modules.Permissions
await uow.CompleteAsync().ConfigureAwait(false);
}
var filteredWords = ServerFilteredWords.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<string>());
var filteredWords = serverFilteredWords.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet<string>());
if (removed == 0)
{
filteredWords.Add(word);
await channel.SendConfirmAsync($"Word `{word}` successfully added to the list of filtered words.")
.ConfigureAwait(false);
await ReplyConfirmLocalized("filter_word_add", Format.Code(word)).ConfigureAwait(false);
}
else
{
filteredWords.TryRemove(word);
await channel.SendConfirmAsync($"Word `{word}` removed from the list of filtered words.")
.ConfigureAwait(false);
await ReplyConfirmLocalized("filter_word_remove", Format.Code(word)).ConfigureAwait(false);
}
}
@ -222,9 +219,9 @@ namespace NadekoBot.Modules.Permissions
var channel = (ITextChannel)Context.Channel;
ConcurrentHashSet<string> filteredWords;
ServerFilteredWords.TryGetValue(channel.Guild.Id, out filteredWords);
serverFilteredWords.TryGetValue(channel.Guild.Id, out filteredWords);
await channel.SendConfirmAsync($"List of filtered words", string.Join("\n", filteredWords))
await channel.SendConfirmAsync(GetText("filter_word_list"), string.Join("\n", filteredWords))
.ConfigureAwait(false);
}
}

View File

@ -15,7 +15,7 @@ using NLog;
namespace NadekoBot.Modules.Permissions
{
[NadekoModule("Permissions", ";")]
public partial class Permissions : NadekoModule
public partial class Permissions : NadekoTopLevelModule
{
public class PermissionCache
{
@ -29,7 +29,7 @@ namespace NadekoBot.Modules.Permissions
static Permissions()
{
var _log = LogManager.GetCurrentClassLogger();
var log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
using (var uow = DbHandler.UnitOfWork())
@ -46,7 +46,7 @@ namespace NadekoBot.Modules.Permissions
}
sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
}
[NadekoCommand, Usage, Description, Aliases]
@ -65,8 +65,14 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.Verbose = config.VerbosePermissions; return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync(" I will " + (action.Value ? "now" : "no longer") + " show permission warnings.").ConfigureAwait(false);
if (action.Value)
{
await ReplyConfirmLocalized("verbose_true").ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("verbose_false").ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -81,10 +87,9 @@ namespace NadekoBot.Modules.Permissions
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set);
if (role == null)
{
await Context.Channel.SendConfirmAsync($" Current permission role is **{config.PermissionRole}**.").ConfigureAwait(false);
await ReplyConfirmLocalized("permrole", Format.Bold(config.PermissionRole)).ConfigureAwait(false);
return;
}
else {
config.PermissionRole = role.Name.Trim();
Cache.AddOrUpdate(Context.Guild.Id, new PermissionCache()
{
@ -94,9 +99,8 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.PermRole = role.Name.Trim(); return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
}
await Context.Channel.SendConfirmAsync($"Users now require **{role.Name}** role in order to edit permissions.").ConfigureAwait(false);
await ReplyConfirmLocalized("permrole_changed", Format.Bold(role.Name)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -105,12 +109,18 @@ namespace NadekoBot.Modules.Permissions
{
if (page < 1 || page > 4)
return;
string toSend = "";
string toSend;
using (var uow = DbHandler.UnitOfWork())
{
var perms = uow.GuildConfigs.PermissionsFor(Context.Guild.Id).RootPermission;
var i = 1 + 20 * (page - 1);
toSend = Format.Code($"📄 Permissions page {page}") + "\n\n" + String.Join("\n", perms.AsEnumerable().Skip((page - 1) * 20).Take(20).Select(p => $"`{(i++)}.` {(p.Next == null ? Format.Bold(p.GetCommand((SocketGuild)Context.Guild) + " [uneditable]") : (p.GetCommand((SocketGuild)Context.Guild)))}"));
toSend = Format.Bold(GetText("page", page)) + "\n\n" + string.Join("\n",
perms.AsEnumerable()
.Skip((page - 1) * 20)
.Take(20)
.Select(
p =>
$"`{(i++)}.` {(p.Next == null ? Format.Bold(p.GetCommand((SocketGuild) Context.Guild) + $" [{GetText("uneditable")}]") : (p.GetCommand((SocketGuild) Context.Guild)))}"));
}
await Context.Channel.SendMessageAsync(toSend).ConfigureAwait(false);
@ -132,7 +142,7 @@ namespace NadekoBot.Modules.Permissions
{
return;
}
else if (index == 0)
if (index == 0)
{
p = perms;
config.RootPermission = perms.Next;
@ -155,12 +165,13 @@ namespace NadekoBot.Modules.Permissions
uow2._context.Remove<Permission>(p);
uow2._context.SaveChanges();
}
await Context.Channel.SendConfirmAsync($"✅ {Context.User.Mention} removed permission **{p.GetCommand((SocketGuild)Context.Guild)}** from position #{index + 1}.").ConfigureAwait(false);
await ReplyConfirmLocalized("removed",
index+1,
Format.Code(p.GetCommand((SocketGuild)Context.Guild))).ConfigureAwait(false);
}
catch (ArgumentOutOfRangeException)
catch (IndexOutOfRangeException)
{
await Context.Channel.SendErrorAsync("❗️`No command on that index found.`").ConfigureAwait(false);
await ReplyErrorLocalized("perm_out_of_range").ConfigureAwait(false);
}
}
@ -180,7 +191,6 @@ namespace NadekoBot.Modules.Permissions
{
var config = uow.GuildConfigs.PermissionsFor(Context.Guild.Id);
var perms = config.RootPermission;
var root = perms;
var index = 0;
var fromFound = false;
var toFound = false;
@ -207,13 +217,13 @@ namespace NadekoBot.Modules.Permissions
{
if (!fromFound)
{
await Context.Channel.SendErrorAsync($"Can't find permission at index `#{++from}`").ConfigureAwait(false);
await ReplyErrorLocalized("not_found", ++from).ConfigureAwait(false);
return;
}
if (!toFound)
{
await Context.Channel.SendErrorAsync($"Can't find permission at index `#{++to}`").ConfigureAwait(false);
await ReplyErrorLocalized("not_found", ++to).ConfigureAwait(false);
return;
}
}
@ -230,7 +240,6 @@ namespace NadekoBot.Modules.Permissions
next.Previous = pre;
if (from == 0)
{
root = next;
}
await uow.CompleteAsync().ConfigureAwait(false);
//Inserting
@ -263,14 +272,18 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.RootPermission = config.RootPermission; return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"`Moved permission:` \"{fromPerm.GetCommand((SocketGuild)Context.Guild)}\" `from #{++from} to #{++to}.`").ConfigureAwait(false);
await ReplyConfirmLocalized("moved_permission",
Format.Code(fromPerm.GetCommand((SocketGuild) Context.Guild)),
++from,
++to)
.ConfigureAwait(false);
return;
}
catch (Exception e) when (e is ArgumentOutOfRangeException || e is IndexOutOfRangeException)
{
}
}
await Context.Channel.SendErrorAsync("`Invalid index(es) specified.`").ConfigureAwait(false);
await ReplyErrorLocalized("perm_out_of_range").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -297,7 +310,19 @@ namespace NadekoBot.Modules.Permissions
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{(action.Value ? " Allowed" : "🆗 Denied")} usage of `{command.Aliases.First()}` command on this server.").ConfigureAwait(false);
if (action.Value)
{
await ReplyConfirmLocalized("sx_enable",
Format.Code(command.Aliases.First()),
GetText("of_command")).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("sx_disable",
Format.Code(command.Aliases.First()),
GetText("of_command")).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -323,7 +348,19 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.RootPermission = config.RootPermission; return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{(action.Value ? " Allowed" : "🆗 Denied")} usage of **`{module.Name}`** module on this server.").ConfigureAwait(false);
if (action.Value)
{
await ReplyConfirmLocalized("sx_enable",
Format.Code(module.Name),
GetText("of_module")).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("sx_disable",
Format.Code(module.Name),
GetText("of_module")).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -349,7 +386,21 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.RootPermission = config.RootPermission; return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{(action.Value ? " Allowed" : "🆗 Denied")} usage of `{command.Aliases.First()}` command for `{user}` user.").ConfigureAwait(false);
if (action.Value)
{
await ReplyConfirmLocalized("ux_enable",
Format.Code(command.Aliases.First()),
GetText("of_command"),
Format.Code(user.ToString())).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("ux_disable",
Format.Code(command.Aliases.First()),
GetText("of_command"),
Format.Code(user.ToString())).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -375,7 +426,21 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.RootPermission = config.RootPermission; return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{(action.Value ? " Allowed" : "🆗 Denied")} usage of `{module.Name}` module for `{user}` user.").ConfigureAwait(false);
if (action.Value)
{
await ReplyConfirmLocalized("ux_enable",
Format.Code(module.Name),
GetText("of_module"),
Format.Code(user.ToString())).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("ux_disable",
Format.Code(module.Name),
GetText("of_module"),
Format.Code(user.ToString())).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -404,7 +469,21 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.RootPermission = config.RootPermission; return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{(action.Value ? " Allowed" : "🆗 Denied")} usage of `{command.Aliases.First()}` command for `{role}` role.").ConfigureAwait(false);
if (action.Value)
{
await ReplyConfirmLocalized("rx_enable",
Format.Code(command.Aliases.First()),
GetText("of_command"),
Format.Code(role.Name)).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("rx_disable",
Format.Code(command.Aliases.First()),
GetText("of_command"),
Format.Code(role.Name)).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -433,14 +512,27 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.RootPermission = config.RootPermission; return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{(action.Value ? " Allowed" : "🆗 Denied")} usage of `{module.Name}` module for `{role}` role.").ConfigureAwait(false);
if (action.Value)
{
await ReplyConfirmLocalized("rx_enable",
Format.Code(module.Name),
GetText("of_module"),
Format.Code(role.Name)).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("rx_disable",
Format.Code(module.Name),
GetText("of_module"),
Format.Code(role.Name)).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ChnlCmd(CommandInfo command, PermissionAction action, [Remainder] ITextChannel chnl)
{
try
{
using (var uow = DbHandler.UnitOfWork())
{
@ -461,11 +553,21 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.RootPermission = config.RootPermission; return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
if (action.Value)
{
await ReplyConfirmLocalized("cx_enable",
Format.Code(command.Aliases.First()),
GetText("of_command"),
Format.Code(chnl.Name)).ConfigureAwait(false);
}
catch (Exception ex) {
_log.Error(ex);
else
{
await ReplyConfirmLocalized("cx_disable",
Format.Code(command.Aliases.First()),
GetText("of_command"),
Format.Code(chnl.Name)).ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{(action.Value ? " Allowed" : "🆗 Denied")} usage of `{command.Aliases.First()}` command for `{chnl}` channel.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -491,7 +593,21 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.RootPermission = config.RootPermission; return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{(action.Value ? " Allowed" : "🆗 Denied")} usage of `{module.Name}` module for `{chnl}` channel.").ConfigureAwait(false);
if (action.Value)
{
await ReplyConfirmLocalized("cx_enable",
Format.Code(module.Name),
GetText("of_module"),
Format.Code(chnl.Name)).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("cx_disable",
Format.Code(module.Name),
GetText("of_module"),
Format.Code(chnl.Name)).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -517,7 +633,17 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.RootPermission = config.RootPermission; return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{(action.Value ? " Allowed" : "🆗 Denied")} usage of `ALL MODULES` for `{chnl}` channel.").ConfigureAwait(false);
if (action.Value)
{
await ReplyConfirmLocalized("acm_enable",
Format.Code(chnl.Name)).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("acm_disable",
Format.Code(chnl.Name)).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -546,7 +672,17 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.RootPermission = config.RootPermission; return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{(action.Value ? " Allowed" : "🆗 Denied")} usage of `ALL MODULES` for `{role}` role.").ConfigureAwait(false);
if (action.Value)
{
await ReplyConfirmLocalized("arm_enable",
Format.Code(role.Name)).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("arm_disable",
Format.Code(role.Name)).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -572,7 +708,17 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.RootPermission = config.RootPermission; return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{(action.Value ? " Allowed" : "🆗 Denied")} usage of `ALL MODULES` for `{user}` user.").ConfigureAwait(false);
if (action.Value)
{
await ReplyConfirmLocalized("aum_enable",
Format.Code(user.ToString())).ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("aum_disable",
Format.Code(user.ToString())).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
@ -609,7 +755,15 @@ namespace NadekoBot.Modules.Permissions
}, (id, old) => { old.RootPermission = config.RootPermission; return old; });
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync($"{(action.Value ? " Allowed" : "🆗 Denied")} usage of `ALL MODULES` on this server.").ConfigureAwait(false);
if (action.Value)
{
await ReplyConfirmLocalized("asm_enable").ConfigureAwait(false);
}
else
{
await ReplyConfirmLocalized("asm_disable").ConfigureAwait(false);
}
}
}
}

View File

@ -16,7 +16,7 @@ using System.Collections.Concurrent;
namespace NadekoBot.Modules.Pokemon
{
[NadekoModule("Pokemon", ">")]
public class Pokemon : NadekoModule
public class Pokemon : NadekoTopLevelModule
{
private static readonly List<PokemonType> _pokemonTypes = new List<PokemonType>();
private static readonly ConcurrentDictionary<ulong, PokeStats> _stats = new ConcurrentDictionary<ulong, PokeStats>();

View File

@ -1,6 +1,5 @@
using AngleSharp;
using AngleSharp.Dom.Html;
using AngleSharp.Extensions;
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
@ -8,7 +7,6 @@ using NadekoBot.Extensions;
using NadekoBot.Modules.Searches.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
@ -21,20 +19,19 @@ namespace NadekoBot.Modules.Searches
public partial class Searches
{
[Group]
public class AnimeSearchCommands : ModuleBase
public class AnimeSearchCommands : NadekoSubmodule
{
private static Timer anilistTokenRefresher { get; }
private static Logger _log { get; }
private static readonly Timer anilistTokenRefresher;
private static string anilistToken { get; set; }
static AnimeSearchCommands()
{
_log = LogManager.GetCurrentClassLogger();
anilistTokenRefresher = new Timer(async (state) =>
{
try
{
var headers = new Dictionary<string, string> {
var headers = new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", "kwoth-w0ki9"},
{"client_secret", "Qd6j4FIAi1ZK6Pc7N7V4Z"},
@ -42,16 +39,17 @@ namespace NadekoBot.Modules.Searches
using (var http = new HttpClient())
{
http.AddFakeHeaders();
//http.AddFakeHeaders();
http.DefaultRequestHeaders.Clear();
var formContent = new FormUrlEncodedContent(headers);
var response = await http.PostAsync("http://anilist.co/api/auth/access_token", formContent).ConfigureAwait(false);
var response = await http.PostAsync("https://anilist.co/api/auth/access_token", formContent).ConfigureAwait(false);
var stringContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
anilistToken = JObject.Parse(stringContent)["access_token"].ToString();
}
}
catch (Exception ex)
catch
{
_log.Error(ex);
// ignored
}
}, null, TimeSpan.FromSeconds(0), TimeSpan.FromMinutes(29));
}
@ -75,7 +73,7 @@ namespace NadekoBot.Modules.Searches
var favorites = document.QuerySelectorAll("div.user-favorites > div.di-tc");
var favAnime = "No favorite anime yet";
var favAnime = GetText("anime_no_fav");
if (favorites[0].QuerySelector("p") == null)
favAnime = string.Join("\n", favorites[0].QuerySelectorAll("ul > li > div.di-tc.va-t > a")
.Shuffle()
@ -106,14 +104,14 @@ namespace NadekoBot.Modules.Searches
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle($"{name}'s MAL profile")
.AddField(efb => efb.WithName("💚 Watching").WithValue(stats[0]).WithIsInline(true))
.AddField(efb => efb.WithName("💙 Completed").WithValue(stats[1]).WithIsInline(true));
.WithTitle(GetText("mal_profile", name))
.AddField(efb => efb.WithName("💚 " + GetText("watching")).WithValue(stats[0]).WithIsInline(true))
.AddField(efb => efb.WithName("💙 " + GetText("completed")).WithValue(stats[1]).WithIsInline(true));
if (info.Count < 3)
embed.AddField(efb => efb.WithName("💛 On-Hold").WithValue(stats[2]).WithIsInline(true));
embed.AddField(efb => efb.WithName("💛 " + GetText("on_hold")).WithValue(stats[2]).WithIsInline(true));
embed
.AddField(efb => efb.WithName("💔 Dropped").WithValue(stats[3]).WithIsInline(true))
.AddField(efb => efb.WithName("⚪ Plan to watch").WithValue(stats[4]).WithIsInline(true))
.AddField(efb => efb.WithName("💔 " + GetText("dropped")).WithValue(stats[3]).WithIsInline(true))
.AddField(efb => efb.WithName("⚪ " + GetText("plan_to_watch")).WithValue(stats[4]).WithIsInline(true))
.AddField(efb => efb.WithName("🕐 " + daysAndMean[0][0]).WithValue(daysAndMean[0][1]).WithIsInline(true))
.AddField(efb => efb.WithName("📊 " + daysAndMean[1][0]).WithValue(daysAndMean[1][1]).WithIsInline(true))
.AddField(efb => efb.WithName(MalInfoToEmoji(info[0].Item1) + " " + info[0].Item1).WithValue(info[0].Item2.TrimTo(20)).WithIsInline(true))
@ -126,7 +124,7 @@ namespace NadekoBot.Modules.Searches
.WithDescription($@"
** https://myanimelist.net/animelist/{ name } **
**Top 3 Favorite Anime:**
**{GetText("top_3_fav_anime")}**
{favAnime}"
//**[Manga List](https://myanimelist.net/mangalist/{name})**
@ -176,7 +174,7 @@ namespace NadekoBot.Modules.Searches
if (animeData == null)
{
await Context.Channel.SendErrorAsync("Failed finding that animu.").ConfigureAwait(false);
await ReplyErrorLocalized("failed_finding_anime").ConfigureAwait(false);
return;
}
@ -185,10 +183,10 @@ namespace NadekoBot.Modules.Searches
.WithTitle(animeData.title_english)
.WithUrl(animeData.Link)
.WithImageUrl(animeData.image_url_lge)
.AddField(efb => efb.WithName("Episodes").WithValue(animeData.total_episodes.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("Status").WithValue(animeData.AiringStatus.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("Genres").WithValue(String.Join(", ", animeData.Genres)).WithIsInline(true))
.WithFooter(efb => efb.WithText("Score: " + animeData.average_score + " / 100"));
.AddField(efb => efb.WithName(GetText("episodes")).WithValue(animeData.total_episodes.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("status")).WithValue(animeData.AiringStatus.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("genres")).WithValue(String.Join(",\n", animeData.Genres)).WithIsInline(true))
.WithFooter(efb => efb.WithText(GetText("score") + " " + animeData.average_score + " / 100"));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
@ -203,7 +201,7 @@ namespace NadekoBot.Modules.Searches
if (mangaData == null)
{
await Context.Channel.SendErrorAsync("Failed finding that mango.").ConfigureAwait(false);
await ReplyErrorLocalized("failed_finding_manga").ConfigureAwait(false);
return;
}
@ -212,10 +210,10 @@ namespace NadekoBot.Modules.Searches
.WithTitle(mangaData.title_english)
.WithUrl(mangaData.Link)
.WithImageUrl(mangaData.image_url_lge)
.AddField(efb => efb.WithName("Episodes").WithValue(mangaData.total_chapters.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("Status").WithValue(mangaData.publishing_status.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("Genres").WithValue(String.Join(", ", mangaData.Genres)).WithIsInline(true))
.WithFooter(efb => efb.WithText("Score: " + mangaData.average_score + " / 100"));
.AddField(efb => efb.WithName(GetText("chapters")).WithValue(mangaData.total_chapters.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("status")).WithValue(mangaData.publishing_status.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("genres")).WithValue(String.Join(",\n", mangaData.Genres)).WithIsInline(true))
.WithFooter(efb => efb.WithText(GetText("score") + " " + mangaData.average_score + " / 100"));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}

View File

@ -1,4 +1,5 @@
using Newtonsoft.Json.Linq;
using System;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Net;
@ -13,7 +14,7 @@ namespace NadekoBot.Modules.Searches
public static GoogleTranslator Instance = _instance ?? (_instance = new GoogleTranslator());
public IEnumerable<string> Languages => _languageDictionary.Keys.OrderBy(x => x);
private Dictionary<string, string> _languageDictionary;
private readonly Dictionary<string, string> _languageDictionary;
static GoogleTranslator() { }
private GoogleTranslator() {
@ -153,13 +154,18 @@ namespace NadekoBot.Modules.Searches
public async Task<string> Translate(string sourceText, string sourceLanguage, string targetLanguage)
{
string text = string.Empty;
string text;
string url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
if(!_languageDictionary.ContainsKey(sourceLanguage) ||
!_languageDictionary.ContainsKey(targetLanguage))
throw new ArgumentException();
var url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}",
ConvertToLanguageCode(sourceLanguage),
ConvertToLanguageCode(targetLanguage),
WebUtility.UrlEncode(sourceText));
using (HttpClient http = new HttpClient())
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36");
text = await http.GetStringAsync(url).ConfigureAwait(false);
@ -170,7 +176,7 @@ namespace NadekoBot.Modules.Searches
private string ConvertToLanguageCode(string language)
{
string mode = string.Empty;
string mode;
_languageDictionary.TryGetValue(language, out mode);
return mode;
}

View File

@ -7,7 +7,6 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
@ -18,11 +17,11 @@ namespace NadekoBot.Modules.Searches
public partial class Searches
{
[Group]
public class JokeCommands : ModuleBase
public class JokeCommands : NadekoSubmodule
{
private static List<WoWJoke> wowJokes { get; } = new List<WoWJoke>();
private static List<MagicItem> magicItems { get; } = new List<MagicItem>();
private static Logger _log { get; }
private new static readonly Logger _log;
static JokeCommands()
{
@ -78,7 +77,7 @@ namespace NadekoBot.Modules.Searches
{
if (!wowJokes.Any())
{
await Context.Channel.SendErrorAsync("Jokes not loaded.").ConfigureAwait(false);
await ReplyErrorLocalized("jokes_not_loaded").ConfigureAwait(false);
return;
}
var joke = wowJokes[new NadekoRandom().Next(0, wowJokes.Count)];
@ -90,7 +89,7 @@ namespace NadekoBot.Modules.Searches
{
if (!wowJokes.Any())
{
await Context.Channel.SendErrorAsync("MagicItems not loaded.").ConfigureAwait(false);
await ReplyErrorLocalized("magicitems_not_loaded").ConfigureAwait(false);
return;
}
var item = magicItems[new NadekoRandom().Next(0, magicItems.Count)];

View File

@ -33,7 +33,7 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases]
public async Task Lolban()
{
var showCount = 8;
const int showCount = 8;
//http://api.champion.gg/stats/champs/mostBanned?api_key=YOUR_API_TOKEN&page=1&limit=2
try
{
@ -44,19 +44,20 @@ namespace NadekoBot.Modules.Searches
$"limit={showCount}")
.ConfigureAwait(false))["data"] as JArray;
var dataList = data.Distinct(new ChampionNameComparer()).Take(showCount).ToList();
var eb = new EmbedBuilder().WithOkColor().WithTitle(Format.Underline($"{dataList.Count} most banned champions"));
for (var i = 0; i < dataList.Count; i++)
var eb = new EmbedBuilder().WithOkColor().WithTitle(Format.Underline(GetText("x_most_banned_champs",dataList.Count)));
foreach (var champ in dataList)
{
var champ = dataList[i];
eb.AddField(efb => efb.WithName(champ["name"].ToString()).WithValue(champ["general"]["banRate"] + "%").WithIsInline(true));
var champ1 = champ;
eb.AddField(efb => efb.WithName(champ1["name"].ToString()).WithValue(champ1["general"]["banRate"] + "%").WithIsInline(true));
}
await Context.Channel.EmbedAsync(eb, Format.Italics(trashTalk[new NadekoRandom().Next(0, trashTalk.Length)])).ConfigureAwait(false);
}
}
catch (Exception)
catch (Exception ex)
{
await Context.Channel.SendMessageAsync("Something went wrong.").ConfigureAwait(false);
_log.Warn(ex);
await ReplyErrorLocalized("something_went_wrong").ConfigureAwait(false);
}
}
}

View File

@ -1,23 +1,44 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Attributes;
using System.Net.Http;
using System.Text;
using Discord.Commands;
using NadekoBot.Extensions;
namespace NadekoBot.Modules.Searches
{
public partial class Searches
{
[Group]
public class MemegenCommands : NadekoSubmodule
{
private static readonly ImmutableDictionary<char, string> _map = new Dictionary<char, string>()
{
{'?', "~q"},
{'%', "~p"},
{'#', "~h"},
{'/', "~s"},
{' ', "-"},
{'-', "--"},
{'_', "__"},
{'"', "''"}
}.ToImmutableDictionary();
[NadekoCommand, Usage, Description, Aliases]
public async Task Memelist()
{
HttpClientHandler handler = new HttpClientHandler();
var handler = new HttpClientHandler
{
AllowAutoRedirect = false
};
handler.AllowAutoRedirect = false;
using (var http = new HttpClient(handler))
{
@ -32,10 +53,27 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases]
public async Task Memegen(string meme, string topText, string botText)
{
var top = Uri.EscapeDataString(topText.Replace(' ', '-'));
var bot = Uri.EscapeDataString(botText.Replace(' ', '-'));
var top = Replace(topText);
var bot = Replace(botText);
await Context.Channel.SendMessageAsync($"http://memegen.link/{meme}/{top}/{bot}.jpg")
.ConfigureAwait(false);
}
private static string Replace(string input)
{
var sb = new StringBuilder();
foreach (var c in input)
{
string tmp;
if (_map.TryGetValue(c, out tmp))
sb.Append(tmp);
else
sb.Append(c);
}
return sb.ToString();
}
}
}
}

View File

@ -33,22 +33,23 @@ namespace NadekoBot.Modules.Searches.Models
public string[] Evos { get; set; }
public string[] EggGroups { get; set; }
public override string ToString() => $@"`Name:` {Species}
`Types:` {string.Join(", ", Types)}
`Stats:` {BaseStats}
`Height:` {HeightM,4}m `Weight:` {WeightKg}kg
`Abilities:` {string.Join(", ", Abilities.Values)}";
// public override string ToString() => $@"`Name:` {Species}
//`Types:` {string.Join(", ", Types)}
//`Stats:` {BaseStats}
//`Height:` {HeightM,4}m `Weight:` {WeightKg}kg
//`Abilities:` {string.Join(", ", Abilities.Values)}";
}
public class SearchPokemonAbility
{
public string Desc { get; set; }
public string ShortDesc { get; set; }
public string Name { get; set; }
public float Rating { get; set; }
public override string ToString() => $@"`Name:` : {Name}
`Rating:` {Rating}
`Description:` {Desc}";
// public override string ToString() => $@"`Name:` : {Name}
//`Rating:` {Rating}
//`Description:` {Desc}";
}
}

View File

@ -3,7 +3,6 @@ using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using Newtonsoft.Json.Linq;
using NLog;
using System;
using System.Globalization;
using System.IO;
@ -16,21 +15,15 @@ namespace NadekoBot.Modules.Searches
public partial class Searches
{
[Group]
public class OsuCommands : ModuleBase
public class OsuCommands : NadekoSubmodule
{
private static Logger _log { get; }
static OsuCommands()
{
_log = LogManager.GetCurrentClassLogger();
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Osu(string usr, [Remainder] string mode = null)
{
if (string.IsNullOrWhiteSpace(usr))
return;
using (HttpClient http = new HttpClient())
using (var http = new HttpClient())
{
try
{
@ -42,15 +35,15 @@ namespace NadekoBot.Modules.Searches
http.AddFakeHeaders();
var res = await http.GetStreamAsync(new Uri($"http://lemmmy.pw/osusig/sig.php?uname={ usr }&flagshadow&xpbar&xpbarhex&pp=2&mode={m}")).ConfigureAwait(false);
MemoryStream ms = new MemoryStream();
var ms = new MemoryStream();
res.CopyTo(ms);
ms.Position = 0;
await Context.Channel.SendFileAsync(ms, $"{usr}.png", $"🎧 **Profile Link:** <https://new.ppy.sh/u/{Uri.EscapeDataString(usr)}>\n`Image provided by https://lemmmy.pw/osusig`").ConfigureAwait(false);
await Context.Channel.SendFileAsync(ms, $"{usr}.png", $"🎧 **{GetText("profile_link")}** <https://new.ppy.sh/u/{Uri.EscapeDataString(usr)}>\n`Image provided by https://lemmmy.pw/osusig`").ConfigureAwait(false);
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync("Failed retrieving osu signature.").ConfigureAwait(false);
_log.Warn(ex, "Osu command failed");
await ReplyErrorLocalized("osu_failed").ConfigureAwait(false);
_log.Warn(ex);
}
}
}
@ -60,7 +53,7 @@ namespace NadekoBot.Modules.Searches
{
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.OsuApiKey))
{
await Context.Channel.SendErrorAsync("An osu! API key is required.").ConfigureAwait(false);
await ReplyErrorLocalized("osu_api_key").ConfigureAwait(false);
return;
}
@ -75,8 +68,8 @@ namespace NadekoBot.Modules.Searches
var reqString = $"https://osu.ppy.sh/api/get_beatmaps?k={NadekoBot.Credentials.OsuApiKey}&{mapId}";
var obj = JArray.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false))[0];
var sb = new System.Text.StringBuilder();
var starRating = Math.Round(Double.Parse($"{obj["difficultyrating"]}", CultureInfo.InvariantCulture), 2);
var time = TimeSpan.FromSeconds(Double.Parse($"{obj["total_length"]}")).ToString(@"mm\:ss");
var starRating = Math.Round(double.Parse($"{obj["difficultyrating"]}", CultureInfo.InvariantCulture), 2);
var time = TimeSpan.FromSeconds(double.Parse($"{obj["total_length"]}")).ToString(@"mm\:ss");
sb.AppendLine($"{obj["artist"]} - {obj["title"]}, mapped by {obj["creator"]}. https://osu.ppy.sh/s/{obj["beatmapset_id"]}");
sb.AppendLine($"{starRating} stars, {obj["bpm"]} BPM | AR{obj["diff_approach"]}, CS{obj["diff_size"]}, OD{obj["diff_overall"]} | Length: {time}");
await Context.Channel.SendMessageAsync(sb.ToString()).ConfigureAwait(false);
@ -84,8 +77,8 @@ namespace NadekoBot.Modules.Searches
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync("Something went wrong.");
_log.Warn(ex, "Osub command failed");
await ReplyErrorLocalized("something_went_wrong").ConfigureAwait(false);
_log.Warn(ex);
}
}
@ -121,54 +114,53 @@ namespace NadekoBot.Modules.Searches
{
var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps?k={NadekoBot.Credentials.OsuApiKey}&b={item["beatmap_id"]}";
var map = JArray.Parse(await http.GetStringAsync(mapReqString).ConfigureAwait(false))[0];
var pp = Math.Round(Double.Parse($"{item["pp"]}", CultureInfo.InvariantCulture), 2);
var pp = Math.Round(double.Parse($"{item["pp"]}", CultureInfo.InvariantCulture), 2);
var acc = CalculateAcc(item, m);
var mods = ResolveMods(Int32.Parse($"{item["enabled_mods"]}"));
if (mods != "+")
sb.AppendLine($"{pp + "pp",-7} | {acc + "%",-7} | {map["artist"] + "-" + map["title"] + " (" + map["version"] + ")",-40} | **{mods,-10}** | /b/{item["beatmap_id"]}");
else
sb.AppendLine($"{pp + "pp",-7} | {acc + "%",-7} | {map["artist"] + "-" + map["title"] + " (" + map["version"] + ")",-40} | /b/{item["beatmap_id"]}");
var mods = ResolveMods(int.Parse($"{item["enabled_mods"]}"));
sb.AppendLine(mods != "+"
? $"{pp + "pp",-7} | {acc + "%",-7} | {map["artist"] + "-" + map["title"] + " (" + map["version"] + ")",-40} | **{mods,-10}** | /b/{item["beatmap_id"]}"
: $"{pp + "pp",-7} | {acc + "%",-7} | {map["artist"] + "-" + map["title"] + " (" + map["version"] + ")",-40} | /b/{item["beatmap_id"]}");
}
sb.Append("```");
await channel.SendMessageAsync(sb.ToString()).ConfigureAwait(false);
}
catch (Exception ex)
{
await channel.SendErrorAsync("Something went wrong.");
_log.Warn(ex, "Osu5 command failed");
await ReplyErrorLocalized("something_went_wrong").ConfigureAwait(false);
_log.Warn(ex);
}
}
}
//https://osu.ppy.sh/wiki/Accuracy
private static Double CalculateAcc(JToken play, int mode)
private static double CalculateAcc(JToken play, int mode)
{
if (mode == 0)
{
var hitPoints = Double.Parse($"{play["count50"]}") * 50 + Double.Parse($"{play["count100"]}") * 100 + Double.Parse($"{play["count300"]}") * 300;
var totalHits = Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countmiss"]}");
var hitPoints = double.Parse($"{play["count50"]}") * 50 + double.Parse($"{play["count100"]}") * 100 + double.Parse($"{play["count300"]}") * 300;
var totalHits = double.Parse($"{play["count50"]}") + double.Parse($"{play["count100"]}") + double.Parse($"{play["count300"]}") + double.Parse($"{play["countmiss"]}");
totalHits *= 300;
return Math.Round(hitPoints / totalHits * 100, 2);
}
else if (mode == 1)
{
var hitPoints = Double.Parse($"{play["countmiss"]}") * 0 + Double.Parse($"{play["count100"]}") * 0.5 + Double.Parse($"{play["count300"]}") * 1;
var totalHits = Double.Parse($"{play["countmiss"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}");
var hitPoints = double.Parse($"{play["countmiss"]}") * 0 + double.Parse($"{play["count100"]}") * 0.5 + double.Parse($"{play["count300"]}") * 1;
var totalHits = double.Parse($"{play["countmiss"]}") + double.Parse($"{play["count100"]}") + double.Parse($"{play["count300"]}");
hitPoints *= 300;
totalHits *= 300;
return Math.Round(hitPoints / totalHits * 100, 2);
}
else if (mode == 2)
{
var fruitsCaught = Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}");
var totalFruits = Double.Parse($"{play["countmiss"]}") + Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countkatu"]}");
var fruitsCaught = double.Parse($"{play["count50"]}") + double.Parse($"{play["count100"]}") + double.Parse($"{play["count300"]}");
var totalFruits = double.Parse($"{play["countmiss"]}") + double.Parse($"{play["count50"]}") + double.Parse($"{play["count100"]}") + double.Parse($"{play["count300"]}") + double.Parse($"{play["countkatu"]}");
return Math.Round(fruitsCaught / totalFruits * 100, 2);
}
else
{
var hitPoints = Double.Parse($"{play["count50"]}") * 50 + Double.Parse($"{play["count100"]}") * 100 + Double.Parse($"{play["countkatu"]}") * 200 + (Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countgeki"]}")) * 300;
var totalHits = Double.Parse($"{play["countmiss"]}") + Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["countkatu"]}") + Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countgeki"]}");
var hitPoints = double.Parse($"{play["count50"]}") * 50 + double.Parse($"{play["count100"]}") * 100 + double.Parse($"{play["countkatu"]}") * 200 + (double.Parse($"{play["count300"]}") + double.Parse($"{play["countgeki"]}")) * 300;
var totalHits = double.Parse($"{play["countmiss"]}") + double.Parse($"{play["count50"]}") + double.Parse($"{play["count100"]}") + double.Parse($"{play["countkatu"]}") + double.Parse($"{play["count300"]}") + double.Parse($"{play["countgeki"]}");
totalHits *= 300;
return Math.Round(hitPoints / totalHits * 100, 2);
}
@ -176,10 +168,10 @@ namespace NadekoBot.Modules.Searches
private static string ResolveMap(string mapLink)
{
Match s = new Regex(@"osu.ppy.sh\/s\/", RegexOptions.IgnoreCase).Match(mapLink);
Match b = new Regex(@"osu.ppy.sh\/b\/", RegexOptions.IgnoreCase).Match(mapLink);
Match p = new Regex(@"osu.ppy.sh\/p\/", RegexOptions.IgnoreCase).Match(mapLink);
Match m = new Regex(@"&m=", RegexOptions.IgnoreCase).Match(mapLink);
var s = new Regex(@"osu.ppy.sh\/s\/", RegexOptions.IgnoreCase).Match(mapLink);
var b = new Regex(@"osu.ppy.sh\/b\/", RegexOptions.IgnoreCase).Match(mapLink);
var p = new Regex(@"osu.ppy.sh\/p\/", RegexOptions.IgnoreCase).Match(mapLink);
var m = new Regex(@"&m=", RegexOptions.IgnoreCase).Match(mapLink);
if (s.Success)
{
var mapId = mapLink.Substring(mapLink.IndexOf("/s/") + 3);

View File

@ -4,7 +4,6 @@ using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Searches.Models;
using Newtonsoft.Json;
using NLog;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@ -14,13 +13,8 @@ namespace NadekoBot.Modules.Searches
public partial class Searches
{
[Group]
public class OverwatchCommands : ModuleBase
public class OverwatchCommands : NadekoSubmodule
{
private readonly Logger _log;
public OverwatchCommands()
{
_log = LogManager.GetCurrentClassLogger();
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Overwatch(string region, [Remainder] string query = null)
{
@ -34,9 +28,9 @@ namespace NadekoBot.Modules.Searches
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
var model = await GetProfile(region, battletag);
var rankimg = $"{model.Competitive.rank_img}";
var rank = $"{model.Competitive.rank}";
//var competitiveplay = $"{model.Games.Competitive.played}";
var rankimg = model.Competitive.rank_img;
var rank = model.Competitive.rank;
if (string.IsNullOrWhiteSpace(rank))
{
var embed = new EmbedBuilder()
@ -44,10 +38,10 @@ namespace NadekoBot.Modules.Searches
.WithUrl($"https://www.overbuff.com/players/pc/{battletag}")
.WithIconUrl($"{model.avatar}"))
.WithThumbnailUrl("https://cdn.discordapp.com/attachments/155726317222887425/255653487512256512/YZ4w2ey.png")
.AddField(fb => fb.WithName("**Level**").WithValue($"{model.level}").WithIsInline(true))
.AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true))
.AddField(fb => fb.WithName("**Competitive Rank**").WithValue("0").WithIsInline(true))
.AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("level")).WithValue($"{model.level}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("quick_wins")).WithValue($"{model.Games.Quick.wins}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("compet_rank")).WithValue("0").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("quick_playtime")).WithValue($"{model.Playtime.quick}").WithIsInline(true))
.WithOkColor();
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
@ -58,22 +52,21 @@ namespace NadekoBot.Modules.Searches
.WithUrl($"https://www.overbuff.com/players/pc/{battletag}")
.WithIconUrl($"{model.avatar}"))
.WithThumbnailUrl(rankimg)
.AddField(fb => fb.WithName("**Level**").WithValue($"{model.level}").WithIsInline(true))
.AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true))
.AddField(fb => fb.WithName("**Competitive Wins**").WithValue($"{model.Games.Competitive.wins}").WithIsInline(true))
.AddField(fb => fb.WithName("**Competitive Loses**").WithValue($"{model.Games.Competitive.lost}").WithIsInline(true))
.AddField(fb => fb.WithName("**Competitive Played**").WithValue($"{model.Games.Competitive.played}").WithIsInline(true))
.AddField(fb => fb.WithName("**Competitive Rank**").WithValue(rank).WithIsInline(true))
.AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true))
.AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("level")).WithValue($"{model.level}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("quick_wins")).WithValue($"{model.Games.Quick.wins}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("compet_wins")).WithValue($"{model.Games.Competitive.wins}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("compet_losses")).WithValue($"{model.Games.Competitive.lost}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("compet_played")).WithValue($"{model.Games.Competitive.played}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("compet_rank")).WithValue(rank).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("compet_played")).WithValue($"{model.Playtime.competitive}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("quick_playtime")).WithValue($"{model.Playtime.quick}").WithIsInline(true))
.WithColor(NadekoBot.OkColor);
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
return;
}
}
catch
{
await Context.Channel.SendErrorAsync("Found no user! Please check the **Region** and **BattleTag** before trying again.");
await ReplyErrorLocalized("ow_user_not_found").ConfigureAwait(false);
}
}
public async Task<OverwatchApiModel.OverwatchPlayer.Data> GetProfile(string region, string battletag)
@ -82,8 +75,8 @@ namespace NadekoBot.Modules.Searches
{
using (var http = new HttpClient())
{
var Url = await http.GetStringAsync($"https://api.lootbox.eu/pc/{region.ToLower()}/{battletag}/profile");
var model = JsonConvert.DeserializeObject<OverwatchApiModel.OverwatchPlayer>(Url);
var url = await http.GetStringAsync($"https://api.lootbox.eu/pc/{region.ToLower()}/{battletag}/profile");
var model = JsonConvert.DeserializeObject<OverwatchApiModel.OverwatchPlayer>(url);
return model.data;
}
}

View File

@ -10,10 +10,9 @@ namespace NadekoBot.Modules.Searches
public partial class Searches
{
[Group]
public class PlaceCommands : ModuleBase
public class PlaceCommands : NadekoSubmodule
{
private static string typesStr { get; } =
string.Format("`List of \"{0}place\" tags:`\n", NadekoBot.ModulePrefixes[typeof(Searches).Name]) + String.Join(", ", Enum.GetNames(typeof(PlaceType)));
private static string typesStr { get; } = string.Join(", ", Enum.GetNames(typeof(PlaceType)));
public enum PlaceType
{
@ -30,14 +29,15 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases]
public async Task Placelist()
{
await Context.Channel.SendConfirmAsync(typesStr)
await Context.Channel.SendConfirmAsync(GetText("list_of_place_tags", NadekoBot.ModulePrefixes[typeof(Searches).Name]),
typesStr)
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Place(PlaceType placeType, uint width = 0, uint height = 0)
{
string url = "";
var url = "";
switch (placeType)
{
case PlaceType.Cage:

View File

@ -6,7 +6,6 @@ using NadekoBot.Modules.Searches.Models;
using Newtonsoft.Json;
using NLog;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@ -16,7 +15,7 @@ namespace NadekoBot.Modules.Searches
public partial class Searches
{
[Group]
public class PokemonSearchCommands : ModuleBase
public class PokemonSearchCommands : NadekoSubmodule
{
private static Dictionary<string, SearchPokemon> pokemons { get; } = new Dictionary<string, SearchPokemon>();
private static Dictionary<string, SearchPokemonAbility> pokemonAbilities { get; } = new Dictionary<string, SearchPokemonAbility>();
@ -24,7 +23,7 @@ namespace NadekoBot.Modules.Searches
public const string PokemonAbilitiesFile = "data/pokemon/pokemon_abilities7.json";
public const string PokemonListFile = "data/pokemon/pokemon_list7.json";
private static Logger _log { get; }
private new static readonly Logger _log;
static PokemonSearchCommands()
{
@ -57,14 +56,13 @@ namespace NadekoBot.Modules.Searches
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(kvp.Key.ToTitleCase())
.WithDescription(p.BaseStats.ToString())
.AddField(efb => efb.WithName("Types").WithValue(string.Join(",\n", p.Types)).WithIsInline(true))
.AddField(efb => efb.WithName("Height/Weight").WithValue($"{p.HeightM}m/{p.WeightKg}kg").WithIsInline(true))
.AddField(efb => efb.WithName("Abilitities").WithValue(string.Join(",\n", p.Abilities.Select(a => a.Value))).WithIsInline(true))
);
.AddField(efb => efb.WithName(GetText("types")).WithValue(string.Join(",\n", p.Types)).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("height_weight")).WithValue(GetText("height_weight_val", p.HeightM, p.WeightKg)).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("abilities")).WithValue(string.Join(",\n", p.Abilities.Select(a => a.Value))).WithIsInline(true)));
return;
}
}
await Context.Channel.SendErrorAsync("No pokemon found.");
await ReplyErrorLocalized("pokemon_none").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -79,13 +77,16 @@ namespace NadekoBot.Modules.Searches
{
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(kvp.Value.Name)
.WithDescription(kvp.Value.Desc)
.AddField(efb => efb.WithName("Rating").WithValue(kvp.Value.Rating.ToString()).WithIsInline(true))
.WithDescription(string.IsNullOrWhiteSpace(kvp.Value.Desc)
? kvp.Value.ShortDesc
: kvp.Value.Desc)
.AddField(efb => efb.WithName(GetText("rating"))
.WithValue(kvp.Value.Rating.ToString(_cultureInfo)).WithIsInline(true))
).ConfigureAwait(false);
return;
}
}
await Context.Channel.SendErrorAsync("No ability found.");
await ReplyErrorLocalized("pokemon_ability_none").ConfigureAwait(false);
}
}
}

View File

@ -12,9 +12,7 @@ using System.Net.Http;
using NadekoBot.Attributes;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using NLog;
using NadekoBot.Extensions;
using System.Diagnostics;
namespace NadekoBot.Modules.Searches
{
@ -65,23 +63,19 @@ namespace NadekoBot.Modules.Searches
}
[Group]
public class StreamNotificationCommands : ModuleBase
public class StreamNotificationCommands : NadekoSubmodule
{
private static Timer checkTimer { get; }
private static ConcurrentDictionary<string, StreamStatus> oldCachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
private static ConcurrentDictionary<string, StreamStatus> cachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
private static Logger _log { get; }
private static readonly Timer _checkTimer;
private static readonly ConcurrentDictionary<string, StreamStatus> _cachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
private static bool FirstPass { get; set; } = true;
private static bool firstPass { get; set; } = true;
static StreamNotificationCommands()
{
_log = LogManager.GetCurrentClassLogger();
checkTimer = new Timer(async (state) =>
_checkTimer = new Timer(async (state) =>
{
oldCachedStatuses = new ConcurrentDictionary<string, StreamStatus>(cachedStatuses);
cachedStatuses.Clear();
var oldCachedStatuses = new ConcurrentDictionary<string, StreamStatus>(_cachedStatuses);
_cachedStatuses.Clear();
IEnumerable<FollowedStream> streams;
using (var uow = DbHandler.UnitOfWork())
{
@ -93,7 +87,7 @@ namespace NadekoBot.Modules.Searches
try
{
var newStatus = await GetStreamStatus(fs).ConfigureAwait(false);
if (FirstPass)
if (firstPass)
{
return;
}
@ -108,7 +102,7 @@ namespace NadekoBot.Modules.Searches
return;
try
{
var msg = await channel.EmbedAsync(fs.GetEmbed(newStatus)).ConfigureAwait(false);
await channel.EmbedAsync(fs.GetEmbed(newStatus, channel.Guild.Id)).ConfigureAwait(false);
}
catch
{
@ -122,7 +116,7 @@ namespace NadekoBot.Modules.Searches
}
}));
FirstPass = false;
firstPass = false;
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(60));
}
@ -134,7 +128,7 @@ namespace NadekoBot.Modules.Searches
{
case FollowedStream.FollowedStreamType.Hitbox:
var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username.ToLowerInvariant()}";
if (checkCache && cachedStatuses.TryGetValue(hitboxUrl, out result))
if (checkCache && _cachedStatuses.TryGetValue(hitboxUrl, out result))
return result;
using (var http = new HttpClient())
{
@ -149,11 +143,11 @@ namespace NadekoBot.Modules.Searches
ApiLink = hitboxUrl,
Views = hbData.Views
};
cachedStatuses.AddOrUpdate(hitboxUrl, result, (key, old) => result);
_cachedStatuses.AddOrUpdate(hitboxUrl, result, (key, old) => result);
return result;
case FollowedStream.FollowedStreamType.Twitch:
var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username.ToLowerInvariant())}?client_id=67w6z9i09xv2uoojdm9l0wsyph4hxo6";
if (checkCache && cachedStatuses.TryGetValue(twitchUrl, out result))
if (checkCache && _cachedStatuses.TryGetValue(twitchUrl, out result))
return result;
using (var http = new HttpClient())
{
@ -170,11 +164,11 @@ namespace NadekoBot.Modules.Searches
ApiLink = twitchUrl,
Views = twData.Stream?.Viewers.ToString() ?? "0"
};
cachedStatuses.AddOrUpdate(twitchUrl, result, (key, old) => result);
_cachedStatuses.AddOrUpdate(twitchUrl, result, (key, old) => result);
return result;
case FollowedStream.FollowedStreamType.Beam:
var beamUrl = $"https://beam.pro/api/v1/channels/{stream.Username.ToLowerInvariant()}";
if (checkCache && cachedStatuses.TryGetValue(beamUrl, out result))
if (checkCache && _cachedStatuses.TryGetValue(beamUrl, out result))
return result;
using (var http = new HttpClient())
{
@ -190,7 +184,7 @@ namespace NadekoBot.Modules.Searches
ApiLink = beamUrl,
Views = bmData.ViewersCurrent.ToString()
};
cachedStatuses.AddOrUpdate(beamUrl, result, (key, old) => result);
_cachedStatuses.AddOrUpdate(beamUrl, result, (key, old) => result);
return result;
default:
break;
@ -234,17 +228,21 @@ namespace NadekoBot.Modules.Searches
if (!streams.Any())
{
await Context.Channel.SendConfirmAsync("You are not following any streams on this server.").ConfigureAwait(false);
await ReplyErrorLocalized("streams_none").ConfigureAwait(false);
return;
}
var text = string.Join("\n", await Task.WhenAll(streams.Select(async snc =>
{
var ch = await Context.Guild.GetTextChannelAsync(snc.ChannelId);
return $"`{snc.Username}`'s stream on **{(ch)?.Name}** channel. 【`{snc.Type.ToString()}`】";
return string.Format("{0}'s stream on {1} channel. 【{2}】",
Format.Code(snc.Username),
Format.Bold(ch?.Name ?? "deleted-channel"),
Format.Code(snc.Type.ToString()));
})));
await Context.Channel.SendConfirmAsync($"You are following **{streams.Count()}** streams on this server.\n\n" + text).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(GetText("streams_following", streams.Count()) + "\n\n" + text)
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -271,10 +269,13 @@ namespace NadekoBot.Modules.Searches
}
if (!removed)
{
await Context.Channel.SendErrorAsync("No such stream.").ConfigureAwait(false);
await ReplyErrorLocalized("stream_no").ConfigureAwait(false);
return;
}
await Context.Channel.SendConfirmAsync($"Removed `{username}`'s stream ({type}) from notifications.").ConfigureAwait(false);
await ReplyConfirmLocalized("stream_removed",
Format.Code(username),
type).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -293,20 +294,24 @@ namespace NadekoBot.Modules.Searches
}));
if (streamStatus.IsLive)
{
await Context.Channel.SendConfirmAsync($"Streamer {username} is online with {streamStatus.Views} viewers.");
await ReplyConfirmLocalized("streamer_online",
username,
streamStatus.Views)
.ConfigureAwait(false);
}
else
{
await Context.Channel.SendConfirmAsync($"Streamer {username} is offline.");
await ReplyConfirmLocalized("streamer_offline",
username).ConfigureAwait(false);
}
}
catch
{
await Context.Channel.SendErrorAsync("No channel found.");
await ReplyErrorLocalized("no_channel_found").ConfigureAwait(false);
}
}
private static async Task TrackStream(ITextChannel channel, string username, FollowedStream.FollowedStreamType type)
private async Task TrackStream(ITextChannel channel, string username, FollowedStream.FollowedStreamType type)
{
username = username.ToLowerInvariant().Trim();
var fs = new FollowedStream
@ -324,7 +329,7 @@ namespace NadekoBot.Modules.Searches
}
catch
{
await channel.SendErrorAsync("Stream probably doesn't exist.").ConfigureAwait(false);
await ReplyErrorLocalized("stream_not_exist").ConfigureAwait(false);
return;
}
@ -335,24 +340,24 @@ namespace NadekoBot.Modules.Searches
.Add(fs);
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.EmbedAsync(fs.GetEmbed(status), $"🆗 I will notify this channel when status changes.").ConfigureAwait(false);
await channel.EmbedAsync(fs.GetEmbed(status, Context.Guild.Id), GetText("stream_tracked")).ConfigureAwait(false);
}
}
}
public static class FollowedStreamExtensions
{
public static EmbedBuilder GetEmbed(this FollowedStream fs, Searches.StreamStatus status)
public static EmbedBuilder GetEmbed(this FollowedStream fs, Searches.StreamStatus status, ulong guildId)
{
var embed = new EmbedBuilder().WithTitle(fs.Username)
.WithUrl(fs.GetLink())
.AddField(efb => efb.WithName("Status")
.AddField(efb => efb.WithName(fs.GetText("status"))
.WithValue(status.IsLive ? "Online" : "Offline")
.WithIsInline(true))
.AddField(efb => efb.WithName("Viewers")
.AddField(efb => efb.WithName(fs.GetText("viewers"))
.WithValue(status.IsLive ? status.Views : "-")
.WithIsInline(true))
.AddField(efb => efb.WithName("Platform")
.AddField(efb => efb.WithName(fs.GetText("platform"))
.WithValue(fs.Type.ToString())
.WithIsInline(true))
.WithColor(status.IsLive ? NadekoBot.OkColor : NadekoBot.ErrorColor);
@ -360,14 +365,20 @@ namespace NadekoBot.Modules.Searches
return embed;
}
public static string GetLink(this FollowedStream fs) {
public static string GetText(this FollowedStream fs, string key, params object[] replacements) =>
NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(fs.GuildId),
typeof(Searches).Name.ToLowerInvariant(),
replacements);
public static string GetLink(this FollowedStream fs)
{
if (fs.Type == FollowedStream.FollowedStreamType.Hitbox)
return $"http://www.hitbox.tv/{fs.Username}/";
else if (fs.Type == FollowedStream.FollowedStreamType.Twitch)
if (fs.Type == FollowedStream.FollowedStreamType.Twitch)
return $"http://www.twitch.tv/{fs.Username}/";
else if (fs.Type == FollowedStream.FollowedStreamType.Beam)
if (fs.Type == FollowedStream.FollowedStreamType.Beam)
return $"https://beam.pro/{fs.Username}/";
else
return "??";
}
}

View File

@ -19,10 +19,10 @@ namespace NadekoBot.Modules.Searches
}
[Group]
public class TranslateCommands : ModuleBase
public class TranslateCommands : NadekoSubmodule
{
private static ConcurrentDictionary<ulong, bool> TranslatedChannels { get; } = new ConcurrentDictionary<ulong, bool>();
private static ConcurrentDictionary<UserChannelPair, string> UserLanguages { get; } = new ConcurrentDictionary<UserChannelPair, string>();
private static ConcurrentDictionary<ulong, bool> translatedChannels { get; } = new ConcurrentDictionary<ulong, bool>();
private static ConcurrentDictionary<UserChannelPair, string> userLanguages { get; } = new ConcurrentDictionary<UserChannelPair, string>();
static TranslateCommands()
{
@ -35,7 +35,7 @@ namespace NadekoBot.Modules.Searches
return;
bool autoDelete;
if (!TranslatedChannels.TryGetValue(umsg.Channel.Id, out autoDelete))
if (!translatedChannels.TryGetValue(umsg.Channel.Id, out autoDelete))
return;
var key = new UserChannelPair()
{
@ -44,10 +44,10 @@ namespace NadekoBot.Modules.Searches
};
string langs;
if (!UserLanguages.TryGetValue(key, out langs))
if (!userLanguages.TryGetValue(key, out langs))
return;
var text = await TranslateInternal(langs, umsg.Resolve(TagHandling.Ignore), true)
var text = await TranslateInternal(langs, umsg.Resolve(TagHandling.Ignore))
.ConfigureAwait(false);
if (autoDelete)
try { await umsg.DeleteAsync().ConfigureAwait(false); } catch { }
@ -64,21 +64,21 @@ namespace NadekoBot.Modules.Searches
{
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
var translation = await TranslateInternal(langs, text);
await Context.Channel.SendConfirmAsync("Translation " + langs, translation).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(GetText("translation") + " " + langs, translation).ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync("Bad input format, or something went wrong...").ConfigureAwait(false);
await ReplyErrorLocalized("bad_input_format").ConfigureAwait(false);
}
}
private static async Task<string> TranslateInternal(string langs, [Remainder] string text = null, bool silent = false)
private static async Task<string> TranslateInternal(string langs, [Remainder] string text = null)
{
var langarr = langs.ToLowerInvariant().Split('>');
if (langarr.Length != 2)
throw new ArgumentException();
string from = langarr[0];
string to = langarr[1];
var from = langarr[0];
var to = langarr[1];
text = text?.Trim();
if (string.IsNullOrWhiteSpace(text))
throw new ArgumentException();
@ -101,20 +101,20 @@ namespace NadekoBot.Modules.Searches
if (autoDelete == AutoDeleteAutoTranslate.Del)
{
TranslatedChannels.AddOrUpdate(channel.Id, true, (key, val) => true);
try { await channel.SendConfirmAsync("Started automatic translation of messages on this channel. User messages will be auto-deleted.").ConfigureAwait(false); } catch { }
translatedChannels.AddOrUpdate(channel.Id, true, (key, val) => true);
await ReplyConfirmLocalized("atl_ad_started").ConfigureAwait(false);
return;
}
bool throwaway;
if (TranslatedChannels.TryRemove(channel.Id, out throwaway))
if (translatedChannels.TryRemove(channel.Id, out throwaway))
{
try { await channel.SendConfirmAsync("Stopped automatic translation of messages on this channel.").ConfigureAwait(false); } catch { }
await ReplyConfirmLocalized("atl_stopped").ConfigureAwait(false);
return;
}
else if (TranslatedChannels.TryAdd(channel.Id, autoDelete == AutoDeleteAutoTranslate.Del))
if (translatedChannels.TryAdd(channel.Id, autoDelete == AutoDeleteAutoTranslate.Del))
{
try { await channel.SendConfirmAsync("Started automatic translation of messages on this channel.").ConfigureAwait(false); } catch { }
await ReplyConfirmLocalized("atl_started").ConfigureAwait(false);
}
}
@ -130,8 +130,8 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(langs))
{
if (UserLanguages.TryRemove(ucp, out langs))
await Context.Channel.SendConfirmAsync($"{Context.User.Mention}'s auto-translate language has been removed.").ConfigureAwait(false);
if (userLanguages.TryRemove(ucp, out langs))
await ReplyConfirmLocalized("atl_removed").ConfigureAwait(false);
return;
}
@ -143,20 +143,20 @@ namespace NadekoBot.Modules.Searches
if (!GoogleTranslator.Instance.Languages.Contains(from) || !GoogleTranslator.Instance.Languages.Contains(to))
{
try { await Context.Channel.SendErrorAsync("Invalid source and/or target language.").ConfigureAwait(false); } catch { }
await ReplyErrorLocalized("invalid_lang").ConfigureAwait(false);
return;
}
UserLanguages.AddOrUpdate(ucp, langs, (key, val) => langs);
userLanguages.AddOrUpdate(ucp, langs, (key, val) => langs);
await Context.Channel.SendConfirmAsync($"Your auto-translate language has been set to {from}>{to}").ConfigureAwait(false);
await ReplyConfirmLocalized("atl_set", from, to).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Translangs()
{
await Context.Channel.SendTableAsync(GoogleTranslator.Instance.Languages, str => $"{str,-15}", columns: 3);
await Context.Channel.SendTableAsync(GoogleTranslator.Instance.Languages, str => $"{str,-15}", 3);
}
}

View File

@ -12,9 +12,9 @@ namespace NadekoBot.Modules.Searches
public partial class Searches
{
[Group]
public class XkcdCommands : ModuleBase
public class XkcdCommands : NadekoSubmodule
{
private const string xkcdUrl = "https://xkcd.com";
private const string _xkcdUrl = "https://xkcd.com";
[NadekoCommand, Usage, Description, Aliases]
[Priority(1)]
@ -24,9 +24,9 @@ namespace NadekoBot.Modules.Searches
{
using (var http = new HttpClient())
{
var res = await http.GetStringAsync($"{xkcdUrl}/info.0.json").ConfigureAwait(false);
var res = await http.GetStringAsync($"{_xkcdUrl}/info.0.json").ConfigureAwait(false);
var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
var sent = await Context.Channel.SendMessageAsync($"{Context.User.Mention} " + comic.ToString())
var sent = await Context.Channel.SendMessageAsync($"{Context.User.Mention} " + comic)
.ConfigureAwait(false);
await Task.Delay(10000).ConfigureAwait(false);
@ -47,14 +47,14 @@ namespace NadekoBot.Modules.Searches
using (var http = new HttpClient())
{
var res = await http.GetStringAsync($"{xkcdUrl}/{num}/info.0.json").ConfigureAwait(false);
var res = await http.GetStringAsync($"{_xkcdUrl}/{num}/info.0.json").ConfigureAwait(false);
var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
var embed = new EmbedBuilder().WithColor(NadekoBot.OkColor)
.WithImageUrl(comic.ImageLink)
.WithAuthor(eab => eab.WithName(comic.Title).WithUrl($"{xkcdUrl}/{num}").WithIconUrl("http://xkcd.com/s/919f27.ico"))
.AddField(efb => efb.WithName("Comic#").WithValue(comic.Num.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName("Date").WithValue($"{comic.Month}/{comic.Year}").WithIsInline(true));
.WithAuthor(eab => eab.WithName(comic.Title).WithUrl($"{_xkcdUrl}/{num}").WithIconUrl("http://xkcd.com/s/919f27.ico"))
.AddField(efb => efb.WithName(GetText("comic_number")).WithValue(comic.Num.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("date")).WithValue($"{comic.Month}/{comic.Year}").WithIsInline(true));
var sent = await Context.Channel.EmbedAsync(embed)
.ConfigureAwait(false);
@ -75,9 +75,6 @@ namespace NadekoBot.Modules.Searches
[JsonProperty("img")]
public string ImageLink { get; set; }
public string Alt { get; set; }
public override string ToString()
=> $"`Comic:` #{Num} `Title:` {Title} `Date:` {Month}/{Year}\n{ImageLink}";
}
}
}

View File

@ -1,5 +1,4 @@
using Discord;
using Discord.Commands;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
@ -8,27 +7,26 @@ using System.Text;
using System.Net.Http;
using NadekoBot.Services;
using System.Threading.Tasks;
using NadekoBot.Attributes;
using System.Text.RegularExpressions;
using System.Net;
using NadekoBot.Modules.Searches.Models;
using System.Collections.Generic;
using ImageSharp;
using NadekoBot.Extensions;
using System.IO;
using NadekoBot.Modules.Searches.Commands.OMDB;
using NadekoBot.Modules.Searches.Commands.Models;
using AngleSharp.Parser.Html;
using AngleSharp;
using AngleSharp.Dom.Html;
using AngleSharp.Dom;
using System.Xml;
using System.Xml.Linq;
using Configuration = AngleSharp.Configuration;
using NadekoBot.Attributes;
using Discord.Commands;
using ImageSharp.Processing.Processors;
namespace NadekoBot.Modules.Searches
{
[NadekoModule("Searches", "~")]
public partial class Searches : NadekoModule
public partial class Searches : NadekoTopLevelModule
{
[NadekoCommand, Usage, Description, Aliases]
public async Task Weather([Remainder] string query)
@ -43,15 +41,15 @@ namespace NadekoBot.Modules.Searches
var data = JsonConvert.DeserializeObject<WeatherData>(response);
var embed = new EmbedBuilder()
.AddField(fb => fb.WithName("🌍 **Location**").WithValue(data.name + ", " + data.sys.country).WithIsInline(true))
.AddField(fb => fb.WithName("📏 **Lat,Long**").WithValue($"{data.coord.lat}, {data.coord.lon}").WithIsInline(true))
.AddField(fb => fb.WithName("☁ **Condition**").WithValue(String.Join(", ", data.weather.Select(w => w.main))).WithIsInline(true))
.AddField(fb => fb.WithName("😓 **Humidity**").WithValue($"{data.main.humidity}%").WithIsInline(true))
.AddField(fb => fb.WithName("💨 **Wind Speed**").WithValue(data.wind.speed + " km/h").WithIsInline(true))
.AddField(fb => fb.WithName("🌡 **Temperature**").WithValue(data.main.temp + "°C").WithIsInline(true))
.AddField(fb => fb.WithName("🔆 **Min - Max**").WithValue($"{data.main.temp_min}°C - {data.main.temp_max}°C").WithIsInline(true))
.AddField(fb => fb.WithName("🌄 **Sunrise (utc)**").WithValue($"{data.sys.sunrise.ToUnixTimestamp():HH:mm}").WithIsInline(true))
.AddField(fb => fb.WithName("🌇 **Sunset (utc)**").WithValue($"{data.sys.sunset.ToUnixTimestamp():HH:mm}").WithIsInline(true))
.AddField(fb => fb.WithName("🌍 " + GetText("location")).WithValue(data.name + ", " + data.sys.country).WithIsInline(true))
.AddField(fb => fb.WithName("📏 " + GetText("latlong")).WithValue($"{data.coord.lat}, {data.coord.lon}").WithIsInline(true))
.AddField(fb => fb.WithName("☁ " + GetText("condition")).WithValue(string.Join(", ", data.weather.Select(w => w.main))).WithIsInline(true))
.AddField(fb => fb.WithName("😓 " + GetText("humidity")).WithValue($"{data.main.humidity}%").WithIsInline(true))
.AddField(fb => fb.WithName("💨 " + GetText("wind_speed")).WithValue(data.wind.speed + " km/h").WithIsInline(true))
.AddField(fb => fb.WithName("🌡 " + GetText("temperature")).WithValue(data.main.temp + "°C").WithIsInline(true))
.AddField(fb => fb.WithName("🔆 " + GetText("min_max")).WithValue($"{data.main.temp_min}°C - {data.main.temp_max}°C").WithIsInline(true))
.AddField(fb => fb.WithName("🌄 " + GetText("sunrise")).WithValue($"{data.sys.sunrise.ToUnixTimestamp():HH:mm}").WithIsInline(true))
.AddField(fb => fb.WithName("🌇 " + GetText("sunset")).WithValue($"{data.sys.sunset.ToUnixTimestamp():HH:mm}").WithIsInline(true))
.WithOkColor()
.WithFooter(efb => efb.WithText("Powered by http://openweathermap.org"));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
@ -60,17 +58,15 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases]
public async Task Youtube([Remainder] string query = null)
{
if (!(await ValidateQuery(Context.Channel, query).ConfigureAwait(false))) return;
if (!await ValidateQuery(Context.Channel, query).ConfigureAwait(false)) return;
var result = (await NadekoBot.Google.GetVideosByKeywordsAsync(query, 1)).FirstOrDefault();
if (string.IsNullOrWhiteSpace(result))
{
await Context.Channel.SendErrorAsync("No results found for that query.").ConfigureAwait(false);
await ReplyErrorLocalized("no_results").ConfigureAwait(false);
return;
}
await Context.Channel.SendMessageAsync(result).ConfigureAwait(false);
//await Context.Channel.EmbedAsync(new Discord.API.Embed() { Video = new Discord.API.EmbedVideo() { Url = result.Replace("watch?v=", "embed/") }, Color = NadekoBot.OkColor }).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -82,7 +78,7 @@ namespace NadekoBot.Modules.Searches
var movie = await OmdbProvider.FindMovie(query);
if (movie == null)
{
await Context.Channel.SendErrorAsync("Failed to find that movie.").ConfigureAwait(false);
await ReplyErrorLocalized("imdb_fail").ConfigureAwait(false);
return;
}
await Context.Channel.EmbedAsync(movie.GetEmbed()).ConfigureAwait(false);
@ -94,7 +90,7 @@ namespace NadekoBot.Modules.Searches
using (var http = new HttpClient())
{
var res = JObject.Parse(await http.GetStringAsync("http://www.random.cat/meow").ConfigureAwait(false));
await Context.Channel.SendMessageAsync(res["file"].ToString()).ConfigureAwait(false);
await Context.Channel.SendMessageAsync(Uri.EscapeUriString(res["file"].ToString())).ConfigureAwait(false);
}
}
@ -122,12 +118,12 @@ namespace NadekoBot.Modules.Searches
var res = await NadekoBot.Google.GetImageAsync(terms).ConfigureAwait(false);
var embed = new EmbedBuilder()
.WithOkColor()
.WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50))
.WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50))
.WithUrl("https://www.google.rs/search?q=" + terms + "&source=lnms&tbm=isch")
.WithIconUrl("http://i.imgur.com/G46fm8J.png"))
.WithDescription(res.Link)
.WithImageUrl(res.Link)
.WithTitle(Context.User.Mention);
.WithTitle(Context.User.ToString());
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
catch
@ -157,7 +153,7 @@ namespace NadekoBot.Modules.Searches
.WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?"))
.WithDescription(source)
.WithImageUrl(source)
.WithTitle(Context.User.Mention);
.WithTitle(Context.User.ToString());
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
}
@ -174,12 +170,12 @@ namespace NadekoBot.Modules.Searches
var res = await NadekoBot.Google.GetImageAsync(terms, new NadekoRandom().Next(0, 50)).ConfigureAwait(false);
var embed = new EmbedBuilder()
.WithOkColor()
.WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50))
.WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50))
.WithUrl("https://www.google.rs/search?q=" + terms + "&source=lnms&tbm=isch")
.WithIconUrl("http://i.imgur.com/G46fm8J.png"))
.WithDescription(res.Link)
.WithImageUrl(res.Link)
.WithTitle(Context.User.Mention);
.WithTitle(Context.User.ToString());
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
catch
@ -205,12 +201,12 @@ namespace NadekoBot.Modules.Searches
var embed = new EmbedBuilder()
.WithOkColor()
.WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50))
.WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50))
.WithUrl(fullQueryLink)
.WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?"))
.WithDescription(source)
.WithImageUrl(source)
.WithTitle(Context.User.Mention);
.WithTitle(Context.User.ToString());
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
}
@ -235,13 +231,14 @@ namespace NadekoBot.Modules.Searches
if (shortened == arg)
{
await Context.Channel.SendErrorAsync("Failed to shorten that url.").ConfigureAwait(false);
await ReplyErrorLocalized("shorten_fail").ConfigureAwait(false);
return;
}
await Context.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
.AddField(efb => efb.WithName("Original Url")
.AddField(efb => efb.WithName(GetText("original_url"))
.WithValue($"<{arg}>"))
.AddField(efb => efb.WithName("Short Url")
.AddField(efb => efb.WithName(GetText("short_url"))
.WithValue($"<{shortened}>")))
.ConfigureAwait(false);
}
@ -273,7 +270,7 @@ namespace NadekoBot.Modules.Searches
var results = elems.Select<IElement, GoogleSearchResult?>(elem =>
{
var aTag = (elem.Children.FirstOrDefault().Children.FirstOrDefault() as IHtmlAnchorElement); // <h3> -> <a>
var aTag = (elem.Children.FirstOrDefault()?.Children.FirstOrDefault() as IHtmlAnchorElement); // <h3> -> <a>
var href = aTag?.Href;
var name = aTag?.TextContent;
if (href == null || name == null)
@ -289,34 +286,30 @@ namespace NadekoBot.Modules.Searches
var embed = new EmbedBuilder()
.WithOkColor()
.WithAuthor(eab => eab.WithName("Search For: " + terms.TrimTo(50))
.WithAuthor(eab => eab.WithName(GetText("search_for") + " " + terms.TrimTo(50))
.WithUrl(fullQueryLink)
.WithIconUrl("http://i.imgur.com/G46fm8J.png"))
.WithTitle(Context.User.Mention)
.WithTitle(Context.User.ToString())
.WithFooter(efb => efb.WithText(totalResults));
var desc = await Task.WhenAll(results.Select(async res =>
$"[{Format.Bold(res?.Title)}]({(await NadekoBot.Google.ShortenUrl(res?.Link))})\n{res?.Text}\n\n"))
.ConfigureAwait(false);
await Context.Channel.EmbedAsync(embed.WithDescription(String.Concat(desc))).ConfigureAwait(false);
await Context.Channel.EmbedAsync(embed.WithDescription(string.Concat(desc))).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task MagicTheGathering([Remainder] string name = null)
public async Task MagicTheGathering([Remainder] string name)
{
var arg = name;
if (string.IsNullOrWhiteSpace(arg))
{
await Context.Channel.SendErrorAsync("Please enter a card name to search for.").ConfigureAwait(false);
return;
}
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
string response = "";
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Clear();
response = await http.GetStringAsync($"https://api.deckbrew.com/mtg/cards?name={Uri.EscapeUriString(arg)}")
var response = await http.GetStringAsync($"https://api.deckbrew.com/mtg/cards?name={Uri.EscapeUriString(arg)}")
.ConfigureAwait(false);
try
{
@ -327,49 +320,45 @@ namespace NadekoBot.Modules.Searches
var storeUrl = await NadekoBot.Google.ShortenUrl(item["store_url"].ToString());
var cost = item["cost"].ToString();
var desc = item["text"].ToString();
var types = String.Join(",\n", item["types"].ToObject<string[]>());
var types = string.Join(",\n", item["types"].ToObject<string[]>());
var img = item["editions"][0]["image_url"].ToString();
var embed = new EmbedBuilder().WithOkColor()
.WithTitle(item["name"].ToString())
.WithDescription(desc)
.WithImageUrl(img)
.AddField(efb => efb.WithName("Store Url").WithValue(storeUrl).WithIsInline(true))
.AddField(efb => efb.WithName("Cost").WithValue(cost).WithIsInline(true))
.AddField(efb => efb.WithName("Types").WithValue(types).WithIsInline(true));
.AddField(efb => efb.WithName(GetText("store_url")).WithValue(storeUrl).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("cost")).WithValue(cost).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("types")).WithValue(types).WithIsInline(true));
//.AddField(efb => efb.WithName("Store Url").WithValue(await NadekoBot.Google.ShortenUrl(items[0]["store_url"].ToString())).WithIsInline(true));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync($"Error could not find the card '{arg}'.").ConfigureAwait(false);
await ReplyErrorLocalized("card_not_found").ConfigureAwait(false);
}
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Hearthstone([Remainder] string name = null)
public async Task Hearthstone([Remainder] string name)
{
var arg = name;
if (string.IsNullOrWhiteSpace(arg))
{
await Context.Channel.SendErrorAsync("Please enter a card name to search for.").ConfigureAwait(false);
return;
}
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey))
{
await Context.Channel.SendErrorAsync("Bot owner didn't specify MashapeApiKey. You can't use this functionality.").ConfigureAwait(false);
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
return;
}
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
string response = "";
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey);
response = await http.GetStringAsync($"https://omgvamp-hearthstone-v1.p.mashape.com/cards/search/{Uri.EscapeUriString(arg)}")
var response = await http.GetStringAsync($"https://omgvamp-hearthstone-v1.p.mashape.com/cards/search/{Uri.EscapeUriString(arg)}")
.ConfigureAwait(false);
try
{
@ -393,17 +382,17 @@ namespace NadekoBot.Modules.Searches
string msg = null;
if (items.Count > 4)
{
msg = "⚠ Found over 4 images. Showing random 4.";
msg = GetText("hs_over_x", 4);
}
var ms = new MemoryStream();
await Task.Run(() => images.AsEnumerable().Merge().SaveAsPng(ms));
await Task.Run(() => images.AsEnumerable().Merge().Save(ms));
ms.Position = 0;
await Context.Channel.SendFileAsync(ms, arg + ".png", msg).ConfigureAwait(false);
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync($"Error occured.").ConfigureAwait(false);
_log.Error(ex);
await ReplyErrorLocalized("error_occured").ConfigureAwait(false);
}
}
}
@ -413,23 +402,20 @@ namespace NadekoBot.Modules.Searches
{
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey))
{
await Context.Channel.SendErrorAsync("Bot owner didn't specify MashapeApiKey. You can't use this functionality.").ConfigureAwait(false);
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
return;
}
var arg = query;
if (string.IsNullOrWhiteSpace(arg))
{
await Context.Channel.SendErrorAsync("Please enter a sentence.").ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(query))
return;
}
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey);
http.DefaultRequestHeaders.Add("Accept", "text/plain");
var res = await http.GetStringAsync($"https://yoda.p.mashape.com/yoda?sentence={Uri.EscapeUriString(arg)}").ConfigureAwait(false);
var res = await http.GetStringAsync($"https://yoda.p.mashape.com/yoda?sentence={Uri.EscapeUriString(query)}").ConfigureAwait(false);
try
{
var embed = new EmbedBuilder()
@ -441,7 +427,7 @@ namespace NadekoBot.Modules.Searches
}
catch
{
await Context.Channel.SendErrorAsync("Failed to yodify your sentence.").ConfigureAwait(false);
await ReplyErrorLocalized("yodify_error").ConfigureAwait(false);
}
}
}
@ -451,22 +437,19 @@ namespace NadekoBot.Modules.Searches
{
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey))
{
await Context.Channel.SendErrorAsync("Bot owner didn't specify MashapeApiKey. You can't use this functionality.").ConfigureAwait(false);
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
return;
}
var arg = query;
if (string.IsNullOrWhiteSpace(arg))
{
await Context.Channel.SendErrorAsync("Please enter a search term.").ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(query))
return;
}
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("Accept", "application/json");
var res = await http.GetStringAsync($"http://api.urbandictionary.com/v0/define?term={Uri.EscapeUriString(arg)}").ConfigureAwait(false);
var res = await http.GetStringAsync($"http://api.urbandictionary.com/v0/define?term={Uri.EscapeUriString(query)}").ConfigureAwait(false);
try
{
var items = JObject.Parse(res);
@ -482,7 +465,7 @@ namespace NadekoBot.Modules.Searches
}
catch
{
await Context.Channel.SendErrorAsync("Failed finding a definition for that term.").ConfigureAwait(false);
await ReplyErrorLocalized("ud_error").ConfigureAwait(false);
}
}
}
@ -509,39 +492,36 @@ namespace NadekoBot.Modules.Searches
definition = ((JArray)JToken.Parse(sense.Definition.ToString())).First.ToString();
var embed = new EmbedBuilder().WithOkColor()
.WithTitle("Define: " + word)
.WithTitle(GetText("define") + " " + word)
.WithDescription(definition)
.WithFooter(efb => efb.WithText(sense.Gramatical_info?.type));
if (sense.Examples != null)
embed.AddField(efb => efb.WithName("Example").WithValue(sense.Examples.First().text));
embed.AddField(efb => efb.WithName(GetText("example")).WithValue(sense.Examples.First().text));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Hashtag([Remainder] string query = null)
public async Task Hashtag([Remainder] string query)
{
var arg = query;
if (string.IsNullOrWhiteSpace(arg))
{
await Context.Channel.SendErrorAsync("Please enter a search term.").ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(query))
return;
}
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey))
{
await Context.Channel.SendErrorAsync("Bot owner didn't specify MashapeApiKey. You can't use this functionality.").ConfigureAwait(false);
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
return;
}
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
var res = "";
string res;
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey);
res = await http.GetStringAsync($"https://tagdef.p.mashape.com/one.{Uri.EscapeUriString(arg)}.json").ConfigureAwait(false);
res = await http.GetStringAsync($"https://tagdef.p.mashape.com/one.{Uri.EscapeUriString(query)}.json").ConfigureAwait(false);
}
try
@ -560,7 +540,7 @@ namespace NadekoBot.Modules.Searches
}
catch
{
await Context.Channel.SendErrorAsync("Failed finding a definition for that tag.").ConfigureAwait(false);
await ReplyErrorLocalized("hashtag_error").ConfigureAwait(false);
}
}
@ -574,7 +554,7 @@ namespace NadekoBot.Modules.Searches
return;
var fact = JObject.Parse(response)["facts"][0].ToString();
await Context.Channel.SendConfirmAsync("🐈fact", fact).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("🐈" + GetText("catfact"), fact).ConfigureAwait(false);
}
}
@ -611,7 +591,7 @@ namespace NadekoBot.Modules.Searches
var result = await http.GetStringAsync("https://en.wikipedia.org//w/api.php?action=query&format=json&prop=info&redirects=1&formatversion=2&inprop=url&titles=" + Uri.EscapeDataString(query));
var data = JsonConvert.DeserializeObject<WikipediaApiModel>(result);
if (data.Query.Pages[0].Missing)
await Context.Channel.SendErrorAsync("That page could not be found.").ConfigureAwait(false);
await ReplyErrorLocalized("wiki_page_not_found").ConfigureAwait(false);
else
await Context.Channel.SendMessageAsync(data.Query.Pages[0].FullUrl).ConfigureAwait(false);
}
@ -625,15 +605,13 @@ namespace NadekoBot.Modules.Searches
return;
var img = new ImageSharp.Image(50, 50);
img.BackgroundColor(new ImageSharp.Color(color));
img.ApplyProcessor(new BackgroundColorProcessor<ImageSharp.Color>(ImageSharp.Color.FromHex(color)), img.Bounds);
await Context.Channel.SendFileAsync(img.ToStream(), $"{color}.png").ConfigureAwait(false); ;
await Context.Channel.SendFileAsync(img.ToStream(), $"{color}.png").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Videocall([Remainder] params IUser[] users)
{
try
public async Task Videocall(params IUser[] users)
{
var allUsrs = users.Append(Context.User);
var allUsrsArray = allUsrs.ToArray();
@ -641,12 +619,7 @@ namespace NadekoBot.Modules.Searches
str += new NadekoRandom().Next();
foreach (var usr in allUsrsArray)
{
await (await (usr as IGuildUser).CreateDMChannelAsync()).SendConfirmAsync(str).ConfigureAwait(false);
}
}
catch (Exception ex)
{
_log.Error(ex);
await (await usr.CreateDMChannelAsync()).SendConfirmAsync(str).ConfigureAwait(false);
}
}
@ -666,11 +639,11 @@ namespace NadekoBot.Modules.Searches
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Wikia(string target, [Remainder] string query = null)
public async Task Wikia(string target, [Remainder] string query)
{
if (string.IsNullOrWhiteSpace(target) || string.IsNullOrWhiteSpace(query))
{
await Context.Channel.SendErrorAsync("Please enter a target wikia, followed by search query.").ConfigureAwait(false);
await ReplyErrorLocalized("wikia_input_error").ConfigureAwait(false);
return;
}
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
@ -682,90 +655,87 @@ namespace NadekoBot.Modules.Searches
var res = await http.GetStringAsync($"http://www.{Uri.EscapeUriString(target)}.wikia.com/api/v1/Search/List?query={Uri.EscapeUriString(query)}&limit=25&minArticleQuality=10&batch=1&namespaces=0%2C14").ConfigureAwait(false);
var items = JObject.Parse(res);
var found = items["items"][0];
var response = $@"`Title:` {found["title"].ToString()}
`Quality:` {found["quality"]}
`URL:` {await NadekoBot.Google.ShortenUrl(found["url"].ToString()).ConfigureAwait(false)}";
var response = $@"`{GetText("title")}` {found["title"]}
`{GetText("quality")}` {found["quality"]}
`{GetText("url")}:` {await NadekoBot.Google.ShortenUrl(found["url"].ToString()).ConfigureAwait(false)}";
await Context.Channel.SendMessageAsync(response).ConfigureAwait(false);
}
catch
{
await Context.Channel.SendErrorAsync($"Failed finding `{query}`.").ConfigureAwait(false);
await ReplyErrorLocalized("wikia_error").ConfigureAwait(false);
}
}
}
[NadekoCommand, Usage, Description, Aliases]
public async Task MCPing([Remainder] string query = null)
{
var arg = query;
if (string.IsNullOrWhiteSpace(arg))
{
await Context.Channel.SendErrorAsync("💢 Please enter a `ip:port`.").ConfigureAwait(false);
return;
}
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Clear();
string ip = arg.Split(':')[0];
string port = arg.Split(':')[1];
var res = await http.GetStringAsync($"https://api.minetools.eu/ping/{Uri.EscapeUriString(ip)}/{Uri.EscapeUriString(port)}").ConfigureAwait(false);
try
{
var items = JObject.Parse(res);
var sb = new StringBuilder();
int ping = (int)Math.Ceiling(Double.Parse(items["latency"].ToString()));
sb.AppendLine($"`Server:` {arg}");
sb.AppendLine($"`Version:` {items["version"]["name"].ToString()} / Protocol {items["version"]["protocol"].ToString()}");
sb.AppendLine($"`Description:` {items["description"].ToString()}");
sb.AppendLine($"`Online Players:` {items["players"]["online"].ToString()}/{items["players"]["max"].ToString()}");
sb.Append($"`Latency:` {ping}");
await Context.Channel.SendMessageAsync(sb.ToString());
}
catch
{
await Context.Channel.SendErrorAsync($"Failed finding `{arg}`.").ConfigureAwait(false);
}
}
}
//[NadekoCommand, Usage, Description, Aliases]
//public async Task MCPing([Remainder] string query2 = null)
//{
// var query = query2;
// if (string.IsNullOrWhiteSpace(query))
// return;
// await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
// using (var http = new HttpClient())
// {
// http.DefaultRequestHeaders.Clear();
// var ip = query.Split(':')[0];
// var port = query.Split(':')[1];
// var res = await http.GetStringAsync($"https://api.minetools.eu/ping/{Uri.EscapeUriString(ip)}/{Uri.EscapeUriString(port)}").ConfigureAwait(false);
// try
// {
// var items = JObject.Parse(res);
// var sb = new StringBuilder();
// var ping = (int)Math.Ceiling(double.Parse(items["latency"].ToString()));
// sb.AppendLine($"`Server:` {query}");
// sb.AppendLine($"`Version:` {items["version"]["name"]} / Protocol {items["version"]["protocol"]}");
// sb.AppendLine($"`Description:` {items["description"]}");
// sb.AppendLine($"`Online Players:` {items["players"]["online"]}/{items["players"]["max"]}");
// sb.Append($"`Latency:` {ping}");
// await Context.Channel.SendMessageAsync(sb.ToString());
// }
// catch
// {
// await Context.Channel.SendErrorAsync($"Failed finding `{query}`.").ConfigureAwait(false);
// }
// }
//}
[NadekoCommand, Usage, Description, Aliases]
public async Task MCQ([Remainder] string query = null)
{
var arg = query;
if (string.IsNullOrWhiteSpace(arg))
{
await Context.Channel.SendErrorAsync("Please enter `ip:port`.").ConfigureAwait(false);
return;
}
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Clear();
try
{
string ip = arg.Split(':')[0];
string port = arg.Split(':')[1];
var res = await http.GetStringAsync($"https://api.minetools.eu/query/{Uri.EscapeUriString(ip)}/{Uri.EscapeUriString(port)}").ConfigureAwait(false);
var items = JObject.Parse(res);
var sb = new StringBuilder();
sb.AppendLine($"`Server:` {arg.ToString()} 〘Status: {items["status"]}〙");
sb.AppendLine($"`Player List (First 5):`");
foreach (var item in items["Playerlist"].Take(5))
{
sb.AppendLine($":rosette: {item}");
}
sb.AppendLine($"`Online Players:` {items["Players"]} / {items["MaxPlayers"]}");
sb.AppendLine($"`Plugins:` {items["Plugins"]}");
sb.Append($"`Version:` {items["Version"]}");
await Context.Channel.SendMessageAsync(sb.ToString());
}
catch
{
await Context.Channel.SendErrorAsync($"Failed finding server `{arg}`.").ConfigureAwait(false);
}
}
}
//[NadekoCommand, Usage, Description, Aliases]
//public async Task MCQ([Remainder] string query = null)
//{
// var arg = query;
// if (string.IsNullOrWhiteSpace(arg))
// {
// await Context.Channel.SendErrorAsync("Please enter `ip:port`.").ConfigureAwait(false);
// return;
// }
// await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
// using (var http = new HttpClient())
// {
// http.DefaultRequestHeaders.Clear();
// try
// {
// var ip = arg.Split(':')[0];
// var port = arg.Split(':')[1];
// var res = await http.GetStringAsync($"https://api.minetools.eu/query/{Uri.EscapeUriString(ip)}/{Uri.EscapeUriString(port)}").ConfigureAwait(false);
// var items = JObject.Parse(res);
// var sb = new StringBuilder();
// sb.AppendLine($"`Server:` {arg} 〘Status: {items["status"]}〙");
// sb.AppendLine("`Player List (First 5):`");
// foreach (var item in items["Playerlist"].Take(5))
// {
// sb.AppendLine($":rosette: {item}");
// }
// sb.AppendLine($"`Online Players:` {items["Players"]} / {items["MaxPlayers"]}");
// sb.AppendLine($"`Plugins:` {items["Plugins"]}");
// sb.Append($"`Version:` {items["Version"]}");
// await Context.Channel.SendMessageAsync(sb.ToString());
// }
// catch
// {
// await Context.Channel.SendErrorAsync($"Failed finding server `{arg}`.").ConfigureAwait(false);
// }
// }
//}
public enum DapiSearchType
{
@ -776,7 +746,7 @@ namespace NadekoBot.Modules.Searches
Yandere
}
public static async Task InternalDapiCommand(IUserMessage umsg, string tag, DapiSearchType type)
public async Task InternalDapiCommand(IUserMessage umsg, string tag, DapiSearchType type)
{
var channel = umsg.Channel;
@ -785,7 +755,7 @@ namespace NadekoBot.Modules.Searches
var url = await InternalDapiSearch(tag, type).ConfigureAwait(false);
if (url == null)
await channel.SendErrorAsync(umsg.Author.Mention + " No results.");
await channel.SendErrorAsync(umsg.Author.Mention + " " + GetText("no_results"));
else
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription(umsg.Author.Mention + " " + tag)
@ -796,7 +766,7 @@ namespace NadekoBot.Modules.Searches
public static async Task<string> InternalDapiSearch(string tag, DapiSearchType type)
{
tag = tag?.Replace(" ", "_");
string website = "";
var website = "";
switch (type)
{
case DapiSearchType.Safebooru:
@ -841,10 +811,10 @@ namespace NadekoBot.Modules.Searches
return null;
}
}
public static async Task<bool> ValidateQuery(IMessageChannel ch, string query)
public async Task<bool> ValidateQuery(IMessageChannel ch, string query)
{
if (!string.IsNullOrEmpty(query.Trim())) return true;
await ch.SendErrorAsync("Please specify search parameters.").ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(query)) return true;
await ch.SendErrorAsync(GetText("specify_search_params")).ConfigureAwait(false);
return false;
}
}

View File

@ -12,7 +12,7 @@ namespace NadekoBot.Modules.Utility
public partial class Utility
{
[Group]
public class CalcCommands : ModuleBase
public class CalcCommands : NadekoSubmodule
{
[NadekoCommand, Usage, Description, Aliases]
public async Task Calculate([Remainder] string expression)
@ -21,9 +21,9 @@ namespace NadekoBot.Modules.Utility
expr.EvaluateParameter += Expr_EvaluateParameter;
var result = expr.Evaluate();
if (expr.Error == null)
await Context.Channel.SendConfirmAsync("Result", $"{result}");
await Context.Channel.SendConfirmAsync("⚙ " + GetText("result"), result.ToString());
else
await Context.Channel.SendErrorAsync($"⚙ Error", expr.Error);
await Context.Channel.SendErrorAsync("⚙ " + GetText("error"), expr.Error);
}
private static void Expr_EvaluateParameter(string name, NCalc.ParameterArgs args)
@ -42,29 +42,26 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases]
public async Task CalcOps()
{
var selection = typeof(Math).GetTypeInfo().GetMethods().Except(typeof(object).GetTypeInfo().GetMethods()).Distinct(new MethodInfoEqualityComparer()).Select(x =>
var selection = typeof(Math).GetTypeInfo()
.GetMethods()
.Distinct(new MethodInfoEqualityComparer())
.Select(x => x.Name)
.Except(new[]
{
return x.Name;
})
.Except(new[] { "ToString",
"ToString",
"Equals",
"GetHashCode",
"GetType"});
await Context.Channel.SendConfirmAsync("Available functions in calc", string.Join(", ", selection));
"GetType"
});
await Context.Channel.SendConfirmAsync(GetText("utility_calcops", Prefix), string.Join(", ", selection));
}
}
class MethodInfoEqualityComparer : IEqualityComparer<MethodInfo>
private class MethodInfoEqualityComparer : IEqualityComparer<MethodInfo>
{
public bool Equals(MethodInfo x, MethodInfo y) => x.Name == y.Name;
public int GetHashCode(MethodInfo obj) => obj.Name.GetHashCode();
}
class ExpressionContext
{
public double Pi { get; set; } = Math.PI;
}
}
}

View File

@ -3,8 +3,6 @@ using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
@ -14,12 +12,11 @@ namespace NadekoBot.Modules.Utility
public partial class Utility
{
[Group]
public class CrossServerTextChannel : ModuleBase
public class CrossServerTextChannel : NadekoSubmodule
{
static CrossServerTextChannel()
{
_log = LogManager.GetCurrentClassLogger();
NadekoBot.Client.MessageReceived += async (imsg) =>
NadekoBot.Client.MessageReceived += async imsg =>
{
try
{
@ -39,21 +36,30 @@ namespace NadekoBot.Modules.Utility
continue;
foreach (var chan in set.Except(new[] {channel}))
{
try { await chan.SendMessageAsync(GetText(channel.Guild, channel, (IGuildUser)msg.Author, msg)).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
try
{
await chan.SendMessageAsync(GetMessage(channel, (IGuildUser) msg.Author,
msg)).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
catch (Exception ex) {
_log.Warn(ex);
}
catch
{
// ignored
}
};
}
private static string GetText(IGuild server, ITextChannel channel, IGuildUser user, IUserMessage message) =>
$"**{server.Name} | {channel.Name}** `{user.Username}`: " + message.Content.SanitizeMentions();
private static string GetMessage(ITextChannel channel, IGuildUser user, IUserMessage message) =>
$"**{channel.Guild.Name} | {channel.Name}** `{user.Username}`: " + message.Content.SanitizeMentions();
public static readonly ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>> Subscribers = new ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>>();
private static Logger _log { get; }
public static readonly ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>> Subscribers =
new ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>>();
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
@ -65,7 +71,8 @@ namespace NadekoBot.Modules.Utility
if (Subscribers.TryAdd(token, set))
{
set.Add((ITextChannel) Context.Channel);
await ((IGuildUser)Context.User).SendConfirmAsync("This is your CSC token", token.ToString()).ConfigureAwait(false);
await ((IGuildUser) Context.User).SendConfirmAsync(GetText("csc_token"), token.ToString())
.ConfigureAwait(false);
}
}
@ -78,7 +85,7 @@ namespace NadekoBot.Modules.Utility
if (!Subscribers.TryGetValue(token, out set))
return;
set.Add((ITextChannel) Context.Channel);
await Context.Channel.SendConfirmAsync("Joined cross server channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("csc_join").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -90,7 +97,7 @@ namespace NadekoBot.Modules.Utility
{
subscriber.Value.TryRemove((ITextChannel) Context.Channel);
}
await Context.Channel.SendMessageAsync("Left cross server channel.").ConfigureAwait(false);
await ReplyConfirmLocalized("csc_leave").ConfigureAwait(false);
}
}
}

View File

@ -12,7 +12,7 @@ namespace NadekoBot.Modules.Utility
public partial class Utility
{
[Group]
public class InfoCommands : ModuleBase
public class InfoCommands : NadekoSubmodule
{
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
@ -24,7 +24,7 @@ namespace NadekoBot.Modules.Utility
if (string.IsNullOrWhiteSpace(guildName))
guild = channel.Guild;
else
guild = NadekoBot.Client.GetGuilds().Where(g => g.Name.ToUpperInvariant() == guildName.ToUpperInvariant()).FirstOrDefault();
guild = NadekoBot.Client.GetGuilds().FirstOrDefault(g => g.Name.ToUpperInvariant() == guildName.ToUpperInvariant());
if (guild == null)
return;
var ownername = await guild.GetUserAsync(guild.OwnerId);
@ -37,22 +37,22 @@ namespace NadekoBot.Modules.Utility
if (string.IsNullOrWhiteSpace(features))
features = "-";
var embed = new EmbedBuilder()
.WithAuthor(eab => eab.WithName("Server Info"))
.WithAuthor(eab => eab.WithName(GetText("server_info")))
.WithTitle(guild.Name)
.AddField(fb => fb.WithName("**ID**").WithValue(guild.Id.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Owner**").WithValue(ownername.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Members**").WithValue(users.Count.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Text Channels**").WithValue(textchn.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Voice Channels**").WithValue(voicechn.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Created At**").WithValue($"{createdAt:dd.MM.yyyy HH:mm}").WithIsInline(true))
.AddField(fb => fb.WithName("**Region**").WithValue(guild.VoiceRegionId.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Roles**").WithValue((guild.Roles.Count - 1).ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Features**").WithValue(features).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("id")).WithValue(guild.Id.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("owner")).WithValue(ownername.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("members")).WithValue(users.Count.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("text_channels")).WithValue(textchn.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("voice_channels")).WithValue(voicechn.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("created_at")).WithValue($"{createdAt:dd.MM.yyyy HH:mm}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("region")).WithValue(guild.VoiceRegionId.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("roles")).WithValue((guild.Roles.Count - 1).ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("features")).WithValue(features).WithIsInline(true))
.WithImageUrl(guild.IconUrl)
.WithColor(NadekoBot.OkColor);
if (guild.Emojis.Any())
{
embed.AddField(fb => fb.WithName($"**Custom Emojis ({guild.Emojis.Count})**").WithValue(string.Join(" ", guild.Emojis.Shuffle().Take(25).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>"))));
embed.AddField(fb => fb.WithName(GetText("custom_emojis") + $"({guild.Emojis.Count})").WithValue(string.Join(" ", guild.Emojis.Shuffle().Take(20).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>"))));
}
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
@ -69,9 +69,9 @@ namespace NadekoBot.Modules.Utility
var embed = new EmbedBuilder()
.WithTitle(ch.Name)
.WithDescription(ch.Topic?.SanitizeMentions())
.AddField(fb => fb.WithName("**ID**").WithValue(ch.Id.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Created At**").WithValue($"{createdAt:dd.MM.yyyy HH:mm}").WithIsInline(true))
.AddField(fb => fb.WithName("**Users**").WithValue(usercount.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("id")).WithValue(ch.Id.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("created_at")).WithValue($"{createdAt:dd.MM.yyyy HH:mm}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("users")).WithValue(usercount.ToString()).WithIsInline(true))
.WithColor(NadekoBot.OkColor);
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
@ -86,15 +86,15 @@ namespace NadekoBot.Modules.Utility
return;
var embed = new EmbedBuilder()
.AddField(fb => fb.WithName("**Name**").WithValue($"**{user.Username}**#{user.Discriminator}").WithIsInline(true));
.AddField(fb => fb.WithName(GetText("name")).WithValue($"**{user.Username}**#{user.Discriminator}").WithIsInline(true));
if (!string.IsNullOrWhiteSpace(user.Nickname))
{
embed.AddField(fb => fb.WithName("**Nickname**").WithValue(user.Nickname).WithIsInline(true));
embed.AddField(fb => fb.WithName(GetText("nickname")).WithValue(user.Nickname).WithIsInline(true));
}
embed.AddField(fb => fb.WithName("**ID**").WithValue(user.Id.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName("**Joined Server**").WithValue($"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm") ?? "unavail."}").WithIsInline(true))
.AddField(fb => fb.WithName("**Joined Discord**").WithValue($"{user.CreatedAt:dd.MM.yyyy HH:mm}").WithIsInline(true))
.AddField(fb => fb.WithName("**Roles**").WithValue($"**({user.RoleIds.Count - 1})** - {string.Join("\n", user.GetRoles().Take(10).Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true))
embed.AddField(fb => fb.WithName(GetText("id")).WithValue(user.Id.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("joined_server")).WithValue($"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm") ?? "?"}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("joined_discord")).WithValue($"{user.CreatedAt:dd.MM.yyyy HH:mm}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("roles")).WithValue($"**({user.RoleIds.Count - 1})** - {string.Join("\n", user.GetRoles().Take(10).Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true))
.WithColor(NadekoBot.OkColor);
if (user.AvatarId != null)
@ -119,12 +119,17 @@ namespace NadekoBot.Modules.Utility
StringBuilder str = new StringBuilder();
foreach (var kvp in NadekoBot.CommandHandler.UserMessagesSent.OrderByDescending(kvp => kvp.Value).Skip(page*activityPerPage).Take(activityPerPage))
{
str.AppendLine($"`{++startCount}.` **{kvp.Key}** [{kvp.Value/NadekoBot.Stats.GetUptime().TotalSeconds:F2}/s] - {kvp.Value} total");
str.AppendLine(GetText("activity_line",
++startCount,
Format.Bold(kvp.Key.ToString()),
kvp.Value / NadekoBot.Stats.GetUptime().TotalSeconds, kvp.Value));
}
await Context.Channel.EmbedAsync(new EmbedBuilder().WithTitle($"Activity Page #{page}")
await Context.Channel.EmbedAsync(new EmbedBuilder()
.WithTitle(GetText("activity_page", page))
.WithOkColor()
.WithFooter(efb => efb.WithText($"{NadekoBot.CommandHandler.UserMessagesSent.Count} users total."))
.WithFooter(efb => efb.WithText(GetText("activity_users_total",
NadekoBot.CommandHandler.UserMessagesSent.Count)))
.WithDescription(str.ToString()));
}
}

View File

@ -5,44 +5,46 @@ using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Discord.WebSocket;
namespace NadekoBot.Modules.Utility
{
public partial class Utility
{
[Group]
public class RepeatCommands : ModuleBase
public class RepeatCommands : NadekoSubmodule
{
//guildid/RepeatRunners
public static ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>> repeaters { get; }
public static ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>> Repeaters { get; set; }
private static bool _ready;
public class RepeatRunner
{
private Logger _log { get; }
private readonly Logger _log;
private CancellationTokenSource source { get; set; }
private CancellationToken token { get; set; }
public Repeater Repeater { get; }
public ITextChannel Channel { get; }
public SocketGuild Guild { get; }
public ITextChannel Channel { get; private set; }
public RepeatRunner(Repeater repeater, ITextChannel channel = null)
{
_log = LogManager.GetCurrentClassLogger();
this.Repeater = repeater;
this.Channel = channel ?? NadekoBot.Client.GetGuild(repeater.GuildId)?.GetTextChannel(repeater.ChannelId);
if (Channel == null)
return;
Repeater = repeater;
Channel = channel;
Guild = NadekoBot.Client.GetGuild(repeater.GuildId);
if(Guild!=null)
Task.Run(Run);
}
@ -64,9 +66,20 @@ namespace NadekoBot.Modules.Utility
// continue;
if (oldMsg != null)
try { await oldMsg.DeleteAsync(); } catch { }
try
{
await oldMsg.DeleteAsync();
}
catch
{
// ignored
}
try
{
if (Channel == null)
Channel = Guild.GetTextChannel(Repeater.ChannelId);
if (Channel != null)
oldMsg = await Channel.SendMessageAsync(toSend).ConfigureAwait(false);
}
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
@ -85,7 +98,9 @@ namespace NadekoBot.Modules.Utility
}
}
}
catch (OperationCanceledException) { }
catch (OperationCanceledException)
{
}
}
public void Reset()
@ -101,22 +116,23 @@ namespace NadekoBot.Modules.Utility
public override string ToString()
{
return $"{this.Channel.Mention} | {(int)this.Repeater.Interval.TotalHours}:{this.Repeater.Interval:mm} | {this.Repeater.Message.TrimTo(33)}";
return
$"{Channel.Mention} | {(int) Repeater.Interval.TotalHours}:{Repeater.Interval:mm} | {Repeater.Message.TrimTo(33)}";
}
}
static RepeatCommands()
{
var _log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
repeaters = new ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>>(NadekoBot.AllGuildConfigs
var _ = Task.Run(async () =>
{
await Task.Delay(5000).ConfigureAwait(false);
Repeaters = new ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>>(NadekoBot.AllGuildConfigs
.ToDictionary(gc => gc.GuildId,
gc => new ConcurrentQueue<RepeatRunner>(gc.GuildRepeaters.Select(gr => new RepeatRunner(gr))
.Where(gr => gr.Channel != null))));
sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
gc => new ConcurrentQueue<RepeatRunner>(gc.GuildRepeaters
.Select(gr => new RepeatRunner(gr))
.Where(x => x.Guild != null))));
_ready = true;
});
}
[NadekoCommand, Usage, Description, Aliases]
@ -124,11 +140,13 @@ namespace NadekoBot.Modules.Utility
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task RepeatInvoke(int index)
{
if (!_ready)
return;
index -= 1;
ConcurrentQueue<RepeatRunner> rep;
if (!repeaters.TryGetValue(Context.Guild.Id, out rep))
if (!Repeaters.TryGetValue(Context.Guild.Id, out rep))
{
await Context.Channel.SendErrorAsync(" **No repeating message found on this server.**").ConfigureAwait(false);
await ReplyErrorLocalized("repeat_invoke_none").ConfigureAwait(false);
return;
}
@ -136,7 +154,7 @@ namespace NadekoBot.Modules.Utility
if (index >= repList.Count)
{
await Context.Channel.SendErrorAsync("Index out of range.").ConfigureAwait(false);
await ReplyErrorLocalized("index_out_of_range").ConfigureAwait(false);
return;
}
var repeater = repList[index].Repeater;
@ -151,19 +169,21 @@ namespace NadekoBot.Modules.Utility
[Priority(0)]
public async Task RepeatRemove(int index)
{
if (!_ready)
return;
if (index < 1)
return;
index -= 1;
ConcurrentQueue<RepeatRunner> rep;
if (!repeaters.TryGetValue(Context.Guild.Id, out rep))
if (!Repeaters.TryGetValue(Context.Guild.Id, out rep))
return;
var repeaterList = rep.ToList();
if (index >= repeaterList.Count)
{
await Context.Channel.SendErrorAsync("Index out of range.").ConfigureAwait(false);
await ReplyErrorLocalized("index_out_of_range").ConfigureAwait(false);
return;
}
@ -179,8 +199,9 @@ namespace NadekoBot.Modules.Utility
await uow.CompleteAsync().ConfigureAwait(false);
}
if (repeaters.TryUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(repeaterList), rep))
await Context.Channel.SendConfirmAsync("Message Repeater",$"#{index+1} stopped.\n\n{repeater.ToString()}").ConfigureAwait(false);
if (Repeaters.TryUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(repeaterList), rep))
await Context.Channel.SendConfirmAsync(GetText("message_repeater"),
GetText("repeater_stopped", index + 1) + $"\n\n{repeater}").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -189,6 +210,8 @@ namespace NadekoBot.Modules.Utility
[Priority(1)]
public async Task Repeat(int minutes, [Remainder] string message)
{
if (!_ready)
return;
if (minutes < 1 || minutes > 10080)
return;
@ -216,13 +239,18 @@ namespace NadekoBot.Modules.Utility
var rep = new RepeatRunner(toAdd, (ITextChannel) Context.Channel);
repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(new[] { rep }), (key, old) =>
Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(new[] {rep}), (key, old) =>
{
old.Enqueue(rep);
return old;
});
await Context.Channel.SendConfirmAsync($"🔁 Repeating **\"{rep.Repeater.Message}\"** every `{rep.Repeater.Interval.Days} day(s), {rep.Repeater.Interval.Hours} hour(s) and {rep.Repeater.Interval.Minutes} minute(s)`.").ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(
"🔁 " + GetText("repeater",
Format.Bold(rep.Repeater.Message),
Format.Bold(rep.Repeater.Interval.Days.ToString()),
Format.Bold(rep.Repeater.Interval.Hours.ToString()),
Format.Bold(rep.Repeater.Interval.Minutes.ToString()))).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -230,26 +258,32 @@ namespace NadekoBot.Modules.Utility
[RequireUserPermission(GuildPermission.ManageMessages)]
public async Task RepeatList()
{
if (!_ready)
return;
ConcurrentQueue<RepeatRunner> repRunners;
if (!repeaters.TryGetValue(Context.Guild.Id, out repRunners))
if (!Repeaters.TryGetValue(Context.Guild.Id, out repRunners))
{
await Context.Channel.SendConfirmAsync("No repeaters running on this server.").ConfigureAwait(false);
await ReplyConfirmLocalized("repeaters_none").ConfigureAwait(false);
return;
}
var replist = repRunners.ToList();
var sb = new StringBuilder();
for (int i = 0; i < replist.Count; i++)
for (var i = 0; i < replist.Count; i++)
{
var rep = replist[i];
sb.AppendLine($"`{i + 1}.` {rep.ToString()}");
sb.AppendLine($"`{i + 1}.` {rep}");
}
var desc = sb.ToString();
if (string.IsNullOrWhiteSpace(desc))
desc = GetText("no_active_repeaters");
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle("List Of Repeaters")
.WithDescription(sb.ToString()))
.WithTitle(GetText("list_of_repeaters"))
.WithDescription(desc))
.ConfigureAwait(false);
}
}

View File

@ -8,13 +8,14 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.DataStructures;
namespace NadekoBot.Modules.Utility
{
public partial class Utility
{
[Group]
public class QuoteCommands : ModuleBase
public class QuoteCommands : NadekoSubmodule
{
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
@ -32,10 +33,11 @@ namespace NadekoBot.Modules.Utility
}
if (quotes.Any())
await Context.Channel.SendConfirmAsync($"💬 **Page {page + 1} of quotes:**\n```xl\n" + String.Join("\n", quotes.Select((q) => $"{q.Keyword,-20} by {q.AuthorName}")) + "\n```")
await Context.Channel.SendConfirmAsync(GetText("quotes_page", page + 1),
string.Join("\n", quotes.Select(q => $"{q.Keyword,-20} by {q.AuthorName}")))
.ConfigureAwait(false);
else
await Context.Channel.SendErrorAsync("No quotes on this page.").ConfigureAwait(false);
await ReplyErrorLocalized("quotes_page_none").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -56,9 +58,41 @@ namespace NadekoBot.Modules.Utility
if (quote == null)
return;
CREmbed crembed;
if (CREmbed.TryParse(quote.Text, out crembed))
{
try { await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "").ConfigureAwait(false); }
catch (Exception ex)
{
_log.Warn("Sending CREmbed failed");
_log.Warn(ex);
}
return;
}
await Context.Channel.SendMessageAsync("📣 " + quote.Text.SanitizeMentions());
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task SearchQuote(string keyword, [Remainder] string text)
{
if (string.IsNullOrWhiteSpace(keyword) || string.IsNullOrWhiteSpace(text))
return;
keyword = keyword.ToUpperInvariant();
Quote keywordquote;
using (var uow = DbHandler.UnitOfWork())
{
keywordquote = await uow.Quotes.SearchQuoteKeywordTextAsync(Context.Guild.Id, keyword, text).ConfigureAwait(false);
}
if (keywordquote == null)
return;
await Context.Channel.SendMessageAsync("💬 " + keyword + ": " + keywordquote.Text.SanitizeMentions());
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task AddQuote(string keyword, [Remainder] string text)
@ -80,7 +114,7 @@ namespace NadekoBot.Modules.Utility
});
await uow.CompleteAsync().ConfigureAwait(false);
}
await Context.Channel.SendConfirmAsync("✅ Quote added.").ConfigureAwait(false);
await ReplyConfirmLocalized("quote_added").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -102,7 +136,7 @@ namespace NadekoBot.Modules.Utility
if (qs == null || !qs.Any())
{
sucess = false;
response = "No quotes found which you can remove.";
response = GetText("quotes_remove_none");
}
else
{
@ -111,7 +145,7 @@ namespace NadekoBot.Modules.Utility
uow.Quotes.Remove(q);
await uow.CompleteAsync().ConfigureAwait(false);
sucess = true;
response = "🗑 **Deleted a random quote.**";
response = GetText("quote_deleted");
}
}
if(sucess)
@ -139,7 +173,7 @@ namespace NadekoBot.Modules.Utility
await uow.CompleteAsync();
}
await Context.Channel.SendConfirmAsync($"🗑 **Deleted all quotes** with **{keyword}** keyword.");
await ReplyConfirmLocalized("quotes_deleted", Format.Bold(keyword)).ConfigureAwait(false);
}
}
}

View File

@ -17,21 +17,21 @@ namespace NadekoBot.Modules.Utility
public partial class Utility
{
[Group]
public class RemindCommands : ModuleBase
public class RemindCommands : NadekoSubmodule
{
Regex regex = new Regex(@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d)w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,2})h)?(?:(?<minutes>\d{1,2})m)?$",
readonly Regex _regex = new Regex(@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d)w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,2})h)?(?:(?<minutes>\d{1,2})m)?$",
RegexOptions.Compiled | RegexOptions.Multiline);
private static string RemindMessageFormat { get; }
private static string remindMessageFormat { get; }
private static IDictionary<string, Func<Reminder, string>> replacements = new Dictionary<string, Func<Reminder, string>>
private static readonly IDictionary<string, Func<Reminder, string>> _replacements = new Dictionary<string, Func<Reminder, string>>
{
{ "%message%" , (r) => r.Message },
{ "%user%", (r) => $"<@!{r.UserId}>" },
{ "%target%", (r) => r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>"}
};
private static Logger _log { get; }
private new static readonly Logger _log;
static RemindCommands()
{
@ -41,7 +41,7 @@ namespace NadekoBot.Modules.Utility
{
reminders = uow.Reminders.GetAll().ToList();
}
RemindMessageFormat = NadekoBot.BotConfig.RemindMessageFormat;
remindMessageFormat = NadekoBot.BotConfig.RemindMessageFormat;
foreach (var r in reminders)
{
@ -58,10 +58,10 @@ namespace NadekoBot.Modules.Utility
if (time.TotalMilliseconds > int.MaxValue)
return;
await Task.Delay(time);
await Task.Delay(time).ConfigureAwait(false);
try
{
IMessageChannel ch = null;
IMessageChannel ch;
if (r.IsPrivate)
{
ch = await NadekoBot.Client.GetDMChannelAsync(r.ChannelId).ConfigureAwait(false);
@ -74,7 +74,7 @@ namespace NadekoBot.Modules.Utility
return;
await ch.SendMessageAsync(
replacements.Aggregate(RemindMessageFormat,
_replacements.Aggregate(remindMessageFormat,
(cur, replace) => cur.Replace(replace.Key, replace.Value(r)))
.SanitizeMentions()
).ConfigureAwait(false); //it works trust me
@ -119,27 +119,21 @@ namespace NadekoBot.Modules.Utility
{
var channel = (ITextChannel)Context.Channel;
if (ch == null)
{
await channel.SendErrorAsync($"{Context.User.Mention} Something went wrong (channel cannot be found) ;(").ConfigureAwait(false);
return;
}
var m = regex.Match(timeStr);
var m = _regex.Match(timeStr);
if (m.Length == 0)
{
await channel.SendErrorAsync("Not a valid time format. Type `-h .remind`").ConfigureAwait(false);
await ReplyErrorLocalized("remind_invalid_format").ConfigureAwait(false);
return;
}
string output = "";
var namesAndValues = new Dictionary<string, int>();
foreach (var groupName in regex.GetGroupNames())
foreach (var groupName in _regex.GetGroupNames())
{
if (groupName == "0") continue;
int value = 0;
int value;
int.TryParse(m.Groups[groupName].Value, out value);
if (string.IsNullOrEmpty(m.Groups[groupName].Value))
@ -147,7 +141,7 @@ namespace NadekoBot.Modules.Utility
namesAndValues[groupName] = 0;
continue;
}
else if (value < 1 ||
if (value < 1 ||
(groupName == "months" && value > 1) ||
(groupName == "weeks" && value > 4) ||
(groupName == "days" && value >= 7) ||
@ -157,7 +151,6 @@ namespace NadekoBot.Modules.Utility
await channel.SendErrorAsync($"Invalid {groupName} value.").ConfigureAwait(false);
return;
}
else
namesAndValues[groupName] = value;
output += m.Groups[groupName].Value + " " + groupName + " ";
}
@ -184,17 +177,26 @@ namespace NadekoBot.Modules.Utility
await uow.CompleteAsync();
}
try { await channel.SendConfirmAsync($"⏰ I will remind **\"{(ch is ITextChannel ? ((ITextChannel)ch).Name : Context.User.Username)}\"** to **\"{message.SanitizeMentions()}\"** in **{output}** `({time:d.M.yyyy.} at {time:HH:mm})`").ConfigureAwait(false); } catch { }
try
{
await channel.SendConfirmAsync(
"⏰ " + GetText("remind",
Format.Bold(ch is ITextChannel ? ((ITextChannel) ch).Name : Context.User.Username),
Format.Bold(message.SanitizeMentions()),
Format.Bold(output),
time, time)).ConfigureAwait(false);
}
catch
{
// ignored
}
await StartReminder(rem);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task RemindTemplate([Remainder] string arg)
{
var channel = (ITextChannel)Context.Channel;
if (string.IsNullOrWhiteSpace(arg))
return;
@ -203,7 +205,8 @@ namespace NadekoBot.Modules.Utility
uow.BotConfig.GetOrCreate().RemindMessageFormat = arg.Trim();
await uow.CompleteAsync().ConfigureAwait(false);
}
await channel.SendConfirmAsync("🆗 New remind template set.");
await ReplyConfirmLocalized("remind_template").ConfigureAwait(false);
}
}
}

View File

@ -9,7 +9,6 @@ using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
@ -21,12 +20,12 @@ namespace NadekoBot.Modules.Utility
public partial class Utility
{
[Group]
public class UnitConverterCommands : ModuleBase
public class UnitConverterCommands : NadekoSubmodule
{
public static List<ConvertUnit> Units { get; set; } = new List<ConvertUnit>();
private static Logger _log { get; }
private new static readonly Logger _log;
private static Timer _timer;
private static TimeSpan updateInterval = new TimeSpan(12, 0, 0);
private static readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0);
static UnitConverterCommands()
{
@ -55,7 +54,7 @@ namespace NadekoBot.Modules.Utility
_log.Warn("Could not load units: " + e.Message);
}
_timer = new Timer(async (obj) => await UpdateCurrency(), null, (int)updateInterval.TotalMilliseconds, (int)updateInterval.TotalMilliseconds);
_timer = new Timer(async (obj) => await UpdateCurrency(), null, _updateInterval, _updateInterval);
}
public static async Task UpdateCurrency()
@ -93,15 +92,34 @@ namespace NadekoBot.Modules.Utility
}
catch
{
_log.Warn("Failed updating currency.");
_log.Warn("Failed updating currency. Ignore this.");
}
}
//[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)]
//public async Task Aurorina(IGuildUser usr = null)
//{
// var rng = new NadekoRandom();
// var nums = Enumerable.Range(48, 10)
// .Concat(Enumerable.Range(65, 26))
// .Concat(Enumerable.Range(97, 26))
// .Concat(new[] {45, 46, 95})
// .ToArray();
// var token = String.Concat(new int[59]
// .Select(x => (char) nums[rng.Next(0, nums.Length)]));
// if (usr == null)
// await Context.Channel.SendConfirmAsync(token).ConfigureAwait(false);
// else
// await Context.Channel.SendConfirmAsync($"Token of user {usr} is `{token}`").ConfigureAwait(false);
//}
[NadekoCommand, Usage, Description, Aliases]
public async Task ConvertList()
{
var res = Units.GroupBy(x => x.UnitType)
.Aggregate(new EmbedBuilder().WithTitle("__Units which can be used by the converter__")
.Aggregate(new EmbedBuilder().WithTitle(GetText("convertlist"))
.WithColor(NadekoBot.OkColor),
(embed, g) => embed.AddField(efb =>
efb.WithName(g.Key.ToTitleCase())
@ -116,12 +134,12 @@ namespace NadekoBot.Modules.Utility
var targetUnit = Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(target.ToLowerInvariant()));
if (originUnit == null || targetUnit == null)
{
await Context.Channel.SendErrorAsync(string.Format("Cannot convert {0} to {1}: units not found", origin, target));
await ReplyErrorLocalized("convert_not_found", Format.Bold(origin), Format.Bold(target)).ConfigureAwait(false);
return;
}
if (originUnit.UnitType != targetUnit.UnitType)
{
await Context.Channel.SendErrorAsync(string.Format("Cannot convert {0} to {1}: types of unit are not equal", originUnit.Triggers.First(), targetUnit.Triggers.First()));
await ReplyErrorLocalized("convert_type_error", Format.Bold(originUnit.Triggers.First()), Format.Bold(targetUnit.Triggers.First())).ConfigureAwait(false);
return;
}
decimal res;
@ -150,8 +168,6 @@ namespace NadekoBot.Modules.Utility
case "F":
res = res * (9m / 5m) - 459.67m;
break;
default:
break;
}
}
else
@ -165,7 +181,7 @@ namespace NadekoBot.Modules.Utility
}
res = Math.Round(res, 4);
await Context.Channel.SendConfirmAsync(string.Format("{0} {1} is equal to {2} {3}", value, (originUnit.Triggers.First() + "s").SnPl(value.IsInteger() ? (int)value : 2), res, (targetUnit.Triggers.First() + "s").SnPl(res.IsInteger() ? (int)res : 2)));
await Context.Channel.SendConfirmAsync(GetText("convert", value, (originUnit.Triggers.First()).SnPl(value.IsInteger() ? (int)value : 2), res, (targetUnit.Triggers.First() + "s").SnPl(res.IsInteger() ? (int)res : 2)));
}
}

View File

@ -6,7 +6,6 @@ using System.Linq;
using System.Threading.Tasks;
using System.Text;
using NadekoBot.Extensions;
using System.Text.RegularExpressions;
using System.Reflection;
using NadekoBot.Services.Impl;
using System.Net.Http;
@ -21,7 +20,7 @@ using NadekoBot.Services;
namespace NadekoBot.Modules.Utility
{
[NadekoModule("Utility", ".")]
public partial class Utility : NadekoModule
public partial class Utility : NadekoTopLevelModule
{
private static ConcurrentDictionary<ulong, Timer> rotatingRoleColors = new ConcurrentDictionary<ulong, Timer>();
@ -118,14 +117,14 @@ namespace NadekoBot.Modules.Utility
if (rotatingRoleColors.TryRemove(role.Id, out t))
{
t.Change(Timeout.Infinite, Timeout.Infinite);
await channel.SendConfirmAsync($"Stopped rotating colors for the **{role.Name}** role").ConfigureAwait(false);
await ReplyConfirmLocalized("rrc_stop", Format.Bold(role.Name)).ConfigureAwait(false);
}
return;
}
var hexColors = hexes.Select(hex =>
{
try { return (ImageSharp.Color?)new ImageSharp.Color(hex.Replace("#", "")); } catch { return null; }
try { return (ImageSharp.Color?)ImageSharp.Color.FromHex(hex.Replace("#", "")); } catch { return null; }
})
.Where(c => c != null)
.Select(c => c.Value)
@ -133,7 +132,7 @@ namespace NadekoBot.Modules.Utility
if (!hexColors.Any())
{
await channel.SendMessageAsync("No colors are in the correct format. Use `#00ff00` for example.").ConfigureAwait(false);
await ReplyErrorLocalized("rrc_no_colors").ConfigureAwait(false);
return;
}
@ -163,8 +162,7 @@ namespace NadekoBot.Modules.Utility
old.Change(Timeout.Infinite, Timeout.Infinite);
return t;
});
await channel.SendFileAsync(images, "magicalgirl.jpg", $"Rotating **{role.Name}** role's color.").ConfigureAwait(false);
await channel.SendFileAsync(images, "magicalgirl.jpg", GetText("rrc_start", Format.Bold(role.Name))).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -181,14 +179,14 @@ namespace NadekoBot.Modules.Utility
.WithAuthor(eab => eab.WithIconUrl("https://togethertube.com/assets/img/favicons/favicon-32x32.png")
.WithName("Together Tube")
.WithUrl("https://togethertube.com/"))
.WithDescription($"{Context.User.Mention} Here is your room link:\n{target}"));
.WithDescription(Context.User.Mention + " " + GetText("togtub_room_link") + "\n" + target));
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task WhosPlaying([Remainder] string game = null)
public async Task WhosPlaying([Remainder] string game)
{
game = game.Trim().ToUpperInvariant();
game = game?.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(game))
return;
@ -208,7 +206,7 @@ namespace NadekoBot.Modules.Utility
int i = 0;
if (arr.Length == 0)
await Context.Channel.SendErrorAsync("Nobody is playing that game.").ConfigureAwait(false);
await ReplyErrorLocalized("nobody_playing_game").ConfigureAwait(false);
else
{
await Context.Channel.SendConfirmAsync("```css\n" + string.Join("\n", arr.GroupBy(item => (i++) / 2)
@ -219,26 +217,24 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task InRole([Remainder] string roles)
public async Task InRole(params IRole[] roles)
{
if (string.IsNullOrWhiteSpace(roles))
if (roles.Length == 0)
return;
var arg = roles.Split(',').Select(r => r.Trim().ToUpperInvariant());
string send = " **Here is a list of users in those roles:**";
foreach (var roleStr in arg.Where(str => !string.IsNullOrWhiteSpace(str) && str != "@EVERYONE" && str != "EVERYONE"))
var send = " " + Format.Bold(GetText("inrole_list"));
var usrs = (await Context.Guild.GetUsersAsync()).ToArray();
foreach (var role in roles.Where(r => r.Id != Context.Guild.Id))
{
var role = Context.Guild.Roles.Where(r => r.Name.ToUpperInvariant() == roleStr).FirstOrDefault();
if (role == null) continue;
send += $"```css\n[{role.Name}]\n";
send += string.Join(", ", (await Context.Guild.GetUsersAsync()).Where(u => u.RoleIds.Contains(role.Id)).Select(u => u.ToString()));
send += $"\n```";
send += string.Join(", ", usrs.Where(u => u.RoleIds.Contains(role.Id)).Select(u => u.ToString()));
send += "\n```";
}
var usr = Context.User as IGuildUser;
var usr = (IGuildUser)Context.User;
while (send.Length > 2000)
{
if (!usr.GetPermissions((ITextChannel)Context.Channel).ManageMessages)
{
await Context.Channel.SendErrorAsync($"⚠️ {usr.Mention} **you are not allowed to use this command on roles with a lot of users in them to prevent abuse.**").ConfigureAwait(false);
await ReplyErrorLocalized("inrole_not_allowed").ConfigureAwait(false);
return;
}
var curstr = send.Substring(0, 2000);
@ -255,15 +251,13 @@ namespace NadekoBot.Modules.Utility
public async Task CheckMyPerms()
{
StringBuilder builder = new StringBuilder("```http\n");
var user = Context.User as IGuildUser;
StringBuilder builder = new StringBuilder();
var user = (IGuildUser) Context.User;
var perms = user.GetPermissions((ITextChannel)Context.Channel);
foreach (var p in perms.GetType().GetProperties().Where(p => !p.GetGetMethod().GetParameters().Any()))
{
builder.AppendLine($"{p.Name} : {p.GetValue(perms, null).ToString()}");
builder.AppendLine($"{p.Name} : {p.GetValue(perms, null)}");
}
builder.Append("```");
await Context.Channel.SendConfirmAsync(builder.ToString());
}
@ -272,20 +266,23 @@ namespace NadekoBot.Modules.Utility
public async Task UserId(IGuildUser target = null)
{
var usr = target ?? Context.User;
await Context.Channel.SendConfirmAsync($"🆔 of the user **{ usr.Username }** is `{ usr.Id }`").ConfigureAwait(false);
await ReplyConfirmLocalized("userid", "🆔", Format.Bold(usr.ToString()),
Format.Code(usr.Id.ToString())).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ChannelId()
{
await Context.Channel.SendConfirmAsync($"🆔 of this channel is `{Context.Channel.Id}`").ConfigureAwait(false);
await ReplyConfirmLocalized("channelid", "🆔", Format.Code(Context.Channel.Id.ToString()))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task ServerId()
{
await Context.Channel.SendConfirmAsync($"🆔 of this server is `{Context.Guild.Id}`").ConfigureAwait(false);
await ReplyConfirmLocalized("serverid", "🆔", Format.Code(Context.Guild.Id.ToString()))
.ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -295,33 +292,36 @@ namespace NadekoBot.Modules.Utility
var channel = (ITextChannel)Context.Channel;
var guild = channel.Guild;
const int RolesPerPage = 20;
const int rolesPerPage = 20;
if (page < 1 || page > 100)
return;
if (target != null)
{
var roles = target.GetRoles().Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * RolesPerPage).Take(RolesPerPage);
var roles = target.GetRoles().Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * rolesPerPage).Take(rolesPerPage).ToArray();
if (!roles.Any())
{
await channel.SendErrorAsync("No roles on this page.").ConfigureAwait(false);
await ReplyErrorLocalized("no_roles_on_page").ConfigureAwait(false);
}
else
{
await channel.SendConfirmAsync($"⚔ **Page #{page} of roles for {target.Username}**", $"```css\n• " + string.Join("\n• ", roles).SanitizeMentions() + "\n```").ConfigureAwait(false);
await channel.SendConfirmAsync(GetText("roles_page", page, Format.Bold(target.ToString())),
"\n• " + string.Join("\n• ", (IEnumerable<IRole>)roles).SanitizeMentions()).ConfigureAwait(false);
}
}
else
{
var roles = guild.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * RolesPerPage).Take(RolesPerPage);
var roles = guild.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * rolesPerPage).Take(rolesPerPage).ToArray();
if (!roles.Any())
{
await channel.SendErrorAsync("No roles on this page.").ConfigureAwait(false);
await ReplyErrorLocalized("no_roles_on_page").ConfigureAwait(false);
}
else
{
await channel.SendConfirmAsync($"⚔ **Page #{page} of all roles on this server:**", $"```css\n• " + string.Join("\n• ", roles).SanitizeMentions() + "\n```").ConfigureAwait(false);
await channel.SendConfirmAsync(GetText("roles_all_page", page),
"\n• " + string.Join("\n• ", (IEnumerable<IRole>)roles).SanitizeMentions()).ConfigureAwait(false);
}
}
}
@ -340,9 +340,9 @@ namespace NadekoBot.Modules.Utility
var topic = channel.Topic;
if (string.IsNullOrWhiteSpace(topic))
await Context.Channel.SendErrorAsync("No topic set.").ConfigureAwait(false);
await ReplyErrorLocalized("no_topic_set").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync("Channel topic", topic).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(GetText("channel_topic"), topic).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -363,11 +363,13 @@ namespace NadekoBot.Modules.Utility
return;
var status = string.Join(", ", NadekoBot.Client.Shards.GroupBy(x => x.ConnectionState)
.Select(x => $"{x.Count()} shards {x.Key}")
.Select(x => $"{x.Count()} {x.Key}")
.ToArray());
var allShardStrings = NadekoBot.Client.Shards
.Select(x => $"Shard **#{x.ShardId.ToString()}** is in {Format.Bold(x.ConnectionState.ToString())} state with {Format.Bold(x.Guilds.Count.ToString())} servers")
.Select(x =>
GetText("shard_stats_txt", x.ShardId.ToString(),
Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.Count.ToString())))
.ToArray();
@ -378,10 +380,10 @@ namespace NadekoBot.Modules.Utility
var str = string.Join("\n", allShardStrings.Skip(25 * (curPage - 1)).Take(25));
if (string.IsNullOrWhiteSpace(str))
str = "No shards on this page.";
str = GetText("no_shards_on_page");
return new EmbedBuilder()
.WithAuthor(a => a.WithName("Shard Stats"))
.WithAuthor(a => a.WithName(GetText("shard_stats")))
.WithTitle(status)
.WithOkColor()
.WithDescription(str);
@ -393,7 +395,7 @@ namespace NadekoBot.Modules.Utility
{
var shardId = NadekoBot.Client.GetShardIdFor(guildid);
await Context.Channel.SendConfirmAsync($"ShardId for **{guildid}** with {NadekoBot.Client.Shards.Count} total shards", shardId.ToString()).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(shardId.ToString()).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -410,17 +412,21 @@ namespace NadekoBot.Modules.Utility
.WithAuthor(eab => eab.WithName($"NadekoBot v{StatsService.BotVersion}")
.WithUrl("http://nadekobot.readthedocs.io/en/latest/")
.WithIconUrl("https://cdn.discordapp.com/avatars/116275390695079945/b21045e778ef21c96d175400e779f0fb.jpg"))
.AddField(efb => efb.WithName(Format.Bold("Author")).WithValue(stats.Author).WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Bot ID")).WithValue(NadekoBot.Client.CurrentUser.Id.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Shard")).WithValue($"#{shardId}, {NadekoBot.Client.Shards.Count} total").WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Commands Ran")).WithValue(stats.CommandsRan.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Messages")).WithValue($"{stats.MessageCounter} ({stats.MessagesPerSecond:F2}/sec)").WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Memory")).WithValue($"{stats.Heap} MB").WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Owner ID(s)")).WithValue(string.Join("\n", NadekoBot.Credentials.OwnerIds)).WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Uptime")).WithValue(stats.GetUptimeString("\n")).WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Presence")).WithValue($"{NadekoBot.Client.GetGuildCount()} Servers\n{stats.TextChannels} Text Channels\n{stats.VoiceChannels} Voice Channels").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("author")).WithValue(stats.Author).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("botid")).WithValue(NadekoBot.Client.CurrentUser.Id.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{shardId} / {NadekoBot.Client.Shards.Count}").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("commands_ran")).WithValue(stats.CommandsRan.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("messages")).WithValue($"{stats.MessageCounter} ({stats.MessagesPerSecond:F2}/sec)").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("memory")).WithValue($"{stats.Heap} MB").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("owner_ids")).WithValue(string.Join("\n", NadekoBot.Credentials.OwnerIds)).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("uptime")).WithValue(stats.GetUptimeString("\n")).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("presence")).WithValue(
GetText("presence_txt",
NadekoBot.Client.GetGuildCount(), stats.TextChannels, stats.VoiceChannels)).WithIsInline(true))
#if !GLOBAL_NADEKO
.WithFooter(efb => efb.WithText($"Playing {Music.Music.MusicPlayers.Where(mp => mp.Value.CurrentSong != null).Count()} songs, {Music.Music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count)} queued."))
.WithFooter(efb => efb.WithText(GetText("stats_songs",
Music.Music.MusicPlayers.Count(mp => mp.Value.CurrentSong != null),
Music.Music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count))))
#endif
);
}
@ -430,10 +436,10 @@ namespace NadekoBot.Modules.Utility
{
var tags = Context.Message.Tags.Where(t => t.Type == TagType.Emoji).Select(t => (Emoji)t.Value);
var result = string.Join("\n", tags.Select(m => $"**Name:** {m} **Link:** {m.Url}"));
var result = string.Join("\n", tags.Select(m => GetText("showemojis", m, m.Url)));
if (string.IsNullOrWhiteSpace(result))
await Context.Channel.SendErrorAsync("No special emojis found.");
await ReplyErrorLocalized("showemojis_none").ConfigureAwait(false);
else
await Context.Channel.SendMessageAsync(result).ConfigureAwait(false);
}
@ -451,13 +457,15 @@ namespace NadekoBot.Modules.Utility
if (!guilds.Any())
{
await Context.Channel.SendErrorAsync("No servers found on that page.").ConfigureAwait(false);
await ReplyErrorLocalized("listservers_none").ConfigureAwait(false);
return;
}
await Context.Channel.EmbedAsync(guilds.Aggregate(new EmbedBuilder().WithOkColor(),
(embed, g) => embed.AddField(efb => efb.WithName(g.Name)
.WithValue($"```css\nID: {g.Id}\nMembers: {g.Users.Count}\nOwnerID: {g.OwnerId} ```")
.WithValue(
GetText("listservers", g.Id, g.Users.Count,
g.OwnerId))
.WithIsInline(false))))
.ConfigureAwait(false);
}

View File

@ -60,6 +60,9 @@ namespace NadekoBot
OkColor = new Color(Convert.ToUInt32(BotConfig.OkColor, 16));
ErrorColor = new Color(Convert.ToUInt32(BotConfig.ErrorColor, 16));
}
//ImageSharp.Configuration.Default.AddImageFormat(new ImageSharp.Formats.PngFormat());
//ImageSharp.Configuration.Default.AddImageFormat(new ImageSharp.Formats.JpegFormat());
}
public async Task RunAsync(params string[] args)

View File

@ -1,4 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cadministration_005Ccommands/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cgambling_005Ccommands/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cgames_005Ccommands/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cgames_005Ccommands/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cpermissions_005Ccommands/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Csearches_005Ccommands/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=modules_005Cutility_005Ccommands/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -501,7 +501,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders..
/// Looks up a localized string similar to Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders. Max message count is 10..
/// </summary>
public static string antispam_desc {
get {
@ -717,7 +717,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to `{0}atl en&gt;fr`.
/// Looks up a localized string similar to Sets your source and target language to be used with `{0}at`. Specify no arguments to remove previously set value..
/// </summary>
public static string autotranslang_desc {
get {
@ -726,7 +726,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to Sets your source and target language to be used with `{0}at`. Specify no arguments to remove previously set value..
/// Looks up a localized string similar to `{0}atl en&gt;fr`.
/// </summary>
public static string autotranslang_usage {
get {
@ -3552,7 +3552,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to Lists every person from the provided role or roles (separated by a &apos;,&apos;) on this server. If the list is too long for 1 message, you must have Manage Messages permission..
/// Looks up a localized string similar to Lists every person from the provided role or roles, separated with space, on this server. You can use role IDs, role names (in quotes if it has multiple words), or role mention If the list is too long for 1 message, you must have Manage Messages permission..
/// </summary>
public static string inrole_desc {
get {
@ -3561,7 +3561,7 @@ namespace NadekoBot.Resources {
}
/// <summary>
/// Looks up a localized string similar to `{0}inrole Role`.
/// Looks up a localized string similar to `{0}inrole Role` or `{0}inrole Role1 &quot;Role 2&quot; @role3`.
/// </summary>
public static string inrole_usage {
get {
@ -5729,6 +5729,33 @@ namespace NadekoBot.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to rategirl.
/// </summary>
public static string rategirl_cmd {
get {
return ResourceManager.GetString("rategirl_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use the universal hot-crazy wife zone matrix to determine the girl&apos;s worth. It is everything young men need to know about women. At any moment in time, any woman you have previously located on this chart can vanish from that location and appear anywhere else on the chart..
/// </summary>
public static string rategirl_desc {
get {
return ResourceManager.GetString("rategirl_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}rategirl @SomeGurl`.
/// </summary>
public static string rategirl_usage {
get {
return ResourceManager.GetString("rategirl_usage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to reloadimages.
/// </summary>
@ -6674,6 +6701,33 @@ namespace NadekoBot.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to qsearch.
/// </summary>
public static string searchquote_cmd {
get {
return ResourceManager.GetString("searchquote_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shows a random quote for a keyword that contains any text specified in the search..
/// </summary>
public static string searchquote_desc {
get {
return ResourceManager.GetString("searchquote_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}qsearch keyword text`.
/// </summary>
public static string searchquote_usage {
get {
return ResourceManager.GetString("searchquote_usage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to send.
/// </summary>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -841,10 +841,10 @@
<value>inrole</value>
</data>
<data name="inrole_desc" xml:space="preserve">
<value>Lists every person from the provided role or roles (separated by a ',') on this server. If the list is too long for 1 message, you must have Manage Messages permission.</value>
<value>Lists every person from the provided role or roles, separated with space, on this server. You can use role IDs, role names (in quotes if it has multiple words), or role mention If the list is too long for 1 message, you must have Manage Messages permission.</value>
</data>
<data name="inrole_usage" xml:space="preserve">
<value>`{0}inrole Role`</value>
<value>`{0}inrole Role` or `{0}inrole Role1 "Role 2" @role3`</value>
</data>
<data name="checkmyperms_cmd" xml:space="preserve">
<value>checkmyperms</value>
@ -1143,6 +1143,15 @@
<data name="showquote_usage" xml:space="preserve">
<value>`{0}.. abc`</value>
</data>
<data name="searchquote_cmd" xml:space="preserve">
<value>qsearch</value>
</data>
<data name="searchquote_desc" xml:space="preserve">
<value>Shows a random quote for a keyword that contains any text specified in the search.</value>
</data>
<data name="searchquote_usage" xml:space="preserve">
<value>`{0}qsearch keyword text`</value>
</data>
<data name="deletequote_cmd" xml:space="preserve">
<value>deletequote delq</value>
</data>
@ -2434,7 +2443,7 @@
<value>antispam</value>
</data>
<data name="antispam_desc" xml:space="preserve">
<value>Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders.</value>
<value>Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders. Max message count is 10.</value>
</data>
<data name="antispam_usage" xml:space="preserve">
<value>`{0}antispam 3 Mute` or `{0}antispam 4 Kick` or `{0}antispam 6 Ban`</value>
@ -2560,10 +2569,10 @@
<value>autotranslang atl</value>
</data>
<data name="autotranslang_desc" xml:space="preserve">
<value>`{0}atl en&gt;fr`</value>
<value>Sets your source and target language to be used with `{0}at`. Specify no arguments to remove previously set value.</value>
</data>
<data name="autotranslang_usage" xml:space="preserve">
<value>Sets your source and target language to be used with `{0}at`. Specify no arguments to remove previously set value.</value>
<value>`{0}atl en&gt;fr`</value>
</data>
<data name="autotranslate_cmd" xml:space="preserve">
<value>autotrans at</value>
@ -3132,4 +3141,13 @@
<data name="languageslist_usage" xml:space="preserve">
<value>`{0}langli`</value>
</data>
<data name="rategirl_cmd" xml:space="preserve">
<value>rategirl</value>
</data>
<data name="rategirl_desc" xml:space="preserve">
<value>Use the universal hot-crazy wife zone matrix to determine the girl's worth. It is everything young men need to know about women. At any moment in time, any woman you have previously located on this chart can vanish from that location and appear anywhere else on the chart.</value>
</data>
<data name="rategirl_usage" xml:space="preserve">
<value>`{0}rategirl @SomeGurl`</value>
</data>
</root>

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

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,9 +5,7 @@ using System.Linq;
using System.Threading.Tasks;
using Discord;
using NLog;
using System.Diagnostics;
using Discord.Commands;
using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Permissions;
using Discord.Net;
using NadekoBot.Extensions;
@ -22,7 +20,7 @@ using NadekoBot.DataStructures;
namespace NadekoBot.Services
{
public class IGuildUserComparer : IEqualityComparer<IGuildUser>
public class GuildUserComparer : IEqualityComparer<IGuildUser>
{
public bool Equals(IGuildUser x, IGuildUser y) => x.Id == y.Id;
@ -40,7 +38,7 @@ namespace NadekoBot.Services
private readonly CommandService _commandService;
private readonly Logger _log;
private List<IDMChannel> ownerChannels { get; set; }
private List<IDMChannel> ownerChannels { get; set; } = new List<IDMChannel>();
public event Func<SocketUserMessage, CommandInfo, Task> CommandExecuted = delegate { return Task.CompletedTask; };
@ -48,7 +46,7 @@ namespace NadekoBot.Services
public ConcurrentDictionary<ulong, uint> UserMessagesSent { get; } = new ConcurrentDictionary<ulong, uint>();
public ConcurrentHashSet<ulong> UsersOnShortCooldown { get; } = new ConcurrentHashSet<ulong>();
private Timer clearUsersOnShortCooldown { get; }
private readonly Timer _clearUsersOnShortCooldown;
public CommandHandler(DiscordShardedClient client, CommandService commandService)
{
@ -56,17 +54,30 @@ namespace NadekoBot.Services
_commandService = commandService;
_log = LogManager.GetCurrentClassLogger();
clearUsersOnShortCooldown = new Timer((_) =>
_clearUsersOnShortCooldown = new Timer(_ =>
{
UsersOnShortCooldown.Clear();
}, null, GlobalCommandsCooldown, GlobalCommandsCooldown);
}
public async Task StartHandling()
public Task StartHandling()
{
var _ = Task.Run(async () =>
{
await Task.Delay(5000).ConfigureAwait(false);
ownerChannels = (await Task.WhenAll(_client.GetGuilds().SelectMany(g => g.Users)
.Where(u => NadekoBot.Credentials.OwnerIds.Contains(u.Id))
.Distinct(new IGuildUserComparer())
.Select(async u => { try { return await u.CreateDMChannelAsync(); } catch { return null; } })))
.Distinct(new GuildUserComparer())
.Select(async u =>
{
try
{
return await u.CreateDMChannelAsync();
}
catch
{
return null;
}
})))
.Where(ch => ch != null)
.ToList();
@ -74,8 +85,31 @@ namespace NadekoBot.Services
_log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file.");
else
_log.Info($"Created {ownerChannels.Count} out of {NadekoBot.Credentials.OwnerIds.Count} owner message channels.");
});
_client.MessageReceived += MessageReceivedHandler;
_client.MessageUpdated += (oldmsg, newMsg) =>
{
var ignore = Task.Run(async () =>
{
try
{
var usrMsg = newMsg as SocketUserMessage;
var guild = (usrMsg?.Channel as ITextChannel)?.Guild;
if (guild != null && !await InviteFiltered(guild, usrMsg).ConfigureAwait(false))
await WordFiltered(guild, usrMsg).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
return Task.CompletedTask;
});
return Task.CompletedTask;
};
return Task.CompletedTask;
}
private async Task<bool> TryRunCleverbot(SocketUserMessage usrMsg, IGuild guild)
@ -100,11 +134,12 @@ namespace NadekoBot.Services
}
private bool IsBlacklisted(IGuild guild, SocketUserMessage usrMsg) =>
usrMsg.Author?.Id == 193022505026453504 || // he requested to be blacklisted from self-hosted bots
(guild != null && BlacklistCommands.BlacklistedGuilds.Contains(guild.Id)) ||
BlacklistCommands.BlacklistedChannels.Contains(usrMsg.Channel.Id) ||
BlacklistCommands.BlacklistedUsers.Contains(usrMsg.Author.Id);
const float oneThousandth = 1.0f / 1000;
private const float _oneThousandth = 1.0f / 1000;
private Task LogSuccessfulExecution(SocketUserMessage usrMsg, ExecuteCommandResult exec, SocketTextChannel channel, int exec1, int exec2, int exec3, int total)
{
_log.Info("Command Executed after {4}/{5}/{6}/{7}s\n\t" +
@ -116,10 +151,10 @@ namespace NadekoBot.Services
(channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1}
(channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2}
usrMsg.Content, // {3}
exec1 * oneThousandth, // {4}
exec2 * oneThousandth, // {5}
exec3 * oneThousandth, // {6}
total * oneThousandth // {7}
exec1 * _oneThousandth, // {4}
exec2 * _oneThousandth, // {5}
exec3 * _oneThousandth, // {6}
total * _oneThousandth // {7}
);
return Task.CompletedTask;
}
@ -137,10 +172,10 @@ namespace NadekoBot.Services
(channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2}
usrMsg.Content,// {3}
exec.Result.ErrorReason, // {4}
exec1 * oneThousandth, // {5}
exec2 * oneThousandth, // {6}
exec3 * oneThousandth, // {7}
total * oneThousandth // {8}
exec1 * _oneThousandth, // {5}
exec2 * _oneThousandth, // {6}
exec3 * _oneThousandth, // {7}
total * _oneThousandth // {8}
);
}
@ -206,6 +241,8 @@ namespace NadekoBot.Services
if (usrMsg == null) //has to be an user message, not system/other messages.
return;
if (usrMsg.Author.Id == 193022505026453504)
return;
#if !GLOBAL_NADEKO
// track how many messagges each user is sending
UserMessagesSent.AddOrUpdate(usrMsg.Author.Id, 1, (key, old) => ++old);
@ -390,10 +427,10 @@ namespace NadekoBot.Services
// Bot will ignore commands which are ran more often than what specified by
// GlobalCommandsCooldown constant (miliseconds)
if (!UsersOnShortCooldown.Add(context.Message.Author.Id))
return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, $"You are on a global cooldown."));
return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, "You are on a global cooldown."));
if (CmdCdsCommands.HasCooldown(cmd, context.Guild, context.User))
return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, $"That command is on a cooldown for you."));
return new ExecuteCommandResult(cmd, null, SearchResult.FromError(CommandError.Exception, "That command is on a cooldown for you."));
return new ExecuteCommandResult(cmd, null, await commands[i].ExecuteAsync(context, parseResult, dependencyMap));
}

View File

@ -7,6 +7,6 @@ namespace NadekoBot.Services.Database.Models
{
[Key]
public int Id { get; set; }
public DateTime DateAdded { get; } = DateTime.UtcNow;
public DateTime? DateAdded { get; set; } = DateTime.UtcNow;
}
}

View File

@ -8,6 +8,7 @@ namespace NadekoBot.Services.Database.Repositories
{
IEnumerable<Quote> GetAllQuotesByKeyword(ulong guildId, string keyword);
Task<Quote> GetRandomQuoteByKeywordAsync(ulong guildId, string keyword);
Task<Quote> SearchQuoteKeywordTextAsync(ulong guildId, string keyword, string text);
IEnumerable<Quote> GetGroup(ulong guildId, int skip, int take);
}
}

View File

@ -23,5 +23,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl
var rng = new NadekoRandom();
return _set.Where(q => q.GuildId == guildId && q.Keyword == keyword).OrderBy(q => rng.Next()).FirstOrDefaultAsync();
}
public Task<Quote> SearchQuoteKeywordTextAsync(ulong guildId, string keyword, string text)
{
var rngk = new NadekoRandom();
return _set.Where(q => q.Text.Contains(text) && q.GuildId == guildId && q.Keyword == keyword).OrderBy(q => rngk.Next()).FirstOrDefaultAsync();
}
}
}

View File

@ -21,6 +21,9 @@ namespace NadekoBot.Services
ImmutableArray<ImmutableArray<byte>> SlotEmojis { get; }
ImmutableArray<ImmutableArray<byte>> SlotNumbers { get; }
ImmutableArray<byte> WifeMatrix { get; }
ImmutableArray<byte> RategirlDot { get; }
Task<TimeSpan> Reload();
}
}

View File

@ -5,6 +5,5 @@ namespace NadekoBot.Services
public interface IStatsService
{
Task<string> Print();
Task Reset();
}
}

View File

@ -1,13 +1,10 @@
using NadekoBot.DataStructures;
using NLog;
using NLog;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services.Impl
@ -16,23 +13,26 @@ namespace NadekoBot.Services.Impl
{
private readonly Logger _log;
private const string basePath = "data/images/";
private const string _basePath = "data/images/";
private const string headsPath = basePath + "coins/heads.png";
private const string tailsPath = basePath + "coins/tails.png";
private const string _headsPath = _basePath + "coins/heads.png";
private const string _tailsPath = _basePath + "coins/tails.png";
private const string currencyImagesPath = basePath + "currency";
private const string diceImagesPath = basePath + "dice";
private const string _currencyImagesPath = _basePath + "currency";
private const string _diceImagesPath = _basePath + "dice";
private const string slotBackgroundPath = basePath + "slots/background.png";
private const string slotNumbersPath = basePath + "slots/numbers/";
private const string slotEmojisPath = basePath + "slots/emojis/";
private const string _slotBackgroundPath = _basePath + "slots/background.png";
private const string _slotNumbersPath = _basePath + "slots/numbers/";
private const string _slotEmojisPath = _basePath + "slots/emojis/";
private const string _wifeMatrixPath = _basePath + "rategirl/wifematrix.png";
private const string _rategirlDot = _basePath + "rategirl/dot.png";
public ImmutableArray<byte> Heads { get; private set; }
public ImmutableArray<byte> Tails { get; private set; }
//todo C#7
//todo C#7 tuples
public ImmutableArray<KeyValuePair<string, ImmutableArray<byte>>> Currency { get; private set; }
public ImmutableArray<KeyValuePair<string, ImmutableArray<byte>>> Dice { get; private set; }
@ -41,6 +41,9 @@ namespace NadekoBot.Services.Impl
public ImmutableArray<ImmutableArray<byte>> SlotNumbers { get; private set; }
public ImmutableArray<ImmutableArray<byte>> SlotEmojis { get; private set; }
public ImmutableArray<byte> WifeMatrix { get; private set; }
public ImmutableArray<byte> RategirlDot { get; private set; }
private ImagesService()
{
_log = LogManager.GetCurrentClassLogger();
@ -59,33 +62,36 @@ namespace NadekoBot.Services.Impl
{
_log.Info("Loading images...");
var sw = Stopwatch.StartNew();
Heads = File.ReadAllBytes(headsPath).ToImmutableArray();
Tails = File.ReadAllBytes(tailsPath).ToImmutableArray();
Heads = File.ReadAllBytes(_headsPath).ToImmutableArray();
Tails = File.ReadAllBytes(_tailsPath).ToImmutableArray();
Currency = Directory.GetFiles(currencyImagesPath)
Currency = Directory.GetFiles(_currencyImagesPath)
.Select(x => new KeyValuePair<string, ImmutableArray<byte>>(
Path.GetFileName(x),
File.ReadAllBytes(x).ToImmutableArray()))
.ToImmutableArray();
Dice = Directory.GetFiles(diceImagesPath)
Dice = Directory.GetFiles(_diceImagesPath)
.OrderBy(x => int.Parse(Path.GetFileNameWithoutExtension(x)))
.Select(x => new KeyValuePair<string, ImmutableArray<byte>>(x,
File.ReadAllBytes(x).ToImmutableArray()))
.ToImmutableArray();
SlotBackground = File.ReadAllBytes(slotBackgroundPath).ToImmutableArray();
SlotBackground = File.ReadAllBytes(_slotBackgroundPath).ToImmutableArray();
SlotNumbers = Directory.GetFiles(slotNumbersPath)
SlotNumbers = Directory.GetFiles(_slotNumbersPath)
.OrderBy(f => int.Parse(Path.GetFileNameWithoutExtension(f)))
.Select(x => File.ReadAllBytes(x).ToImmutableArray())
.ToImmutableArray();
SlotEmojis = Directory.GetFiles(slotEmojisPath)
SlotEmojis = Directory.GetFiles(_slotEmojisPath)
.OrderBy(f => int.Parse(Path.GetFileNameWithoutExtension(f)))
.Select(x => File.ReadAllBytes(x).ToImmutableArray())
.ToImmutableArray();
WifeMatrix = File.ReadAllBytes(_wifeMatrixPath).ToImmutableArray();
RategirlDot = File.ReadAllBytes(_rategirlDot).ToImmutableArray();
sw.Stop();
_log.Info($"Images loaded after {sw.Elapsed.TotalSeconds:F2}s!");
return sw.Elapsed;

View File

@ -3,6 +3,7 @@ using Discord.WebSocket;
using NadekoBot.Extensions;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading;
@ -12,75 +13,108 @@ namespace NadekoBot.Services.Impl
{
public class StatsService : IStatsService
{
private DiscordShardedClient client;
private DateTime started;
private readonly DiscordShardedClient _client;
private readonly DateTime _started;
public const string BotVersion = "1.1.8-alpha";
public const string BotVersion = "1.2";
public string Author => "Kwoth#2560";
public string Library => "Discord.Net";
public int MessageCounter { get; private set; } = 0;
public int CommandsRan { get; private set; } = 0;
public string Heap =>
Math.Round((double)GC.GetTotalMemory(false) / 1.MiB(), 2).ToString();
Math.Round((double)GC.GetTotalMemory(false) / 1.MiB(), 2).ToString(CultureInfo.InvariantCulture);
public double MessagesPerSecond => MessageCounter / GetUptime().TotalSeconds;
private int _textChannels = 0;
public int TextChannels => _textChannels;
private int _voiceChannels = 0;
public int VoiceChannels => _voiceChannels;
Timer carbonitexTimer { get; }
private long _textChannels;
public long TextChannels => Interlocked.Read(ref _textChannels);
private long _voiceChannels;
public long VoiceChannels => Interlocked.Read(ref _voiceChannels);
private long _messageCounter;
public long MessageCounter => Interlocked.Read(ref _messageCounter);
private long _commandsRan;
public long CommandsRan => Interlocked.Read(ref _commandsRan);
private Timer carbonitexTimer { get; }
public StatsService(DiscordShardedClient client, CommandHandler cmdHandler)
{
this.client = client;
_client = client;
Reset();
this.client.MessageReceived += _ => Task.FromResult(MessageCounter++);
cmdHandler.CommandExecuted += (_, e) => Task.FromResult(CommandsRan++);
_started = DateTime.Now;
_client.MessageReceived += _ => Task.FromResult(Interlocked.Increment(ref _messageCounter));
cmdHandler.CommandExecuted += (_, e) => Task.FromResult(Interlocked.Increment(ref _commandsRan));
this.client.ChannelCreated += (c) =>
_client.ChannelCreated += (c) =>
{
if (c is ITextChannel)
++_textChannels;
Interlocked.Increment(ref _textChannels);
else if (c is IVoiceChannel)
++_voiceChannels;
Interlocked.Increment(ref _voiceChannels);
return Task.CompletedTask;
};
this.client.ChannelDestroyed += (c) =>
_client.ChannelDestroyed += (c) =>
{
if (c is ITextChannel)
--_textChannels;
Interlocked.Decrement(ref _textChannels);
else if (c is IVoiceChannel)
--_voiceChannels;
Interlocked.Decrement(ref _voiceChannels);
return Task.CompletedTask;
};
this.client.JoinedGuild += (g) =>
_client.GuildAvailable += (g) =>
{
var tc = g.Channels.Where(cx => cx is ITextChannel).Count();
var vc = g.Channels.Count - tc;
_textChannels += tc;
_voiceChannels += vc;
return Task.CompletedTask;
};
this.client.LeftGuild += (g) =>
var _ = Task.Run(() =>
{
var tc = g.Channels.Where(cx => cx is ITextChannel).Count();
var tc = g.Channels.Count(cx => cx is ITextChannel);
var vc = g.Channels.Count - tc;
_textChannels -= tc;
_voiceChannels -= vc;
Interlocked.Add(ref _textChannels, tc);
Interlocked.Add(ref _voiceChannels, vc);
});
return Task.CompletedTask;
};
_client.JoinedGuild += (g) =>
{
var _ = Task.Run(() =>
{
var tc = g.Channels.Count(cx => cx is ITextChannel);
var vc = g.Channels.Count - tc;
Interlocked.Add(ref _textChannels, tc);
Interlocked.Add(ref _voiceChannels, vc);
});
return Task.CompletedTask;
};
_client.GuildUnavailable += (g) =>
{
var _ = Task.Run(() =>
{
var tc = g.Channels.Count(cx => cx is ITextChannel);
var vc = g.Channels.Count - tc;
Interlocked.Add(ref _textChannels, -tc);
Interlocked.Add(ref _voiceChannels, -vc);
});
return Task.CompletedTask;
};
this.carbonitexTimer = new Timer(async (state) =>
_client.LeftGuild += (g) =>
{
var _ = Task.Run(() =>
{
var tc = g.Channels.Count(cx => cx is ITextChannel);
var vc = g.Channels.Count - tc;
Interlocked.Add(ref _textChannels, -tc);
Interlocked.Add(ref _voiceChannels, -vc);
});
return Task.CompletedTask;
};
carbonitexTimer = new Timer(async (state) =>
{
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.CarbonKey))
return;
@ -90,7 +124,7 @@ namespace NadekoBot.Services.Impl
{
using (var content = new FormUrlEncodedContent(
new Dictionary<string, string> {
{ "servercount", this.client.GetGuildCount().ToString() },
{ "servercount", _client.GetGuildCount().ToString() },
{ "key", NadekoBot.Credentials.CarbonKey }}))
{
content.Headers.Clear();
@ -98,7 +132,7 @@ namespace NadekoBot.Services.Impl
await http.PostAsync("https://www.carbonitex.net/discord/data/botdata.php", content).ConfigureAwait(false);
}
};
}
}
catch
{
@ -109,34 +143,27 @@ namespace NadekoBot.Services.Impl
public void Initialize()
{
var guilds = this.client.GetGuilds().ToArray();
var guilds = _client.GetGuilds().ToArray();
_textChannels = guilds.Sum(g => g.Channels.Count(cx => cx is ITextChannel));
_voiceChannels = guilds.Sum(g => g.Channels.Count) - _textChannels;
}
public Task<string> Print()
{
var curUser = client.CurrentUser;
var curUser = _client.CurrentUser;
return Task.FromResult($@"
Author: [{Author}] | Library: [{Library}]
Bot Version: [{BotVersion}]
Bot ID: {curUser.Id}
Owner ID(s): {string.Join(", ", NadekoBot.Credentials.OwnerIds)}
Uptime: {GetUptimeString()}
Servers: {client.GetGuildCount()} | TextChannels: {TextChannels} | VoiceChannels: {VoiceChannels}
Servers: {_client.GetGuildCount()} | TextChannels: {TextChannels} | VoiceChannels: {VoiceChannels}
Commands Ran this session: {CommandsRan}
Messages: {MessageCounter} [{MessagesPerSecond:F2}/sec] Heap: [{Heap} MB]");
}
public Task Reset()
{
MessageCounter = 0;
started = DateTime.Now;
return Task.CompletedTask;
}
public TimeSpan GetUptime() =>
DateTime.Now - started;
DateTime.Now - _started;
public string GetUptimeString(string separator = ", ")
{

View File

@ -22,7 +22,11 @@ namespace NadekoBot.Extensions
private const string arrow_right = "➡";
public static Stream ToStream(this IEnumerable<byte> bytes, bool canWrite = false)
=> new MemoryStream(bytes as byte[] ?? bytes.ToArray(), canWrite);
{
var ms = new MemoryStream(bytes as byte[] ?? bytes.ToArray(), canWrite);
ms.Seek(0, SeekOrigin.Begin);
return ms;
}
/// <summary>
/// danny kamisama
@ -398,22 +402,15 @@ namespace NadekoBot.Extensions
public static ImageSharp.Image Merge(this IEnumerable<ImageSharp.Image> images)
{
var imgList = images.ToList();
var imgs = images.ToArray();
var canvas = new ImageSharp.Image(imgList.Sum(img => img.Width), imgList.Max(img => img.Height));
var canvas = new ImageSharp.Image(imgs.Sum(img => img.Width), imgs.Max(img => img.Height));
var canvasPixels = canvas.Lock();
int offsetX = 0;
foreach (var img in imgList.Select(img => img.Lock()))
var xOffset = 0;
for (int i = 0; i < imgs.Length; i++)
{
for (int i = 0; i < img.Width; i++)
{
for (int j = 0; j < img.Height; j++)
{
canvasPixels[i + offsetX, j] = img[i, j];
}
}
offsetX += img.Width;
canvas.DrawImage(imgs[i], 100, default(Size), new Point(xOffset, 0));
xOffset += imgs[i].Bounds.Width;
}
return canvas;
@ -422,7 +419,7 @@ namespace NadekoBot.Extensions
public static Stream ToStream(this ImageSharp.Image img)
{
var imageStream = new MemoryStream();
img.SaveAsPng(imageStream);
img.Save(imageStream);
imageStream.Position = 0;
return imageStream;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -23,7 +23,12 @@
"Google.Apis.Urlshortener.v1": "1.19.0.138",
"Google.Apis.YouTube.v3": "1.20.0.701",
"Google.Apis.Customsearch.v1": "1.20.0.466",
"ImageSharp": "1.0.0-alpha-000079",
"ImageSharp": "1.0.0-alpha2-*",
"ImageSharp.Processing": "1.0.0-alpha2-*",
"ImageSharp.Formats.Png": "1.0.0-alpha2-*",
"ImageSharp.Formats.Jpeg": "1.0.0-alpha2-*",
"ImageSharp.Drawing": "1.0.0-alpha2-*",
"ImageSharp.Drawing.Paths": "1.0.0-alpha2-*",
"Microsoft.EntityFrameworkCore": "1.1.0",
"Microsoft.EntityFrameworkCore.Design": "1.1.0",
"Microsoft.EntityFrameworkCore.Sqlite": "1.1.0",
@ -39,7 +44,6 @@
},
"Newtonsoft.Json": "9.0.2-beta1",
"NLog": "5.0.0-beta03",
"System.Diagnostics.Contracts": "4.3.0",
"System.Xml.XPath": "4.3.0",
"Discord.Net.Commands": {
"target": "project",