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 NadekoBot.Services;
using NadekoBot.Attributes;
using Discord.WebSocket;
using NadekoBot.Services.Database.Models;
using static NadekoBot.Modules.Permissions.Permissions;
using System.Collections.Concurrent;
using NLog;
using NadekoBot.Modules.Permissions;
using NadekoBot.Services.Administration;
namespace NadekoBot.Modules.Administration
{
[NadekoModule("Administration", ".")]
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;
static Administration()
public Administration(DbHandler db, AdministrationService admin)
{
_log = LogManager.GetCurrentClassLogger();
NadekoBot.CommandHandler.CommandExecuted += DelMsgOnCmd_Handler;
deleteMessagesOnCommand = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs.Where(g => g.DeleteMessageOnCommand).Select(g => g.GuildId));
_db = db;
_admin = admin;
}
private static 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(ex, "Delmsgoncmd errored...");
}
});
return Task.CompletedTask;
}
////todo permissions
//[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)]
//[RequireUserPermission(GuildPermission.Administrator)]
//public async Task ResetPermissions()
//{
// using (var uow = _db.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 = _db.UnitOfWork)
// {
// var gc = uow.BotConfig.GetOrCreate();
// gc.BlockedCommands.Clear();
// gc.BlockedModules.Clear();
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
public async Task ResetPermissions()
{
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);
}
// GlobalPermissionCommands.BlockedCommands.Clear();
// GlobalPermissionCommands.BlockedModules.Clear();
// await uow.CompleteAsync();
// }
// await ReplyConfirmLocalized("global_perms_reset").ConfigureAwait(false);
//}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
@ -91,7 +63,7 @@ namespace NadekoBot.Modules.Administration
public async Task Delmsgoncmd()
{
bool enabled;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
var conf = uow.GuildConfigs.For(Context.Guild.Id, set => set);
enabled = conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand;
@ -100,12 +72,12 @@ namespace NadekoBot.Modules.Administration
}
if (enabled)
{
deleteMessagesOnCommand.Add(Context.Guild.Id);
_admin.DeleteMessagesOnCommand.Add(Context.Guild.Id);
await ReplyConfirmLocalized("delmsg_on").ConfigureAwait(false);
}
else
{
deleteMessagesOnCommand.TryRemove(Context.Guild.Id);
_admin.DeleteMessagesOnCommand.TryRemove(Context.Guild.Id);
await ReplyConfirmLocalized("delmsg_off").ConfigureAwait(false);
}
}
@ -454,19 +426,18 @@ namespace NadekoBot.Modules.Administration
await Context.Channel.SendMessageAsync(send).ConfigureAwait(false);
}
private IGuild _nadekoSupportServer;
[NadekoCommand, Usage, Description, Aliases]
public async Task Donators()
{
IEnumerable<Donator> donatorsOrdered;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
donatorsOrdered = uow.Donators.GetDonatorsOrdered();
}
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);
if (patreonRole == null)
@ -482,7 +453,7 @@ namespace NadekoBot.Modules.Administration
public async Task Donadd(IUser donator, int amount)
{
Donator don;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
don = uow.Donators.AddOrUpdateDonator(donator.Id, donator.Username, amount);
await uow.CompleteAsync();

View File

@ -4,6 +4,7 @@ using Discord.WebSocket;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Attributes;
using NadekoBot.Services;
using NadekoBot.Services.Administration;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
@ -19,232 +20,13 @@ namespace NadekoBot.Modules.Administration
[Group]
public class MuteCommands : NadekoSubmodule
{
private static ConcurrentDictionary<ulong, string> GuildMuteRoles { get; }
private static ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> MutedUsers { get; }
private static ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>> UnmuteTimers { get; }
= new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, Timer>>();
private readonly MuteService _service;
private readonly DbHandler _db;
public static event Action<IGuildUser, MuteType> UserMuted = delegate { };
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()
public MuteCommands(MuteService service, DbHandler db)
{
var configs = NadekoBot.AllGuildConfigs;
GuildMuteRoles = new ConcurrentDictionary<ulong, string>(configs
.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();
}
_service = service;
_db = db;
}
[NadekoCommand, Usage, Description, Aliases]
@ -257,11 +39,11 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(name))
return;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set);
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 ReplyConfirmLocalized("mute_role_set").ConfigureAwait(false);
@ -283,7 +65,7 @@ namespace NadekoBot.Modules.Administration
{
try
{
await MuteUser(user).ConfigureAwait(false);
await _service.MuteUser(user).ConfigureAwait(false);
await ReplyConfirmLocalized("user_muted", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
@ -303,7 +85,7 @@ namespace NadekoBot.Modules.Administration
return;
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);
}
catch (Exception ex)
@ -321,7 +103,7 @@ namespace NadekoBot.Modules.Administration
{
try
{
await UnmuteUser(user).ConfigureAwait(false);
await _service.UnmuteUser(user).ConfigureAwait(false);
await ReplyConfirmLocalized("user_unmuted", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
@ -337,8 +119,7 @@ namespace NadekoBot.Modules.Administration
{
try
{
await user.AddRoleAsync(await GetMuteRole(Context.Guild).ConfigureAwait(false)).ConfigureAwait(false);
UserMuted(user, MuteType.Chat);
await _service.MuteUser(user, MuteType.Chat).ConfigureAwait(false);
await ReplyConfirmLocalized("user_chat_mute", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
@ -354,8 +135,7 @@ namespace NadekoBot.Modules.Administration
{
try
{
await user.RemoveRoleAsync(await GetMuteRole(Context.Guild).ConfigureAwait(false)).ConfigureAwait(false);
UserUnmuted(user, MuteType.Chat);
await _service.UnmuteUser(user, MuteType.Chat).ConfigureAwait(false);
await ReplyConfirmLocalized("user_chat_unmute", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
@ -367,12 +147,11 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.MuteMembers)]
public async Task VoiceMute(IGuildUser user)
public async Task VoiceMute([Remainder] IGuildUser user)
{
try
{
await user.ModifyAsync(usr => usr.Mute = true).ConfigureAwait(false);
UserMuted(user, MuteType.Voice);
await _service.MuteUser(user, MuteType.Voice).ConfigureAwait(false);
await ReplyConfirmLocalized("user_voice_mute", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
@ -384,12 +163,11 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.MuteMembers)]
public async Task VoiceUnmute(IGuildUser user)
public async Task VoiceUnmute([Remainder] IGuildUser user)
{
try
{
await user.ModifyAsync(usr => usr.Mute = false).ConfigureAwait(false);
UserUnmuted(user, MuteType.Voice);
await _service.UnmuteUser(user, MuteType.Voice).ConfigureAwait(false);
await ReplyConfirmLocalized("user_voice_unmute", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch

View File

@ -15,6 +15,7 @@ using Microsoft.EntityFrameworkCore;
using System.Collections.Immutable;
using NadekoBot.DataStructures;
using NLog;
using NadekoBot.Services.Administration;
namespace NadekoBot.Modules.Administration
{
@ -23,34 +24,20 @@ namespace NadekoBot.Modules.Administration
[Group]
public class SelfCommands : NadekoSubmodule
{
private static volatile bool _forwardDMs;
private static volatile bool _forwardDMsToAllOwners;
private readonly DbHandler _db;
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;
static SelfCommands()
public SelfCommands(DbHandler db, SelfService service, DiscordShardedClient client,
IImagesService images)
{
_log = LogManager.GetCurrentClassLogger();
using (var uow = DbHandler.UnitOfWork())
{
var config = uow.BotConfig.GetOrCreate();
_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);
}
});
_db = db;
_service = service;
_client = client;
_images = images;
}
[NadekoCommand, Usage, Description, Aliases]
@ -69,7 +56,7 @@ namespace NadekoBot.Modules.Administration
VoiceChannelId = guser.VoiceChannel?.Id,
VoiceChannelName = guser.VoiceChannel?.Name,
};
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
uow.BotConfig
.GetOrCreate(set => set.Include(x => x.StartupCommands))
@ -96,7 +83,7 @@ namespace NadekoBot.Modules.Administration
return;
page -= 1;
IEnumerable<StartupCommand> scmds;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
scmds = uow.BotConfig
.GetOrCreate(set => set.Include(x => x.StartupCommands))
@ -148,7 +135,7 @@ namespace NadekoBot.Modules.Administration
public async Task StartupCommandRemove([Remainder] string cmdText)
{
StartupCommand cmd;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
var cmds = uow.BotConfig
.GetOrCreate(set => set.Include(x => x.StartupCommands))
@ -174,7 +161,7 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public async Task StartupCommandsClear()
{
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
uow.BotConfig
.GetOrCreate(set => set.Include(x => x.StartupCommands))
@ -190,14 +177,13 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public async Task ForwardMessages()
{
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
var config = uow.BotConfig.GetOrCreate();
lock (_locker)
_forwardDMs = config.ForwardMessages = !config.ForwardMessages;
_service.ForwardDMs = config.ForwardMessages = !config.ForwardMessages;
uow.Complete();
}
if (_forwardDMs)
if (_service.ForwardDMs)
await ReplyConfirmLocalized("fwdm_start").ConfigureAwait(false);
else
await ReplyConfirmLocalized("fwdm_stop").ConfigureAwait(false);
@ -207,83 +193,83 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public async Task ForwardToAll()
{
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
var config = uow.BotConfig.GetOrCreate();
lock (_locker)
_forwardDMsToAllOwners = config.ForwardToAllOwners = !config.ForwardToAllOwners;
_service.ForwardDMsToAllOwners = config.ForwardToAllOwners = !config.ForwardToAllOwners;
uow.Complete();
}
if (_forwardDMsToAllOwners)
if (_service.ForwardDMsToAllOwners)
await ReplyConfirmLocalized("fwall_start").ConfigureAwait(false);
else
await ReplyConfirmLocalized("fwall_stop").ConfigureAwait(false);
}
public static async Task HandleDmForwarding(IUserMessage msg, ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels)
{
if (_forwardDMs && ownerChannels.Length > 0)
{
var title = GetTextStatic("dm_from",
NadekoBot.Localization.DefaultCultureInfo,
typeof(Administration).Name.ToLowerInvariant()) +
$" [{msg.Author}]({msg.Author.Id})";
//todo dm forwarding
//public async Task HandleDmForwarding(IUserMessage msg, ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels)
//{
// if (_service.ForwardDMs && ownerChannels.Length > 0)
// {
// var title = _strings.GetText("dm_from",
// NadekoBot.Localization.DefaultCultureInfo,
// typeof(Administration).Name.ToLowerInvariant()) +
// $" [{msg.Author}]({msg.Author.Id})";
var attachamentsTxt = GetTextStatic("attachments",
NadekoBot.Localization.DefaultCultureInfo,
typeof(Administration).Name.ToLowerInvariant());
// var attachamentsTxt = GetTextStatic("attachments",
// NadekoBot.Localization.DefaultCultureInfo,
// typeof(Administration).Name.ToLowerInvariant());
var toSend = msg.Content;
// var toSend = msg.Content;
if (msg.Attachments.Count > 0)
{
toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n" +
string.Join("\n", msg.Attachments.Select(a => a.ProxyUrl));
}
// if (msg.Attachments.Count > 0)
// {
// toSend += $"\n\n{Format.Code(attachamentsTxt)}:\n" +
// 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]
[OwnerOnly]
public async Task ConnectShard(int shardid)
{
var shard = NadekoBot.Client.GetShard(shardid);
var shard = _client.GetShard(shardid);
if (shard == null)
{
@ -307,15 +293,15 @@ namespace NadekoBot.Modules.Administration
public async Task Leave([Remainder] string guildStr)
{
guildStr = guildStr.Trim().ToUpperInvariant();
var server = NadekoBot.Client.Guilds.FirstOrDefault(g => g.Id.ToString() == guildStr) ??
NadekoBot.Client.Guilds.FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == guildStr);
var server = _client.Guilds.FirstOrDefault(g => g.Id.ToString() == guildStr) ??
_client.Guilds.FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == guildStr);
if (server == null)
{
await ReplyErrorLocalized("no_server").ConfigureAwait(false);
return;
}
if (server.OwnerId != NadekoBot.Client.CurrentUser.Id)
if (server.OwnerId != _client.CurrentUser.Id)
{
await server.LeaveAsync().ConfigureAwait(false);
await ReplyConfirmLocalized("left_server", Format.Bold(server.Name)).ConfigureAwait(false);
@ -351,7 +337,7 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(newName))
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);
}
@ -360,7 +346,7 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
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);
}
@ -380,7 +366,7 @@ namespace NadekoBot.Modules.Administration
await sr.CopyToAsync(imgStream);
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]
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);
}
@ -402,7 +388,7 @@ namespace NadekoBot.Modules.Administration
{
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);
}
@ -418,7 +404,7 @@ namespace NadekoBot.Modules.Administration
if (ids.Length != 2)
return;
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)
return;
@ -455,7 +441,7 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
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)
return;
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]
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);
}

View File

@ -1,14 +1,9 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.DataStructures;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration
@ -18,158 +13,13 @@ namespace NadekoBot.Modules.Administration
[Group]
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; }
private static readonly GreetSettingsService greetService;
static ServerGreetCommands()
public ServerGreetCommands(GreetSettingsService greetService, DbHandler db)
{
NadekoBot.Client.UserJoined += UserJoined;
NadekoBot.Client.UserLeft += UserLeft;
_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;
_greetService = greetService;
_db = db;
}
[NadekoCommand, Usage, Description, Aliases]
@ -180,7 +30,7 @@ namespace NadekoBot.Modules.Administration
if (timer < 0 || timer > 600)
return;
await ServerGreetCommands.SetGreetDel(Context.Guild.Id, timer).ConfigureAwait(false);
await _greetService.SetGreetDel(Context.Guild.Id, timer).ConfigureAwait(false);
if (timer > 0)
await ReplyConfirmLocalized("greetdel_on", timer).ConfigureAwait(false);
@ -188,29 +38,12 @@ namespace NadekoBot.Modules.Administration
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]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageGuild)]
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)
await ReplyConfirmLocalized("greet_on").ConfigureAwait(false);
@ -226,7 +59,7 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(text))
{
string channelGreetMessageText;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
channelGreetMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelGreetMessageText;
}
@ -234,7 +67,7 @@ namespace NadekoBot.Modules.Administration
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);
if (!sendGreetEnabled)
@ -246,7 +79,7 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageGuild)]
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)
await ReplyConfirmLocalized("greetdm_on").ConfigureAwait(false);
@ -262,7 +95,7 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(text))
{
GuildConfig config;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
config = uow.GuildConfigs.For(Context.Guild.Id);
}
@ -270,7 +103,7 @@ namespace NadekoBot.Modules.Administration
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);
if (!sendGreetEnabled)
@ -282,7 +115,7 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageGuild)]
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)
await ReplyConfirmLocalized("bye_on").ConfigureAwait(false);
@ -298,7 +131,7 @@ namespace NadekoBot.Modules.Administration
if (string.IsNullOrWhiteSpace(text))
{
string byeMessageText;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
byeMessageText = uow.GuildConfigs.For(Context.Guild.Id, set => set).ChannelByeMessageText;
}
@ -306,7 +139,7 @@ namespace NadekoBot.Modules.Administration
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);
if (!sendByeEnabled)
@ -318,7 +151,7 @@ namespace NadekoBot.Modules.Administration
[RequireUserPermission(GuildPermission.ManageGuild)]
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)
await ReplyConfirmLocalized("byedel_on", timer).ConfigureAwait(false);

View File

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

View File

@ -10,6 +10,7 @@ using Microsoft.EntityFrameworkCore;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Administration;
namespace NadekoBot.Modules.Administration
{
@ -18,86 +19,13 @@ namespace NadekoBot.Modules.Administration
[Group]
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;
VcRoles = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>>();
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;
_service = service;
_db = db;
}
[NadekoCommand, Usage, Description, Aliases]
@ -118,14 +46,14 @@ namespace NadekoBot.Modules.Administration
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 (guildVcRoles.TryRemove(vc.Id, out role))
{
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));
conf.VcRoleInfos.RemoveWhere(x => x.VoiceChannelId == vc.Id);
@ -136,7 +64,7 @@ namespace NadekoBot.Modules.Administration
else
{
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));
conf.VcRoleInfos.RemoveWhere(x => x.VoiceChannelId == vc.Id); // remove old one
@ -157,7 +85,7 @@ namespace NadekoBot.Modules.Administration
{
var guild = (SocketGuild) Context.Guild;
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())
{

View File

@ -4,6 +4,7 @@ using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Administration;
using NLog;
using System;
using System.Collections.Concurrent;
@ -21,140 +22,15 @@ namespace NadekoBot.Modules.Administration
[Group]
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);
private static readonly ConcurrentHashSet<ulong> _voicePlusTextCache;
private static readonly ConcurrentDictionary<ulong, SemaphoreSlim> _guildLockObjects = new ConcurrentDictionary<ulong, SemaphoreSlim>();
static VoicePlusTextCommands()
public VoicePlusTextCommands(VplusTService service, DbHandler db)
{
_log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
_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");
_service = service;
_db = db;
}
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]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]
@ -184,7 +60,7 @@ namespace NadekoBot.Modules.Administration
try
{
bool isEnabled;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
var conf = uow.GuildConfigs.For(guild.Id, set => set);
isEnabled = conf.VoicePlusTextEnabled = !conf.VoicePlusTextEnabled;
@ -192,7 +68,7 @@ namespace NadekoBot.Modules.Administration
}
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")))
{
try { await textChannel.DeleteAsync().ConfigureAwait(false); } catch { }
@ -207,7 +83,7 @@ namespace NadekoBot.Modules.Administration
await ReplyConfirmLocalized("vt_disabled").ConfigureAwait(false);
return;
}
_voicePlusTextCache.Add(guild.Id);
_service.VoicePlusTextCache.Add(guild.Id);
await ReplyConfirmLocalized("vt_enabled").ConfigureAwait(false);
}
@ -236,7 +112,7 @@ namespace NadekoBot.Modules.Administration
var voiceChannels = await guild.GetVoiceChannelsAsync().ConfigureAwait(false);
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));
foreach (var c in invalidTxtChannels)
@ -246,7 +122,7 @@ namespace NadekoBot.Modules.Administration
}
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));
foreach (var r in invalidRoles)

View File

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

View File

@ -1,16 +1,10 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NadekoBot.Services.Games;
namespace NadekoBot.Modules.Games
{
@ -19,72 +13,13 @@ namespace NadekoBot.Modules.Games
[Group]
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; }
static CleverBotCommands()
public CleverBotCommands(DbHandler db, GamesService games)
{
_log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
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;
_db = db;
_games = games;
}
[NadekoCommand, Usage, Description, Aliases]
@ -94,10 +29,9 @@ namespace NadekoBot.Modules.Games
{
var channel = (ITextChannel)Context.Channel;
Lazy<ChatterBotSession> throwaway;
if (CleverbotGuilds.TryRemove(channel.Guild.Id, out throwaway))
if (_games.CleverbotGuilds.TryRemove(channel.Guild.Id, out Lazy<ChatterBotSession> throwaway))
{
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
uow.GuildConfigs.SetCleverbotEnabled(Context.Guild.Id, false);
await uow.CompleteAsync().ConfigureAwait(false);
@ -106,9 +40,9 @@ namespace NadekoBot.Modules.Games
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);
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
{
private readonly Logger _log;
private readonly DiscordShardedClient _client;
public IMessageChannel GameChannel { get; }
public HashSet<char> Guesses { get; } = new HashSet<char>();
@ -81,9 +82,11 @@ namespace NadekoBot.Modules.Games.Hangman
public event Action<HangmanGame> OnEnded;
public HangmanGame(IMessageChannel channel, string type)
public HangmanGame(DiscordShardedClient client, IMessageChannel channel, string type)
{
_log = LogManager.GetCurrentClassLogger();
_client = client;
this.GameChannel = channel;
this.TermType = type.ToTitleCase();
}
@ -95,12 +98,12 @@ namespace NadekoBot.Modules.Games.Hangman
if (this.Term == null)
throw new KeyNotFoundException("Can't find a term with that type. Use hangmanlist command.");
// start listening for answers when game starts
NadekoBot.Client.MessageReceived += PotentialGuess;
_client.MessageReceived += PotentialGuess;
}
public async Task End()
{
NadekoBot.Client.MessageReceived -= PotentialGuess;
_client.MessageReceived -= PotentialGuess;
OnEnded(this);
var toSend = "Game ended. You **" + (Errors >= MaxErrors ? "LOSE" : "WIN") + "**!\n" + GetHangman();
var embed = new EmbedBuilder().WithTitle("Hangman Game")
@ -199,7 +202,7 @@ namespace NadekoBot.Modules.Games.Hangman
public void Dispose()
{
NadekoBot.Client.MessageReceived -= PotentialGuess;
_client.MessageReceived -= PotentialGuess;
OnEnded = null;
}
}

View File

@ -6,6 +6,7 @@ using System.Collections.Concurrent;
using System.Threading.Tasks;
using NadekoBot.Modules.Games.Hangman;
using Discord;
using Discord.WebSocket;
namespace NadekoBot.Modules.Games
{
@ -14,6 +15,13 @@ namespace NadekoBot.Modules.Games
[Group]
public class HangmanCommands : NadekoSubmodule
{
private readonly DiscordShardedClient _client;
public HangmanCommands(DiscordShardedClient client)
{
_client = client;
}
//channelId, game
public static ConcurrentDictionary<ulong, HangmanGame> HangmanGames { get; } = new ConcurrentDictionary<ulong, HangmanGame>();
[NadekoCommand, Usage, Description, Aliases]
@ -25,7 +33,7 @@ namespace NadekoBot.Modules.Games
[NadekoCommand, Usage, Description, Aliases]
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))
{
@ -35,8 +43,7 @@ namespace NadekoBot.Modules.Games
hm.OnEnded += g =>
{
HangmanGame throwaway;
HangmanGames.TryRemove(g.GameChannel.Id, out throwaway);
HangmanGames.TryRemove(g.GameChannel.Id, out HangmanGame throwaway);
};
try
{
@ -45,8 +52,7 @@ namespace NadekoBot.Modules.Games
catch (Exception ex)
{
try { await Context.Channel.SendErrorAsync(GetText("hangman_start_errored") + " " + ex.Message).ConfigureAwait(false); } catch { }
HangmanGame throwaway;
HangmanGames.TryRemove(Context.Channel.Id, out throwaway);
HangmanGames.TryRemove(Context.Channel.Id, out HangmanGame throwaway);
throwaway.Dispose();
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.Services;
using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Games;
using NLog;
using System;
using System.Collections.Concurrent;
@ -28,89 +29,20 @@ namespace NadekoBot.Modules.Games
[Group]
public class PlantPickCommands : NadekoSubmodule
{
private static ConcurrentHashSet<ulong> generationChannels { get; }
//channelid/message
private static ConcurrentDictionary<ulong, List<IUserMessage>> plantedFlowers { get; } = new ConcurrentDictionary<ulong, List<IUserMessage>>();
//channelId/last generation
private static ConcurrentDictionary<ulong, DateTime> lastGenerations { get; } = new ConcurrentDictionary<ulong, DateTime>();
private readonly CurrencyHandler _ch;
private readonly BotConfig _bc;
private readonly GamesService _games;
private readonly DbHandler _db;
static PlantPickCommands()
public PlantPickCommands(BotConfig bc, CurrencyHandler ch, GamesService games,
DbHandler db)
{
NadekoBot.Client.MessageReceived += PotentialFlowerGeneration;
generationChannels = new ConcurrentHashSet<ulong>(NadekoBot.AllGuildConfigs
.SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj => obj.ChannelId)));
_bc = bc;
_ch = ch;
_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]
[RequireContext(ContextType.Guild)]
public async Task Pick()
@ -120,16 +52,15 @@ namespace NadekoBot.Modules.Games
if (!(await channel.Guild.GetCurrentUserAsync()).GetPermissions(channel).ManageMessages)
return;
List<IUserMessage> msgs;
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;
await Task.WhenAll(msgs.Where(m => m != null).Select(toDelete => toDelete.DeleteAsync())).ConfigureAwait(false);
await CurrencyHandler.AddCurrencyAsync((IGuildUser)Context.User, $"Picked {NadekoBot.BotConfig.CurrencyPluralName}", msgs.Count, false).ConfigureAwait(false);
var msg = await ReplyConfirmLocalized("picked", msgs.Count + NadekoBot.BotConfig.CurrencySign)
await _ch.AddCurrencyAsync((IGuildUser)Context.User, $"Picked {_bc.CurrencyPluralName}", msgs.Count, false).ConfigureAwait(false);
var msg = await ReplyConfirmLocalized("picked", msgs.Count + _bc.CurrencySign)
.ConfigureAwait(false);
msg.DeleteAfter(10);
}
@ -141,21 +72,21 @@ namespace NadekoBot.Modules.Games
if (amount < 1)
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)
{
await ReplyErrorLocalized("not_enough", NadekoBot.BotConfig.CurrencySign).ConfigureAwait(false);
await ReplyErrorLocalized("not_enough", _bc.CurrencySign).ConfigureAwait(false);
return;
}
var imgData = GetRandomCurrencyImage();
var imgData = _games.GetRandomCurrencyImage();
//todo upload all currency images to transfer.sh and use that one as cdn
//and then
var msgToSend = GetText("planted",
Format.Bold(Context.User.ToString()),
amount + NadekoBot.BotConfig.CurrencySign,
amount + _bc.CurrencySign,
Prefix);
if (amount > 1)
@ -172,7 +103,7 @@ namespace NadekoBot.Modules.Games
var msgs = new IUserMessage[amount];
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);
return old;
@ -190,7 +121,7 @@ namespace NadekoBot.Modules.Games
var channel = (ITextChannel)Context.Channel;
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));
@ -198,13 +129,13 @@ namespace NadekoBot.Modules.Games
if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))
{
guildConfig.GenerateCurrencyChannelIds.Add(toAdd);
generationChannels.Add(channel.Id);
_games.GenerationChannels.Add(channel.Id);
enabled = true;
}
else
{
guildConfig.GenerateCurrencyChannelIds.Remove(toAdd);
generationChannels.TryRemove(channel.Id);
_games.GenerationChannels.TryRemove(channel.Id);
enabled = false;
}
await uow.CompleteAsync();
@ -218,14 +149,6 @@ namespace NadekoBot.Modules.Games
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.Threading.Tasks;
using ImageSharp.Processing;
using NadekoBot.Services;
namespace NadekoBot.Modules.Games
{
@ -19,6 +20,12 @@ namespace NadekoBot.Modules.Games
public class PollCommands : NadekoSubmodule
{
public static ConcurrentDictionary<ulong, Poll> ActivePolls = new ConcurrentDictionary<ulong, Poll>();
private readonly DiscordShardedClient _client;
public PollCommands(DiscordShardedClient client)
{
_client = client;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)]
@ -54,7 +61,7 @@ namespace NadekoBot.Modules.Games
if (data.Length < 3)
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))
{
await poll.StartPoll().ConfigureAwait(false);
@ -83,10 +90,16 @@ namespace NadekoBot.Modules.Games
private string[] answers { get; }
private readonly ConcurrentDictionary<ulong, int> _participants = new ConcurrentDictionary<ulong, int>();
private readonly string _question;
private readonly DiscordShardedClient _client;
private readonly NadekoStrings _strings;
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;
_guild = ((ITextChannel)umsg.Channel).Guild;
_question = question;
@ -134,7 +147,7 @@ namespace NadekoBot.Modules.Games
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 num = 1;
msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n");
@ -147,7 +160,7 @@ namespace NadekoBot.Modules.Games
public async Task StopPoll()
{
NadekoBot.Client.MessageReceived -= Vote;
_client.MessageReceived -= Vote;
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)
=> NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(_guild.Id),
=> _strings.GetText(key,
_guild.Id,
typeof(Games).Name.ToLowerInvariant(),
replacements);
}

View File

@ -3,14 +3,11 @@ using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Commands.Models;
using NadekoBot.Services;
using NadekoBot.Modules.Games.Models;
using NadekoBot.Services.Games;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@ -19,145 +16,18 @@ namespace NadekoBot.Modules.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]
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>();
private readonly GamesService _games;
private readonly DiscordShardedClient _client;
public SpeedTypingCommands(DiscordShardedClient client, GamesService games)
{
_games = games;
_client = client;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
@ -165,7 +35,7 @@ namespace NadekoBot.Modules.Games
{
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)
{
@ -202,13 +72,14 @@ namespace NadekoBot.Modules.Games
{
var channel = (ITextChannel)Context.Channel;
TypingArticles.Add(new TypingArticle
_games.TypingArticles.Add(new TypingArticle
{
Title = $"Text added on {DateTime.UtcNow} by {Context.User}",
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);
}
@ -222,7 +93,7 @@ namespace NadekoBot.Modules.Games
if (page < 1)
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())
{
@ -242,13 +113,13 @@ namespace NadekoBot.Modules.Games
var channel = (ITextChannel)Context.Channel;
index -= 1;
if (index < 0 || index >= TypingArticles.Count)
if (index < 0 || index >= _games.TypingArticles.Count)
return;
var removed = TypingArticles[index];
TypingArticles.RemoveAt(index);
var removed = _games.TypingArticles[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)}")
.ConfigureAwait(false);

View File

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

View File

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

View File

@ -1,8 +1,11 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Trivia;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using System.Collections.Concurrent;
using System.Threading.Tasks;
@ -14,8 +17,19 @@ namespace NadekoBot.Modules.Games
[Group]
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 TriviaCommands(DiscordShardedClient client, BotConfig bc, CurrencyHandler ch)
{
_ch = ch;
_client = client;
_bc = bc;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public Task Trivia([Remainder] string additionalArgs = "")
@ -35,7 +49,7 @@ namespace NadekoBot.Modules.Games
var showHints = !additionalArgs.Contains("nohint");
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))
{
try

View File

@ -14,19 +14,20 @@ using System.Net.Http;
using ImageSharp;
using NadekoBot.DataStructures;
using NLog;
using NadekoBot.Services.Games;
namespace NadekoBot.Modules.Games
{
[NadekoModule("Games", ">")]
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();
}, null, TimeSpan.FromDays(1), TimeSpan.FromDays(1));
_games = games;
_images = images;
}
[NadekoCommand, Usage, Description, Aliases]
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)
.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]
@ -94,7 +95,7 @@ namespace NadekoBot.Modules.Games
else if ((pick == 0 && nadekoPick == 1) ||
(pick == 1 && nadekoPick == 2) ||
(pick == 2 && nadekoPick == 0))
msg = GetText("rps_win", NadekoBot.Client.CurrentUser.Mention,
msg = GetText("rps_win", Context.Client.CurrentUser.Mention,
getRpsPick(nadekoPick), getRpsPick(pick));
else
msg = GetText("rps_win", Context.User.Mention, getRpsPick(pick),
@ -103,73 +104,12 @@ namespace NadekoBot.Modules.Games
await Context.Channel.SendConfirmAsync(msg).ConfigureAwait(false);
}
private static readonly ConcurrentDictionary<ulong, GirlRating> _girlRatings = new ConcurrentDictionary<ulong, GirlRating>();
public class GirlRating
{
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
public double Crazy { get; }
public double Hot { get; }
public int Roll { get; }
public string Advice { get; }
public AsyncLazy<string> Url { get; }
public GirlRating(double crazy, double hot, int roll, string advice)
{
Crazy = crazy;
Hot = hot;
Roll = roll;
Advice = advice; // convenient to have it here, even though atm there are only few different ones.
Url = new AsyncLazy<string>(async () =>
{
try
{
using (var ms = new MemoryStream(NadekoBot.Images.WifeMatrix.ToArray(), false))
using (var img = new ImageSharp.Image(ms))
{
const int minx = 35;
const int miny = 385;
const int length = 345;
var pointx = (int)(minx + length * (Hot / 10));
var pointy = (int)(miny - length * ((Crazy - 4) / 6));
using (var pointMs = new MemoryStream(NadekoBot.Images.RategirlDot.ToArray(), false))
using (var pointImg = new ImageSharp.Image(pointMs))
{
img.DrawImage(pointImg, 100, default(Size), new Point(pointx - 10, pointy - 10));
}
string url;
using (var http = new HttpClient())
using (var imgStream = new MemoryStream())
{
img.Save(imgStream);
var byteContent = new ByteArrayContent(imgStream.ToArray());
http.AddFakeHeaders();
var reponse = await http.PutAsync("https://transfer.sh/img.png", byteContent);
url = await reponse.Content.ReadAsStringAsync();
}
return url;
}
}
catch (Exception ex)
{
_log.Warn(ex);
return null;
}
});
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task RateGirl(IGuildUser usr)
{
var gr = _girlRatings.GetOrAdd(usr.Id, GetGirl);
var gr = _games.GirlRatings.GetOrAdd(usr.Id, GetGirl);
var img = await gr.Url;
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle("Girl Rating For " + usr)
@ -255,7 +195,7 @@ namespace NadekoBot.Modules.Games
"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]

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.Modules.Searches.Models;
using NadekoBot.Services;
using NadekoBot.Services.Searches;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
@ -19,27 +20,11 @@ namespace NadekoBot.Modules.Searches
[Group]
public class JokeCommands : NadekoSubmodule
{
private static List<WoWJoke> wowJokes { get; } = new List<WoWJoke>();
private static List<MagicItem> magicItems { get; } = new List<MagicItem>();
private new static readonly Logger _log;
private readonly SearchesService _searches;
static JokeCommands()
public JokeCommands(SearchesService searches)
{
_log = LogManager.GetCurrentClassLogger();
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.");
_searches = searches;
}
[NadekoCommand, Usage, Description, Aliases]
@ -75,24 +60,24 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases]
public async Task WowJoke()
{
if (!wowJokes.Any())
if (!_searches.WowJokes.Any())
{
await ReplyErrorLocalized("jokes_not_loaded").ConfigureAwait(false);
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);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task MagicItem()
{
if (!wowJokes.Any())
if (!_searches.WowJokes.Any())
{
await ReplyErrorLocalized("magicitems_not_loaded").ConfigureAwait(false);
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);
}

View File

@ -22,7 +22,7 @@ namespace NadekoBot.Modules.Searches
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.",
"DONT READ BELOW! Ban Urgot mid OP 100%. Im smurf Diamond 1.",
"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())
{
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}")
.ConfigureAwait(false))["data"] as JArray;
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);
// 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;
// if (role != null)
// {
@ -168,7 +168,7 @@ namespace NadekoBot.Modules.Searches
// roles[i] = ">" + roles[i] + "<";
// }
// 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))
// .FirstOrDefault(jt => jt["role"].ToString() == role)?["general"];
// 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.API;
using NadekoBot.Extensions;
using NadekoBot.Services;
using Newtonsoft.Json;
using System;
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";
public static async Task<OmdbMovie> FindMovie(string name)
public static async Task<OmdbMovie> FindMovie(string name, IGoogleApiService google)
{
using (var http = new HttpClient())
{
@ -20,7 +21,7 @@ namespace NadekoBot.Modules.Searches.Commands.OMDB
var movie = JsonConvert.DeserializeObject<OmdbMovie>(res);
if (movie?.Title == null)
return null;
movie.Poster = await NadekoBot.Google.ShortenUrl(movie.Poster);
movie.Poster = await google.ShortenUrl(movie.Poster);
return movie;
}
}

View File

@ -2,6 +2,7 @@
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using Newtonsoft.Json.Linq;
using System;
using System.Globalization;
@ -17,6 +18,15 @@ namespace NadekoBot.Modules.Searches
[Group]
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]
public async Task Osu(string usr, [Remainder] string mode = null)
{
@ -51,7 +61,7 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases]
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);
return;
@ -65,7 +75,7 @@ namespace NadekoBot.Modules.Searches
using (var http = new HttpClient())
{
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 sb = new System.Text.StringBuilder();
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)
{
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);
return;
@ -107,12 +117,12 @@ namespace NadekoBot.Modules.Searches
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 sb = new System.Text.StringBuilder($"`Top 5 plays for {user}:`\n```xl" + Environment.NewLine);
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 pp = Math.Round(double.Parse($"{item["pp"]}", CultureInfo.InvariantCulture), 2);
var acc = CalculateAcc(item, m);

View File

@ -29,7 +29,7 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases]
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)
.ConfigureAwait(false);
}

View File

@ -3,6 +3,7 @@ using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Searches.Models;
using NadekoBot.Services.Searches;
using Newtonsoft.Json;
using NLog;
using System.Collections.Generic;
@ -17,28 +18,14 @@ namespace NadekoBot.Modules.Searches
[Group]
public class PokemonSearchCommands : NadekoSubmodule
{
private static Dictionary<string, SearchPokemon> pokemons { get; } = new Dictionary<string, SearchPokemon>();
private static Dictionary<string, SearchPokemonAbility> pokemonAbilities { get; } = new Dictionary<string, SearchPokemonAbility>();
private readonly SearchesService _searches;
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";
private new static readonly Logger _log;
static PokemonSearchCommands()
public PokemonSearchCommands(SearchesService searches)
{
_log = LogManager.GetCurrentClassLogger();
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.");
_searches = searches;
}
[NadekoCommand, Usage, Description, Aliases]
@ -48,7 +35,7 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(pokemon))
return;
foreach (var kvp in pokemons)
foreach (var kvp in Pokemons)
{
if (kvp.Key.ToUpperInvariant() == pokemon.ToUpperInvariant())
{
@ -71,7 +58,7 @@ namespace NadekoBot.Modules.Searches
ability = ability?.Trim().ToUpperInvariant().Replace(" ", "");
if (string.IsNullOrWhiteSpace(ability))
return;
foreach (var kvp in pokemonAbilities)
foreach (var kvp in PokemonAbilities)
{
if (kvp.Key.ToUpperInvariant() == ability)
{

View File

@ -1,195 +1,30 @@
using Discord.Commands;
using Discord;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Services;
using System.Threading;
using System.Collections.Generic;
using NadekoBot.Services.Database.Models;
using System.Net.Http;
using NadekoBot.Attributes;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using NadekoBot.Extensions;
using NadekoBot.Services.Searches;
namespace NadekoBot.Modules.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]
public class StreamNotificationCommands : NadekoSubmodule
{
private static readonly Timer _checkTimer;
private static readonly ConcurrentDictionary<string, StreamStatus> _cachedStatuses = new ConcurrentDictionary<string, StreamStatus>();
private readonly DbHandler _db;
private readonly StreamNotificationService _service;
private static bool firstPass { get; set; } = true;
static StreamNotificationCommands()
public StreamNotificationCommands(DbHandler db, StreamNotificationService service)
{
_checkTimer = new Timer(async (state) =>
{
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;
_db = db;
_service = service;
}
[NadekoCommand, Usage, Description, Aliases]
@ -218,7 +53,7 @@ namespace NadekoBot.Modules.Searches
public async Task ListStreams()
{
IEnumerable<FollowedStream> streams;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
streams = uow.GuildConfigs
.For(Context.Guild.Id,
@ -260,7 +95,7 @@ namespace NadekoBot.Modules.Searches
};
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));
removed = config.FollowedStreams.Remove(fs);
@ -287,7 +122,7 @@ namespace NadekoBot.Modules.Searches
return;
try
{
var streamStatus = (await GetStreamStatus(new FollowedStream
var streamStatus = (await _service.GetStreamStatus(new FollowedStream
{
Username = stream,
Type = platform,
@ -325,7 +160,7 @@ namespace NadekoBot.Modules.Searches
StreamStatus status;
try
{
status = await GetStreamStatus(fs).ConfigureAwait(false);
status = await _service.GetStreamStatus(fs).ConfigureAwait(false);
}
catch
{
@ -333,53 +168,15 @@ namespace NadekoBot.Modules.Searches
return;
}
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.FollowedStreams))
.FollowedStreams
.Add(fs);
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.Linq;
using Discord.WebSocket;
using NadekoBot.Services.Searches;
using NadekoBot.Services;
namespace NadekoBot.Modules.Searches
{
public partial class Searches
{
public struct UserChannelPair
{
public ulong UserId { get; set; }
public ulong ChannelId { get; set; }
}
[Group]
public class TranslateCommands : NadekoSubmodule
{
private static ConcurrentDictionary<ulong, bool> translatedChannels { get; } = new ConcurrentDictionary<ulong, bool>();
private static ConcurrentDictionary<UserChannelPair, string> userLanguages { get; } = new ConcurrentDictionary<UserChannelPair, string>();
private readonly SearchesService _searches;
private readonly IGoogleApiService _google;
static TranslateCommands()
public TranslateCommands(SearchesService searches, IGoogleApiService google)
{
NadekoBot.Client.MessageReceived += async (msg) =>
{
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 { }
};
_searches = searches;
_google = google;
}
[NadekoCommand, Usage, Description, Aliases]
@ -63,7 +32,7 @@ namespace NadekoBot.Modules.Searches
try
{
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);
}
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
{
Del,
@ -101,18 +57,17 @@ namespace NadekoBot.Modules.Searches
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);
return;
}
bool throwaway;
if (translatedChannels.TryRemove(channel.Id, out throwaway))
if (_searches.TranslatedChannels.TryRemove(channel.Id, out var throwaway))
{
await ReplyConfirmLocalized("atl_stopped").ConfigureAwait(false);
return;
}
if (translatedChannels.TryAdd(channel.Id, autoDelete == AutoDeleteAutoTranslate.Del))
if (_searches.TranslatedChannels.TryAdd(channel.Id, autoDelete == AutoDeleteAutoTranslate.Del))
{
await ReplyConfirmLocalized("atl_started").ConfigureAwait(false);
}
@ -130,7 +85,7 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(langs))
{
if (userLanguages.TryRemove(ucp, out langs))
if (_searches.UserLanguages.TryRemove(ucp, out langs))
await ReplyConfirmLocalized("atl_removed").ConfigureAwait(false);
return;
}
@ -141,13 +96,13 @@ namespace NadekoBot.Modules.Searches
var from = langarr[0];
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);
return;
}
userLanguages.AddOrUpdate(ucp, langs, (key, val) => langs);
_searches.UserLanguages.AddOrUpdate(ucp, langs, (key, val) => langs);
await ReplyConfirmLocalized("atl_set", from, to).ConfigureAwait(false);
}
@ -156,7 +111,7 @@ namespace NadekoBot.Modules.Searches
[RequireContext(ContextType.Guild)]
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 System;
using System.Linq;
using System.Text;
using System.Net.Http;
using NadekoBot.Services;
using System.Threading.Tasks;
@ -17,18 +16,27 @@ using NadekoBot.Modules.Searches.Commands.Models;
using AngleSharp;
using AngleSharp.Dom.Html;
using AngleSharp.Dom;
using System.Xml;
using Configuration = AngleSharp.Configuration;
using NadekoBot.Attributes;
using Discord.Commands;
using ImageSharp.Processing.Processors;
using ImageSharp;
using NadekoBot.Services.Searches;
namespace NadekoBot.Modules.Searches
{
[NadekoModule("Searches", "~")]
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]
public async Task Weather([Remainder] string query)
{
@ -59,16 +67,16 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases]
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;
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 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 time = DateTime.UtcNow.AddSeconds(timeObj.DstOffset + timeObj.RawOffset);
@ -81,7 +89,7 @@ namespace NadekoBot.Modules.Searches
public async Task Youtube([Remainder] string query = null)
{
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))
{
await ReplyErrorLocalized("no_results").ConfigureAwait(false);
@ -97,7 +105,7 @@ namespace NadekoBot.Modules.Searches
if (!(await ValidateQuery(Context.Channel, query).ConfigureAwait(false))) return;
await Context.Channel.TriggerTypingAsync().ConfigureAwait(false);
var movie = await OmdbProvider.FindMovie(query);
var movie = await OmdbProvider.FindMovie(query, _google);
if (movie == null)
{
await ReplyErrorLocalized("imdb_fail").ConfigureAwait(false);
@ -137,7 +145,7 @@ namespace NadekoBot.Modules.Searches
try
{
var res = await NadekoBot.Google.GetImageAsync(terms).ConfigureAwait(false);
var res = await _google.GetImageAsync(terms).ConfigureAwait(false);
var embed = new EmbedBuilder()
.WithOkColor()
.WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50))
@ -189,7 +197,7 @@ namespace NadekoBot.Modules.Searches
terms = WebUtility.UrlEncode(terms).Replace(' ', '+');
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()
.WithOkColor()
.WithAuthor(eab => eab.WithName(GetText("image_search_for") + " " + terms.TrimTo(50))
@ -239,7 +247,7 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(ffs))
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);
}
@ -249,7 +257,7 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(arg))
return;
var shortened = await NadekoBot.Google.ShortenUrl(arg).ConfigureAwait(false);
var shortened = await _google.ShortenUrl(arg).ConfigureAwait(false);
if (shortened == arg)
{
@ -315,7 +323,7 @@ namespace NadekoBot.Modules.Searches
.WithFooter(efb => efb.WithText(totalResults));
var desc = await Task.WhenAll(results.Select(async res =>
$"[{Format.Bold(res?.Title)}]({(await NadekoBot.Google.ShortenUrl(res?.Link))})\n{res?.Text}\n\n"))
$"[{Format.Bold(res?.Title)}]({(await _google.ShortenUrl(res?.Link))})\n{res?.Text}\n\n"))
.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)
throw new KeyNotFoundException("Cannot find a card by that name");
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 desc = item["text"].ToString();
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("cost")).WithValue(cost).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("types")).WithValue(types).WithIsInline(true));
//.AddField(efb => efb.WithName("Store Url").WithValue(await NadekoBot.Google.ShortenUrl(items[0]["store_url"].ToString())).WithIsInline(true));
//.AddField(efb => efb.WithName("Store Url").WithValue(await _google.ShortenUrl(items[0]["store_url"].ToString())).WithIsInline(true));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
}
@ -369,7 +377,7 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(arg))
return;
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey))
if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
{
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
return;
@ -379,7 +387,7 @@ namespace NadekoBot.Modules.Searches
using (var http = new HttpClient())
{
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)}")
.ConfigureAwait(false);
try
@ -422,7 +430,7 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases]
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);
return;
@ -435,7 +443,7 @@ namespace NadekoBot.Modules.Searches
using (var http = new HttpClient())
{
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");
var res = await http.GetStringAsync($"https://yoda.p.mashape.com/yoda?sentence={Uri.EscapeUriString(query)}").ConfigureAwait(false);
try
@ -457,7 +465,7 @@ namespace NadekoBot.Modules.Searches
[NadekoCommand, Usage, Description, Aliases]
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);
return;
@ -531,7 +539,7 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(query))
return;
if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey))
if (string.IsNullOrWhiteSpace(_creds.MashapeKey))
{
await ReplyErrorLocalized("mashape_api_missing").ConfigureAwait(false);
return;
@ -542,7 +550,7 @@ namespace NadekoBot.Modules.Searches
using (var http = new HttpClient())
{
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);
}
@ -655,7 +663,7 @@ namespace NadekoBot.Modules.Searches
usr = (IGuildUser)Context.User;
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()
.AddField(efb => efb.WithName("Username").WithValue(usr.ToString()).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 response = $@"`{GetText("title")}` {found["title"]}
`{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);
}
catch
@ -769,7 +777,7 @@ namespace NadekoBot.Modules.Searches
tag = tag?.Trim() ?? "";
var url = await InternalDapiSearch(tag, type).ConfigureAwait(false);
var url = await _searches.DapiSearch(tag, type).ConfigureAwait(false);
if (url == null)
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.Music;
using NadekoBot.Services.CustomReactions;
using NadekoBot.Services.Games;
using NadekoBot.Services.Administration;
namespace NadekoBot
{
@ -73,11 +75,10 @@ namespace NadekoBot
});
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 strings = new NadekoStrings(localization);
var greetSettingsService = new GreetSettingsService(Client, AllGuildConfigs, db);
var commandService = new CommandService(new CommandServiceConfig()
{
@ -97,10 +98,18 @@ namespace NadekoBot
//module services
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 musicService = new MusicService(google, strings, localization, db, soundcloud, credentials);
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
Services = new NServiceProvider.ServiceProviderBuilder() //todo all Adds should be interfaces
@ -124,6 +133,10 @@ namespace NadekoBot
.Add<MusicService>(musicService)
.Add<GreetSettingsService>(greetSettingsService)
.Add<CustomReactionsService>(crService)
.Add<GamesService>(gamesService)
.Add(selfService)
.Add(vcRoleService)
.Add(vPlusTService)
.Build();
commandHandler.AddServices(Services);

View File

@ -29,21 +29,22 @@
<ItemGroup>
<Compile Remove="data\**\*;credentials.json;credentials_example.json" />
<Compile Remove="Modules\Administration\**" />
<Compile Remove="Modules\Games\**" />
<Compile Remove="Modules\NSFW\**" />
<Compile Remove="Modules\Permissions\**" />
<Compile Remove="Modules\Searches\**" />
<EmbeddedResource Remove="Modules\Administration\**" />
<EmbeddedResource Remove="Modules\Games\**" />
<EmbeddedResource Remove="Modules\NSFW\**" />
<EmbeddedResource Remove="Modules\Permissions\**" />
<EmbeddedResource Remove="Modules\Searches\**" />
<None Remove="Modules\Administration\**" />
<None Remove="Modules\Games\**" />
<None Remove="Modules\NSFW\**" />
<None Remove="Modules\Permissions\**" />
<None Remove="Modules\Searches\**" />
<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">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</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;
//////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)
////// {
////// 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();
optionsBuilder.UseSqlite(creds.Db.ConnectionString);
options = optionsBuilder.Options;
//switch (NadekoBot.Credentials.Db.Type.ToUpperInvariant())
//switch (_creds.Db.Type.ToUpperInvariant())
//{
// case "SQLITE":
// 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
{

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.Database.Models;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -15,11 +19,158 @@ namespace NadekoBot.Services
private readonly DbHandler _db;
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;
_client = client;
_log = LogManager.GetCurrentClassLogger();
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)
@ -210,6 +361,23 @@ namespace NadekoBot.Services
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

View File

@ -17,7 +17,8 @@ namespace NadekoBot.Services
string CarbonKey { get; }
DBConfig Db { get; }
string SoundCloudClientId { get; set; }
string SoundCloudClientId { get; }
string OsuApiKey { get; }
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.Threading.Tasks;
@ -6,12 +7,28 @@ namespace NadekoBot.Services
{
public interface IGoogleApiService
{
IEnumerable<string> Languages { get; }
Task<IEnumerable<string>> GetVideosByKeywordsAsync(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>> GetPlaylistTracksAsync(string playlistId, int count = 50);
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);
}
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"
: _soundcloudClientId;
}
set {
private set {
_soundcloudClientId = value;
}
}

View File

@ -9,7 +9,9 @@ using Google.Apis.Urlshortener.v1;
using Google.Apis.Urlshortener.v1.Data;
using NLog;
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
{
@ -39,6 +41,7 @@ namespace NadekoBot.Services.Impl
sh = new UrlshortenerService(bcs);
cs = new CustomsearchService(bcs);
}
public async Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1)
{
if (string.IsNullOrWhiteSpace(keywords))
@ -190,18 +193,6 @@ namespace NadekoBot.Services.Impl
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)
{
if (string.IsNullOrWhiteSpace(query))
@ -218,5 +209,168 @@ namespace NadekoBot.Services.Impl
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 Newtonsoft.Json;
using System;
using Discord;
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.
/// </summary>
private readonly CultureInfo _usCultureInfo = new CultureInfo("en-US");
private readonly ILocalization _localization;
public NadekoStrings()
public NadekoStrings(ILocalization loc)
{
_log = LogManager.GetCurrentClassLogger();
_localization = loc;
var sw = Stopwatch.StartNew();
var allLangsDict = new Dictionary<string, ImmutableDictionary<string, string>>(); // lang:(name:value)
foreach (var file in Directory.GetFiles(stringsPath))
@ -58,6 +62,9 @@ namespace NadekoBot.Services
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)
{
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 Description { get; set; }

View File

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace NadekoBot.Modules.Searches.Models
namespace NadekoBot.Services.Searches
{
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.Threading.Tasks;
using System.Xml;
@ -7,6 +15,101 @@ namespace NadekoBot.Services.Searches
{
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)
{
tag = tag?.Replace(" ", "_");
@ -65,4 +168,18 @@ namespace NadekoBot.Services.Searches
Rule34,
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
{