commit
c75a625aa7
@ -1 +1 @@
|
||||
Subproject commit 7c0cce6d35b04d883cf5ec2d775b051e4bc8739f
|
||||
Subproject commit 4506fc5a54fe31d826649dc413467c52a3cd7896
|
@ -45,7 +45,7 @@ namespace NadekoBot.Modules.Administration
|
||||
var channel = msg.Channel as SocketTextChannel;
|
||||
if (channel == null)
|
||||
return;
|
||||
if (DeleteMessagesOnCommand.Contains(channel.Guild.Id))
|
||||
if (DeleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune")
|
||||
await msg.DeleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -25,7 +25,6 @@ namespace NadekoBot.Modules.Administration
|
||||
static AutoAssignRoleCommands()
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
AutoAssignedRoles = new ConcurrentDictionary<ulong, ulong>(NadekoBot.AllGuildConfigs.Where(x => x.AutoAssignRoleId != 0)
|
||||
.ToDictionary(k => k.GuildId, v => v.AutoAssignRoleId));
|
||||
@ -46,9 +45,6 @@ namespace NadekoBot.Modules.Administration
|
||||
}
|
||||
catch (Exception ex) { _log.Warn(ex); }
|
||||
};
|
||||
|
||||
sw.Stop();
|
||||
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
|
@ -10,6 +10,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Administration
|
||||
@ -22,8 +23,8 @@ namespace NadekoBot.Modules.Administration
|
||||
private static Logger _log { get; }
|
||||
public static List<PlayingStatus> RotatingStatusMessages { get; }
|
||||
public static bool RotatingStatuses { get; private set; } = false;
|
||||
|
||||
//todo wtf is with this while(true) in constructor
|
||||
private static Timer _t { get; }
|
||||
|
||||
static PlayingRotateCommands()
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
@ -31,48 +32,43 @@ namespace NadekoBot.Modules.Administration
|
||||
RotatingStatusMessages = NadekoBot.BotConfig.RotatingStatusMessages;
|
||||
RotatingStatuses = NadekoBot.BotConfig.RotatingStatuses;
|
||||
|
||||
var t = Task.Run(async () =>
|
||||
|
||||
|
||||
_t = new Timer(async (_) =>
|
||||
{
|
||||
var index = 0;
|
||||
do
|
||||
try
|
||||
{
|
||||
try
|
||||
if (!RotatingStatuses)
|
||||
return;
|
||||
else
|
||||
{
|
||||
if (!RotatingStatuses)
|
||||
continue;
|
||||
else
|
||||
{
|
||||
if (index >= RotatingStatusMessages.Count)
|
||||
index = 0;
|
||||
if (index >= RotatingStatusMessages.Count)
|
||||
index = 0;
|
||||
|
||||
if (!RotatingStatusMessages.Any())
|
||||
continue;
|
||||
var status = RotatingStatusMessages[index++].Status;
|
||||
if (string.IsNullOrWhiteSpace(status))
|
||||
continue;
|
||||
PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value()));
|
||||
var shards = NadekoBot.Client.Shards;
|
||||
for (int i = 0; i < shards.Count; i++)
|
||||
if (!RotatingStatusMessages.Any())
|
||||
return;
|
||||
var status = RotatingStatusMessages[index++].Status;
|
||||
if (string.IsNullOrWhiteSpace(status))
|
||||
return;
|
||||
PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value()));
|
||||
var shards = NadekoBot.Client.Shards;
|
||||
for (int i = 0; i < shards.Count; i++)
|
||||
{
|
||||
ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(shards.ElementAt(i))));
|
||||
try { await shards.ElementAt(i).SetGameAsync(status).ConfigureAwait(false); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(shards.ElementAt(i))));
|
||||
try { await shards.ElementAt(i).SetGameAsync(status).ConfigureAwait(false); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
}
|
||||
_log.Warn(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn("Rotating playing status errored.\n" + ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(1));
|
||||
}
|
||||
} while (true);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn("Rotating playing status errored.\n" + ex);
|
||||
}
|
||||
}, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
|
||||
}
|
||||
|
||||
public static Dictionary<string, Func<string>> PlayingPlaceholders { get; } =
|
||||
@ -92,7 +88,7 @@ namespace NadekoBot.Modules.Administration
|
||||
}
|
||||
},
|
||||
{ "%queued%", () => Music.Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()},
|
||||
{ "%time%", () => DateTime.Now.ToString("hh:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) },
|
||||
{ "%time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) },
|
||||
{ "%shardcount%", () => NadekoBot.Client.Shards.Count.ToString() },
|
||||
};
|
||||
|
||||
|
@ -132,7 +132,7 @@ namespace NadekoBot.Modules.Administration
|
||||
if (ids[1].ToUpperInvariant().StartsWith("C:"))
|
||||
{
|
||||
var cid = ulong.Parse(ids[1].Substring(2));
|
||||
var ch = (await server.GetTextChannelsAsync()).Where(c => c.Id == cid).FirstOrDefault();
|
||||
var ch = server.TextChannels.Where(c => c.Id == cid).FirstOrDefault();
|
||||
if (ch == null)
|
||||
{
|
||||
return;
|
||||
@ -159,9 +159,7 @@ namespace NadekoBot.Modules.Administration
|
||||
[OwnerOnly]
|
||||
public async Task Announce([Remainder] string message)
|
||||
{
|
||||
var channels = await Task.WhenAll(NadekoBot.Client.GetGuilds().Select(g =>
|
||||
g.GetDefaultChannelAsync()
|
||||
)).ConfigureAwait(false);
|
||||
var channels = NadekoBot.Client.GetGuilds().Select(g => g.DefaultChannel).ToArray();
|
||||
if (channels == null)
|
||||
return;
|
||||
await Task.WhenAll(channels.Where(c => c != null).Select(c => c.SendConfirmAsync($"🆕 Message from {Context.User} `[Bot Owner]`:", message)))
|
||||
|
@ -4,9 +4,11 @@ using Discord.WebSocket;
|
||||
using NadekoBot.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -17,24 +19,76 @@ namespace NadekoBot.Modules.Administration
|
||||
[Group]
|
||||
public class ServerGreetCommands : ModuleBase
|
||||
{
|
||||
//make this to a field in the guildconfig table
|
||||
class GreetSettings
|
||||
{
|
||||
public int AutoDeleteGreetMessagesTimer { get; set; }
|
||||
public int AutoDeleteByeMessagesTimer { get; set; }
|
||||
|
||||
public ulong GreetMessageChannelId { get; set; }
|
||||
public ulong ByeMessageChannelId { get; set; }
|
||||
|
||||
public bool SendDmGreetMessage { get; set; }
|
||||
public string DmGreetMessageText { get; set; }
|
||||
|
||||
public bool SendChannelGreetMessage { get; set; }
|
||||
public string ChannelGreetMessageText { get; set; }
|
||||
|
||||
public bool SendChannelByeMessage { get; set; }
|
||||
public string ChannelByeMessageText { get; set; }
|
||||
|
||||
public static GreetSettings Create(GuildConfig g) => new GreetSettings()
|
||||
{
|
||||
AutoDeleteByeMessagesTimer = g.AutoDeleteByeMessagesTimer,
|
||||
AutoDeleteGreetMessagesTimer = g.AutoDeleteGreetMessagesTimer,
|
||||
GreetMessageChannelId = g.GreetMessageChannelId,
|
||||
ByeMessageChannelId = g.ByeMessageChannelId,
|
||||
SendDmGreetMessage = g.SendDmGreetMessage,
|
||||
DmGreetMessageText = g.DmGreetMessageText,
|
||||
SendChannelGreetMessage = g.SendChannelGreetMessage,
|
||||
ChannelGreetMessageText = g.ChannelGreetMessageText,
|
||||
SendChannelByeMessage = g.SendChannelByeMessage,
|
||||
ChannelByeMessageText = g.ChannelByeMessageText,
|
||||
};
|
||||
}
|
||||
|
||||
private static Logger _log { get; }
|
||||
|
||||
private static ConcurrentDictionary<ulong, GreetSettings> GuildConfigsCache { get; } = new ConcurrentDictionary<ulong, GreetSettings>();
|
||||
|
||||
static ServerGreetCommands()
|
||||
{
|
||||
NadekoBot.Client.UserJoined += UserJoined;
|
||||
NadekoBot.Client.UserLeft += UserLeft;
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
GuildConfigsCache = new ConcurrentDictionary<ulong, GreetSettings>(NadekoBot.AllGuildConfigs.ToDictionary(g => g.GuildId, (g) => GreetSettings.Create(g)));
|
||||
}
|
||||
|
||||
private static GreetSettings GetOrAddSettingsForGuild(ulong guildId)
|
||||
{
|
||||
GreetSettings settings;
|
||||
GuildConfigsCache.TryGetValue(guildId, out settings);
|
||||
|
||||
if (settings != null)
|
||||
return settings;
|
||||
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
{
|
||||
var gc = uow.GuildConfigs.For(guildId, set => set);
|
||||
settings = GreetSettings.Create(gc);
|
||||
}
|
||||
|
||||
GuildConfigsCache.TryAdd(guildId, settings);
|
||||
return settings;
|
||||
}
|
||||
|
||||
//todo optimize ASAP
|
||||
private static async Task UserLeft(IGuildUser user)
|
||||
{
|
||||
try
|
||||
{
|
||||
GuildConfig conf;
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
{
|
||||
conf = uow.GuildConfigs.For(user.Guild.Id, set => set);
|
||||
}
|
||||
var conf = GetOrAddSettingsForGuild(user.GuildId);
|
||||
|
||||
if (!conf.SendChannelByeMessage) return;
|
||||
var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.ByeMessageChannelId);
|
||||
@ -62,11 +116,7 @@ namespace NadekoBot.Modules.Administration
|
||||
{
|
||||
try
|
||||
{
|
||||
GuildConfig conf;
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
{
|
||||
conf = uow.GuildConfigs.For(user.Guild.Id, set => set);
|
||||
}
|
||||
var conf = GetOrAddSettingsForGuild(user.GuildId);
|
||||
|
||||
if (conf.SendChannelGreetMessage)
|
||||
{
|
||||
@ -133,6 +183,9 @@ namespace NadekoBot.Modules.Administration
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -159,6 +212,9 @@ namespace NadekoBot.Modules.Administration
|
||||
enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage;
|
||||
conf.GreetMessageChannelId = channelId;
|
||||
|
||||
var toAdd = GreetSettings.Create(conf);
|
||||
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
return enabled;
|
||||
@ -201,6 +257,9 @@ namespace NadekoBot.Modules.Administration
|
||||
conf.ChannelGreetMessageText = message;
|
||||
greetMsgEnabled = conf.SendChannelGreetMessage;
|
||||
|
||||
var toAdd = GreetSettings.Create(conf);
|
||||
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
|
||||
|
||||
uow.Complete();
|
||||
}
|
||||
return greetMsgEnabled;
|
||||
@ -227,6 +286,9 @@ namespace NadekoBot.Modules.Administration
|
||||
var conf = uow.GuildConfigs.For(guildId, set => set);
|
||||
enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage;
|
||||
|
||||
var toAdd = GreetSettings.Create(conf);
|
||||
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
return enabled;
|
||||
@ -269,6 +331,9 @@ namespace NadekoBot.Modules.Administration
|
||||
conf.DmGreetMessageText = message;
|
||||
greetMsgEnabled = conf.SendDmGreetMessage;
|
||||
|
||||
var toAdd = GreetSettings.Create(conf);
|
||||
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
|
||||
|
||||
uow.Complete();
|
||||
}
|
||||
return greetMsgEnabled;
|
||||
@ -296,6 +361,9 @@ namespace NadekoBot.Modules.Administration
|
||||
enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage;
|
||||
conf.ByeMessageChannelId = channelId;
|
||||
|
||||
var toAdd = GreetSettings.Create(conf);
|
||||
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
|
||||
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
return enabled;
|
||||
@ -338,6 +406,9 @@ namespace NadekoBot.Modules.Administration
|
||||
conf.ChannelByeMessageText = message;
|
||||
byeMsgEnabled = conf.SendChannelByeMessage;
|
||||
|
||||
var toAdd = GreetSettings.Create(conf);
|
||||
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
|
||||
|
||||
uow.Complete();
|
||||
}
|
||||
return byeMsgEnabled;
|
||||
@ -356,16 +427,19 @@ namespace NadekoBot.Modules.Administration
|
||||
await Context.Channel.SendConfirmAsync("ℹ️ Automatic deletion of bye messages has been **disabled**.").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task SetByeDel(ulong id, int timer)
|
||||
private static async Task SetByeDel(ulong guildId, int timer)
|
||||
{
|
||||
if (timer < 0 || timer > 600)
|
||||
return;
|
||||
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
{
|
||||
var conf = uow.GuildConfigs.For(id, set => set);
|
||||
var conf = uow.GuildConfigs.For(guildId, set => set);
|
||||
conf.AutoDeleteByeMessagesTimer = timer;
|
||||
|
||||
var toAdd = GreetSettings.Create(conf);
|
||||
GuildConfigsCache.AddOrUpdate(guildId, toAdd, (key, old) => toAdd);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ namespace NadekoBot.Modules.Administration
|
||||
{
|
||||
try
|
||||
{
|
||||
await (await guild.GetOwnerAsync()).SendErrorAsync(
|
||||
await guild.Owner.SendErrorAsync(
|
||||
"⚠️ I don't have **manage server** and/or **manage channels** permission," +
|
||||
$" so I cannot run `voice+text` on **{guild.Name}** server.").ConfigureAwait(false);
|
||||
}
|
||||
@ -75,16 +75,16 @@ namespace NadekoBot.Modules.Administration
|
||||
var beforeVch = before.VoiceChannel;
|
||||
if (beforeVch != null)
|
||||
{
|
||||
var textChannel = (await guild.GetTextChannelsAsync()).Where(t => t.Name == GetChannelName(beforeVch.Name).ToLowerInvariant()).FirstOrDefault();
|
||||
var textChannel = guild.TextChannels.Where(t => t.Name == GetChannelName(beforeVch.Name).ToLowerInvariant()).FirstOrDefault();
|
||||
if (textChannel != null)
|
||||
await textChannel.AddPermissionOverwriteAsync(user,
|
||||
new OverwritePermissions(readMessages: PermValue.Deny,
|
||||
sendMessages: PermValue.Deny)).ConfigureAwait(false);
|
||||
}
|
||||
var afterVch = after.VoiceChannel;
|
||||
if (afterVch != null && guild.AFKChannelId != afterVch.Id)
|
||||
if (afterVch != null && guild.AFKChannel?.Id != afterVch.Id)
|
||||
{
|
||||
var textChannel = (await guild.GetTextChannelsAsync())
|
||||
ITextChannel textChannel = guild.TextChannels
|
||||
.Where(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant())
|
||||
.FirstOrDefault();
|
||||
if (textChannel == null)
|
||||
|
@ -36,10 +36,8 @@ namespace NadekoBot.Modules.ClashOfClans
|
||||
.GetAllWars()
|
||||
.Select(cw =>
|
||||
{
|
||||
cw.Channel = NadekoBot.Client.GetGuild(cw.GuildId)
|
||||
?.GetTextChannelAsync(cw.ChannelId)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
cw.Channel = NadekoBot.Client.GetGuild(cw.GuildId)?
|
||||
.GetTextChannel(cw.ChannelId);
|
||||
return cw;
|
||||
})
|
||||
.Where(cw => cw.Channel != null)
|
||||
@ -322,7 +320,7 @@ namespace NadekoBot.Modules.ClashOfClans
|
||||
|
||||
public static async Task<ClashWar> CreateWar(string enemyClan, int size, ulong serverId, ulong channelId)
|
||||
{
|
||||
var channel = await NadekoBot.Client.GetGuild(serverId)?.GetTextChannelAsync(channelId);
|
||||
var channel = NadekoBot.Client.GetGuild(serverId)?.GetTextChannel(channelId);
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
{
|
||||
var cw = new ClashWar
|
||||
|
@ -10,14 +10,16 @@ using NadekoBot.Extensions;
|
||||
using NLog;
|
||||
using System.Diagnostics;
|
||||
using Discord.WebSocket;
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.CustomReactions
|
||||
{
|
||||
[NadekoModule("CustomReactions", ".")]
|
||||
public class CustomReactions : DiscordModule
|
||||
{
|
||||
public static ConcurrentHashSet<CustomReaction> GlobalReactions { get; } = new ConcurrentHashSet<CustomReaction>();
|
||||
public static ConcurrentDictionary<ulong, ConcurrentHashSet<CustomReaction>> GuildReactions { get; } = new ConcurrentDictionary<ulong, ConcurrentHashSet<CustomReaction>>();
|
||||
private static CustomReaction[] _globalReactions = new CustomReaction[] { };
|
||||
public static CustomReaction[] GlobalReactions => _globalReactions;
|
||||
public static ConcurrentDictionary<ulong, CustomReaction[]> GuildReactions { get; } = new ConcurrentDictionary<ulong, CustomReaction[]>();
|
||||
|
||||
public static ConcurrentDictionary<string, uint> ReactionStats { get; } = new ConcurrentDictionary<string, uint>();
|
||||
|
||||
@ -30,8 +32,8 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
{
|
||||
var items = uow.CustomReactions.GetAll();
|
||||
GuildReactions = new ConcurrentDictionary<ulong, ConcurrentHashSet<CustomReaction>>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => new ConcurrentHashSet<CustomReaction>(g)));
|
||||
GlobalReactions = new ConcurrentHashSet<CustomReaction>(items.Where(g => g.GuildId == null || g.GuildId == 0));
|
||||
GuildReactions = new ConcurrentDictionary<ulong, CustomReaction[]>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray()));
|
||||
_globalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray();
|
||||
}
|
||||
sw.Stop();
|
||||
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
|
||||
@ -46,32 +48,46 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
return false;
|
||||
|
||||
var content = umsg.Content.Trim().ToLowerInvariant();
|
||||
ConcurrentHashSet<CustomReaction> reactions;
|
||||
CustomReaction[] reactions;
|
||||
|
||||
GuildReactions.TryGetValue(channel.Guild.Id, out reactions);
|
||||
if (reactions != null && reactions.Any())
|
||||
{
|
||||
var reaction = reactions.Where(cr =>
|
||||
var rs = reactions.Where(cr =>
|
||||
{
|
||||
if (cr == null)
|
||||
return false;
|
||||
|
||||
var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%");
|
||||
var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant();
|
||||
return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger);
|
||||
}).Shuffle().FirstOrDefault();
|
||||
if (reaction != null)
|
||||
{
|
||||
if (reaction.Response != "-")
|
||||
try { await channel.SendMessageAsync(reaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { }
|
||||
}).ToArray();
|
||||
|
||||
ReactionStats.AddOrUpdate(reaction.Trigger, 1, (k, old) => ++old);
|
||||
return true;
|
||||
if (rs.Length != 0)
|
||||
{
|
||||
var reaction = rs[new NadekoRandom().Next(0, rs.Length)];
|
||||
if (reaction != null)
|
||||
{
|
||||
if (reaction.Response != "-")
|
||||
try { await channel.SendMessageAsync(reaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { }
|
||||
|
||||
ReactionStats.AddOrUpdate(reaction.Trigger, 1, (k, old) => ++old);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
var greaction = GlobalReactions.Where(cr =>
|
||||
|
||||
var grs = GlobalReactions.Where(cr =>
|
||||
{
|
||||
if (cr == null)
|
||||
return false;
|
||||
var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%");
|
||||
var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant();
|
||||
return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger);
|
||||
}).Shuffle().FirstOrDefault();
|
||||
}).ToArray();
|
||||
if (grs.Length == 0)
|
||||
return false;
|
||||
var greaction = grs[new NadekoRandom().Next(0, grs.Length)];
|
||||
|
||||
if (greaction != null)
|
||||
{
|
||||
@ -114,12 +130,19 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
|
||||
if (channel == null)
|
||||
{
|
||||
GlobalReactions.Add(cr);
|
||||
Array.Resize(ref _globalReactions, _globalReactions.Length + 1);
|
||||
_globalReactions[_globalReactions.Length - 1] = cr;
|
||||
}
|
||||
else
|
||||
{
|
||||
var reactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet<CustomReaction>());
|
||||
reactions.Add(cr);
|
||||
var reactions = GuildReactions.AddOrUpdate(Context.Guild.Id,
|
||||
Array.Empty<CustomReaction>(),
|
||||
(k, old) =>
|
||||
{
|
||||
Array.Resize(ref old, old.Length + 1);
|
||||
old[old.Length - 1] = cr;
|
||||
return old;
|
||||
});
|
||||
}
|
||||
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
@ -136,17 +159,17 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
{
|
||||
if (page < 1 || page > 1000)
|
||||
return;
|
||||
ConcurrentHashSet<CustomReaction> customReactions;
|
||||
CustomReaction[] customReactions;
|
||||
if (Context.Guild == null)
|
||||
customReactions = GlobalReactions;
|
||||
customReactions = GlobalReactions.Where(cr => cr != null).ToArray();
|
||||
else
|
||||
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet<CustomReaction>());
|
||||
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, Array.Empty<CustomReaction>()).Where(cr => cr != null).ToArray();
|
||||
|
||||
if (customReactions == null || !customReactions.Any())
|
||||
await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
var lastPage = customReactions.Count / 20;
|
||||
var lastPage = customReactions.Length / 20;
|
||||
await Context.Channel.SendPaginatedConfirmAsync(page, curPage =>
|
||||
new EmbedBuilder().WithOkColor()
|
||||
.WithTitle("Custom reactions")
|
||||
@ -167,11 +190,11 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
[Priority(1)]
|
||||
public async Task ListCustReact(All x)
|
||||
{
|
||||
ConcurrentHashSet<CustomReaction> customReactions;
|
||||
CustomReaction[] customReactions;
|
||||
if (Context.Guild == null)
|
||||
customReactions = GlobalReactions;
|
||||
customReactions = GlobalReactions.Where(cr => cr != null).ToArray();
|
||||
else
|
||||
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet<CustomReaction>());
|
||||
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray();
|
||||
|
||||
if (customReactions == null || !customReactions.Any())
|
||||
await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false);
|
||||
@ -195,11 +218,11 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
{
|
||||
if (page < 1 || page > 10000)
|
||||
return;
|
||||
ConcurrentHashSet<CustomReaction> customReactions;
|
||||
CustomReaction[] customReactions;
|
||||
if (Context.Guild == null)
|
||||
customReactions = GlobalReactions;
|
||||
customReactions = GlobalReactions.Where(cr => cr != null).ToArray();
|
||||
else
|
||||
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet<CustomReaction>());
|
||||
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ }).Where(cr => cr != null).ToArray();
|
||||
|
||||
if (customReactions == null || !customReactions.Any())
|
||||
await Context.Channel.SendErrorAsync("No custom reactions found").ConfigureAwait(false);
|
||||
@ -225,13 +248,13 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ShowCustReact(int id)
|
||||
{
|
||||
ConcurrentHashSet<CustomReaction> customReactions;
|
||||
CustomReaction[] customReactions;
|
||||
if (Context.Guild == null)
|
||||
customReactions = GlobalReactions;
|
||||
else
|
||||
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet<CustomReaction>());
|
||||
customReactions = GuildReactions.GetOrAdd(Context.Guild.Id, new CustomReaction[]{ });
|
||||
|
||||
var found = customReactions.FirstOrDefault(cr => cr.Id == id);
|
||||
var found = customReactions.FirstOrDefault(cr => cr?.Id == id);
|
||||
|
||||
if (found == null)
|
||||
await Context.Channel.SendErrorAsync("No custom reaction found with that id.").ConfigureAwait(false);
|
||||
@ -265,13 +288,17 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
if ((toDelete.GuildId == null || toDelete.GuildId == 0) && Context.Guild == null)
|
||||
{
|
||||
uow.CustomReactions.Remove(toDelete);
|
||||
GlobalReactions.RemoveWhere(cr => cr.Id == toDelete.Id);
|
||||
//todo i can dramatically improve performance of this, if Ids are ordered.
|
||||
_globalReactions = GlobalReactions.Where(cr => cr?.Id != toDelete.Id).ToArray();
|
||||
success = true;
|
||||
}
|
||||
else if ((toDelete.GuildId != null && toDelete.GuildId != 0) && Context.Guild.Id == toDelete.GuildId)
|
||||
{
|
||||
uow.CustomReactions.Remove(toDelete);
|
||||
GuildReactions.GetOrAdd(Context.Guild.Id, new ConcurrentHashSet<CustomReaction>()).RemoveWhere(cr => cr.Id == toDelete.Id);
|
||||
GuildReactions.AddOrUpdate(Context.Guild.Id, new CustomReaction[] { }, (key, old) =>
|
||||
{
|
||||
return old.Where(cr => cr?.Id != toDelete.Id).ToArray();
|
||||
});
|
||||
success = true;
|
||||
}
|
||||
if (success)
|
||||
@ -312,8 +339,10 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
{
|
||||
if (page < 1)
|
||||
return;
|
||||
var ordered = ReactionStats.OrderByDescending(x => x.Value).ToList();
|
||||
var lastPage = ordered.Count / 9;
|
||||
var ordered = ReactionStats.OrderByDescending(x => x.Value).ToArray();
|
||||
if (!ordered.Any())
|
||||
return;
|
||||
var lastPage = ordered.Length / 9;
|
||||
await Context.Channel.SendPaginatedConfirmAsync(page,
|
||||
(curPage) => ordered.Skip((curPage - 1) * 9)
|
||||
.Take(9)
|
||||
|
@ -1,9 +1,11 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@ -25,9 +27,13 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
if(ch == null)
|
||||
return "";
|
||||
|
||||
var usrs = (ch.Guild.GetUsersAsync().GetAwaiter().GetResult());
|
||||
var g = ch.Guild as SocketGuild;
|
||||
if(g == null)
|
||||
return "";
|
||||
|
||||
return usrs.Skip(new NadekoRandom().Next(0,usrs.Count-1)).Shuffle().FirstOrDefault()?.Mention ?? "";
|
||||
var users = g.Users.ToArray();
|
||||
|
||||
return users[new NadekoRandom().Next(0, users.Length-1)].Mention;
|
||||
} }
|
||||
//{"%rng%", (ctx) => { return new NadekoRandom().Next(0,10).ToString(); } }
|
||||
};
|
||||
|
@ -59,14 +59,14 @@ namespace NadekoBot.Modules.Gambling
|
||||
catch
|
||||
{
|
||||
try { await msg.DeleteAsync().ConfigureAwait(false); }
|
||||
catch { }
|
||||
catch { return; }
|
||||
}
|
||||
}
|
||||
using (msg.OnReaction(async (r) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (r.Emoji.Name == "🌸" && r.User.IsSpecified && _flowerReactionAwardedUsers.Add(r.User.Value.Id))
|
||||
if (r.Emoji.Name == "🌸" && r.User.IsSpecified && ((DateTime.UtcNow - r.User.Value.CreatedAt).TotalDays > 5) && _flowerReactionAwardedUsers.Add(r.User.Value.Id))
|
||||
{
|
||||
try { await CurrencyHandler.AddCurrencyAsync(r.User.Value, "Flower Reaction Event", 100, false).ConfigureAwait(false); } catch { }
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ namespace NadekoBot.Modules.Gambling
|
||||
var members = role.Members().Where(u => u.Status != UserStatus.Offline && u.Status != UserStatus.Unknown);
|
||||
var membersArray = members as IUser[] ?? members.ToArray();
|
||||
var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)];
|
||||
await Context.Channel.SendConfirmAsync("🎟 Raffled user", $"**{usr.Username}#{usr.Discriminator}** ID: `{usr.Id}`").ConfigureAwait(false);
|
||||
await Context.Channel.SendConfirmAsync("🎟 Raffled user", $"**{usr.Username}#{usr.Discriminator}**", footer: $"ID: {usr.Id}").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
|
@ -69,6 +69,7 @@ namespace NadekoBot.Modules.Games
|
||||
private readonly ConcurrentDictionary<string, IGuildUser> submissions = new ConcurrentDictionary<string, IGuildUser>();
|
||||
public IReadOnlyDictionary<string, IGuildUser> Submissions => submissions;
|
||||
|
||||
private readonly ConcurrentHashSet<ulong> usersWhoSubmitted = new ConcurrentHashSet<ulong>();
|
||||
private readonly ConcurrentHashSet<ulong> usersWhoVoted = new ConcurrentHashSet<ulong>();
|
||||
|
||||
private int spamCount = 0;
|
||||
@ -190,10 +191,6 @@ namespace NadekoBot.Modules.Games
|
||||
try { await channel.EmbedAsync(GetEmbed()).ConfigureAwait(false); }
|
||||
catch { }
|
||||
}
|
||||
//user didn't input something already
|
||||
IGuildUser throwaway;
|
||||
if (submissions.TryGetValue(input, out throwaway))
|
||||
return;
|
||||
var inputWords = input.Split(' '); //get all words
|
||||
|
||||
if (inputWords.Length != startingLetters.Length) // number of words must be the same as the number of the starting letters
|
||||
@ -207,9 +204,15 @@ namespace NadekoBot.Modules.Games
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!usersWhoSubmitted.Add(guildUser.Id))
|
||||
return;
|
||||
//try adding it to the list of answers
|
||||
if (!submissions.TryAdd(input, guildUser))
|
||||
{
|
||||
usersWhoSubmitted.TryRemove(guildUser.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
// all good. valid input. answer recorded
|
||||
await channel.SendConfirmAsync("Acrophobia", $"{guildUser.Mention} submitted their sentence. ({submissions.Count} total)");
|
||||
|
@ -189,13 +189,13 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
|
||||
catch (Exception ex) { _log.Warn(ex); }
|
||||
}
|
||||
|
||||
public string GetHangman() => $@"\_\_\_\_\_\_\_\_\_
|
||||
| |
|
||||
| |
|
||||
{(Errors > 0 ? "😲" : " ")} |
|
||||
{(Errors > 1 ? "/" : " ")} {(Errors > 2 ? "|" : " ")} {(Errors > 3 ? "\\" : " ")} |
|
||||
{(Errors > 4 ? "/" : " ")} {(Errors > 5 ? "\\" : " ")} |
|
||||
/-\";
|
||||
public string GetHangman() => $@". ┌─────┐
|
||||
.┃...............┋
|
||||
.┃...............┋
|
||||
.┃{(Errors > 0 ? ".............😲" : "")}
|
||||
.┃{(Errors > 1 ? "............./" : "")} {(Errors > 2 ? "|" : "")} {(Errors > 3 ? "\\" : "")}
|
||||
.┃{(Errors > 4 ? "............../" : "")} {(Errors > 5 ? "\\" : "")}
|
||||
/-\";
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -61,7 +61,7 @@ namespace NadekoBot.Modules.Games
|
||||
return;
|
||||
}
|
||||
|
||||
await Context.Channel.SendConfirmAsync("Hangman game started", hm.ScrambledWord + "\n" + hm.GetHangman() + "\n" + hm.ScrambledWord);
|
||||
await Context.Channel.SendConfirmAsync("Hangman game started", hm.ScrambledWord + "\n" + hm.GetHangman());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,9 +76,9 @@ namespace NadekoBot.Modules.Games.Trivia
|
||||
|
||||
questionMessage = await channel.EmbedAsync(questionEmbed).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound ||
|
||||
ex.StatusCode == System.Net.HttpStatusCode.Forbidden ||
|
||||
ex.StatusCode == System.Net.HttpStatusCode.BadRequest)
|
||||
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound ||
|
||||
ex.HttpCode == System.Net.HttpStatusCode.Forbidden ||
|
||||
ex.HttpCode == System.Net.HttpStatusCode.BadRequest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -106,7 +106,7 @@ namespace NadekoBot.Modules.Games.Trivia
|
||||
await questionMessage.ModifyAsync(m => m.Embed = questionEmbed.WithFooter(efb => efb.WithText(CurrentQuestion.GetHint())).Build())
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound || ex.StatusCode == System.Net.HttpStatusCode.Forbidden)
|
||||
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound || ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
@ -711,14 +711,11 @@ namespace NadekoBot.Modules.Music
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
|
||||
//todo only author or owner
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task DeletePlaylist([Remainder] int id)
|
||||
{
|
||||
|
||||
|
||||
bool success = false;
|
||||
MusicPlaylist pl = null;
|
||||
try
|
||||
@ -747,7 +744,7 @@ namespace NadekoBot.Modules.Music
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
_log.Warn(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
using Discord;
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom.Html;
|
||||
using AngleSharp.Extensions;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
@ -8,6 +11,7 @@ using Newtonsoft.Json.Linq;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -52,6 +56,116 @@ namespace NadekoBot.Modules.Searches
|
||||
}, null, TimeSpan.FromSeconds(0), TimeSpan.FromMinutes(29));
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(1)]
|
||||
public async Task Mal([Remainder] string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return;
|
||||
|
||||
var fullQueryLink = "https://myanimelist.net/profile/" + name;
|
||||
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
|
||||
|
||||
var imageElem = document.QuerySelector("body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-left > div.user-profile > div.user-image > img");
|
||||
var imageUrl = ((IHtmlImageElement)imageElem)?.Source ?? "http://icecream.me/uploads/870b03f36b59cc16ebfe314ef2dde781.png";
|
||||
|
||||
var stats = document.QuerySelectorAll("body > div#myanimelist > div.wrapper > div#contentWrapper > div#content > div.content-container > div.container-right > div#statistics > div.user-statistics-stats > div.stats > div.clearfix > ul.stats-status > li > span").Select(x => x.InnerHtml).ToList();
|
||||
|
||||
var favorites = document.QuerySelectorAll("div.user-favorites > div.di-tc");
|
||||
|
||||
var favAnime = "No favorite anime yet";
|
||||
if (favorites[0].QuerySelector("p") == null)
|
||||
favAnime = string.Join("\n", favorites[0].QuerySelectorAll("ul > li > div.di-tc.va-t > a")
|
||||
.Shuffle()
|
||||
.Take(3)
|
||||
.Select(x =>
|
||||
{
|
||||
var elem = (IHtmlAnchorElement)x;
|
||||
return $"[{elem.InnerHtml}]({elem.Href})";
|
||||
}));
|
||||
|
||||
//var favManga = "No favorite manga yet.";
|
||||
//if (favorites[1].QuerySelector("p") == null)
|
||||
// favManga = string.Join("\n", favorites[1].QuerySelectorAll("ul > li > div.di-tc.va-t > a")
|
||||
// .Take(3)
|
||||
// .Select(x =>
|
||||
// {
|
||||
// var elem = (IHtmlAnchorElement)x;
|
||||
// return $"[{elem.InnerHtml}]({elem.Href})";
|
||||
// }));
|
||||
|
||||
var info = document.QuerySelectorAll("ul.user-status:nth-child(3) > li")
|
||||
.Select(x => Tuple.Create(x.Children[0].InnerHtml, x.Children[1].InnerHtml))
|
||||
.ToList();
|
||||
|
||||
var daysAndMean = document.QuerySelectorAll("div.anime:nth-child(1) > div:nth-child(2) > div")
|
||||
.Select(x => x.TextContent.Split(':').Select(y => y.Trim()).ToArray())
|
||||
.ToArray();
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithTitle($"{name}'s MAL profile")
|
||||
.AddField(efb => efb.WithName("💚 Watching").WithValue(stats[0]).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName("💙 Completed").WithValue(stats[1]).WithIsInline(true));
|
||||
if (info.Count < 3)
|
||||
embed.AddField(efb => efb.WithName("💛 On-Hold").WithValue(stats[2]).WithIsInline(true));
|
||||
embed
|
||||
.AddField(efb => efb.WithName("💔 Dropped").WithValue(stats[3]).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName("⚪ Plan to watch").WithValue(stats[4]).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName("🕐 " + daysAndMean[0][0]).WithValue(daysAndMean[0][1]).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName("📊 " + daysAndMean[1][0]).WithValue(daysAndMean[1][1]).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(MalInfoToEmoji(info[0].Item1) + " " + info[0].Item1).WithValue(info[0].Item2.TrimTo(20)).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(MalInfoToEmoji(info[1].Item1) + " " + info[1].Item1).WithValue(info[1].Item2.TrimTo(20)).WithIsInline(true));
|
||||
if (info.Count > 2)
|
||||
embed.AddField(efb => efb.WithName(MalInfoToEmoji(info[2].Item1) + " " + info[2].Item1).WithValue(info[2].Item2.TrimTo(20)).WithIsInline(true));
|
||||
//if(info.Count > 3)
|
||||
// embed.AddField(efb => efb.WithName(MalInfoToEmoji(info[3].Item1) + " " + info[3].Item1).WithValue(info[3].Item2).WithIsInline(true))
|
||||
embed
|
||||
.WithDescription($@"
|
||||
** https://myanimelist.net/animelist/{ name } **
|
||||
|
||||
**Top 3 Favorite Anime:**
|
||||
{favAnime}"
|
||||
|
||||
//**[Manga List](https://myanimelist.net/mangalist/{name})**
|
||||
//💚`Reading:` {stats[5]}
|
||||
//💙`Completed:` {stats[6]}
|
||||
//💔`Dropped:` {stats[8]}
|
||||
//⚪`Plan to read:` {stats[9]}
|
||||
|
||||
//**Top 3 Favorite Manga:**
|
||||
//{favManga}"
|
||||
|
||||
)
|
||||
.WithUrl(fullQueryLink)
|
||||
.WithImageUrl(imageUrl);
|
||||
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string MalInfoToEmoji(string info) {
|
||||
info = info.Trim().ToLowerInvariant();
|
||||
switch (info)
|
||||
{
|
||||
case "gender":
|
||||
return "🚁";
|
||||
case "location":
|
||||
return "🗺";
|
||||
case "last online":
|
||||
return "👥";
|
||||
case "birthday":
|
||||
return "📆";
|
||||
default:
|
||||
return "❔";
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[Priority(0)]
|
||||
public Task Mal(IUser usr) => Mal(usr.Username);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Anime([Remainder] string query)
|
||||
{
|
||||
|
@ -45,7 +45,7 @@ namespace NadekoBot.Modules.Searches
|
||||
MemoryStream ms = new MemoryStream();
|
||||
res.CopyTo(ms);
|
||||
ms.Position = 0;
|
||||
await Context.Channel.SendFileAsync(ms, $"{usr}.png", $"🎧 **Profile Link: **https://osu.ppy.sh/u/{Uri.EscapeDataString(usr)}\n`Image provided by https://lemmmy.pw/osusig`").ConfigureAwait(false);
|
||||
await Context.Channel.SendFileAsync(ms, $"{usr}.png", $"🎧 **Profile Link:** <https://new.ppy.sh/u/{Uri.EscapeDataString(usr)}>\n`Image provided by https://lemmmy.pw/osusig`").ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -60,9 +60,9 @@ namespace NadekoBot.Modules.Searches
|
||||
.WithThumbnailUrl(rankimg)
|
||||
.AddField(fb => fb.WithName("**Level**").WithValue($"{model.level}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Current Competitive Wins**").WithValue($"{model.Games.Competitive.wins}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Current Competitive Loses**").WithValue($"{model.Games.Competitive.lost}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Current Competitive Played**").WithValue($"{model.Games.Competitive.played}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Competitive Wins**").WithValue($"{model.Games.Competitive.wins}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Competitive Loses**").WithValue($"{model.Games.Competitive.lost}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Competitive Played**").WithValue($"{model.Games.Competitive.played}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Competitive Rank**").WithValue(rank).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true))
|
||||
|
@ -105,7 +105,7 @@ namespace NadekoBot.Modules.Searches
|
||||
var server = NadekoBot.Client.GetGuild(fs.GuildId);
|
||||
if (server == null)
|
||||
return;
|
||||
var channel = await server.GetTextChannelAsync(fs.ChannelId);
|
||||
var channel = server.GetTextChannel(fs.ChannelId);
|
||||
if (channel == null)
|
||||
return;
|
||||
try
|
||||
|
@ -117,31 +117,49 @@ namespace NadekoBot.Modules.Searches
|
||||
|
||||
terms = WebUtility.UrlEncode(terms).Replace(' ', '+');
|
||||
|
||||
var fullQueryLink = $"http://imgur.com/search?q={ terms }";
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
|
||||
try
|
||||
{
|
||||
var res = await NadekoBot.Google.GetImageAsync(terms).ConfigureAwait(false);
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50))
|
||||
.WithUrl("https://www.google.rs/search?q=" + terms + "&source=lnms&tbm=isch")
|
||||
.WithIconUrl("http://i.imgur.com/G46fm8J.png"))
|
||||
.WithDescription(res.Link)
|
||||
.WithImageUrl(res.Link)
|
||||
.WithTitle(Context.User.Mention);
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Warn("Falling back to Imgur search.");
|
||||
|
||||
var elems = document.QuerySelectorAll("a.image-list-link");
|
||||
var fullQueryLink = $"http://imgur.com/search?q={ terms }";
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
|
||||
|
||||
if (!elems.Any())
|
||||
return;
|
||||
var elems = document.QuerySelectorAll("a.image-list-link");
|
||||
|
||||
var img = (elems.FirstOrDefault()?.Children?.FirstOrDefault() as IHtmlImageElement);
|
||||
if (!elems.Any())
|
||||
return;
|
||||
|
||||
if (img?.Source == null)
|
||||
return;
|
||||
var img = (elems.FirstOrDefault()?.Children?.FirstOrDefault() as IHtmlImageElement);
|
||||
|
||||
var source = img.Source.Replace("b.", ".");
|
||||
if (img?.Source == null)
|
||||
return;
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50))
|
||||
.WithUrl(fullQueryLink)
|
||||
.WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?"))
|
||||
.WithDescription(source)
|
||||
.WithImageUrl(source)
|
||||
.WithTitle(Context.User.Mention);
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
var source = img.Source.Replace("b.", ".");
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50))
|
||||
.WithUrl(fullQueryLink)
|
||||
.WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?"))
|
||||
.WithDescription(source)
|
||||
.WithImageUrl(source)
|
||||
.WithTitle(Context.User.Mention);
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
@ -150,34 +168,51 @@ namespace NadekoBot.Modules.Searches
|
||||
terms = terms?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(terms))
|
||||
return;
|
||||
|
||||
terms = WebUtility.UrlEncode(terms).Replace(' ', '+');
|
||||
try
|
||||
{
|
||||
var res = await NadekoBot.Google.GetImageAsync(terms, new NadekoRandom().Next(0, 50)).ConfigureAwait(false);
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50))
|
||||
.WithUrl("https://www.google.rs/search?q=" + terms + "&source=lnms&tbm=isch")
|
||||
.WithIconUrl("http://i.imgur.com/G46fm8J.png"))
|
||||
.WithDescription(res.Link)
|
||||
.WithImageUrl(res.Link)
|
||||
.WithTitle(Context.User.Mention);
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Warn("Falling back to Imgur");
|
||||
terms = WebUtility.UrlEncode(terms).Replace(' ', '+');
|
||||
|
||||
var fullQueryLink = $"http://imgur.com/search?q={ terms }";
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
|
||||
var fullQueryLink = $"http://imgur.com/search?q={ terms }";
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
|
||||
|
||||
var elems = document.QuerySelectorAll("a.image-list-link").ToList();
|
||||
var elems = document.QuerySelectorAll("a.image-list-link").ToList();
|
||||
|
||||
if (!elems.Any())
|
||||
return;
|
||||
if (!elems.Any())
|
||||
return;
|
||||
|
||||
var img = (elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Count))?.Children?.FirstOrDefault() as IHtmlImageElement);
|
||||
var img = (elems.ElementAtOrDefault(new NadekoRandom().Next(0, elems.Count))?.Children?.FirstOrDefault() as IHtmlImageElement);
|
||||
|
||||
if (img?.Source == null)
|
||||
return;
|
||||
if (img?.Source == null)
|
||||
return;
|
||||
|
||||
var source = img.Source.Replace("b.", ".");
|
||||
var source = img.Source.Replace("b.", ".");
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50))
|
||||
.WithUrl(fullQueryLink)
|
||||
.WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?"))
|
||||
.WithDescription(source)
|
||||
.WithImageUrl(source)
|
||||
.WithTitle(Context.User.Mention);
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
var embed = new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName("Image Search For: " + terms.TrimTo(50))
|
||||
.WithUrl(fullQueryLink)
|
||||
.WithIconUrl("http://s.imgur.com/images/logo-1200-630.jpg?"))
|
||||
.WithDescription(source)
|
||||
.WithImageUrl(source)
|
||||
.WithTitle(Context.User.Mention);
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
@ -260,7 +295,7 @@ namespace NadekoBot.Modules.Searches
|
||||
.WithTitle(Context.User.Mention)
|
||||
.WithFooter(efb => efb.WithText(totalResults));
|
||||
|
||||
var desc = await Task.WhenAll(results.Select(async res =>
|
||||
var desc = await Task.WhenAll(results.Select(async res =>
|
||||
$"[{Format.Bold(res?.Title)}]({(await NadekoBot.Google.ShortenUrl(res?.Link))})\n{res?.Text}\n\n"))
|
||||
.ConfigureAwait(false);
|
||||
await Context.Channel.EmbedAsync(embed.WithDescription(String.Concat(desc))).ConfigureAwait(false);
|
||||
|
@ -50,7 +50,7 @@ namespace NadekoBot.Modules.Utility
|
||||
}
|
||||
|
||||
private static string GetText(IGuild server, ITextChannel channel, IGuildUser user, IUserMessage message) =>
|
||||
$"**{server.Name} | {channel.Name}** `{user.Username}`: " + message.Content;
|
||||
$"**{server.Name} | {channel.Name}** `{user.Username}`: " + message.Content.SanitizeMentions();
|
||||
|
||||
public static readonly ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>> Subscribers = new ConcurrentDictionary<int, ConcurrentHashSet<ITextChannel>>();
|
||||
private static Logger _log { get; }
|
||||
|
@ -40,7 +40,7 @@ namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
this.Repeater = repeater;
|
||||
this.Channel = channel ?? NadekoBot.Client.GetGuild(repeater.GuildId)?.GetTextChannelAsync(repeater.ChannelId).GetAwaiter().GetResult();
|
||||
this.Channel = channel ?? NadekoBot.Client.GetGuild(repeater.GuildId)?.GetTextChannel(repeater.ChannelId);
|
||||
if (Channel == null)
|
||||
return;
|
||||
Task.Run(Run);
|
||||
@ -69,12 +69,12 @@ namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
oldMsg = await Channel.SendMessageAsync(toSend).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Forbidden)
|
||||
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
|
||||
{
|
||||
_log.Warn("Missing permissions. Repeater stopped. ChannelId : {0}", Channel?.Id);
|
||||
return;
|
||||
}
|
||||
catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
_log.Warn("Channel not found. Repeater stopped. ChannelId : {0}", Channel?.Id);
|
||||
return;
|
||||
|
@ -68,9 +68,7 @@ namespace NadekoBot.Modules.Utility
|
||||
}
|
||||
else
|
||||
{
|
||||
var t = NadekoBot.Client.GetGuild(r.ServerId)?.GetTextChannelAsync(r.ChannelId).ConfigureAwait(false);
|
||||
if (t != null)
|
||||
ch = await t.Value;
|
||||
ch = NadekoBot.Client.GetGuild(r.ServerId)?.GetTextChannel(r.ChannelId);
|
||||
}
|
||||
if (ch == null)
|
||||
return;
|
||||
|
@ -116,15 +116,18 @@ namespace NadekoBot.Modules.Utility
|
||||
var arr = (await (Context.Channel as IGuildChannel).Guild.GetUsersAsync())
|
||||
.Where(u => u.Game?.Name?.ToUpperInvariant() == game)
|
||||
.Select(u => u.Username)
|
||||
.Shuffle()
|
||||
.Take(60)
|
||||
.ToList();
|
||||
|
||||
int i = 0;
|
||||
if (!arr.Any())
|
||||
await Context.Channel.SendErrorAsync("Nobody is playing that game.").ConfigureAwait(false);
|
||||
else
|
||||
else {
|
||||
await Context.Channel.SendConfirmAsync("```css\n" + string.Join("\n", arr.GroupBy(item => (i++) / 2)
|
||||
.Select(ig => string.Concat(ig.Select(el => $"• {el,-27}")))) + "\n```")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
|
@ -75,7 +75,8 @@ namespace NadekoBot
|
||||
|
||||
//initialize Services
|
||||
CommandService = new CommandService(new CommandServiceConfig() {
|
||||
CaseSensitiveCommands = false
|
||||
CaseSensitiveCommands = false,
|
||||
DefaultRunMode = RunMode.Sync
|
||||
});
|
||||
Google = new GoogleApiService();
|
||||
CommandHandler = new CommandHandler(Client, CommandService);
|
||||
|
29
src/NadekoBot/Resources/CommandStrings.Designer.cs
generated
29
src/NadekoBot/Resources/CommandStrings.Designer.cs
generated
@ -177,7 +177,7 @@ namespace NadekoBot.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%.
|
||||
/// Looks up a localized string similar to Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%, %time%,%shardid%,%shardcount%, %shardguilds%.
|
||||
/// </summary>
|
||||
public static string addplaying_desc {
|
||||
get {
|
||||
@ -4406,6 +4406,33 @@ namespace NadekoBot.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to mal.
|
||||
/// </summary>
|
||||
public static string mal_cmd {
|
||||
get {
|
||||
return ResourceManager.GetString("mal_cmd", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Shows basic info from myanimelist profile..
|
||||
/// </summary>
|
||||
public static string mal_desc {
|
||||
get {
|
||||
return ResourceManager.GetString("mal_desc", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to `{0}mal straysocks`.
|
||||
/// </summary>
|
||||
public static string mal_usage {
|
||||
get {
|
||||
return ResourceManager.GetString("mal_usage", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to manga mang mq.
|
||||
/// </summary>
|
||||
|
@ -292,7 +292,7 @@
|
||||
<value>addplaying adpl</value>
|
||||
</data>
|
||||
<data name="addplaying_desc" xml:space="preserve">
|
||||
<value>Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%</value>
|
||||
<value>Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%, %time%,%shardid%,%shardcount%, %shardguilds%</value>
|
||||
</data>
|
||||
<data name="addplaying_usage" xml:space="preserve">
|
||||
<value>`{0}adpl`</value>
|
||||
@ -1376,7 +1376,6 @@
|
||||
</data>
|
||||
<data name="plant_usage" xml:space="preserve">
|
||||
<value>`{0}plant` or `{0}plant 5`</value>
|
||||
<comment> </comment>
|
||||
</data>
|
||||
<data name="gencurrency_cmd" xml:space="preserve">
|
||||
<value>gencurrency gc</value>
|
||||
@ -3025,4 +3024,13 @@
|
||||
<data name="waifuinfo_usage" xml:space="preserve">
|
||||
<value>`{0}waifuinfo @MyCrush` or `{0}waifuinfo`</value>
|
||||
</data>
|
||||
<data name="mal_cmd" xml:space="preserve">
|
||||
<value>mal</value>
|
||||
</data>
|
||||
<data name="mal_desc" xml:space="preserve">
|
||||
<value>Shows basic info from myanimelist profile.</value>
|
||||
</data>
|
||||
<data name="mal_usage" xml:space="preserve">
|
||||
<value>`{0}mal straysocks`</value>
|
||||
</data>
|
||||
</root>
|
@ -159,8 +159,8 @@ namespace NadekoBot.Services
|
||||
|
||||
private async Task<bool> WordFiltered(IGuild guild, SocketUserMessage usrMsg)
|
||||
{
|
||||
var filteredChannelWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id);
|
||||
var filteredServerWords = Permissions.FilterCommands.FilteredWordsForServer(guild.Id);
|
||||
var filteredChannelWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id) ?? new ConcurrentHashSet<string>();
|
||||
var filteredServerWords = Permissions.FilterCommands.FilteredWordsForServer(guild.Id) ?? new ConcurrentHashSet<string>();
|
||||
var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' ');
|
||||
if (filteredChannelWords.Count != 0 || filteredServerWords.Count != 0)
|
||||
{
|
||||
@ -222,6 +222,7 @@ namespace NadekoBot.Services
|
||||
return;
|
||||
|
||||
// maybe this message is a custom reaction
|
||||
// todo log custom reaction executions. return struct with info
|
||||
var crExecuted = await Task.Run(() => CustomReactions.TryExecuteCustomReaction(usrMsg)).ConfigureAwait(false);
|
||||
if (crExecuted) //if it was, don't execute the command
|
||||
return;
|
||||
|
@ -8,13 +8,19 @@ using System.Text.RegularExpressions;
|
||||
using Google.Apis.Urlshortener.v1;
|
||||
using Google.Apis.Urlshortener.v1.Data;
|
||||
using NLog;
|
||||
using Google.Apis.Customsearch.v1;
|
||||
using Google.Apis.Customsearch.v1.Data;
|
||||
|
||||
namespace NadekoBot.Services.Impl
|
||||
{
|
||||
public class GoogleApiService : IGoogleApiService
|
||||
{
|
||||
const string search_engine_id = "018084019232060951019:hs5piey28-e";
|
||||
|
||||
private YouTubeService yt;
|
||||
private UrlshortenerService sh;
|
||||
private CustomsearchService cs;
|
||||
|
||||
private Logger _log { get; }
|
||||
|
||||
public GoogleApiService()
|
||||
@ -22,13 +28,14 @@ namespace NadekoBot.Services.Impl
|
||||
var bcs = new BaseClientService.Initializer
|
||||
{
|
||||
ApplicationName = "Nadeko Bot",
|
||||
ApiKey = NadekoBot.Credentials.GoogleApiKey
|
||||
ApiKey = NadekoBot.Credentials.GoogleApiKey,
|
||||
};
|
||||
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
yt = new YouTubeService(bcs);
|
||||
sh = new UrlshortenerService(bcs);
|
||||
cs = new CustomsearchService(bcs);
|
||||
}
|
||||
public async Task<IEnumerable<string>> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1)
|
||||
{
|
||||
@ -150,7 +157,7 @@ namespace NadekoBot.Services.Impl
|
||||
return toReturn;
|
||||
}
|
||||
//todo AsyncEnumerable
|
||||
public async Task<IReadOnlyDictionary<string,TimeSpan>> GetVideoDurationsAsync(IEnumerable<string> videoIds)
|
||||
public async Task<IReadOnlyDictionary<string, TimeSpan>> GetVideoDurationsAsync(IEnumerable<string> videoIds)
|
||||
{
|
||||
var videoIdsList = videoIds as List<string> ?? videoIds.ToList();
|
||||
|
||||
@ -179,5 +186,34 @@ 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))
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
|
||||
var req = cs.Cse.List(query);
|
||||
req.Cx = search_engine_id;
|
||||
req.Num = 1;
|
||||
req.Fields = "items(image(contextLink,thumbnailLink),link)";
|
||||
req.SearchType = CseResource.ListRequest.SearchTypeEnum.Image;
|
||||
req.Start = start;
|
||||
|
||||
var search = await req.ExecuteAsync().ConfigureAwait(false);
|
||||
|
||||
return new ImageResult(search.Items[0].Image, search.Items[0].Link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace NadekoBot.Services.Impl
|
||||
private DiscordShardedClient client;
|
||||
private DateTime started;
|
||||
|
||||
public const string BotVersion = "1.1.4";
|
||||
public const string BotVersion = "1.1.5";
|
||||
|
||||
public string Author => "Kwoth#2560";
|
||||
public string Library => "Discord.Net";
|
||||
|
@ -237,7 +237,7 @@
|
||||
},
|
||||
{
|
||||
"Title":"Careers in Psychology: Opportunities in a Changing World",
|
||||
"Text":"This text addresses the growing need among students and faculty for information about the careers available in psychology at the bachelorâs and graduate level."
|
||||
"Text":"This text addresses the growing need among students and faculty for information about the careers available in psychology at the bachelors and graduate level."
|
||||
},
|
||||
{
|
||||
"Title":"Philosophy of Psychology",
|
||||
|
Loading…
Reference in New Issue
Block a user