Removed module projects because it can't work like that atm. Commented out package commands.
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user