music and clash of clans readded

This commit is contained in:
Master Kwoth 2017-05-24 06:43:00 +02:00
parent 2df415341c
commit c183e8ad58
22 changed files with 793 additions and 728 deletions

View File

@ -6,7 +6,7 @@ namespace NadekoBot.Attributes
[AttributeUsage(AttributeTargets.Class)]
sealed class NadekoModuleAttribute : GroupAttribute
{
public NadekoModuleAttribute(string moduleName) : base("")
public NadekoModuleAttribute(string moduleName) : base(moduleName)
{
}
}

View File

@ -15,6 +15,11 @@ namespace NadekoBot.TypeReaders
public override Task<TypeReaderResult> Read(ICommandContext context, string input)
{
input = input.ToUpperInvariant();
if (!input.StartsWith(NadekoBot.Prefix.ToUpperInvariant()))
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found."));
input = input.Substring(NadekoBot.Prefix.Length);
var cmd = _cmds.Commands.FirstOrDefault(c =>
c.Aliases.Select(a => a.ToUpperInvariant()).Contains(input));
if (cmd == null)

View File

@ -1,81 +1,23 @@
using Discord.Commands;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Discord;
using NadekoBot.Services;
using NadekoBot.Attributes;
using NadekoBot.Services.Database.Models;
using System.Linq;
using NadekoBot.Extensions;
using System.Threading;
using NadekoBot.Services.ClashOfClans;
namespace NadekoBot.Modules.ClashOfClans
{
[NadekoModule("ClashOfClans", ",")]
public class ClashOfClans : NadekoTopLevelModule
{
public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; }
private readonly ClashOfClansService _service;
private static Timer checkWarTimer { get; }
static ClashOfClans()
public ClashOfClans(ClashOfClansService service)
{
using (var uow = DbHandler.UnitOfWork())
{
ClashWars = new ConcurrentDictionary<ulong, List<ClashWar>>(
uow.ClashOfClans
.GetAllWars()
.Select(cw =>
{
cw.Channel = NadekoBot.Client.GetGuild(cw.GuildId)?
.GetTextChannel(cw.ChannelId);
return cw;
})
.Where(cw => cw.Channel != null)
.GroupBy(cw => cw.GuildId)
.ToDictionary(g => g.Key, g => g.ToList()));
}
checkWarTimer = new Timer(async _ =>
{
foreach (var kvp in ClashWars)
{
foreach (var war in kvp.Value)
{
try { await CheckWar(TimeSpan.FromHours(2), war).ConfigureAwait(false); } catch { }
}
}
}, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
private static async Task CheckWar(TimeSpan callExpire, ClashWar war)
{
var Bases = war.Bases;
for (var i = 0; i < Bases.Count; i++)
{
var callUser = Bases[i].CallUser;
if (callUser == null) continue;
if ((!Bases[i].BaseDestroyed) && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire)
{
if (Bases[i].Stars != 3)
Bases[i].BaseDestroyed = true;
else
Bases[i] = null;
try
{
SaveWar(war);
await war.Channel.SendErrorAsync(GetTextStatic("claim_expired",
NadekoBot.Localization.GetCultureInfo(war.Channel.GuildId),
typeof(ClashOfClans).Name.ToLowerInvariant(),
Format.Bold(Bases[i].CallUser),
war.ShortPrint()));
}
catch { }
}
}
_service = service;
}
[NadekoCommand, Usage, Description, Aliases]
@ -92,18 +34,18 @@ namespace NadekoBot.Modules.ClashOfClans
return;
}
List<ClashWar> wars;
if (!ClashWars.TryGetValue(Context.Guild.Id, out wars))
if (!_service.ClashWars.TryGetValue(Context.Guild.Id, out wars))
{
wars = new List<ClashWar>();
if (!ClashWars.TryAdd(Context.Guild.Id, wars))
if (!_service.ClashWars.TryAdd(Context.Guild.Id, wars))
return;
}
var cw = await CreateWar(enemyClan, size, Context.Guild.Id, Context.Channel.Id);
var cw = await _service.CreateWar(enemyClan, size, Context.Guild.Id, Context.Channel.Id);
wars.Add(cw);
await ReplyErrorLocalized("war_created", cw.ShortPrint()).ConfigureAwait(false);
await ReplyErrorLocalized("war_created", _service.ShortPrint(cw)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -113,7 +55,7 @@ namespace NadekoBot.Modules.ClashOfClans
int num = 0;
int.TryParse(number, out num);
var warsInfo = GetWarInfo(Context.Guild, num);
var warsInfo = _service.GetWarInfo(Context.Guild, num);
if (warsInfo == null)
{
await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
@ -123,13 +65,13 @@ namespace NadekoBot.Modules.ClashOfClans
try
{
war.Start();
await ReplyConfirmLocalized("war_started", war.ShortPrint()).ConfigureAwait(false);
await ReplyConfirmLocalized("war_started", _service.ShortPrint(war)).ConfigureAwait(false);
}
catch
{
await ReplyErrorLocalized("war_already_started", war.ShortPrint()).ConfigureAwait(false);
await ReplyErrorLocalized("war_already_started", _service.ShortPrint(war)).ConfigureAwait(false);
}
SaveWar(war);
_service.SaveWar(war);
}
[NadekoCommand, Usage, Description, Aliases]
@ -142,7 +84,7 @@ namespace NadekoBot.Modules.ClashOfClans
{
//check if there are any wars
List<ClashWar> wars = null;
ClashWars.TryGetValue(Context.Guild.Id, out wars);
_service.ClashWars.TryGetValue(Context.Guild.Id, out wars);
if (wars == null || wars.Count == 0)
{
await ReplyErrorLocalized("no_active_wars").ConfigureAwait(false);
@ -163,21 +105,21 @@ namespace NadekoBot.Modules.ClashOfClans
var num = 0;
int.TryParse(number, out num);
//if number is not null, print the war needed
var warsInfo = GetWarInfo(Context.Guild, num);
var warsInfo = _service.GetWarInfo(Context.Guild, num);
if (warsInfo == null)
{
await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
return;
}
var war = warsInfo.Item1[warsInfo.Item2];
await Context.Channel.SendConfirmAsync(war.Localize("info_about_war", $"`{war.EnemyClan}` ({war.Size} v {war.Size})"), war.ToPrettyString()).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync(_service.Localize(war, "info_about_war", $"`{war.EnemyClan}` ({war.Size} v {war.Size})"), _service.ToPrettyString(war)).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Claim(int number, int baseNumber, [Remainder] string other_name = null)
{
var warsInfo = GetWarInfo(Context.Guild, number);
var warsInfo = _service.GetWarInfo(Context.Guild, number);
if (warsInfo == null || warsInfo.Item1.Count == 0)
{
await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
@ -190,9 +132,9 @@ namespace NadekoBot.Modules.ClashOfClans
try
{
var war = warsInfo.Item1[warsInfo.Item2];
war.Call(usr, baseNumber - 1);
SaveWar(war);
await ConfirmLocalized("claimed_base", Format.Bold(usr.ToString()), baseNumber, war.ShortPrint()).ConfigureAwait(false);
_service.Call(war, usr, baseNumber - 1);
_service.SaveWar(war);
await ConfirmLocalized("claimed_base", Format.Bold(usr.ToString()), baseNumber, _service.ShortPrint(war)).ConfigureAwait(false);
}
catch (Exception ex)
{
@ -225,7 +167,7 @@ namespace NadekoBot.Modules.ClashOfClans
[RequireContext(ContextType.Guild)]
public async Task EndWar(int number)
{
var warsInfo = GetWarInfo(Context.Guild, number);
var warsInfo = _service.GetWarInfo(Context.Guild, number);
if (warsInfo == null)
{
await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
@ -233,8 +175,8 @@ namespace NadekoBot.Modules.ClashOfClans
}
var war = warsInfo.Item1[warsInfo.Item2];
war.End();
SaveWar(war);
await ReplyConfirmLocalized("war_ended", warsInfo.Item1[warsInfo.Item2].ShortPrint()).ConfigureAwait(false);
_service.SaveWar(war);
await ReplyConfirmLocalized("war_ended", _service.ShortPrint(warsInfo.Item1[warsInfo.Item2])).ConfigureAwait(false);
warsInfo.Item1.RemoveAt(warsInfo.Item2);
}
@ -243,7 +185,7 @@ namespace NadekoBot.Modules.ClashOfClans
[RequireContext(ContextType.Guild)]
public async Task Unclaim(int number, [Remainder] string otherName = null)
{
var warsInfo = GetWarInfo(Context.Guild, number);
var warsInfo = _service.GetWarInfo(Context.Guild, number);
if (warsInfo == null || warsInfo.Item1.Count == 0)
{
await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
@ -256,9 +198,9 @@ namespace NadekoBot.Modules.ClashOfClans
try
{
var war = warsInfo.Item1[warsInfo.Item2];
var baseNumber = war.Uncall(usr);
SaveWar(war);
await ReplyConfirmLocalized("base_unclaimed", usr, baseNumber + 1, war.ShortPrint()).ConfigureAwait(false);
var baseNumber = _service.Uncall(war, usr);
_service.SaveWar(war);
await ReplyConfirmLocalized("base_unclaimed", usr, baseNumber + 1, _service.ShortPrint(war)).ConfigureAwait(false);
}
catch (Exception ex)
{
@ -268,7 +210,7 @@ namespace NadekoBot.Modules.ClashOfClans
private async Task FinishClaim(int number, int baseNumber, int stars = 3)
{
var warInfo = GetWarInfo(Context.Guild, number);
var warInfo = _service.GetWarInfo(Context.Guild, number);
if (warInfo == null || warInfo.Item1.Count == 0)
{
await ReplyErrorLocalized("war_not_exist").ConfigureAwait(false);
@ -279,87 +221,19 @@ namespace NadekoBot.Modules.ClashOfClans
{
if (baseNumber == -1)
{
baseNumber = war.FinishClaim(Context.User.Username, stars);
SaveWar(war);
baseNumber = _service.FinishClaim(war, Context.User.Username, stars);
_service.SaveWar(war);
}
else
{
war.FinishClaim(baseNumber, stars);
_service.FinishClaim(war, baseNumber, stars);
}
await ReplyConfirmLocalized("base_destroyed", baseNumber +1, war.ShortPrint()).ConfigureAwait(false);
await ReplyConfirmLocalized("base_destroyed", baseNumber + 1, _service.ShortPrint(war)).ConfigureAwait(false);
}
catch (Exception ex)
{
await Context.Channel.SendErrorAsync($"🔰 {ex.Message}").ConfigureAwait(false);
}
}
private static Tuple<List<ClashWar>, int> GetWarInfo(IGuild guild, int num)
{
List<ClashWar> wars = null;
ClashWars.TryGetValue(guild.Id, out wars);
if (wars == null || wars.Count == 0)
{
return null;
}
// get the number of the war
else if (num < 1 || num > wars.Count)
{
return null;
}
num -= 1;
//get the actual war
return new Tuple<List<ClashWar>, int>(wars, num);
}
public static async Task<ClashWar> CreateWar(string enemyClan, int size, ulong serverId, ulong channelId)
{
var channel = NadekoBot.Client.GetGuild(serverId)?.GetTextChannel(channelId);
using (var uow = DbHandler.UnitOfWork())
{
var cw = new ClashWar
{
EnemyClan = enemyClan,
Size = size,
Bases = new List<ClashCaller>(size),
GuildId = serverId,
ChannelId = channelId,
Channel = channel,
};
cw.Bases.Capacity = size;
for (int i = 0; i < size; i++)
{
cw.Bases.Add(new ClashCaller()
{
CallUser = null,
SequenceNumber = i,
});
}
Console.WriteLine(cw.Bases.Capacity);
uow.ClashOfClans.Add(cw);
await uow.CompleteAsync();
return cw;
}
}
public static void SaveWar(ClashWar cw)
{
if (cw.WarState == ClashWar.StateOfWar.Ended)
{
using (var uow = DbHandler.UnitOfWork())
{
uow.ClashOfClans.Remove(cw);
uow.CompleteAsync();
}
return;
}
using (var uow = DbHandler.UnitOfWork())
{
uow.ClashOfClans.Update(cw);
uow.CompleteAsync();
}
}
}
}

View File

@ -1,148 +0,0 @@
using NadekoBot.Services.Database.Models;
using System;
using System.Linq;
using System.Text;
using static NadekoBot.Services.Database.Models.ClashWar;
namespace NadekoBot.Modules.ClashOfClans
{
public static class Extensions
{
public static void ResetTime(this ClashCaller c)
{
c.TimeAdded = DateTime.UtcNow;
}
public static void Destroy(this ClashCaller c)
{
c.BaseDestroyed = true;
}
public static void End(this ClashWar cw)
{
//Ended = true;
cw.WarState = StateOfWar.Ended;
}
public static void Call(this ClashWar cw, string u, int baseNumber)
{
if (baseNumber < 0 || baseNumber >= cw.Bases.Count)
throw new ArgumentException(cw.Localize("invalid_base_number"));
if (cw.Bases[baseNumber].CallUser != null && cw.Bases[baseNumber].Stars == 3)
throw new ArgumentException(cw.Localize("base_already_claimed"));
for (var i = 0; i < cw.Bases.Count; i++)
{
if (cw.Bases[i]?.BaseDestroyed == false && cw.Bases[i]?.CallUser == u)
throw new ArgumentException(cw.Localize("claimed_other", u, i + 1));
}
var cc = cw.Bases[baseNumber];
cc.CallUser = u.Trim();
cc.TimeAdded = DateTime.UtcNow;
cc.BaseDestroyed = false;
}
public static void Start(this ClashWar cw)
{
if (cw.WarState == StateOfWar.Started)
throw new InvalidOperationException("war_already_started");
//if (Started)
// throw new InvalidOperationException();
//Started = true;
cw.WarState = StateOfWar.Started;
cw.StartedAt = DateTime.UtcNow;
foreach (var b in cw.Bases.Where(b => b.CallUser != null))
{
b.ResetTime();
}
}
public static int Uncall(this ClashWar cw, string user)
{
user = user.Trim();
for (var i = 0; i < cw.Bases.Count; i++)
{
if (cw.Bases[i]?.CallUser != user) continue;
cw.Bases[i].CallUser = null;
return i;
}
throw new InvalidOperationException(cw.Localize("not_partic"));
}
public static string ShortPrint(this ClashWar cw) =>
$"`{cw.EnemyClan}` ({cw.Size} v {cw.Size})";
public static string ToPrettyString(this ClashWar cw)
{
var sb = new StringBuilder();
if (cw.WarState == StateOfWar.Created)
sb.AppendLine("`not started`");
var twoHours = new TimeSpan(2, 0, 0);
for (var i = 0; i < cw.Bases.Count; i++)
{
if (cw.Bases[i].CallUser == null)
{
sb.AppendLine($"`{i + 1}.` ❌*{cw.Localize("not_claimed")}*");
}
else
{
if (cw.Bases[i].BaseDestroyed)
{
sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {new string('⭐', cw.Bases[i].Stars)}");
}
else
{
var left = (cw.WarState == StateOfWar.Started) ? twoHours - (DateTime.UtcNow - cw.Bases[i].TimeAdded) : twoHours;
if (cw.Bases[i].Stars == 3)
{
sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left");
}
else
{
sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left {new string('⭐', cw.Bases[i].Stars)} {string.Concat(Enumerable.Repeat("🔸", 3 - cw.Bases[i].Stars))}");
}
}
}
}
return sb.ToString();
}
public static int FinishClaim(this ClashWar cw, string user, int stars = 3)
{
user = user.Trim();
for (var i = 0; i < cw.Bases.Count; i++)
{
if (cw.Bases[i]?.BaseDestroyed != false || cw.Bases[i]?.CallUser != user) continue;
cw.Bases[i].BaseDestroyed = true;
cw.Bases[i].Stars = stars;
return i;
}
throw new InvalidOperationException(cw.Localize("not_partic_or_destroyed", user));
}
public static void FinishClaim(this ClashWar cw, int index, int stars = 3)
{
if (index < 0 || index > cw.Bases.Count)
throw new ArgumentOutOfRangeException(nameof(index));
var toFinish = cw.Bases[index];
if (toFinish.BaseDestroyed != false) throw new InvalidOperationException(cw.Localize("base_already_destroyed"));
if (toFinish.CallUser == null) throw new InvalidOperationException(cw.Localize("base_already_unclaimed"));
toFinish.BaseDestroyed = true;
toFinish.Stars = stars;
}
public static string Localize(this ClashWar cw, string key)
{
return NadekoTopLevelModule.GetTextStatic(key,
NadekoBot.Localization.GetCultureInfo(cw.Channel?.GuildId),
typeof(ClashOfClans).Name.ToLowerInvariant());
}
public static string Localize(this ClashWar cw, string key, params object[] replacements)
{
return string.Format(cw.Localize(key), replacements);
}
}
}

View File

@ -17,97 +17,12 @@ namespace NadekoBot.Modules.CustomReactions
{
public static class CustomReactionExtensions
{
public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage context)
{
var channel = cr.DmResponse ? await context.Author.CreateDMChannelAsync() : context.Channel;
CustomReactions.ReactionStats.AddOrUpdate(cr.Trigger, 1, (k, old) => ++old);
CREmbed crembed;
if (CREmbed.TryParse(cr.Response, out crembed))
{
return await channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "");
}
return await channel.SendMessageAsync(cr.ResponseWithContext(context).SanitizeMentions());
}
}
[NadekoModule("CustomReactions", ".")]
public class CustomReactions : NadekoTopLevelModule
{
private static CustomReaction[] _globalReactions = new CustomReaction[] { };
public static CustomReaction[] GlobalReactions => _globalReactions;
public static ConcurrentDictionary<ulong, CustomReaction[]> GuildReactions { get; } = new ConcurrentDictionary<ulong, CustomReaction[]>();
public static ConcurrentDictionary<string, uint> ReactionStats { get; } = new ConcurrentDictionary<string, uint>();
private new static readonly Logger _log;
static CustomReactions()
{
_log = LogManager.GetCurrentClassLogger();
var sw = Stopwatch.StartNew();
using (var uow = DbHandler.UnitOfWork())
{
var items = uow.CustomReactions.GetAll();
GuildReactions = new ConcurrentDictionary<ulong, CustomReaction[]>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray()));
_globalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray();
}
sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
}
public void ClearStats() => ReactionStats.Clear();
public static CustomReaction TryGetCustomReaction(IUserMessage umsg)
{
var channel = umsg.Channel as SocketTextChannel;
if (channel == null)
return null;
var content = umsg.Content.Trim().ToLowerInvariant();
CustomReaction[] reactions;
GuildReactions.TryGetValue(channel.Guild.Id, out reactions);
if (reactions != null && reactions.Any())
{
var rs = reactions.Where(cr =>
{
if (cr == null)
return false;
var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%");
var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant();
return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger);
}).ToArray();
if (rs.Length != 0)
{
var reaction = rs[new NadekoRandom().Next(0, rs.Length)];
if (reaction != null)
{
if (reaction.Response == "-")
return null;
return reaction;
}
}
}
var grs = GlobalReactions.Where(cr =>
{
if (cr == null)
return false;
var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%");
var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant();
return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger);
}).ToArray();
if (grs.Length == 0)
return null;
var greaction = grs[new NadekoRandom().Next(0, grs.Length)];
return greaction;
}
[NadekoCommand, Usage, Description, Aliases]
public async Task AddCustReact(string key, [Remainder] string message)
{
@ -131,7 +46,7 @@ namespace NadekoBot.Modules.CustomReactions
Response = message,
};
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
uow.CustomReactions.Add(cr);
@ -309,7 +224,7 @@ namespace NadekoBot.Modules.CustomReactions
var success = false;
CustomReaction toDelete;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
toDelete = uow.CustomReactions.Get(id);
if (toDelete == null) //not found
@ -381,7 +296,7 @@ namespace NadekoBot.Modules.CustomReactions
var setValue = reaction.DmResponse = !reaction.DmResponse;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
uow.CustomReactions.Get(id).DmResponse = setValue;
uow.Complete();
@ -432,7 +347,7 @@ namespace NadekoBot.Modules.CustomReactions
var setValue = reaction.AutoDeleteTrigger = !reaction.AutoDeleteTrigger;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
uow.CustomReactions.Get(id).AutoDeleteTrigger = setValue;
uow.Complete();

View File

@ -9,19 +9,27 @@ using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Modules.Help
{
[NadekoModule("Help", "-")]
public class Help : NadekoTopLevelModule
{
private static string helpString { get; } = NadekoBot.BotConfig.HelpString;
public static string HelpString => String.Format(helpString, NadekoBot.Credentials.ClientId, NadekoBot.ModulePrefixes[typeof(Help).Name]);
public static string DMHelpString { get; } = NadekoBot.BotConfig.DMHelpString;
public const string PatreonUrl = "https://patreon.com/nadekobot";
public const string PaypalUrl = "https://paypal.me/Kwoth";
private readonly IBotCredentials _creds;
private readonly BotConfig _config;
private readonly CommandService _cmds;
public string HelpString => String.Format(_config.HelpString, _creds.ClientId, NadekoBot.Prefix);
public string DMHelpString => _config.DMHelpString;
public Help(IBotCredentials creds, BotConfig config, CommandService cmds)
{
_creds = creds;
_config = config;
_cmds = cmds;
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Modules()
@ -30,8 +38,9 @@ namespace NadekoBot.Modules.Help
.WithFooter(efb => efb.WithText("" + GetText("modules_footer", Prefix)))
.WithTitle(GetText("list_of_modules"))
.WithDescription(string.Join("\n",
NadekoBot.CommandService.Modules.GroupBy(m => m.GetTopLevelModule())
.Where(m => !Permissions.Permissions.GlobalPermissionCommands.BlockedModules.Contains(m.Key.Name.ToLowerInvariant()))
_cmds.Modules.GroupBy(m => m.GetTopLevelModule())
//todo perms
//.Where(m => !Permissions.Permissions.GlobalPermissionCommands.BlockedModules.Contains(m.Key.Name.ToLowerInvariant()))
.Select(m => "• " + m.Key.Name)
.OrderBy(s => s)));
await Context.Channel.EmbedAsync(embed).ConfigureAwait(false);
@ -45,8 +54,9 @@ namespace NadekoBot.Modules.Help
module = module?.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(module))
return;
var cmds = NadekoBot.CommandService.Commands.Where(c => c.Module.GetTopLevelModule().Name.ToUpperInvariant().StartsWith(module))
.Where(c => !Permissions.Permissions.GlobalPermissionCommands.BlockedCommands.Contains(c.Aliases.First().ToLowerInvariant()))
var cmds = _cmds.Commands.Where(c => c.Module.GetTopLevelModule().Name.ToUpperInvariant().StartsWith(module))
//todo perms
//.Where(c => !Permissions.Permissions.GlobalPermissionCommands.BlockedCommands.Contains(c.Aliases.First().ToLowerInvariant()))
.OrderBy(c => c.Aliases.First())
.Distinct(new CommandTextEqualityComparer())
.AsEnumerable();
@ -62,36 +72,33 @@ namespace NadekoBot.Modules.Help
for (int i = 0; i < groups.Count(); i++)
{
await channel.SendTableAsync(i == 0 ? $"📃 **{GetText("list_of_commands")}**\n" : "", groups.ElementAt(i), el => $"{el.Aliases.First(),-15} {"[" + el.Aliases.Skip(1).FirstOrDefault() + "]",-8}").ConfigureAwait(false);
await channel.SendTableAsync(i == 0 ? $"📃 **{GetText("list_of_commands")}**\n" : "", groups.ElementAt(i), el => $"{Prefix + el.Aliases.First(),-15} {"[" + el.Aliases.Skip(1).FirstOrDefault() + "]",-8}").ConfigureAwait(false);
}
await ConfirmLocalized("commands_instr", Prefix).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task H([Remainder] string comToFind = null)
public async Task H([Remainder] CommandInfo com = null)
{
var channel = Context.Channel;
comToFind = comToFind?.ToLowerInvariant();
if (string.IsNullOrWhiteSpace(comToFind))
if (com == null)
{
IMessageChannel ch = channel is ITextChannel ? await ((IGuildUser)Context.User).CreateDMChannelAsync() : channel;
await ch.SendMessageAsync(HelpString).ConfigureAwait(false);
return;
}
var com = NadekoBot.CommandService.Commands.FirstOrDefault(c => c.Aliases.Select(a=>a.ToLowerInvariant()).Contains(comToFind));
if (com == null)
{
await ReplyErrorLocalized("command_not_found").ConfigureAwait(false);
return;
}
var str = string.Format("**`{0}`**", com.Aliases.First());
var str = string.Format("**`{0}`**", Prefix + com.Aliases.First());
var alias = com.Aliases.Skip(1).FirstOrDefault();
if (alias != null)
str += string.Format(" **/ `{0}`**", alias);
str += string.Format(" **/ `{0}`**", Prefix + alias);
var embed = new EmbedBuilder()
.AddField(fb => fb.WithName(str).WithValue($"{com.RealSummary()} {GetCommandRequirements(com)}").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("usage")).WithValue(com.RealRemarks()).WithIsInline(false))
@ -122,7 +129,7 @@ namespace NadekoBot.Modules.Help
var helpstr = new StringBuilder();
helpstr.AppendLine(GetText("cmdlist_donate", PatreonUrl, PaypalUrl) + "\n");
helpstr.AppendLine("##"+ GetText("table_of_contents"));
helpstr.AppendLine(string.Join("\n", NadekoBot.CommandService.Modules.Where(m => m.GetTopLevelModule().Name.ToLowerInvariant() != "help")
helpstr.AppendLine(string.Join("\n", _cmds.Modules.Where(m => m.GetTopLevelModule().Name.ToLowerInvariant() != "help")
.Select(m => m.GetTopLevelModule().Name)
.Distinct()
.OrderBy(m => m)
@ -130,7 +137,7 @@ namespace NadekoBot.Modules.Help
.Select(m => string.Format("- [{0}](#{1})", m, m.ToLowerInvariant()))));
helpstr.AppendLine();
string lastModule = null;
foreach (var com in NadekoBot.CommandService.Commands.OrderBy(com => com.Module.GetTopLevelModule().Name).GroupBy(c => c.Aliases.First()).Select(g => g.First()))
foreach (var com in _cmds.Commands.OrderBy(com => com.Module.GetTopLevelModule().Name).GroupBy(c => c.Aliases.First()).Select(g => g.First()))
{
var module = com.Module.GetTopLevelModule();
if (module.Name != lastModule)
@ -147,10 +154,9 @@ namespace NadekoBot.Modules.Help
lastModule = module.Name;
}
helpstr.AppendLine($"{string.Join(" ", com.Aliases.Select(a => "`" + a + "`"))} |" +
$" {string.Format(com.Summary, com.Module.GetPrefix())} {GetCommandRequirements(com)} |" +
$" {string.Format(com.Remarks, com.Module.GetPrefix())}");
$" {string.Format(com.Summary, NadekoBot.Prefix)} {GetCommandRequirements(com)} |" +
$" {string.Format(com.Remarks, NadekoBot.Prefix)}");
}
helpstr = helpstr.Replace(NadekoBot.Client.CurrentUser.Username , "@BotName");
File.WriteAllText("../../docs/Commands List.md", helpstr.ToString());
await ReplyConfirmLocalized("commandlist_regen").ConfigureAwait(false);
}

View File

@ -1,6 +1,4 @@
using Discord.Commands;
using NadekoBot.Modules.Music.Classes;
using System.Collections.Concurrent;
using Discord.WebSocket;
using NadekoBot.Services;
using System.IO;
@ -19,26 +17,28 @@ using NadekoBot.Services.Music;
namespace NadekoBot.Modules.Music
{
[NadekoModule("Music", "!!")]
[DontAutoLoad]
public class Music : NadekoTopLevelModule
{
private static MusicService music;
private static MusicService _music;
private readonly DiscordShardedClient _client;
private readonly IBotCredentials _creds;
private readonly IGoogleApiService _google;
private readonly DbHandler _db;
static Music()
public Music(DiscordShardedClient client, IBotCredentials creds, IGoogleApiService google,
DbHandler db, MusicService music)
{
_client = client;
_creds = creds;
_google = google;
_db = db;
_music = music;
//it can fail if its currenctly opened or doesn't exist. Either way i don't care
try { Directory.Delete(MusicDataPath, true); } catch { }
NadekoBot.Client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated;
Directory.CreateDirectory(MusicDataPath);
//todo move to service
music = NadekoBot.MusicService;
_client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated;
}
private static Task Client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState oldState, SocketVoiceState newState)
private Task Client_UserVoiceStateUpdated(SocketUser iusr, SocketVoiceState oldState, SocketVoiceState newState)
{
var usr = iusr as SocketGuildUser;
if (usr == null ||
@ -46,14 +46,14 @@ namespace NadekoBot.Modules.Music
return Task.CompletedTask;
MusicPlayer player;
if ((player = music.GetPlayer(usr.Guild.Id)) == null)
if ((player = _music.GetPlayer(usr.Guild.Id)) == null)
return Task.CompletedTask;
try
{
//if bot moved
if ((player.PlaybackVoiceChannel == oldState.VoiceChannel) &&
usr.Id == NadekoBot.Client.CurrentUser.Id)
usr.Id == _client.CurrentUser.Id)
{
if (player.Paused && newState.VoiceChannel.Users.Count > 1) //unpause if there are people in the new channel
player.TogglePause();
@ -92,7 +92,7 @@ namespace NadekoBot.Modules.Music
return Task.CompletedTask;
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return Task.CompletedTask;
if (musicPlayer.PlaybackVoiceChannel == ((IGuildUser)Context.User).VoiceChannel)
{
@ -110,7 +110,7 @@ namespace NadekoBot.Modules.Music
public Task Stop()
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return Task.CompletedTask;
if (((IGuildUser)Context.User).VoiceChannel == musicPlayer.PlaybackVoiceChannel)
{
@ -125,10 +125,10 @@ namespace NadekoBot.Modules.Music
public Task Destroy()
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return Task.CompletedTask;
if (((IGuildUser)Context.User).VoiceChannel == musicPlayer.PlaybackVoiceChannel)
music.DestroyPlayer(Context.Guild.Id);
_music.DestroyPlayer(Context.Guild.Id);
return Task.CompletedTask;
@ -139,7 +139,7 @@ namespace NadekoBot.Modules.Music
public Task Pause()
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return Task.CompletedTask;
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return Task.CompletedTask;
@ -153,7 +153,7 @@ namespace NadekoBot.Modules.Music
{
var channel = (ITextChannel)Context.Channel;
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return;
@ -173,7 +173,7 @@ namespace NadekoBot.Modules.Music
[RequireContext(ContextType.Guild)]
public async Task Queue([Remainder] string query)
{
await music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, query).ConfigureAwait(false);
await _music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, query).ConfigureAwait(false);
if ((await Context.Guild.GetCurrentUserAsync()).GetPermissions((IGuildChannel)Context.Channel).ManageMessages)
{
Context.Message.DeleteAfter(10);
@ -184,7 +184,7 @@ namespace NadekoBot.Modules.Music
[RequireContext(ContextType.Guild)]
public async Task SoundCloudQueue([Remainder] string query)
{
await music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, query, musicType: MusicType.Soundcloud).ConfigureAwait(false);
await _music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, query, musicType: MusicType.Soundcloud).ConfigureAwait(false);
if ((await Context.Guild.GetCurrentUserAsync()).GetPermissions((IGuildChannel)Context.Channel).ManageMessages)
{
Context.Message.DeleteAfter(10);
@ -197,7 +197,7 @@ namespace NadekoBot.Modules.Music
{
Song currentSong;
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
if ((currentSong = musicPlayer?.CurrentSong) == null)
{
@ -250,7 +250,7 @@ namespace NadekoBot.Modules.Music
return embed;
};
await Context.Channel.SendPaginatedConfirmAsync(page, printAction, lastPage, false).ConfigureAwait(false);
await Context.Channel.SendPaginatedConfirmAsync(_client, page, printAction, lastPage, false).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
@ -258,7 +258,7 @@ namespace NadekoBot.Modules.Music
public async Task NowPlaying()
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
var currentSong = musicPlayer.CurrentSong;
if (currentSong == null)
@ -279,7 +279,7 @@ namespace NadekoBot.Modules.Music
public async Task Volume(int val)
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return;
@ -301,7 +301,7 @@ namespace NadekoBot.Modules.Music
await ReplyErrorLocalized("volume_input_invalid").ConfigureAwait(false);
return;
}
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
uow.GuildConfigs.For(Context.Guild.Id, set => set).DefaultMusicVolume = val / 100.0f;
uow.Complete();
@ -314,7 +314,7 @@ namespace NadekoBot.Modules.Music
public async Task ShufflePlaylist()
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return;
@ -338,13 +338,13 @@ namespace NadekoBot.Modules.Music
await ReplyErrorLocalized("must_be_in_voice").ConfigureAwait(false);
return;
}
var plId = (await NadekoBot.Google.GetPlaylistIdsByKeywordsAsync(arg).ConfigureAwait(false)).FirstOrDefault();
var plId = (await _google.GetPlaylistIdsByKeywordsAsync(arg).ConfigureAwait(false)).FirstOrDefault();
if (plId == null)
{
await ReplyErrorLocalized("no_search_results").ConfigureAwait(false);
return;
}
var ids = await NadekoBot.Google.GetPlaylistTracksAsync(plId, 500).ConfigureAwait(false);
var ids = await _google.GetPlaylistTracksAsync(plId, 500).ConfigureAwait(false);
if (!ids.Any())
{
await ReplyErrorLocalized("no_search_results").ConfigureAwait(false);
@ -366,7 +366,7 @@ namespace NadekoBot.Modules.Music
return;
try
{
await music.QueueSong(gusr, (ITextChannel)Context.Channel, gusr.VoiceChannel, id, true).ConfigureAwait(false);
await _music.QueueSong(gusr, (ITextChannel)Context.Channel, gusr.VoiceChannel, id, true).ConfigureAwait(false);
}
catch (SongNotFoundException) { }
catch { try { cancelSource.Cancel(); } catch { } }
@ -391,11 +391,11 @@ namespace NadekoBot.Modules.Music
using (var http = new HttpClient())
{
var scvids = JObject.Parse(await http.GetStringAsync($"http://api.soundcloud.com/resolve?url={pl}&client_id={NadekoBot.Credentials.SoundCloudClientId}").ConfigureAwait(false))["tracks"].ToObject<SoundCloudVideo[]>();
await music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, scvids[0].TrackLink).ConfigureAwait(false);
var scvids = JObject.Parse(await http.GetStringAsync($"http://api.soundcloud.com/resolve?url={pl}&client_id={_creds.SoundCloudClientId}").ConfigureAwait(false))["tracks"].ToObject<SoundCloudVideo[]>();
await _music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, scvids[0].TrackLink).ConfigureAwait(false);
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
foreach (var svideo in scvids.Skip(1))
@ -406,7 +406,7 @@ namespace NadekoBot.Modules.Music
{
Title = svideo.FullName,
Provider = "SoundCloud",
Uri = svideo.StreamLink,
Uri = svideo.GetStreamLink(_creds),
ProviderType = MusicType.Normal,
Query = svideo.TrackLink,
}), ((IGuildUser)Context.User).Username);
@ -433,7 +433,7 @@ namespace NadekoBot.Modules.Music
{
try
{
await music.QueueSong(gusr, (ITextChannel)Context.Channel, gusr.VoiceChannel, file.FullName, true, MusicType.Local).ConfigureAwait(false);
await _music.QueueSong(gusr, (ITextChannel)Context.Channel, gusr.VoiceChannel, file.FullName, true, MusicType.Local).ConfigureAwait(false);
}
catch (PlaylistFullException)
{
@ -457,7 +457,7 @@ namespace NadekoBot.Modules.Music
await ReplyErrorLocalized("must_be_in_voice").ConfigureAwait(false);
return;
}
await music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, radioLink, musicType: MusicType.Radio).ConfigureAwait(false);
await _music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, radioLink, musicType: MusicType.Radio).ConfigureAwait(false);
if ((await Context.Guild.GetCurrentUserAsync()).GetPermissions((IGuildChannel)Context.Channel).ManageMessages)
{
Context.Message.DeleteAfter(10);
@ -473,7 +473,7 @@ namespace NadekoBot.Modules.Music
var arg = path;
if (string.IsNullOrWhiteSpace(arg))
return;
await music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, path, musicType: MusicType.Local).ConfigureAwait(false);
await _music.QueueSong(((IGuildUser)Context.User), (ITextChannel)Context.Channel, ((IGuildUser)Context.User).VoiceChannel, path, musicType: MusicType.Local).ConfigureAwait(false);
}
@ -495,7 +495,7 @@ namespace NadekoBot.Modules.Music
public Task Remove(int num)
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return Task.CompletedTask;
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return Task.CompletedTask;
@ -512,7 +512,7 @@ namespace NadekoBot.Modules.Music
if (all.Trim().ToUpperInvariant() != "ALL")
return;
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
musicPlayer.ClearQueue();
await ReplyConfirmLocalized("queue_cleared").ConfigureAwait(false);
@ -526,7 +526,7 @@ namespace NadekoBot.Modules.Music
return;
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
fromto = fromto?.Trim();
@ -569,7 +569,7 @@ namespace NadekoBot.Modules.Music
public async Task SetMaxQueue(uint size = 0)
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
musicPlayer.MaxQueueSize = size;
@ -589,7 +589,7 @@ namespace NadekoBot.Modules.Music
var channel = (ITextChannel)Context.Channel;
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
musicPlayer.MaxPlaytimeSeconds = seconds;
if (seconds == 0)
@ -603,7 +603,7 @@ namespace NadekoBot.Modules.Music
public async Task ReptCurSong()
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
var currentSong = musicPlayer.CurrentSong;
if (currentSong == null)
@ -626,7 +626,7 @@ namespace NadekoBot.Modules.Music
public async Task RepeatPl()
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
var currentValue = musicPlayer.ToggleRepeatPlaylist();
if(currentValue)
@ -640,7 +640,7 @@ namespace NadekoBot.Modules.Music
public async Task Save([Remainder] string name)
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
var curSong = musicPlayer.CurrentSong;
@ -655,7 +655,7 @@ namespace NadekoBot.Modules.Music
}).ToList();
MusicPlaylist playlist;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
playlist = new MusicPlaylist
{
@ -679,7 +679,7 @@ namespace NadekoBot.Modules.Music
public async Task Load([Remainder] int id)
{
MusicPlaylist mpl;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
mpl = uow.MusicPlaylists.GetWithSongs(id);
}
@ -696,7 +696,7 @@ namespace NadekoBot.Modules.Music
var usr = (IGuildUser)Context.User;
try
{
await music.QueueSong(usr, (ITextChannel)Context.Channel, usr.VoiceChannel, item.Query, true, item.ProviderType).ConfigureAwait(false);
await _music.QueueSong(usr, (ITextChannel)Context.Channel, usr.VoiceChannel, item.Query, true, item.ProviderType).ConfigureAwait(false);
}
catch (SongNotFoundException) { }
catch { break; }
@ -714,7 +714,7 @@ namespace NadekoBot.Modules.Music
List<MusicPlaylist> playlists;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
playlists = uow.MusicPlaylists.GetPlaylistsOnPage(num);
}
@ -735,13 +735,13 @@ namespace NadekoBot.Modules.Music
var success = false;
try
{
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
var pl = uow.MusicPlaylists.Get(id);
if (pl != null)
{
if (NadekoBot.Credentials.IsOwner(Context.User) || pl.AuthorId == Context.User.Id)
if (_creds.IsOwner(Context.User) || pl.AuthorId == Context.User.Id)
{
uow.MusicPlaylists.Remove(pl);
await uow.CompleteAsync().ConfigureAwait(false);
@ -766,7 +766,7 @@ namespace NadekoBot.Modules.Music
public async Task Goto(int time)
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
if (((IGuildUser)Context.User).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return;
@ -801,7 +801,7 @@ namespace NadekoBot.Modules.Music
public async Task Autoplay()
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
return;
if (!musicPlayer.ToggleAutoplay())
@ -816,7 +816,7 @@ namespace NadekoBot.Modules.Music
public async Task SetMusicChannel()
{
MusicPlayer musicPlayer;
if ((musicPlayer = music.GetPlayer(Context.Guild.Id)) == null)
if ((musicPlayer = _music.GetPlayer(Context.Guild.Id)) == null)
{
await ReplyErrorLocalized("no_player").ConfigureAwait(false);
return;

View File

@ -15,7 +15,6 @@ using NadekoBot.Services.Searches;
namespace NadekoBot.Modules.NSFW
{
[NadekoModule("NSFW")]
public class NSFW : NadekoTopLevelModule
{
private static readonly ConcurrentDictionary<ulong, Timer> _autoHentaiTimers = new ConcurrentDictionary<ulong, Timer>();

View File

@ -17,7 +17,6 @@ namespace NadekoBot.Modules
public readonly string ModuleTypeName;
public readonly string LowerModuleTypeName;
//todo :thinking:
public NadekoStrings _strings { get; set; }
public ILocalization _localization { get; set; }

View File

@ -20,7 +20,6 @@ using System.Diagnostics;
namespace NadekoBot.Modules.Utility
{
[NadekoModule("Utility")]
public partial class Utility : NadekoTopLevelModule
{
private static ConcurrentDictionary<ulong, Timer> _rotatingRoleColors = new ConcurrentDictionary<ulong, Timer>();

View File

@ -18,6 +18,8 @@ using NadekoBot.Services.Database.Models;
using System.Threading;
using NadekoBot.Modules.Utility;
using NadekoBot.Services.Searches;
using NadekoBot.Services.ClashOfClans;
using NadekoBot.Services.Music;
namespace NadekoBot
{
@ -75,7 +77,6 @@ namespace NadekoBot
var greetSettingsService = new GreetSettingsService(AllGuildConfigs, db);
var localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), db);
//var musicService = new MusicService(google, strings, localization);
var commandService = new CommandService(new CommandServiceConfig()
{
@ -89,9 +90,15 @@ namespace NadekoBot
var images = new ImagesService();
var currencyHandler = new CurrencyHandler(BotConfig, db);
var soundcloud = new SoundCloudApiService(credentials);
//module services
var utilityService = new UtilityService(AllGuildConfigs, Client, BotConfig, db);
var searchesService = new SearchesService();
var clashService = new ClashOfClansService(Client, db, localization, strings);
var musicService = new MusicService(google, strings, localization, db, soundcloud, credentials);
//initialize Services
Services = new NServiceProvider.ServiceProviderBuilder() //todo all Adds should be interfaces
@ -105,12 +112,15 @@ namespace NadekoBot
.Add<NadekoStrings>(strings)
.Add<DiscordShardedClient>(Client)
.Add<GreetSettingsService>(greetSettingsService)
//.Add(musicService)
.Add<BotConfig>(BotConfig)
.Add<CurrencyHandler>(currencyHandler)
.Add(musicService)
.Add<CommandHandler>(commandHandler)
.Add<DbHandler>(db)
//modules
.Add<UtilityService>(utilityService)
.Add<SearchesService>(searchesService)
.Add<ClashOfClansService>(clashService)
.Build();
commandHandler.AddServices(Services);

View File

@ -29,35 +29,26 @@
<ItemGroup>
<Compile Remove="data\**\*;credentials.json;credentials_example.json" />
<Compile Remove="Modules\Administration\**" />
<Compile Remove="Modules\ClashOfClans\**" />
<Compile Remove="Modules\CustomReactions\**" />
<Compile Remove="Modules\Gambling\**" />
<Compile Remove="Modules\Games\**" />
<Compile Remove="Modules\Help\**" />
<Compile Remove="Modules\Music\**" />
<Compile Remove="Modules\NSFW\**" />
<Compile Remove="Modules\Permissions\**" />
<Compile Remove="Modules\Searches\**" />
<Compile Remove="Services\Music\**" />
<EmbeddedResource Remove="Modules\Administration\**" />
<EmbeddedResource Remove="Modules\ClashOfClans\**" />
<EmbeddedResource Remove="Modules\CustomReactions\**" />
<EmbeddedResource Remove="Modules\Gambling\**" />
<EmbeddedResource Remove="Modules\Games\**" />
<EmbeddedResource Remove="Modules\Help\**" />
<EmbeddedResource Remove="Modules\Music\**" />
<EmbeddedResource Remove="Modules\NSFW\**" />
<EmbeddedResource Remove="Modules\Permissions\**" />
<EmbeddedResource Remove="Modules\Searches\**" />
<EmbeddedResource Remove="Services\Music\**" />
<None Remove="Modules\Administration\**" />
<None Remove="Modules\ClashOfClans\**" />
<None Remove="Modules\CustomReactions\**" />
<None Remove="Modules\Gambling\**" />
<None Remove="Modules\Games\**" />
<None Remove="Modules\Help\**" />
<None Remove="Modules\Music\**" />
<None Remove="Modules\NSFW\**" />
<None Remove="Modules\Permissions\**" />
<None Remove="Modules\Searches\**" />
<None Remove="Services\Music\**" />
<None Update="libsodium.dll;opus.dll;libsodium.so;libopus.so">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@ -105,4 +96,8 @@
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
<DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Modules\Music\Classes\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,263 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Services.ClashOfClans
{
// todo, just made this compile, it's a complete mess. A lot of the things here should actually be in the actual module.
// service should just handle the state, module should print out what happened, so everything that has to do with strings
// shouldn't be here
public class ClashOfClansService
{
private readonly DiscordShardedClient _client;
private readonly DbHandler _db;
private readonly ILocalization _localization;
private readonly NadekoStrings _strings;
private readonly Timer checkWarTimer;
public ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; }
public ClashOfClansService(DiscordShardedClient client, DbHandler db, ILocalization localization, NadekoStrings strings)
{
_client = client;
_db = db;
_localization = localization;
_strings = strings;
using (var uow = _db.UnitOfWork)
{
ClashWars = new ConcurrentDictionary<ulong, List<ClashWar>>(
uow.ClashOfClans
.GetAllWars()
.Select(cw =>
{
cw.Channel = _client.GetGuild(cw.GuildId)?
.GetTextChannel(cw.ChannelId);
return cw;
})
.Where(cw => cw.Channel != null)
.GroupBy(cw => cw.GuildId)
.ToDictionary(g => g.Key, g => g.ToList()));
}
checkWarTimer = new Timer(async _ =>
{
foreach (var kvp in ClashWars)
{
foreach (var war in kvp.Value)
{
try { await CheckWar(TimeSpan.FromHours(2), war).ConfigureAwait(false); } catch { }
}
}
}, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
private async Task CheckWar(TimeSpan callExpire, ClashWar war)
{
var Bases = war.Bases;
for (var i = 0; i < Bases.Count; i++)
{
var callUser = Bases[i].CallUser;
if (callUser == null) continue;
if ((!Bases[i].BaseDestroyed) && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire)
{
if (Bases[i].Stars != 3)
Bases[i].BaseDestroyed = true;
else
Bases[i] = null;
try
{
SaveWar(war);
await war.Channel.SendErrorAsync(_strings.GetText("claim_expired",
_localization.GetCultureInfo(war.Channel.GuildId),
typeof(ClashOfClansService).Name.ToLowerInvariant(),
Format.Bold(Bases[i].CallUser),
ShortPrint(war)));
}
catch { }
}
}
}
public Tuple<List<ClashWar>, int> GetWarInfo(IGuild guild, int num)
{
List<ClashWar> wars = null;
ClashWars.TryGetValue(guild.Id, out wars);
if (wars == null || wars.Count == 0)
{
return null;
}
// get the number of the war
else if (num < 1 || num > wars.Count)
{
return null;
}
num -= 1;
//get the actual war
return new Tuple<List<ClashWar>, int>(wars, num);
}
public async Task<ClashWar> CreateWar(string enemyClan, int size, ulong serverId, ulong channelId)
{
var channel = _client.GetGuild(serverId)?.GetTextChannel(channelId);
using (var uow = _db.UnitOfWork)
{
var cw = new ClashWar
{
EnemyClan = enemyClan,
Size = size,
Bases = new List<ClashCaller>(size),
GuildId = serverId,
ChannelId = channelId,
Channel = channel,
};
cw.Bases.Capacity = size;
for (int i = 0; i < size; i++)
{
cw.Bases.Add(new ClashCaller()
{
CallUser = null,
SequenceNumber = i,
});
}
Console.WriteLine(cw.Bases.Capacity);
uow.ClashOfClans.Add(cw);
await uow.CompleteAsync();
return cw;
}
}
public void SaveWar(ClashWar cw)
{
if (cw.WarState == StateOfWar.Ended)
{
using (var uow = _db.UnitOfWork)
{
uow.ClashOfClans.Remove(cw);
uow.CompleteAsync();
}
return;
}
using (var uow = _db.UnitOfWork)
{
uow.ClashOfClans.Update(cw);
uow.CompleteAsync();
}
}
public void Call(ClashWar cw, string u, int baseNumber)
{
if (baseNumber < 0 || baseNumber >= cw.Bases.Count)
throw new ArgumentException(Localize(cw, "invalid_base_number"));
if (cw.Bases[baseNumber].CallUser != null && cw.Bases[baseNumber].Stars == 3)
throw new ArgumentException(Localize(cw, "base_already_claimed"));
for (var i = 0; i < cw.Bases.Count; i++)
{
if (cw.Bases[i]?.BaseDestroyed == false && cw.Bases[i]?.CallUser == u)
throw new ArgumentException(Localize(cw, "claimed_other", u, i + 1));
}
var cc = cw.Bases[baseNumber];
cc.CallUser = u.Trim();
cc.TimeAdded = DateTime.UtcNow;
cc.BaseDestroyed = false;
}
public int FinishClaim(ClashWar cw, string user, int stars = 3)
{
user = user.Trim();
for (var i = 0; i < cw.Bases.Count; i++)
{
if (cw.Bases[i]?.BaseDestroyed != false || cw.Bases[i]?.CallUser != user) continue;
cw.Bases[i].BaseDestroyed = true;
cw.Bases[i].Stars = stars;
return i;
}
throw new InvalidOperationException(Localize(cw, "not_partic_or_destroyed", user));
}
public void FinishClaim(ClashWar cw, int index, int stars = 3)
{
if (index < 0 || index > cw.Bases.Count)
throw new ArgumentOutOfRangeException(nameof(index));
var toFinish = cw.Bases[index];
if (toFinish.BaseDestroyed != false) throw new InvalidOperationException(Localize(cw, "base_already_destroyed"));
if (toFinish.CallUser == null) throw new InvalidOperationException(Localize(cw, "base_already_unclaimed"));
toFinish.BaseDestroyed = true;
toFinish.Stars = stars;
}
public int Uncall(ClashWar cw, string user)
{
user = user.Trim();
for (var i = 0; i < cw.Bases.Count; i++)
{
if (cw.Bases[i]?.CallUser != user) continue;
cw.Bases[i].CallUser = null;
return i;
}
throw new InvalidOperationException(Localize(cw, "not_partic"));
}
public string ShortPrint(ClashWar cw) =>
$"`{cw.EnemyClan}` ({cw.Size} v {cw.Size})";
public string ToPrettyString(ClashWar cw)
{
var sb = new StringBuilder();
if (cw.WarState == StateOfWar.Created)
sb.AppendLine("`not started`");
var twoHours = new TimeSpan(2, 0, 0);
for (var i = 0; i < cw.Bases.Count; i++)
{
if (cw.Bases[i].CallUser == null)
{
sb.AppendLine($"`{i + 1}.` ❌*{Localize(cw, "not_claimed")}*");
}
else
{
if (cw.Bases[i].BaseDestroyed)
{
sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {new string('⭐', cw.Bases[i].Stars)}");
}
else
{
var left = (cw.WarState == StateOfWar.Started) ? twoHours - (DateTime.UtcNow - cw.Bases[i].TimeAdded) : twoHours;
if (cw.Bases[i].Stars == 3)
{
sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left");
}
else
{
sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left {new string('⭐', cw.Bases[i].Stars)} {string.Concat(Enumerable.Repeat("🔸", 3 - cw.Bases[i].Stars))}");
}
}
}
}
return sb.ToString();
}
public string Localize(ClashWar cw, string key, params object[] replacements)
{
return string.Format(Localize(cw, key), replacements);
}
public string Localize(ClashWar cw, string key)
{
return _strings.GetText(key,
_localization.GetCultureInfo(cw.Channel?.GuildId),
"ClashOfClans".ToLowerInvariant());
}
}
}

View File

@ -0,0 +1,40 @@
using NadekoBot.Services.Database.Models;
using System;
using System.Linq;
namespace NadekoBot.Services.ClashOfClans
{
public static class Extensions
{
public static void ResetTime(this ClashCaller c)
{
c.TimeAdded = DateTime.UtcNow;
}
public static void Destroy(this ClashCaller c)
{
c.BaseDestroyed = true;
}
public static void End(this ClashWar cw)
{
//Ended = true;
cw.WarState = StateOfWar.Ended;
}
public static void Start(this ClashWar cw)
{
if (cw.WarState == StateOfWar.Started)
throw new InvalidOperationException("war_already_started");
//if (Started)
// throw new InvalidOperationException();
//Started = true;
cw.WarState = StateOfWar.Started;
cw.StartedAt = DateTime.UtcNow;
foreach (var b in cw.Bases.Where(b => b.CallUser != null))
{
b.ResetTime();
}
}
}
}

View File

@ -0,0 +1,88 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Services.Database.Models;
using NLog;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
namespace NadekoBot.Services
{
public class CustomReactions
{
private CustomReaction[] _globalReactions = new CustomReaction[] { };
public CustomReaction[] GlobalReactions => _globalReactions;
public ConcurrentDictionary<ulong, CustomReaction[]> GuildReactions { get; } = new ConcurrentDictionary<ulong, CustomReaction[]>();
public ConcurrentDictionary<string, uint> ReactionStats { get; } = new ConcurrentDictionary<string, uint>();
private readonly Logger _log;
private readonly DbHandler _db;
public CustomReactions(DbHandler db)
{
_log = LogManager.GetCurrentClassLogger();
_db = db;
var sw = Stopwatch.StartNew();
using (var uow = _db.UnitOfWork)
{
var items = uow.CustomReactions.GetAll();
GuildReactions = new ConcurrentDictionary<ulong, CustomReaction[]>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray()));
_globalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray();
}
sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
}
public void ClearStats() => ReactionStats.Clear();
public CustomReaction TryGetCustomReaction(IUserMessage umsg)
{
var channel = umsg.Channel as SocketTextChannel;
if (channel == null)
return null;
var content = umsg.Content.Trim().ToLowerInvariant();
CustomReaction[] reactions;
GuildReactions.TryGetValue(channel.Guild.Id, out reactions);
if (reactions != null && reactions.Any())
{
var rs = reactions.Where(cr =>
{
if (cr == null)
return false;
var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%");
var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant();
return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger);
}).ToArray();
if (rs.Length != 0)
{
var reaction = rs[new NadekoRandom().Next(0, rs.Length)];
if (reaction != null)
{
if (reaction.Response == "-")
return null;
return reaction;
}
}
}
var grs = GlobalReactions.Where(cr =>
{
if (cr == null)
return false;
var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%");
var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant();
return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger);
}).ToArray();
if (grs.Length == 0)
return null;
var greaction = grs[new NadekoRandom().Next(0, grs.Length)];
return greaction;
}
}
}

View File

@ -1,13 +1,12 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.DataStructures;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace NadekoBot.Modules.CustomReactions
{
@ -103,5 +102,19 @@ namespace NadekoBot.Modules.CustomReactions
public static string ResponseWithContext(this CustomReaction cr, IUserMessage ctx)
=> cr.Response.ResolveResponseString(ctx, cr.Trigger.ResolveTriggerString(ctx));
public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage context)
{
var channel = cr.DmResponse ? await context.Author.CreateDMChannelAsync() : context.Channel;
CustomReactions.ReactionStats.AddOrUpdate(cr.Trigger, 1, (k, old) => ++old);
CREmbed crembed;
if (CREmbed.TryParse(cr.Response, out crembed))
{
return await channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "");
}
return await channel.SendMessageAsync(cr.ResponseWithContext(context).SanitizeMentions());
}
}
}

View File

@ -7,15 +7,6 @@ namespace NadekoBot.Services.Database.Models
{
public class ClashWar : DbEntity
{
public enum DestroyStars
{
One, Two, Three
}
public enum StateOfWar
{
Started, Ended, Created
}
public string EnemyClan { get; set; }
public int Size { get; set; }
public StateOfWar WarState { get; set; } = StateOfWar.Created;
@ -29,4 +20,13 @@ namespace NadekoBot.Services.Database.Models
public List<ClashCaller> Bases { get; set; } = new List<ClashCaller>();
}
public enum DestroyStars
{
One, Two, Three
}
public enum StateOfWar
{
Started, Ended, Created
}
}

View File

@ -8,12 +8,10 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using NadekoBot.Services.Music;
using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services.Music
{
public enum StreamState
{
Resolving,
@ -24,7 +22,6 @@ namespace NadekoBot.Services.Music
public class MusicPlayer
{
public const string MusicDataPath = "data/musicdata";
private IAudioClient AudioClient { get; set; }
/// <summary>

View File

@ -6,23 +6,45 @@ using Discord;
using NadekoBot.Extensions;
using NadekoBot.Modules;
using NadekoBot.Services.Impl;
using NadekoBot.Services.Database.Models;
using System.Text.RegularExpressions;
using NLog;
using System.IO;
using VideoLibrary;
using System.Net.Http;
namespace NadekoBot.Services.Music
{
public class MusicService
{
public const string MusicDataPath = "data/musicdata";
private readonly IGoogleApiService _google;
private readonly NadekoStrings _strings;
private readonly ILocalization _localization;
private GoogleApiService google;
private readonly DbHandler _db;
private readonly Logger _log;
private readonly SoundCloudApiService _sc;
private readonly IBotCredentials _creds;
public ConcurrentDictionary<ulong, MusicPlayer> MusicPlayers { get; } = new ConcurrentDictionary<ulong, MusicPlayer>();
public MusicService(IGoogleApiService google, NadekoStrings strings, ILocalization localization)
public MusicService(IGoogleApiService google,
NadekoStrings strings, ILocalization localization, DbHandler db,
SoundCloudApiService sc, IBotCredentials creds)
{
_google = google;
_strings = strings;
_localization = localization;
_db = db;
_sc = sc;
_creds = creds;
_log = LogManager.GetCurrentClassLogger();
try { Directory.Delete(MusicDataPath, true); } catch { }
Directory.CreateDirectory(MusicDataPath);
}
public MusicPlayer GetPlayer(ulong guildId)
@ -39,7 +61,7 @@ namespace NadekoBot.Services.Music
return MusicPlayers.GetOrAdd(guildId, server =>
{
float vol;// SpecificConfigurations.Default.Of(server.Id).DefaultMusicVolume;
using (var uow = DbHandler.UnitOfWork())
using (var uow = _db.UnitOfWork)
{
//todo move to cached variable
vol = uow.GuildConfigs.For(guildId, set => set).DefaultMusicVolume;
@ -168,7 +190,7 @@ namespace NadekoBot.Services.Music
try
{
musicPlayer.ThrowIfQueueFull();
resolvedSong = await SongHandler.ResolveSong(query, musicType).ConfigureAwait(false);
resolvedSong = await ResolveSong(query, musicType).ConfigureAwait(false);
if (resolvedSong == null)
throw new SongNotFoundException();
@ -213,5 +235,202 @@ namespace NadekoBot.Services.Music
}
public async Task<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal)
{
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
if (musicType != MusicType.Local && IsRadioLink(query))
{
musicType = MusicType.Radio;
query = await HandleStreamContainers(query).ConfigureAwait(false) ?? query;
}
try
{
switch (musicType)
{
case MusicType.Local:
return new Song(new SongInfo
{
Uri = "\"" + Path.GetFullPath(query) + "\"",
Title = Path.GetFileNameWithoutExtension(query),
Provider = "Local File",
ProviderType = musicType,
Query = query,
});
case MusicType.Radio:
return new Song(new SongInfo
{
Uri = query,
Title = $"{query}",
Provider = "Radio Stream",
ProviderType = musicType,
Query = query
})
{ TotalTime = TimeSpan.MaxValue };
}
if (_sc.IsSoundCloudLink(query))
{
var svideo = await _sc.ResolveVideoAsync(query).ConfigureAwait(false);
return new Song(new SongInfo
{
Title = svideo.FullName,
Provider = "SoundCloud",
Uri = svideo.GetStreamLink(_creds),
ProviderType = musicType,
Query = svideo.TrackLink,
AlbumArt = svideo.artwork_url,
})
{ TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) };
}
if (musicType == MusicType.Soundcloud)
{
var svideo = await _sc.GetVideoByQueryAsync(query).ConfigureAwait(false);
return new Song(new SongInfo
{
Title = svideo.FullName,
Provider = "SoundCloud",
Uri = svideo.GetStreamLink(_creds),
ProviderType = MusicType.Soundcloud,
Query = svideo.TrackLink,
AlbumArt = svideo.artwork_url,
})
{ TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) };
}
var link = (await _google.GetVideosByKeywordsAsync(query).ConfigureAwait(false)).FirstOrDefault();
if (string.IsNullOrWhiteSpace(link))
throw new OperationCanceledException("Not a valid youtube query.");
var allVideos = await Task.Run(async () => { try { return await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false); } catch { return Enumerable.Empty<YouTubeVideo>(); } }).ConfigureAwait(false);
var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio);
var video = videos
.Where(v => v.AudioBitrate < 256)
.OrderByDescending(v => v.AudioBitrate)
.FirstOrDefault();
if (video == null) // do something with this error
throw new Exception("Could not load any video elements based on the query.");
var m = Regex.Match(query, @"\?t=(?<t>\d*)");
int gotoTime = 0;
if (m.Captures.Count > 0)
int.TryParse(m.Groups["t"].ToString(), out gotoTime);
var song = new Song(new SongInfo
{
Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube"
Provider = "YouTube",
Uri = await video.GetUriAsync().ConfigureAwait(false),
Query = link,
ProviderType = musicType,
});
song.SkipTo = gotoTime;
return song;
}
catch (Exception ex)
{
_log.Warn($"Failed resolving the link.{ex.Message}");
_log.Warn(ex);
return null;
}
}
private async Task<string> HandleStreamContainers(string query)
{
string file = null;
try
{
using (var http = new HttpClient())
{
file = await http.GetStringAsync(query).ConfigureAwait(false);
}
}
catch
{
return query;
}
if (query.Contains(".pls"))
{
//File1=http://armitunes.com:8000/
//Regex.Match(query)
try
{
var m = Regex.Match(file, "File1=(?<url>.*?)\\n");
var res = m.Groups["url"]?.ToString();
return res?.Trim();
}
catch
{
_log.Warn($"Failed reading .pls:\n{file}");
return null;
}
}
if (query.Contains(".m3u"))
{
/*
# This is a comment
C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3
C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3
*/
try
{
var m = Regex.Match(file, "(?<url>^[^#].*)", RegexOptions.Multiline);
var res = m.Groups["url"]?.ToString();
return res?.Trim();
}
catch
{
_log.Warn($"Failed reading .m3u:\n{file}");
return null;
}
}
if (query.Contains(".asx"))
{
//<ref href="http://armitunes.com:8000"/>
try
{
var m = Regex.Match(file, "<ref href=\"(?<url>.*?)\"");
var res = m.Groups["url"]?.ToString();
return res?.Trim();
}
catch
{
_log.Warn($"Failed reading .asx:\n{file}");
return null;
}
}
if (query.Contains(".xspf"))
{
/*
<?xml version="1.0" encoding="UTF-8"?>
<playlist version="1" xmlns="http://xspf.org/ns/0/">
<trackList>
<track><location>file:///mp3s/song_1.mp3</location></track>
*/
try
{
var m = Regex.Match(file, "<location>(?<url>.*?)</location>");
var res = m.Groups["url"]?.ToString();
return res?.Trim();
}
catch
{
_log.Warn($"Failed reading .xspf:\n{file}");
return null;
}
}
return query;
}
private bool IsRadioLink(string query) =>
(query.StartsWith("http") ||
query.StartsWith("ww"))
&&
(query.Contains(".pls") ||
query.Contains(".m3u") ||
query.Contains(".asx") ||
query.Contains(".xspf"));
}
}

View File

@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Net;
using Discord;
using NadekoBot.Services.Database.Models;
namespace NadekoBot.Services.Music
{
@ -144,7 +145,7 @@ namespace NadekoBot.Services.Music
public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
{
BytesSent = (ulong) SkipTo * 3840 * 50;
var filename = Path.Combine(MusicPlayer.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
var filename = Path.Combine(MusicService.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
var inStream = new SongBuffer(MusicPlayer, filename, SongInfo, SkipTo, _frameBytes * 100);
var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false);

View File

@ -1,214 +1,9 @@
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using NLog;
using VideoLibrary;
using NLog;
namespace NadekoBot.Services.Music
{
public static class SongHandler
{
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
public static async Task<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal)
{
if (string.IsNullOrWhiteSpace(query))
throw new ArgumentNullException(nameof(query));
if (musicType != MusicType.Local && IsRadioLink(query))
{
musicType = MusicType.Radio;
query = await HandleStreamContainers(query).ConfigureAwait(false) ?? query;
}
try
{
switch (musicType)
{
case MusicType.Local:
return new Song(new SongInfo
{
Uri = "\"" + Path.GetFullPath(query) + "\"",
Title = Path.GetFileNameWithoutExtension(query),
Provider = "Local File",
ProviderType = musicType,
Query = query,
});
case MusicType.Radio:
return new Song(new SongInfo
{
Uri = query,
Title = $"{query}",
Provider = "Radio Stream",
ProviderType = musicType,
Query = query
})
{ TotalTime = TimeSpan.MaxValue };
}
var sc = SoundCloud.GetInstance(_creds);
if (SoundCloud.Default.IsSoundCloudLink(query))
{
var svideo = await SoundCloud.Default.ResolveVideoAsync(query).ConfigureAwait(false);
return new Song(new SongInfo
{
Title = svideo.FullName,
Provider = "SoundCloud",
Uri = svideo.StreamLink,
ProviderType = musicType,
Query = svideo.TrackLink,
AlbumArt = svideo.artwork_url,
})
{ TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) };
}
if (musicType == MusicType.Soundcloud)
{
var svideo = await SoundCloud.Default.GetVideoByQueryAsync(query).ConfigureAwait(false);
return new Song(new SongInfo
{
Title = svideo.FullName,
Provider = "SoundCloud",
Uri = svideo.StreamLink,
ProviderType = MusicType.Soundcloud,
Query = svideo.TrackLink,
AlbumArt = svideo.artwork_url,
})
{ TotalTime = TimeSpan.FromMilliseconds(svideo.Duration) };
}
var link = (await NadekoBot.Google.GetVideosByKeywordsAsync(query).ConfigureAwait(false)).FirstOrDefault();
if (string.IsNullOrWhiteSpace(link))
throw new OperationCanceledException("Not a valid youtube query.");
var allVideos = await Task.Run(async () => { try { return await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false); } catch { return Enumerable.Empty<YouTubeVideo>(); } }).ConfigureAwait(false);
var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio);
var video = videos
.Where(v => v.AudioBitrate < 256)
.OrderByDescending(v => v.AudioBitrate)
.FirstOrDefault();
if (video == null) // do something with this error
throw new Exception("Could not load any video elements based on the query.");
var m = Regex.Match(query, @"\?t=(?<t>\d*)");
int gotoTime = 0;
if (m.Captures.Count > 0)
int.TryParse(m.Groups["t"].ToString(), out gotoTime);
var song = new Song(new SongInfo
{
Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube"
Provider = "YouTube",
Uri = await video.GetUriAsync().ConfigureAwait(false),
Query = link,
ProviderType = musicType,
});
song.SkipTo = gotoTime;
return song;
}
catch (Exception ex)
{
_log.Warn($"Failed resolving the link.{ex.Message}");
_log.Warn(ex);
return null;
}
}
private static async Task<string> HandleStreamContainers(string query)
{
string file = null;
try
{
using (var http = new HttpClient())
{
file = await http.GetStringAsync(query).ConfigureAwait(false);
}
}
catch
{
return query;
}
if (query.Contains(".pls"))
{
//File1=http://armitunes.com:8000/
//Regex.Match(query)
try
{
var m = Regex.Match(file, "File1=(?<url>.*?)\\n");
var res = m.Groups["url"]?.ToString();
return res?.Trim();
}
catch
{
_log.Warn($"Failed reading .pls:\n{file}");
return null;
}
}
if (query.Contains(".m3u"))
{
/*
# This is a comment
C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3
C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3
*/
try
{
var m = Regex.Match(file, "(?<url>^[^#].*)", RegexOptions.Multiline);
var res = m.Groups["url"]?.ToString();
return res?.Trim();
}
catch
{
_log.Warn($"Failed reading .m3u:\n{file}");
return null;
}
}
if (query.Contains(".asx"))
{
//<ref href="http://armitunes.com:8000"/>
try
{
var m = Regex.Match(file, "<ref href=\"(?<url>.*?)\"");
var res = m.Groups["url"]?.ToString();
return res?.Trim();
}
catch
{
_log.Warn($"Failed reading .asx:\n{file}");
return null;
}
}
if (query.Contains(".xspf"))
{
/*
<?xml version="1.0" encoding="UTF-8"?>
<playlist version="1" xmlns="http://xspf.org/ns/0/">
<trackList>
<track><location>file:///mp3s/song_1.mp3</location></track>
*/
try
{
var m = Regex.Match(file, "<location>(?<url>.*?)</location>");
var res = m.Groups["url"]?.ToString();
return res?.Trim();
}
catch
{
_log.Warn($"Failed reading .xspf:\n{file}");
return null;
}
}
return query;
}
private static bool IsRadioLink(string query) =>
(query.StartsWith("http") ||
query.StartsWith("ww"))
&&
(query.Contains(".pls") ||
query.Contains(".m3u") ||
query.Contains(".asx") ||
query.Contains(".xspf"));
}
}

View File

@ -6,16 +6,11 @@ using System.Threading.Tasks;
namespace NadekoBot.Services.Music
{
public class SoundCloud
public class SoundCloudApiService
{
private readonly IBotCredentials _creds;
//todo make a service
private static SoundCloud _instance = null;
public static SoundCloud GetInstance(IBotCredentials creds) => _instance ?? (_instance = new SoundCloud(creds));
static SoundCloud() { }
public SoundCloud(IBotCredentials creds)
public SoundCloudApiService(IBotCredentials creds)
{
_creds = creds;
}
@ -36,7 +31,7 @@ namespace NadekoBot.Services.Music
}
var responseObj = Newtonsoft.Json.JsonConvert.DeserializeObject<SoundCloudVideo>(response);
var responseObj = JsonConvert.DeserializeObject<SoundCloudVideo>(response);
if (responseObj?.Kind != "track")
throw new InvalidOperationException("Url is either not a track, or it doesn't exist.");
@ -84,7 +79,7 @@ namespace NadekoBot.Services.Music
}
public class SoundCloudUser
{
[Newtonsoft.Json.JsonProperty("username")]
[JsonProperty("username")]
public string Name { get; set; }
}
/*