commit
e03b1690b1
@ -120,6 +120,8 @@ Command and aliases | Description | Usage
|
||||
`.listcustreactg` `.lcrg` | Lists global or server custom reactions (20 commands per page) grouped by trigger, and show a number of responses for each. Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. | `.lcrg 1`
|
||||
`.showcustreact` `.scr` | Shows a custom reaction's response on a given ID. | `.scr 1`
|
||||
`.delcustreact` `.dcr` | Deletes a custom reaction on a specific index. If ran in DM, it is bot owner only and deletes a global custom reaction. If ran in a server, it requires Administration priviledges and removes server custom reaction. | `.dcr 5`
|
||||
`.crstatsclear` | Resets the counters on `.crstats`. You can specify a trigger to clear stats only for that trigger. | `.crstatsclear` or `.crstatsclear rng`
|
||||
`.crstats` | Shows a list of custom reactions and the number of times they have been executed. Paginated with 10 per page. Use `.crstatsclear` to reset the counters. | `.crstats` or `.crstats 3`
|
||||
|
||||
###### [Back to TOC](#table-of-contents)
|
||||
|
||||
@ -157,6 +159,8 @@ Command and aliases | Description | Usage
|
||||
`>publicpoll` `>ppoll` | Creates a public poll which requires users to type a number of the voting option in the channel command is ran in. **Requires ManageMessages server permission.** | `>ppoll Question?;Answer1;Answ 2;A_3`
|
||||
`>pollend` | Stops active poll on this server and prints the results in this channel. **Requires ManageMessages server permission.** | `>pollend`
|
||||
`>cleverbot` | Toggles cleverbot session. When enabled, the bot will reply to messages starting with bot mention in the server. Custom reactions starting with %mention% won't work if cleverbot is enabled. **Requires ManageMessages server permission.** | `>cleverbot`
|
||||
`>hangmanlist` | Shows a list of hangman term types. | `> hangmanlist`
|
||||
`>hangman` | Starts a game of hangman in the channel. Use `>hangmanlist` to see a list of available term types. Defaults to 'all'. | `>hangman` or `>hangman movies`
|
||||
`>pick` | Picks the currency planted in this channel. | `>pick`
|
||||
`>plant` | Spend a unit of currency to plant it in this channel. (If bot is restarted or crashes, the currency will be lost) | `>plant`
|
||||
`>gencurrency` `>gc` | Toggles currency generation on this channel. Every posted message will have chance to spawn currency. Chance is specified by the Bot Owner. (default is 2%) **Requires ManageMessages server permission.** | `>gc`
|
||||
@ -322,6 +326,7 @@ Command and aliases | Description | Usage
|
||||
`~osu` | Shows osu stats for a player. | `~osu Name` or `~osu Name taiko`
|
||||
`~osub` | Shows information about an osu beatmap. | `~osub https://osu.ppy.sh/s/127712`
|
||||
`~osu5` | Displays a user's top 5 plays. | `~osu5 Name`
|
||||
`~overwatch` `~ow` | Show's basic stats on a player (competitive rank, playtime, level etc) Region codes are: `eu` `us` `cn` `kr` | `~ow us Battletag#1337` or `~overwatch eu Battletag#2016`
|
||||
`~placelist` | Shows the list of available tags for the `~place` command. | `~placelist`
|
||||
`~place` | Shows a placeholder image of a given tag. Use `~placelist` to see all available tags. You can specify the width and height of the image as the last two optional arguments. | `~place Cage` or `~place steven 500 400`
|
||||
`~pokemon` `~poke` | Searches for a pokemon. | `~poke Sylveon`
|
||||
|
@ -615,7 +615,9 @@ namespace NadekoBot.Modules.Administration
|
||||
g.GetDefaultChannelAsync()
|
||||
)).ConfigureAwait(false);
|
||||
|
||||
await Task.WhenAll(channels.Select(c => c.SendConfirmAsync($"🆕 Message from {umsg.Author} `[Bot Owner]`:", message)))
|
||||
if (channels == null)
|
||||
return;
|
||||
await Task.WhenAll(channels.Where(c => c != null).Select(c => c.SendConfirmAsync($"🆕 Message from {umsg.Author} `[Bot Owner]`:", message)))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await umsg.Channel.SendConfirmAsync("🆗").ConfigureAwait(false);
|
||||
|
@ -6,6 +6,7 @@ using NadekoBot.Services;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -17,9 +18,13 @@ namespace NadekoBot.Modules.Administration
|
||||
public class AutoAssignRoleCommands
|
||||
{
|
||||
private static Logger _log { get; }
|
||||
//guildid/roleid
|
||||
private static ConcurrentDictionary<ulong, ulong> AutoAssignedRoles { get; }
|
||||
|
||||
static AutoAssignRoleCommands()
|
||||
{
|
||||
AutoAssignedRoles = new ConcurrentDictionary<ulong, ulong>(NadekoBot.AllGuildConfigs.Where(x => x.AutoAssignRoleId != 0)
|
||||
.ToDictionary(k => k.GuildId, v => v.AutoAssignRoleId));
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
NadekoBot.Client.UserJoined += (user) =>
|
||||
{
|
||||
@ -27,15 +32,16 @@ namespace NadekoBot.Modules.Administration
|
||||
{
|
||||
try
|
||||
{
|
||||
GuildConfig conf = NadekoBot.AllGuildConfigs.FirstOrDefault(gc => gc.GuildId == user.Guild.Id);
|
||||
ulong roleId = 0;
|
||||
AutoAssignedRoles.TryGetValue(user.Guild.Id, out roleId);
|
||||
|
||||
if (conf.AutoAssignRoleId == 0)
|
||||
if (roleId == 0)
|
||||
return;
|
||||
|
||||
var role = user.Guild.Roles.FirstOrDefault(r => r.Id == conf.AutoAssignRoleId);
|
||||
var role = user.Guild.Roles.FirstOrDefault(r => r.Id == roleId);
|
||||
|
||||
if (role != null)
|
||||
await user.AddRolesAsync(role);
|
||||
await user.AddRolesAsync(role).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex) { _log.Warn(ex); }
|
||||
});
|
||||
@ -55,9 +61,16 @@ namespace NadekoBot.Modules.Administration
|
||||
{
|
||||
conf = uow.GuildConfigs.For(channel.Guild.Id, set => set);
|
||||
if (role == null)
|
||||
{
|
||||
conf.AutoAssignRoleId = 0;
|
||||
ulong throwaway;
|
||||
AutoAssignedRoles.TryRemove(channel.Guild.Id, out throwaway);
|
||||
}
|
||||
else
|
||||
{
|
||||
conf.AutoAssignRoleId = role.Id;
|
||||
AutoAssignedRoles.AddOrUpdate(channel.Guild.Id, role.Id, (key, val) => role.Id);
|
||||
}
|
||||
|
||||
await uow.CompleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
@ -195,12 +195,8 @@ namespace NadekoBot.Modules.Administration
|
||||
|
||||
if (conf.AutoDeleteSelfAssignedRoleMessages)
|
||||
{
|
||||
var t = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(3000).ConfigureAwait(false);
|
||||
try { await msg.DeleteAsync().ConfigureAwait(false); } catch { } // if 502 or something, i don't want bot crashing
|
||||
try { await usrMsg.DeleteAsync().ConfigureAwait(false); } catch { }
|
||||
});
|
||||
msg.DeleteAfter(3);
|
||||
umsg.DeleteAfter(3);
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,12 +238,8 @@ namespace NadekoBot.Modules.Administration
|
||||
|
||||
if (autoDeleteSelfAssignedRoleMessages)
|
||||
{
|
||||
var t = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(3000).ConfigureAwait(false);
|
||||
try { await msg.DeleteAsync().ConfigureAwait(false); } catch { } // if 502 or something, i don't want bot crashing
|
||||
try { await umsg.DeleteAsync().ConfigureAwait(false); } catch { }
|
||||
});
|
||||
msg.DeleteAfter(3);
|
||||
umsg.DeleteAfter(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,11 +52,7 @@ namespace NadekoBot.Modules.Administration
|
||||
var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false);
|
||||
if (conf.AutoDeleteByeMessagesTimer > 0)
|
||||
{
|
||||
var t = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(conf.AutoDeleteByeMessagesTimer * 1000).ConfigureAwait(false); // 5 minutes
|
||||
try { await toDelete.DeleteAsync().ConfigureAwait(false); } catch { }
|
||||
});
|
||||
toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { _log.Warn(ex); }
|
||||
@ -91,11 +87,7 @@ namespace NadekoBot.Modules.Administration
|
||||
var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false);
|
||||
if (conf.AutoDeleteGreetMessagesTimer > 0)
|
||||
{
|
||||
var t = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(conf.AutoDeleteGreetMessagesTimer * 1000).ConfigureAwait(false); // 5 minutes
|
||||
try { await toDelete.DeleteAsync().ConfigureAwait(false); } catch { }
|
||||
});
|
||||
toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { _log.Warn(ex); }
|
||||
|
@ -11,6 +11,7 @@ using Discord.WebSocket;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Linq;
|
||||
using NadekoBot.Extensions;
|
||||
using System.Threading;
|
||||
|
||||
namespace NadekoBot.Modules.ClashOfClans
|
||||
{
|
||||
@ -19,6 +20,8 @@ namespace NadekoBot.Modules.ClashOfClans
|
||||
{
|
||||
public static ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; } = new ConcurrentDictionary<ulong, List<ClashWar>>();
|
||||
|
||||
private static Timer checkWarTimer { get; }
|
||||
|
||||
static ClashOfClans()
|
||||
{
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
@ -32,13 +35,21 @@ namespace NadekoBot.Modules.ClashOfClans
|
||||
?.GetTextChannel(cw.ChannelId);
|
||||
return cw;
|
||||
})
|
||||
.Where(cw => cw?.Channel != null)
|
||||
.Where(cw => cw.Channel != null)
|
||||
.GroupBy(cw => cw.GuildId)
|
||||
.ToDictionary(g => g.Key, g => g.ToList()));
|
||||
}
|
||||
}
|
||||
public ClashOfClans() : base()
|
||||
{
|
||||
|
||||
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)
|
||||
@ -46,12 +57,21 @@ namespace NadekoBot.Modules.ClashOfClans
|
||||
var Bases = war.Bases;
|
||||
for (var i = 0; i < Bases.Count; i++)
|
||||
{
|
||||
if (Bases[i].CallUser == null) continue;
|
||||
if (!Bases[i].BaseDestroyed && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire)
|
||||
var callUser = Bases[i].CallUser;
|
||||
if (callUser == null) continue;
|
||||
if ((!Bases[i].BaseDestroyed) && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire)
|
||||
{
|
||||
Bases[i] = null;
|
||||
try { await war.Channel.SendErrorAsync($"❗🔰**Claim from @{Bases[i].CallUser} for a war against {war.ShortPrint()} has expired.**").ConfigureAwait(false); } catch { }
|
||||
}
|
||||
if (Bases[i].Stars != 3)
|
||||
Bases[i].BaseDestroyed = true;
|
||||
else
|
||||
Bases[i] = null;
|
||||
try
|
||||
{
|
||||
SaveWar(war);
|
||||
await war.Channel.SendErrorAsync($"❗🔰**Claim from @{Bases[i].CallUser} for a war against {war.ShortPrint()} has expired.**").ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,7 +236,7 @@ namespace NadekoBot.Modules.ClashOfClans
|
||||
{
|
||||
var channel = (ITextChannel)umsg.Channel;
|
||||
|
||||
var warsInfo = GetWarInfo(umsg,number);
|
||||
var warsInfo = GetWarInfo(umsg, number);
|
||||
if (warsInfo == null)
|
||||
{
|
||||
await channel.SendErrorAsync("🔰 That war does not exist.").ConfigureAwait(false);
|
||||
@ -359,4 +379,4 @@ namespace NadekoBot.Modules.ClashOfClans
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -115,7 +115,7 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
reactions.Add(cr);
|
||||
}
|
||||
|
||||
await imsg.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
await imsg.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithTitle("New Custom Reaction")
|
||||
.WithDescription($"#{cr.Id}")
|
||||
.AddField(efb => efb.WithName("Trigger").WithValue(key))
|
||||
@ -225,7 +225,7 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
await imsg.Channel.SendErrorAsync("No custom reaction found with that id.").ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
await imsg.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
await imsg.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithDescription($"#{id}")
|
||||
.AddField(efb => efb.WithName("Trigger").WithValue(found.Trigger))
|
||||
.AddField(efb => efb.WithName("Response").WithValue(found.Response + "\n```css\n" + found.Response + "```"))
|
||||
@ -304,7 +304,7 @@ namespace NadekoBot.Modules.CustomReactions
|
||||
await imsg.Channel.EmbedAsync(ReactionStats.OrderByDescending(x => x.Value)
|
||||
.Skip((page - 1)*9)
|
||||
.Take(9)
|
||||
.Aggregate(new EmbedBuilder().WithColor(NadekoBot.OkColor).WithTitle($"Custom Reaction stats page #{page}"),
|
||||
.Aggregate(new EmbedBuilder().WithOkColor().WithTitle($"Custom Reaction stats page #{page}"),
|
||||
(agg, cur) => agg.AddField(efb => efb.WithName(cur.Key).WithValue(cur.Value.ToString()).WithIsInline(true)))
|
||||
.Build())
|
||||
.ConfigureAwait(false);
|
||||
|
@ -46,54 +46,52 @@ namespace NadekoBot.Modules.Gambling
|
||||
|
||||
await channel.SendFileAsync(imageStream, "dice.png", $"{umsg.Author.Mention} rolled " + Format.Code(gen.ToString())).ConfigureAwait(false);
|
||||
}
|
||||
//todo merge into internallDndRoll and internalRoll
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task Roll(IUserMessage umsg, string arg)
|
||||
|
||||
public enum RollOrderType
|
||||
{
|
||||
var channel = (ITextChannel)umsg.Channel;
|
||||
if (channel == null)
|
||||
return;
|
||||
|
||||
var ordered = true;
|
||||
var rng = new NadekoRandom();
|
||||
Match match;
|
||||
if ((match = dndRegex.Match(arg)).Length != 0)
|
||||
{
|
||||
int n1;
|
||||
int n2;
|
||||
if (int.TryParse(match.Groups["n1"].ToString(), out n1) &&
|
||||
int.TryParse(match.Groups["n2"].ToString(), out n2) &&
|
||||
n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0)
|
||||
{
|
||||
var add = 0;
|
||||
var sub = 0;
|
||||
int.TryParse(match.Groups["add"].Value, out add);
|
||||
int.TryParse(match.Groups["sub"].Value, out sub);
|
||||
|
||||
var arr = new int[n1];
|
||||
for (int i = 0; i < n1; i++)
|
||||
{
|
||||
arr[i] = rng.Next(1, n2 + 1) + add - sub;
|
||||
}
|
||||
var elemCnt = 0;
|
||||
await channel.SendConfirmAsync($"{umsg.Author.Mention} rolled {n1} {(n1 == 1 ? "die" : "dice")} `1 to {n2}` +`{add}` -`{sub}`.\n`Result:` " + string.Join(", ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => elemCnt++ % 2 == 0 ? $"**{x}**" : x.ToString()))).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
Ordered,
|
||||
Unordered
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public async Task Roll(IUserMessage umsg, int num)
|
||||
{
|
||||
await InternalRoll(umsg, num, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(0)]
|
||||
public async Task Rolluo(IUserMessage umsg, int num)
|
||||
{
|
||||
await InternalRoll(umsg, num, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task Roll(IUserMessage umsg, string arg)
|
||||
{
|
||||
await InternallDndRoll(umsg, arg, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public async Task Rolluo(IUserMessage umsg, string arg)
|
||||
{
|
||||
await InternallDndRoll(umsg, arg, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task InternalRoll(IUserMessage umsg, int num, bool ordered)
|
||||
{
|
||||
var channel = (ITextChannel)umsg.Channel;
|
||||
if (channel == null)
|
||||
return;
|
||||
|
||||
var ordered = true;
|
||||
|
||||
if (num < 1 || num > 30)
|
||||
{
|
||||
await channel.SendErrorAsync("Invalid number specified. You can roll up to 1-30 dice at a time.").ConfigureAwait(false);
|
||||
@ -137,15 +135,12 @@ namespace NadekoBot.Modules.Gambling
|
||||
await channel.SendFileAsync(ms, "dice.png", $"{umsg.Author.Mention} rolled {values.Count} {(values.Count == 1 ? "die" : "dice")}. Total: **{values.Sum()}** Average: **{(values.Sum() / (1.0f * values.Count)).ToString("N2")}**").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Rolluo(IUserMessage umsg, string arg)
|
||||
private async Task InternallDndRoll(IUserMessage umsg, string arg, bool ordered)
|
||||
{
|
||||
var channel = (ITextChannel)umsg.Channel;
|
||||
if (channel == null)
|
||||
return;
|
||||
|
||||
var ordered = false;
|
||||
var rng = new NadekoRandom();
|
||||
Match match;
|
||||
if ((match = dndRegex.Match(arg)).Length != 0)
|
||||
@ -172,59 +167,6 @@ namespace NadekoBot.Modules.Gambling
|
||||
}
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Rolluo(IUserMessage umsg, int num)
|
||||
{
|
||||
var channel = (ITextChannel)umsg.Channel;
|
||||
if (channel == null)
|
||||
return;
|
||||
|
||||
var ordered = false;
|
||||
|
||||
if (num < 1 || num > 30)
|
||||
{
|
||||
await channel.SendErrorAsync("Invalid number specified. You can roll up to 1-30 dice at a time.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var rng = new NadekoRandom();
|
||||
|
||||
var dice = new List<Image>(num);
|
||||
var values = new List<int>(num);
|
||||
for (var i = 0; i < num; i++)
|
||||
{
|
||||
var randomNumber = rng.Next(1, 7);
|
||||
var toInsert = dice.Count;
|
||||
if (ordered)
|
||||
{
|
||||
if (randomNumber == 6 || dice.Count == 0)
|
||||
toInsert = 0;
|
||||
else if (randomNumber != 1)
|
||||
for (var j = 0; j < dice.Count; j++)
|
||||
{
|
||||
if (values[j] < randomNumber)
|
||||
{
|
||||
toInsert = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
toInsert = dice.Count;
|
||||
}
|
||||
dice.Insert(toInsert, GetDice(randomNumber));
|
||||
values.Insert(toInsert, randomNumber);
|
||||
}
|
||||
|
||||
var bitmap = dice.Merge();
|
||||
var ms = new MemoryStream();
|
||||
bitmap.SaveAsPng(ms);
|
||||
ms.Position = 0;
|
||||
await channel.SendFileAsync(ms, "dice.png", $"{umsg.Author.Mention} rolled {values.Count} {(values.Count == 1 ? "die" : "dice")}. Total: **{values.Sum()}** Average: **{(values.Sum() / (1.0f * values.Count)).ToString("N2")}**").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task NRoll(IUserMessage umsg, [Remainder] string range)
|
||||
@ -241,7 +183,7 @@ namespace NadekoBot.Modules.Gambling
|
||||
.Select(int.Parse)
|
||||
.ToArray();
|
||||
if (arr[0] > arr[1])
|
||||
throw new ArgumentException("First argument should be bigger than the second one.");
|
||||
throw new ArgumentException("Second argument must be larger than the first one.");
|
||||
rolled = new NadekoRandom().Next(arr[0], arr[1] + 1);
|
||||
}
|
||||
else
|
||||
|
@ -53,7 +53,7 @@ namespace NadekoBot.Modules.Gambling
|
||||
MemoryStream bitmapStream = new MemoryStream();
|
||||
images.Merge().SaveAsPng(bitmapStream);
|
||||
bitmapStream.Position = 0;
|
||||
//todo CARD NAMES?
|
||||
|
||||
var toSend = $"{msg.Author.Mention}";
|
||||
if (cardObjects.Count == 5)
|
||||
toSend += $" drew `{Cards.GetHandValue(cardObjects)}`";
|
||||
|
@ -63,20 +63,11 @@ namespace NadekoBot.Modules.Gambling
|
||||
.ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
// todo update this
|
||||
long userFlowers;
|
||||
using (var uow = DbHandler.UnitOfWork())
|
||||
var removed = await CurrencyHandler.RemoveCurrencyAsync(guildUser, "Betflip Gamble", amount, false).ConfigureAwait(false);
|
||||
if (!removed)
|
||||
{
|
||||
userFlowers = uow.Currency.GetOrCreate(umsg.Author.Id).Amount;
|
||||
await channel.SendErrorAsync($"{guildUser.Mention} You don't have enough {Gambling.CurrencyPluralName}.").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (userFlowers < amount)
|
||||
{
|
||||
await channel.SendErrorAsync($"{umsg.Author.Mention} You don't have enough {Gambling.CurrencyPluralName}. You only have {userFlowers}{Gambling.CurrencySign}.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await CurrencyHandler.RemoveCurrencyAsync(guildUser, "Betflip Gamble", amount, false).ConfigureAwait(false);
|
||||
//heads = true
|
||||
//tails = false
|
||||
|
||||
|
288
src/NadekoBot/Modules/Games/Commands/Acropobia.cs
Normal file
288
src/NadekoBot/Modules/Games/Commands/Acropobia.cs
Normal file
@ -0,0 +1,288 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Attributes;
|
||||
using NadekoBot.Extensions;
|
||||
using NadekoBot.Services;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Games
|
||||
{
|
||||
public partial class Games
|
||||
{
|
||||
[Group]
|
||||
public class Acropobia
|
||||
{
|
||||
//channelId, game
|
||||
public static ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>();
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Acro(IUserMessage imsg, int time = 60)
|
||||
{
|
||||
var channel = (ITextChannel)imsg.Channel;
|
||||
|
||||
var game = new AcrophobiaGame(channel, time);
|
||||
if (AcrophobiaGames.TryAdd(channel.Id, game))
|
||||
{
|
||||
try
|
||||
{
|
||||
await game.Run();
|
||||
}
|
||||
finally
|
||||
{
|
||||
game.EnsureStopped();
|
||||
AcrophobiaGames.TryRemove(channel.Id, out game);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await channel.SendErrorAsync("Acrophobia game is already running in this channel.").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum AcroPhase
|
||||
{
|
||||
Submitting,
|
||||
Idle, // used to wait for some other actions while transitioning through phases
|
||||
Voting
|
||||
}
|
||||
|
||||
public class AcrophobiaGame
|
||||
{
|
||||
private readonly ITextChannel channel;
|
||||
private readonly int time;
|
||||
private readonly NadekoRandom rng;
|
||||
private readonly ImmutableArray<char> startingLetters;
|
||||
private readonly CancellationTokenSource source;
|
||||
private AcroPhase phase { get; set; } = AcroPhase.Submitting;
|
||||
|
||||
private readonly ConcurrentDictionary<string, IGuildUser> submissions = new ConcurrentDictionary<string, IGuildUser>();
|
||||
public IReadOnlyDictionary<string, IGuildUser> Submissions => submissions;
|
||||
|
||||
private readonly ConcurrentHashSet<ulong> usersWhoVoted = new ConcurrentHashSet<ulong>();
|
||||
|
||||
private int spamCount = 0;
|
||||
|
||||
//text, votes
|
||||
private readonly ConcurrentDictionary<string, int> votes = new ConcurrentDictionary<string, int>();
|
||||
|
||||
public AcrophobiaGame(ITextChannel channel, int time)
|
||||
{
|
||||
this.channel = channel;
|
||||
this.time = time;
|
||||
this.source = new CancellationTokenSource();
|
||||
|
||||
this.rng = new NadekoRandom();
|
||||
var wordCount = rng.Next(3, 6);
|
||||
|
||||
var lettersArr = new char[wordCount];
|
||||
|
||||
for (int i = 0; i < wordCount; i++)
|
||||
{
|
||||
var randChar = (char)rng.Next(65, 91);
|
||||
lettersArr[i] = randChar == 'X' ? (char)rng.Next(65, 88) : randChar;
|
||||
}
|
||||
startingLetters = lettersArr.ToImmutableArray();
|
||||
}
|
||||
|
||||
private EmbedBuilder GetEmbed()
|
||||
{
|
||||
var i = 0;
|
||||
return phase == AcroPhase.Submitting
|
||||
|
||||
? new EmbedBuilder().WithOkColor()
|
||||
.WithTitle("Acrophobia")
|
||||
.WithDescription($"Game started. Create a sentence with the following acronym: **{string.Join(".", startingLetters)}.**\n")
|
||||
.WithFooter(efb => efb.WithText("You have " + this.time + " seconds to make a submission."))
|
||||
|
||||
: new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithTitle("Acrophobia - Submissions Closed")
|
||||
.WithDescription($@"Acronym was **{string.Join(".", startingLetters)}.**
|
||||
--
|
||||
{this.submissions.Aggregate("", (agg, cur) => agg + $"`{++i}.` **{cur.Key.ToLowerInvariant().ToTitleCase()}**\n")}
|
||||
--")
|
||||
.WithFooter(efb => efb.WithText("Vote by typing a number of the submission"));
|
||||
}
|
||||
|
||||
public async Task Run()
|
||||
{
|
||||
NadekoBot.Client.MessageReceived += PotentialAcro;
|
||||
var embed = GetEmbed();
|
||||
|
||||
//SUBMISSIONS PHASE
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await Task.Delay(time * 1000, source.Token).ConfigureAwait(false);
|
||||
phase = AcroPhase.Idle;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
if (submissions.Count == 0)
|
||||
{
|
||||
await channel.SendErrorAsync("Acrophobia", "Game ended with no submissions.");
|
||||
return;
|
||||
}
|
||||
else if (submissions.Count == 1)
|
||||
{
|
||||
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithDescription($"{submissions.First().Value.Mention} is the winner for being the only user who made a submission!")
|
||||
.WithFooter(efb => efb.WithText(submissions.First().Key.ToLowerInvariant().ToTitleCase()))
|
||||
.Build()).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var submissionClosedEmbed = GetEmbed();
|
||||
|
||||
await channel.EmbedAsync(submissionClosedEmbed.Build()).ConfigureAwait(false);
|
||||
|
||||
//VOTING PHASE
|
||||
this.phase = AcroPhase.Voting;
|
||||
try
|
||||
{
|
||||
//30 secondds for voting
|
||||
await Task.Delay(30000, source.Token).ConfigureAwait(false);
|
||||
this.phase = AcroPhase.Idle;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await End().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Task PotentialAcro(IMessage arg)
|
||||
{
|
||||
var t = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var msg = arg as IUserMessage;
|
||||
if (msg == null || msg.Author.IsBot || msg.Channel.Id != channel.Id)
|
||||
return;
|
||||
|
||||
++spamCount;
|
||||
|
||||
var guildUser = (IGuildUser)msg.Author;
|
||||
|
||||
var input = msg.Content.ToUpperInvariant().Trim();
|
||||
|
||||
if (phase == AcroPhase.Submitting)
|
||||
{
|
||||
if (spamCount > 10)
|
||||
{
|
||||
spamCount = 0;
|
||||
try { await channel.EmbedAsync(GetEmbed().Build()).ConfigureAwait(false); }
|
||||
catch { }
|
||||
}
|
||||
//user didn't input something already
|
||||
IGuildUser throwaway;
|
||||
if (submissions.TryGetValue(input, out throwaway))
|
||||
return;
|
||||
var inputWords = input.Split(' '); //get all words
|
||||
|
||||
if (inputWords.Length != startingLetters.Length) // number of words must be the same as the number of the starting letters
|
||||
return;
|
||||
|
||||
for (int i = 0; i < startingLetters.Length; i++)
|
||||
{
|
||||
var letter = startingLetters[i];
|
||||
|
||||
if (!inputWords[i].StartsWith(letter.ToString())) // all first letters must match
|
||||
return;
|
||||
}
|
||||
|
||||
//try adding it to the list of answers
|
||||
if (!submissions.TryAdd(input, guildUser))
|
||||
return;
|
||||
|
||||
// all good. valid input. answer recorded
|
||||
await channel.SendConfirmAsync("Acrophobia", $"{guildUser.Mention} submitted their sentence. ({submissions.Count} total)");
|
||||
try
|
||||
{
|
||||
await msg.DeleteAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await msg.DeleteAsync(); //try twice
|
||||
}
|
||||
}
|
||||
else if (phase == AcroPhase.Voting)
|
||||
{
|
||||
if (spamCount > 10)
|
||||
{
|
||||
spamCount = 0;
|
||||
try { await channel.EmbedAsync(GetEmbed().Build()).ConfigureAwait(false); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
IGuildUser usr;
|
||||
//if (submissions.TryGetValue(input, out usr) && usr.Id != guildUser.Id)
|
||||
//{
|
||||
// if (!usersWhoVoted.Add(guildUser.Id))
|
||||
// return;
|
||||
// votes.AddOrUpdate(input, 1, (key, old) => ++old);
|
||||
// await channel.SendConfirmAsync("Acrophobia", $"{guildUser.Mention} cast their vote!").ConfigureAwait(false);
|
||||
// await msg.DeleteAsync().ConfigureAwait(false);
|
||||
// return;
|
||||
//}
|
||||
|
||||
int num;
|
||||
if (int.TryParse(input, out num) && num > 0 && num <= submissions.Count)
|
||||
{
|
||||
var kvp = submissions.Skip(num - 1).First();
|
||||
usr = kvp.Value;
|
||||
//can't vote for yourself, can't vote multiple times
|
||||
if (usr.Id == guildUser.Id || !usersWhoVoted.Add(guildUser.Id))
|
||||
return;
|
||||
votes.AddOrUpdate(kvp.Key, 1, (key, old) => ++old);
|
||||
await channel.SendConfirmAsync("Acrophobia", $"{guildUser.Mention} cast their vote!").ConfigureAwait(false);
|
||||
await msg.DeleteAsync().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task End()
|
||||
{
|
||||
if (!votes.Any())
|
||||
{
|
||||
await channel.SendErrorAsync("Acrophobia", "No votes cast. Game ended with no winner.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
var table = votes.OrderByDescending(v => v.Value);
|
||||
var winner = table.First();
|
||||
var embed = new EmbedBuilder().WithOkColor()
|
||||
.WithTitle("Acrophobia")
|
||||
.WithDescription($"Winner is {submissions[winner.Key].Mention} with {winner.Value} points.\n")
|
||||
.WithFooter(efb => efb.WithText(winner.Key.ToLowerInvariant().ToTitleCase()));
|
||||
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void EnsureStopped()
|
||||
{
|
||||
NadekoBot.Client.MessageReceived -= PotentialAcro;
|
||||
if (!source.IsCancellationRequested)
|
||||
source.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -118,9 +118,9 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
|
||||
.AddField(efb => efb.WithName("It was").WithValue(Term.Word))
|
||||
.WithImage(eib => eib.WithUrl(Term.ImageUrl));
|
||||
if (Errors >= MaxErrors)
|
||||
await GameChannel.EmbedAsync(embed.WithColor(NadekoBot.ErrorColor).Build()).ConfigureAwait(false);
|
||||
await GameChannel.EmbedAsync(embed.WithErrorColor().Build()).ConfigureAwait(false);
|
||||
else
|
||||
await GameChannel.EmbedAsync(embed.WithColor(NadekoBot.OkColor).Build()).ConfigureAwait(false);
|
||||
await GameChannel.EmbedAsync(embed.WithOkColor().Build()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Task PotentialGuess(IMessage msg)
|
||||
@ -143,10 +143,8 @@ namespace NadekoBot.Modules.Games.Commands.Hangman
|
||||
if (!(char.IsLetter(msg.Content[0]) || char.IsDigit(msg.Content[0])))// and a letter or a digit
|
||||
return Task.CompletedTask;
|
||||
|
||||
var guess = char.ToUpperInvariant(msg.Content[0]);
|
||||
// todo hmmmm
|
||||
// how do i want to limit the users on guessing?
|
||||
// one guess every 5 seconds if wrong?
|
||||
var guess = char.ToUpperInvariant(msg.Content[0]);
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
|
@ -17,8 +17,6 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Games
|
||||
{
|
||||
//todo make currency generation change and cooldown modifyable
|
||||
//only by bot owner through commands
|
||||
public partial class Games
|
||||
{
|
||||
/// <summary>
|
||||
@ -119,11 +117,7 @@ namespace NadekoBot.Modules.Games
|
||||
|
||||
await CurrencyHandler.AddCurrencyAsync((IGuildUser)imsg.Author, "Picked flower(s).", msgs.Count, false).ConfigureAwait(false);
|
||||
var msg = await channel.SendConfirmAsync($"**{imsg.Author}** picked {msgs.Count}{Gambling.Gambling.CurrencySign}!").ConfigureAwait(false);
|
||||
var t = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(10000).ConfigureAwait(false);
|
||||
try { await msg.DeleteAsync().ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
|
||||
});
|
||||
msg.DeleteAfter(10);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
|
@ -172,8 +172,7 @@ namespace NadekoBot.Modules.Games
|
||||
else
|
||||
{
|
||||
var toDelete = await ch.SendConfirmAsync($"{msg.Author.Mention} cast their vote.").ConfigureAwait(false);
|
||||
await Task.Delay(5000);
|
||||
await toDelete.DeleteAsync().ConfigureAwait(false);
|
||||
toDelete.DeleteAfter(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ namespace NadekoBot.Modules.Games.Trivia
|
||||
Games.TriviaCommands.RunningTrivias.TryRemove(channel.Guild.Id, out throwaway);
|
||||
try
|
||||
{
|
||||
await channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithTitle("Leaderboard")
|
||||
.WithDescription(GetLeaderboard())
|
||||
.Build(), "Trivia game ended.").ConfigureAwait(false);
|
||||
|
@ -49,7 +49,7 @@ namespace NadekoBot.Modules.Games
|
||||
return;
|
||||
var rng = new NadekoRandom();
|
||||
|
||||
await channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.AddField(efb => efb.WithName("❓ Question").WithValue(question).WithIsInline(false))
|
||||
.AddField(efb => efb.WithName("🎱 8Ball").WithValue(_8BallResponses.Shuffle().FirstOrDefault()).WithIsInline(false))
|
||||
.Build());
|
||||
|
@ -98,7 +98,7 @@ namespace NadekoBot.Modules.Help
|
||||
var embed = new EmbedBuilder()
|
||||
.AddField(fb => fb.WithIndex(1).WithName(str).WithValue($"{ string.Format(com.Summary, com.Module.Prefix)} { GetCommandRequirements(com)}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithIndex(2).WithName("**Usage**").WithValue($"{string.Format(com.Remarks, com.Module.Prefix)}").WithIsInline(false))
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
.WithOkColor();
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
@ -41,8 +41,8 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
|
||||
public float Volume { get; private set; }
|
||||
|
||||
public event EventHandler<Song> OnCompleted = delegate { };
|
||||
public event EventHandler<Song> OnStarted = delegate { };
|
||||
public event Action<MusicPlayer, Song> OnCompleted = delegate { };
|
||||
public event Action<MusicPlayer, Song> OnStarted = delegate { };
|
||||
public event Action<bool> OnPauseChanged = delegate { };
|
||||
|
||||
public IVoiceChannel PlaybackVoiceChannel { get; private set; }
|
||||
@ -130,6 +130,7 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
Console.WriteLine("Music thread almost crashed.");
|
||||
Console.WriteLine(ex);
|
||||
await Task.Delay(3000).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -250,11 +251,11 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
var curSong = CurrentSong;
|
||||
var toUpdate = playlist.Where(s => s.SongInfo.ProviderType == MusicType.Normal &&
|
||||
s.TotalLength == TimeSpan.Zero);
|
||||
s.TotalTime == TimeSpan.Zero);
|
||||
if (curSong != null)
|
||||
toUpdate = toUpdate.Append(curSong);
|
||||
var ids = toUpdate.Select(s => s.SongInfo.Query.Substring(s.SongInfo.Query.LastIndexOf("?v=") + 3))
|
||||
.Distinct();
|
||||
.Distinct();
|
||||
|
||||
var durations = await NadekoBot.Google.GetVideoDurationsAsync(ids);
|
||||
|
||||
@ -264,11 +265,12 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
if (s.SongInfo.Query.EndsWith(kvp.Key))
|
||||
{
|
||||
s.TotalLength = kvp.Value;
|
||||
s.TotalTime = kvp.Value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
|
15
src/NadekoBot/Modules/Music/Classes/MusicExtensions.cs
Normal file
15
src/NadekoBot/Modules/Music/Classes/MusicExtensions.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Discord;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
public static class MusicExtensions
|
||||
{
|
||||
public static EmbedAuthorBuilder WithMusicIcon(this EmbedAuthorBuilder eab) =>
|
||||
eab.WithIconUrl("http://i.imgur.com/nhKS3PT.png");
|
||||
}
|
||||
}
|
@ -18,42 +18,20 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
public string Provider { get; set; }
|
||||
public MusicType ProviderType { get; set; }
|
||||
/// <summary>
|
||||
/// Will be set only if the providertype is normal
|
||||
/// </summary>
|
||||
public string Query { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Uri { get; set; }
|
||||
public string AlbumArt { get; set; }
|
||||
public string AlbumArt { get; set; }
|
||||
}
|
||||
|
||||
public class Song
|
||||
{
|
||||
public StreamState State { get; set; }
|
||||
public string PrettyName =>
|
||||
$"**{SongInfo.Title.TrimTo(55)} `{(SongInfo.Provider ?? "-")} by {QueuerName}`**";
|
||||
//$"{SongInfo.Title.TrimTo(70)}";
|
||||
public SongInfo SongInfo { get; }
|
||||
public MusicPlayer MusicPlayer { get; set; }
|
||||
|
||||
public string PrettyUser =>
|
||||
$"{QueuerName}";
|
||||
public string QueuerName { get; set; }
|
||||
|
||||
public string PrettyProvider =>
|
||||
$"{(SongInfo.Provider ?? "No Provider")}";
|
||||
|
||||
public string PrettyCurrentTime()
|
||||
{
|
||||
var time = TimeSpan.FromSeconds(bytesSent / 3840 / 50);
|
||||
var str = $"{(int)time.TotalMinutes}m {time.Seconds}s / ";
|
||||
if (TotalLength == TimeSpan.Zero)
|
||||
str += "**?**";
|
||||
else if (TotalLength == TimeSpan.MaxValue)
|
||||
str += "**∞**";
|
||||
else
|
||||
str += $"{(int)TotalLength.TotalMinutes}m {TotalLength.Seconds}s";
|
||||
return str;
|
||||
}
|
||||
public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
|
||||
public TimeSpan CurrentTime => TimeSpan.FromSeconds(bytesSent / frameBytes / (1000 / milliseconds));
|
||||
|
||||
const int milliseconds = 20;
|
||||
const int samplesPerFrame = (48000 / 1000) * milliseconds;
|
||||
@ -61,11 +39,33 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
|
||||
private ulong bytesSent { get; set; } = 0;
|
||||
|
||||
public bool PrintStatusMessage { get; set; } = true;
|
||||
//pwetty
|
||||
|
||||
public string PrettyProvider =>
|
||||
$"{(SongInfo.Provider ?? "No Provider")}";
|
||||
|
||||
public string PrettyFullTime => PrettyCurrentTime + " / " + PrettyTotalTime;
|
||||
|
||||
public string PrettyName => $"**[{SongInfo.Title.TrimTo(70)}]({SongInfo.Query})**";
|
||||
|
||||
public string PrettyInfo => $"{PrettyTotalTime} | {PrettyProvider} | {QueuerName}";
|
||||
|
||||
public string PrettyFullName => $"{PrettyName}\n\t\t*{PrettyInfo}*";
|
||||
|
||||
public string PrettyCurrentTime => CurrentTime.ToString(@"mm\:ss");
|
||||
|
||||
private string PrettyTotalTime {
|
||||
get {
|
||||
if (TotalTime == TimeSpan.Zero)
|
||||
return "(?)";
|
||||
else if (TotalTime == TimeSpan.MaxValue)
|
||||
return "**∞**";
|
||||
else
|
||||
return TotalTime.ToString(@"mm\:ss");
|
||||
}
|
||||
}
|
||||
|
||||
private int skipTo = 0;
|
||||
private Logger _log;
|
||||
|
||||
public int SkipTo {
|
||||
get { return skipTo; }
|
||||
set {
|
||||
@ -74,7 +74,7 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan TotalLength { get; set; } = TimeSpan.Zero;
|
||||
private readonly Logger _log;
|
||||
|
||||
public Song(SongInfo songInfo)
|
||||
{
|
||||
@ -86,16 +86,9 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
var s = new Song(SongInfo);
|
||||
s.MusicPlayer = MusicPlayer;
|
||||
s.State = StreamState.Queued;
|
||||
return s;
|
||||
}
|
||||
|
||||
public Song SetMusicPlayer(MusicPlayer mp)
|
||||
{
|
||||
this.MusicPlayer = mp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
|
||||
{
|
||||
var filename = Path.Combine(Music.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
|
||||
@ -105,7 +98,7 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
|
||||
try
|
||||
{
|
||||
var attempt = 0;
|
||||
var attempt = 0;
|
||||
|
||||
var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 1.MiB()); //Fast connection can do this easy
|
||||
var finished = false;
|
||||
@ -126,7 +119,7 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
_log.Warn("Slow connection buffering more to ensure no disruption, consider hosting in cloud");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (inStream.BufferingCompleted && count == 1)
|
||||
{
|
||||
_log.Debug("Prebuffering canceled. Cannot get any data from the stream.");
|
||||
@ -136,7 +129,7 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (prebufferingTask.IsCanceled)
|
||||
{
|
||||
_log.Debug("Prebuffering canceled. Cannot get any data from the stream.");
|
||||
@ -145,7 +138,7 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
finished = true;
|
||||
}
|
||||
sw.Stop();
|
||||
_log.Debug("Prebuffering successfully completed in "+ sw.Elapsed);
|
||||
_log.Debug("Prebuffering successfully completed in " + sw.Elapsed);
|
||||
|
||||
var outStream = voiceClient.CreatePCMStream(960);
|
||||
|
||||
@ -157,7 +150,7 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
//Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------");
|
||||
var read = await inStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
||||
//await inStream.CopyToAsync(voiceClient.OutputStream);
|
||||
if(read < frameBytes)
|
||||
if (read < frameBytes)
|
||||
_log.Debug("read {0}", read);
|
||||
unchecked
|
||||
{
|
||||
@ -177,10 +170,15 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
if (slowconnection)
|
||||
{
|
||||
_log.Warn("Slow connection has disrupted music, waiting a bit for buffer");
|
||||
|
||||
await Task.Delay(1000, cancelToken).ConfigureAwait(false);
|
||||
nextTime = Environment.TickCount + milliseconds;
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(100, cancelToken).ConfigureAwait(false);
|
||||
nextTime = Environment.TickCount + milliseconds;
|
||||
}
|
||||
}
|
||||
else
|
||||
attempt = 0;
|
||||
@ -189,7 +187,10 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
attempt = 0;
|
||||
|
||||
while (this.MusicPlayer.Paused)
|
||||
{
|
||||
await Task.Delay(200, cancelToken).ConfigureAwait(false);
|
||||
nextTime = Environment.TickCount + milliseconds;
|
||||
}
|
||||
|
||||
|
||||
buffer = AdjustVolume(buffer, MusicPlayer.Volume);
|
||||
@ -204,7 +205,7 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
finally
|
||||
{
|
||||
await bufferTask;
|
||||
if(inStream != null)
|
||||
if (inStream != null)
|
||||
inStream.Dispose();
|
||||
}
|
||||
}
|
||||
@ -218,35 +219,6 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
_log.Debug("Buffering successfull");
|
||||
}
|
||||
|
||||
/*
|
||||
//stackoverflow ftw
|
||||
private static byte[] AdjustVolume(byte[] audioSamples, float volume)
|
||||
{
|
||||
if (Math.Abs(volume - 1.0f) < 0.01f)
|
||||
return audioSamples;
|
||||
var array = new byte[audioSamples.Length];
|
||||
for (var i = 0; i < array.Length; i += 2)
|
||||
{
|
||||
|
||||
// convert byte pair to int
|
||||
short buf1 = audioSamples[i + 1];
|
||||
short buf2 = audioSamples[i];
|
||||
|
||||
buf1 = (short)((buf1 & 0xff) << 8);
|
||||
buf2 = (short)(buf2 & 0xff);
|
||||
|
||||
var res = (short)(buf1 | buf2);
|
||||
res = (short)(res * volume);
|
||||
|
||||
// convert back
|
||||
array[i] = (byte)res;
|
||||
array[i + 1] = (byte)(res >> 8);
|
||||
|
||||
}
|
||||
return array;
|
||||
}
|
||||
*/
|
||||
|
||||
//aidiakapi ftw
|
||||
public unsafe static byte[] AdjustVolume(byte[] audioSamples, float volume)
|
||||
{
|
||||
@ -272,202 +244,5 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
|
||||
return audioSamples;
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
{ TotalLength = TimeSpan.MaxValue };
|
||||
}
|
||||
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,
|
||||
})
|
||||
{ TotalLength = 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.Normal,
|
||||
Query = svideo.TrackLink,
|
||||
AlbumArt = svideo.artwork_url,
|
||||
})
|
||||
{ TotalLength = 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 = video.Uri,
|
||||
Query = link,
|
||||
ProviderType = musicType,
|
||||
});
|
||||
song.SkipTo = gotoTime;
|
||||
return song;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed resolving the link.{ex.Message}");
|
||||
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
|
||||
{
|
||||
Console.WriteLine($"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
|
||||
{
|
||||
Console.WriteLine($"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
|
||||
{
|
||||
Console.WriteLine($"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
|
||||
{
|
||||
Console.WriteLine($"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"));
|
||||
}
|
||||
}
|
||||
}
|
@ -25,21 +25,21 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
_log = LogManager.GetCurrentClassLogger();
|
||||
}
|
||||
|
||||
MusicPlayer MusicPlayer;
|
||||
MusicPlayer MusicPlayer { get; }
|
||||
|
||||
private string Basename;
|
||||
private string Basename { get; }
|
||||
|
||||
private SongInfo SongInfo;
|
||||
private SongInfo SongInfo { get; }
|
||||
|
||||
private int SkipTo;
|
||||
private int SkipTo { get; }
|
||||
|
||||
private int MaxFileSize = 2.MiB();
|
||||
private int MaxFileSize { get; } = 2.MiB();
|
||||
|
||||
private long FileNumber = -1;
|
||||
|
||||
private long NextFileToRead = 0;
|
||||
|
||||
public bool BufferingCompleted { get; private set;} = false;
|
||||
public bool BufferingCompleted { get; private set; } = false;
|
||||
|
||||
private ulong CurrentBufferSize = 0;
|
||||
|
||||
@ -76,7 +76,8 @@ namespace NadekoBot.Modules.Music.Classes
|
||||
try
|
||||
{
|
||||
outStream.Dispose();
|
||||
}catch { }
|
||||
}
|
||||
catch { }
|
||||
outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read);
|
||||
currentFileSize = bytesRead;
|
||||
}
|
||||
@ -108,8 +109,8 @@ Check the guides for your platform on how to setup ffmpeg correctly:
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(outStream != null)
|
||||
outStream.Dispose();
|
||||
if (outStream != null)
|
||||
outStream.Dispose();
|
||||
Console.WriteLine($"Buffering done.");
|
||||
if (p != null)
|
||||
{
|
||||
@ -130,7 +131,7 @@ Check the guides for your platform on how to setup ffmpeg correctly:
|
||||
private string GetNextFile()
|
||||
{
|
||||
string filename = Basename + "-" + NextFileToRead;
|
||||
|
||||
|
||||
if (NextFileToRead != 0)
|
||||
{
|
||||
try
|
||||
@ -151,7 +152,7 @@ Check the guides for your platform on how to setup ffmpeg correctly:
|
||||
|
||||
private void CleanFiles()
|
||||
{
|
||||
for (long i = NextFileToRead - 1 ; i <= FileNumber; i++)
|
||||
for (long i = NextFileToRead - 1; i <= FileNumber; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -169,7 +170,7 @@ Check the guides for your platform on how to setup ffmpeg correctly:
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override long Length => (long) CurrentBufferSize;
|
||||
public override long Length => (long)CurrentBufferSize;
|
||||
|
||||
public override long Position { get; set; } = 0;
|
||||
|
||||
@ -178,7 +179,7 @@ Check the guides for your platform on how to setup ffmpeg correctly:
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int read = CurrentFileStream.Read(buffer, offset, count);
|
||||
if(read < count)
|
||||
if (read < count)
|
||||
{
|
||||
if (!BufferingCompleted || IsNextFileReady())
|
||||
{
|
||||
@ -215,4 +216,4 @@ Check the guides for your platform on how to setup ffmpeg correctly:
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
212
src/NadekoBot/Modules/Music/Classes/SongHandler.cs
Normal file
212
src/NadekoBot/Modules/Music/Classes/SongHandler.cs
Normal file
@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using VideoLibrary;
|
||||
|
||||
namespace NadekoBot.Modules.Music.Classes
|
||||
{
|
||||
public static class SongHandler
|
||||
{
|
||||
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 };
|
||||
}
|
||||
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.Normal,
|
||||
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 = video.Uri,
|
||||
Query = link,
|
||||
ProviderType = musicType,
|
||||
});
|
||||
song.SkipTo = gotoTime;
|
||||
return song;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed resolving the link.{ex.Message}");
|
||||
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
|
||||
{
|
||||
Console.WriteLine($"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
|
||||
{
|
||||
Console.WriteLine($"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
|
||||
{
|
||||
Console.WriteLine($"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
|
||||
{
|
||||
Console.WriteLine($"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"));
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
namespace NadekoBot.Modules.Music
|
||||
{
|
||||
@ -29,13 +30,13 @@ namespace NadekoBot.Modules.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;
|
||||
|
||||
NadekoBot.Client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated;
|
||||
|
||||
Directory.CreateDirectory(MusicDataPath);
|
||||
}
|
||||
|
||||
private Task Client_UserVoiceStateUpdated(IUser iusr, IVoiceState oldState, IVoiceState newState)
|
||||
|
||||
private Task Client_UserVoiceStateUpdated(IUser iusr, IVoiceState oldState, IVoiceState newState)
|
||||
{
|
||||
var usr = iusr as IGuildUser;
|
||||
if (usr == null ||
|
||||
@ -98,16 +99,17 @@ namespace NadekoBot.Modules.Music
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Destroy(IUserMessage umsg)
|
||||
public async Task Destroy(IUserMessage umsg)
|
||||
{
|
||||
var channel = (ITextChannel)umsg.Channel;
|
||||
await channel.SendErrorAsync("This command is temporarily disabled.").ConfigureAwait(false);
|
||||
|
||||
MusicPlayer musicPlayer;
|
||||
/*MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) return Task.CompletedTask;
|
||||
if (((IGuildUser)umsg.Author).VoiceChannel == musicPlayer.PlaybackVoiceChannel)
|
||||
if(MusicPlayers.TryRemove(channel.Guild.Id, out musicPlayer))
|
||||
musicPlayer.Destroy();
|
||||
return Task.CompletedTask;
|
||||
return Task.CompletedTask;*/
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
@ -133,8 +135,7 @@ namespace NadekoBot.Modules.Music
|
||||
await QueueSong(((IGuildUser)umsg.Author), channel, ((IGuildUser)umsg.Author).VoiceChannel, query).ConfigureAwait(false);
|
||||
if (channel.Guild.GetCurrentUser().GetPermissions(channel).ManageMessages)
|
||||
{
|
||||
await Task.Delay(10000).ConfigureAwait(false);
|
||||
await ((IUserMessage)umsg).DeleteAsync().ConfigureAwait(false);
|
||||
umsg.DeleteAfter(10);
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,8 +148,7 @@ namespace NadekoBot.Modules.Music
|
||||
await QueueSong(((IGuildUser)umsg.Author), channel, ((IGuildUser)umsg.Author).VoiceChannel, query, musicType: MusicType.Soundcloud).ConfigureAwait(false);
|
||||
if (channel.Guild.GetCurrentUser().GetPermissions(channel).ManageMessages)
|
||||
{
|
||||
await Task.Delay(10000).ConfigureAwait(false);
|
||||
await ((IUserMessage)umsg).DeleteAsync().ConfigureAwait(false);
|
||||
umsg.DeleteAfter(10);
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,28 +168,40 @@ namespace NadekoBot.Modules.Music
|
||||
|
||||
var currentSong = musicPlayer.CurrentSong;
|
||||
if (currentSong == null)
|
||||
return;
|
||||
|
||||
if (currentSong.TotalLength == TimeSpan.Zero)
|
||||
{
|
||||
await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false);
|
||||
await channel.SendErrorAsync("🎵 No active music player.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
//var toSend = $"🎵 Currently Playing {currentSong.PrettyName} " + $"`{currentSong.PrettyCurrentTime()}`\n";
|
||||
var toSend = $"🎵 Currently Playing {currentSong.PrettyName}\n";
|
||||
if (musicPlayer.RepeatSong)
|
||||
toSend += "🔂";
|
||||
else if (musicPlayer.RepeatPlaylist)
|
||||
toSend += "🔁";
|
||||
toSend += $" `{musicPlayer.Playlist.Count} tracks currently queued. Showing page {page}:` ";
|
||||
if (musicPlayer.MaxQueueSize != 0 && musicPlayer.Playlist.Count >= musicPlayer.MaxQueueSize)
|
||||
toSend += "**Song queue is full!**\n";
|
||||
else
|
||||
toSend += "\n";
|
||||
const int itemsPerPage = 15;
|
||||
try { await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); } catch { }
|
||||
|
||||
const int itemsPerPage = 10;
|
||||
int startAt = itemsPerPage * (page - 1);
|
||||
var number = 1 + startAt;
|
||||
await channel.SendConfirmAsync(toSend + string.Join("\n", musicPlayer.Playlist.Skip(startAt).Take(15).Select(v => $"`{number++}.` {v.PrettyName}"))).ConfigureAwait(false);
|
||||
var number = 0 + startAt;
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithAuthor(eab => eab.WithName($"Player Queue - Page {page}")
|
||||
.WithMusicIcon())
|
||||
.WithDescription(string.Join("\n", musicPlayer.Playlist
|
||||
.Skip(startAt)
|
||||
.Take(10)
|
||||
.Select(v => $"`{++number}.` {v.PrettyFullName}")))
|
||||
.WithFooter(ef => ef.WithText($"{musicPlayer.Playlist.Count} tracks currently queued."))
|
||||
.WithOkColor();
|
||||
|
||||
if (musicPlayer.RepeatSong)
|
||||
{
|
||||
embed.WithTitle($"🔂 Repeating Song: {currentSong.SongInfo.Title} | {currentSong.PrettyFullTime}");
|
||||
}
|
||||
else if (musicPlayer.RepeatPlaylist)
|
||||
{
|
||||
embed.WithTitle("🔁 Repeating Playlist");
|
||||
}
|
||||
if (musicPlayer.MaxQueueSize != 0 && musicPlayer.Playlist.Count >= musicPlayer.MaxQueueSize)
|
||||
{
|
||||
embed.WithTitle("🎵 Song queue is full!");
|
||||
}
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
@ -197,35 +209,23 @@ namespace NadekoBot.Modules.Music
|
||||
public async Task NowPlaying(IUserMessage umsg)
|
||||
{
|
||||
var channel = (ITextChannel)umsg.Channel;
|
||||
|
||||
|
||||
MusicPlayer musicPlayer;
|
||||
if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer))
|
||||
return;
|
||||
var currentSong = musicPlayer.CurrentSong;
|
||||
if (currentSong == null)
|
||||
return;
|
||||
var videoid = Regex.Match(currentSong.SongInfo.Query, "<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+");
|
||||
var videoid = Regex.Match(currentSong.SongInfo.Query, "<=v=[a-zA-Z0-9-]+(?=&)|(?<=[0-9])[^&\n]+|(?<=v=)[^&\n]+");
|
||||
|
||||
if (currentSong.TotalLength == TimeSpan.Zero)
|
||||
{
|
||||
await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false);
|
||||
}
|
||||
var embed = new EmbedBuilder()
|
||||
.WithAuthor(eab => eab.WithName("Now Playing").WithIconUrl("https://cdn.discordapp.com/attachments/155726317222887425/258605269972549642/music1.png"))
|
||||
.WithTitle($"{currentSong.SongInfo.Title}")
|
||||
.WithUrl($"{currentSong.SongInfo.Query}")
|
||||
.WithDescription($"{currentSong.PrettyCurrentTime()}")
|
||||
.WithFooter(ef => ef.WithText($"{currentSong.PrettyProvider} | {currentSong.PrettyUser}"))
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
if (currentSong.SongInfo.Provider.Equals("YouTube", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
embed.WithThumbnail(tn => tn.Url = $"https://img.youtube.com/vi/{videoid}/0.jpg");
|
||||
}
|
||||
else if (currentSong.SongInfo.Provider.Equals("SoundCloud", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
embed.WithThumbnail(tn => tn.Url = $"{currentSong.SongInfo.AlbumArt}");
|
||||
}
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
var embed = new EmbedBuilder()
|
||||
.WithAuthor(eab => eab.WithName("Now Playing")
|
||||
.WithMusicIcon())
|
||||
.WithDescription(currentSong.PrettyName)
|
||||
.WithFooter(ef => ef.WithText(currentSong.PrettyCurrentTime + "/" + currentSong.PrettyInfo))
|
||||
.WithOkColor();
|
||||
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
@ -310,17 +310,25 @@ namespace NadekoBot.Modules.Music
|
||||
}
|
||||
var idArray = ids as string[] ?? ids.ToArray();
|
||||
var count = idArray.Length;
|
||||
|
||||
var msg =
|
||||
await channel.SendMessageAsync($"🎵 Attempting to queue **{count}** songs".SnPl(count) + "...").ConfigureAwait(false);
|
||||
foreach (var id in idArray)
|
||||
var cancelSource = new CancellationTokenSource();
|
||||
|
||||
var tasks = Task.WhenAll(idArray.Select(async id =>
|
||||
{
|
||||
if (cancelSource.Token.IsCancellationRequested)
|
||||
return;
|
||||
try
|
||||
{
|
||||
await QueueSong(((IGuildUser)umsg.Author), channel, ((IGuildUser)umsg.Author).VoiceChannel, id, true).ConfigureAwait(false);
|
||||
}
|
||||
catch (SongNotFoundException) { }
|
||||
catch { break; }
|
||||
}
|
||||
catch { try { cancelSource.Cancel(); } catch { } }
|
||||
}));
|
||||
|
||||
await Task.WhenAny(tasks, Task.Delay(Timeout.Infinite, cancelSource.Token));
|
||||
|
||||
await msg.ModifyAsync(m => m.Content = "✅ Playlist queue complete.").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@ -405,8 +413,7 @@ namespace NadekoBot.Modules.Music
|
||||
await QueueSong(((IGuildUser)umsg.Author), channel, ((IGuildUser)umsg.Author).VoiceChannel, radio_link, musicType: MusicType.Radio).ConfigureAwait(false);
|
||||
if (channel.Guild.GetCurrentUser().GetPermissions(channel).ManageMessages)
|
||||
{
|
||||
await Task.Delay(10000).ConfigureAwait(false);
|
||||
await ((IUserMessage)umsg).DeleteAsync().ConfigureAwait(false);
|
||||
umsg.DeleteAfter(10);
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,7 +460,14 @@ namespace NadekoBot.Modules.Music
|
||||
return;
|
||||
var song = (musicPlayer.Playlist as List<Song>)?[num - 1];
|
||||
musicPlayer.RemoveSongAt(num - 1);
|
||||
await channel.SendConfirmAsync($"🎵 Track {song.PrettyName} at position `#{num}` has been **removed**.").ConfigureAwait(false);
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithAuthor(eab => eab.WithName("Song Removed!").WithMusicIcon())
|
||||
.AddField(fb => fb.WithName("**Song Position**").WithValue($"#{num}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Song Name**").WithValue($"**[{song.SongInfo.Title.TrimTo(70)}]({song.SongInfo.Query})** `{song.PrettyProvider} | {song.QueuerName.TrimTo(15)}`").WithIsInline(true))
|
||||
.WithErrorColor();
|
||||
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
@ -502,19 +516,15 @@ namespace NadekoBot.Modules.Music
|
||||
playlist.Insert(n2 - 1, s);
|
||||
var nn1 = n2 < n1 ? n1 : n1 - 1;
|
||||
playlist.RemoveAt(nn1);
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle($"{s.SongInfo.Title.TrimTo(70)}")
|
||||
.WithUrl($"{s.SongInfo.Query}")
|
||||
.WithAuthor(eab => eab.WithName("Song Moved").WithIconUrl("https://cdn.discordapp.com/attachments/155726317222887425/258605269972549642/music1.png"))
|
||||
.AddField(fb => fb.WithName("**From Position**").WithValue($"#{n1}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**To Position**").WithValue($"#{n2}").WithIsInline(true))
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.WithTitle($"{s.SongInfo.Title.TrimTo(70)}")
|
||||
.WithUrl($"{s.SongInfo.Query}")
|
||||
.WithAuthor(eab => eab.WithName("Song Moved").WithMusicIcon())
|
||||
.AddField(fb => fb.WithName("**From Position**").WithValue($"#{n1}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**To Position**").WithValue($"#{n2}").WithIsInline(true))
|
||||
.WithOkColor();
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
|
||||
//await channel.SendConfirmAsync($"🎵Moved {s.PrettyName} `from #{n1} to #{n2}`").ConfigureAwait(false);
|
||||
|
||||
|
||||
}
|
||||
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
@ -543,9 +553,15 @@ namespace NadekoBot.Modules.Music
|
||||
if (currentSong == null)
|
||||
return;
|
||||
var currentValue = musicPlayer.ToggleRepeatSong();
|
||||
await channel.SendConfirmAsync(currentValue ?
|
||||
$"🔂 Repeating track: {currentSong.PrettyName}" :
|
||||
$"🔂 Current track repeat stopped.")
|
||||
|
||||
if (currentValue)
|
||||
await channel.EmbedAsync(new EmbedBuilder()
|
||||
.WithOkColor()
|
||||
.WithAuthor(eab => eab.WithMusicIcon().WithName("🔂 Repeating track"))
|
||||
.WithDescription(currentSong.PrettyFullName)
|
||||
.Build()).ConfigureAwait(false);
|
||||
else
|
||||
await channel.SendConfirmAsync($"🔂 Current track repeat stopped.")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@ -572,7 +588,8 @@ namespace NadekoBot.Modules.Music
|
||||
|
||||
var curSong = musicPlayer.CurrentSong;
|
||||
var songs = musicPlayer.Playlist.Append(curSong)
|
||||
.Select(s=> new PlaylistSong() {
|
||||
.Select(s => new PlaylistSong()
|
||||
{
|
||||
Provider = s.SongInfo.Provider,
|
||||
ProviderType = s.SongInfo.ProviderType,
|
||||
Title = s.SongInfo.Title,
|
||||
@ -646,9 +663,12 @@ namespace NadekoBot.Modules.Music
|
||||
playlists = uow.MusicPlaylists.GetPlaylistsOnPage(num);
|
||||
}
|
||||
|
||||
await channel.SendConfirmAsync($@"🎶 **Page {num} of saved playlists:**
|
||||
var embed = new EmbedBuilder()
|
||||
.WithAuthor(eab => eab.WithName($"Page {num} of Saved Playlists").WithMusicIcon())
|
||||
.WithDescription(string.Join("\n", playlists.Select(r => $"`#{r.Id}` - **{r.Name}**\t by **`{r.Author}`**\t ({r.Songs.Count} songs)")))
|
||||
.WithOkColor();
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
|
||||
" + string.Join("\n", playlists.Select(r => $"`#{r.Id}` - **{r.Name}** by __{r.Author}__ ({r.Songs.Count} songs)"))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
//todo only author or owner
|
||||
@ -750,7 +770,7 @@ namespace NadekoBot.Modules.Music
|
||||
}
|
||||
else
|
||||
{
|
||||
await channel.SendConfirmAsync($"🎶 Selected song **{selSong.SongInfo.Title}**: <{selSong.SongInfo.Query}>").ConfigureAwait(false);
|
||||
await channel.SendMessageAsync($"🎶 Selected song **{selSong.SongInfo.Title}**: <{selSong.SongInfo.Query}>").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -758,7 +778,7 @@ namespace NadekoBot.Modules.Music
|
||||
var curSong = musicPlayer.CurrentSong;
|
||||
if (curSong == null)
|
||||
return;
|
||||
await channel.SendConfirmAsync($"🎶 Current song **{curSong.SongInfo.Title}**: <{curSong.SongInfo.Query}>").ConfigureAwait(false);
|
||||
await channel.SendMessageAsync($"🎶 Current song **{curSong.SongInfo.Title}**: <{curSong.SongInfo.Query}>").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -796,47 +816,61 @@ namespace NadekoBot.Modules.Music
|
||||
vol = uow.GuildConfigs.For(textCh.Guild.Id, set => set).DefaultMusicVolume;
|
||||
}
|
||||
var mp = new MusicPlayer(voiceCh, vol);
|
||||
IUserMessage playingMessage = null;
|
||||
IUserMessage lastFinishedMessage = null;
|
||||
mp.OnCompleted += async (s, song) =>
|
||||
{
|
||||
if (song.PrintStatusMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (lastFinishedMessage != null)
|
||||
await lastFinishedMessage.DeleteAsync().ConfigureAwait(false);
|
||||
if (playingMessage != null)
|
||||
await playingMessage.DeleteAsync().ConfigureAwait(false);
|
||||
try { lastFinishedMessage = await textCh.SendConfirmAsync($"🎵 Finished {song.PrettyName}").ConfigureAwait(false); } catch { }
|
||||
if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.Provider == "YouTube")
|
||||
{
|
||||
await QueueSong(queuer.Guild.GetCurrentUser(), textCh, voiceCh, (await NadekoBot.Google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList().Shuffle().FirstOrDefault(), silent, musicType).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
};
|
||||
mp.OnStarted += async (s, song) =>
|
||||
{
|
||||
if (song.PrintStatusMessage)
|
||||
{
|
||||
var sender = s as MusicPlayer;
|
||||
if (sender == null)
|
||||
return;
|
||||
|
||||
var msgTxt = $"🎵 Playing {song.PrettyName}\t `Vol: {(int)(sender.Volume * 100)}%`";
|
||||
try { playingMessage = await textCh.SendConfirmAsync(msgTxt).ConfigureAwait(false); } catch { }
|
||||
}
|
||||
};
|
||||
mp.OnPauseChanged += async (paused) =>
|
||||
IUserMessage finishedMessage = null;
|
||||
mp.OnCompleted += async (s, song) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (finishedMessage != null)
|
||||
finishedMessage.DeleteAfter(0);
|
||||
finishedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName("Finished Song").WithMusicIcon())
|
||||
.WithDescription(song.PrettyName)
|
||||
.WithFooter(ef => ef.WithText(song.PrettyInfo))
|
||||
.Build())
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.Provider == "YouTube")
|
||||
{
|
||||
await QueueSong(queuer.Guild.GetCurrentUser(), textCh, voiceCh, (await NadekoBot.Google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList().Shuffle().FirstOrDefault(), silent, musicType).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
};
|
||||
IUserMessage playingMessage = null;
|
||||
mp.OnStarted += async (player, song) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (playingMessage != null)
|
||||
playingMessage.DeleteAfter(0);
|
||||
|
||||
playingMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName("Playing Song").WithMusicIcon())
|
||||
.WithDescription(song.PrettyName)
|
||||
.WithFooter(ef => ef.WithText(song.PrettyInfo))
|
||||
.Build())
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
};
|
||||
|
||||
mp.OnPauseChanged += async (paused) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
IUserMessage pauseMessage = null;
|
||||
if (paused)
|
||||
await textCh.SendConfirmAsync("🎵 Music playback **paused**.").ConfigureAwait(false);
|
||||
{
|
||||
pauseMessage = await textCh.SendConfirmAsync("🎵 Music playback **paused**.").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
await textCh.SendConfirmAsync("🎵 Music playback **resumed**.").ConfigureAwait(false);
|
||||
{
|
||||
pauseMessage = await textCh.SendConfirmAsync("🎵 Music playback **resumed**.").ConfigureAwait(false);
|
||||
}
|
||||
if (pauseMessage != null)
|
||||
pauseMessage.DeleteAfter(15);
|
||||
}
|
||||
catch { }
|
||||
};
|
||||
@ -846,7 +880,7 @@ namespace NadekoBot.Modules.Music
|
||||
try
|
||||
{
|
||||
musicPlayer.ThrowIfQueueFull();
|
||||
resolvedSong = await Song.ResolveSong(query, musicType).ConfigureAwait(false);
|
||||
resolvedSong = await SongHandler.ResolveSong(query, musicType).ConfigureAwait(false);
|
||||
|
||||
if (resolvedSong == null)
|
||||
throw new SongNotFoundException();
|
||||
@ -862,20 +896,19 @@ namespace NadekoBot.Modules.Music
|
||||
{
|
||||
try
|
||||
{
|
||||
var queuedMessage = await textCh.SendConfirmAsync($"🎵 Queued **{resolvedSong.SongInfo.Title.TrimTo(55)}** at `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false);
|
||||
var t = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(10000).ConfigureAwait(false);
|
||||
|
||||
await queuedMessage.DeleteAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
}).ConfigureAwait(false);
|
||||
//var queuedMessage = await textCh.SendConfirmAsync($"🎵 Queued **{resolvedSong.SongInfo.Title}** at `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false);
|
||||
var queuedMessage = await textCh.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithAuthor(eab => eab.WithName("Queued Song").WithMusicIcon())
|
||||
.WithTitle($"{resolvedSong.SongInfo.Title}")
|
||||
.WithDescription($"Queue #{musicPlayer.Playlist.Count + 1}")
|
||||
.WithFooter(ef => ef.WithText($"{resolvedSong.PrettyProvider}"))
|
||||
.Build())
|
||||
.ConfigureAwait(false);
|
||||
if (queuedMessage != null)
|
||||
queuedMessage.DeleteAfter(10);
|
||||
}
|
||||
catch { } // if queued message sending fails, don't attempt to delete it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -247,7 +247,7 @@ namespace NadekoBot.Modules.NSFW
|
||||
|
||||
if (matches.Count == 0)
|
||||
return null;
|
||||
return matches[rng.Next(0, matches.Count)].Groups["ll"].Value;
|
||||
return "http:" + matches[rng.Next(0, matches.Count)].Groups["ll"].Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,7 +105,10 @@ namespace NadekoBot.Modules.Permissions
|
||||
|
||||
}
|
||||
|
||||
await channel.SendConfirmAsync($"Blacklisted a `{type}` with id `{id}`").ConfigureAwait(false);
|
||||
if(action == AddRemove.Add)
|
||||
await channel.SendConfirmAsync($"Blacklisted a `{type}` with id `{id}`").ConfigureAwait(false);
|
||||
else
|
||||
await channel.SendConfirmAsync($"Unblacklisted a `{type}` with id `{id}`").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -316,7 +316,7 @@ namespace NadekoBot.Modules.Pokemon
|
||||
var targetType = StringToPokemonType(typeTargeted);
|
||||
if (targetType == null)
|
||||
{
|
||||
await channel.EmbedAsync(PokemonTypes.Aggregate(new EmbedBuilder().WithDescription("List of the available types:"), (eb, pt) => eb.AddField(efb => efb.WithName(pt.Name).WithValue(pt.Icon).WithIsInline(true))).WithColor(NadekoBot.OkColor).Build()).ConfigureAwait(false);
|
||||
await channel.EmbedAsync(PokemonTypes.Aggregate(new EmbedBuilder().WithDescription("List of the available types:"), (eb, pt) => eb.AddField(efb => efb.WithName(pt.Name).WithValue(pt.Icon).WithIsInline(true))).WithOkColor().Build()).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
if (targetType == GetPokeType(user.Id))
|
||||
|
@ -49,6 +49,7 @@ namespace NadekoBot.Modules.Searches
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var response = await http.GetStringAsync("http://api.yomomma.info/").ConfigureAwait(false);
|
||||
System.Console.WriteLine(response);
|
||||
await msg.Channel.SendConfirmAsync(JObject.Parse(response)["joke"].ToString() + " 😆").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ namespace NadekoBot.Modules.Searches
|
||||
$"limit={showCount}")
|
||||
.ConfigureAwait(false))["data"] as JArray;
|
||||
var dataList = data.Distinct(new ChampionNameComparer()).Take(showCount).ToList();
|
||||
var eb = new EmbedBuilder().WithColor(NadekoBot.OkColor).WithTitle(Format.Underline($"{dataList.Count} most banned champions"));
|
||||
var eb = new EmbedBuilder().WithOkColor().WithTitle(Format.Underline($"{dataList.Count} most banned champions"));
|
||||
for (var i = 0; i < dataList.Count; i++)
|
||||
{
|
||||
var champ = dataList[i];
|
||||
|
@ -39,8 +39,8 @@ namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
var channel = (ITextChannel)umsg.Channel;
|
||||
|
||||
var top = Uri.EscapeDataString(topText.Replace(' ', '-'));
|
||||
var bot = Uri.EscapeDataString(botText.Replace(' ', '-'));
|
||||
var top = topText.Replace(' ', '-');
|
||||
var bot = botText.Replace(' ', '-');
|
||||
await channel.SendMessageAsync($"http://memegen.link/{meme}/{top}/{bot}.jpg")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Modules.Searches.Commands.Models
|
||||
{
|
||||
public class Coord
|
||||
{
|
||||
public double lon { get; set; }
|
||||
public double lat { get; set; }
|
||||
}
|
||||
|
||||
public class Weather
|
||||
{
|
||||
public int id { get; set; }
|
||||
public string main { get; set; }
|
||||
public string description { get; set; }
|
||||
public string icon { get; set; }
|
||||
}
|
||||
|
||||
public class Main
|
||||
{
|
||||
public double temp { get; set; }
|
||||
public int pressure { get; set; }
|
||||
public int humidity { get; set; }
|
||||
public double temp_min { get; set; }
|
||||
public double temp_max { get; set; }
|
||||
}
|
||||
|
||||
public class Wind
|
||||
{
|
||||
public double speed { get; set; }
|
||||
public double deg { get; set; }
|
||||
}
|
||||
|
||||
public class Clouds
|
||||
{
|
||||
public int all { get; set; }
|
||||
}
|
||||
|
||||
public class Sys
|
||||
{
|
||||
public int type { get; set; }
|
||||
public int id { get; set; }
|
||||
public double message { get; set; }
|
||||
public string country { get; set; }
|
||||
public double sunrise { get; set; }
|
||||
public double sunset { get; set; }
|
||||
}
|
||||
|
||||
public class WeatherData
|
||||
{
|
||||
public Coord coord { get; set; }
|
||||
public List<Weather> weather { get; set; }
|
||||
public Main main { get; set; }
|
||||
public int visibility { get; set; }
|
||||
public Wind wind { get; set; }
|
||||
public Clouds clouds { get; set; }
|
||||
public int dt { get; set; }
|
||||
public Sys sys { get; set; }
|
||||
public int id { get; set; }
|
||||
public string name { get; set; }
|
||||
public int cod { get; set; }
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using Discord;
|
||||
using Discord.API;
|
||||
using NadekoBot.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
@ -35,7 +36,7 @@ namespace NadekoBot.Modules.Searches.Commands.OMDB
|
||||
public string Poster { get; set; }
|
||||
|
||||
public Embed GetEmbed() =>
|
||||
new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(Title)
|
||||
.WithUrl($"http://www.imdb.com/title/{ImdbId}/")
|
||||
.WithDescription(Plot)
|
||||
|
@ -31,12 +31,15 @@ namespace NadekoBot.Modules.Searches
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
var battletag = Regex.Replace(query, "#", "-", RegexOptions.IgnoreCase);
|
||||
|
||||
await channel.TriggerTypingAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var model = await GetProfile(region, battletag);
|
||||
|
||||
var rankimg = $"{model.Competitive.rank_img}";
|
||||
var rank = $"{model.Competitive.rank}";
|
||||
var competitiveplay = $"{model.Games.Competitive.played}";
|
||||
if (string.IsNullOrWhiteSpace(rank))
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
@ -52,7 +55,7 @@ namespace NadekoBot.Modules.Searches
|
||||
.AddField(fb => fb.WithName("**Competitive Rank**").WithValue("0").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true))
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
.WithOkColor();
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
@ -70,9 +73,22 @@ namespace NadekoBot.Modules.Searches
|
||||
.AddField(fb => fb.WithName("**Competitive Rank**").WithValue(rank).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"{model.Playtime.competitive}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true))
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
.WithOkColor();
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(competitiveplay))
|
||||
{
|
||||
var embed = new EmbedBuilder()
|
||||
.WithAuthor(eau => eau.WithName($"{model.username}")
|
||||
.WithUrl($"https://www.overbuff.com/players/pc/{battletag}")
|
||||
.WithIconUrl($"{model.avatar}"))
|
||||
.WithThumbnail(th => th.WithUrl("https://cdn.discordapp.com/attachments/155726317222887425/255653487512256512/YZ4w2ey.png"))
|
||||
.AddField(fb => fb.WithName("**Level**").WithValue($"{model.level}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Quick Wins**").WithValue($"{model.Games.Quick.wins}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Competitive Playtime**").WithValue($"0 hour").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Quick Playtime**").WithValue($"{model.Playtime.quick}").WithIsInline(true))
|
||||
.WithOkColor();
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
@ -80,23 +96,23 @@ namespace NadekoBot.Modules.Searches
|
||||
await channel.SendErrorAsync("Found no user! Please check the **Region** and **BattleTag** before trying again.");
|
||||
}
|
||||
}
|
||||
public async Task<OverwatchApiModel.OverwatchPlayer.Data> GetProfile(string region, string battletag)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var Url = await http.GetStringAsync($"https://api.lootbox.eu/pc/{region.ToLower()}/{battletag}/profile");
|
||||
var model = JsonConvert.DeserializeObject<OverwatchApiModel.OverwatchPlayer>(Url);
|
||||
return model.data;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public async Task<OverwatchApiModel.OverwatchPlayer.Data> GetProfile(string region, string battletag)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var http = new HttpClient())
|
||||
{
|
||||
var Url = await http.GetStringAsync($"https://api.lootbox.eu/pc/{region.ToLower()}/{battletag}/profile");
|
||||
var model = JsonConvert.DeserializeObject<OverwatchApiModel.OverwatchPlayer>(Url);
|
||||
return model.data;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ namespace NadekoBot.Modules.Searches
|
||||
if (kvp.Key.ToUpperInvariant() == pokemon.ToUpperInvariant())
|
||||
{
|
||||
var p = kvp.Value;
|
||||
await channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(kvp.Key.ToTitleCase())
|
||||
.WithDescription(p.BaseStats.ToString())
|
||||
.AddField(efb => efb.WithName("Types").WithValue(string.Join(",\n", p.Types)).WithIsInline(true))
|
||||
@ -81,7 +81,7 @@ namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
if (kvp.Key.ToUpperInvariant() == ability)
|
||||
{
|
||||
await channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(kvp.Value.Name)
|
||||
.WithDescription(kvp.Value.Desc)
|
||||
.AddField(efb => efb.WithName("Rating").WithValue(kvp.Value.Rating.ToString()).WithIsInline(true))
|
||||
|
@ -56,7 +56,7 @@ namespace NadekoBot.Modules.Searches
|
||||
var res = await http.GetStringAsync($"{xkcdUrl}/{num}/info.0.json").ConfigureAwait(false);
|
||||
|
||||
var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
|
||||
var embed = new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
var embed = new EmbedBuilder().WithOkColor()
|
||||
.WithImage(eib => eib.WithUrl(comic.ImageLink))
|
||||
.WithAuthor(eab => eab.WithName(comic.Title).WithUrl($"{xkcdUrl}/{num}").WithIconUrl("http://xkcd.com/s/919f27.ico"))
|
||||
.AddField(efb => efb.WithName("Comic#").WithValue(comic.Num.ToString()).WithIsInline(true))
|
||||
|
@ -17,6 +17,7 @@ using ImageSharp;
|
||||
using NadekoBot.Extensions;
|
||||
using System.IO;
|
||||
using NadekoBot.Modules.Searches.Commands.OMDB;
|
||||
using NadekoBot.Modules.Searches.Commands.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
@ -25,28 +26,30 @@ namespace NadekoBot.Modules.Searches
|
||||
{
|
||||
[NadekoCommand, Usage, Description, Aliases]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task Weather(IUserMessage umsg, string city, string country)
|
||||
public async Task Weather(IUserMessage umsg, [Remainder] string query)
|
||||
{
|
||||
var channel = (ITextChannel)umsg.Channel;
|
||||
city = city.Replace(" ", "");
|
||||
country = city.Replace(" ", "");
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return;
|
||||
|
||||
string response;
|
||||
using (var http = new HttpClient())
|
||||
response = await http.GetStringAsync($"http://api.ninetales.us/nadekobot/weather/?city={city}&country={country}").ConfigureAwait(false);
|
||||
response = await http.GetStringAsync($"http://api.openweathermap.org/data/2.5/weather?q={query}&appid=42cd627dd60debf25a5739e50a217d74&units=metric").ConfigureAwait(false);
|
||||
|
||||
var obj = JObject.Parse(response)["weather"];
|
||||
var data = JsonConvert.DeserializeObject<WeatherData>(response);
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.AddField(fb => fb.WithName("🌍 **Location**").WithValue($"{obj["target"]}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("📏 **Lat,Long**").WithValue($"{obj["latitude"]}, {obj["longitude"]}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("☁ **Condition**").WithValue($"{obj["condition"]}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("😓 **Humidity**").WithValue($"{obj["humidity"]}%").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("💨 **Wind Speed**").WithValue($"{obj["windspeedk"]}km/h ({obj["windspeedm"]}mph)").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("🌡 **Temperature**").WithValue($"{obj["centigrade"]}°C ({obj["fahrenheit"]}°F)").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("🔆 **Feels like**").WithValue($"{obj["feelscentigrade"]}°C ({obj["feelsfahrenheit"]}°F)").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("🌄 **Sunrise**").WithValue($"{obj["sunrise"]}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("🌇 **Sunset**").WithValue($"{obj["sunset"]}").WithIsInline(true))
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
.AddField(fb => fb.WithName("🌍 **Location**").WithValue(data.name + ", " + data.sys.country).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("📏 **Lat,Long**").WithValue($"{data.coord.lat}, {data.coord.lon}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("☁ **Condition**").WithValue(String.Join(", ", data.weather.Select(w=>w.main))).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("😓 **Humidity**").WithValue($"{data.main.humidity}%").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("💨 **Wind Speed**").WithValue(data.wind.speed + " km/h").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("🌡 **Temperature**").WithValue(data.main.temp + "°C").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("🔆 **Min - Max**").WithValue($"{data.main.temp_min}°C - {data.main.temp_max}°C").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("🌄 **Sunrise (utc)**").WithValue($"{data.sys.sunrise.ToUnixTimestamp():HH:mm}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("🌇 **Sunset (utc)**").WithValue($"{data.sys.sunset.ToUnixTimestamp():HH:mm}").WithIsInline(true))
|
||||
.WithOkColor()
|
||||
.WithFooter(efb => efb.WithText("Powered by http://openweathermap.org"));
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@ -202,7 +205,7 @@ namespace NadekoBot.Modules.Searches
|
||||
await msg.Channel.SendErrorAsync("Failed to shorten that url.").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await msg.Channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
await msg.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.AddField(efb => efb.WithName("Original Url")
|
||||
.WithValue($"<{arg}>"))
|
||||
.AddField(efb => efb.WithName("Short Url")
|
||||
@ -221,7 +224,7 @@ namespace NadekoBot.Modules.Searches
|
||||
if (string.IsNullOrWhiteSpace(terms))
|
||||
return;
|
||||
|
||||
await channel.SendConfirmAsync($"https://google.com/search?q={ WebUtility.UrlEncode(terms).Replace(' ', '+') }")
|
||||
await channel.SendMessageAsync($"https://google.com/search?q={ WebUtility.UrlEncode(terms).Replace(' ', '+') }")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@ -255,7 +258,7 @@ namespace NadekoBot.Modules.Searches
|
||||
var desc = item["text"].ToString();
|
||||
var types = String.Join(",\n", item["types"].ToObject<string[]>());
|
||||
var img = item["editions"][0]["image_url"].ToString();
|
||||
var embed = new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
var embed = new EmbedBuilder().WithOkColor()
|
||||
.WithTitle(item["name"].ToString())
|
||||
.WithDescription(desc)
|
||||
.WithImage(eib => eib.WithUrl(img))
|
||||
@ -364,7 +367,7 @@ namespace NadekoBot.Modules.Searches
|
||||
.WithUrl("http://www.yodaspeak.co.uk/")
|
||||
.WithAuthor(au => au.WithName("Yoda").WithIconUrl("http://www.yodaspeak.co.uk/yoda-small1.gif"))
|
||||
.WithDescription(res)
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
.WithOkColor();
|
||||
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
@ -405,7 +408,7 @@ namespace NadekoBot.Modules.Searches
|
||||
var word = item["word"].ToString();
|
||||
var def = item["definition"].ToString();
|
||||
var link = item["permalink"].ToString();
|
||||
var embed = new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
var embed = new EmbedBuilder().WithOkColor()
|
||||
.WithUrl(link)
|
||||
.WithAuthor(eab => eab.WithIconUrl("http://i.imgur.com/nwERwQE.jpg").WithName(word))
|
||||
.WithDescription(def);
|
||||
@ -452,7 +455,7 @@ namespace NadekoBot.Modules.Searches
|
||||
var hashtag = item["hashtag"].ToString();
|
||||
var link = item["uri"].ToString();
|
||||
var desc = item["text"].ToString();
|
||||
await channel.EmbedAsync(new EmbedBuilder().WithColor(NadekoBot.OkColor)
|
||||
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
||||
.WithAuthor(eab => eab.WithUrl(link)
|
||||
.WithIconUrl("http://res.cloudinary.com/urbandictionary/image/upload/a_exif,c_fit,h_200,w_200/v1394975045/b8oszuu3tbq7ebyo7vo1.jpg")
|
||||
.WithName(query))
|
||||
|
@ -49,7 +49,7 @@ namespace NadekoBot.Modules.Utility
|
||||
"Equals",
|
||||
"GetHashCode",
|
||||
"GetType"});
|
||||
await msg.Channel.SendConfirmAsync(string.Join(", ",selection));
|
||||
await msg.Channel.SendConfirmAsync("Available functions in calc", string.Join(", ", selection));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ namespace NadekoBot.Modules.Utility
|
||||
.AddField(fb => fb.WithName("**Region**").WithValue(guild.VoiceRegionId.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Roles**").WithValue(guild.Roles.Count().ToString()).WithIsInline(true))
|
||||
.WithImage(tn => tn.WithUrl(guild.IconUrl))
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
.WithOkColor();
|
||||
if (guild.Emojis.Count() > 0)
|
||||
{
|
||||
embed.AddField(fb => fb.WithName("**Custom Emojis**").WithValue(Format.Italics(string.Join(", ", guild.Emojis))).WithIsInline(true));
|
||||
@ -67,7 +67,7 @@ namespace NadekoBot.Modules.Utility
|
||||
.AddField(fb => fb.WithName("**ID**").WithValue(ch.Id.ToString()).WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Created At**").WithValue($"{createdAt.ToString("dd.MM.yyyy HH:mm")}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Users**").WithValue(usercount.ToString()).WithIsInline(true))
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
.WithOkColor();
|
||||
await msg.Channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ namespace NadekoBot.Modules.Utility
|
||||
.AddField(fb => fb.WithName("**Current Game**").WithValue($"{(user.Game?.Name == null ? "-" : user.Game.Name)}").WithIsInline(true))
|
||||
.AddField(fb => fb.WithName("**Roles**").WithValue($"**({user.Roles.Count()})** - {string.Join(", ", user.Roles.Select(r => r.Name)).SanitizeMentions()}").WithIsInline(true))
|
||||
.WithThumbnail(tn => tn.WithUrl(user.AvatarUrl))
|
||||
.WithColor(NadekoBot.OkColor);
|
||||
.WithOkColor();
|
||||
await msg.Channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ namespace NadekoBot.Modules.Utility
|
||||
{
|
||||
var res = Units.GroupBy(x => x.UnitType)
|
||||
.Aggregate(new EmbedBuilder().WithTitle("__Units which can be used by the converter__")
|
||||
.WithColor(NadekoBot.OkColor),
|
||||
.WithOkColor(),
|
||||
(embed, g) => embed.AddField(efb =>
|
||||
efb.WithName(g.Key.ToTitleCase())
|
||||
.WithValue(String.Join(", ", g.Select(x => x.Triggers.FirstOrDefault())
|
||||
|
@ -35,16 +35,16 @@ namespace NadekoBot.Modules.Utility
|
||||
game = game.Trim().ToUpperInvariant();
|
||||
if (string.IsNullOrWhiteSpace(game))
|
||||
return;
|
||||
var arr = (await (umsg.Channel as IGuildChannel).Guild.GetUsersAsync())
|
||||
var usrs = (await (umsg.Channel as IGuildChannel).Guild.GetUsersAsync())
|
||||
.Where(u => u.Game?.Name?.ToUpperInvariant() == game)
|
||||
.Select(u => u.Username)
|
||||
.ToList();
|
||||
|
||||
int i = 0;
|
||||
if (!arr.Any())
|
||||
if (!usrs.Any())
|
||||
await channel.SendErrorAsync("Nobody is playing that game.").ConfigureAwait(false);
|
||||
else
|
||||
await channel.SendConfirmAsync("```css\n" + string.Join("\n", arr.GroupBy(item => (i++) / 2)
|
||||
await channel.SendConfirmAsync($"List of users playing {game} game. Total {usrs.Count}.", "```css\n" + string.Join("\n", usrs.Take(30).GroupBy(item => (i++) / 2)
|
||||
.Select(ig => string.Concat(ig.Select(el => $"• {el,-27}")))) + "\n```")
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
@ -132,13 +132,30 @@ namespace NadekoBot.Modules.Utility
|
||||
|
||||
if (page < 1 || page > 100)
|
||||
return;
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
await channel.SendConfirmAsync($"⚔ **Page #{page} of roles for {target.Username}**", $"```css\n• " + string.Join("\n• ", target.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * RolesPerPage).Take(RolesPerPage)).SanitizeMentions() + "\n```");
|
||||
var roles = target.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * RolesPerPage).Take(RolesPerPage);
|
||||
if (!roles.Any())
|
||||
{
|
||||
await channel.SendErrorAsync("No roles on this page.").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await channel.SendConfirmAsync($"⚔ **Page #{page} of roles for {target.Username}**", $"```css\n• " + string.Join("\n• ", roles).SanitizeMentions() + "\n```").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await channel.SendConfirmAsync($"⚔ **Page #{page} of all roles on this server:**", $"```css\n• " + string.Join("\n• ", guild.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * RolesPerPage).Take(RolesPerPage)).SanitizeMentions() + "\n```");
|
||||
var roles = guild.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * RolesPerPage).Take(RolesPerPage);
|
||||
if (!roles.Any())
|
||||
{
|
||||
await channel.SendErrorAsync("No roles on this page.").ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await channel.SendConfirmAsync($"⚔ **Page #{page} of all roles on this server:**", $"```css\n• " + string.Join("\n• ", roles).SanitizeMentions() + "\n```").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,7 +280,7 @@ namespace NadekoBot.Modules.Utility
|
||||
return;
|
||||
}
|
||||
|
||||
await channel.EmbedAsync(guilds.Aggregate(new EmbedBuilder().WithColor(NadekoBot.OkColor),
|
||||
await channel.EmbedAsync(guilds.Aggregate(new EmbedBuilder().WithOkColor(),
|
||||
(embed, g) => embed.AddField(efb => efb.WithName(g.Name)
|
||||
.WithValue($"```css\nID: {g.Id}\nMembers: {g.GetUsers().Count}\nOwnerID: {g.OwnerId} ```")
|
||||
.WithIsInline(false)))
|
||||
@ -271,4 +288,4 @@ namespace NadekoBot.Modules.Utility
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
src/NadekoBot/Resources/CommandStrings.Designer.cs
generated
35
src/NadekoBot/Resources/CommandStrings.Designer.cs
generated
@ -86,6 +86,33 @@ namespace NadekoBot.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to acrophobia acro.
|
||||
/// </summary>
|
||||
public static string acro_cmd {
|
||||
get {
|
||||
return ResourceManager.GetString("acro_cmd", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Starts an Acrophobia game. Second argment is optional round length in seconds. (default is 60).
|
||||
/// </summary>
|
||||
public static string acro_desc {
|
||||
get {
|
||||
return ResourceManager.GetString("acro_desc", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to `{0}acro` or `{0}acro 30`.
|
||||
/// </summary>
|
||||
public static string acro_usage {
|
||||
get {
|
||||
return ResourceManager.GetString("acro_usage", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to addcustreact acr.
|
||||
/// </summary>
|
||||
@ -4648,8 +4675,8 @@ namespace NadekoBot.Resources {
|
||||
return ResourceManager.GetString("osub_usage", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to overwatch ow.
|
||||
/// </summary>
|
||||
public static string overwatch_cmd {
|
||||
@ -7818,7 +7845,7 @@ namespace NadekoBot.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations..
|
||||
/// Looks up a localized string similar to Shows weather data for a specified city. You can also specify a country after a comma..
|
||||
/// </summary>
|
||||
public static string weather_desc {
|
||||
get {
|
||||
@ -7827,7 +7854,7 @@ namespace NadekoBot.Resources {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to `{0}we Moscow RF`.
|
||||
/// Looks up a localized string similar to `{0}we Moscow, RU`.
|
||||
/// </summary>
|
||||
public static string weather_usage {
|
||||
get {
|
||||
|
@ -1876,10 +1876,10 @@
|
||||
<value>weather we</value>
|
||||
</data>
|
||||
<data name="weather_desc" xml:space="preserve">
|
||||
<value>Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations.</value>
|
||||
<value>Shows weather data for a specified city. You can also specify a country after a comma.</value>
|
||||
</data>
|
||||
<data name="weather_usage" xml:space="preserve">
|
||||
<value>`{0}we Moscow RF`</value>
|
||||
<value>`{0}we Moscow, RU`</value>
|
||||
</data>
|
||||
<data name="youtube_cmd" xml:space="preserve">
|
||||
<value>youtube yt</value>
|
||||
@ -2781,7 +2781,7 @@
|
||||
<data name="crstats_usage" xml:space="preserve">
|
||||
<value>`{0}crstats` or `{0}crstats 3`</value>
|
||||
</data>
|
||||
<data name="overwatch_cmd" xml:space="preserve">
|
||||
<data name="overwatch_cmd" xml:space="preserve">
|
||||
<value>overwatch ow</value>
|
||||
</data>
|
||||
<data name="overwatch_desc" xml:space="preserve">
|
||||
@ -2790,4 +2790,13 @@
|
||||
<data name="overwatch_usage" xml:space="preserve">
|
||||
<value>`{0}ow us Battletag#1337` or `{0}overwatch eu Battletag#2016`</value>
|
||||
</data>
|
||||
<data name="acro_cmd" xml:space="preserve">
|
||||
<value>acrophobia acro</value>
|
||||
</data>
|
||||
<data name="acro_desc" xml:space="preserve">
|
||||
<value>Starts an Acrophobia game. Second argment is optional round length in seconds. (default is 60)</value>
|
||||
</data>
|
||||
<data name="acro_usage" xml:space="preserve">
|
||||
<value>`{0}acro` or `{0}acro 30`</value>
|
||||
</data>
|
||||
</root>
|
@ -167,7 +167,8 @@ namespace NadekoBot.Services.Impl
|
||||
remaining -= toGet;
|
||||
|
||||
var q = yt.Videos.List("contentDetails");
|
||||
q.Id = string.Join(",", videoIds);
|
||||
q.Id = string.Join(",", videoIdsList.Take(toGet));
|
||||
videoIdsList = videoIdsList.Skip(toGet).ToList();
|
||||
var items = (await q.ExecuteAsync().ConfigureAwait(false)).Items;
|
||||
foreach (var i in items)
|
||||
{
|
||||
|
@ -25,6 +25,23 @@ namespace NadekoBot.Extensions
|
||||
http.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||
}
|
||||
|
||||
public static EmbedBuilder WithOkColor(this EmbedBuilder eb) =>
|
||||
eb.WithColor(NadekoBot.OkColor);
|
||||
|
||||
public static EmbedBuilder WithErrorColor(this EmbedBuilder eb) =>
|
||||
eb.WithColor(NadekoBot.ErrorColor);
|
||||
|
||||
public static IMessage DeleteAfter(this IUserMessage msg, int seconds)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(seconds * 1000);
|
||||
try { await msg.DeleteAsync().ConfigureAwait(false); }
|
||||
catch { }
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static async Task<IMessage> SendMessageToOwnerAsync(this IGuild guild, string message)
|
||||
{
|
||||
var ownerPrivate = await (await guild.GetOwnerAsync().ConfigureAwait(false)).CreateDMChannelAsync()
|
||||
@ -65,6 +82,8 @@ namespace NadekoBot.Extensions
|
||||
|
||||
public static double UnixTimestamp(this DateTime dt) => dt.ToUniversalTime().Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
|
||||
|
||||
public static DateTime ToUnixTimestamp(this double number) => new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(number);
|
||||
|
||||
public static async Task<IUserMessage> SendMessageAsync(this IGuildUser user, string message, bool isTTS = false) =>
|
||||
await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(message, isTTS).ConfigureAwait(false);
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
"target": "project"
|
||||
},
|
||||
"Google.Apis.Urlshortener.v1": "1.19.0.138",
|
||||
"Google.Apis.YouTube.v3": "1.19.0.655",
|
||||
"Google.Apis.YouTube.v3": "1.20.0.701",
|
||||
"ImageSharp": "1.0.0-alpha-000079",
|
||||
"Microsoft.EntityFrameworkCore": "1.1.0",
|
||||
"Microsoft.EntityFrameworkCore.Design": "1.1.0",
|
||||
|
Loading…
Reference in New Issue
Block a user