Removed module projects because it can't work like that atm. Commented out package commands.
This commit is contained in:
46
NadekoBot.Core/Modules/Utility/BotConfigCommands.cs
Normal file
46
NadekoBot.Core/Modules/Utility/BotConfigCommands.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Core.Services;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public partial class Utility
|
||||
{
|
||||
public class BotConfigCommands : NadekoSubmodule
|
||||
{
|
||||
private readonly IBotConfigProvider _service;
|
||||
|
||||
public BotConfigCommands(IBotConfigProvider service)
|
||||
{
|
||||
_service = service;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task BotConfigEdit()
|
||||
{
|
||||
var names = Enum.GetNames(typeof(BotConfigEditType));
|
||||
await ReplyAsync(string.Join(", ", names)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task BotConfigEdit(BotConfigEditType type, [Remainder]string newValue = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(newValue))
|
||||
newValue = null;
|
||||
|
||||
var success = _service.Edit(type, newValue);
|
||||
|
||||
if (!success)
|
||||
await ReplyErrorLocalized("bot_config_edit_fail", Format.Bold(type.ToString()), Format.Bold(newValue ?? "NULL")).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalized("bot_config_edit_success", Format.Bold(type.ToString()), Format.Bold(newValue ?? "NULL")).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
67
NadekoBot.Core/Modules/Utility/CalcCommands.cs
Normal file
67
NadekoBot.Core/Modules/Utility/CalcCommands.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public partial class Utility
|
||||
{
|
||||
[Group]
|
||||
public class CalcCommands : NadekoSubmodule
|
||||
{
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Calculate([Remainder] string expression)
|
||||
{
|
||||
var expr = new NCalc.Expression(expression, NCalc.EvaluateOptions.IgnoreCase);
|
||||
expr.EvaluateParameter += Expr_EvaluateParameter;
|
||||
var result = expr.Evaluate();
|
||||
if (expr.Error == null)
|
||||
await Context.Channel.SendConfirmAsync("⚙ " + GetText("result"), result.ToString());
|
||||
else
|
||||
await Context.Channel.SendErrorAsync("⚙ " + GetText("error"), expr.Error);
|
||||
}
|
||||
|
||||
private static void Expr_EvaluateParameter(string name, NCalc.ParameterArgs args)
|
||||
{
|
||||
switch (name.ToLowerInvariant())
|
||||
{
|
||||
case "pi":
|
||||
args.Result = Math.PI;
|
||||
break;
|
||||
case "e":
|
||||
args.Result = Math.E;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task CalcOps()
|
||||
{
|
||||
var selection = typeof(Math).GetTypeInfo()
|
||||
.GetMethods()
|
||||
.Distinct(new MethodInfoEqualityComparer())
|
||||
.Select(x => x.Name)
|
||||
.Except(new[]
|
||||
{
|
||||
"ToString",
|
||||
"Equals",
|
||||
"GetHashCode",
|
||||
"GetType"
|
||||
});
|
||||
await Context.Channel.SendConfirmAsync(GetText("calcops", Prefix), string.Join(", ", selection));
|
||||
}
|
||||
}
|
||||
|
||||
private class MethodInfoEqualityComparer : IEqualityComparer<MethodInfo>
|
||||
{
|
||||
public bool Equals(MethodInfo x, MethodInfo y) => x.Name == y.Name;
|
||||
|
||||
public int GetHashCode(MethodInfo obj) => obj.Name.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
133
NadekoBot.Core/Modules/Utility/CommandMapCommands.cs
Normal file
133
NadekoBot.Core/Modules/Utility/CommandMapCommands.cs
Normal file
@ -0,0 +1,133 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public partial class Utility
|
||||
{
|
||||
[Group]
|
||||
public class CommandMapCommands : NadekoSubmodule<CommandMapService>
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
public CommandMapCommands(DbService db, DiscordSocketClient client)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireUserPermission(GuildPermission.Administrator)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Alias(string trigger, [Remainder] string mapping = null)
|
||||
{
|
||||
var channel = (ITextChannel)Context.Channel;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(trigger))
|
||||
return;
|
||||
|
||||
trigger = trigger.Trim().ToLowerInvariant();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mapping))
|
||||
{
|
||||
if (!_service.AliasMaps.TryGetValue(Context.Guild.Id, out var maps) ||
|
||||
!maps.TryRemove(trigger, out _))
|
||||
{
|
||||
await ReplyErrorLocalized("alias_remove_fail", Format.Code(trigger)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.CommandAliases));
|
||||
var toAdd = new CommandAlias()
|
||||
{
|
||||
Mapping = mapping,
|
||||
Trigger = trigger
|
||||
};
|
||||
config.CommandAliases.RemoveWhere(x => x.Trigger == trigger);
|
||||
uow.Complete();
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalized("alias_removed", Format.Code(trigger)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
_service.AliasMaps.AddOrUpdate(Context.Guild.Id, (_) =>
|
||||
{
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.CommandAliases));
|
||||
config.CommandAliases.Add(new CommandAlias()
|
||||
{
|
||||
Mapping = mapping,
|
||||
Trigger = trigger
|
||||
});
|
||||
uow.Complete();
|
||||
}
|
||||
return new ConcurrentDictionary<string, string>(new Dictionary<string, string>() {
|
||||
{trigger.Trim().ToLowerInvariant(), mapping.ToLowerInvariant() },
|
||||
});
|
||||
}, (_, map) =>
|
||||
{
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var config = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.CommandAliases));
|
||||
var toAdd = new CommandAlias()
|
||||
{
|
||||
Mapping = mapping,
|
||||
Trigger = trigger
|
||||
};
|
||||
config.CommandAliases.RemoveWhere(x => x.Trigger == trigger);
|
||||
config.CommandAliases.Add(toAdd);
|
||||
uow.Complete();
|
||||
}
|
||||
map.AddOrUpdate(trigger, mapping, (key, old) => mapping);
|
||||
return map;
|
||||
});
|
||||
|
||||
await ReplyConfirmLocalized("alias_added", Format.Code(trigger), Format.Code(mapping)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AliasList(int page = 1)
|
||||
{
|
||||
var channel = (ITextChannel)Context.Channel;
|
||||
page -= 1;
|
||||
|
||||
if (page < 0)
|
||||
return;
|
||||
|
||||
if (!_service.AliasMaps.TryGetValue(Context.Guild.Id, out var maps) || !maps.Any())
|
||||
{
|
||||
await ReplyErrorLocalized("aliases_none").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var arr = maps.ToArray();
|
||||
|
||||
await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) =>
|
||||
{
|
||||
return new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(GetText("alias_list"))
|
||||
.WithDescription(string.Join("\n",
|
||||
arr.Skip(curPage * 10).Take(10).Select(x => $"`{x.Key}` => `{x.Value}`")));
|
||||
|
||||
}, arr.Length / 10).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Common.Exceptions
|
||||
{
|
||||
public class StreamRoleNotFoundException : Exception
|
||||
{
|
||||
public StreamRoleNotFoundException() : base("Stream role wasn't found.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Common.Exceptions
|
||||
{
|
||||
public class StreamRolePermissionException : Exception
|
||||
{
|
||||
public StreamRolePermissionException() : base("Stream role was unable to be applied.")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
23
NadekoBot.Core/Modules/Utility/Common/Patreon/PatreonData.cs
Normal file
23
NadekoBot.Core/Modules/Utility/Common/Patreon/PatreonData.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Common.Patreon
|
||||
{
|
||||
public class PatreonData
|
||||
{
|
||||
public JObject[] Included { get; set; }
|
||||
public JObject[] Data { get; set; }
|
||||
public PatreonDataLinks Links { get; set; }
|
||||
}
|
||||
|
||||
public class PatreonDataLinks
|
||||
{
|
||||
public string first { get; set; }
|
||||
public string next { get; set; }
|
||||
}
|
||||
|
||||
public class PatreonUserAndReward
|
||||
{
|
||||
public PatreonUser User { get; set; }
|
||||
public PatreonPledge Reward { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
namespace NadekoBot.Modules.Utility.Common.Patreon
|
||||
{
|
||||
public class Attributes
|
||||
{
|
||||
public int amount_cents { get; set; }
|
||||
public string created_at { get; set; }
|
||||
public object declined_since { get; set; }
|
||||
public bool is_twitch_pledge { get; set; }
|
||||
public bool patron_pays_fees { get; set; }
|
||||
public int pledge_cap_cents { get; set; }
|
||||
}
|
||||
|
||||
public class Address
|
||||
{
|
||||
public object data { get; set; }
|
||||
}
|
||||
|
||||
public class Data
|
||||
{
|
||||
public string id { get; set; }
|
||||
public string type { get; set; }
|
||||
}
|
||||
|
||||
public class Links
|
||||
{
|
||||
public string related { get; set; }
|
||||
}
|
||||
|
||||
public class Creator
|
||||
{
|
||||
public Data data { get; set; }
|
||||
public Links links { get; set; }
|
||||
}
|
||||
|
||||
public class Patron
|
||||
{
|
||||
public Data data { get; set; }
|
||||
public Links links { get; set; }
|
||||
}
|
||||
|
||||
public class Reward
|
||||
{
|
||||
public Data data { get; set; }
|
||||
public Links links { get; set; }
|
||||
}
|
||||
|
||||
public class Relationships
|
||||
{
|
||||
public Address address { get; set; }
|
||||
public Creator creator { get; set; }
|
||||
public Patron patron { get; set; }
|
||||
public Reward reward { get; set; }
|
||||
}
|
||||
|
||||
public class PatreonPledge
|
||||
{
|
||||
public Attributes attributes { get; set; }
|
||||
public string id { get; set; }
|
||||
public Relationships relationships { get; set; }
|
||||
public string type { get; set; }
|
||||
}
|
||||
}
|
64
NadekoBot.Core/Modules/Utility/Common/Patreon/PatreonUser.cs
Normal file
64
NadekoBot.Core/Modules/Utility/Common/Patreon/PatreonUser.cs
Normal file
@ -0,0 +1,64 @@
|
||||
namespace NadekoBot.Modules.Utility.Common.Patreon
|
||||
{
|
||||
public class DiscordConnection
|
||||
{
|
||||
public string user_id { get; set; }
|
||||
}
|
||||
|
||||
public class SocialConnections
|
||||
{
|
||||
public object deviantart { get; set; }
|
||||
public DiscordConnection discord { get; set; }
|
||||
public object facebook { get; set; }
|
||||
public object spotify { get; set; }
|
||||
public object twitch { get; set; }
|
||||
public object twitter { get; set; }
|
||||
public object youtube { get; set; }
|
||||
}
|
||||
|
||||
public class UserAttributes
|
||||
{
|
||||
public string about { get; set; }
|
||||
public string created { get; set; }
|
||||
public object discord_id { get; set; }
|
||||
public string email { get; set; }
|
||||
public object facebook { get; set; }
|
||||
public object facebook_id { get; set; }
|
||||
public string first_name { get; set; }
|
||||
public string full_name { get; set; }
|
||||
public int gender { get; set; }
|
||||
public bool has_password { get; set; }
|
||||
public string image_url { get; set; }
|
||||
public bool is_deleted { get; set; }
|
||||
public bool is_nuked { get; set; }
|
||||
public bool is_suspended { get; set; }
|
||||
public string last_name { get; set; }
|
||||
public SocialConnections social_connections { get; set; }
|
||||
public int status { get; set; }
|
||||
public string thumb_url { get; set; }
|
||||
public object twitch { get; set; }
|
||||
public string twitter { get; set; }
|
||||
public string url { get; set; }
|
||||
public string vanity { get; set; }
|
||||
public object youtube { get; set; }
|
||||
}
|
||||
|
||||
public class Campaign
|
||||
{
|
||||
public Data data { get; set; }
|
||||
public Links links { get; set; }
|
||||
}
|
||||
|
||||
public class UserRelationships
|
||||
{
|
||||
public Campaign campaign { get; set; }
|
||||
}
|
||||
|
||||
public class PatreonUser
|
||||
{
|
||||
public UserAttributes attributes { get; set; }
|
||||
public string id { get; set; }
|
||||
public UserRelationships relationships { get; set; }
|
||||
public string type { get; set; }
|
||||
}
|
||||
}
|
107
NadekoBot.Core/Modules/Utility/Common/RepeatRunner.cs
Normal file
107
NadekoBot.Core/Modules/Utility/Common/RepeatRunner.cs
Normal file
@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Net;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Common
|
||||
{
|
||||
public class RepeatRunner
|
||||
{
|
||||
private readonly Logger _log;
|
||||
|
||||
public Repeater Repeater { get; }
|
||||
public SocketGuild Guild { get; }
|
||||
public ITextChannel Channel { get; private set; }
|
||||
public TimeSpan InitialInterval { get; private set; }
|
||||
|
||||
private IUserMessage oldMsg = null;
|
||||
private Timer _t;
|
||||
|
||||
public RepeatRunner(DiscordSocketClient client, SocketGuild guild, Repeater repeater)
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
Repeater = repeater;
|
||||
Guild = guild;
|
||||
|
||||
InitialInterval = Repeater.Interval;
|
||||
|
||||
Run();
|
||||
}
|
||||
|
||||
private void Run()
|
||||
{
|
||||
if (Repeater.StartTimeOfDay != null)
|
||||
{
|
||||
if ((InitialInterval = Repeater.StartTimeOfDay.Value - DateTime.UtcNow.TimeOfDay) < TimeSpan.Zero)
|
||||
InitialInterval += TimeSpan.FromDays(1);
|
||||
}
|
||||
|
||||
_t = new Timer(async (_) => {
|
||||
|
||||
try { await Trigger().ConfigureAwait(false); } catch { }
|
||||
|
||||
}, null, InitialInterval, Repeater.Interval);
|
||||
}
|
||||
|
||||
public async Task Trigger()
|
||||
{
|
||||
var toSend = "🔄 " + Repeater.Message;
|
||||
//var lastMsgInChannel = (await Channel.GetMessagesAsync(2)).FirstOrDefault();
|
||||
// if (lastMsgInChannel.Id == oldMsg?.Id) //don't send if it's the same message in the channel
|
||||
// continue;
|
||||
|
||||
if (oldMsg != null)
|
||||
try
|
||||
{
|
||||
await oldMsg.DeleteAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
try
|
||||
{
|
||||
if (Channel == null)
|
||||
Channel = Guild.GetTextChannel(Repeater.ChannelId);
|
||||
|
||||
if (Channel != null)
|
||||
oldMsg = await Channel.SendMessageAsync(toSend.SanitizeMentions()).ConfigureAwait(false);
|
||||
}
|
||||
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.HttpCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
_log.Warn("Channel not found. Repeater stopped. ChannelId : {0}", Channel?.Id);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Stop();
|
||||
Run();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_t.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
|
||||
public override string ToString() =>
|
||||
$"{Channel?.Mention ?? $"⚠<#{Repeater.ChannelId}>" } " +
|
||||
$"| {(int)Repeater.Interval.TotalHours}:{Repeater.Interval:mm} " +
|
||||
$"| {Repeater.Message.TrimTo(33)}";
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
namespace NadekoBot.Modules.Utility.Common
|
||||
{
|
||||
public enum StreamRoleListType
|
||||
{
|
||||
Whitelist,
|
||||
Blacklist,
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using NadekoBot.Core.Services.Database.Repositories;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Extensions
|
||||
{
|
||||
public static class StreamRoleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets full stream role settings for the guild with the specified id.
|
||||
/// </summary>
|
||||
/// <param name="gc"></param>
|
||||
/// <param name="guildId">Id of the guild to get stream role settings for.</param>
|
||||
/// <returns></returns>
|
||||
public static StreamRoleSettings GetStreamRoleSettings(this IGuildConfigRepository gc, ulong guildId)
|
||||
{
|
||||
var conf = gc.For(guildId, x => x.Include(y => y.StreamRole)
|
||||
.Include(y => y.StreamRole.Whitelist)
|
||||
.Include(y => y.StreamRole.Blacklist));
|
||||
|
||||
if (conf.StreamRole == null)
|
||||
conf.StreamRole = new StreamRoleSettings();
|
||||
|
||||
return conf.StreamRole;
|
||||
}
|
||||
}
|
||||
}
|
147
NadekoBot.Core/Modules/Utility/InfoCommands.cs
Normal file
147
NadekoBot.Core/Modules/Utility/InfoCommands.cs
Normal file
@ -0,0 +1,147 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public partial class Utility
|
||||
{
|
||||
[Group]
|
||||
public class InfoCommands : NadekoSubmodule
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IStatsService _stats;
|
||||
|
||||
public InfoCommands(DiscordSocketClient client, IStatsService stats, CommandHandler ch)
|
||||
{
|
||||
_client = client;
|
||||
_stats = stats;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ServerInfo(string guildName = null)
|
||||
{
|
||||
var channel = (ITextChannel)Context.Channel;
|
||||
guildName = guildName?.ToUpperInvariant();
|
||||
SocketGuild guild;
|
||||
if (string.IsNullOrWhiteSpace(guildName))
|
||||
guild = (SocketGuild)channel.Guild;
|
||||
else
|
||||
guild = _client.Guilds.FirstOrDefault(g => g.Name.ToUpperInvariant() == guildName.ToUpperInvariant());
|
||||
if (guild == null)
|
||||
return;
|
||||
var ownername = guild.GetUser(guild.OwnerId);
|
||||
var textchn = guild.TextChannels.Count();
|
||||
var voicechn = guild.VoiceChannels.Count();
|
||||
|
||||
var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(guild.Id >> 22);
|
||||
var features = string.Join("\n", guild.Features);
|
||||
if (string.IsNullOrWhiteSpace(features))
|
||||
features = "-";
|
||||
var embed = new EmbedBuilder()
|
||||
.WithAuthor(eab => eab.WithName(GetText("server_info")))
|
||||
.WithTitle(guild.Name)
|
||||
.AddField(fb => fb.WithName(GetText("id")).WithValue(guild.Id.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("owner")).WithValue(ownername.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("members")).WithValue(guild.MemberCount.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("text_channels")).WithValue(textchn.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("voice_channels")).WithValue(voicechn.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("created_at")).WithValue($"{createdAt:dd.MM.yyyy HH:mm}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("region")).WithValue(guild.VoiceRegionId.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("roles")).WithValue((guild.Roles.Count - 1).ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("features")).WithValue(features).WithIsInline(true))
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
if (Uri.IsWellFormedUriString(guild.IconUrl, UriKind.Absolute))
|
||||
embed.WithImageUrl(guild.IconUrl);
|
||||
if (guild.Emotes.Any())
|
||||
{
|
||||
embed.AddField(fb => fb.WithName(GetText("custom_emojis") + $"({guild.Emotes.Count})").WithValue(string.Join(" ", guild.Emotes.Shuffle().Take(20).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>"))));
|
||||
}
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ChannelInfo(ITextChannel channel = null)
|
||||
{
|
||||
var ch = channel ?? (ITextChannel)Context.Channel;
|
||||
if (ch == null)
|
||||
return;
|
||||
var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(ch.Id >> 22);
|
||||
var usercount = (await ch.GetUsersAsync().Flatten()).Count();
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle(ch.Name)
|
||||
.WithDescription(ch.Topic?.SanitizeMentions())
|
||||
.AddField(fb => fb.WithName(GetText("id")).WithValue(ch.Id.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("created_at")).WithValue($"{createdAt:dd.MM.yyyy HH:mm}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("users")).WithValue(usercount.ToString()).WithIsInline(true))
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task UserInfo(IGuildUser usr = null)
|
||||
{
|
||||
var user = usr ?? Context.User as IGuildUser;
|
||||
|
||||
if (user == null)
|
||||
return;
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.AddField(fb => fb.WithName(GetText("name")).WithValue($"**{user.Username}**#{user.Discriminator}").WithIsInline(true));
|
||||
if (!string.IsNullOrWhiteSpace(user.Nickname))
|
||||
{
|
||||
embed.AddField(fb => fb.WithName(GetText("nickname")).WithValue(user.Nickname).WithIsInline(true));
|
||||
}
|
||||
embed.AddField(fb => fb.WithName(GetText("id")).WithValue(user.Id.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("joined_server")).WithValue($"{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm") ?? "?"}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("joined_discord")).WithValue($"{user.CreatedAt:dd.MM.yyyy HH:mm}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName(GetText("roles")).WithValue($"**({user.RoleIds.Count - 1})** - {string.Join("\n", user.GetRoles().Take(10).Where(r => r.Id != r.Guild.EveryoneRole.Id).Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true))
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
|
||||
if (user.AvatarId != null)
|
||||
embed.WithThumbnailUrl(user.RealAvatarUrl());
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task Activity(int page = 1)
|
||||
{
|
||||
const int activityPerPage = 15;
|
||||
page -= 1;
|
||||
|
||||
if (page < 0)
|
||||
return;
|
||||
|
||||
int startCount = page * activityPerPage;
|
||||
|
||||
StringBuilder str = new StringBuilder();
|
||||
foreach (var kvp in _cmdHandler.UserMessagesSent.OrderByDescending(kvp => kvp.Value).Skip(page * activityPerPage).Take(activityPerPage))
|
||||
{
|
||||
str.AppendLine(GetText("activity_line",
|
||||
++startCount,
|
||||
Format.Bold(kvp.Key.ToString()),
|
||||
kvp.Value / _stats.GetUptime().TotalSeconds, kvp.Value));
|
||||
}
|
||||
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder()
|
||||
.WithTitle(GetText("activity_page", page + 1))
|
||||
.WithOkColor()
|
||||
.WithFooter(efb => efb.WithText(GetText("activity_users_total",
|
||||
_cmdHandler.UserMessagesSent.Count)))
|
||||
.WithDescription(str.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputPath>..\src\NadekoBot\bin\$(Configuration)\netcoreapp2.0\modules\$(AssemblyName)\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="0.9.9" />
|
||||
<PackageReference Include="Discord.Net" Version="2.0.0-alpha-build-00832" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NadekoBot.Core\NadekoBot.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
81
NadekoBot.Core/Modules/Utility/PatreonCommands.cs
Normal file
81
NadekoBot.Core/Modules/Utility/PatreonCommands.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using System.Threading.Tasks;
|
||||
using Discord.Commands;
|
||||
using System;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Extensions;
|
||||
using Discord;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public partial class Utility
|
||||
{
|
||||
[Group]
|
||||
public class PatreonCommands : NadekoSubmodule<PatreonRewardsService>
|
||||
{
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly IBotConfigProvider _config;
|
||||
private readonly DbService _db;
|
||||
private readonly CurrencyService _currency;
|
||||
|
||||
public PatreonCommands(IBotCredentials creds, IBotConfigProvider config, DbService db, CurrencyService currency)
|
||||
{
|
||||
_creds = creds;
|
||||
_config = config;
|
||||
_db = db;
|
||||
_currency = currency;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.DM)]
|
||||
public async Task PatreonRewardsReload()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken))
|
||||
return;
|
||||
await _service.RefreshPledges().ConfigureAwait(false);
|
||||
|
||||
await Context.Channel.SendConfirmAsync("👌").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ClaimPatreonRewards()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken))
|
||||
return;
|
||||
|
||||
if (DateTime.UtcNow.Day < 5)
|
||||
{
|
||||
await ReplyErrorLocalized("clpa_too_early").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
int amount = 0;
|
||||
try
|
||||
{
|
||||
amount = await _service.ClaimReward(Context.User.Id).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
}
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
await ReplyConfirmLocalized("clpa_success", amount + _config.BotConfig.CurrencySign).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var rem = (_service.Interval - (DateTime.UtcNow - _service.LastUpdate));
|
||||
var helpcmd = Format.Code(Prefix + "donate");
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithDescription(GetText("clpa_fail"))
|
||||
.AddField(efb => efb.WithName(GetText("clpa_fail_already_title")).WithValue(GetText("clpa_fail_already")))
|
||||
.AddField(efb => efb.WithName(GetText("clpa_fail_wait_title")).WithValue(GetText("clpa_fail_wait")))
|
||||
.AddField(efb => efb.WithName(GetText("clpa_fail_conn_title")).WithValue(GetText("clpa_fail_conn")))
|
||||
.AddField(efb => efb.WithName(GetText("clpa_fail_sup_title")).WithValue(GetText("clpa_fail_sup", helpcmd)))
|
||||
.WithFooter(efb => efb.WithText(GetText("clpa_next_update", rem))))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
217
NadekoBot.Core/Modules/Utility/QuoteCommands.cs
Normal file
217
NadekoBot.Core/Modules/Utility/QuoteCommands.cs
Normal file
@ -0,0 +1,217 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Common.Replacements;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public partial class Utility
|
||||
{
|
||||
[Group]
|
||||
public class QuoteCommands : NadekoSubmodule
|
||||
{
|
||||
private readonly DbService _db;
|
||||
|
||||
public QuoteCommands(DbService db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ListQuotes(int page = 1)
|
||||
{
|
||||
page -= 1;
|
||||
|
||||
if (page < 0)
|
||||
return;
|
||||
|
||||
IEnumerable<Quote> quotes;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
quotes = uow.Quotes.GetGroup(Context.Guild.Id, page * 16, 16);
|
||||
}
|
||||
|
||||
if (quotes.Any())
|
||||
await Context.Channel.SendConfirmAsync(GetText("quotes_page", page + 1),
|
||||
string.Join("\n", quotes.Select(q => $"`#{q.Id}` {Format.Bold(q.Keyword.SanitizeMentions()),-20} by {q.AuthorName.SanitizeMentions()}")))
|
||||
.ConfigureAwait(false);
|
||||
else
|
||||
await ReplyErrorLocalized("quotes_page_none").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ShowQuote([Remainder] string keyword)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(keyword))
|
||||
return;
|
||||
|
||||
keyword = keyword.ToUpperInvariant();
|
||||
|
||||
Quote quote;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
quote =
|
||||
await uow.Quotes.GetRandomQuoteByKeywordAsync(Context.Guild.Id, keyword).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (quote == null)
|
||||
return;
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(Context)
|
||||
.Build();
|
||||
|
||||
if (CREmbed.TryParse(quote.Text, out var crembed))
|
||||
{
|
||||
rep.Replace(crembed);
|
||||
await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText?.SanitizeMentions() ?? "")
|
||||
.ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
await Context.Channel.SendMessageAsync($"`#{quote.Id}` 📣 " + rep.Replace(quote.Text)?.SanitizeMentions());
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task QuoteSearch(string keyword, [Remainder] string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(keyword) || string.IsNullOrWhiteSpace(text))
|
||||
return;
|
||||
|
||||
keyword = keyword.ToUpperInvariant();
|
||||
|
||||
Quote keywordquote;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
keywordquote =
|
||||
await uow.Quotes.SearchQuoteKeywordTextAsync(Context.Guild.Id, keyword, text)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (keywordquote == null)
|
||||
return;
|
||||
|
||||
await Context.Channel.SendMessageAsync($"`#{keywordquote.Id}` 💬 " + keyword.ToLowerInvariant() + ": " +
|
||||
keywordquote.Text.SanitizeMentions());
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task QuoteId(int id)
|
||||
{
|
||||
if (id < 0)
|
||||
return;
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var qfromid = uow.Quotes.Get(id);
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithDefault(Context)
|
||||
.Build();
|
||||
|
||||
if (qfromid == null)
|
||||
{
|
||||
await Context.Channel.SendErrorAsync(GetText("quotes_notfound"));
|
||||
}
|
||||
else if (CREmbed.TryParse(qfromid.Text, out var crembed))
|
||||
{
|
||||
rep.Replace(crembed);
|
||||
|
||||
await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText?.SanitizeMentions() ?? "")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Context.Channel.SendMessageAsync($"`#{qfromid.Id}` 🗯️ " + qfromid.Keyword.ToLowerInvariant().SanitizeMentions() + ": " +
|
||||
rep.Replace(qfromid.Text)?.SanitizeMentions());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task AddQuote(string keyword, [Remainder] string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(keyword) || string.IsNullOrWhiteSpace(text))
|
||||
return;
|
||||
|
||||
keyword = keyword.ToUpperInvariant();
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.Quotes.Add(new Quote
|
||||
{
|
||||
AuthorId = Context.Message.Author.Id,
|
||||
AuthorName = Context.Message.Author.Username,
|
||||
GuildId = Context.Guild.Id,
|
||||
Keyword = keyword,
|
||||
Text = text,
|
||||
});
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
await ReplyConfirmLocalized("quote_added").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task QuoteDelete(int id)
|
||||
{
|
||||
var isAdmin = ((IGuildUser) Context.Message.Author).GuildPermissions.Administrator;
|
||||
|
||||
var success = false;
|
||||
string response;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var q = uow.Quotes.Get(id);
|
||||
|
||||
if (q == null || (!isAdmin && q.AuthorId != Context.Message.Author.Id))
|
||||
{
|
||||
response = GetText("quotes_remove_none");
|
||||
}
|
||||
else
|
||||
{
|
||||
uow.Quotes.Remove(q);
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
success = true;
|
||||
response = GetText("quote_deleted", id);
|
||||
}
|
||||
}
|
||||
if (success)
|
||||
await Context.Channel.SendConfirmAsync(response);
|
||||
else
|
||||
await Context.Channel.SendErrorAsync(response);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.Administrator)]
|
||||
public async Task DelAllQuotes([Remainder] string keyword)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(keyword))
|
||||
return;
|
||||
|
||||
keyword = keyword.ToUpperInvariant();
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.Quotes.RemoveAllByKeyword(Context.Guild.Id, keyword.ToUpperInvariant());
|
||||
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalized("quotes_deleted", Format.Bold(keyword.SanitizeMentions())).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
156
NadekoBot.Core/Modules/Utility/RemindCommands.cs
Normal file
156
NadekoBot.Core/Modules/Utility/RemindCommands.cs
Normal file
@ -0,0 +1,156 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public partial class Utility
|
||||
{
|
||||
[Group]
|
||||
public class RemindCommands : NadekoSubmodule<RemindService>
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly GuildTimezoneService _tz;
|
||||
|
||||
public RemindCommands(DbService db, GuildTimezoneService tz)
|
||||
{
|
||||
_db = db;
|
||||
_tz = tz;
|
||||
}
|
||||
|
||||
public enum MeOrHere
|
||||
{
|
||||
Me,Here
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task Remind(MeOrHere meorhere, string timeStr, [Remainder] string message)
|
||||
{
|
||||
ulong target;
|
||||
target = meorhere == MeOrHere.Me ? Context.User.Id : Context.Channel.Id;
|
||||
await RemindInternal(target, meorhere == MeOrHere.Me, timeStr, message).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
[Priority(0)]
|
||||
public async Task Remind(ITextChannel channel, string timeStr, [Remainder] string message)
|
||||
{
|
||||
var perms = ((IGuildUser)Context.User).GetPermissions((ITextChannel)channel);
|
||||
if (!perms.SendMessages || !perms.ReadMessages)
|
||||
{
|
||||
await ReplyErrorLocalized("cant_read_or_send").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
var _ = RemindInternal(channel.Id, false, timeStr, message).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemindInternal(ulong targetId, bool isPrivate, string timeStr, [Remainder] string message)
|
||||
{
|
||||
var m = _service.Regex.Match(timeStr);
|
||||
|
||||
if (m.Length == 0)
|
||||
{
|
||||
await ReplyErrorLocalized("remind_invalid_format").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
string output = "";
|
||||
var namesAndValues = new Dictionary<string, int>();
|
||||
|
||||
foreach (var groupName in _service.Regex.GetGroupNames())
|
||||
{
|
||||
if (groupName == "0") continue;
|
||||
int value;
|
||||
int.TryParse(m.Groups[groupName].Value, out value);
|
||||
|
||||
if (string.IsNullOrEmpty(m.Groups[groupName].Value))
|
||||
{
|
||||
namesAndValues[groupName] = 0;
|
||||
continue;
|
||||
}
|
||||
if (value < 1 ||
|
||||
(groupName == "months" && value > 1) ||
|
||||
(groupName == "weeks" && value > 4) ||
|
||||
(groupName == "days" && value >= 7) ||
|
||||
(groupName == "hours" && value > 23) ||
|
||||
(groupName == "minutes" && value > 59))
|
||||
{
|
||||
await Context.Channel.SendErrorAsync($"Invalid {groupName} value.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
namesAndValues[groupName] = value;
|
||||
output += m.Groups[groupName].Value + " " + groupName + " ";
|
||||
}
|
||||
var time = DateTime.UtcNow + new TimeSpan(30 * namesAndValues["months"] +
|
||||
7 * namesAndValues["weeks"] +
|
||||
namesAndValues["days"],
|
||||
namesAndValues["hours"],
|
||||
namesAndValues["minutes"],
|
||||
0);
|
||||
|
||||
var rem = new Reminder
|
||||
{
|
||||
ChannelId = targetId,
|
||||
IsPrivate = isPrivate,
|
||||
When = time,
|
||||
Message = message,
|
||||
UserId = Context.User.Id,
|
||||
ServerId = Context.Guild.Id
|
||||
};
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.Reminders.Add(rem);
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
|
||||
var gTime = TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(Context.Guild.Id));
|
||||
try
|
||||
{
|
||||
await Context.Channel.SendConfirmAsync(
|
||||
"⏰ " + GetText("remind",
|
||||
Format.Bold(!isPrivate ? $"<#{targetId}>" : Context.User.Username),
|
||||
Format.Bold(message.SanitizeMentions()),
|
||||
Format.Bold(output),
|
||||
gTime, gTime)).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
await _service.StartReminder(rem);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task RemindTemplate([Remainder] string arg)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(arg))
|
||||
return;
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.BotConfig.GetOrCreate().RemindMessageFormat = arg.Trim();
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await ReplyConfirmLocalized("remind_template").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
236
NadekoBot.Core/Modules/Utility/RepeatCommands.cs
Normal file
236
NadekoBot.Core/Modules/Utility/RepeatCommands.cs
Normal file
@ -0,0 +1,236 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Common.TypeReaders;
|
||||
using NadekoBot.Modules.Utility.Common;
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public partial class Utility
|
||||
{
|
||||
[Group]
|
||||
public class RepeatCommands : NadekoSubmodule<MessageRepeaterService>
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly DbService _db;
|
||||
|
||||
public RepeatCommands(DiscordSocketClient client, DbService db)
|
||||
{
|
||||
_client = client;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
public async Task RepeatInvoke(int index)
|
||||
{
|
||||
if (!_service.RepeaterReady)
|
||||
return;
|
||||
index -= 1;
|
||||
if (!_service.Repeaters.TryGetValue(Context.Guild.Id, out var rep))
|
||||
{
|
||||
await ReplyErrorLocalized("repeat_invoke_none").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var repList = rep.ToList();
|
||||
|
||||
if (index >= repList.Count)
|
||||
{
|
||||
await ReplyErrorLocalized("index_out_of_range").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var repeater = repList[index].Repeater;
|
||||
repList[index].Reset();
|
||||
await repList[index].Trigger();
|
||||
|
||||
await Context.Channel.SendMessageAsync("🔄").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
public async Task RepeatRemove(int index)
|
||||
{
|
||||
if (!_service.RepeaterReady)
|
||||
return;
|
||||
if (index < 1)
|
||||
return;
|
||||
index -= 1;
|
||||
|
||||
if (!_service.Repeaters.TryGetValue(Context.Guild.Id, out var rep))
|
||||
return;
|
||||
|
||||
var repeaterList = rep.ToList();
|
||||
|
||||
if (index >= repeaterList.Count)
|
||||
{
|
||||
await ReplyErrorLocalized("index_out_of_range").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var repeater = repeaterList[index];
|
||||
repeater.Stop();
|
||||
repeaterList.RemoveAt(index);
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var guildConfig = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(gc => gc.GuildRepeaters));
|
||||
|
||||
guildConfig.GuildRepeaters.RemoveWhere(r => r.Id == repeater.Repeater.Id);
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (_service.Repeaters.TryUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(repeaterList), rep))
|
||||
await Context.Channel.SendConfirmAsync(GetText("message_repeater"),
|
||||
GetText("repeater_stopped", index + 1) + $"\n\n{repeater}").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
[Priority(0)]
|
||||
public async Task Repeat(int minutes, [Remainder] string message)
|
||||
{
|
||||
if (!_service.RepeaterReady)
|
||||
return;
|
||||
if (minutes < 1 || minutes > 10080)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
return;
|
||||
|
||||
var toAdd = new GuildRepeater()
|
||||
{
|
||||
ChannelId = Context.Channel.Id,
|
||||
GuildId = Context.Guild.Id,
|
||||
Interval = TimeSpan.FromMinutes(minutes),
|
||||
Message = message
|
||||
};
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.GuildRepeaters));
|
||||
|
||||
if (gc.GuildRepeaters.Count >= 5)
|
||||
return;
|
||||
gc.GuildRepeaters.Add(toAdd);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var rep = new RepeatRunner(_client, (SocketGuild)Context.Guild, toAdd);
|
||||
|
||||
_service.Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(new[] {rep}), (key, old) =>
|
||||
{
|
||||
old.Enqueue(rep);
|
||||
return old;
|
||||
});
|
||||
|
||||
await Context.Channel.SendConfirmAsync(
|
||||
"🔁 " + GetText("repeater",
|
||||
Format.Bold(((IGuildUser)Context.User).GuildPermissions.MentionEveryone ? rep.Repeater.Message : rep.Repeater.Message.SanitizeMentions()),
|
||||
Format.Bold(rep.Repeater.Interval.Days.ToString()),
|
||||
Format.Bold(rep.Repeater.Interval.Hours.ToString()),
|
||||
Format.Bold(rep.Repeater.Interval.Minutes.ToString()))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
[Priority(1)]
|
||||
public async Task Repeat(GuildDateTime gt, [Remainder] string message)
|
||||
{
|
||||
if (!_service.RepeaterReady)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
return;
|
||||
|
||||
var toAdd = new GuildRepeater()
|
||||
{
|
||||
ChannelId = Context.Channel.Id,
|
||||
GuildId = Context.Guild.Id,
|
||||
Interval = TimeSpan.FromHours(24),
|
||||
StartTimeOfDay = gt.InputTimeUtc.TimeOfDay,
|
||||
Message = message
|
||||
};
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var gc = uow.GuildConfigs.For(Context.Guild.Id, set => set.Include(x => x.GuildRepeaters));
|
||||
|
||||
if (gc.GuildRepeaters.Count >= 5)
|
||||
return;
|
||||
gc.GuildRepeaters.Add(toAdd);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var rep = new RepeatRunner(_client, (SocketGuild)Context.Guild, toAdd);
|
||||
|
||||
_service.Repeaters.AddOrUpdate(Context.Guild.Id, new ConcurrentQueue<RepeatRunner>(new[] { rep }), (key, old) =>
|
||||
{
|
||||
old.Enqueue(rep);
|
||||
return old;
|
||||
});
|
||||
|
||||
var secondPart = GetText("repeater_initial",
|
||||
Format.Bold(rep.InitialInterval.Hours.ToString()),
|
||||
Format.Bold(rep.InitialInterval.Minutes.ToString()));
|
||||
|
||||
await Context.Channel.SendConfirmAsync(
|
||||
"🔁 " + GetText("repeater",
|
||||
Format.Bold(((IGuildUser)Context.User).GuildPermissions.MentionEveryone ? rep.Repeater.Message : rep.Repeater.Message.SanitizeMentions()),
|
||||
Format.Bold(rep.Repeater.Interval.Days.ToString()),
|
||||
Format.Bold(rep.Repeater.Interval.Hours.ToString()),
|
||||
Format.Bold(rep.Repeater.Interval.Minutes.ToString())) + " " + secondPart).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||
public async Task RepeatList()
|
||||
{
|
||||
if (!_service.RepeaterReady)
|
||||
return;
|
||||
if (!_service.Repeaters.TryGetValue(Context.Guild.Id, out var repRunners))
|
||||
{
|
||||
await ReplyConfirmLocalized("repeaters_none").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var replist = repRunners.ToList();
|
||||
var sb = new StringBuilder();
|
||||
|
||||
for (var i = 0; i < replist.Count; i++)
|
||||
{
|
||||
var rep = replist[i];
|
||||
|
||||
sb.AppendLine($"`{i + 1}.` {rep}");
|
||||
}
|
||||
var desc = sb.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(desc))
|
||||
desc = GetText("no_active_repeaters");
|
||||
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(GetText("list_of_repeaters"))
|
||||
.WithDescription(desc))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
NadekoBot.Core/Modules/Utility/Services/CommandMapService.cs
Normal file
77
NadekoBot.Core/Modules/Utility/Services/CommandMapService.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services
|
||||
{
|
||||
public class CommandMapService : IInputTransformer, INService
|
||||
{
|
||||
private readonly Logger _log;
|
||||
|
||||
public ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>> AliasMaps { get; } = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>();
|
||||
//commandmap
|
||||
public CommandMapService(NadekoBot bot)
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
AliasMaps = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>>(
|
||||
bot.AllGuildConfigs.ToDictionary(
|
||||
x => x.GuildId,
|
||||
x => new ConcurrentDictionary<string, string>(x.CommandAliases
|
||||
.Distinct(new CommandAliasEqualityComparer())
|
||||
.ToDictionary(ca => ca.Trigger, ca => ca.Mapping))));
|
||||
}
|
||||
|
||||
public async Task<string> TransformInput(IGuild guild, IMessageChannel channel, IUser user, string input)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
if (guild == null || string.IsNullOrWhiteSpace(input))
|
||||
return input;
|
||||
|
||||
if (guild != null)
|
||||
{
|
||||
input = input.ToLowerInvariant();
|
||||
if (AliasMaps.TryGetValue(guild.Id, out ConcurrentDictionary<string, string> maps))
|
||||
{
|
||||
var keys = maps.Keys
|
||||
.OrderByDescending(x => x.Length);
|
||||
|
||||
foreach (var k in keys)
|
||||
{
|
||||
string newInput;
|
||||
if (input.StartsWith(k + " "))
|
||||
newInput = maps[k] + input.Substring(k.Length, input.Length - k.Length);
|
||||
else if (input == k)
|
||||
newInput = maps[k];
|
||||
else
|
||||
continue;
|
||||
|
||||
_log.Info(@"--Mapping Command--
|
||||
GuildId: {0}
|
||||
Trigger: {1}
|
||||
Mapping: {2}", guild.Id, input, newInput);
|
||||
|
||||
try { await channel.SendConfirmAsync($"{input} => {newInput}").ConfigureAwait(false); } catch { }
|
||||
return newInput;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
public class CommandAliasEqualityComparer : IEqualityComparer<CommandAlias>
|
||||
{
|
||||
public bool Equals(CommandAlias x, CommandAlias y) => x.Trigger == y.Trigger;
|
||||
|
||||
public int GetHashCode(CommandAlias obj) => obj.Trigger.GetHashCode();
|
||||
}
|
||||
}
|
148
NadekoBot.Core/Modules/Utility/Services/ConverterService.cs
Normal file
148
NadekoBot.Core/Modules/Utility/Services/ConverterService.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services
|
||||
{
|
||||
//todo rewrite
|
||||
public class ConverterService : INService, IUnloadableService
|
||||
{
|
||||
public List<ConvertUnit> Units { get; } = new List<ConvertUnit>();
|
||||
private readonly Logger _log;
|
||||
private readonly Timer _currencyUpdater;
|
||||
private readonly TimeSpan _updateInterval = new TimeSpan(12, 0, 0);
|
||||
private readonly DbService _db;
|
||||
private readonly ConvertUnit[] fileData;
|
||||
|
||||
public ConverterService(DiscordSocketClient client, DbService db)
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
_db = db;
|
||||
|
||||
if (client.ShardId == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
fileData = JsonConvert.DeserializeObject<List<MeasurementUnit>>(
|
||||
File.ReadAllText("data/units.json"))
|
||||
.Select(u => new ConvertUnit()
|
||||
{
|
||||
Modifier = u.Modifier,
|
||||
UnitType = u.UnitType,
|
||||
InternalTrigger = string.Join("|", u.Triggers)
|
||||
}).ToArray();
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
if (uow.ConverterUnits.Empty())
|
||||
{
|
||||
uow.ConverterUnits.AddRange(fileData);
|
||||
|
||||
Units = uow.ConverterUnits.GetAll().ToList();
|
||||
uow.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn("Could not load units: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
_currencyUpdater = new Timer(async (shouldLoad) => await UpdateCurrency((bool)shouldLoad),
|
||||
client.ShardId == 0,
|
||||
TimeSpan.FromSeconds(1),
|
||||
_updateInterval);
|
||||
}
|
||||
|
||||
private async Task<Rates> GetCurrencyRates()
|
||||
{
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var res = await http.GetStringAsync("http://api.fixer.io/latest").ConfigureAwait(false);
|
||||
return JsonConvert.DeserializeObject<Rates>(res);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateCurrency(bool shouldLoad)
|
||||
{
|
||||
try
|
||||
{
|
||||
var unitTypeString = "currency";
|
||||
if (shouldLoad)
|
||||
{
|
||||
var currencyRates = await GetCurrencyRates();
|
||||
var baseType = new ConvertUnit()
|
||||
{
|
||||
Triggers = new[] { currencyRates.Base },
|
||||
Modifier = decimal.One,
|
||||
UnitType = unitTypeString
|
||||
};
|
||||
var range = currencyRates.ConversionRates.Select(u => new ConvertUnit()
|
||||
{
|
||||
InternalTrigger = u.Key,
|
||||
Modifier = u.Value,
|
||||
UnitType = unitTypeString
|
||||
}).ToArray();
|
||||
var toRemove = Units.Where(u => u.UnitType == unitTypeString);
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
if(toRemove.Any())
|
||||
uow.ConverterUnits.RemoveRange(toRemove.ToArray());
|
||||
uow.ConverterUnits.Add(baseType);
|
||||
uow.ConverterUnits.AddRange(range);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
Units.RemoveAll(u => u.UnitType == unitTypeString);
|
||||
Units.Add(baseType);
|
||||
Units.AddRange(range);
|
||||
Units.AddRange(fileData);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
Units.RemoveAll(u => u.UnitType == unitTypeString);
|
||||
Units.AddRange(uow.ConverterUnits.GetAll().ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_log.Warn("Failed updating currency. Ignore this.");
|
||||
}
|
||||
}
|
||||
|
||||
public Task Unload()
|
||||
{
|
||||
_currencyUpdater.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class MeasurementUnit
|
||||
{
|
||||
public List<string> Triggers { get; set; }
|
||||
public string UnitType { get; set; }
|
||||
public decimal Modifier { get; set; }
|
||||
}
|
||||
|
||||
public class Rates
|
||||
{
|
||||
public string Base { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
[JsonProperty("rates")]
|
||||
public Dictionary<string, decimal> ConversionRates { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Modules.Utility.Common;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services
|
||||
{
|
||||
public class MessageRepeaterService : INService
|
||||
{
|
||||
//messagerepeater
|
||||
//guildid/RepeatRunners
|
||||
public ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>> Repeaters { get; set; }
|
||||
public bool RepeaterReady { get; private set; }
|
||||
|
||||
public MessageRepeaterService(NadekoBot bot, DiscordSocketClient client)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
await bot.Ready.Task.ConfigureAwait(false);
|
||||
|
||||
Repeaters = new ConcurrentDictionary<ulong, ConcurrentQueue<RepeatRunner>>(
|
||||
bot.AllGuildConfigs
|
||||
.Select(gc =>
|
||||
{
|
||||
var guild = client.GetGuild(gc.GuildId);
|
||||
if (guild == null)
|
||||
return (0, null);
|
||||
return (gc.GuildId, new ConcurrentQueue<RepeatRunner>(gc.GuildRepeaters
|
||||
.Select(gr => new RepeatRunner(client, guild, gr))
|
||||
.Where(x => x.Guild != null)));
|
||||
})
|
||||
.Where(x => x.Item2 != null)
|
||||
.ToDictionary(x => x.GuildId, x => x.Item2));
|
||||
RepeaterReady = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
195
NadekoBot.Core/Modules/Utility/Services/PatreonRewardsService.cs
Normal file
195
NadekoBot.Core/Modules/Utility/Services/PatreonRewardsService.cs
Normal file
@ -0,0 +1,195 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Modules.Utility.Common.Patreon;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Common.Caching;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services
|
||||
{
|
||||
public class PatreonRewardsService : INService, IUnloadableService
|
||||
{
|
||||
private readonly SemaphoreSlim getPledgesLocker = new SemaphoreSlim(1, 1);
|
||||
|
||||
private readonly FactoryCache<PatreonUserAndReward[]> _pledges;
|
||||
public PatreonUserAndReward[] Pledges => _pledges.GetValue();
|
||||
|
||||
public readonly Timer Updater;
|
||||
private readonly SemaphoreSlim claimLockJustInCase = new SemaphoreSlim(1, 1);
|
||||
private readonly Logger _log;
|
||||
|
||||
public readonly TimeSpan Interval = TimeSpan.FromMinutes(3);
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly DbService _db;
|
||||
private readonly CurrencyService _currency;
|
||||
private readonly IDataCache _cache;
|
||||
private readonly string _key;
|
||||
|
||||
public DateTime LastUpdate { get; private set; } = DateTime.UtcNow;
|
||||
|
||||
public PatreonRewardsService(IBotCredentials creds, DbService db,
|
||||
CurrencyService currency,
|
||||
DiscordSocketClient client, IDataCache cache)
|
||||
{
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
_creds = creds;
|
||||
_db = db;
|
||||
_currency = currency;
|
||||
_cache = cache;
|
||||
_key = _creds.RedisKey() + "_patreon_rewards";
|
||||
|
||||
_pledges = new FactoryCache<PatreonUserAndReward[]>(() =>
|
||||
{
|
||||
var r = _cache.Redis.GetDatabase();
|
||||
var data = r.StringGet(_key);
|
||||
if (data.IsNullOrEmpty)
|
||||
return null;
|
||||
else
|
||||
{
|
||||
return JsonConvert.DeserializeObject<PatreonUserAndReward[]>(data);
|
||||
}
|
||||
}, TimeSpan.FromSeconds(20));
|
||||
|
||||
if(client.ShardId == 0)
|
||||
Updater = new Timer(async _ => await RefreshPledges(),
|
||||
null, TimeSpan.Zero, Interval);
|
||||
}
|
||||
|
||||
public async Task RefreshPledges()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken))
|
||||
return;
|
||||
|
||||
LastUpdate = DateTime.UtcNow;
|
||||
await getPledgesLocker.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var rewards = new List<PatreonPledge>();
|
||||
var users = new List<PatreonUser>();
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
http.DefaultRequestHeaders.Clear();
|
||||
http.DefaultRequestHeaders.Add("Authorization", "Bearer " + _creds.PatreonAccessToken);
|
||||
var data = new PatreonData()
|
||||
{
|
||||
Links = new PatreonDataLinks()
|
||||
{
|
||||
next = $"https://api.patreon.com/oauth2/api/campaigns/{_creds.PatreonCampaignId}/pledges"
|
||||
}
|
||||
};
|
||||
do
|
||||
{
|
||||
var res = await http.GetStringAsync(data.Links.next)
|
||||
.ConfigureAwait(false);
|
||||
data = JsonConvert.DeserializeObject<PatreonData>(res);
|
||||
var pledgers = data.Data.Where(x => x["type"].ToString() == "pledge");
|
||||
rewards.AddRange(pledgers.Select(x => JsonConvert.DeserializeObject<PatreonPledge>(x.ToString()))
|
||||
.Where(x => x.attributes.declined_since == null));
|
||||
users.AddRange(data.Included
|
||||
.Where(x => x["type"].ToString() == "user")
|
||||
.Select(x => JsonConvert.DeserializeObject<PatreonUser>(x.ToString())));
|
||||
} while (!string.IsNullOrWhiteSpace(data.Links.next));
|
||||
}
|
||||
var db = _cache.Redis.GetDatabase();
|
||||
var toSet = JsonConvert.SerializeObject(rewards.Join(users, (r) => r.relationships?.patron?.data?.id, (u) => u.id, (x, y) => new PatreonUserAndReward()
|
||||
{
|
||||
User = y,
|
||||
Reward = x,
|
||||
}).ToArray());
|
||||
|
||||
db.StringSet(_key, toSet);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
getPledgesLocker.Release();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task<int> ClaimReward(ulong userId)
|
||||
{
|
||||
await claimLockJustInCase.WaitAsync();
|
||||
var now = DateTime.UtcNow;
|
||||
try
|
||||
{
|
||||
var data = Pledges?.FirstOrDefault(x => x.User.attributes?.social_connections?.discord?.user_id == userId.ToString());
|
||||
|
||||
if (data == null)
|
||||
return 0;
|
||||
|
||||
var amount = data.Reward.attributes.amount_cents;
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var users = uow._context.Set<RewardedUser>();
|
||||
var usr = users.FirstOrDefault(x => x.PatreonUserId == data.User.id);
|
||||
|
||||
if (usr == null)
|
||||
{
|
||||
users.Add(new RewardedUser()
|
||||
{
|
||||
UserId = userId,
|
||||
PatreonUserId = data.User.id,
|
||||
LastReward = now,
|
||||
AmountRewardedThisMonth = amount,
|
||||
});
|
||||
|
||||
await _currency.AddAsync(userId, "Patreon reward - new", amount, uow).ConfigureAwait(false);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
return amount;
|
||||
}
|
||||
|
||||
if (usr.LastReward.Month != now.Month)
|
||||
{
|
||||
usr.LastReward = now;
|
||||
usr.AmountRewardedThisMonth = amount;
|
||||
usr.PatreonUserId = data.User.id;
|
||||
|
||||
await _currency.AddAsync(userId, "Patreon reward - recurring", amount, uow).ConfigureAwait(false);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
return amount;
|
||||
}
|
||||
|
||||
if (usr.AmountRewardedThisMonth < amount)
|
||||
{
|
||||
var toAward = amount - usr.AmountRewardedThisMonth;
|
||||
|
||||
usr.LastReward = now;
|
||||
usr.AmountRewardedThisMonth = amount;
|
||||
usr.PatreonUserId = data.User.id;
|
||||
|
||||
await _currency.AddAsync(usr.UserId, "Patreon reward - update", toAward, uow).ConfigureAwait(false);
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
return toAward;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
claimLockJustInCase.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public Task Unload()
|
||||
{
|
||||
Updater?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
105
NadekoBot.Core/Modules/Utility/Services/RemindService.cs
Normal file
105
NadekoBot.Core/Modules/Utility/Services/RemindService.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Common.Replacements;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using NadekoBot.Core.Services.Impl;
|
||||
using NLog;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services
|
||||
{
|
||||
public class RemindService : INService
|
||||
{
|
||||
public readonly Regex Regex = new Regex(@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d)w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,2})h)?(?:(?<minutes>\d{1,2})m)?$",
|
||||
RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
|
||||
public string RemindMessageFormat { get; }
|
||||
|
||||
private readonly Logger _log;
|
||||
private readonly CancellationTokenSource cancelSource;
|
||||
private readonly CancellationToken cancelAllToken;
|
||||
private readonly IBotConfigProvider _config;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly DbService _db;
|
||||
|
||||
public RemindService(DiscordSocketClient client,
|
||||
IBotConfigProvider config,
|
||||
DbService db,
|
||||
StartingGuildsService guilds)
|
||||
{
|
||||
_config = config;
|
||||
_client = client;
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
_db = db;
|
||||
|
||||
cancelSource = new CancellationTokenSource();
|
||||
cancelAllToken = cancelSource.Token;
|
||||
|
||||
List<Reminder> reminders;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
reminders = uow.Reminders.GetIncludedReminders(guilds).ToList();
|
||||
}
|
||||
RemindMessageFormat = _config.BotConfig.RemindMessageFormat;
|
||||
|
||||
foreach (var r in reminders)
|
||||
{
|
||||
Task.Run(() => StartReminder(r));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartReminder(Reminder r)
|
||||
{
|
||||
var t = cancelAllToken;
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var time = r.When - now;
|
||||
|
||||
if (time.TotalMilliseconds > int.MaxValue)
|
||||
return;
|
||||
|
||||
await Task.Delay(time, t).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
IMessageChannel ch;
|
||||
if (r.IsPrivate)
|
||||
{
|
||||
var user = _client.GetGuild(r.ServerId).GetUser(r.ChannelId);
|
||||
if (user == null)
|
||||
return;
|
||||
ch = await user.GetOrCreateDMChannelAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ch = _client.GetGuild(r.ServerId)?.GetTextChannel(r.ChannelId);
|
||||
}
|
||||
if (ch == null)
|
||||
return;
|
||||
|
||||
var rep = new ReplacementBuilder()
|
||||
.WithOverride("%user%", () => $"<@!{r.UserId}>")
|
||||
.WithOverride("%message%", () => r.Message)
|
||||
.WithOverride("%target%", () => r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>")
|
||||
.Build();
|
||||
|
||||
await ch.SendMessageAsync(rep.Replace(RemindMessageFormat).SanitizeMentions()).ConfigureAwait(false); //it works trust me
|
||||
}
|
||||
catch (Exception ex) { _log.Warn(ex); }
|
||||
finally
|
||||
{
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
uow.Reminders.Remove(r);
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
305
NadekoBot.Core/Modules/Utility/Services/StreamRoleService.cs
Normal file
305
NadekoBot.Core/Modules/Utility/Services/StreamRoleService.cs
Normal file
@ -0,0 +1,305 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
using NLog;
|
||||
using NadekoBot.Modules.Utility.Extensions;
|
||||
using NadekoBot.Common.TypeReaders;
|
||||
using NadekoBot.Modules.Utility.Common;
|
||||
using NadekoBot.Modules.Utility.Common.Exceptions;
|
||||
using Discord.Net;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services
|
||||
{
|
||||
public class StreamRoleService : INService, IUnloadableService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ConcurrentDictionary<ulong, StreamRoleSettings> guildSettings;
|
||||
private readonly Logger _log;
|
||||
|
||||
public StreamRoleService(DiscordSocketClient client, DbService db, NadekoBot bot)
|
||||
{
|
||||
this._log = LogManager.GetCurrentClassLogger();
|
||||
this._db = db;
|
||||
this._client = client;
|
||||
|
||||
guildSettings = bot.AllGuildConfigs
|
||||
.ToDictionary(x => x.GuildId, x => x.StreamRole)
|
||||
.Where(x => x.Value != null && x.Value.Enabled)
|
||||
.ToConcurrent();
|
||||
|
||||
_client.GuildMemberUpdated += Client_GuildMemberUpdated;
|
||||
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(client.Guilds.Select(g => RescanUsers(g))).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Task Unload()
|
||||
{
|
||||
_client.GuildMemberUpdated -= Client_GuildMemberUpdated;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Client_GuildMemberUpdated(SocketGuildUser before, SocketGuildUser after)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
//if user wasn't streaming or didn't have a game status at all
|
||||
if (guildSettings.TryGetValue(after.Guild.Id, out var setting))
|
||||
{
|
||||
await RescanUser(after, setting).ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds or removes a user from a blacklist or a whitelist in the specified guild.
|
||||
/// </summary>
|
||||
/// <param name="guild">Guild</param>
|
||||
/// <param name="action">Add or rem action</param>
|
||||
/// <param name="userId">User's Id</param>
|
||||
/// <param name="userName">User's name#discrim</param>
|
||||
/// <returns>Whether the operation was successful</returns>
|
||||
public async Task<bool> ApplyListAction(StreamRoleListType listType, IGuild guild, AddRemove action, ulong userId, string userName)
|
||||
{
|
||||
userName.ThrowIfNull(nameof(userName));
|
||||
|
||||
bool success;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var streamRoleSettings = uow.GuildConfigs.GetStreamRoleSettings(guild.Id);
|
||||
|
||||
if (listType == StreamRoleListType.Whitelist)
|
||||
{
|
||||
var userObj = new StreamRoleWhitelistedUser()
|
||||
{
|
||||
UserId = userId,
|
||||
Username = userName,
|
||||
};
|
||||
|
||||
if (action == AddRemove.Rem)
|
||||
success = streamRoleSettings.Whitelist.Remove(userObj);
|
||||
else
|
||||
success = streamRoleSettings.Whitelist.Add(userObj);
|
||||
}
|
||||
else
|
||||
{
|
||||
var userObj = new StreamRoleBlacklistedUser()
|
||||
{
|
||||
UserId = userId,
|
||||
Username = userName,
|
||||
};
|
||||
|
||||
if (action == AddRemove.Rem)
|
||||
success = streamRoleSettings.Blacklist.Remove(userObj);
|
||||
else
|
||||
success = streamRoleSettings.Blacklist.Add(userObj);
|
||||
}
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
UpdateCache(guild.Id, streamRoleSettings);
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
await RescanUsers(guild).ConfigureAwait(false);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets keyword on a guild and updates the cache.
|
||||
/// </summary>
|
||||
/// <param name="guild">Guild Id</param>
|
||||
/// <param name="keyword">Keyword to set</param>
|
||||
/// <returns>The keyword set</returns>
|
||||
public async Task<string> SetKeyword(IGuild guild, string keyword)
|
||||
{
|
||||
keyword = keyword?.Trim()?.ToLowerInvariant();
|
||||
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var streamRoleSettings = uow.GuildConfigs.GetStreamRoleSettings(guild.Id);
|
||||
|
||||
streamRoleSettings.Keyword = keyword;
|
||||
UpdateCache(guild.Id, streamRoleSettings);
|
||||
uow.Complete();
|
||||
}
|
||||
|
||||
await RescanUsers(guild).ConfigureAwait(false);
|
||||
return keyword;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently set keyword on a guild.
|
||||
/// </summary>
|
||||
/// <param name="guildId">Guild Id</param>
|
||||
/// <returns>The keyword set</returns>
|
||||
public string GetKeyword(ulong guildId)
|
||||
{
|
||||
if (guildSettings.TryGetValue(guildId, out var outSetting))
|
||||
return outSetting.Keyword;
|
||||
|
||||
StreamRoleSettings setting;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
setting = uow.GuildConfigs.GetStreamRoleSettings(guildId);
|
||||
}
|
||||
|
||||
UpdateCache(guildId, setting);
|
||||
|
||||
return setting.Keyword;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the role to monitor, and a role to which to add to
|
||||
/// the user who starts streaming in the monitored role.
|
||||
/// </summary>
|
||||
/// <param name="fromRole">Role to monitor</param>
|
||||
/// <param name="addRole">Role to add to the user</param>
|
||||
public async Task SetStreamRole(IRole fromRole, IRole addRole)
|
||||
{
|
||||
fromRole.ThrowIfNull(nameof(fromRole));
|
||||
addRole.ThrowIfNull(nameof(addRole));
|
||||
|
||||
StreamRoleSettings setting;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var streamRoleSettings = uow.GuildConfigs.GetStreamRoleSettings(fromRole.Guild.Id);
|
||||
|
||||
streamRoleSettings.Enabled = true;
|
||||
streamRoleSettings.AddRoleId = addRole.Id;
|
||||
streamRoleSettings.FromRoleId = fromRole.Id;
|
||||
|
||||
setting = streamRoleSettings;
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
UpdateCache(fromRole.Guild.Id, setting);
|
||||
|
||||
foreach (var usr in await fromRole.GetMembersAsync())
|
||||
{
|
||||
if (usr is IGuildUser x)
|
||||
await RescanUser(x, setting, addRole).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the stream role feature on the specified guild.
|
||||
/// </summary>
|
||||
/// <param name="guildId">Guild's Id</param>
|
||||
public async Task StopStreamRole(IGuild guild, bool cleanup = false)
|
||||
{
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var streamRoleSettings = uow.GuildConfigs.GetStreamRoleSettings(guild.Id);
|
||||
streamRoleSettings.Enabled = false;
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (guildSettings.TryRemove(guild.Id, out var setting) && cleanup)
|
||||
await RescanUsers(guild).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task RescanUser(IGuildUser user, StreamRoleSettings setting, IRole addRole = null)
|
||||
{
|
||||
if (user.Game.HasValue &&
|
||||
user.Game.Value.StreamType != StreamType.NotStreaming
|
||||
&& setting.Enabled
|
||||
&& !setting.Blacklist.Any(x => x.UserId == user.Id)
|
||||
&& user.RoleIds.Contains(setting.FromRoleId)
|
||||
&& (string.IsNullOrWhiteSpace(setting.Keyword)
|
||||
|| user.Game.Value.Name.ToLowerInvariant().Contains(setting.Keyword.ToLowerInvariant())
|
||||
|| setting.Whitelist.Any(x => x.UserId == user.Id)))
|
||||
{
|
||||
try
|
||||
{
|
||||
addRole = addRole ?? user.Guild.GetRole(setting.AddRoleId);
|
||||
if (addRole == null)
|
||||
throw new StreamRoleNotFoundException();
|
||||
|
||||
//check if he doesn't have addrole already, to avoid errors
|
||||
if (!user.RoleIds.Contains(setting.AddRoleId))
|
||||
await user.AddRoleAsync(addRole).ConfigureAwait(false);
|
||||
_log.Info("Added stream role to user {0} in {1} server", user.ToString(), user.Guild.ToString());
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
|
||||
{
|
||||
await StopStreamRole(user.Guild).ConfigureAwait(false);
|
||||
_log.Warn("Error adding stream role(s). Forcibly disabling stream role feature.");
|
||||
_log.Error(ex);
|
||||
throw new StreamRolePermissionException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Warn("Failed adding stream role.");
|
||||
_log.Error(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//check if user is in the addrole
|
||||
if (user.RoleIds.Contains(setting.AddRoleId))
|
||||
{
|
||||
try
|
||||
{
|
||||
addRole = addRole ?? user.Guild.GetRole(setting.AddRoleId);
|
||||
if (addRole == null)
|
||||
throw new StreamRoleNotFoundException();
|
||||
|
||||
await user.RemoveRoleAsync(addRole).ConfigureAwait(false);
|
||||
_log.Info("Removed stream role from the user {0} in {1} server", user.ToString(), user.Guild.ToString());
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
|
||||
{
|
||||
await StopStreamRole(user.Guild).ConfigureAwait(false);
|
||||
_log.Warn("Error removing stream role(s). Forcibly disabling stream role feature.");
|
||||
_log.Error(ex);
|
||||
throw new StreamRolePermissionException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RescanUsers(IGuild guild)
|
||||
{
|
||||
if (!guildSettings.TryGetValue(guild.Id, out var setting))
|
||||
return;
|
||||
|
||||
var addRole = guild.GetRole(setting.AddRoleId);
|
||||
if (addRole == null)
|
||||
return;
|
||||
|
||||
if (setting.Enabled)
|
||||
{
|
||||
var users = await guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false);
|
||||
foreach (var usr in users.Where(x => x.RoleIds.Contains(setting.FromRoleId) || x.RoleIds.Contains(addRole.Id)))
|
||||
{
|
||||
if(usr is IGuildUser x)
|
||||
await RescanUser(x, setting, addRole).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCache(ulong guildId, StreamRoleSettings setting)
|
||||
{
|
||||
guildSettings.AddOrUpdate(guildId, (key) => setting, (key, old) => setting);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Modules.Help.Services;
|
||||
using NadekoBot.Core.Services;
|
||||
using NadekoBot.Core.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services
|
||||
{
|
||||
public class VerboseErrorsService : INService, IUnloadableService
|
||||
{
|
||||
private readonly ConcurrentHashSet<ulong> guildsEnabled;
|
||||
private readonly DbService _db;
|
||||
private readonly CommandHandler _ch;
|
||||
private readonly HelpService _hs;
|
||||
|
||||
public VerboseErrorsService(NadekoBot bot, DbService db, CommandHandler ch, HelpService hs)
|
||||
{
|
||||
_db = db;
|
||||
_ch = ch;
|
||||
_hs = hs;
|
||||
|
||||
_ch.CommandErrored += LogVerboseError;
|
||||
|
||||
guildsEnabled = new ConcurrentHashSet<ulong>(bot
|
||||
.AllGuildConfigs
|
||||
.Where(x => x.VerboseErrors)
|
||||
.Select(x => x.GuildId));
|
||||
}
|
||||
|
||||
public Task Unload()
|
||||
{
|
||||
_ch.CommandErrored -= LogVerboseError;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task LogVerboseError(CommandInfo cmd, ITextChannel channel, string reason)
|
||||
{
|
||||
if (channel == null || !guildsEnabled.Contains(channel.GuildId))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var embed = _hs.GetCommandHelp(cmd, channel.Guild)
|
||||
.WithTitle("Command Error")
|
||||
.WithDescription(reason)
|
||||
.WithErrorColor();
|
||||
|
||||
await channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
|
||||
public bool ToggleVerboseErrors(ulong guildId)
|
||||
{
|
||||
bool enabled;
|
||||
using (var uow = _db.UnitOfWork)
|
||||
{
|
||||
var gc = uow.GuildConfigs.For(guildId, set => set);
|
||||
|
||||
enabled = gc.VerboseErrors = !gc.VerboseErrors;
|
||||
|
||||
uow.Complete();
|
||||
|
||||
if (gc.VerboseErrors)
|
||||
guildsEnabled.Add(guildId);
|
||||
else
|
||||
guildsEnabled.TryRemove(guildId);
|
||||
}
|
||||
|
||||
if (enabled)
|
||||
guildsEnabled.Add(guildId);
|
||||
else
|
||||
guildsEnabled.TryRemove(guildId);
|
||||
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
}
|
94
NadekoBot.Core/Modules/Utility/StreamRoleCommands.cs
Normal file
94
NadekoBot.Core/Modules/Utility/StreamRoleCommands.cs
Normal file
@ -0,0 +1,94 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
using NadekoBot.Common.TypeReaders;
|
||||
using NadekoBot.Modules.Utility.Common;
|
||||
using NadekoBot.Common;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public partial class Utility
|
||||
{
|
||||
public class StreamRoleCommands : NadekoSubmodule<StreamRoleService>
|
||||
{
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireBotPermission(GuildPermission.ManageRoles)]
|
||||
[RequireUserPermission(GuildPermission.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamRole(IRole fromRole, IRole addRole)
|
||||
{
|
||||
await this._service.SetStreamRole(fromRole, addRole).ConfigureAwait(false);
|
||||
|
||||
await ReplyConfirmLocalized("stream_role_enabled", Format.Bold(fromRole.ToString()), Format.Bold(addRole.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireBotPermission(GuildPermission.ManageRoles)]
|
||||
[RequireUserPermission(GuildPermission.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamRole()
|
||||
{
|
||||
await this._service.StopStreamRole(Context.Guild).ConfigureAwait(false);
|
||||
await ReplyConfirmLocalized("stream_role_disabled").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireBotPermission(GuildPermission.ManageRoles)]
|
||||
[RequireUserPermission(GuildPermission.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamRoleKeyword([Remainder]string keyword = null)
|
||||
{
|
||||
string kw = await this._service.SetKeyword(Context.Guild, keyword).ConfigureAwait(false);
|
||||
|
||||
if(string.IsNullOrWhiteSpace(keyword))
|
||||
await ReplyConfirmLocalized("stream_role_kw_reset").ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalized("stream_role_kw_set", Format.Bold(kw)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireBotPermission(GuildPermission.ManageRoles)]
|
||||
[RequireUserPermission(GuildPermission.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamRoleBlacklist(AddRemove action, [Remainder] IGuildUser user)
|
||||
{
|
||||
var success = await this._service.ApplyListAction(StreamRoleListType.Blacklist, Context.Guild, action, user.Id, user.ToString())
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if(action == AddRemove.Add)
|
||||
if(success)
|
||||
await ReplyConfirmLocalized("stream_role_bl_add", Format.Bold(user.ToString())).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalized("stream_role_bl_add_fail", Format.Bold(user.ToString())).ConfigureAwait(false);
|
||||
else
|
||||
if (success)
|
||||
await ReplyConfirmLocalized("stream_role_bl_rem", Format.Bold(user.ToString())).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyErrorLocalized("stream_role_bl_rem_fail", Format.Bold(user.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireBotPermission(GuildPermission.ManageRoles)]
|
||||
[RequireUserPermission(GuildPermission.ManageRoles)]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task StreamRoleWhitelist(AddRemove action, [Remainder] IGuildUser user)
|
||||
{
|
||||
var success = await this._service.ApplyListAction(StreamRoleListType.Whitelist, Context.Guild, action, user.Id, user.ToString())
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (action == AddRemove.Add)
|
||||
if(success)
|
||||
await ReplyConfirmLocalized("stream_role_wl_add", Format.Bold(user.ToString())).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalized("stream_role_wl_add_fail", Format.Bold(user.ToString())).ConfigureAwait(false);
|
||||
else
|
||||
if (success)
|
||||
await ReplyConfirmLocalized("stream_role_wl_rem", Format.Bold(user.ToString())).ConfigureAwait(false);
|
||||
else
|
||||
await ReplyErrorLocalized("stream_role_wl_rem_fail", Format.Bold(user.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
NadekoBot.Core/Modules/Utility/UnitConversionCommands.cs
Normal file
87
NadekoBot.Core/Modules/Utility/UnitConversionCommands.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public partial class Utility
|
||||
{
|
||||
[Group]
|
||||
public class UnitConverterCommands : NadekoSubmodule<ConverterService>
|
||||
{
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ConvertList()
|
||||
{
|
||||
var res = _service.Units.GroupBy(x => x.UnitType)
|
||||
.Aggregate(new EmbedBuilder().WithTitle(GetText("convertlist"))
|
||||
.WithColor(NadekoBot.OkColor),
|
||||
(embed, g) => embed.AddField(efb =>
|
||||
efb.WithName(g.Key.ToTitleCase())
|
||||
.WithValue(String.Join(", ", g.Select(x => x.Triggers.FirstOrDefault())
|
||||
.OrderBy(x => x)))));
|
||||
await Context.Channel.EmbedAsync(res);
|
||||
}
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Convert(string origin, string target, decimal value)
|
||||
{
|
||||
var originUnit = _service.Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(origin.ToLowerInvariant()));
|
||||
var targetUnit = _service.Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(target.ToLowerInvariant()));
|
||||
if (originUnit == null || targetUnit == null)
|
||||
{
|
||||
await ReplyErrorLocalized("convert_not_found", Format.Bold(origin), Format.Bold(target)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
if (originUnit.UnitType != targetUnit.UnitType)
|
||||
{
|
||||
await ReplyErrorLocalized("convert_type_error", Format.Bold(originUnit.Triggers.First()), Format.Bold(targetUnit.Triggers.First())).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
decimal res;
|
||||
if (originUnit.Triggers == targetUnit.Triggers) res = value;
|
||||
else if (originUnit.UnitType == "temperature")
|
||||
{
|
||||
//don't really care too much about efficiency, so just convert to Kelvin, then to target
|
||||
switch (originUnit.Triggers.First().ToUpperInvariant())
|
||||
{
|
||||
case "C":
|
||||
res = value + 273.15m; //celcius!
|
||||
break;
|
||||
case "F":
|
||||
res = (value + 459.67m) * (5m / 9m);
|
||||
break;
|
||||
default:
|
||||
res = value;
|
||||
break;
|
||||
}
|
||||
//from Kelvin to target
|
||||
switch (targetUnit.Triggers.First().ToUpperInvariant())
|
||||
{
|
||||
case "C":
|
||||
res = res - 273.15m; //celcius!
|
||||
break;
|
||||
case "F":
|
||||
res = res * (9m / 5m) - 459.67m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (originUnit.UnitType == "currency")
|
||||
{
|
||||
res = (value * targetUnit.Modifier) / originUnit.Modifier;
|
||||
}
|
||||
else
|
||||
res = (value * originUnit.Modifier) / targetUnit.Modifier;
|
||||
}
|
||||
res = Math.Round(res, 4);
|
||||
|
||||
await Context.Channel.SendConfirmAsync(GetText("convert", value, (originUnit.Triggers.First()).SnPl(value.IsInteger() ? (int)value : 2), res, (targetUnit.Triggers.First() + "s").SnPl(res.IsInteger() ? (int)res : 2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
322
NadekoBot.Core/Modules/Utility/Utility.cs
Normal file
322
NadekoBot.Core/Modules/Utility/Utility.cs
Normal file
@ -0,0 +1,322 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Core.Services.Impl;
|
||||
using System.Net.Http;
|
||||
using ImageSharp;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Discord.WebSocket;
|
||||
using System.Diagnostics;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Core.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public partial class Utility : NadekoTopLevelModule
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IStatsService _stats;
|
||||
private readonly IBotCredentials _creds;
|
||||
private readonly NadekoBot _bot;
|
||||
|
||||
public Utility(NadekoBot nadeko, DiscordSocketClient client, IStatsService stats, IBotCredentials creds)
|
||||
{
|
||||
_client = client;
|
||||
_stats = stats;
|
||||
_creds = creds;
|
||||
_bot = nadeko;
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task TogetherTube()
|
||||
{
|
||||
Uri target;
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var res = await http.GetAsync("https://togethertube.com/room/create").ConfigureAwait(false);
|
||||
target = res.RequestMessage.RequestUri;
|
||||
}
|
||||
|
||||
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithAuthor(eab => eab.WithIconUrl("https://togethertube.com/assets/img/favicons/favicon-32x32.png")
|
||||
.WithName("Together Tube")
|
||||
.WithUrl("https://togethertube.com/"))
|
||||
.WithDescription(Context.User.Mention + " " + GetText("togtub_room_link") + "\n" + target));
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task WhosPlaying([Remainder] string game)
|
||||
{
|
||||
game = game?.Trim().ToUpperInvariant();
|
||||
if (string.IsNullOrWhiteSpace(game))
|
||||
return;
|
||||
|
||||
var socketGuild = Context.Guild as SocketGuild;
|
||||
if (socketGuild == null)
|
||||
{
|
||||
_log.Warn("Can't cast guild to socket guild.");
|
||||
return;
|
||||
}
|
||||
var rng = new NadekoRandom();
|
||||
var arr = await Task.Run(() => socketGuild.Users
|
||||
.Where(u => u.Game?.Name?.ToUpperInvariant() == game)
|
||||
.Select(u => u.Username)
|
||||
.OrderBy(x => rng.Next())
|
||||
.Take(60)
|
||||
.ToArray()).ConfigureAwait(false);
|
||||
|
||||
int i = 0;
|
||||
if (arr.Length == 0)
|
||||
await ReplyErrorLocalized("nobody_playing_game").ConfigureAwait(false);
|
||||
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]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task InRole([Remainder] IRole role)
|
||||
{
|
||||
var rng = new NadekoRandom();
|
||||
var usrs = (await Context.Guild.GetUsersAsync()).ToArray();
|
||||
var roleUsers = usrs.Where(u => u.RoleIds.Contains(role.Id)).Select(u => u.ToString())
|
||||
.ToArray();
|
||||
var inroleusers = string.Join(", ", roleUsers
|
||||
.OrderBy(x => rng.Next())
|
||||
.Take(50));
|
||||
var embed = new EmbedBuilder().WithOkColor()
|
||||
.WithTitle("ℹ️ " + Format.Bold(GetText("inrole_list", Format.Bold(role.Name))) + $" - {roleUsers.Length}")
|
||||
.WithDescription($"```css\n[{role.Name}]\n{inroleusers}```");
|
||||
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task CheckMyPerms()
|
||||
{
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
var user = (IGuildUser) Context.User;
|
||||
var perms = user.GetPermissions((ITextChannel)Context.Channel);
|
||||
foreach (var p in perms.GetType().GetProperties().Where(p => !p.GetGetMethod().GetParameters().Any()))
|
||||
{
|
||||
builder.AppendLine($"{p.Name} : {p.GetValue(perms, null)}");
|
||||
}
|
||||
await Context.Channel.SendConfirmAsync(builder.ToString());
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task UserId([Remainder] IGuildUser target = null)
|
||||
{
|
||||
var usr = target ?? Context.User;
|
||||
await ReplyConfirmLocalized("userid", "🆔", Format.Bold(usr.ToString()),
|
||||
Format.Code(usr.Id.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task ChannelId()
|
||||
{
|
||||
await ReplyConfirmLocalized("channelid", "🆔", Format.Code(Context.Channel.Id.ToString()))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ServerId()
|
||||
{
|
||||
await ReplyConfirmLocalized("serverid", "🆔", Format.Code(Context.Guild.Id.ToString()))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Roles(IGuildUser target, int page = 1)
|
||||
{
|
||||
var channel = (ITextChannel)Context.Channel;
|
||||
var guild = channel.Guild;
|
||||
|
||||
const int rolesPerPage = 20;
|
||||
|
||||
if (page < 1 || page > 100)
|
||||
return;
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
var roles = target.GetRoles().Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * rolesPerPage).Take(rolesPerPage).ToArray();
|
||||
if (!roles.Any())
|
||||
{
|
||||
await ReplyErrorLocalized("no_roles_on_page").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
await channel.SendConfirmAsync(GetText("roles_page", page, Format.Bold(target.ToString())),
|
||||
"\n• " + string.Join("\n• ", (IEnumerable<IRole>)roles).SanitizeMentions()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var roles = guild.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * rolesPerPage).Take(rolesPerPage).ToArray();
|
||||
if (!roles.Any())
|
||||
{
|
||||
await ReplyErrorLocalized("no_roles_on_page").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await channel.SendConfirmAsync(GetText("roles_all_page", page),
|
||||
"\n• " + string.Join("\n• ", (IEnumerable<IRole>)roles).SanitizeMentions()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Roles(int page = 1) =>
|
||||
Roles(null, page);
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task ChannelTopic([Remainder]ITextChannel channel = null)
|
||||
{
|
||||
if (channel == null)
|
||||
channel = (ITextChannel)Context.Channel;
|
||||
|
||||
var topic = channel.Topic;
|
||||
if (string.IsNullOrWhiteSpace(topic))
|
||||
await ReplyErrorLocalized("no_topic_set").ConfigureAwait(false);
|
||||
else
|
||||
await Context.Channel.SendConfirmAsync(GetText("channel_topic"), topic).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireBotPermission(ChannelPermission.CreateInstantInvite)]
|
||||
[RequireUserPermission(ChannelPermission.CreateInstantInvite)]
|
||||
public async Task CreateInvite()
|
||||
{
|
||||
var invite = await ((ITextChannel)Context.Channel).CreateInviteAsync(0, null, isUnique: true);
|
||||
|
||||
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} https://discord.gg/{invite.Code}");
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Stats()
|
||||
{
|
||||
await Context.Channel.EmbedAsync(
|
||||
new EmbedBuilder().WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName($"NadekoBot v{StatsService.BotVersion}")
|
||||
.WithUrl("http://nadekobot.readthedocs.io/en/latest/")
|
||||
.WithIconUrl("https://cdn.discordapp.com/avatars/116275390695079945/b21045e778ef21c96d175400e779f0fb.jpg"))
|
||||
.AddField(efb => efb.WithName(GetText("author")).WithValue(_stats.Author).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("botid")).WithValue(_client.CurrentUser.Id.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{_client.ShardId} / {_creds.TotalShards}").WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("commands_ran")).WithValue(_stats.CommandsRan.ToString()).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("messages")).WithValue($"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)").WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("memory")).WithValue($"{_stats.Heap} MB").WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("owner_ids")).WithValue(string.Join("\n", _creds.OwnerIds)).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("uptime")).WithValue(_stats.GetUptimeString("\n")).WithIsInline(true))
|
||||
.AddField(efb => efb.WithName(GetText("presence")).WithValue(
|
||||
GetText("presence_txt",
|
||||
_bot.GuildCount, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true)));
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Showemojis([Remainder] string emojis)
|
||||
{
|
||||
var tags = Context.Message.Tags.Where(t => t.Type == TagType.Emoji).Select(t => (Emote)t.Value);
|
||||
|
||||
var result = string.Join("\n", tags.Select(m => GetText("showemojis", m, m.Url)));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(result))
|
||||
await ReplyErrorLocalized("showemojis_none").ConfigureAwait(false);
|
||||
else
|
||||
await Context.Channel.SendMessageAsync(result).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[OwnerOnly]
|
||||
public async Task ListServers(int page = 1)
|
||||
{
|
||||
page -= 1;
|
||||
|
||||
if (page < 0)
|
||||
return;
|
||||
|
||||
var guilds = await Task.Run(() => _client.Guilds.OrderBy(g => g.Name).Skip((page) * 15).Take(15)).ConfigureAwait(false);
|
||||
|
||||
if (!guilds.Any())
|
||||
{
|
||||
await ReplyErrorLocalized("listservers_none").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await Context.Channel.EmbedAsync(guilds.Aggregate(new EmbedBuilder().WithOkColor(),
|
||||
(embed, g) => embed.AddField(efb => efb.WithName(g.Name)
|
||||
.WithValue(
|
||||
GetText("listservers", g.Id, g.Users.Count,
|
||||
g.OwnerId))
|
||||
.WithIsInline(false))))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[OwnerOnly]
|
||||
public async Task SaveChat(int cnt)
|
||||
{
|
||||
var msgs = new List<IMessage>(cnt);
|
||||
await Context.Channel.GetMessagesAsync(cnt).ForEachAsync(dled => msgs.AddRange(dled)).ConfigureAwait(false);
|
||||
|
||||
var title = $"Chatlog-{Context.Guild.Name}/#{Context.Channel.Name}-{DateTime.Now}.txt";
|
||||
var grouping = msgs.GroupBy(x => $"{x.CreatedAt.Date:dd.MM.yyyy}")
|
||||
.Select(g => new
|
||||
{
|
||||
date = g.Key,
|
||||
messages = g.OrderBy(x => x.CreatedAt).Select(s =>
|
||||
{
|
||||
var msg = $"【{s.Timestamp:HH:mm:ss}】{s.Author}:";
|
||||
if (string.IsNullOrWhiteSpace(s.ToString()))
|
||||
{
|
||||
if (s.Attachments.Any())
|
||||
{
|
||||
msg += "FILES_UPLOADED: " + string.Join("\n", s.Attachments.Select(x => x.Url));
|
||||
}
|
||||
else if (s.Embeds.Any())
|
||||
{
|
||||
msg += "EMBEDS: " + string.Join("\n--------\n", s.Embeds.Select(x => $"Description: {x.Description}"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
msg += s.ToString();
|
||||
}
|
||||
return msg;
|
||||
})
|
||||
});
|
||||
await Context.User.SendFileAsync(
|
||||
await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream().ConfigureAwait(false), title, title, false).ConfigureAwait(false);
|
||||
}
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
public async Task Ping()
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
var msg = await Context.Channel.SendMessageAsync("🏓").ConfigureAwait(false);
|
||||
sw.Stop();
|
||||
msg.DeleteAfter(0);
|
||||
|
||||
await Context.Channel.SendConfirmAsync($"{Format.Bold(Context.User.ToString())} 🏓 {(int)sw.Elapsed.TotalMilliseconds}ms").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
27
NadekoBot.Core/Modules/Utility/VerboseErrorCommands.cs
Normal file
27
NadekoBot.Core/Modules/Utility/VerboseErrorCommands.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using Discord.Commands;
|
||||
using System.Threading.Tasks;
|
||||
using NadekoBot.Common.Attributes;
|
||||
using NadekoBot.Modules.Utility.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
public partial class Utility
|
||||
{
|
||||
[Group]
|
||||
public class VerboseErrorCommands : NadekoSubmodule<VerboseErrorsService>
|
||||
{
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[RequireUserPermission(Discord.GuildPermission.ManageMessages)]
|
||||
public async Task VerboseError()
|
||||
{
|
||||
var state = _service.ToggleVerboseErrors(Context.Guild.Id);
|
||||
|
||||
if (state)
|
||||
await ReplyConfirmLocalized("verbose_errors_enabled").ConfigureAwait(false);
|
||||
else
|
||||
await ReplyConfirmLocalized("verbose_errors_disabled").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user