Games done, admin half done

This commit is contained in:
Master Kwoth 2017-05-27 10:19:27 +02:00
parent 3797fbd439
commit 355425bf80
57 changed files with 2330 additions and 1901 deletions

View File

@ -7,82 +7,54 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using Discord.WebSocket;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using static NadekoBot.Modules.Permissions.Permissions; using NadekoBot.Services.Administration;
using System.Collections.Concurrent;
using NLog;
using NadekoBot.Modules.Permissions;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
{ {
[NadekoModule("Administration", ".")]
public partial class Administration : NadekoTopLevelModule public partial class Administration : NadekoTopLevelModule
{ {
private static readonly ConcurrentHashSet<ulong> deleteMessagesOnCommand; private IGuild _nadekoSupportServer;
private readonly DbHandler _db;
private readonly AdministrationService _admin;
private new static readonly Logger _log; public Administration(DbHandler db, AdministrationService admin)
static Administration()
{ {
_log = LogManager.GetCurrentClassLogger(); _db = db;
NadekoBot.CommandHandler.CommandExecuted += DelMsgOnCmd_Handler; _admin = admin;
deleteMessagesOnCommand = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId));
} }
private static Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd) ////todo permissions
{ //[NadekoCommand, Usage, Description, Aliases]
var _ = Task.Run(async () => //[RequireContext(ContextType.Guild)]
{ //[RequireUserPermission(GuildPermission.Administrator)]
try //public async Task ResetPermissions()
{ //{
var channel = msg.Channel as SocketTextChannel; // using (var uow = _db.UnitOfWork)
if (channel == null) // {
return; // var config = uow.GuildConfigs.GcWithPermissionsv2For(Context.Guild.Id);
if (deleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune" && cmd.Name != "pick") // config.Permissions = Permissionv2.GetDefaultPermlist;
await msg.DeleteAsync().ConfigureAwait(false); // await uow.CompleteAsync();
} // UpdateCache(config);
catch (Exception ex) // }
{ // await ReplyConfirmLocalized("perms_reset").ConfigureAwait(false);
_log.Warn(ex, "Delmsgoncmd errored..."); //}
} //[NadekoCommand, Usage, Description, Aliases]
}); //[OwnerOnly]
return Task.CompletedTask; //public async Task ResetGlobalPermissions()
} //{
// using (var uow = _db.UnitOfWork)
// {
// var gc = uow.BotConfig.GetOrCreate();
// gc.BlockedCommands.Clear();
// gc.BlockedModules.Clear();
[NadekoCommand, Usage, Description, Aliases] // GlobalPermissionCommands.BlockedCommands.Clear();
[RequireContext(ContextType.Guild)] // GlobalPermissionCommands.BlockedModules.Clear();
[RequireUserPermission(GuildPermission.Administrator)] // await uow.CompleteAsync();
public async Task ResetPermissions() // }
{ // await ReplyConfirmLocalized("global_perms_reset").ConfigureAwait(false);
using (var uow = DbHandler.UnitOfWork()) //}
{
var config = uow.GuildConfigs.GcWithPermissionsv2For(Context.Guild.Id);
config.Permissions = Permissionv2.GetDefaultPermlist;
await uow.CompleteAsync();
UpdateCache(config);
}
await ReplyConfirmLocalized("perms_reset").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ResetGlobalPermissions()
{
using (var uow = DbHandler.UnitOfWork())
{
var gc = uow.BotConfig.GetOrCreate();
gc.BlockedCommands.Clear();
gc.BlockedModules.Clear();
GlobalPermissionCommands.BlockedCommands.Clear();
GlobalPermissionCommands.BlockedModules.Clear();
await uow.CompleteAsync();
}
await ReplyConfirmLocalized("global_perms_reset").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
@ -91,7 +63,7 @@ namespace NadekoBot.Modules.Administration
public async Task Delmsgoncmd() public async Task Delmsgoncmd()
{ {
bool enabled; bool enabled;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set); var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set);
enabled = conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand; enabled = conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand;
@ -100,12 +72,12 @@ namespace NadekoBot.Modules.Administration
} }
if (enabled) if (enabled)
{ {
deleteMessagesOnCommand.Add(Context.Guild.Id); _admin.DeleteMessagesOnCommand.Add(Context.Guild.Id);
await ReplyConfirmLocalized("delmsg_on").ConfigureAwait(false); await ReplyConfirmLocalized("delmsg_on").ConfigureAwait(false);
} }
else else
{ {
deleteMessagesOnCommand.TryRemove(Context.Guild.Id); _admin.DeleteMessagesOnCommand.TryRemove(Context.Guild.Id);
await ReplyConfirmLocalized("delmsg_off").ConfigureAwait(false); await ReplyConfirmLocalized("delmsg_off").ConfigureAwait(false);
} }
} }
@ -454,19 +426,18 @@ namespace NadekoBot.Modules.Administration
await Context.Channel.SendMessageAsync(send).ConfigureAwait(false); await Context.Channel.SendMessageAsync(send).ConfigureAwait(false);
} }
private IGuild _nadekoSupportServer;
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Donators() public async Task Donators()
{ {
IEnumerable<Donator> donatorsOrdered; IEnumerable<Donator> donatorsOrdered;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
donatorsOrdered = uow.Donators.GetDonatorsOrdered(); donatorsOrdered = uow.Donators.GetDonatorsOrdered();
} }
await Context.Channel.SendConfirmAsync(GetText("donators"), string.Join("⭐", donatorsOrdered.Select(d => d.Name))).ConfigureAwait(false); await Context.Channel.SendConfirmAsync(GetText("donators"), string.Join("⭐", donatorsOrdered.Select(d => d.Name))).ConfigureAwait(false);
_nadekoSupportServer = _nadekoSupportServer ?? NadekoBot.Client.GetGuild(117523346618318850); _nadekoSupportServer = _nadekoSupportServer ?? (await Context.Client.GetGuildAsync(117523346618318850));
var patreonRole = _nadekoSupportServer?.GetRole(236667642088259585); var patreonRole = _nadekoSupportServer?.GetRole(236667642088259585);
if (patreonRole == null) if (patreonRole == null)
@ -482,7 +453,7 @@ namespace NadekoBot.Modules.Administration
public async Task Donadd(IUser donator, int amount) public async Task Donadd(IUser donator, int amount)
{ {
Donator don; Donator don;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
don = uow.Donators.AddOrUpdateDonator(donator.Id, donator.Username, amount); don = uow.Donators.AddOrUpdateDonator(donator.Id, donator.Username, amount);
await uow.CompleteAsync(); await uow.CompleteAsync();

View File

@ -4,6 +4,7 @@ using Discord.WebSocket;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Administration;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NLog; using NLog;
using System; using System;
@ -19,232 +20,13 @@ namespace NadekoBot.Modules.Administration
[Group] [Group]
public class MuteCommands : NadekoSubmodule public class MuteCommands : NadekoSubmodule
{ {
private static ConcurrentDictionary<ulong, string> GuildMuteRoles { get; } private readonly MuteService _service;
private static ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> MutedUsers { get; } private readonly DbHandler _db;
private static ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>> UnmuteTimers { get; }
= new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>>();
public static event Action<IGuildUser, MuteType> UserMuted = delegate { }; public MuteCommands(MuteService service, DbHandler db)
public static event Action<IGuildUser, MuteType> UserUnmuted = delegate { };
public enum MuteType {
Voice,
Chat,
All
}
private static readonly OverwritePermissions denyOverwrite = new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny);
private static readonly new Logger _log = LogManager.GetCurrentClassLogger();
static MuteCommands()
{ {
var configs = NadekoBot.AllGuildConfigs; _service = service;
GuildMuteRoles = new ConcurrentDictionary<ulong, string>(configs _db = db;
.Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName))
.ToDictionary(c => c.GuildId, c => c.MuteRoleName));
MutedUsers = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(configs.ToDictionary(
k => k.GuildId,
v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId))
));
foreach (var conf in configs)
{
foreach (var x in conf.UnmuteTimers)
{
TimeSpan after;
if (x.UnmuteAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
{
after = TimeSpan.FromMinutes(2);
}
else
{
after = x.UnmuteAt - DateTime.UtcNow;
}
StartUnmuteTimer(conf.GuildId, x.UserId, after);
}
}
NadekoBot.Client.UserJoined += Client_UserJoined;
}
private static async Task Client_UserJoined(IGuildUser usr)
{
try
{
MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted);
if (muted == null || !muted.Contains(usr.Id))
return;
await MuteUser(usr).ConfigureAwait(false);
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Warn(ex);
}
}
public static async Task MuteUser(IGuildUser usr)
{
await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false);
var muteRole = await GetMuteRole(usr.Guild);
if (!usr.RoleIds.Contains(muteRole.Id))
await usr.AddRoleAsync(muteRole).ConfigureAwait(false);
StopUnmuteTimer(usr.GuildId, usr.Id);
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(usr.Guild.Id,
set => set.Include(gc => gc.MutedUsers)
.Include(gc => gc.UnmuteTimers));
config.MutedUsers.Add(new MutedUserId()
{
UserId = usr.Id
});
if (MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted))
muted.Add(usr.Id);
config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
await uow.CompleteAsync().ConfigureAwait(false);
}
UserMuted(usr, MuteType.All);
}
public static async Task UnmuteUser(IGuildUser usr)
{
StopUnmuteTimer(usr.GuildId, usr.Id);
try { await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false); } catch { }
try { await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild)).ConfigureAwait(false); } catch { /*ignore*/ }
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(usr.Guild.Id, set => set.Include(gc => gc.MutedUsers)
.Include(gc => gc.UnmuteTimers));
config.MutedUsers.Remove(new MutedUserId()
{
UserId = usr.Id
});
if (MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted))
muted.TryRemove(usr.Id);
config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
await uow.CompleteAsync().ConfigureAwait(false);
}
UserUnmuted(usr, MuteType.All);
}
public static async Task<IRole>GetMuteRole(IGuild guild)
{
const string defaultMuteRoleName = "nadeko-mute";
var muteRoleName = GuildMuteRoles.GetOrAdd(guild.Id, defaultMuteRoleName);
var muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName);
if (muteRole == null)
{
//if it doesn't exist, create it
try { muteRole = await guild.CreateRoleAsync(muteRoleName, GuildPermissions.None).ConfigureAwait(false); }
catch
{
//if creations fails, maybe the name is not correct, find default one, if doesn't work, create default one
muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName) ??
await guild.CreateRoleAsync(defaultMuteRoleName, GuildPermissions.None).ConfigureAwait(false);
}
}
foreach (var toOverwrite in (await guild.GetTextChannelsAsync()))
{
try
{
if (!toOverwrite.PermissionOverwrites.Select(x => x.Permissions).Contains(denyOverwrite))
{
await toOverwrite.AddPermissionOverwriteAsync(muteRole, denyOverwrite)
.ConfigureAwait(false);
await Task.Delay(200).ConfigureAwait(false);
}
}
catch
{
// ignored
}
}
return muteRole;
}
public static async Task TimedMute(IGuildUser user, TimeSpan after)
{
await MuteUser(user).ConfigureAwait(false); // mute the user. This will also remove any previous unmute timers
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(user.GuildId, set => set.Include(x => x.UnmuteTimers));
config.UnmuteTimers.Add(new UnmuteTimer()
{
UserId = user.Id,
UnmuteAt = DateTime.UtcNow + after,
}); // add teh unmute timer to the database
uow.Complete();
}
StartUnmuteTimer(user.GuildId, user.Id, after); // start the timer
}
public static void StartUnmuteTimer(ulong guildId, ulong userId, TimeSpan after)
{
//load the unmute timers for this guild
var userUnmuteTimers = UnmuteTimers.GetOrAdd(guildId, new ConcurrentDictionary<ulong, Timer>());
//unmute timer to be added
var toAdd = new Timer(async _ =>
{
try
{
var guild = NadekoBot.Client.GetGuild(guildId); // load the guild
if (guild == null)
{
RemoveUnmuteTimerFromDb(guildId, userId);
return; // if guild can't be found, just remove the timer from db
}
// unmute the user, this will also remove the timer from the db
await UnmuteUser(guild.GetUser(userId)).ConfigureAwait(false);
}
catch (Exception ex)
{
RemoveUnmuteTimerFromDb(guildId, userId); // if unmute errored, just remove unmute from db
Administration._log.Warn("Couldn't unmute user {0} in guild {1}", userId, guildId);
Administration._log.Warn(ex);
}
}, null, after, Timeout.InfiniteTimeSpan);
//add it, or stop the old one and add this one
userUnmuteTimers.AddOrUpdate(userId, (key) => toAdd, (key, old) =>
{
old.Change(Timeout.Infinite, Timeout.Infinite);
return toAdd;
});
}
public static void StopUnmuteTimer(ulong guildId, ulong userId)
{
if (!UnmuteTimers.TryGetValue(guildId, out ConcurrentDictionary<ulong, Timer> userUnmuteTimers)) return;
if (userUnmuteTimers.TryRemove(userId, out Timer removed))
{
removed.Change(Timeout.Infinite, Timeout.Infinite);
}
}
private static void RemoveUnmuteTimerFromDb(ulong guildId, ulong userId)
{
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.GuildConfigs.For(guildId, set => set.Include(x => x.UnmuteTimers));
config.UnmuteTimers.RemoveWhere(x => x.UserId == userId);
uow.Complete();
}
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -257,11 +39,11 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(name)) if (string.IsNullOrWhiteSpace(name))
return; return;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set); var config = uow.GuildConfigs.For(Context.Guild.Id, set => set);
config.MuteRoleName = name; config.MuteRoleName = name;
GuildMuteRoles.AddOrUpdate(Context.Guild.Id, name, (id, old) => name); _service.GuildMuteRoles.AddOrUpdate(Context.Guild.Id, name, (id, old) => name);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
await ReplyConfirmLocalized("mute_role_set").ConfigureAwait(false); await ReplyConfirmLocalized("mute_role_set").ConfigureAwait(false);
@ -283,7 +65,7 @@ namespace NadekoBot.Modules.Administration
{ {
try try
{ {
await MuteUser(user).ConfigureAwait(false); await _service.MuteUser(user).ConfigureAwait(false);
await ReplyConfirmLocalized("user_muted", Format.Bold(user.ToString())).ConfigureAwait(false); await ReplyConfirmLocalized("user_muted", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch
@ -303,7 +85,7 @@ namespace NadekoBot.Modules.Administration
return; return;
try try
{ {
await TimedMute(user, TimeSpan.FromMinutes(minutes)).ConfigureAwait(false); await _service.TimedMute(user, TimeSpan.FromMinutes(minutes)).ConfigureAwait(false);
await ReplyConfirmLocalized("user_muted_time", Format.Bold(user.ToString()), minutes).ConfigureAwait(false); await ReplyConfirmLocalized("user_muted_time", Format.Bold(user.ToString()), minutes).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
@ -321,7 +103,7 @@ namespace NadekoBot.Modules.Administration
{ {
try try
{ {
await UnmuteUser(user).ConfigureAwait(false); await _service.UnmuteUser(user).ConfigureAwait(false);
await ReplyConfirmLocalized("user_unmuted", Format.Bold(user.ToString())).ConfigureAwait(false); await ReplyConfirmLocalized("user_unmuted", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch
@ -337,8 +119,7 @@ namespace NadekoBot.Modules.Administration
{ {
try try
{ {
await user.AddRoleAsync(await GetMuteRole(Context.Guild).ConfigureAwait(false)).ConfigureAwait(false); await _service.MuteUser(user, MuteType.Chat).ConfigureAwait(false);
UserMuted(user, MuteType.Chat);
await ReplyConfirmLocalized("user_chat_mute", Format.Bold(user.ToString())).ConfigureAwait(false); await ReplyConfirmLocalized("user_chat_mute", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch
@ -354,8 +135,7 @@ namespace NadekoBot.Modules.Administration
{ {
try try
{ {
await user.RemoveRoleAsync(await GetMuteRole(Context.Guild).ConfigureAwait(false)).ConfigureAwait(false); await _service.UnmuteUser(user, MuteType.Chat).ConfigureAwait(false);
UserUnmuted(user, MuteType.Chat);
await ReplyConfirmLocalized("user_chat_unmute", Format.Bold(user.ToString())).ConfigureAwait(false); await ReplyConfirmLocalized("user_chat_unmute", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch
@ -367,12 +147,11 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.MuteMembers)] [RequireUserPermission(GuildPermission.MuteMembers)]
public async Task VoiceMute(IGuildUser user) public async Task VoiceMute([Remainder] IGuildUser user)
{ {
try try
{ {
await user.ModifyAsync(usr => usr.Mute = true).ConfigureAwait(false); await _service.MuteUser(user, MuteType.Voice).ConfigureAwait(false);
UserMuted(user, MuteType.Voice);
await ReplyConfirmLocalized("user_voice_mute", Format.Bold(user.ToString())).ConfigureAwait(false); await ReplyConfirmLocalized("user_voice_mute", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch
@ -384,12 +163,11 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.MuteMembers)] [RequireUserPermission(GuildPermission.MuteMembers)]
public async Task VoiceUnmute(IGuildUser user) public async Task VoiceUnmute([Remainder] IGuildUser user)
{ {
try try
{ {
await user.ModifyAsync(usr => usr.Mute = false).ConfigureAwait(false); await _service.UnmuteUser(user, MuteType.Voice).ConfigureAwait(false);
UserUnmuted(user, MuteType.Voice);
await ReplyConfirmLocalized("user_voice_unmute", Format.Bold(user.ToString())).ConfigureAwait(false); await ReplyConfirmLocalized("user_voice_unmute", Format.Bold(user.ToString())).ConfigureAwait(false);
} }
catch catch

View File

@ -15,6 +15,7 @@ using Microsoft.EntityFrameworkCore;
using System.Collections.Immutable; using System.Collections.Immutable;
using NadekoBot.DataStructures; using NadekoBot.DataStructures;
using NLog; using NLog;
using NadekoBot.Services.Administration;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
{ {
@ -23,34 +24,20 @@ namespace NadekoBot.Modules.Administration
[Group] [Group]
public class SelfCommands : NadekoSubmodule public class SelfCommands : NadekoSubmodule
{ {
private static volatile bool _forwardDMs; private readonly DbHandler _db;
private static volatile bool _forwardDMsToAllOwners;
private static readonly object _locker = new object(); private static readonly object _locker = new object();
private readonly SelfService _service;
private readonly DiscordShardedClient _client;
private readonly IImagesService _images;
private new static readonly Logger _log; public SelfCommands(DbHandler db, SelfService service, DiscordShardedClient client,
IImagesService images)
static SelfCommands()
{ {
_log = LogManager.GetCurrentClassLogger(); _db = db;
using (var uow = DbHandler.UnitOfWork()) _service = service;
{ _client = client;
var config = uow.BotConfig.GetOrCreate(); _images = images;
_forwardDMs = config.ForwardMessages;
_forwardDMsToAllOwners = config.ForwardToAllOwners;
}
var _ = Task.Run(async () =>
{
while (!NadekoBot.Ready)
await Task.Delay(1000);
foreach (var cmd in NadekoBot.BotConfig.StartupCommands)
{
await NadekoBot.CommandHandler.ExecuteExternal(cmd.GuildId, cmd.ChannelId, cmd.CommandText);
await Task.Delay(400).ConfigureAwait(false);
}
});
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -69,7 +56,7 @@ namespace NadekoBot.Modules.Administration
VoiceChannelId = guser.VoiceChannel?.Id, VoiceChannelId = guser.VoiceChannel?.Id,
VoiceChannelName = guser.VoiceChannel?.Name, VoiceChannelName = guser.VoiceChannel?.Name,
}; };
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
uow.BotConfig uow.BotConfig
.GetOrCreate(set => set.Include(x => x.StartupCommands)) .GetOrCreate(set => set.Include(x => x.StartupCommands))
@ -96,7 +83,7 @@ namespace NadekoBot.Modules.Administration
return; return;
page -= 1; page -= 1;
IEnumerable<StartupCommand> scmds; IEnumerable<StartupCommand> scmds;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
scmds = uow.BotConfig scmds = uow.BotConfig
.GetOrCreate(set => set.Include(x => x.StartupCommands)) .GetOrCreate(set => set.Include(x => x.StartupCommands))
@ -148,7 +135,7 @@ namespace NadekoBot.Modules.Administration
public async Task StartupCommandRemove([Remainder] string cmdText) public async Task StartupCommandRemove([Remainder] string cmdText)
{ {
StartupCommand cmd; StartupCommand cmd;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var cmds = uow.BotConfig var cmds = uow.BotConfig
.GetOrCreate(set => set.Include(x => x.StartupCommands)) .GetOrCreate(set => set.Include(x => x.StartupCommands))
@ -174,7 +161,7 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task StartupCommandsClear() public async Task StartupCommandsClear()
{ {
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
uow.BotConfig uow.BotConfig
.GetOrCreate(set => set.Include(x => x.StartupCommands)) .GetOrCreate(set => set.Include(x => x.StartupCommands))
@ -190,14 +177,13 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task ForwardMessages() public async Task ForwardMessages()
{ {
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var config = uow.BotConfig.GetOrCreate(); var config = uow.BotConfig.GetOrCreate();
lock (_locker) _service.ForwardDMs = config.ForwardMessages = !config.ForwardMessages;
_forwardDMs = config.ForwardMessages = !config.ForwardMessages;
uow.Complete(); uow.Complete();
} }
if (_forwardDMs) if (_service.ForwardDMs)
await ReplyConfirmLocalized("fwdm_start").ConfigureAwait(false); await ReplyConfirmLocalized("fwdm_start").ConfigureAwait(false);
else else
await ReplyConfirmLocalized("fwdm_stop").ConfigureAwait(false); await ReplyConfirmLocalized("fwdm_stop").ConfigureAwait(false);
@ -207,83 +193,83 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task ForwardToAll() public async Task ForwardToAll()
{ {
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var config = uow.BotConfig.GetOrCreate(); var config = uow.BotConfig.GetOrCreate();
lock (_locker) lock (_locker)
_forwardDMsToAllOwners = config.ForwardToAllOwners = !config.ForwardToAllOwners; _service.ForwardDMsToAllOwners = config.ForwardToAllOwners = !config.ForwardToAllOwners;
uow.Complete(); uow.Complete();
} }
if (_forwardDMsToAllOwners) if (_service.ForwardDMsToAllOwners)
await ReplyConfirmLocalized("fwall_start").ConfigureAwait(false); await ReplyConfirmLocalized("fwall_start").ConfigureAwait(false);
else else
await ReplyConfirmLocalized("fwall_stop").ConfigureAwait(false); await ReplyConfirmLocalized("fwall_stop").ConfigureAwait(false);
} }
public static async Task HandleDmForwarding(IUserMessage msg, ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels) //todo dm forwarding
{ //public async Task HandleDmForwarding(IUserMessage msg, ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels)
if (_forwardDMs && ownerChannels.Length > 0) //{
{ // if (_service.ForwardDMs && ownerChannels.Length > 0)
var title = GetTextStatic("dm_from", // {
NadekoBot.Localization.DefaultCultureInfo, // var title = _strings.GetText("dm_from",
typeof(Administration).Name.ToLowerInvariant()) + // NadekoBot.Localization.DefaultCultureInfo,
$" [{msg.Author}]({msg.Author.Id})"; // typeof(Administration).Name.ToLowerInvariant()) +
// $" [{msg.Author}]({msg.Author.Id})";
var attachamentsTxt = GetTextStatic("attachments", // var attachamentsTxt = GetTextStatic("attachments",
NadekoBot.Localization.DefaultCultureInfo, // NadekoBot.Localization.DefaultCultureInfo,
typeof(Administration).Name.ToLowerInvariant()); // typeof(Administration).Name.ToLowerInvariant());
var toSend = msg.Content; // var toSend = msg.Content;
if (msg.Attachments.Count > 0) // if (msg.Attachments.Count > 0)
{ // {
toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n" + // toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n" +
string.Join("\n", msg.Attachments.Select(a => a.ProxyUrl)); // string.Join("\n", msg.Attachments.Select(a => a.ProxyUrl));
} // }
if (_forwardDMsToAllOwners)
{
var allOwnerChannels = await Task.WhenAll(ownerChannels
.Select(x => x.Value))
.ConfigureAwait(false);
foreach (var ownerCh in allOwnerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id))
{
try
{
await ownerCh.SendConfirmAsync(title, toSend).ConfigureAwait(false);
}
catch
{
_log.Warn("Can't contact owner with id {0}", ownerCh.Recipient.Id);
}
}
}
else
{
var firstOwnerChannel = await ownerChannels[0];
if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
{
try
{
await firstOwnerChannel.SendConfirmAsync(title, toSend).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
}
}
// if (_service.ForwardDMsToAllOwners)
// {
// var allOwnerChannels = await Task.WhenAll(ownerChannels
// .Select(x => x.Value))
// .ConfigureAwait(false);
// foreach (var ownerCh in allOwnerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id))
// {
// try
// {
// await ownerCh.SendConfirmAsync(title, toSend).ConfigureAwait(false);
// }
// catch
// {
// _log.Warn("Can't contact owner with id {0}", ownerCh.Recipient.Id);
// }
// }
// }
// else
// {
// var firstOwnerChannel = await ownerChannels[0];
// if (firstOwnerChannel.Recipient.Id != msg.Author.Id)
// {
// try
// {
// await firstOwnerChannel.SendConfirmAsync(title, toSend).ConfigureAwait(false);
// }
// catch
// {
// // ignored
// }
// }
// }
// }
// }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[OwnerOnly] [OwnerOnly]
public async Task ConnectShard(int shardid) public async Task ConnectShard(int shardid)
{ {
var shard = NadekoBot.Client.GetShard(shardid); var shard = _client.GetShard(shardid);
if (shard == null) if (shard == null)
{ {
@ -307,15 +293,15 @@ namespace NadekoBot.Modules.Administration
public async Task Leave([Remainder] string guildStr) public async Task Leave([Remainder] string guildStr)
{ {
guildStr = guildStr.Trim().ToUpperInvariant(); guildStr = guildStr.Trim().ToUpperInvariant();
var server = NadekoBot.Client.Guilds.FirstOrDefault(g => g.Id.ToString() == guildStr) ?? var server = _client.Guilds.FirstOrDefault(g => g.Id.ToString() == guildStr) ??
NadekoBot.Client.Guilds.FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == guildStr); _client.Guilds.FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == guildStr);
if (server == null) if (server == null)
{ {
await ReplyErrorLocalized("no_server").ConfigureAwait(false); await ReplyErrorLocalized("no_server").ConfigureAwait(false);
return; return;
} }
if (server.OwnerId != NadekoBot.Client.CurrentUser.Id) if (server.OwnerId != _client.CurrentUser.Id)
{ {
await server.LeaveAsync().ConfigureAwait(false); await server.LeaveAsync().ConfigureAwait(false);
await ReplyConfirmLocalized("left_server", Format.Bold(server.Name)).ConfigureAwait(false); await ReplyConfirmLocalized("left_server", Format.Bold(server.Name)).ConfigureAwait(false);
@ -351,7 +337,7 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(newName)) if (string.IsNullOrWhiteSpace(newName))
return; return;
await NadekoBot.Client.CurrentUser.ModifyAsync(u => u.Username = newName).ConfigureAwait(false); await _client.CurrentUser.ModifyAsync(u => u.Username = newName).ConfigureAwait(false);
await ReplyConfirmLocalized("bot_name", Format.Bold(newName)).ConfigureAwait(false); await ReplyConfirmLocalized("bot_name", Format.Bold(newName)).ConfigureAwait(false);
} }
@ -360,7 +346,7 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task SetStatus([Remainder] SettableUserStatus status) public async Task SetStatus([Remainder] SettableUserStatus status)
{ {
await NadekoBot.Client.SetStatusAsync(SettableUserStatusToUserStatus(status)).ConfigureAwait(false); await _client.SetStatusAsync(SettableUserStatusToUserStatus(status)).ConfigureAwait(false);
await ReplyConfirmLocalized("bot_status", Format.Bold(status.ToString())).ConfigureAwait(false); await ReplyConfirmLocalized("bot_status", Format.Bold(status.ToString())).ConfigureAwait(false);
} }
@ -380,7 +366,7 @@ namespace NadekoBot.Modules.Administration
await sr.CopyToAsync(imgStream); await sr.CopyToAsync(imgStream);
imgStream.Position = 0; imgStream.Position = 0;
await NadekoBot.Client.CurrentUser.ModifyAsync(u => u.Avatar = new Image(imgStream)).ConfigureAwait(false); await _client.CurrentUser.ModifyAsync(u => u.Avatar = new Image(imgStream)).ConfigureAwait(false);
} }
} }
@ -391,7 +377,7 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task SetGame([Remainder] string game = null) public async Task SetGame([Remainder] string game = null)
{ {
await NadekoBot.Client.SetGameAsync(game).ConfigureAwait(false); await _client.SetGameAsync(game).ConfigureAwait(false);
await ReplyConfirmLocalized("set_game").ConfigureAwait(false); await ReplyConfirmLocalized("set_game").ConfigureAwait(false);
} }
@ -402,7 +388,7 @@ namespace NadekoBot.Modules.Administration
{ {
name = name ?? ""; name = name ?? "";
await NadekoBot.Client.SetGameAsync(name, url, StreamType.Twitch).ConfigureAwait(false); await _client.SetGameAsync(name, url, StreamType.Twitch).ConfigureAwait(false);
await ReplyConfirmLocalized("set_stream").ConfigureAwait(false); await ReplyConfirmLocalized("set_stream").ConfigureAwait(false);
} }
@ -418,7 +404,7 @@ namespace NadekoBot.Modules.Administration
if (ids.Length != 2) if (ids.Length != 2)
return; return;
var sid = ulong.Parse(ids[0]); var sid = ulong.Parse(ids[0]);
var server = NadekoBot.Client.Guilds.FirstOrDefault(s => s.Id == sid); var server = _client.Guilds.FirstOrDefault(s => s.Id == sid);
if (server == null) if (server == null)
return; return;
@ -455,7 +441,7 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task Announce([Remainder] string message) public async Task Announce([Remainder] string message)
{ {
var channels = NadekoBot.Client.Guilds.Select(g => g.DefaultChannel).ToArray(); var channels = _client.Guilds.Select(g => g.DefaultChannel).ToArray();
if (channels == null) if (channels == null)
return; return;
await Task.WhenAll(channels.Where(c => c != null).Select(c => c.SendConfirmAsync(GetText("message_from_bo", Context.User.ToString()), message))) await Task.WhenAll(channels.Where(c => c != null).Select(c => c.SendConfirmAsync(GetText("message_from_bo", Context.User.ToString()), message)))
@ -468,7 +454,7 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly] [OwnerOnly]
public async Task ReloadImages() public async Task ReloadImages()
{ {
var time = await NadekoBot.Images.Reload().ConfigureAwait(false); var time = _images.Reload();
await ReplyConfirmLocalized("images_loaded", time.TotalSeconds.ToString("F3")).ConfigureAwait(false); await ReplyConfirmLocalized("images_loaded", time.TotalSeconds.ToString("F3")).ConfigureAwait(false);
} }

View File

@ -1,14 +1,9 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.DataStructures;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
@ -18,158 +13,13 @@ namespace NadekoBot.Modules.Administration
[Group] [Group]
public class ServerGreetCommands : NadekoSubmodule public class ServerGreetCommands : NadekoSubmodule
{ {
//make this to a field in the guildconfig table private readonly GreetSettingsService _greetService;
private readonly DbHandler _db;
private new static Logger _log { get; } public ServerGreetCommands(GreetSettingsService greetService, DbHandler db)
private static readonly GreetSettingsService greetService;
static ServerGreetCommands()
{ {
NadekoBot.Client.UserJoined += UserJoined; _greetService = greetService;
NadekoBot.Client.UserLeft += UserLeft; _db = db;
_log = LogManager.GetCurrentClassLogger();
//todo di
greetService = NadekoBot.GreetSettingsService;
}
private static Task UserLeft(IGuildUser user)
{
var _ = Task.Run(async () =>
{
try
{
var conf = greetService.GetOrAddSettingsForGuild(user.GuildId);
if (!conf.SendChannelByeMessage) return;
var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.ByeMessageChannelId);
if (channel == null) //maybe warn the server owner that the channel is missing
return;
CREmbed embedData;
if (CREmbed.TryParse(conf.ChannelByeMessageText, out embedData))
{
embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Description = embedData.Description?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Title = embedData.Title?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
try
{
var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
if (conf.AutoDeleteByeMessagesTimer > 0)
{
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
}
}
catch (Exception ex) { _log.Warn(ex); }
}
else
{
var msg = conf.ChannelByeMessageText.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
if (string.IsNullOrWhiteSpace(msg))
return;
try
{
var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false);
if (conf.AutoDeleteByeMessagesTimer > 0)
{
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
}
}
catch (Exception ex) { _log.Warn(ex); }
}
}
catch
{
// ignored
}
});
return Task.CompletedTask;
}
private static Task UserJoined(IGuildUser user)
{
var _ = Task.Run(async () =>
{
try
{
var conf = greetService.GetOrAddSettingsForGuild(user.GuildId);
if (conf.SendChannelGreetMessage)
{
var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.GreetMessageChannelId);
if (channel != null) //maybe warn the server owner that the channel is missing
{
CREmbed embedData;
if (CREmbed.TryParse(conf.ChannelGreetMessageText, out embedData))
{
embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Description = embedData.Description?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
try
{
var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
if (conf.AutoDeleteGreetMessagesTimer > 0)
{
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
}
}
catch (Exception ex) { _log.Warn(ex); }
}
else
{
var msg = conf.ChannelGreetMessageText.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
if (!string.IsNullOrWhiteSpace(msg))
{
try
{
var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false);
if (conf.AutoDeleteGreetMessagesTimer > 0)
{
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
}
}
catch (Exception ex) { _log.Warn(ex); }
}
}
}
}
if (conf.SendDmGreetMessage)
{
var channel = await user.CreateDMChannelAsync();
if (channel != null)
{
CREmbed embedData;
if (CREmbed.TryParse(conf.ChannelGreetMessageText, out embedData))
{
embedData.PlainText = embedData.PlainText?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Description = embedData.Description?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
try
{
await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
}
else
{
var msg = conf.DmGreetMessageText.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
if (!string.IsNullOrWhiteSpace(msg))
{
await channel.SendConfirmAsync(msg).ConfigureAwait(false);
}
}
}
}
}
catch
{
// ignored
}
});
return Task.CompletedTask;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -180,7 +30,7 @@ namespace NadekoBot.Modules.Administration
if (timer < 0 || timer > 600) if (timer < 0 || timer > 600)
return; return;
await ServerGreetCommands.SetGreetDel(Context.Guild.Id, timer).ConfigureAwait(false); await _greetService.SetGreetDel(Context.Guild.Id, timer).ConfigureAwait(false);
if (timer > 0) if (timer > 0)
await ReplyConfirmLocalized("greetdel_on", timer).ConfigureAwait(false); await ReplyConfirmLocalized("greetdel_on", timer).ConfigureAwait(false);
@ -188,29 +38,12 @@ namespace NadekoBot.Modules.Administration
await ReplyConfirmLocalized("greetdel_off").ConfigureAwait(false); await ReplyConfirmLocalized("greetdel_off").ConfigureAwait(false);
} }
private static async Task SetGreetDel(ulong id, int timer)
{
if (timer < 0 || timer > 600)
return;
using (var uow = DbHandler.UnitOfWork())
{
var conf = uow.GuildConfigs.For(id, set => set);
conf.AutoDeleteGreetMessagesTimer = timer;
var toAdd = GreetSettings.Create(conf);
greetService.GuildConfigsCache.AddOrUpdate(id, toAdd, (key, old) => toAdd);
await uow.CompleteAsync().ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageGuild)] [RequireUserPermission(GuildPermission.ManageGuild)]
public async Task Greet() public async Task Greet()
{ {
var enabled = await greetService.SetGreet(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false); var enabled = await _greetService.SetGreet(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false);
if (enabled) if (enabled)
await ReplyConfirmLocalized("greet_on").ConfigureAwait(false); await ReplyConfirmLocalized("greet_on").ConfigureAwait(false);
@ -226,7 +59,7 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(text)) if (string.IsNullOrWhiteSpace(text))
{ {
string channelGreetMessageText; string channelGreetMessageText;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
channelGreetMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelGreetMessageText; channelGreetMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelGreetMessageText;
} }
@ -234,7 +67,7 @@ namespace NadekoBot.Modules.Administration
return; return;
} }
var sendGreetEnabled = greetService.SetGreetMessage(Context.Guild.Id, ref text); var sendGreetEnabled = _greetService.SetGreetMessage(Context.Guild.Id, ref text);
await ReplyConfirmLocalized("greetmsg_new").ConfigureAwait(false); await ReplyConfirmLocalized("greetmsg_new").ConfigureAwait(false);
if (!sendGreetEnabled) if (!sendGreetEnabled)
@ -246,7 +79,7 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageGuild)] [RequireUserPermission(GuildPermission.ManageGuild)]
public async Task GreetDm() public async Task GreetDm()
{ {
var enabled = await greetService.SetGreetDm(Context.Guild.Id).ConfigureAwait(false); var enabled = await _greetService.SetGreetDm(Context.Guild.Id).ConfigureAwait(false);
if (enabled) if (enabled)
await ReplyConfirmLocalized("greetdm_on").ConfigureAwait(false); await ReplyConfirmLocalized("greetdm_on").ConfigureAwait(false);
@ -262,7 +95,7 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(text)) if (string.IsNullOrWhiteSpace(text))
{ {
GuildConfig config; GuildConfig config;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
config = uow.GuildConfigs.For(Context.Guild.Id); config = uow.GuildConfigs.For(Context.Guild.Id);
} }
@ -270,7 +103,7 @@ namespace NadekoBot.Modules.Administration
return; return;
} }
var sendGreetEnabled = greetService.SetGreetDmMessage(Context.Guild.Id, ref text); var sendGreetEnabled = _greetService.SetGreetDmMessage(Context.Guild.Id, ref text);
await ReplyConfirmLocalized("greetdmmsg_new").ConfigureAwait(false); await ReplyConfirmLocalized("greetdmmsg_new").ConfigureAwait(false);
if (!sendGreetEnabled) if (!sendGreetEnabled)
@ -282,7 +115,7 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageGuild)] [RequireUserPermission(GuildPermission.ManageGuild)]
public async Task Bye() public async Task Bye()
{ {
var enabled = await greetService.SetBye(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false); var enabled = await _greetService.SetBye(Context.Guild.Id, Context.Channel.Id).ConfigureAwait(false);
if (enabled) if (enabled)
await ReplyConfirmLocalized("bye_on").ConfigureAwait(false); await ReplyConfirmLocalized("bye_on").ConfigureAwait(false);
@ -298,7 +131,7 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(text)) if (string.IsNullOrWhiteSpace(text))
{ {
string byeMessageText; string byeMessageText;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
byeMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelByeMessageText; byeMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelByeMessageText;
} }
@ -306,7 +139,7 @@ namespace NadekoBot.Modules.Administration
return; return;
} }
var sendByeEnabled = greetService.SetByeMessage(Context.Guild.Id, ref text); var sendByeEnabled = _greetService.SetByeMessage(Context.Guild.Id, ref text);
await ReplyConfirmLocalized("byemsg_new").ConfigureAwait(false); await ReplyConfirmLocalized("byemsg_new").ConfigureAwait(false);
if (!sendByeEnabled) if (!sendByeEnabled)
@ -318,7 +151,7 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageGuild)] [RequireUserPermission(GuildPermission.ManageGuild)]
public async Task ByeDel(int timer = 30) public async Task ByeDel(int timer = 30)
{ {
await greetService.SetByeDel(Context.Guild.Id, timer).ConfigureAwait(false); await _greetService.SetByeDel(Context.Guild.Id, timer).ConfigureAwait(false);
if (timer > 0) if (timer > 0)
await ReplyConfirmLocalized("byedel_on", timer).ConfigureAwait(false); await ReplyConfirmLocalized("byedel_on", timer).ConfigureAwait(false);

View File

@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Administration;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -18,6 +19,16 @@ namespace NadekoBot.Modules.Administration
[Group] [Group]
public class UserPunishCommands : NadekoSubmodule public class UserPunishCommands : NadekoSubmodule
{ {
private readonly DbHandler _db;
private readonly MuteService _muteService;
public UserPunishCommands(DbHandler db, MuteService muteService)
{
_db = db;
_muteService = muteService;
}
private async Task<PunishmentAction?> InternalWarn(IGuild guild, ulong userId, string modName, string reason) private async Task<PunishmentAction?> InternalWarn(IGuild guild, ulong userId, string modName, string reason)
{ {
if (string.IsNullOrWhiteSpace(reason)) if (string.IsNullOrWhiteSpace(reason))
@ -36,7 +47,7 @@ namespace NadekoBot.Modules.Administration
int warnings = 1; int warnings = 1;
List<WarningPunishment> ps; List<WarningPunishment> ps;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
ps = uow.GuildConfigs.For(guildId, set => set.Include(x => x.WarnPunishments)) ps = uow.GuildConfigs.For(guildId, set => set.Include(x => x.WarnPunishments))
.WarnPunishments; .WarnPunishments;
@ -62,9 +73,9 @@ namespace NadekoBot.Modules.Administration
{ {
case PunishmentAction.Mute: case PunishmentAction.Mute:
if (p.Time == 0) if (p.Time == 0)
await MuteCommands.MuteUser(user).ConfigureAwait(false); await _muteService.MuteUser(user).ConfigureAwait(false);
else else
await MuteCommands.TimedMute(user, TimeSpan.FromMinutes(p.Time)).ConfigureAwait(false); await _muteService.TimedMute(user, TimeSpan.FromMinutes(p.Time)).ConfigureAwait(false);
break; break;
case PunishmentAction.Kick: case PunishmentAction.Kick:
await user.KickAsync().ConfigureAwait(false); await user.KickAsync().ConfigureAwait(false);
@ -147,7 +158,7 @@ namespace NadekoBot.Modules.Administration
if (page < 0) if (page < 0)
return; return;
Warning[] warnings; Warning[] warnings;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
warnings = uow.Warnings.For(Context.Guild.Id, userId); warnings = uow.Warnings.For(Context.Guild.Id, userId);
} }
@ -192,7 +203,7 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.BanMembers)] [RequireUserPermission(GuildPermission.BanMembers)]
public async Task Warnclear(ulong userId) public async Task Warnclear(ulong userId)
{ {
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
await uow.Warnings.ForgiveAll(Context.Guild.Id, userId, Context.User.ToString()).ConfigureAwait(false); await uow.Warnings.ForgiveAll(Context.Guild.Id, userId, Context.User.ToString()).ConfigureAwait(false);
uow.Complete(); uow.Complete();
@ -212,7 +223,7 @@ namespace NadekoBot.Modules.Administration
if (number <= 0) if (number <= 0)
return; return;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var ps = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.WarnPunishments)).WarnPunishments; var ps = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
ps.RemoveAll(x => x.Count == number); ps.RemoveAll(x => x.Count == number);
@ -239,7 +250,7 @@ namespace NadekoBot.Modules.Administration
if (number <= 0) if (number <= 0)
return; return;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var ps = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.WarnPunishments)).WarnPunishments; var ps = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.WarnPunishments)).WarnPunishments;
var p = ps.FirstOrDefault(x => x.Count == number); var p = ps.FirstOrDefault(x => x.Count == number);
@ -260,7 +271,7 @@ namespace NadekoBot.Modules.Administration
public async Task WarnPunishList() public async Task WarnPunishList()
{ {
WarningPunishment[] ps; WarningPunishment[] ps;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
ps = uow.GuildConfigs.For(Context.Guild.Id, gc => gc.Include(x => x.WarnPunishments)) ps = uow.GuildConfigs.For(Context.Guild.Id, gc => gc.Include(x => x.WarnPunishments))
.WarnPunishments .WarnPunishments

View File

@ -10,6 +10,7 @@ using Microsoft.EntityFrameworkCore;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Administration;
namespace NadekoBot.Modules.Administration namespace NadekoBot.Modules.Administration
{ {
@ -18,86 +19,13 @@ namespace NadekoBot.Modules.Administration
[Group] [Group]
public class VcRoleCommands : NadekoSubmodule public class VcRoleCommands : NadekoSubmodule
{ {
private static ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>> VcRoles { get; } private readonly VcRoleService _service;
private readonly DbHandler _db;
static VcRoleCommands() public VcRoleCommands(VcRoleService service, DbHandler db)
{ {
NadekoBot.Client.UserVoiceStateUpdated += ClientOnUserVoiceStateUpdated; _service = service;
VcRoles = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>>(); _db = db;
foreach (var gconf in NadekoBot.AllGuildConfigs)
{
var g = NadekoBot.Client.GetGuild(gconf.GuildId);
if (g == null)
continue; //todo delete everything from db if guild doesn't exist?
var infos = new ConcurrentDictionary<ulong, IRole>();
VcRoles.TryAdd(gconf.GuildId, infos);
foreach (var ri in gconf.VcRoleInfos)
{
var role = g.GetRole(ri.RoleId);
if (role == null)
continue; //todo remove this entry from db
infos.TryAdd(ri.VoiceChannelId, role);
}
}
}
private static Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState,
SocketVoiceState newState)
{
var gusr = usr as SocketGuildUser;
if (gusr == null)
return Task.CompletedTask;
var oldVc = oldState.VoiceChannel;
var newVc = newState.VoiceChannel;
var _ = Task.Run(async () =>
{
try
{
if (oldVc != newVc)
{
ulong guildId;
guildId = newVc?.Guild.Id ?? oldVc.Guild.Id;
if (VcRoles.TryGetValue(guildId, out ConcurrentDictionary<ulong, IRole> guildVcRoles))
{
//remove old
if (oldVc != null && guildVcRoles.TryGetValue(oldVc.Id, out IRole role))
{
if (gusr.Roles.Contains(role))
{
try
{
await gusr.RemoveRoleAsync(role).ConfigureAwait(false);
await Task.Delay(500).ConfigureAwait(false);
}
catch
{
await Task.Delay(200).ConfigureAwait(false);
await gusr.RemoveRoleAsync(role).ConfigureAwait(false);
await Task.Delay(500).ConfigureAwait(false);
}
}
}
//add new
if (newVc != null && guildVcRoles.TryGetValue(newVc.Id, out role))
{
if (!gusr.Roles.Contains(role))
await gusr.AddRoleAsync(role).ConfigureAwait(false);
}
}
}
}
catch (Exception ex)
{
Administration._log.Warn(ex);
}
});
return Task.CompletedTask;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -118,14 +46,14 @@ namespace NadekoBot.Modules.Administration
return; return;
} }
var guildVcRoles = VcRoles.GetOrAdd(user.GuildId, new ConcurrentDictionary<ulong, IRole>()); var guildVcRoles = _service.VcRoles.GetOrAdd(user.GuildId, new ConcurrentDictionary<ulong, IRole>());
if (role == null) if (role == null)
{ {
if (guildVcRoles.TryRemove(vc.Id, out role)) if (guildVcRoles.TryRemove(vc.Id, out role))
{ {
await ReplyConfirmLocalized("vcrole_removed", Format.Bold(vc.Name)).ConfigureAwait(false); await ReplyConfirmLocalized("vcrole_removed", Format.Bold(vc.Name)).ConfigureAwait(false);
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.VcRoleInfos)); var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.VcRoleInfos));
conf.VcRoleInfos.RemoveWhere(x => x.VoiceChannelId == vc.Id); conf.VcRoleInfos.RemoveWhere(x => x.VoiceChannelId == vc.Id);
@ -136,7 +64,7 @@ namespace NadekoBot.Modules.Administration
else else
{ {
guildVcRoles.AddOrUpdate(vc.Id, role, (key, old) => role); guildVcRoles.AddOrUpdate(vc.Id, role, (key, old) => role);
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.VcRoleInfos)); var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.VcRoleInfos));
conf.VcRoleInfos.RemoveWhere(x => x.VoiceChannelId == vc.Id); // remove old one conf.VcRoleInfos.RemoveWhere(x => x.VoiceChannelId == vc.Id); // remove old one
@ -157,7 +85,7 @@ namespace NadekoBot.Modules.Administration
{ {
var guild = (SocketGuild) Context.Guild; var guild = (SocketGuild) Context.Guild;
string text; string text;
if (VcRoles.TryGetValue(Context.Guild.Id, out ConcurrentDictionary<ulong, IRole> roles)) if (_service.VcRoles.TryGetValue(Context.Guild.Id, out ConcurrentDictionary<ulong, IRole> roles))
{ {
if (!roles.Any()) if (!roles.Any())
{ {

View File

@ -4,6 +4,7 @@ using Discord.WebSocket;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Administration;
using NLog; using NLog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -21,140 +22,15 @@ namespace NadekoBot.Modules.Administration
[Group] [Group]
public class VoicePlusTextCommands : NadekoSubmodule public class VoicePlusTextCommands : NadekoSubmodule
{ {
private new static readonly Logger _log; private readonly VplusTService _service;
private readonly DbHandler _db;
private static readonly Regex _channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled); public VoicePlusTextCommands(VplusTService service, DbHandler db)
private static readonly ConcurrentHashSet<ulong> _voicePlusTextCache;
private static readonly ConcurrentDictionary<ulong, SemaphoreSlim> _guildLockObjects = new ConcurrentDictionary<ulong, SemaphoreSlim>();
static VoicePlusTextCommands()
{ {
_log = LogManager.GetCurrentClassLogger(); _service = service;
var sw = Stopwatch.StartNew(); _db = db;
_voicePlusTextCache = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs.Where(g => g.VoicePlusTextEnabled).Select(g => g.GuildId));
NadekoBot.Client.UserVoiceStateUpdated += UserUpdatedEventHandler;
sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
} }
private static Task UserUpdatedEventHandler(SocketUser iuser, SocketVoiceState before, SocketVoiceState after)
{
var user = (iuser as SocketGuildUser);
var guild = user?.Guild;
if (guild == null)
return Task.CompletedTask;
var botUserPerms = guild.CurrentUser.GuildPermissions;
if (before.VoiceChannel == after.VoiceChannel)
return Task.CompletedTask;
if (!_voicePlusTextCache.Contains(guild.Id))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
if (!botUserPerms.ManageChannels || !botUserPerms.ManageRoles)
{
try
{
await guild.Owner.SendErrorAsync(
GetTextStatic("vt_exit",
NadekoBot.Localization.GetCultureInfo(guild),
typeof(Administration).Name.ToLowerInvariant(),
Format.Bold(guild.Name))).ConfigureAwait(false);
}
catch
{
// ignored
}
using (var uow = DbHandler.UnitOfWork())
{
uow.GuildConfigs.For(guild.Id, set => set).VoicePlusTextEnabled = false;
_voicePlusTextCache.TryRemove(guild.Id);
await uow.CompleteAsync().ConfigureAwait(false);
}
return;
}
var semaphore = _guildLockObjects.GetOrAdd(guild.Id, (key) => new SemaphoreSlim(1, 1));
try
{
await semaphore.WaitAsync().ConfigureAwait(false);
var beforeVch = before.VoiceChannel;
if (beforeVch != null)
{
var beforeRoleName = GetRoleName(beforeVch);
var beforeRole = guild.Roles.FirstOrDefault(x => x.Name == beforeRoleName);
if (beforeRole != null)
try
{
_log.Info("Removing role " + beforeRoleName + " from user " + user.Username);
await user.RemoveRoleAsync(beforeRole).ConfigureAwait(false);
await Task.Delay(200).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
var afterVch = after.VoiceChannel;
if (afterVch != null && guild.AFKChannel?.Id != afterVch.Id)
{
var roleName = GetRoleName(afterVch);
var roleToAdd = guild.Roles.FirstOrDefault(x => x.Name == roleName) ??
(IRole) await guild.CreateRoleAsync(roleName, GuildPermissions.None).ConfigureAwait(false);
ITextChannel textChannel = guild.TextChannels
.FirstOrDefault(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant());
if (textChannel == null)
{
var created = (await guild.CreateTextChannelAsync(GetChannelName(afterVch.Name).ToLowerInvariant()).ConfigureAwait(false));
try { await guild.CurrentUser.AddRoleAsync(roleToAdd).ConfigureAwait(false); } catch {/*ignored*/}
await Task.Delay(50).ConfigureAwait(false);
await created.AddPermissionOverwriteAsync(roleToAdd, new OverwritePermissions(
readMessages: PermValue.Allow,
sendMessages: PermValue.Allow))
.ConfigureAwait(false);
await Task.Delay(50).ConfigureAwait(false);
await created.AddPermissionOverwriteAsync(guild.EveryoneRole, new OverwritePermissions(
readMessages: PermValue.Deny,
sendMessages: PermValue.Deny))
.ConfigureAwait(false);
await Task.Delay(50).ConfigureAwait(false);
}
_log.Warn("Adding role " + roleToAdd.Name + " to user " + user.Username);
await user.AddRoleAsync(roleToAdd).ConfigureAwait(false);
}
}
finally
{
semaphore.Release();
}
}
catch (Exception ex)
{
_log.Warn(ex);
}
});
return Task.CompletedTask;
}
private static string GetChannelName(string voiceName) =>
_channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice";
private static string GetRoleName(IVoiceChannel ch) =>
"nvoice-" + ch.Id;
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)] [RequireUserPermission(GuildPermission.ManageRoles)]
@ -184,7 +60,7 @@ namespace NadekoBot.Modules.Administration
try try
{ {
bool isEnabled; bool isEnabled;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var conf = uow.GuildConfigs.For(guild.Id, set => set); var conf = uow.GuildConfigs.For(guild.Id, set => set);
isEnabled = conf.VoicePlusTextEnabled = !conf.VoicePlusTextEnabled; isEnabled = conf.VoicePlusTextEnabled = !conf.VoicePlusTextEnabled;
@ -192,7 +68,7 @@ namespace NadekoBot.Modules.Administration
} }
if (!isEnabled) if (!isEnabled)
{ {
_voicePlusTextCache.TryRemove(guild.Id); _service.VoicePlusTextCache.TryRemove(guild.Id);
foreach (var textChannel in (await guild.GetTextChannelsAsync().ConfigureAwait(false)).Where(c => c.Name.EndsWith("-voice"))) foreach (var textChannel in (await guild.GetTextChannelsAsync().ConfigureAwait(false)).Where(c => c.Name.EndsWith("-voice")))
{ {
try { await textChannel.DeleteAsync().ConfigureAwait(false); } catch { } try { await textChannel.DeleteAsync().ConfigureAwait(false); } catch { }
@ -207,7 +83,7 @@ namespace NadekoBot.Modules.Administration
await ReplyConfirmLocalized("vt_disabled").ConfigureAwait(false); await ReplyConfirmLocalized("vt_disabled").ConfigureAwait(false);
return; return;
} }
_voicePlusTextCache.Add(guild.Id); _service.VoicePlusTextCache.Add(guild.Id);
await ReplyConfirmLocalized("vt_enabled").ConfigureAwait(false); await ReplyConfirmLocalized("vt_enabled").ConfigureAwait(false);
} }
@ -236,7 +112,7 @@ namespace NadekoBot.Modules.Administration
var voiceChannels = await guild.GetVoiceChannelsAsync().ConfigureAwait(false); var voiceChannels = await guild.GetVoiceChannelsAsync().ConfigureAwait(false);
var boundTextChannels = textChannels.Where(c => c.Name.EndsWith("-voice")); var boundTextChannels = textChannels.Where(c => c.Name.EndsWith("-voice"));
var validTxtChannelNames = new HashSet<string>(voiceChannels.Select(c => GetChannelName(c.Name).ToLowerInvariant())); var validTxtChannelNames = new HashSet<string>(voiceChannels.Select(c => _service.GetChannelName(c.Name).ToLowerInvariant()));
var invalidTxtChannels = boundTextChannels.Where(c => !validTxtChannelNames.Contains(c.Name)); var invalidTxtChannels = boundTextChannels.Where(c => !validTxtChannelNames.Contains(c.Name));
foreach (var c in invalidTxtChannels) foreach (var c in invalidTxtChannels)
@ -246,7 +122,7 @@ namespace NadekoBot.Modules.Administration
} }
var boundRoles = guild.Roles.Where(r => r.Name.StartsWith("nvoice-")); var boundRoles = guild.Roles.Where(r => r.Name.StartsWith("nvoice-"));
var validRoleNames = new HashSet<string>(voiceChannels.Select(c => GetRoleName(c).ToLowerInvariant())); var validRoleNames = new HashSet<string>(voiceChannels.Select(c => _service.GetRoleName(c).ToLowerInvariant()));
var invalidRoles = boundRoles.Where(r => !validRoleNames.Contains(r.Name)); var invalidRoles = boundRoles.Where(r => !validRoleNames.Contains(r.Name));
foreach (var r in invalidRoles) foreach (var r in invalidRoles)

View File

@ -21,9 +21,16 @@ namespace NadekoBot.Modules.Games
[Group] [Group]
public class Acropobia : NadekoSubmodule public class Acropobia : NadekoSubmodule
{ {
private readonly DiscordShardedClient _client;
//channelId, game //channelId, game
public static ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>(); public static ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>();
public Acropobia(DiscordShardedClient client)
{
_client = client;
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Acro(int time = 60) public async Task Acro(int time = 60)
@ -32,7 +39,7 @@ namespace NadekoBot.Modules.Games
return; return;
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
var game = new AcrophobiaGame(channel, time); var game = new AcrophobiaGame(_client, _strings, channel, time);
if (AcrophobiaGames.TryAdd(channel.Id, game)) if (AcrophobiaGames.TryAdd(channel.Id, game))
{ {
try try
@ -59,6 +66,7 @@ namespace NadekoBot.Modules.Games
Voting Voting
} }
//todo Isolate, this shouldn't print or anything like that.
public class AcrophobiaGame public class AcrophobiaGame
{ {
private readonly ITextChannel _channel; private readonly ITextChannel _channel;
@ -79,10 +87,14 @@ namespace NadekoBot.Modules.Games
//text, votes //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; private readonly Logger _log;
private readonly DiscordShardedClient _client;
private readonly NadekoStrings _strings;
public AcrophobiaGame(ITextChannel channel, int time) public AcrophobiaGame(DiscordShardedClient client, NadekoStrings strings, ITextChannel channel, int time)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_client = client;
_strings = strings;
_channel = channel; _channel = channel;
_time = time; _time = time;
@ -123,7 +135,7 @@ $@"--
public async Task Run() public async Task Run()
{ {
NadekoBot.Client.MessageReceived += PotentialAcro; _client.MessageReceived += PotentialAcro;
var embed = GetEmbed(); var embed = GetEmbed();
//SUBMISSIONS PHASE //SUBMISSIONS PHASE
@ -292,14 +304,14 @@ $@"--
public void EnsureStopped() public void EnsureStopped()
{ {
NadekoBot.Client.MessageReceived -= PotentialAcro; _client.MessageReceived -= PotentialAcro;
if (!_source.IsCancellationRequested) if (!_source.IsCancellationRequested)
_source.Cancel(); _source.Cancel();
} }
private string GetText(string key, params object[] replacements) private string GetText(string key, params object[] replacements)
=> GetTextStatic(key, => _strings.GetText(key,
NadekoBot.Localization.GetCultureInfo(_channel.Guild), _channel.Guild.Id,
typeof(Games).Name.ToLowerInvariant(), typeof(Games).Name.ToLowerInvariant(),
replacements); replacements);
} }

View File

@ -1,16 +1,10 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NLog;
using System; using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using NadekoBot.Services.Games;
namespace NadekoBot.Modules.Games namespace NadekoBot.Modules.Games
{ {
@ -19,72 +13,13 @@ namespace NadekoBot.Modules.Games
[Group] [Group]
public class CleverBotCommands : NadekoSubmodule public class CleverBotCommands : NadekoSubmodule
{ {
private new static Logger _log { get; } private readonly DbHandler _db;
private readonly GamesService _games;
public static ConcurrentDictionary<ulong, Lazy<ChatterBotSession>> CleverbotGuilds { get; } public CleverBotCommands(DbHandler db, GamesService games)
static CleverBotCommands()
{ {
_log = LogManager.GetCurrentClassLogger(); _db = db;
var sw = Stopwatch.StartNew(); _games = games;
CleverbotGuilds = new ConcurrentDictionary<ulong, Lazy<ChatterBotSession>>(
NadekoBot.AllGuildConfigs
.Where(gc => gc.CleverbotEnabled)
.ToDictionary(gc => gc.GuildId, gc => new Lazy<ChatterBotSession>(() => new ChatterBotSession(gc.GuildId), true)));
sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
}
public static string PrepareMessage(IUserMessage msg, out ChatterBotSession cleverbot)
{
var channel = msg.Channel as ITextChannel;
cleverbot = null;
if (channel == null)
return null;
Lazy<ChatterBotSession> lazyCleverbot;
if (!CleverbotGuilds.TryGetValue(channel.Guild.Id, out lazyCleverbot))
return null;
cleverbot = lazyCleverbot.Value;
var nadekoId = NadekoBot.Client.CurrentUser.Id;
var normalMention = $"<@{nadekoId}> ";
var nickMention = $"<@!{nadekoId}> ";
string message;
if (msg.Content.StartsWith(normalMention))
{
message = msg.Content.Substring(normalMention.Length).Trim();
}
else if (msg.Content.StartsWith(nickMention))
{
message = msg.Content.Substring(nickMention.Length).Trim();
}
else
{
return null;
}
return message;
}
public static async Task<bool> TryAsk(ChatterBotSession cleverbot, ITextChannel channel, string message)
{
await channel.TriggerTypingAsync().ConfigureAwait(false);
var response = await cleverbot.Think(message).ConfigureAwait(false);
try
{
await channel.SendConfirmAsync(response.SanitizeMentions()).ConfigureAwait(false);
}
catch
{
await channel.SendConfirmAsync(response.SanitizeMentions()).ConfigureAwait(false); // try twice :\
}
return true;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -94,10 +29,9 @@ namespace NadekoBot.Modules.Games
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
Lazy<ChatterBotSession> throwaway; if (_games.CleverbotGuilds.TryRemove(channel.Guild.Id, out Lazy<ChatterBotSession> throwaway))
if (CleverbotGuilds.TryRemove(channel.Guild.Id, out throwaway))
{ {
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
uow.GuildConfigs.SetCleverbotEnabled(Context.Guild.Id, false); uow.GuildConfigs.SetCleverbotEnabled(Context.Guild.Id, false);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
@ -106,9 +40,9 @@ namespace NadekoBot.Modules.Games
return; return;
} }
CleverbotGuilds.TryAdd(channel.Guild.Id, new Lazy<ChatterBotSession>(() => new ChatterBotSession(Context.Guild.Id), true)); _games.CleverbotGuilds.TryAdd(channel.Guild.Id, new Lazy<ChatterBotSession>(() => new ChatterBotSession(Context.Guild.Id), true));
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
uow.GuildConfigs.SetCleverbotEnabled(Context.Guild.Id, true); uow.GuildConfigs.SetCleverbotEnabled(Context.Guild.Id, true);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
@ -118,41 +52,6 @@ namespace NadekoBot.Modules.Games
} }
} }
public class ChatterBotSession
{
private static NadekoRandom rng { get; } = new NadekoRandom();
public string ChatterbotId { get; }
public string ChannelId { get; }
private int _botId = 6;
public ChatterBotSession(ulong channelId)
{
ChannelId = channelId.ToString().ToBase64();
ChatterbotId = rng.Next(0, 1000000).ToString().ToBase64();
}
private string apiEndpoint => "http://api.program-o.com/v2/chatbot/" +
$"?bot_id={_botId}&" +
"say={0}&" +
$"convo_id=nadekobot_{ChatterbotId}_{ChannelId}&" +
"format=json";
public async Task<string> Think(string message)
{
using (var http = new HttpClient())
{
var res = await http.GetStringAsync(string.Format(apiEndpoint, message)).ConfigureAwait(false);
var cbr = JsonConvert.DeserializeObject<ChatterBotResponse>(res);
//Console.WriteLine(cbr.Convo_id);
return cbr.BotSay.Replace("<br/>", "\n");
}
}
}
public class ChatterBotResponse
{
public string Convo_id { get; set; }
public string BotSay { get; set; }
}
} }
} }

View File

@ -56,6 +56,7 @@ namespace NadekoBot.Modules.Games.Hangman
public class HangmanGame: IDisposable public class HangmanGame: IDisposable
{ {
private readonly Logger _log; private readonly Logger _log;
private readonly DiscordShardedClient _client;
public IMessageChannel GameChannel { get; } public IMessageChannel GameChannel { get; }
public HashSet<char> Guesses { get; } = new HashSet<char>(); public HashSet<char> Guesses { get; } = new HashSet<char>();
@ -81,9 +82,11 @@ namespace NadekoBot.Modules.Games.Hangman
public event Action<HangmanGame> OnEnded; public event Action<HangmanGame> OnEnded;
public HangmanGame(IMessageChannel channel, string type) public HangmanGame(DiscordShardedClient client, IMessageChannel channel, string type)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_client = client;
this.GameChannel = channel; this.GameChannel = channel;
this.TermType = type.ToTitleCase(); this.TermType = type.ToTitleCase();
} }
@ -95,12 +98,12 @@ namespace NadekoBot.Modules.Games.Hangman
if (this.Term == null) if (this.Term == null)
throw new KeyNotFoundException("Can't find a term with that type. Use hangmanlist command."); throw new KeyNotFoundException("Can't find a term with that type. Use hangmanlist command.");
// start listening for answers when game starts // start listening for answers when game starts
NadekoBot.Client.MessageReceived += PotentialGuess; _client.MessageReceived += PotentialGuess;
} }
public async Task End() public async Task End()
{ {
NadekoBot.Client.MessageReceived -= PotentialGuess; _client.MessageReceived -= PotentialGuess;
OnEnded(this); OnEnded(this);
var toSend = "Game ended. You **" + (Errors >= MaxErrors ? "LOSE" : "WIN") + "**!\n" + GetHangman(); var toSend = "Game ended. You **" + (Errors >= MaxErrors ? "LOSE" : "WIN") + "**!\n" + GetHangman();
var embed = new EmbedBuilder().WithTitle("Hangman Game") var embed = new EmbedBuilder().WithTitle("Hangman Game")
@ -199,7 +202,7 @@ namespace NadekoBot.Modules.Games.Hangman
public void Dispose() public void Dispose()
{ {
NadekoBot.Client.MessageReceived -= PotentialGuess; _client.MessageReceived -= PotentialGuess;
OnEnded = null; OnEnded = null;
} }
} }

View File

@ -6,6 +6,7 @@ using System.Collections.Concurrent;
using System.Threading.Tasks; using System.Threading.Tasks;
using NadekoBot.Modules.Games.Hangman; using NadekoBot.Modules.Games.Hangman;
using Discord; using Discord;
using Discord.WebSocket;
namespace NadekoBot.Modules.Games namespace NadekoBot.Modules.Games
{ {
@ -14,6 +15,13 @@ namespace NadekoBot.Modules.Games
[Group] [Group]
public class HangmanCommands : NadekoSubmodule public class HangmanCommands : NadekoSubmodule
{ {
private readonly DiscordShardedClient _client;
public HangmanCommands(DiscordShardedClient client)
{
_client = client;
}
//channelId, game //channelId, game
public static ConcurrentDictionary<ulong, HangmanGame> HangmanGames { get; } = new ConcurrentDictionary<ulong, HangmanGame>(); public static ConcurrentDictionary<ulong, HangmanGame> HangmanGames { get; } = new ConcurrentDictionary<ulong, HangmanGame>();
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -25,7 +33,7 @@ namespace NadekoBot.Modules.Games
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Hangman([Remainder]string type = "All") public async Task Hangman([Remainder]string type = "All")
{ {
var hm = new HangmanGame(Context.Channel, type); var hm = new HangmanGame(_client, Context.Channel, type);
if (!HangmanGames.TryAdd(Context.Channel.Id, hm)) if (!HangmanGames.TryAdd(Context.Channel.Id, hm))
{ {
@ -35,8 +43,7 @@ namespace NadekoBot.Modules.Games
hm.OnEnded += g => hm.OnEnded += g =>
{ {
HangmanGame throwaway; HangmanGames.TryRemove(g.GameChannel.Id, out HangmanGame throwaway);
HangmanGames.TryRemove(g.GameChannel.Id, out throwaway);
}; };
try try
{ {
@ -45,8 +52,7 @@ namespace NadekoBot.Modules.Games
catch (Exception ex) catch (Exception ex)
{ {
try { await Context.Channel.SendErrorAsync(GetText("hangman_start_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 HangmanGame throwaway);
HangmanGames.TryRemove(Context.Channel.Id, out throwaway);
throwaway.Dispose(); throwaway.Dispose();
return; return;
} }

View File

@ -0,0 +1,147 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Games;
using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Models
{
public class TypingGame
{
public const float WORD_VALUE = 4.5f;
public ITextChannel Channel { get; }
public string CurrentSentence { get; private set; }
public bool IsActive { get; private set; }
private readonly Stopwatch sw;
private readonly List<ulong> finishedUserIds;
private readonly DiscordShardedClient _client;
private readonly GamesService _games;
private Logger _log { get; }
public TypingGame(GamesService games, DiscordShardedClient client, ITextChannel channel)
{
_log = LogManager.GetCurrentClassLogger();
_games = games;
_client = client;
this.Channel = channel;
IsActive = false;
sw = new Stopwatch();
finishedUserIds = new List<ulong>();
}
public async Task<bool> Stop()
{
if (!IsActive) return false;
_client.MessageReceived -= AnswerReceived;
finishedUserIds.Clear();
IsActive = false;
sw.Stop();
sw.Reset();
try { await Channel.SendConfirmAsync("Typing contest stopped.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
return true;
}
public async Task Start()
{
if (IsActive) return; // can't start running game
IsActive = true;
CurrentSentence = GetRandomSentence();
var i = (int)(CurrentSentence.Length / WORD_VALUE * 1.7f);
try
{
await Channel.SendConfirmAsync($@":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.").ConfigureAwait(false);
var msg = await Channel.SendMessageAsync("Starting new typing contest in **3**...").ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
try
{
await msg.ModifyAsync(m => m.Content = "Starting new typing contest in **2**...").ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
await msg.ModifyAsync(m => m.Content = "Starting new typing contest in **1**...").ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
await msg.ModifyAsync(m => m.Content = Format.Bold(Format.Sanitize(CurrentSentence.Replace(" ", " \x200B")).SanitizeMentions())).ConfigureAwait(false);
sw.Start();
HandleAnswers();
while (i > 0)
{
await Task.Delay(1000).ConfigureAwait(false);
i--;
if (!IsActive)
return;
}
}
catch { }
finally
{
await Stop().ConfigureAwait(false);
}
}
public string GetRandomSentence()
{
if (_games.TypingArticles.Any())
return _games.TypingArticles[new NadekoRandom().Next(0, _games.TypingArticles.Count)].Text;
else
return $"No typing articles found. Use {NadekoBot.Prefix}typeadd command to add a new article for typing.";
}
private void HandleAnswers()
{
_client.MessageReceived += AnswerReceived;
}
private async Task AnswerReceived(SocketMessage imsg)
{
try
{
if (imsg.Author.IsBot)
return;
var msg = imsg as SocketUserMessage;
if (msg == null)
return;
if (this.Channel == null || this.Channel.Id != msg.Channel.Id) return;
var guess = msg.Content;
var distance = CurrentSentence.LevenshteinDistance(guess);
var decision = Judge(distance, guess.Length);
if (decision && !finishedUserIds.Contains(msg.Author.Id))
{
var elapsed = sw.Elapsed;
var wpm = CurrentSentence.Length / WORD_VALUE / elapsed.TotalSeconds * 60;
finishedUserIds.Add(msg.Author.Id);
await this.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle($"{msg.Author} finished the race!")
.AddField(efb => efb.WithName("Place").WithValue($"#{finishedUserIds.Count}").WithIsInline(true))
.AddField(efb => efb.WithName("WPM").WithValue($"{wpm:F1} *[{elapsed.TotalSeconds:F2}sec]*").WithIsInline(true))
.AddField(efb => efb.WithName("Errors").WithValue(distance.ToString()).WithIsInline(true)))
.ConfigureAwait(false);
if (finishedUserIds.Count % 4 == 0)
{
await this.Channel.SendConfirmAsync($":exclamation: A lot of people finished, here is the text for those still typing:\n\n**{Format.Sanitize(CurrentSentence.Replace(" ", " \x200B")).SanitizeMentions()}**").ConfigureAwait(false);
}
}
}
catch (Exception ex) { _log.Warn(ex); }
}
private bool Judge(int errors, int textLength) => errors <= textLength / 25;
}
}

View File

@ -6,6 +6,7 @@ using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Games;
using NLog; using NLog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -28,89 +29,20 @@ namespace NadekoBot.Modules.Games
[Group] [Group]
public class PlantPickCommands : NadekoSubmodule public class PlantPickCommands : NadekoSubmodule
{ {
private static ConcurrentHashSet<ulong> generationChannels { get; } private readonly CurrencyHandler _ch;
//channelid/message private readonly BotConfig _bc;
private static ConcurrentDictionary<ulong, List<IUserMessage>> plantedFlowers { get; } = new ConcurrentDictionary<ulong, List<IUserMessage>>(); private readonly GamesService _games;
//channelId/last generation private readonly DbHandler _db;
private static ConcurrentDictionary<ulong, DateTime> lastGenerations { get; } = new ConcurrentDictionary<ulong, DateTime>();
static PlantPickCommands() public PlantPickCommands(BotConfig bc, CurrencyHandler ch, GamesService games,
DbHandler db)
{ {
NadekoBot.Client.MessageReceived += PotentialFlowerGeneration; _bc = bc;
generationChannels = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs _ch = ch;
.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId))); _games = games;
_db = db;
} }
private static Task PotentialFlowerGeneration(SocketMessage imsg)
{
var msg = imsg as SocketUserMessage;
if (msg == null || msg.IsAuthor() || msg.Author.IsBot)
return Task.CompletedTask;
var channel = imsg.Channel as ITextChannel;
if (channel == null)
return Task.CompletedTask;
if (!generationChannels.Contains(channel.Id))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
var lastGeneration = lastGenerations.GetOrAdd(channel.Id, DateTime.MinValue);
var rng = new NadekoRandom();
//todo i'm stupid :rofl: wtg kwoth. real async programming :100: :ok_hand: :100: :100: :thumbsup:
if (DateTime.Now - TimeSpan.FromSeconds(NadekoBot.BotConfig.CurrencyGenerationCooldown) < lastGeneration) //recently generated in this channel, don't generate again
return;
var num = rng.Next(1, 101) + NadekoBot.BotConfig.CurrencyGenerationChance * 100;
if (num > 100)
{
lastGenerations.AddOrUpdate(channel.Id, DateTime.Now, (id, old) => DateTime.Now);
var dropAmount = NadekoBot.BotConfig.CurrencyDropAmount;
if (dropAmount > 0)
{
var msgs = new IUserMessage[dropAmount];
var prefix = NadekoBot.ModulePrefixes[typeof(Games).Name];
var toSend = dropAmount == 1
? GetLocalText(channel, "curgen_sn", NadekoBot.BotConfig.CurrencySign)
+ " " + GetLocalText(channel, "pick_sn", prefix)
: GetLocalText(channel, "curgen_pl", dropAmount, NadekoBot.BotConfig.CurrencySign)
+ " " + GetLocalText(channel, "pick_pl", prefix);
var file = GetRandomCurrencyImage();
using (var fileStream = file.Value.ToStream())
{
var sent = await channel.SendFileAsync(
fileStream,
file.Key,
toSend).ConfigureAwait(false);
msgs[0] = sent;
}
plantedFlowers.AddOrUpdate(channel.Id, msgs.ToList(), (id, old) => { old.AddRange(msgs); return old; });
}
}
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Warn(ex);
}
});
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] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Pick() public async Task Pick()
@ -120,16 +52,15 @@ namespace NadekoBot.Modules.Games
if (!(await channel.Guild.GetCurrentUserAsync()).GetPermissions(channel).ManageMessages) if (!(await channel.Guild.GetCurrentUserAsync()).GetPermissions(channel).ManageMessages)
return; return;
List<IUserMessage> msgs;
try { await Context.Message.DeleteAsync().ConfigureAwait(false); } catch { } try { await Context.Message.DeleteAsync().ConfigureAwait(false); } catch { }
if (!plantedFlowers.TryRemove(channel.Id, out msgs)) if (!_games.PlantedFlowers.TryRemove(channel.Id, out List<IUserMessage> msgs))
return; return;
await Task.WhenAll(msgs.Where(m => m != null).Select(toDelete => toDelete.DeleteAsync())).ConfigureAwait(false); 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); await _ch.AddCurrencyAsync((IGuildUser)Context.User, $"Picked {_bc.CurrencyPluralName}", msgs.Count, false).ConfigureAwait(false);
var msg = await ReplyConfirmLocalized("picked", msgs.Count + NadekoBot.BotConfig.CurrencySign) var msg = await ReplyConfirmLocalized("picked", msgs.Count + _bc.CurrencySign)
.ConfigureAwait(false); .ConfigureAwait(false);
msg.DeleteAfter(10); msg.DeleteAfter(10);
} }
@ -141,21 +72,21 @@ namespace NadekoBot.Modules.Games
if (amount < 1) if (amount < 1)
return; return;
var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)Context.User, $"Planted a {NadekoBot.BotConfig.CurrencyName}", amount, false).ConfigureAwait(false); var removed = await _ch.RemoveCurrencyAsync((IGuildUser)Context.User, $"Planted a {_bc.CurrencyName}", amount, false).ConfigureAwait(false);
if (!removed) if (!removed)
{ {
await ReplyErrorLocalized("not_enough", NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false); await ReplyErrorLocalized("not_enough", _bc.CurrencySign).ConfigureAwait(false);
return; return;
} }
var imgData = GetRandomCurrencyImage(); var imgData = _games.GetRandomCurrencyImage();
//todo upload all currency images to transfer.sh and use that one as cdn //todo upload all currency images to transfer.sh and use that one as cdn
//and then //and then
var msgToSend = GetText("planted", var msgToSend = GetText("planted",
Format.Bold(Context.User.ToString()), Format.Bold(Context.User.ToString()),
amount + NadekoBot.BotConfig.CurrencySign, amount + _bc.CurrencySign,
Prefix); Prefix);
if (amount > 1) if (amount > 1)
@ -172,7 +103,7 @@ namespace NadekoBot.Modules.Games
var msgs = new IUserMessage[amount]; var msgs = new IUserMessage[amount];
msgs[0] = msg; msgs[0] = msg;
plantedFlowers.AddOrUpdate(Context.Channel.Id, msgs.ToList(), (id, old) => _games.PlantedFlowers.AddOrUpdate(Context.Channel.Id, msgs.ToList(), (id, old) =>
{ {
old.AddRange(msgs); old.AddRange(msgs);
return old; return old;
@ -190,7 +121,7 @@ namespace NadekoBot.Modules.Games
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
bool enabled; bool enabled;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var guildConfig = uow.GuildConfigs.For(channel.Id, set => set.Include(gc => gc.GenerateCurrencyChannelIds)); var guildConfig = uow.GuildConfigs.For(channel.Id, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
@ -198,13 +129,13 @@ namespace NadekoBot.Modules.Games
if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd)) if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
{ {
guildConfig.GenerateCurrencyChannelIds.Add(toAdd); guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
generationChannels.Add(channel.Id); _games.GenerationChannels.Add(channel.Id);
enabled = true; enabled = true;
} }
else else
{ {
guildConfig.GenerateCurrencyChannelIds.Remove(toAdd); guildConfig.GenerateCurrencyChannelIds.Remove(toAdd);
generationChannels.TryRemove(channel.Id); _games.GenerationChannels.TryRemove(channel.Id);
enabled = false; enabled = false;
} }
await uow.CompleteAsync(); await uow.CompleteAsync();
@ -218,14 +149,6 @@ namespace NadekoBot.Modules.Games
await ReplyConfirmLocalized("curgen_disabled").ConfigureAwait(false); await ReplyConfirmLocalized("curgen_disabled").ConfigureAwait(false);
} }
} }
private static KeyValuePair<string, ImmutableArray<byte>> GetRandomCurrencyImage()
{
var rng = new NadekoRandom();
var images = NadekoBot.Images.Currency;
return images[rng.Next(0, images.Length)];
}
} }
} }
} }

View File

@ -10,6 +10,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using ImageSharp.Processing; using ImageSharp.Processing;
using NadekoBot.Services;
namespace NadekoBot.Modules.Games namespace NadekoBot.Modules.Games
{ {
@ -19,6 +20,12 @@ namespace NadekoBot.Modules.Games
public class PollCommands : NadekoSubmodule public class PollCommands : NadekoSubmodule
{ {
public static ConcurrentDictionary<ulong, Poll> ActivePolls = new ConcurrentDictionary<ulong, Poll>(); public static ConcurrentDictionary<ulong, Poll> ActivePolls = new ConcurrentDictionary<ulong, Poll>();
private readonly DiscordShardedClient _client;
public PollCommands(DiscordShardedClient client)
{
_client = client;
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)] [RequireUserPermission(GuildPermission.ManageMessages)]
@ -54,7 +61,7 @@ namespace NadekoBot.Modules.Games
if (data.Length < 3) if (data.Length < 3)
return; return;
var poll = new Poll(Context.Message, data[0], data.Skip(1), isPublic: isPublic); var poll = new Poll(_client, _strings, Context.Message, data[0], data.Skip(1), isPublic: isPublic);
if (ActivePolls.TryAdd(channel.Guild.Id, poll)) if (ActivePolls.TryAdd(channel.Guild.Id, poll))
{ {
await poll.StartPoll().ConfigureAwait(false); await poll.StartPoll().ConfigureAwait(false);
@ -83,10 +90,16 @@ namespace NadekoBot.Modules.Games
private string[] answers { get; } private string[] answers { get; }
private readonly ConcurrentDictionary<ulong, int> _participants = new ConcurrentDictionary<ulong, int>(); private readonly ConcurrentDictionary<ulong, int> _participants = new ConcurrentDictionary<ulong, int>();
private readonly string _question; private readonly string _question;
private readonly DiscordShardedClient _client;
private readonly NadekoStrings _strings;
public bool IsPublic { get; } public bool IsPublic { get; }
public Poll(IUserMessage umsg, string question, IEnumerable<string> enumerable, bool isPublic = false) public Poll(DiscordShardedClient client, NadekoStrings strings, IUserMessage umsg, string question, IEnumerable<string> enumerable, bool isPublic = false)
{ {
_client = client;
_strings = strings;
_originalMessage = umsg; _originalMessage = umsg;
_guild = ((ITextChannel)umsg.Channel).Guild; _guild = ((ITextChannel)umsg.Channel).Guild;
_question = question; _question = question;
@ -134,7 +147,7 @@ namespace NadekoBot.Modules.Games
public async Task StartPoll() public async Task StartPoll()
{ {
NadekoBot.Client.MessageReceived += Vote; _client.MessageReceived += Vote;
var msgToSend = GetText("poll_created", Format.Bold(_originalMessage.Author.Username)) + "\n\n" + Format.Bold(_question) + "\n"; var msgToSend = GetText("poll_created", Format.Bold(_originalMessage.Author.Username)) + "\n\n" + Format.Bold(_question) + "\n";
var num = 1; var num = 1;
msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n"); msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n");
@ -147,7 +160,7 @@ namespace NadekoBot.Modules.Games
public async Task StopPoll() public async Task StopPoll()
{ {
NadekoBot.Client.MessageReceived -= Vote; _client.MessageReceived -= Vote;
await _originalMessage.Channel.EmbedAsync(GetStats("POLL CLOSED")).ConfigureAwait(false); await _originalMessage.Channel.EmbedAsync(GetStats("POLL CLOSED")).ConfigureAwait(false);
} }
@ -205,8 +218,8 @@ namespace NadekoBot.Modules.Games
} }
private string GetText(string key, params object[] replacements) private string GetText(string key, params object[] replacements)
=> NadekoTopLevelModule.GetTextStatic(key, => _strings.GetText(key,
NadekoBot.Localization.GetCultureInfo(_guild.Id), _guild.Id,
typeof(Games).Name.ToLowerInvariant(), typeof(Games).Name.ToLowerInvariant(),
replacements); replacements);
} }

View File

@ -3,14 +3,11 @@ using Discord.Commands;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Commands.Models; using NadekoBot.Modules.Games.Models;
using NadekoBot.Services; using NadekoBot.Services.Games;
using Newtonsoft.Json; using Newtonsoft.Json;
using NLog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -19,145 +16,18 @@ namespace NadekoBot.Modules.Games
{ {
public partial class Games public partial class Games
{ {
public class TypingGame
{
public const float WORD_VALUE = 4.5f;
public ITextChannel Channel { get; }
public string CurrentSentence { get; private set; }
public bool IsActive { get; private set; }
private readonly Stopwatch sw;
private readonly List<ulong> finishedUserIds;
private Logger _log { get; }
public TypingGame(ITextChannel channel)
{
_log = LogManager.GetCurrentClassLogger();
this.Channel = channel;
IsActive = false;
sw = new Stopwatch();
finishedUserIds = new List<ulong>();
}
public async Task<bool> Stop()
{
if (!IsActive) return false;
NadekoBot.Client.MessageReceived -= AnswerReceived;
finishedUserIds.Clear();
IsActive = false;
sw.Stop();
sw.Reset();
try { await Channel.SendConfirmAsync("Typing contest stopped.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
return true;
}
public async Task Start()
{
if (IsActive) return; // can't start running game
IsActive = true;
CurrentSentence = GetRandomSentence();
var i = (int)(CurrentSentence.Length / WORD_VALUE * 1.7f);
try
{
await Channel.SendConfirmAsync($@":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.").ConfigureAwait(false);
var msg = await Channel.SendMessageAsync("Starting new typing contest in **3**...").ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
try
{
await msg.ModifyAsync(m => m.Content = "Starting new typing contest in **2**...").ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
await msg.ModifyAsync(m => m.Content = "Starting new typing contest in **1**...").ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
await msg.ModifyAsync(m => m.Content = Format.Bold(Format.Sanitize(CurrentSentence.Replace(" ", " \x200B")).SanitizeMentions())).ConfigureAwait(false);
sw.Start();
HandleAnswers();
while (i > 0)
{
await Task.Delay(1000).ConfigureAwait(false);
i--;
if (!IsActive)
return;
}
}
catch { }
finally
{
await Stop().ConfigureAwait(false);
}
}
public string GetRandomSentence()
{
if (SpeedTypingCommands.TypingArticles.Any())
return SpeedTypingCommands.TypingArticles[new NadekoRandom().Next(0, SpeedTypingCommands.TypingArticles.Count)].Text;
else
return $"No typing articles found. Use {NadekoBot.ModulePrefixes[typeof(Games).Name]}typeadd command to add a new article for typing.";
}
private void HandleAnswers()
{
NadekoBot.Client.MessageReceived += AnswerReceived;
}
private async Task AnswerReceived(SocketMessage imsg)
{
try
{
if (imsg.Author.IsBot)
return;
var msg = imsg as SocketUserMessage;
if (msg == null)
return;
if (this.Channel == null || this.Channel.Id != msg.Channel.Id) return;
var guess = msg.Content;
var distance = CurrentSentence.LevenshteinDistance(guess);
var decision = Judge(distance, guess.Length);
if (decision && !finishedUserIds.Contains(msg.Author.Id))
{
var elapsed = sw.Elapsed;
var wpm = CurrentSentence.Length / WORD_VALUE / elapsed.TotalSeconds * 60;
finishedUserIds.Add(msg.Author.Id);
await this.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle($"{msg.Author} finished the race!")
.AddField(efb => efb.WithName("Place").WithValue($"#{finishedUserIds.Count}").WithIsInline(true))
.AddField(efb => efb.WithName("WPM").WithValue($"{wpm:F1} *[{elapsed.TotalSeconds:F2}sec]*").WithIsInline(true))
.AddField(efb => efb.WithName("Errors").WithValue(distance.ToString()).WithIsInline(true)))
.ConfigureAwait(false);
if (finishedUserIds.Count % 4 == 0)
{
await this.Channel.SendConfirmAsync($":exclamation: A lot of people finished, here is the text for those still typing:\n\n**{Format.Sanitize(CurrentSentence.Replace(" ", " \x200B")).SanitizeMentions()}**").ConfigureAwait(false);
}
}
}
catch (Exception ex) { _log.Warn(ex); }
}
private bool Judge(int errors, int textLength) => errors <= textLength / 25;
}
[Group] [Group]
public class SpeedTypingCommands : NadekoSubmodule public class SpeedTypingCommands : NadekoSubmodule
{ {
public static List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>();
private const string _typingArticlesPath = "data/typing_articles2.json";
static SpeedTypingCommands()
{
try { TypingArticles = JsonConvert.DeserializeObject<List<TypingArticle>>(File.ReadAllText(_typingArticlesPath)); } catch { }
}
public static ConcurrentDictionary<ulong, TypingGame> RunningContests = new ConcurrentDictionary<ulong, TypingGame>(); public static ConcurrentDictionary<ulong, TypingGame> RunningContests = new ConcurrentDictionary<ulong, TypingGame>();
private readonly GamesService _games;
private readonly DiscordShardedClient _client;
public SpeedTypingCommands(DiscordShardedClient client, GamesService games)
{
_games = games;
_client = client;
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
@ -165,7 +35,7 @@ namespace NadekoBot.Modules.Games
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
var game = RunningContests.GetOrAdd(channel.Guild.Id, id => new TypingGame(channel)); var game = RunningContests.GetOrAdd(channel.Guild.Id, id => new TypingGame(_games, _client, channel));
if (game.IsActive) if (game.IsActive)
{ {
@ -202,13 +72,14 @@ namespace NadekoBot.Modules.Games
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
TypingArticles.Add(new TypingArticle _games.TypingArticles.Add(new TypingArticle
{ {
Title = $"Text added on {DateTime.UtcNow} by {Context.User}", Title = $"Text added on {DateTime.UtcNow} by {Context.User}",
Text = text.SanitizeMentions(), Text = text.SanitizeMentions(),
}); });
File.WriteAllText(_typingArticlesPath, JsonConvert.SerializeObject(TypingArticles)); //todo move this to service
File.WriteAllText(_games.TypingArticlesPath, JsonConvert.SerializeObject(_games.TypingArticles));
await channel.SendConfirmAsync("Added new article for typing game.").ConfigureAwait(false); await channel.SendConfirmAsync("Added new article for typing game.").ConfigureAwait(false);
} }
@ -222,7 +93,7 @@ namespace NadekoBot.Modules.Games
if (page < 1) if (page < 1)
return; return;
var articles = TypingArticles.Skip((page - 1) * 15).Take(15).ToArray(); var articles = _games.TypingArticles.Skip((page - 1) * 15).Take(15).ToArray();
if (!articles.Any()) if (!articles.Any())
{ {
@ -242,13 +113,13 @@ namespace NadekoBot.Modules.Games
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
index -= 1; index -= 1;
if (index < 0 || index >= TypingArticles.Count) if (index < 0 || index >= _games.TypingArticles.Count)
return; return;
var removed = TypingArticles[index]; var removed = _games.TypingArticles[index];
TypingArticles.RemoveAt(index); _games.TypingArticles.RemoveAt(index);
File.WriteAllText(_typingArticlesPath, JsonConvert.SerializeObject(TypingArticles)); File.WriteAllText(_games.TypingArticlesPath, JsonConvert.SerializeObject(_games.TypingArticles));
await channel.SendConfirmAsync($"`Removed typing article:` #{index + 1} - {removed.Text.TrimTo(50)}") await channel.SendConfirmAsync($"`Removed typing article:` #{index + 1} - {removed.Text.TrimTo(50)}")
.ConfigureAwait(false); .ConfigureAwait(false);

View File

@ -1,8 +1,9 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NLog; using NadekoBot.Services;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
@ -20,6 +21,12 @@ namespace NadekoBot.Modules.Games
private static readonly Dictionary<ulong, TicTacToe> _games = new Dictionary<ulong, TicTacToe>(); private static readonly Dictionary<ulong, TicTacToe> _games = new Dictionary<ulong, TicTacToe>();
private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1);
private readonly DiscordShardedClient _client;
public TicTacToeCommands(DiscordShardedClient client)
{
_client = client;
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
@ -39,7 +46,7 @@ namespace NadekoBot.Modules.Games
}); });
return; return;
} }
game = new TicTacToe(channel, (IGuildUser)Context.User); game = new TicTacToe(_strings, _client, channel, (IGuildUser)Context.User);
_games.Add(channel.Id, game); _games.Add(channel.Id, game);
await ReplyConfirmLocalized("ttt_created").ConfigureAwait(false); await ReplyConfirmLocalized("ttt_created").ConfigureAwait(false);
@ -79,10 +86,15 @@ namespace NadekoBot.Modules.Games
private IUserMessage _previousMessage; private IUserMessage _previousMessage;
private Timer _timeoutTimer; private Timer _timeoutTimer;
private readonly NadekoStrings _strings;
private readonly DiscordShardedClient _client;
public TicTacToe(ITextChannel channel, IGuildUser firstUser) public TicTacToe(NadekoStrings strings, DiscordShardedClient client, ITextChannel channel, IGuildUser firstUser)
{ {
_channel = channel; _channel = channel;
_strings = strings;
_client = client;
_users = new[] { firstUser, null }; _users = new[] { firstUser, null };
_state = new int?[,] { _state = new int?[,] {
{ null, null, null }, { null, null, null },
@ -95,8 +107,8 @@ namespace NadekoBot.Modules.Games
} }
private string GetText(string key, params object[] replacements) => private string GetText(string key, params object[] replacements) =>
NadekoTopLevelModule.GetTextStatic(key, _strings.GetText(key,
NadekoBot.Localization.GetCultureInfo(_channel.GuildId), _channel.GuildId,
typeof(Games).Name.ToLowerInvariant(), typeof(Games).Name.ToLowerInvariant(),
replacements); replacements);
@ -206,7 +218,7 @@ namespace NadekoBot.Modules.Games
} }
}, null, 15000, Timeout.Infinite); }, null, 15000, Timeout.Infinite);
NadekoBot.Client.MessageReceived += Client_MessageReceived; _client.MessageReceived += Client_MessageReceived;
_previousMessage = await _channel.EmbedAsync(GetEmbed(GetText("game_started"))).ConfigureAwait(false); _previousMessage = await _channel.EmbedAsync(GetEmbed(GetText("game_started"))).ConfigureAwait(false);
@ -283,14 +295,14 @@ namespace NadekoBot.Modules.Games
{ {
reason = GetText("ttt_matched_three"); reason = GetText("ttt_matched_three");
_winner = _users[_curUserIndex]; _winner = _users[_curUserIndex];
NadekoBot.Client.MessageReceived -= Client_MessageReceived; _client.MessageReceived -= Client_MessageReceived;
OnEnded?.Invoke(this); OnEnded?.Invoke(this);
} }
else if (IsDraw()) else if (IsDraw())
{ {
reason = GetText("ttt_a_draw"); reason = GetText("ttt_a_draw");
_phase = Phase.Ended; _phase = Phase.Ended;
NadekoBot.Client.MessageReceived -= Client_MessageReceived; _client.MessageReceived -= Client_MessageReceived;
OnEnded?.Invoke(this); OnEnded?.Invoke(this);
} }

View File

@ -3,6 +3,7 @@ using Discord.Net;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NLog; using NLog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -18,6 +19,10 @@ namespace NadekoBot.Modules.Games.Trivia
{ {
private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1, 1);
private readonly Logger _log; private readonly Logger _log;
private readonly NadekoStrings _strings;
private readonly DiscordShardedClient _client;
private readonly BotConfig _bc;
private readonly CurrencyHandler _ch;
public IGuild Guild { get; } public IGuild Guild { get; }
public ITextChannel Channel { get; } public ITextChannel Channel { get; }
@ -38,9 +43,15 @@ namespace NadekoBot.Modules.Games.Trivia
public int WinRequirement { get; } public int WinRequirement { get; }
public TriviaGame(IGuild guild, ITextChannel channel, bool showHints, int winReq, bool isPokemon) public TriviaGame(NadekoStrings strings, DiscordShardedClient client, BotConfig bc,
CurrencyHandler ch, IGuild guild, ITextChannel channel,
bool showHints, int winReq, bool isPokemon)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_strings = strings;
_client = client;
_bc = bc;
_ch = ch;
ShowHints = showHints; ShowHints = showHints;
Guild = guild; Guild = guild;
@ -50,8 +61,8 @@ namespace NadekoBot.Modules.Games.Trivia
} }
private string GetText(string key, params object[] replacements) => private string GetText(string key, params object[] replacements) =>
NadekoTopLevelModule.GetTextStatic(key, _strings.GetText(key,
NadekoBot.Localization.GetCultureInfo(Channel.GuildId), Channel.GuildId,
typeof(Games).Name.ToLowerInvariant(), typeof(Games).Name.ToLowerInvariant(),
replacements); replacements);
@ -99,7 +110,7 @@ namespace NadekoBot.Modules.Games.Trivia
//receive messages //receive messages
try try
{ {
NadekoBot.Client.MessageReceived += PotentialGuess; _client.MessageReceived += PotentialGuess;
//allow people to guess //allow people to guess
GameActive = true; GameActive = true;
@ -128,7 +139,7 @@ namespace NadekoBot.Modules.Games.Trivia
finally finally
{ {
GameActive = false; GameActive = false;
NadekoBot.Client.MessageReceived -= PotentialGuess; _client.MessageReceived -= PotentialGuess;
} }
if (!triviaCancelSource.IsCancellationRequested) if (!triviaCancelSource.IsCancellationRequested)
{ {
@ -214,9 +225,9 @@ namespace NadekoBot.Modules.Games.Trivia
{ {
// ignored // ignored
} }
var reward = NadekoBot.BotConfig.TriviaCurrencyReward; var reward = _bc.TriviaCurrencyReward;
if (reward > 0) if (reward > 0)
await CurrencyHandler.AddCurrencyAsync(guildUser, "Won trivia", reward, true).ConfigureAwait(false); await _ch.AddCurrencyAsync(guildUser, "Won trivia", reward, true).ConfigureAwait(false);
return; return;
} }

View File

@ -1,8 +1,11 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Trivia; using NadekoBot.Modules.Games.Trivia;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -14,8 +17,19 @@ namespace NadekoBot.Modules.Games
[Group] [Group]
public class TriviaCommands : NadekoSubmodule public class TriviaCommands : NadekoSubmodule
{ {
private readonly CurrencyHandler _ch;
private readonly DiscordShardedClient _client;
private readonly BotConfig _bc;
public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>(); public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>();
public TriviaCommands(DiscordShardedClient client, BotConfig bc, CurrencyHandler ch)
{
_ch = ch;
_client = client;
_bc = bc;
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public Task Trivia([Remainder] string additionalArgs = "") public Task Trivia([Remainder] string additionalArgs = "")
@ -35,7 +49,7 @@ namespace NadekoBot.Modules.Games
var showHints = !additionalArgs.Contains("nohint"); var showHints = !additionalArgs.Contains("nohint");
var isPokemon = additionalArgs.Contains("pokemon"); var isPokemon = additionalArgs.Contains("pokemon");
var trivia = new TriviaGame(channel.Guild, channel, showHints, winReq, isPokemon); var trivia = new TriviaGame(_strings, _client, _bc, _ch, channel.Guild, channel, showHints, winReq, isPokemon);
if (RunningTrivias.TryAdd(channel.Guild.Id, trivia)) if (RunningTrivias.TryAdd(channel.Guild.Id, trivia))
{ {
try try

View File

@ -14,19 +14,20 @@ using System.Net.Http;
using ImageSharp; using ImageSharp;
using NadekoBot.DataStructures; using NadekoBot.DataStructures;
using NLog; using NLog;
using NadekoBot.Services.Games;
namespace NadekoBot.Modules.Games namespace NadekoBot.Modules.Games
{ {
[NadekoModule("Games", ">")]
public partial class Games : NadekoTopLevelModule public partial class Games : NadekoTopLevelModule
{ {
private static readonly ImmutableArray<string> _8BallResponses = NadekoBot.BotConfig.EightBallResponses.Select(ebr => ebr.Text).ToImmutableArray(); private readonly GamesService _games;
private readonly IImagesService _images;
private static readonly Timer _t = new Timer((_) => public Games(GamesService games, IImagesService images)
{ {
_girlRatings.Clear(); _games = games;
_images = images;
}, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1)); }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Choose([Remainder] string list = null) public async Task Choose([Remainder] string list = null)
@ -48,7 +49,7 @@ namespace NadekoBot.Modules.Games
await Context.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor) await Context.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
.AddField(efb => efb.WithName("❓ " + GetText("question") ).WithValue(question).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))); .AddField(efb => efb.WithName("🎱 " + GetText("8ball")).WithValue(_games.EightBallResponses[new NadekoRandom().Next(0, _games.EightBallResponses.Length)]).WithIsInline(false)));
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -94,7 +95,7 @@ namespace NadekoBot.Modules.Games
else if ((pick == 0 && nadekoPick == 1) || else if ((pick == 0 && nadekoPick == 1) ||
(pick == 1 && nadekoPick == 2) || (pick == 1 && nadekoPick == 2) ||
(pick == 2 && nadekoPick == 0)) (pick == 2 && nadekoPick == 0))
msg = GetText("rps_win", NadekoBot.Client.CurrentUser.Mention, msg = GetText("rps_win", Context.Client.CurrentUser.Mention,
getRpsPick(nadekoPick), getRpsPick(pick)); getRpsPick(nadekoPick), getRpsPick(pick));
else else
msg = GetText("rps_win", Context.User.Mention, getRpsPick(pick), msg = GetText("rps_win", Context.User.Mention, getRpsPick(pick),
@ -103,73 +104,12 @@ namespace NadekoBot.Modules.Games
await Context.Channel.SendConfirmAsync(msg).ConfigureAwait(false); 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] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task RateGirl(IGuildUser usr) public async Task RateGirl(IGuildUser usr)
{ {
var gr = _girlRatings.GetOrAdd(usr.Id, GetGirl); var gr = _games.GirlRatings.GetOrAdd(usr.Id, GetGirl);
var img = await gr.Url; var img = await gr.Url;
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle("Girl Rating For " + usr) .WithTitle("Girl Rating For " + usr)
@ -255,7 +195,7 @@ namespace NadekoBot.Modules.Games
"and maybe look at how to replicate that."; "and maybe look at how to replicate that.";
} }
return new GirlRating(crazy, hot, roll, advice); return new GirlRating(_images, crazy, hot, roll, advice);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]

View File

@ -1,184 +0,0 @@
using System;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Searches
{
public class GoogleTranslator
{
private static GoogleTranslator _instance;
public static GoogleTranslator Instance = _instance ?? (_instance = new GoogleTranslator());
public IEnumerable<string> Languages => _languageDictionary.Keys.OrderBy(x => x);
private readonly Dictionary<string, string> _languageDictionary;
static GoogleTranslator() { }
private GoogleTranslator() {
_languageDictionary = new Dictionary<string, string>() {
{ "afrikaans", "af"},
{ "albanian", "sq"},
{ "arabic", "ar"},
{ "armenian", "hy"},
{ "azerbaijani", "az"},
{ "basque", "eu"},
{ "belarusian", "be"},
{ "bengali", "bn"},
{ "bulgarian", "bg"},
{ "catalan", "ca"},
{ "chinese-traditional", "zh-TW"},
{ "chinese-simplified", "zh-CN"},
{ "chinese", "zh-CN"},
{ "croatian", "hr"},
{ "czech", "cs"},
{ "danish", "da"},
{ "dutch", "nl"},
{ "english", "en"},
{ "esperanto", "eo"},
{ "estonian", "et"},
{ "filipino", "tl"},
{ "finnish", "fi"},
{ "french", "fr"},
{ "galician", "gl"},
{ "german", "de"},
{ "georgian", "ka"},
{ "greek", "el"},
{ "haitian Creole", "ht"},
{ "hebrew", "iw"},
{ "hindi", "hi"},
{ "hungarian", "hu"},
{ "icelandic", "is"},
{ "indonesian", "id"},
{ "irish", "ga"},
{ "italian", "it"},
{ "japanese", "ja"},
{ "korean", "ko"},
{ "lao", "lo"},
{ "latin", "la"},
{ "latvian", "lv"},
{ "lithuanian", "lt"},
{ "macedonian", "mk"},
{ "malay", "ms"},
{ "maltese", "mt"},
{ "norwegian", "no"},
{ "persian", "fa"},
{ "polish", "pl"},
{ "portuguese", "pt"},
{ "romanian", "ro"},
{ "russian", "ru"},
{ "serbian", "sr"},
{ "slovak", "sk"},
{ "slovenian", "sl"},
{ "spanish", "es"},
{ "swahili", "sw"},
{ "swedish", "sv"},
{ "tamil", "ta"},
{ "telugu", "te"},
{ "thai", "th"},
{ "turkish", "tr"},
{ "ukrainian", "uk"},
{ "urdu", "ur"},
{ "vietnamese", "vi"},
{ "welsh", "cy"},
{ "yiddish", "yi"},
{ "af", "af"},
{ "sq", "sq"},
{ "ar", "ar"},
{ "hy", "hy"},
{ "az", "az"},
{ "eu", "eu"},
{ "be", "be"},
{ "bn", "bn"},
{ "bg", "bg"},
{ "ca", "ca"},
{ "zh-tw", "zh-TW"},
{ "zh-cn", "zh-CN"},
{ "hr", "hr"},
{ "cs", "cs"},
{ "da", "da"},
{ "nl", "nl"},
{ "en", "en"},
{ "eo", "eo"},
{ "et", "et"},
{ "tl", "tl"},
{ "fi", "fi"},
{ "fr", "fr"},
{ "gl", "gl"},
{ "de", "de"},
{ "ka", "ka"},
{ "el", "el"},
{ "ht", "ht"},
{ "iw", "iw"},
{ "hi", "hi"},
{ "hu", "hu"},
{ "is", "is"},
{ "id", "id"},
{ "ga", "ga"},
{ "it", "it"},
{ "ja", "ja"},
{ "ko", "ko"},
{ "lo", "lo"},
{ "la", "la"},
{ "lv", "lv"},
{ "lt", "lt"},
{ "mk", "mk"},
{ "ms", "ms"},
{ "mt", "mt"},
{ "no", "no"},
{ "fa", "fa"},
{ "pl", "pl"},
{ "pt", "pt"},
{ "ro", "ro"},
{ "ru", "ru"},
{ "sr", "sr"},
{ "sk", "sk"},
{ "sl", "sl"},
{ "es", "es"},
{ "sw", "sw"},
{ "sv", "sv"},
{ "ta", "ta"},
{ "te", "te"},
{ "th", "th"},
{ "tr", "tr"},
{ "uk", "uk"},
{ "ur", "ur"},
{ "vi", "vi"},
{ "cy", "cy"},
{ "yi", "yi"},
};
}
public async Task<string> Translate(string sourceText, string sourceLanguage, string targetLanguage)
{
string text;
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 (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);
}
return (string.Concat(JArray.Parse(text)[0].Select(x => x[0])));
}
private string ConvertToLanguageCode(string language)
{
string mode;
_languageDictionary.TryGetValue(language, out mode);
return mode;
}
}
}

View File

@ -3,6 +3,7 @@ using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Searches.Models; using NadekoBot.Modules.Searches.Models;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Searches;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NLog; using NLog;
@ -19,27 +20,11 @@ namespace NadekoBot.Modules.Searches
[Group] [Group]
public class JokeCommands : NadekoSubmodule public class JokeCommands : NadekoSubmodule
{ {
private static List<WoWJoke> wowJokes { get; } = new List<WoWJoke>(); private readonly SearchesService _searches;
private static List<MagicItem> magicItems { get; } = new List<MagicItem>();
private new static readonly Logger _log;
static JokeCommands() public JokeCommands(SearchesService searches)
{ {
_log = LogManager.GetCurrentClassLogger(); _searches = searches;
if (File.Exists("data/wowjokes.json"))
{
wowJokes = JsonConvert.DeserializeObject<List<WoWJoke>>(File.ReadAllText("data/wowjokes.json"));
}
else
_log.Warn("data/wowjokes.json is missing. WOW Jokes are not loaded.");
if (File.Exists("data/magicitems.json"))
{
magicItems = JsonConvert.DeserializeObject<List<MagicItem>>(File.ReadAllText("data/magicitems.json"));
}
else
_log.Warn("data/magicitems.json is missing. Magic items are not loaded.");
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -75,24 +60,24 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task WowJoke() public async Task WowJoke()
{ {
if (!wowJokes.Any()) if (!_searches.WowJokes.Any())
{ {
await ReplyErrorLocalized("jokes_not_loaded").ConfigureAwait(false); await ReplyErrorLocalized("jokes_not_loaded").ConfigureAwait(false);
return; return;
} }
var joke = wowJokes[new NadekoRandom().Next(0, wowJokes.Count)]; var joke = _searches.WowJokes[new NadekoRandom().Next(0, _searches.WowJokes.Count)];
await Context.Channel.SendConfirmAsync(joke.Question, joke.Answer).ConfigureAwait(false); await Context.Channel.SendConfirmAsync(joke.Question, joke.Answer).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task MagicItem() public async Task MagicItem()
{ {
if (!wowJokes.Any()) if (!_searches.WowJokes.Any())
{ {
await ReplyErrorLocalized("magicitems_not_loaded").ConfigureAwait(false); await ReplyErrorLocalized("magicitems_not_loaded").ConfigureAwait(false);
return; return;
} }
var item = magicItems[new NadekoRandom().Next(0, magicItems.Count)]; var item = _searches.MagicItems[new NadekoRandom().Next(0, _searches.MagicItems.Count)];
await Context.Channel.SendConfirmAsync("✨" + item.Name, item.Description).ConfigureAwait(false); await Context.Channel.SendConfirmAsync("✨" + item.Name, item.Description).ConfigureAwait(false);
} }

View File

@ -22,7 +22,7 @@ namespace NadekoBot.Modules.Searches
obj["name"].GetHashCode(); obj["name"].GetHashCode();
} }
private static string[] trashTalk { get; } = { "Better ban your counters. You are going to carry the game anyway.", private static readonly string[] trashTalk = { "Better ban your counters. You are going to carry the game anyway.",
"Go with the flow. Don't think. Just ban one of these.", "Go with the flow. Don't think. Just ban one of these.",
"DONT READ BELOW! Ban Urgot mid OP 100%. Im smurf Diamond 1.", "DONT READ BELOW! Ban Urgot mid OP 100%. Im smurf Diamond 1.",
"Ask your teammates what would they like to play, and ban that.", "Ask your teammates what would they like to play, and ban that.",
@ -40,7 +40,7 @@ namespace NadekoBot.Modules.Searches
using (var http = new HttpClient()) using (var http = new HttpClient())
{ {
var data = JObject.Parse(await http.GetStringAsync($"http://api.champion.gg/stats/champs/mostBanned?" + var data = JObject.Parse(await http.GetStringAsync($"http://api.champion.gg/stats/champs/mostBanned?" +
$"api_key={NadekoBot.Credentials.LoLApiKey}&page=1&" + $"api_key={_creds.LoLApiKey}&page=1&" +
$"limit={showCount}") $"limit={showCount}")
.ConfigureAwait(false))["data"] as JArray; .ConfigureAwait(false))["data"] as JArray;
var dataList = data.Distinct(new ChampionNameComparer()).Take(showCount).ToList(); var dataList = data.Distinct(new ChampionNameComparer()).Take(showCount).ToList();
@ -127,7 +127,7 @@ namespace NadekoBot.Modules.Searches
// await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false); // await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false);
// return; // return;
// } // }
// var allData = JArray.Parse(await Classes.http.GetStringAsync($"http://api.champion.gg/champion/{name}?api_key={NadekoBot.Credentials.LOLAPIKey}").ConfigureAwait(false)); // var allData = JArray.Parse(await Classes.http.GetStringAsync($"http://api.champion.gg/champion/{name}?api_key={_creds.LOLAPIKey}").ConfigureAwait(false));
// JToken data = null; // JToken data = null;
// if (role != null) // if (role != null)
// { // {
@ -168,7 +168,7 @@ namespace NadekoBot.Modules.Searches
// roles[i] = ">" + roles[i] + "<"; // roles[i] = ">" + roles[i] + "<";
// } // }
// var general = JArray.Parse(await http.GetStringAsync($"http://api.champion.gg/stats/" + // var general = JArray.Parse(await http.GetStringAsync($"http://api.champion.gg/stats/" +
// $"champs/{name}?api_key={NadekoBot.Credentials.LOLAPIKey}") // $"champs/{name}?api_key={_creds.LOLAPIKey}")
// .ConfigureAwait(false)) // .ConfigureAwait(false))
// .FirstOrDefault(jt => jt["role"].ToString() == role)?["general"]; // .FirstOrDefault(jt => jt["role"].ToString() == role)?["general"];
// if (general == null) // if (general == null)

View File

@ -1,33 +0,0 @@
using NadekoBot.Extensions;
using System.Collections.Generic;
using System.Net;
namespace NadekoBot.Modules.Searches.Models
{
public class ImdbMovie
{
public bool Status { get; set; }
public string Id { get; set; }
public string Title { get; set; }
public string OriginalTitle { get; set; }
public string Year { get; set; }
public string Rating { get; set; }
public string Plot { get; set; }
public string Poster { get; set; }
public List<string> Genres { get; set; }
public string ImdbURL { get; set; }
public Dictionary<string, string> Aka { get; set; }
public override string ToString() =>
$@"`Title:` {WebUtility.HtmlDecode(Title)} {(string.IsNullOrEmpty(OriginalTitle) ? "" : $"({OriginalTitle})")}
`Year:` {Year}
`Rating:` {Rating}
`Genre:` {GenresAsString}
`Link:` <{ImdbURL}>
`Plot:` {System.Net.WebUtility.HtmlDecode(Plot.TrimTo(500))}
`Poster:` " + NadekoBot.Google.ShortenUrl(Poster).GetAwaiter().GetResult();
public string GenresAsString =>
string.Join(", ", Genres);
}
}

View File

@ -1,6 +1,7 @@
using Discord; using Discord;
using Discord.API; using Discord.API;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Net.Http; using System.Net.Http;
@ -12,7 +13,7 @@ namespace NadekoBot.Modules.Searches.Commands.OMDB
{ {
private const string queryUrl = "http://www.omdbapi.com/?t={0}&y=&plot=full&r=json"; private const string queryUrl = "http://www.omdbapi.com/?t={0}&y=&plot=full&r=json";
public static async Task<OmdbMovie> FindMovie(string name) public static async Task<OmdbMovie> FindMovie(string name, IGoogleApiService google)
{ {
using (var http = new HttpClient()) using (var http = new HttpClient())
{ {
@ -20,7 +21,7 @@ namespace NadekoBot.Modules.Searches.Commands.OMDB
var movie = JsonConvert.DeserializeObject<OmdbMovie>(res); var movie = JsonConvert.DeserializeObject<OmdbMovie>(res);
if (movie?.Title == null) if (movie?.Title == null)
return null; return null;
movie.Poster = await NadekoBot.Google.ShortenUrl(movie.Poster); movie.Poster = await google.ShortenUrl(movie.Poster);
return movie; return movie;
} }
} }

View File

@ -2,6 +2,7 @@
using Discord.Commands; using Discord.Commands;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Globalization; using System.Globalization;
@ -17,6 +18,15 @@ namespace NadekoBot.Modules.Searches
[Group] [Group]
public class OsuCommands : NadekoSubmodule public class OsuCommands : NadekoSubmodule
{ {
private readonly IGoogleApiService _google;
private readonly IBotCredentials _creds;
public OsuCommands(IGoogleApiService google, IBotCredentials creds)
{
_google = google;
_creds = creds;
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Osu(string usr, [Remainder] string mode = null) public async Task Osu(string usr, [Remainder] string mode = null)
{ {
@ -51,7 +61,7 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Osub([Remainder] string map) public async Task Osub([Remainder] string map)
{ {
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.OsuApiKey)) if (string.IsNullOrWhiteSpace(_creds.OsuApiKey))
{ {
await ReplyErrorLocalized("osu_api_key").ConfigureAwait(false); await ReplyErrorLocalized("osu_api_key").ConfigureAwait(false);
return; return;
@ -65,7 +75,7 @@ namespace NadekoBot.Modules.Searches
using (var http = new HttpClient()) using (var http = new HttpClient())
{ {
var mapId = ResolveMap(map); var mapId = ResolveMap(map);
var reqString = $"https://osu.ppy.sh/api/get_beatmaps?k={NadekoBot.Credentials.OsuApiKey}&{mapId}"; var reqString = $"https://osu.ppy.sh/api/get_beatmaps?k={_creds.OsuApiKey}&{mapId}";
var obj = JArray.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false))[0]; var obj = JArray.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false))[0];
var sb = new System.Text.StringBuilder(); var sb = new System.Text.StringBuilder();
var starRating = Math.Round(double.Parse($"{obj["difficultyrating"]}", CultureInfo.InvariantCulture), 2); var starRating = Math.Round(double.Parse($"{obj["difficultyrating"]}", CultureInfo.InvariantCulture), 2);
@ -86,7 +96,7 @@ namespace NadekoBot.Modules.Searches
public async Task Osu5(string user, [Remainder] string mode = null) public async Task Osu5(string user, [Remainder] string mode = null)
{ {
var channel = (ITextChannel)Context.Channel; var channel = (ITextChannel)Context.Channel;
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.OsuApiKey)) if (string.IsNullOrWhiteSpace(_creds.OsuApiKey))
{ {
await channel.SendErrorAsync("An osu! API key is required.").ConfigureAwait(false); await channel.SendErrorAsync("An osu! API key is required.").ConfigureAwait(false);
return; return;
@ -107,12 +117,12 @@ namespace NadekoBot.Modules.Searches
m = ResolveGameMode(mode); m = ResolveGameMode(mode);
} }
var reqString = $"https://osu.ppy.sh/api/get_user_best?k={NadekoBot.Credentials.OsuApiKey}&u={Uri.EscapeDataString(user)}&type=string&limit=5&m={m}"; var reqString = $"https://osu.ppy.sh/api/get_user_best?k={_creds.OsuApiKey}&u={Uri.EscapeDataString(user)}&type=string&limit=5&m={m}";
var obj = JArray.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false)); var obj = JArray.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false));
var sb = new System.Text.StringBuilder($"`Top 5 plays for {user}:`\n```xl" + Environment.NewLine); var sb = new System.Text.StringBuilder($"`Top 5 plays for {user}:`\n```xl" + Environment.NewLine);
foreach (var item in obj) foreach (var item in obj)
{ {
var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps?k={NadekoBot.Credentials.OsuApiKey}&b={item["beatmap_id"]}"; var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps?k={_creds.OsuApiKey}&b={item["beatmap_id"]}";
var map = JArray.Parse(await http.GetStringAsync(mapReqString).ConfigureAwait(false))[0]; 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 acc = CalculateAcc(item, m);

View File

@ -29,7 +29,7 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Placelist() public async Task Placelist()
{ {
await Context.Channel.SendConfirmAsync(GetText("list_of_place_tags", NadekoBot.ModulePrefixes[typeof(Searches).Name]), await Context.Channel.SendConfirmAsync(GetText("list_of_place_tags", NadekoBot.Prefix),
typesStr) typesStr)
.ConfigureAwait(false); .ConfigureAwait(false);
} }

View File

@ -3,6 +3,7 @@ using Discord.Commands;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Modules.Searches.Models; using NadekoBot.Modules.Searches.Models;
using NadekoBot.Services.Searches;
using Newtonsoft.Json; using Newtonsoft.Json;
using NLog; using NLog;
using System.Collections.Generic; using System.Collections.Generic;
@ -17,28 +18,14 @@ namespace NadekoBot.Modules.Searches
[Group] [Group]
public class PokemonSearchCommands : NadekoSubmodule public class PokemonSearchCommands : NadekoSubmodule
{ {
private static Dictionary<string, SearchPokemon> pokemons { get; } = new Dictionary<string, SearchPokemon>(); private readonly SearchesService _searches;
private static Dictionary<string, SearchPokemonAbility> pokemonAbilities { get; } = new Dictionary<string, SearchPokemonAbility>();
public const string PokemonAbilitiesFile = "data/pokemon/pokemon_abilities7.json"; public Dictionary<string, SearchPokemon> Pokemons => _searches.Pokemons;
public Dictionary<string, SearchPokemonAbility> PokemonAbilities => _searches.PokemonAbilities;
public const string PokemonListFile = "data/pokemon/pokemon_list7.json"; public PokemonSearchCommands(SearchesService searches)
private new static readonly Logger _log;
static PokemonSearchCommands()
{ {
_log = LogManager.GetCurrentClassLogger(); _searches = searches;
if (File.Exists(PokemonListFile))
{
pokemons = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemon>>(File.ReadAllText(PokemonListFile));
}
else
_log.Warn(PokemonListFile + " is missing. Pokemon abilities not loaded.");
if (File.Exists(PokemonAbilitiesFile))
pokemonAbilities = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemonAbility>>(File.ReadAllText(PokemonAbilitiesFile));
else
_log.Warn(PokemonAbilitiesFile + " is missing. Pokemon abilities not loaded.");
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -48,7 +35,7 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(pokemon)) if (string.IsNullOrWhiteSpace(pokemon))
return; return;
foreach (var kvp in pokemons) foreach (var kvp in Pokemons)
{ {
if (kvp.Key.ToUpperInvariant() == pokemon.ToUpperInvariant()) if (kvp.Key.ToUpperInvariant() == pokemon.ToUpperInvariant())
{ {
@ -71,7 +58,7 @@ namespace NadekoBot.Modules.Searches
ability = ability?.Trim().ToUpperInvariant().Replace(" ", ""); ability = ability?.Trim().ToUpperInvariant().Replace(" ", "");
if (string.IsNullOrWhiteSpace(ability)) if (string.IsNullOrWhiteSpace(ability))
return; return;
foreach (var kvp in pokemonAbilities) foreach (var kvp in PokemonAbilities)
{ {
if (kvp.Key.ToUpperInvariant() == ability) if (kvp.Key.ToUpperInvariant() == ability)
{ {

View File

@ -1,195 +1,30 @@
using Discord.Commands; using Discord.Commands;
using Discord; using Discord;
using System;
using System.Collections.Concurrent;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using NadekoBot.Services; using NadekoBot.Services;
using System.Threading; using System.Threading;
using System.Collections.Generic; using System.Collections.Generic;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using System.Net.Http;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services.Searches;
namespace NadekoBot.Modules.Searches namespace NadekoBot.Modules.Searches
{ {
public partial class Searches public partial class Searches
{ {
public class StreamStatus
{
public bool IsLive { get; set; }
public string ApiLink { get; set; }
public string Views { get; set; }
}
public class HitboxResponse {
public bool Success { get; set; } = true;
[JsonProperty("media_is_live")]
public string MediaIsLive { get; set; }
public bool IsLive => MediaIsLive == "1";
[JsonProperty("media_views")]
public string Views { get; set; }
}
public class TwitchResponse
{
public string Error { get; set; } = null;
public bool IsLive => Stream != null;
public StreamInfo Stream { get; set; }
public class StreamInfo
{
public int Viewers { get; set; }
}
}
public class BeamResponse
{
public string Error { get; set; } = null;
[JsonProperty("online")]
public bool IsLive { get; set; }
public int ViewersCurrent { get; set; }
}
public class StreamNotFoundException : Exception
{
public StreamNotFoundException(string message) : base("Stream '" + message + "' not found.")
{
}
}
[Group] [Group]
public class StreamNotificationCommands : NadekoSubmodule public class StreamNotificationCommands : NadekoSubmodule
{ {
private static readonly Timer _checkTimer; private readonly DbHandler _db;
private static readonly ConcurrentDictionary<string, StreamStatus> _cachedStatuses = new ConcurrentDictionary<string, StreamStatus>(); private readonly StreamNotificationService _service;
private static bool firstPass { get; set; } = true; public StreamNotificationCommands(DbHandler db, StreamNotificationService service)
static StreamNotificationCommands()
{ {
_checkTimer = new Timer(async (state) => _db = db;
{ _service = service;
var oldCachedStatuses = new ConcurrentDictionary<string, StreamStatus>(_cachedStatuses);
_cachedStatuses.Clear();
IEnumerable<FollowedStream> streams;
using (var uow = DbHandler.UnitOfWork())
{
streams = uow.GuildConfigs.GetAllFollowedStreams();
}
await Task.WhenAll(streams.Select(async fs =>
{
try
{
var newStatus = await GetStreamStatus(fs).ConfigureAwait(false);
if (firstPass)
{
return;
}
StreamStatus oldStatus;
if (oldCachedStatuses.TryGetValue(newStatus.ApiLink, out oldStatus) &&
oldStatus.IsLive != newStatus.IsLive)
{
var server = NadekoBot.Client.GetGuild(fs.GuildId);
var channel = server?.GetTextChannel(fs.ChannelId);
if (channel == null)
return;
try
{
await channel.EmbedAsync(fs.GetEmbed(newStatus, channel.Guild.Id)).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
catch
{
// ignored
}
}));
firstPass = false;
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(60));
}
private static async Task<StreamStatus> GetStreamStatus(FollowedStream stream, bool checkCache = true)
{
string response;
StreamStatus result;
switch (stream.Type)
{
case FollowedStream.FollowedStreamType.Hitbox:
var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username.ToLowerInvariant()}";
if (checkCache && _cachedStatuses.TryGetValue(hitboxUrl, out result))
return result;
using (var http = new HttpClient())
{
response = await http.GetStringAsync(hitboxUrl).ConfigureAwait(false);
}
var hbData = JsonConvert.DeserializeObject<HitboxResponse>(response);
if (!hbData.Success)
throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
result = new StreamStatus()
{
IsLive = hbData.IsLive,
ApiLink = hitboxUrl,
Views = hbData.Views
};
_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))
return result;
using (var http = new HttpClient())
{
response = await http.GetStringAsync(twitchUrl).ConfigureAwait(false);
}
var twData = JsonConvert.DeserializeObject<TwitchResponse>(response);
if (twData.Error != null)
{
throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
}
result = new StreamStatus()
{
IsLive = twData.IsLive,
ApiLink = twitchUrl,
Views = twData.Stream?.Viewers.ToString() ?? "0"
};
_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))
return result;
using (var http = new HttpClient())
{
response = await http.GetStringAsync(beamUrl).ConfigureAwait(false);
}
var bmData = JsonConvert.DeserializeObject<BeamResponse>(response);
if (bmData.Error != null)
throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
result = new StreamStatus()
{
IsLive = bmData.IsLive,
ApiLink = beamUrl,
Views = bmData.ViewersCurrent.ToString()
};
_cachedStatuses.AddOrUpdate(beamUrl, result, (key, old) => result);
return result;
default:
break;
}
return null;
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -218,7 +53,7 @@ namespace NadekoBot.Modules.Searches
public async Task ListStreams() public async Task ListStreams()
{ {
IEnumerable<FollowedStream> streams; IEnumerable<FollowedStream> streams;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
streams = uow.GuildConfigs streams = uow.GuildConfigs
.For(Context.Guild.Id, .For(Context.Guild.Id,
@ -260,7 +95,7 @@ namespace NadekoBot.Modules.Searches
}; };
bool removed; bool removed;
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(gc => gc.FollowedStreams)); var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(gc => gc.FollowedStreams));
removed = config.FollowedStreams.Remove(fs); removed = config.FollowedStreams.Remove(fs);
@ -287,7 +122,7 @@ namespace NadekoBot.Modules.Searches
return; return;
try try
{ {
var streamStatus = (await GetStreamStatus(new FollowedStream var streamStatus = (await _service.GetStreamStatus(new FollowedStream
{ {
Username = stream, Username = stream,
Type = platform, Type = platform,
@ -325,7 +160,7 @@ namespace NadekoBot.Modules.Searches
StreamStatus status; StreamStatus status;
try try
{ {
status = await GetStreamStatus(fs).ConfigureAwait(false); status = await _service.GetStreamStatus(fs).ConfigureAwait(false);
} }
catch catch
{ {
@ -333,53 +168,15 @@ namespace NadekoBot.Modules.Searches
return; return;
} }
using (var uow = DbHandler.UnitOfWork()) using (var uow = _db.UnitOfWork)
{ {
uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.FollowedStreams)) uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.FollowedStreams))
.FollowedStreams .FollowedStreams
.Add(fs); .Add(fs);
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
await channel.EmbedAsync(fs.GetEmbed(status, Context.Guild.Id), GetText("stream_tracked")).ConfigureAwait(false); await channel.EmbedAsync(_service.GetEmbed(fs, status, Context.Guild.Id), GetText("stream_tracked")).ConfigureAwait(false);
} }
} }
} }
public static class FollowedStreamExtensions
{
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(fs.GetText("status"))
.WithValue(status.IsLive ? "Online" : "Offline")
.WithIsInline(true))
.AddField(efb => efb.WithName(fs.GetText("viewers"))
.WithValue(status.IsLive ? status.Views : "-")
.WithIsInline(true))
.AddField(efb => efb.WithName(fs.GetText("platform"))
.WithValue(fs.Type.ToString())
.WithIsInline(true))
.WithColor(status.IsLive ? NadekoBot.OkColor : NadekoBot.ErrorColor);
return embed;
}
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}/";
if (fs.Type == FollowedStream.FollowedStreamType.Twitch)
return $"http://www.twitch.tv/{fs.Username}/";
if (fs.Type == FollowedStream.FollowedStreamType.Beam)
return $"https://beam.pro/{fs.Username}/";
return "??";
}
}
} }

View File

@ -7,54 +7,23 @@ using System.Threading.Tasks;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Linq; using System.Linq;
using Discord.WebSocket; using Discord.WebSocket;
using NadekoBot.Services.Searches;
using NadekoBot.Services;
namespace NadekoBot.Modules.Searches namespace NadekoBot.Modules.Searches
{ {
public partial class Searches public partial class Searches
{ {
public struct UserChannelPair
{
public ulong UserId { get; set; }
public ulong ChannelId { get; set; }
}
[Group] [Group]
public class TranslateCommands : NadekoSubmodule public class TranslateCommands : NadekoSubmodule
{ {
private static ConcurrentDictionary<ulong, bool> translatedChannels { get; } = new ConcurrentDictionary<ulong, bool>(); private readonly SearchesService _searches;
private static ConcurrentDictionary<UserChannelPair, string> userLanguages { get; } = new ConcurrentDictionary<UserChannelPair, string>(); private readonly IGoogleApiService _google;
static TranslateCommands() public TranslateCommands(SearchesService searches, IGoogleApiService google)
{ {
NadekoBot.Client.MessageReceived += async (msg) => _searches = searches;
{ _google = google;
try
{
var umsg = msg as SocketUserMessage;
if (umsg == null)
return;
bool autoDelete;
if (!translatedChannels.TryGetValue(umsg.Channel.Id, out autoDelete))
return;
var key = new UserChannelPair()
{
UserId = umsg.Author.Id,
ChannelId = umsg.Channel.Id,
};
string langs;
if (!userLanguages.TryGetValue(key, out langs))
return;
var text = await TranslateInternal(langs, umsg.Resolve(TagHandling.Ignore))
.ConfigureAwait(false);
if (autoDelete)
try { await umsg.DeleteAsync().ConfigureAwait(false); } catch { }
await umsg.Channel.SendConfirmAsync($"{umsg.Author.Mention} `:` " + text.Replace("<@ ", "<@").Replace("<@! ", "<@!")).ConfigureAwait(false);
}
catch { }
};
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -63,7 +32,7 @@ namespace NadekoBot.Modules.Searches
try try
{ {
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false); await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
var translation = await TranslateInternal(langs, text); var translation = await _searches.Translate(langs, text);
await Context.Channel.SendConfirmAsync(GetText("translation") + " " + langs, translation).ConfigureAwait(false); await Context.Channel.SendConfirmAsync(GetText("translation") + " " + langs, translation).ConfigureAwait(false);
} }
catch catch
@ -72,19 +41,6 @@ namespace NadekoBot.Modules.Searches
} }
} }
private static async Task<string> TranslateInternal(string langs, [Remainder] string text = null)
{
var langarr = langs.ToLowerInvariant().Split('>');
if (langarr.Length != 2)
throw new ArgumentException();
var from = langarr[0];
var to = langarr[1];
text = text?.Trim();
if (string.IsNullOrWhiteSpace(text))
throw new ArgumentException();
return (await GoogleTranslator.Instance.Translate(text, from, to).ConfigureAwait(false)).SanitizeMentions();
}
public enum AutoDeleteAutoTranslate public enum AutoDeleteAutoTranslate
{ {
Del, Del,
@ -101,18 +57,17 @@ namespace NadekoBot.Modules.Searches
if (autoDelete == AutoDeleteAutoTranslate.Del) if (autoDelete == AutoDeleteAutoTranslate.Del)
{ {
translatedChannels.AddOrUpdate(channel.Id, true, (key, val) => true); _searches.TranslatedChannels.AddOrUpdate(channel.Id, true, (key, val) => true);
await ReplyConfirmLocalized("atl_ad_started").ConfigureAwait(false); await ReplyConfirmLocalized("atl_ad_started").ConfigureAwait(false);
return; return;
} }
bool throwaway; if (_searches.TranslatedChannels.TryRemove(channel.Id, out var throwaway))
if (translatedChannels.TryRemove(channel.Id, out throwaway))
{ {
await ReplyConfirmLocalized("atl_stopped").ConfigureAwait(false); await ReplyConfirmLocalized("atl_stopped").ConfigureAwait(false);
return; return;
} }
if (translatedChannels.TryAdd(channel.Id, autoDelete == AutoDeleteAutoTranslate.Del)) if (_searches.TranslatedChannels.TryAdd(channel.Id, autoDelete == AutoDeleteAutoTranslate.Del))
{ {
await ReplyConfirmLocalized("atl_started").ConfigureAwait(false); await ReplyConfirmLocalized("atl_started").ConfigureAwait(false);
} }
@ -130,7 +85,7 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(langs)) if (string.IsNullOrWhiteSpace(langs))
{ {
if (userLanguages.TryRemove(ucp, out langs)) if (_searches.UserLanguages.TryRemove(ucp, out langs))
await ReplyConfirmLocalized("atl_removed").ConfigureAwait(false); await ReplyConfirmLocalized("atl_removed").ConfigureAwait(false);
return; return;
} }
@ -141,13 +96,13 @@ namespace NadekoBot.Modules.Searches
var from = langarr[0]; var from = langarr[0];
var to = langarr[1]; var to = langarr[1];
if (!GoogleTranslator.Instance.Languages.Contains(from) || !GoogleTranslator.Instance.Languages.Contains(to)) if (!_google.Languages.Contains(from) || !_google.Languages.Contains(to))
{ {
await ReplyErrorLocalized("invalid_lang").ConfigureAwait(false); await ReplyErrorLocalized("invalid_lang").ConfigureAwait(false);
return; return;
} }
userLanguages.AddOrUpdate(ucp, langs, (key, val) => langs); _searches.UserLanguages.AddOrUpdate(ucp, langs, (key, val) => langs);
await ReplyConfirmLocalized("atl_set", from, to).ConfigureAwait(false); await ReplyConfirmLocalized("atl_set", from, to).ConfigureAwait(false);
} }
@ -156,7 +111,7 @@ namespace NadekoBot.Modules.Searches
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Translangs() public async Task Translangs()
{ {
await Context.Channel.SendTableAsync(GoogleTranslator.Instance.Languages, str => $"{str,-15}", 3); await Context.Channel.SendTableAsync(_google.Languages, str => $"{str,-15}", 3);
} }
} }

View File

@ -3,7 +3,6 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Linq; using System.Linq;
using System.Text;
using System.Net.Http; using System.Net.Http;
using NadekoBot.Services; using NadekoBot.Services;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -17,18 +16,27 @@ using NadekoBot.Modules.Searches.Commands.Models;
using AngleSharp; using AngleSharp;
using AngleSharp.Dom.Html; using AngleSharp.Dom.Html;
using AngleSharp.Dom; using AngleSharp.Dom;
using System.Xml;
using Configuration = AngleSharp.Configuration; using Configuration = AngleSharp.Configuration;
using NadekoBot.Attributes; using NadekoBot.Attributes;
using Discord.Commands; using Discord.Commands;
using ImageSharp.Processing.Processors;
using ImageSharp; using ImageSharp;
using NadekoBot.Services.Searches;
namespace NadekoBot.Modules.Searches namespace NadekoBot.Modules.Searches
{ {
[NadekoModule("Searches", "~")]
public partial class Searches : NadekoTopLevelModule public partial class Searches : NadekoTopLevelModule
{ {
private readonly IBotCredentials _creds;
private readonly IGoogleApiService _google;
private readonly SearchesService _searches;
public Searches(IBotCredentials creds, IGoogleApiService google, SearchesService searches)
{
_creds = creds;
_google = google;
_searches = searches;
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Weather([Remainder] string query) public async Task Weather([Remainder] string query)
{ {
@ -59,16 +67,16 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Time([Remainder] string arg) public async Task Time([Remainder] string arg)
{ {
if (string.IsNullOrWhiteSpace(arg) || string.IsNullOrWhiteSpace(NadekoBot.Credentials.GoogleApiKey)) if (string.IsNullOrWhiteSpace(arg) || string.IsNullOrWhiteSpace(_creds.GoogleApiKey))
return; return;
using (var http = new HttpClient()) using (var http = new HttpClient())
{ {
var res = await http.GetStringAsync($"https://maps.googleapis.com/maps/api/geocode/json?address={arg}&key={NadekoBot.Credentials.GoogleApiKey}").ConfigureAwait(false); var res = await http.GetStringAsync($"https://maps.googleapis.com/maps/api/geocode/json?address={arg}&key={_creds.GoogleApiKey}").ConfigureAwait(false);
var obj = JsonConvert.DeserializeObject<GeolocationResult>(res); var obj = JsonConvert.DeserializeObject<GeolocationResult>(res);
var currentSeconds = DateTime.UtcNow.UnixTimestamp(); var currentSeconds = DateTime.UtcNow.UnixTimestamp();
var timeRes = await http.GetStringAsync($"https://maps.googleapis.com/maps/api/timezone/json?location={obj.results[0].Geometry.Location.Lat},{obj.results[0].Geometry.Location.Lng}&timestamp={currentSeconds}&key={NadekoBot.Credentials.GoogleApiKey}").ConfigureAwait(false); var timeRes = await http.GetStringAsync($"https://maps.googleapis.com/maps/api/timezone/json?location={obj.results[0].Geometry.Location.Lat},{obj.results[0].Geometry.Location.Lng}&timestamp={currentSeconds}&key={_creds.GoogleApiKey}").ConfigureAwait(false);
var timeObj = JsonConvert.DeserializeObject<TimeZoneResult>(timeRes); var timeObj = JsonConvert.DeserializeObject<TimeZoneResult>(timeRes);
var time = DateTime.UtcNow.AddSeconds(timeObj.DstOffset + timeObj.RawOffset); var time = DateTime.UtcNow.AddSeconds(timeObj.DstOffset + timeObj.RawOffset);
@ -81,7 +89,7 @@ namespace NadekoBot.Modules.Searches
public async Task Youtube([Remainder] string query = null) 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(); var result = (await _google.GetVideosByKeywordsAsync(query, 1)).FirstOrDefault();
if (string.IsNullOrWhiteSpace(result)) if (string.IsNullOrWhiteSpace(result))
{ {
await ReplyErrorLocalized("no_results").ConfigureAwait(false); await ReplyErrorLocalized("no_results").ConfigureAwait(false);
@ -97,7 +105,7 @@ namespace NadekoBot.Modules.Searches
if (!(await ValidateQuery(Context.Channel, query).ConfigureAwait(false))) return; if (!(await ValidateQuery(Context.Channel, query).ConfigureAwait(false))) return;
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false); await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
var movie = await OmdbProvider.FindMovie(query); var movie = await OmdbProvider.FindMovie(query, _google);
if (movie == null) if (movie == null)
{ {
await ReplyErrorLocalized("imdb_fail").ConfigureAwait(false); await ReplyErrorLocalized("imdb_fail").ConfigureAwait(false);
@ -137,7 +145,7 @@ namespace NadekoBot.Modules.Searches
try try
{ {
var res = await NadekoBot.Google.GetImageAsync(terms).ConfigureAwait(false); var res = await _google.GetImageAsync(terms).ConfigureAwait(false);
var embed = new EmbedBuilder() var embed = new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50)) .WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50))
@ -189,7 +197,7 @@ namespace NadekoBot.Modules.Searches
terms = WebUtility.UrlEncode(terms).Replace(' ', '+'); terms = WebUtility.UrlEncode(terms).Replace(' ', '+');
try try
{ {
var res = await NadekoBot.Google.GetImageAsync(terms, new NadekoRandom().Next(0, 50)).ConfigureAwait(false); var res = await _google.GetImageAsync(terms, new NadekoRandom().Next(0, 50)).ConfigureAwait(false);
var embed = new EmbedBuilder() var embed = new EmbedBuilder()
.WithOkColor() .WithOkColor()
.WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50)) .WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50))
@ -239,7 +247,7 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(ffs)) if (string.IsNullOrWhiteSpace(ffs))
return; return;
await Context.Channel.SendConfirmAsync("<" + await NadekoBot.Google.ShortenUrl($"http://lmgtfy.com/?q={ Uri.EscapeUriString(ffs) }") + ">") await Context.Channel.SendConfirmAsync("<" + await _google.ShortenUrl($"http://lmgtfy.com/?q={ Uri.EscapeUriString(ffs) }") + ">")
.ConfigureAwait(false); .ConfigureAwait(false);
} }
@ -249,7 +257,7 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(arg)) if (string.IsNullOrWhiteSpace(arg))
return; return;
var shortened = await NadekoBot.Google.ShortenUrl(arg).ConfigureAwait(false); var shortened = await _google.ShortenUrl(arg).ConfigureAwait(false);
if (shortened == arg) if (shortened == arg)
{ {
@ -315,7 +323,7 @@ namespace NadekoBot.Modules.Searches
.WithFooter(efb => efb.WithText(totalResults)); .WithFooter(efb => efb.WithText(totalResults));
var desc = await Task.WhenAll(results.Select(async res => var desc = await Task.WhenAll(results.Select(async res =>
$"[{Format.Bold(res?.Title)}]({(await NadekoBot.Google.ShortenUrl(res?.Link))})\n{res?.Text}\n\n")) $"[{Format.Bold(res?.Title)}]({(await _google.ShortenUrl(res?.Link))})\n{res?.Text}\n\n"))
.ConfigureAwait(false); .ConfigureAwait(false);
await Context.Channel.EmbedAsync(embed.WithDescription(string.Concat(desc))).ConfigureAwait(false); await Context.Channel.EmbedAsync(embed.WithDescription(string.Concat(desc))).ConfigureAwait(false);
} }
@ -339,7 +347,7 @@ namespace NadekoBot.Modules.Searches
if (items == null || items.Length == 0) if (items == null || items.Length == 0)
throw new KeyNotFoundException("Cannot find a card by that name"); throw new KeyNotFoundException("Cannot find a card by that name");
var item = items[new NadekoRandom().Next(0, items.Length)]; var item = items[new NadekoRandom().Next(0, items.Length)];
var storeUrl = await NadekoBot.Google.ShortenUrl(item["store_url"].ToString()); var storeUrl = await _google.ShortenUrl(item["store_url"].ToString());
var cost = item["cost"].ToString(); var cost = item["cost"].ToString();
var desc = item["text"].ToString(); var desc = item["text"].ToString();
var types = string.Join(",\n", item["types"].ToObject<string[]>()); var types = string.Join(",\n", item["types"].ToObject<string[]>());
@ -351,7 +359,7 @@ namespace NadekoBot.Modules.Searches
.AddField(efb => efb.WithName(GetText("store_url")).WithValue(storeUrl).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("cost")).WithValue(cost).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("types")).WithValue(types).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)); //.AddField(efb => efb.WithName("Store Url").WithValue(await _google.ShortenUrl(items[0]["store_url"].ToString())).WithIsInline(true));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false); await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
} }
@ -369,7 +377,7 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(arg)) if (string.IsNullOrWhiteSpace(arg))
return; return;
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey)) if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
{ {
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false); await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
return; return;
@ -379,7 +387,7 @@ namespace NadekoBot.Modules.Searches
using (var http = new HttpClient()) using (var http = new HttpClient())
{ {
http.DefaultRequestHeaders.Clear(); http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey); http.DefaultRequestHeaders.Add("X-Mashape-Key", _creds.MashapeKey);
var 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); .ConfigureAwait(false);
try try
@ -422,7 +430,7 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task Yodify([Remainder] string query = null) public async Task Yodify([Remainder] string query = null)
{ {
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey)) if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
{ {
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false); await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
return; return;
@ -435,7 +443,7 @@ namespace NadekoBot.Modules.Searches
using (var http = new HttpClient()) using (var http = new HttpClient())
{ {
http.DefaultRequestHeaders.Clear(); http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey); http.DefaultRequestHeaders.Add("X-Mashape-Key", _creds.MashapeKey);
http.DefaultRequestHeaders.Add("Accept", "text/plain"); http.DefaultRequestHeaders.Add("Accept", "text/plain");
var res = await http.GetStringAsync($"https://yoda.p.mashape.com/yoda?sentence={Uri.EscapeUriString(query)}").ConfigureAwait(false); var res = await http.GetStringAsync($"https://yoda.p.mashape.com/yoda?sentence={Uri.EscapeUriString(query)}").ConfigureAwait(false);
try try
@ -457,7 +465,7 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
public async Task UrbanDict([Remainder] string query = null) public async Task UrbanDict([Remainder] string query = null)
{ {
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey)) if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
{ {
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false); await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
return; return;
@ -531,7 +539,7 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(query)) if (string.IsNullOrWhiteSpace(query))
return; return;
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey)) if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
{ {
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false); await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
return; return;
@ -542,7 +550,7 @@ namespace NadekoBot.Modules.Searches
using (var http = new HttpClient()) using (var http = new HttpClient())
{ {
http.DefaultRequestHeaders.Clear(); http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey); http.DefaultRequestHeaders.Add("X-Mashape-Key", _creds.MashapeKey);
res = await http.GetStringAsync($"https://tagdef.p.mashape.com/one.{Uri.EscapeUriString(query)}.json").ConfigureAwait(false); res = await http.GetStringAsync($"https://tagdef.p.mashape.com/one.{Uri.EscapeUriString(query)}.json").ConfigureAwait(false);
} }
@ -655,7 +663,7 @@ namespace NadekoBot.Modules.Searches
usr = (IGuildUser)Context.User; usr = (IGuildUser)Context.User;
var avatarUrl = usr.RealAvatarUrl(); var avatarUrl = usr.RealAvatarUrl();
var shortenedAvatarUrl = await NadekoBot.Google.ShortenUrl(avatarUrl).ConfigureAwait(false); var shortenedAvatarUrl = await _google.ShortenUrl(avatarUrl).ConfigureAwait(false);
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor() await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.AddField(efb => efb.WithName("Username").WithValue(usr.ToString()).WithIsInline(false)) .AddField(efb => efb.WithName("Username").WithValue(usr.ToString()).WithIsInline(false))
.AddField(efb => efb.WithName("Avatar Url").WithValue(shortenedAvatarUrl).WithIsInline(false)) .AddField(efb => efb.WithName("Avatar Url").WithValue(shortenedAvatarUrl).WithIsInline(false))
@ -682,7 +690,7 @@ namespace NadekoBot.Modules.Searches
var found = items["items"][0]; var found = items["items"][0];
var response = $@"`{GetText("title")}` {found["title"]} var response = $@"`{GetText("title")}` {found["title"]}
`{GetText("quality")}` {found["quality"]} `{GetText("quality")}` {found["quality"]}
`{GetText("url")}:` {await NadekoBot.Google.ShortenUrl(found["url"].ToString()).ConfigureAwait(false)}"; `{GetText("url")}:` {await _google.ShortenUrl(found["url"].ToString()).ConfigureAwait(false)}";
await Context.Channel.SendMessageAsync(response).ConfigureAwait(false); await Context.Channel.SendMessageAsync(response).ConfigureAwait(false);
} }
catch catch
@ -769,7 +777,7 @@ namespace NadekoBot.Modules.Searches
tag = tag?.Trim() ?? ""; tag = tag?.Trim() ?? "";
var url = await InternalDapiSearch(tag, type).ConfigureAwait(false); var url = await _searches.DapiSearch(tag, type).ConfigureAwait(false);
if (url == null) if (url == null)
await channel.SendErrorAsync(umsg.Author.Mention + " " + GetText("no_results")); await channel.SendErrorAsync(umsg.Author.Mention + " " + GetText("no_results"));

View File

@ -21,6 +21,8 @@ using NadekoBot.Services.Searches;
using NadekoBot.Services.ClashOfClans; using NadekoBot.Services.ClashOfClans;
using NadekoBot.Services.Music; using NadekoBot.Services.Music;
using NadekoBot.Services.CustomReactions; using NadekoBot.Services.CustomReactions;
using NadekoBot.Services.Games;
using NadekoBot.Services.Administration;
namespace NadekoBot namespace NadekoBot
{ {
@ -73,11 +75,10 @@ namespace NadekoBot
}); });
var google = new GoogleApiService(credentials); var google = new GoogleApiService(credentials);
var strings = new NadekoStrings();
var greetSettingsService = new GreetSettingsService(AllGuildConfigs, db);
var localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), db); var localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), db);
var strings = new NadekoStrings(localization);
var greetSettingsService = new GreetSettingsService(Client, AllGuildConfigs, db);
var commandService = new CommandService(new CommandServiceConfig() var commandService = new CommandService(new CommandServiceConfig()
{ {
@ -97,10 +98,18 @@ namespace NadekoBot
//module services //module services
var utilityService = new UtilityService(AllGuildConfigs, Client, BotConfig, db); var utilityService = new UtilityService(AllGuildConfigs, Client, BotConfig, db);
var searchesService = new SearchesService(); var searchesService = new SearchesService(Client, google, db);
var clashService = new ClashOfClansService(Client, db, localization, strings); var clashService = new ClashOfClansService(Client, db, localization, strings);
var musicService = new MusicService(google, strings, localization, db, soundcloud, credentials); var musicService = new MusicService(google, strings, localization, db, soundcloud, credentials);
var crService = new CustomReactionsService(db, Client); var crService = new CustomReactionsService(db, Client);
var gamesService = new GamesService(Client, BotConfig, AllGuildConfigs, strings, images);
#region administration
var administrationService = new AdministrationService(AllGuildConfigs, commandHandler);
var selfService = new SelfService(this, commandHandler, db, BotConfig);
var vcRoleService = new VcRoleService(Client, AllGuildConfigs);
var vPlusTService = new VplusTService(Client, AllGuildConfigs, strings, db);
#endregion
//initialize Services //initialize Services
Services = new NServiceProvider.ServiceProviderBuilder() //todo all Adds should be interfaces Services = new NServiceProvider.ServiceProviderBuilder() //todo all Adds should be interfaces
@ -124,6 +133,10 @@ namespace NadekoBot
.Add<MusicService>(musicService) .Add<MusicService>(musicService)
.Add<GreetSettingsService>(greetSettingsService) .Add<GreetSettingsService>(greetSettingsService)
.Add<CustomReactionsService>(crService) .Add<CustomReactionsService>(crService)
.Add<GamesService>(gamesService)
.Add(selfService)
.Add(vcRoleService)
.Add(vPlusTService)
.Build(); .Build();
commandHandler.AddServices(Services); commandHandler.AddServices(Services);

View File

@ -29,21 +29,22 @@
<ItemGroup> <ItemGroup>
<Compile Remove="data\**\*;credentials.json;credentials_example.json" /> <Compile Remove="data\**\*;credentials.json;credentials_example.json" />
<Compile Remove="Modules\Administration\**" /> <Compile Remove="Modules\Administration\**" />
<Compile Remove="Modules\Games\**" />
<Compile Remove="Modules\NSFW\**" /> <Compile Remove="Modules\NSFW\**" />
<Compile Remove="Modules\Permissions\**" /> <Compile Remove="Modules\Permissions\**" />
<Compile Remove="Modules\Searches\**" />
<EmbeddedResource Remove="Modules\Administration\**" /> <EmbeddedResource Remove="Modules\Administration\**" />
<EmbeddedResource Remove="Modules\Games\**" />
<EmbeddedResource Remove="Modules\NSFW\**" /> <EmbeddedResource Remove="Modules\NSFW\**" />
<EmbeddedResource Remove="Modules\Permissions\**" /> <EmbeddedResource Remove="Modules\Permissions\**" />
<EmbeddedResource Remove="Modules\Searches\**" />
<None Remove="Modules\Administration\**" /> <None Remove="Modules\Administration\**" />
<None Remove="Modules\Games\**" />
<None Remove="Modules\NSFW\**" /> <None Remove="Modules\NSFW\**" />
<None Remove="Modules\Permissions\**" /> <None Remove="Modules\Permissions\**" />
<None Remove="Modules\Searches\**" />
<Compile Remove="Modules\Gambling\Commands\Lucky7Commands.cs" /> <Compile Remove="Modules\Gambling\Commands\Lucky7Commands.cs" />
<Compile Include="Modules\Administration\Administration.cs" />
<Compile Include="Modules\Administration\Commands\MuteCommands.cs" />
<Compile Include="Modules\Administration\Commands\SelfCommands.cs" />
<Compile Include="Modules\Administration\Commands\ServerGreetCommands.cs" />
<Compile Include="Modules\Administration\Commands\UserPunishCommands.cs" />
<Compile Include="Modules\Administration\Commands\VcRoleCommands.cs" />
<Compile Include="Modules\Administration\Commands\VoicePlusTextCommands.cs" />
<None Update="libsodium.dll;opus.dll;libsodium.so;libopus.so"> <None Update="libsodium.dll;opus.dll;libsodium.so;libopus.so">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>

View File

@ -0,0 +1,49 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services.Administration
{
public class AdministrationService
{
public readonly ConcurrentHashSet<ulong> DeleteMessagesOnCommand;
private readonly Logger _log;
public AdministrationService(IEnumerable<GuildConfig> gcs, CommandHandler cmdHandler)
{
_log = LogManager.GetCurrentClassLogger();
DeleteMessagesOnCommand = new ConcurrentHashSet<ulong>(gcs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId));
cmdHandler.CommandExecuted += DelMsgOnCmd_Handler;
}
private Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd)
{
var _ = Task.Run(async () =>
{
try
{
var channel = msg.Channel as SocketTextChannel;
if (channel == null)
return;
if (DeleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune" && cmd.Name != "pick")
await msg.DeleteAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn("Delmsgoncmd errored...");
_log.Warn(ex);
}
});
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,275 @@
using Discord;
using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Services.Administration
{
public class MuteService
{
public ConcurrentDictionary<ulong, string> GuildMuteRoles { get; }
public ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> MutedUsers { get; }
public ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>> UnmuteTimers { get; }
= new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>>();
public event Action<IGuildUser, MuteType> UserMuted = delegate { };
public event Action<IGuildUser, MuteType> UserUnmuted = delegate { };
private static readonly OverwritePermissions denyOverwrite = new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny);
private readonly Logger _log = LogManager.GetCurrentClassLogger();
private readonly DiscordShardedClient _client;
private readonly DbHandler _db;
public MuteService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs, DbHandler db)
{
_client = client;
_db = db;
GuildMuteRoles = new ConcurrentDictionary<ulong, string>(gcs
.Where(c => !string.IsNullOrWhiteSpace(c.MuteRoleName))
.ToDictionary(c => c.GuildId, c => c.MuteRoleName));
MutedUsers = new ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>>(gcs.ToDictionary(
k => k.GuildId,
v => new ConcurrentHashSet<ulong>(v.MutedUsers.Select(m => m.UserId))
));
foreach (var conf in gcs)
{
foreach (var x in conf.UnmuteTimers)
{
TimeSpan after;
if (x.UnmuteAt - TimeSpan.FromMinutes(2) <= DateTime.UtcNow)
{
after = TimeSpan.FromMinutes(2);
}
else
{
after = x.UnmuteAt - DateTime.UtcNow;
}
StartUnmuteTimer(conf.GuildId, x.UserId, after);
}
}
_client.UserJoined += Client_UserJoined;
}
private async Task Client_UserJoined(IGuildUser usr)
{
try
{
MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted);
if (muted == null || !muted.Contains(usr.Id))
return;
await MuteUser(usr).ConfigureAwait(false);
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Warn(ex);
}
}
public async Task MuteUser(IGuildUser usr, MuteType type = MuteType.All)
{
if (type == MuteType.All)
{
await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false);
var muteRole = await GetMuteRole(usr.Guild);
if (!usr.RoleIds.Contains(muteRole.Id))
await usr.AddRoleAsync(muteRole).ConfigureAwait(false);
StopUnmuteTimer(usr.GuildId, usr.Id);
using (var uow = _db.UnitOfWork)
{
var config = uow.GuildConfigs.For(usr.Guild.Id,
set => set.Include(gc => gc.MutedUsers)
.Include(gc => gc.UnmuteTimers));
config.MutedUsers.Add(new MutedUserId()
{
UserId = usr.Id
});
if (MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted))
muted.Add(usr.Id);
config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
await uow.CompleteAsync().ConfigureAwait(false);
}
UserMuted(usr, MuteType.All);
}
else if (type == MuteType.Voice)
{
await usr.ModifyAsync(x => x.Mute = true).ConfigureAwait(false);
UserMuted(usr, MuteType.Voice);
}
else if (type == MuteType.Chat)
{
await usr.AddRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false);
UserMuted(usr, MuteType.Chat);
}
}
public async Task UnmuteUser(IGuildUser usr, MuteType type = MuteType.All)
{
if (type == MuteType.All)
{
StopUnmuteTimer(usr.GuildId, usr.Id);
try { await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false); } catch { }
try { await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild)).ConfigureAwait(false); } catch { /*ignore*/ }
using (var uow = _db.UnitOfWork)
{
var config = uow.GuildConfigs.For(usr.Guild.Id, set => set.Include(gc => gc.MutedUsers)
.Include(gc => gc.UnmuteTimers));
config.MutedUsers.Remove(new MutedUserId()
{
UserId = usr.Id
});
if (MutedUsers.TryGetValue(usr.Guild.Id, out ConcurrentHashSet<ulong> muted))
muted.TryRemove(usr.Id);
config.UnmuteTimers.RemoveWhere(x => x.UserId == usr.Id);
await uow.CompleteAsync().ConfigureAwait(false);
}
UserUnmuted(usr, MuteType.All);
}
else if (type == MuteType.Voice)
{
await usr.ModifyAsync(x => x.Mute = false).ConfigureAwait(false);
UserUnmuted(usr, MuteType.Voice);
}
else if (type == MuteType.Chat)
{
await usr.RemoveRoleAsync(await GetMuteRole(usr.Guild).ConfigureAwait(false)).ConfigureAwait(false);
UserUnmuted(usr, MuteType.Chat);
}
}
public async Task<IRole> GetMuteRole(IGuild guild)
{
const string defaultMuteRoleName = "nadeko-mute";
var muteRoleName = GuildMuteRoles.GetOrAdd(guild.Id, defaultMuteRoleName);
var muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName);
if (muteRole == null)
{
//if it doesn't exist, create it
try { muteRole = await guild.CreateRoleAsync(muteRoleName, GuildPermissions.None).ConfigureAwait(false); }
catch
{
//if creations fails, maybe the name is not correct, find default one, if doesn't work, create default one
muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName) ??
await guild.CreateRoleAsync(defaultMuteRoleName, GuildPermissions.None).ConfigureAwait(false);
}
}
foreach (var toOverwrite in (await guild.GetTextChannelsAsync()))
{
try
{
if (!toOverwrite.PermissionOverwrites.Select(x => x.Permissions).Contains(denyOverwrite))
{
await toOverwrite.AddPermissionOverwriteAsync(muteRole, denyOverwrite)
.ConfigureAwait(false);
await Task.Delay(200).ConfigureAwait(false);
}
}
catch
{
// ignored
}
}
return muteRole;
}
public async Task TimedMute(IGuildUser user, TimeSpan after)
{
await MuteUser(user).ConfigureAwait(false); // mute the user. This will also remove any previous unmute timers
using (var uow = _db.UnitOfWork)
{
var config = uow.GuildConfigs.For(user.GuildId, set => set.Include(x => x.UnmuteTimers));
config.UnmuteTimers.Add(new UnmuteTimer()
{
UserId = user.Id,
UnmuteAt = DateTime.UtcNow + after,
}); // add teh unmute timer to the database
uow.Complete();
}
StartUnmuteTimer(user.GuildId, user.Id, after); // start the timer
}
public void StartUnmuteTimer(ulong guildId, ulong userId, TimeSpan after)
{
//load the unmute timers for this guild
var userUnmuteTimers = UnmuteTimers.GetOrAdd(guildId, new ConcurrentDictionary<ulong, Timer>());
//unmute timer to be added
var toAdd = new Timer(async _ =>
{
try
{
var guild = _client.GetGuild(guildId); // load the guild
if (guild == null)
{
RemoveUnmuteTimerFromDb(guildId, userId);
return; // if guild can't be found, just remove the timer from db
}
// unmute the user, this will also remove the timer from the db
await UnmuteUser(guild.GetUser(userId)).ConfigureAwait(false);
}
catch (Exception ex)
{
RemoveUnmuteTimerFromDb(guildId, userId); // if unmute errored, just remove unmute from db
_log.Warn("Couldn't unmute user {0} in guild {1}", userId, guildId);
_log.Warn(ex);
}
}, null, after, Timeout.InfiniteTimeSpan);
//add it, or stop the old one and add this one
userUnmuteTimers.AddOrUpdate(userId, (key) => toAdd, (key, old) =>
{
old.Change(Timeout.Infinite, Timeout.Infinite);
return toAdd;
});
}
public void StopUnmuteTimer(ulong guildId, ulong userId)
{
if (!UnmuteTimers.TryGetValue(guildId, out ConcurrentDictionary<ulong, Timer> userUnmuteTimers)) return;
if (userUnmuteTimers.TryRemove(userId, out Timer removed))
{
removed.Change(Timeout.Infinite, Timeout.Infinite);
}
}
private void RemoveUnmuteTimerFromDb(ulong guildId, ulong userId)
{
using (var uow = _db.UnitOfWork)
{
var config = uow.GuildConfigs.For(guildId, set => set.Include(x => x.UnmuteTimers));
config.UnmuteTimers.RemoveWhere(x => x.UserId == userId);
uow.Complete();
}
}
}
public enum MuteType
{
Voice,
Chat,
All
}
}

View File

@ -0,0 +1,48 @@
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services.Administration
{
public class SelfService
{
public volatile bool ForwardDMs;
public volatile bool ForwardDMsToAllOwners;
private readonly Logger _log;
private readonly NadekoBot _bot;
private readonly CommandHandler _cmdHandler;
private readonly DbHandler _db;
public SelfService(NadekoBot bot, CommandHandler cmdHandler, DbHandler db,
BotConfig bc)
{
_bot = bot;
_cmdHandler = cmdHandler;
_db = db;
using (var uow = _db.UnitOfWork)
{
var config = uow.BotConfig.GetOrCreate();
ForwardDMs = config.ForwardMessages;
ForwardDMsToAllOwners = config.ForwardToAllOwners;
}
var _ = Task.Run(async () =>
{
while (!bot.Ready)
await Task.Delay(1000);
foreach (var cmd in bc.StartupCommands)
{
await cmdHandler.ExecuteExternal(cmd.GuildId, cmd.ChannelId, cmd.CommandText);
await Task.Delay(400).ConfigureAwait(false);
}
});
}
}
}

View File

@ -0,0 +1,102 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Services.Administration
{
public class VcRoleService
{
private readonly Logger _log;
public ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>> VcRoles { get; }
public VcRoleService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs)
{
_log = LogManager.GetCurrentClassLogger();
client.UserVoiceStateUpdated += ClientOnUserVoiceStateUpdated;
VcRoles = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>>();
foreach (var gconf in gcs)
{
var g = client.GetGuild(gconf.GuildId);
if (g == null)
continue; //todo delete everything from db if guild doesn't exist?
var infos = new ConcurrentDictionary<ulong, IRole>();
VcRoles.TryAdd(gconf.GuildId, infos);
foreach (var ri in gconf.VcRoleInfos)
{
var role = g.GetRole(ri.RoleId);
if (role == null)
continue; //todo remove this entry from db
infos.TryAdd(ri.VoiceChannelId, role);
}
}
}
private Task ClientOnUserVoiceStateUpdated(SocketUser usr, SocketVoiceState oldState,
SocketVoiceState newState)
{
var gusr = usr as SocketGuildUser;
if (gusr == null)
return Task.CompletedTask;
var oldVc = oldState.VoiceChannel;
var newVc = newState.VoiceChannel;
var _ = Task.Run(async () =>
{
try
{
if (oldVc != newVc)
{
ulong guildId;
guildId = newVc?.Guild.Id ?? oldVc.Guild.Id;
if (VcRoles.TryGetValue(guildId, out ConcurrentDictionary<ulong, IRole> guildVcRoles))
{
//remove old
if (oldVc != null && guildVcRoles.TryGetValue(oldVc.Id, out IRole role))
{
if (gusr.Roles.Contains(role))
{
try
{
await gusr.RemoveRoleAsync(role).ConfigureAwait(false);
await Task.Delay(500).ConfigureAwait(false);
}
catch
{
await Task.Delay(200).ConfigureAwait(false);
await gusr.RemoveRoleAsync(role).ConfigureAwait(false);
await Task.Delay(500).ConfigureAwait(false);
}
}
}
//add new
if (newVc != null && guildVcRoles.TryGetValue(newVc.Id, out role))
{
if (!gusr.Roles.Contains(role))
await gusr.AddRoleAsync(role).ConfigureAwait(false);
}
}
}
}
catch (Exception ex)
{
_log.Warn(ex);
}
});
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,157 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Services.Administration
{
public class VplusTService
{
private readonly Regex _channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled);
public readonly ConcurrentHashSet<ulong> VoicePlusTextCache;
private readonly ConcurrentDictionary<ulong, SemaphoreSlim> _guildLockObjects = new ConcurrentDictionary<ulong, SemaphoreSlim>();
private readonly DiscordShardedClient _client;
private readonly NadekoStrings _strings;
private readonly DbHandler _db;
private readonly Logger _log;
public VplusTService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs, NadekoStrings strings,
DbHandler db)
{
_client = client;
_strings = strings;
_db = db;
_log = LogManager.GetCurrentClassLogger();
VoicePlusTextCache = new ConcurrentHashSet<ulong>(gcs.Where(g => g.VoicePlusTextEnabled).Select(g => g.GuildId));
_client.UserVoiceStateUpdated += UserUpdatedEventHandler;
}
private Task UserUpdatedEventHandler(SocketUser iuser, SocketVoiceState before, SocketVoiceState after)
{
var user = (iuser as SocketGuildUser);
var guild = user?.Guild;
if (guild == null)
return Task.CompletedTask;
var botUserPerms = guild.CurrentUser.GuildPermissions;
if (before.VoiceChannel == after.VoiceChannel)
return Task.CompletedTask;
if (!VoicePlusTextCache.Contains(guild.Id))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
if (!botUserPerms.ManageChannels || !botUserPerms.ManageRoles)
{
try
{
await guild.Owner.SendErrorAsync(
_strings.GetText("vt_exit",
guild.Id,
"Administration".ToLowerInvariant(),
Format.Bold(guild.Name))).ConfigureAwait(false);
}
catch
{
// ignored
}
using (var uow = _db.UnitOfWork)
{
uow.GuildConfigs.For(guild.Id, set => set).VoicePlusTextEnabled = false;
VoicePlusTextCache.TryRemove(guild.Id);
await uow.CompleteAsync().ConfigureAwait(false);
}
return;
}
var semaphore = _guildLockObjects.GetOrAdd(guild.Id, (key) => new SemaphoreSlim(1, 1));
try
{
await semaphore.WaitAsync().ConfigureAwait(false);
var beforeVch = before.VoiceChannel;
if (beforeVch != null)
{
var beforeRoleName = GetRoleName(beforeVch);
var beforeRole = guild.Roles.FirstOrDefault(x => x.Name == beforeRoleName);
if (beforeRole != null)
try
{
_log.Info("Removing role " + beforeRoleName + " from user " + user.Username);
await user.RemoveRoleAsync(beforeRole).ConfigureAwait(false);
await Task.Delay(200).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
var afterVch = after.VoiceChannel;
if (afterVch != null && guild.AFKChannel?.Id != afterVch.Id)
{
var roleName = GetRoleName(afterVch);
var roleToAdd = guild.Roles.FirstOrDefault(x => x.Name == roleName) ??
(IRole)await guild.CreateRoleAsync(roleName, GuildPermissions.None).ConfigureAwait(false);
ITextChannel textChannel = guild.TextChannels
.FirstOrDefault(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant());
if (textChannel == null)
{
var created = (await guild.CreateTextChannelAsync(GetChannelName(afterVch.Name).ToLowerInvariant()).ConfigureAwait(false));
try { await guild.CurrentUser.AddRoleAsync(roleToAdd).ConfigureAwait(false); } catch {/*ignored*/}
await Task.Delay(50).ConfigureAwait(false);
await created.AddPermissionOverwriteAsync(roleToAdd, new OverwritePermissions(
readMessages: PermValue.Allow,
sendMessages: PermValue.Allow))
.ConfigureAwait(false);
await Task.Delay(50).ConfigureAwait(false);
await created.AddPermissionOverwriteAsync(guild.EveryoneRole, new OverwritePermissions(
readMessages: PermValue.Deny,
sendMessages: PermValue.Deny))
.ConfigureAwait(false);
await Task.Delay(50).ConfigureAwait(false);
}
_log.Warn("Adding role " + roleToAdd.Name + " to user " + user.Username);
await user.AddRoleAsync(roleToAdd).ConfigureAwait(false);
}
}
finally
{
semaphore.Release();
}
}
catch (Exception ex)
{
_log.Warn(ex);
}
});
return Task.CompletedTask;
}
public string GetChannelName(string voiceName) =>
_channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice";
public string GetRoleName(IVoiceChannel ch) =>
"nvoice-" + ch.Id;
}
}

View File

@ -544,7 +544,7 @@ namespace NadekoBot.Services
//////int price; //////int price;
//////if (Permissions.CommandCostCommands.CommandCosts.TryGetValue(cmd.Aliases.First().Trim().ToLowerInvariant(), out price) && price > 0) //////if (Permissions.CommandCostCommands.CommandCosts.TryGetValue(cmd.Aliases.First().Trim().ToLowerInvariant(), out price) && price > 0)
//////{ //////{
////// var success = await CurrencyHandler.RemoveCurrencyAsync(context.User.Id, $"Running {cmd.Name} command.", price).ConfigureAwait(false); ////// var success = await _ch.RemoveCurrencyAsync(context.User.Id, $"Running {cmd.Name} command.", price).ConfigureAwait(false);
////// if (!success) ////// if (!success)
////// { ////// {
////// return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, $"Insufficient funds. You need {price}{NadekoBot.BotConfig.CurrencySign} to run this command.")); ////// return new ExecuteCommandResult(cmd, pc, SearchResult.FromError(CommandError.Exception, $"Insufficient funds. You need {price}{NadekoBot.BotConfig.CurrencySign} to run this command."));

View File

@ -19,7 +19,7 @@ namespace NadekoBot.Services
var optionsBuilder = new DbContextOptionsBuilder(); var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlite(creds.Db.ConnectionString); optionsBuilder.UseSqlite(creds.Db.ConnectionString);
options = optionsBuilder.Options; options = optionsBuilder.Options;
//switch (NadekoBot.Credentials.Db.Type.ToUpperInvariant()) //switch (_creds.Db.Type.ToUpperInvariant())
//{ //{
// case "SQLITE": // case "SQLITE":
// dbType = typeof(NadekoSqliteContext); // dbType = typeof(NadekoSqliteContext);

View File

@ -0,0 +1,8 @@
namespace NadekoBot.Services.Games
{
public class ChatterBotResponse
{
public string Convo_id { get; set; }
public string BotSay { get; set; }
}
}

View File

@ -0,0 +1,39 @@
using NadekoBot.Extensions;
using Newtonsoft.Json;
using System.Collections.Immutable;
using System.Net.Http;
using System.Threading.Tasks;
namespace NadekoBot.Services.Games
{
public class ChatterBotSession
{
private static NadekoRandom rng { get; } = new NadekoRandom();
public string ChatterbotId { get; }
public string ChannelId { get; }
private int _botId = 6;
public ChatterBotSession(ulong channelId)
{
ChannelId = channelId.ToString().ToBase64();
ChatterbotId = rng.Next(0, 1000000).ToString().ToBase64();
}
private string apiEndpoint => "http://api.program-o.com/v2/chatbot/" +
$"?bot_id={_botId}&" +
"say={0}&" +
$"convo_id=nadekobot_{ChatterbotId}_{ChannelId}&" +
"format=json";
public async Task<string> Think(string message)
{
using (var http = new HttpClient())
{
var res = await http.GetStringAsync(string.Format(apiEndpoint, message)).ConfigureAwait(false);
var cbr = JsonConvert.DeserializeObject<ChatterBotResponse>(res);
//Console.WriteLine(cbr.Convo_id);
return cbr.BotSay.Replace("<br/>", "\n");
}
}
}
}

View File

@ -0,0 +1,206 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services.Database.Models;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Services.Games
{
public class GamesService
{
private readonly BotConfig _bc;
public readonly ConcurrentDictionary<ulong, GirlRating> GirlRatings = new ConcurrentDictionary<ulong, GirlRating>();
public readonly ImmutableArray<string> EightBallResponses;
private readonly Timer _t;
private readonly DiscordShardedClient _client;
private readonly NadekoStrings _strings;
private readonly IImagesService _images;
private readonly Logger _log;
public ConcurrentDictionary<ulong, Lazy<ChatterBotSession>> CleverbotGuilds { get; }
public readonly string TypingArticlesPath = "data/typing_articles2.json";
public List<TypingArticle> TypingArticles { get; } = new List<TypingArticle>();
public GamesService(DiscordShardedClient client, BotConfig bc, IEnumerable<GuildConfig> gcs,
NadekoStrings strings, IImagesService images)
{
_bc = bc;
_client = client;
_strings = strings;
_images = images;
_log = LogManager.GetCurrentClassLogger();
//8ball
EightBallResponses = _bc.EightBallResponses.Select(ebr => ebr.Text).ToImmutableArray();
//girl ratings
_t = new Timer((_) =>
{
GirlRatings.Clear();
}, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1));
//cleverbot
CleverbotGuilds = new ConcurrentDictionary<ulong, Lazy<ChatterBotSession>>(
gcs.Where(gc => gc.CleverbotEnabled)
.ToDictionary(gc => gc.GuildId, gc => new Lazy<ChatterBotSession>(() => new ChatterBotSession(gc.GuildId), true)));
//plantpick
client.MessageReceived += PotentialFlowerGeneration;
GenerationChannels = new ConcurrentHashSet<ulong>(gcs
.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
try
{
TypingArticles = JsonConvert.DeserializeObject<List<TypingArticle>>(File.ReadAllText(TypingArticlesPath));
}
catch (Exception ex)
{
_log.Warn("Error while loading typing articles {0}", ex.ToString());
TypingArticles = new List<TypingArticle>();
}
}
public string PrepareMessage(IUserMessage msg, out ChatterBotSession cleverbot)
{
var channel = msg.Channel as ITextChannel;
cleverbot = null;
if (channel == null)
return null;
Lazy<ChatterBotSession> lazyCleverbot;
if (!CleverbotGuilds.TryGetValue(channel.Guild.Id, out lazyCleverbot))
return null;
cleverbot = lazyCleverbot.Value;
var nadekoId = _client.CurrentUser.Id;
var normalMention = $"<@{nadekoId}> ";
var nickMention = $"<@!{nadekoId}> ";
string message;
if (msg.Content.StartsWith(normalMention))
{
message = msg.Content.Substring(normalMention.Length).Trim();
}
else if (msg.Content.StartsWith(nickMention))
{
message = msg.Content.Substring(nickMention.Length).Trim();
}
else
{
return null;
}
return message;
}
public async Task<bool> TryAsk(ChatterBotSession cleverbot, ITextChannel channel, string message)
{
await channel.TriggerTypingAsync().ConfigureAwait(false);
var response = await cleverbot.Think(message).ConfigureAwait(false);
try
{
await channel.SendConfirmAsync(response.SanitizeMentions()).ConfigureAwait(false);
}
catch
{
await channel.SendConfirmAsync(response.SanitizeMentions()).ConfigureAwait(false); // try twice :\
}
return true;
}
public ConcurrentHashSet<ulong> GenerationChannels { get; }
//channelid/message
public ConcurrentDictionary<ulong, List<IUserMessage>> PlantedFlowers { get; } = new ConcurrentDictionary<ulong, List<IUserMessage>>();
//channelId/last generation
public ConcurrentDictionary<ulong, DateTime> LastGenerations { get; } = new ConcurrentDictionary<ulong, DateTime>();
public KeyValuePair<string, ImmutableArray<byte>> GetRandomCurrencyImage()
{
var rng = new NadekoRandom();
return _images.Currency[rng.Next(0, _images.Currency.Length)];
}
private string GetText(ITextChannel ch, string key, params object[] rep)
=> _strings.GetText(key, ch.GuildId, "Games".ToLowerInvariant(), rep);
private Task PotentialFlowerGeneration(SocketMessage imsg)
{
var msg = imsg as SocketUserMessage;
if (msg == null || msg.Author.IsBot)
return Task.CompletedTask;
var channel = imsg.Channel as ITextChannel;
if (channel == null)
return Task.CompletedTask;
if (!GenerationChannels.Contains(channel.Id))
return Task.CompletedTask;
var _ = Task.Run(async () =>
{
try
{
var lastGeneration = LastGenerations.GetOrAdd(channel.Id, DateTime.MinValue);
var rng = new NadekoRandom();
//todo i'm stupid :rofl: wtg kwoth. real async programming :100: :ok_hand: :100: :100: :thumbsup:
if (DateTime.Now - TimeSpan.FromSeconds(_bc.CurrencyGenerationCooldown) < lastGeneration) //recently generated in this channel, don't generate again
return;
var num = rng.Next(1, 101) + _bc.CurrencyGenerationChance * 100;
if (num > 100)
{
LastGenerations.AddOrUpdate(channel.Id, DateTime.Now, (id, old) => DateTime.Now);
var dropAmount = _bc.CurrencyDropAmount;
if (dropAmount > 0)
{
var msgs = new IUserMessage[dropAmount];
var prefix = NadekoBot.Prefix;
var toSend = dropAmount == 1
? GetText(channel, "curgen_sn", _bc.CurrencySign)
+ " " + GetText(channel, "pick_sn", prefix)
: GetText(channel, "curgen_pl", dropAmount, _bc.CurrencySign)
+ " " + GetText(channel, "pick_pl", prefix);
var file = GetRandomCurrencyImage();
using (var fileStream = file.Value.ToStream())
{
var sent = await channel.SendFileAsync(
fileStream,
file.Key,
toSend).ConfigureAwait(false);
msgs[0] = sent;
}
PlantedFlowers.AddOrUpdate(channel.Id, msgs.ToList(), (id, old) => { old.AddRange(msgs); return old; });
}
}
}
catch (Exception ex)
{
LogManager.GetCurrentClassLogger().Warn(ex);
}
});
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,71 @@
using ImageSharp;
using NadekoBot.DataStructures;
using NadekoBot.Extensions;
using NLog;
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
namespace NadekoBot.Services.Games
{
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(IImagesService _images, 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(_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(_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;
}
});
}
}
}

View File

@ -1,4 +1,4 @@
namespace NadekoBot.Modules.Games.Commands.Models namespace NadekoBot.Services.Games
{ {
public class TypingArticle public class TypingArticle
{ {

View File

@ -1,6 +1,10 @@
using NadekoBot.Extensions; using Discord;
using Discord.WebSocket;
using NadekoBot.DataStructures;
using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NLog;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -15,11 +19,158 @@ namespace NadekoBot.Services
private readonly DbHandler _db; private readonly DbHandler _db;
public readonly ConcurrentDictionary<ulong, GreetSettings> GuildConfigsCache; public readonly ConcurrentDictionary<ulong, GreetSettings> GuildConfigsCache;
private readonly DiscordShardedClient _client;
private readonly Logger _log;
public GreetSettingsService(IEnumerable<GuildConfig> guildConfigs, DbHandler db) public GreetSettingsService(DiscordShardedClient client, IEnumerable<GuildConfig> guildConfigs, DbHandler db)
{ {
_db = db; _db = db;
_client = client;
_log = LogManager.GetCurrentClassLogger();
GuildConfigsCache = new ConcurrentDictionary<ulong, GreetSettings>(guildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create)); GuildConfigsCache = new ConcurrentDictionary<ulong, GreetSettings>(guildConfigs.ToDictionary(g => g.GuildId, GreetSettings.Create));
_client.UserJoined += UserJoined;
_client.UserLeft += UserLeft;
}
private Task UserLeft(IGuildUser user)
{
var _ = Task.Run(async () =>
{
try
{
var conf = GetOrAddSettingsForGuild(user.GuildId);
if (!conf.SendChannelByeMessage) return;
var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.ByeMessageChannelId);
if (channel == null) //maybe warn the server owner that the channel is missing
return;
CREmbed embedData;
if (CREmbed.TryParse(conf.ChannelByeMessageText, out embedData))
{
embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Description = embedData.Description?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Title = embedData.Title?.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
try
{
var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
if (conf.AutoDeleteByeMessagesTimer > 0)
{
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
}
}
catch (Exception ex) { _log.Warn(ex); }
}
else
{
var msg = conf.ChannelByeMessageText.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
if (string.IsNullOrWhiteSpace(msg))
return;
try
{
var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false);
if (conf.AutoDeleteByeMessagesTimer > 0)
{
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
}
}
catch (Exception ex) { _log.Warn(ex); }
}
}
catch
{
// ignored
}
});
return Task.CompletedTask;
}
private Task UserJoined(IGuildUser user)
{
var _ = Task.Run(async () =>
{
try
{
var conf = GetOrAddSettingsForGuild(user.GuildId);
if (conf.SendChannelGreetMessage)
{
var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.GreetMessageChannelId);
if (channel != null) //maybe warn the server owner that the channel is missing
{
CREmbed embedData;
if (CREmbed.TryParse(conf.ChannelGreetMessageText, out embedData))
{
embedData.PlainText = embedData.PlainText?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Description = embedData.Description?.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
try
{
var toDelete = await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
if (conf.AutoDeleteGreetMessagesTimer > 0)
{
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
}
}
catch (Exception ex) { _log.Warn(ex); }
}
else
{
var msg = conf.ChannelGreetMessageText.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
if (!string.IsNullOrWhiteSpace(msg))
{
try
{
var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false);
if (conf.AutoDeleteGreetMessagesTimer > 0)
{
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
}
}
catch (Exception ex) { _log.Warn(ex); }
}
}
}
}
if (conf.SendDmGreetMessage)
{
var channel = await user.CreateDMChannelAsync();
if (channel != null)
{
CREmbed embedData;
if (CREmbed.TryParse(conf.ChannelGreetMessageText, out embedData))
{
embedData.PlainText = embedData.PlainText?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Description = embedData.Description?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
embedData.Title = embedData.Title?.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
try
{
await channel.EmbedAsync(embedData.ToEmbed(), embedData.PlainText ?? "").ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
}
else
{
var msg = conf.DmGreetMessageText.Replace("%user%", user.ToString()).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name);
if (!string.IsNullOrWhiteSpace(msg))
{
await channel.SendConfirmAsync(msg).ConfigureAwait(false);
}
}
}
}
}
catch
{
// ignored
}
});
return Task.CompletedTask;
} }
public GreetSettings GetOrAddSettingsForGuild(ulong guildId) public GreetSettings GetOrAddSettingsForGuild(ulong guildId)
@ -210,6 +361,23 @@ namespace NadekoBot.Services
await uow.CompleteAsync().ConfigureAwait(false); await uow.CompleteAsync().ConfigureAwait(false);
} }
} }
public async Task SetGreetDel(ulong id, int timer)
{
if (timer < 0 || timer > 600)
return;
using (var uow = _db.UnitOfWork)
{
var conf = uow.GuildConfigs.For(id, set => set);
conf.AutoDeleteGreetMessagesTimer = timer;
var toAdd = GreetSettings.Create(conf);
GuildConfigsCache.AddOrUpdate(id, toAdd, (key, old) => toAdd);
await uow.CompleteAsync().ConfigureAwait(false);
}
}
} }
public class GreetSettings public class GreetSettings

View File

@ -17,7 +17,8 @@ namespace NadekoBot.Services
string CarbonKey { get; } string CarbonKey { get; }
DBConfig Db { get; } DBConfig Db { get; }
string SoundCloudClientId { get; set; } string SoundCloudClientId { get; }
string OsuApiKey { get; }
bool IsOwner(IUser u); bool IsOwner(IUser u);
} }

View File

@ -1,4 +1,5 @@
using System; using Google.Apis.Customsearch.v1.Data;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -6,12 +7,28 @@ namespace NadekoBot.Services
{ {
public interface IGoogleApiService public interface IGoogleApiService
{ {
IEnumerable<string> Languages { get; }
Task<IEnumerable<string>> GetVideosByKeywordsAsync(string keywords, int count = 1); Task<IEnumerable<string>> GetVideosByKeywordsAsync(string keywords, int count = 1);
Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1); Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1);
Task<IEnumerable<string>> GetRelatedVideosAsync(string url, int count = 1); Task<IEnumerable<string>> GetRelatedVideosAsync(string url, int count = 1);
Task<IEnumerable<string>> GetPlaylistTracksAsync(string playlistId, int count = 50); Task<IEnumerable<string>> GetPlaylistTracksAsync(string playlistId, int count = 50);
Task<IReadOnlyDictionary<string, TimeSpan>> GetVideoDurationsAsync(IEnumerable<string> videoIds); Task<IReadOnlyDictionary<string, TimeSpan>> GetVideoDurationsAsync(IEnumerable<string> videoIds);
Task<ImageResult> GetImageAsync(string query, int start = 1);
Task<string> Translate(string sourceText, string sourceLanguage, string targetLanguage);
Task<string> ShortenUrl(string url); Task<string> ShortenUrl(string url);
} }
public struct ImageResult
{
public Result.ImageData Image { get; }
public string Link { get; }
public ImageResult(Result.ImageData image, string link)
{
this.Image = image;
this.Link = link;
}
}
} }

View File

@ -32,7 +32,7 @@ namespace NadekoBot.Services.Impl
? "d0bd7768e3a1a2d15430f0dccb871117" ? "d0bd7768e3a1a2d15430f0dccb871117"
: _soundcloudClientId; : _soundcloudClientId;
} }
set { private set {
_soundcloudClientId = value; _soundcloudClientId = value;
} }
} }

View File

@ -9,7 +9,9 @@ using Google.Apis.Urlshortener.v1;
using Google.Apis.Urlshortener.v1.Data; using Google.Apis.Urlshortener.v1.Data;
using NLog; using NLog;
using Google.Apis.Customsearch.v1; using Google.Apis.Customsearch.v1;
using Google.Apis.Customsearch.v1.Data; using System.Net.Http;
using System.Net;
using Newtonsoft.Json.Linq;
namespace NadekoBot.Services.Impl namespace NadekoBot.Services.Impl
{ {
@ -20,7 +22,7 @@ namespace NadekoBot.Services.Impl
private YouTubeService yt; private YouTubeService yt;
private UrlshortenerService sh; private UrlshortenerService sh;
private CustomsearchService cs; private CustomsearchService cs;
private Logger _log { get; } private Logger _log { get; }
public GoogleApiService(IBotCredentials creds) public GoogleApiService(IBotCredentials creds)
@ -39,6 +41,7 @@ namespace NadekoBot.Services.Impl
sh = new UrlshortenerService(bcs); sh = new UrlshortenerService(bcs);
cs = new CustomsearchService(bcs); cs = new CustomsearchService(bcs);
} }
public async Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1) public async Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1)
{ {
if (string.IsNullOrWhiteSpace(keywords)) if (string.IsNullOrWhiteSpace(keywords))
@ -190,18 +193,6 @@ namespace NadekoBot.Services.Impl
return toReturn; return toReturn;
} }
public struct ImageResult
{
public Result.ImageData Image { get; }
public string Link { get; }
public ImageResult(Result.ImageData image, string link)
{
this.Image = image;
this.Link = link;
}
}
public async Task<ImageResult> GetImageAsync(string query, int start = 1) public async Task<ImageResult> GetImageAsync(string query, int start = 1)
{ {
if (string.IsNullOrWhiteSpace(query)) if (string.IsNullOrWhiteSpace(query))
@ -218,5 +209,168 @@ namespace NadekoBot.Services.Impl
return new ImageResult(search.Items[0].Image, search.Items[0].Link); return new ImageResult(search.Items[0].Image, search.Items[0].Link);
} }
public IEnumerable<string> Languages => _languageDictionary.Keys.OrderBy(x => x);
private readonly Dictionary<string, string> _languageDictionary = new Dictionary<string, string>() {
{ "afrikaans", "af"},
{ "albanian", "sq"},
{ "arabic", "ar"},
{ "armenian", "hy"},
{ "azerbaijani", "az"},
{ "basque", "eu"},
{ "belarusian", "be"},
{ "bengali", "bn"},
{ "bulgarian", "bg"},
{ "catalan", "ca"},
{ "chinese-traditional", "zh-TW"},
{ "chinese-simplified", "zh-CN"},
{ "chinese", "zh-CN"},
{ "croatian", "hr"},
{ "czech", "cs"},
{ "danish", "da"},
{ "dutch", "nl"},
{ "english", "en"},
{ "esperanto", "eo"},
{ "estonian", "et"},
{ "filipino", "tl"},
{ "finnish", "fi"},
{ "french", "fr"},
{ "galician", "gl"},
{ "german", "de"},
{ "georgian", "ka"},
{ "greek", "el"},
{ "haitian Creole", "ht"},
{ "hebrew", "iw"},
{ "hindi", "hi"},
{ "hungarian", "hu"},
{ "icelandic", "is"},
{ "indonesian", "id"},
{ "irish", "ga"},
{ "italian", "it"},
{ "japanese", "ja"},
{ "korean", "ko"},
{ "lao", "lo"},
{ "latin", "la"},
{ "latvian", "lv"},
{ "lithuanian", "lt"},
{ "macedonian", "mk"},
{ "malay", "ms"},
{ "maltese", "mt"},
{ "norwegian", "no"},
{ "persian", "fa"},
{ "polish", "pl"},
{ "portuguese", "pt"},
{ "romanian", "ro"},
{ "russian", "ru"},
{ "serbian", "sr"},
{ "slovak", "sk"},
{ "slovenian", "sl"},
{ "spanish", "es"},
{ "swahili", "sw"},
{ "swedish", "sv"},
{ "tamil", "ta"},
{ "telugu", "te"},
{ "thai", "th"},
{ "turkish", "tr"},
{ "ukrainian", "uk"},
{ "urdu", "ur"},
{ "vietnamese", "vi"},
{ "welsh", "cy"},
{ "yiddish", "yi"},
{ "af", "af"},
{ "sq", "sq"},
{ "ar", "ar"},
{ "hy", "hy"},
{ "az", "az"},
{ "eu", "eu"},
{ "be", "be"},
{ "bn", "bn"},
{ "bg", "bg"},
{ "ca", "ca"},
{ "zh-tw", "zh-TW"},
{ "zh-cn", "zh-CN"},
{ "hr", "hr"},
{ "cs", "cs"},
{ "da", "da"},
{ "nl", "nl"},
{ "en", "en"},
{ "eo", "eo"},
{ "et", "et"},
{ "tl", "tl"},
{ "fi", "fi"},
{ "fr", "fr"},
{ "gl", "gl"},
{ "de", "de"},
{ "ka", "ka"},
{ "el", "el"},
{ "ht", "ht"},
{ "iw", "iw"},
{ "hi", "hi"},
{ "hu", "hu"},
{ "is", "is"},
{ "id", "id"},
{ "ga", "ga"},
{ "it", "it"},
{ "ja", "ja"},
{ "ko", "ko"},
{ "lo", "lo"},
{ "la", "la"},
{ "lv", "lv"},
{ "lt", "lt"},
{ "mk", "mk"},
{ "ms", "ms"},
{ "mt", "mt"},
{ "no", "no"},
{ "fa", "fa"},
{ "pl", "pl"},
{ "pt", "pt"},
{ "ro", "ro"},
{ "ru", "ru"},
{ "sr", "sr"},
{ "sk", "sk"},
{ "sl", "sl"},
{ "es", "es"},
{ "sw", "sw"},
{ "sv", "sv"},
{ "ta", "ta"},
{ "te", "te"},
{ "th", "th"},
{ "tr", "tr"},
{ "uk", "uk"},
{ "ur", "ur"},
{ "vi", "vi"},
{ "cy", "cy"},
{ "yi", "yi"},
};
public async Task<string> Translate(string sourceText, string sourceLanguage, string targetLanguage)
{
string text;
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 (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);
}
return (string.Concat(JArray.Parse(text)[0].Select(x => x[0])));
}
private string ConvertToLanguageCode(string language)
{
string mode;
_languageDictionary.TryGetValue(language, out mode);
return mode;
}
} }
} }

View File

@ -7,6 +7,7 @@ using NLog;
using System.Diagnostics; using System.Diagnostics;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using Discord;
namespace NadekoBot.Services namespace NadekoBot.Services
{ {
@ -20,10 +21,13 @@ namespace NadekoBot.Services
/// Used as failsafe in case response key doesn't exist in the selected or default language. /// Used as failsafe in case response key doesn't exist in the selected or default language.
/// </summary> /// </summary>
private readonly CultureInfo _usCultureInfo = new CultureInfo("en-US"); private readonly CultureInfo _usCultureInfo = new CultureInfo("en-US");
private readonly ILocalization _localization;
public NadekoStrings() public NadekoStrings(ILocalization loc)
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
_localization = loc;
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
var allLangsDict = new Dictionary<string, ImmutableDictionary<string, string>>(); // lang:(name:value) var allLangsDict = new Dictionary<string, ImmutableDictionary<string, string>>(); // lang:(name:value)
foreach (var file in Directory.GetFiles(stringsPath)) foreach (var file in Directory.GetFiles(stringsPath))
@ -58,6 +62,9 @@ namespace NadekoBot.Services
return val; return val;
} }
public string GetText(string key, ulong guildId, string lowerModuleTypeName, params object[] replacements) =>
GetText(key, _localization.GetCultureInfo(guildId), lowerModuleTypeName);
public string GetText(string key, CultureInfo cultureInfo, string lowerModuleTypeName) public string GetText(string key, CultureInfo cultureInfo, string lowerModuleTypeName)
{ {
var text = GetString(lowerModuleTypeName + "_" + key, cultureInfo); var text = GetString(lowerModuleTypeName + "_" + key, cultureInfo);

View File

@ -1,6 +1,6 @@
namespace NadekoBot.Modules.Searches.Models namespace NadekoBot.Services.Searches
{ {
class MagicItem public class MagicItem
{ {
public string Name { get; set; } public string Name { get; set; }
public string Description { get; set; } public string Description { get; set; }

View File

@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace NadekoBot.Modules.Searches.Models namespace NadekoBot.Services.Searches
{ {
public class SearchPokemon public class SearchPokemon
{ {

View File

@ -1,4 +1,12 @@
using NadekoBot.Extensions; using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
@ -7,6 +15,101 @@ namespace NadekoBot.Services.Searches
{ {
public class SearchesService public class SearchesService
{ {
private readonly DiscordShardedClient _client;
private readonly IGoogleApiService _google;
private readonly DbHandler _db;
private readonly Logger _log;
public ConcurrentDictionary<ulong, bool> TranslatedChannels { get; } = new ConcurrentDictionary<ulong, bool>();
public ConcurrentDictionary<UserChannelPair, string> UserLanguages { get; } = new ConcurrentDictionary<UserChannelPair, string>();
public readonly string PokemonAbilitiesFile = "data/pokemon/pokemon_abilities7.json";
public readonly string PokemonListFile = "data/pokemon/pokemon_list7.json";
public Dictionary<string, SearchPokemon> Pokemons { get; } = new Dictionary<string, SearchPokemon>();
public Dictionary<string, SearchPokemonAbility> PokemonAbilities { get; } = new Dictionary<string, SearchPokemonAbility>();
public List<WoWJoke> WowJokes { get; } = new List<WoWJoke>();
public List<MagicItem> MagicItems { get; } = new List<MagicItem>();
public SearchesService(DiscordShardedClient client, IGoogleApiService google, DbHandler db)
{
_client = client;
_google = google;
_db = db;
_log = LogManager.GetCurrentClassLogger();
//translate commands
_client.MessageReceived += async (msg) =>
{
try
{
var umsg = msg as SocketUserMessage;
if (umsg == null)
return;
if (!TranslatedChannels.TryGetValue(umsg.Channel.Id, out var autoDelete))
return;
var key = new UserChannelPair()
{
UserId = umsg.Author.Id,
ChannelId = umsg.Channel.Id,
};
string langs;
if (!UserLanguages.TryGetValue(key, out langs))
return;
var text = await Translate(langs, umsg.Resolve(TagHandling.Ignore))
.ConfigureAwait(false);
if (autoDelete)
try { await umsg.DeleteAsync().ConfigureAwait(false); } catch { }
await umsg.Channel.SendConfirmAsync($"{umsg.Author.Mention} `:` " + text.Replace("<@ ", "<@").Replace("<@! ", "<@!")).ConfigureAwait(false);
}
catch { }
};
//pokemon commands
if (File.Exists(PokemonListFile))
{
Pokemons = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemon>>(File.ReadAllText(PokemonListFile));
}
else
_log.Warn(PokemonListFile + " is missing. Pokemon abilities not loaded.");
if (File.Exists(PokemonAbilitiesFile))
PokemonAbilities = JsonConvert.DeserializeObject<Dictionary<string, SearchPokemonAbility>>(File.ReadAllText(PokemonAbilitiesFile));
else
_log.Warn(PokemonAbilitiesFile + " is missing. Pokemon abilities not loaded.");
//joke commands
if (File.Exists("data/wowjokes.json"))
{
WowJokes = JsonConvert.DeserializeObject<List<WoWJoke>>(File.ReadAllText("data/wowjokes.json"));
}
else
_log.Warn("data/wowjokes.json is missing. WOW Jokes are not loaded.");
if (File.Exists("data/magicitems.json"))
{
MagicItems = JsonConvert.DeserializeObject<List<MagicItem>>(File.ReadAllText("data/magicitems.json"));
}
else
_log.Warn("data/magicitems.json is missing. Magic items are not loaded.");
}
public async Task<string> Translate(string langs, string text = null)
{
var langarr = langs.ToLowerInvariant().Split('>');
if (langarr.Length != 2)
throw new ArgumentException();
var from = langarr[0];
var to = langarr[1];
text = text?.Trim();
if (string.IsNullOrWhiteSpace(text))
throw new ArgumentException();
return (await _google.Translate(text, from, to).ConfigureAwait(false)).SanitizeMentions();
}
public async Task<string> DapiSearch(string tag, DapiSearchType type) public async Task<string> DapiSearch(string tag, DapiSearchType type)
{ {
tag = tag?.Replace(" ", "_"); tag = tag?.Replace(" ", "_");
@ -65,4 +168,18 @@ namespace NadekoBot.Services.Searches
Rule34, Rule34,
Yandere Yandere
} }
public struct UserChannelPair
{
public ulong UserId { get; set; }
public ulong ChannelId { get; set; }
}
public class StreamStatus
{
public bool IsLive { get; set; }
public string ApiLink { get; set; }
public string Views { get; set; }
}
} }

View File

@ -0,0 +1,11 @@
using System;
namespace NadekoBot.Services.Searches
{
public class StreamNotFoundException : Exception
{
public StreamNotFoundException(string message) : base($"Stream '{message}' not found.")
{
}
}
}

View File

@ -0,0 +1,190 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services.Database.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Services.Searches
{
public class StreamNotificationService
{
private readonly Timer _streamCheckTimer;
private bool firstStreamNotifPass { get; set; } = true;
private readonly ConcurrentDictionary<string, StreamStatus> _cachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
private readonly DbHandler _db;
private readonly DiscordShardedClient _client;
private readonly NadekoStrings _strings;
public StreamNotificationService(DbHandler db, DiscordShardedClient client, NadekoStrings strings)
{
_db = db;
_client = client;
_strings = strings;
}
public StreamNotificationService()
{
_streamCheckTimer = new Timer(async (state) =>
{
var oldCachedStatuses = new ConcurrentDictionary<string, StreamStatus>(_cachedStatuses);
_cachedStatuses.Clear();
IEnumerable<FollowedStream> streams;
using (var uow = _db.UnitOfWork)
{
streams = uow.GuildConfigs.GetAllFollowedStreams();
}
await Task.WhenAll(streams.Select(async fs =>
{
try
{
var newStatus = await GetStreamStatus(fs).ConfigureAwait(false);
if (firstStreamNotifPass)
{
return;
}
StreamStatus oldStatus;
if (oldCachedStatuses.TryGetValue(newStatus.ApiLink, out oldStatus) &&
oldStatus.IsLive != newStatus.IsLive)
{
var server = _client.GetGuild(fs.GuildId);
var channel = server?.GetTextChannel(fs.ChannelId);
if (channel == null)
return;
try
{
await channel.EmbedAsync(GetEmbed(fs, newStatus, channel.Guild.Id)).ConfigureAwait(false);
}
catch
{
// ignored
}
}
}
catch
{
// ignored
}
}));
firstStreamNotifPass = false;
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(60));
}
public async Task<StreamStatus> GetStreamStatus(FollowedStream stream, bool checkCache = true)
{
string response;
StreamStatus result;
switch (stream.Type)
{
case FollowedStream.FollowedStreamType.Hitbox:
var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username.ToLowerInvariant()}";
if (checkCache && _cachedStatuses.TryGetValue(hitboxUrl, out result))
return result;
using (var http = new HttpClient())
{
response = await http.GetStringAsync(hitboxUrl).ConfigureAwait(false);
}
var hbData = JsonConvert.DeserializeObject<HitboxResponse>(response);
if (!hbData.Success)
throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
result = new StreamStatus()
{
IsLive = hbData.IsLive,
ApiLink = hitboxUrl,
Views = hbData.Views
};
_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))
return result;
using (var http = new HttpClient())
{
response = await http.GetStringAsync(twitchUrl).ConfigureAwait(false);
}
var twData = JsonConvert.DeserializeObject<TwitchResponse>(response);
if (twData.Error != null)
{
throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
}
result = new StreamStatus()
{
IsLive = twData.IsLive,
ApiLink = twitchUrl,
Views = twData.Stream?.Viewers.ToString() ?? "0"
};
_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))
return result;
using (var http = new HttpClient())
{
response = await http.GetStringAsync(beamUrl).ConfigureAwait(false);
}
var bmData = JsonConvert.DeserializeObject<BeamResponse>(response);
if (bmData.Error != null)
throw new StreamNotFoundException($"{stream.Username} [{stream.Type}]");
result = new StreamStatus()
{
IsLive = bmData.IsLive,
ApiLink = beamUrl,
Views = bmData.ViewersCurrent.ToString()
};
_cachedStatuses.AddOrUpdate(beamUrl, result, (key, old) => result);
return result;
default:
break;
}
return null;
}
public EmbedBuilder GetEmbed(FollowedStream fs, StreamStatus status, ulong guildId)
{
var embed = new EmbedBuilder().WithTitle(fs.Username)
.WithUrl(GetLink(fs))
.AddField(efb => efb.WithName(GetText(fs, "status"))
.WithValue(status.IsLive ? "Online" : "Offline")
.WithIsInline(true))
.AddField(efb => efb.WithName(GetText(fs, "viewers"))
.WithValue(status.IsLive ? status.Views : "-")
.WithIsInline(true))
.AddField(efb => efb.WithName(GetText(fs, "platform"))
.WithValue(fs.Type.ToString())
.WithIsInline(true))
.WithColor(status.IsLive ? NadekoBot.OkColor : NadekoBot.ErrorColor);
return embed;
}
public string GetText(FollowedStream fs, string key, params object[] replacements) =>
_strings.GetText(key,
fs.GuildId,
"Searches".ToLowerInvariant(),
replacements);
public string GetLink(FollowedStream fs)
{
if (fs.Type == FollowedStream.FollowedStreamType.Hitbox)
return $"http://www.hitbox.tv/{fs.Username}/";
if (fs.Type == FollowedStream.FollowedStreamType.Twitch)
return $"http://www.twitch.tv/{fs.Username}/";
if (fs.Type == FollowedStream.FollowedStreamType.Beam)
return $"https://beam.pro/{fs.Username}/";
return "??";
}
}
}

View File

@ -0,0 +1,35 @@
using Newtonsoft.Json;
namespace NadekoBot.Services.Searches
{
public class HitboxResponse
{
public bool Success { get; set; } = true;
[JsonProperty("media_is_live")]
public string MediaIsLive { get; set; }
public bool IsLive => MediaIsLive == "1";
[JsonProperty("media_views")]
public string Views { get; set; }
}
public class TwitchResponse
{
public string Error { get; set; } = null;
public bool IsLive => Stream != null;
public StreamInfo Stream { get; set; }
public class StreamInfo
{
public int Viewers { get; set; }
}
}
public class BeamResponse
{
public string Error { get; set; } = null;
[JsonProperty("online")]
public bool IsLive { get; set; }
public int ViewersCurrent { get; set; }
}
}

View File

@ -1,4 +1,4 @@
namespace NadekoBot.Modules.Searches.Models namespace NadekoBot.Services.Searches
{ {
public class WoWJoke public class WoWJoke
{ {