Merge pull request #52 from Kwoth/dev

ups from kwoth
This commit is contained in:
samvaio 2016-12-25 19:17:02 +05:30 committed by GitHub
commit 37c3fd240d
16 changed files with 362 additions and 22925 deletions

View File

@ -628,32 +628,49 @@ namespace NadekoBot.Modules.Administration
switch (logChannelType) switch (logChannelType)
{ {
case LogType.Other: case LogType.Other:
newLogSetting.LogOtherId = null;
break; break;
case LogType.MessageUpdated: case LogType.MessageUpdated:
newLogSetting.MessageUpdatedId = null;
break; break;
case LogType.MessageDeleted: case LogType.MessageDeleted:
newLogSetting.MessageDeletedId = null;
break; break;
case LogType.UserJoined: case LogType.UserJoined:
newLogSetting.UserJoinedId = null;
break; break;
case LogType.UserLeft: case LogType.UserLeft:
newLogSetting.UserLeftId = null;
break; break;
case LogType.UserBanned: case LogType.UserBanned:
newLogSetting.UserBannedId = null;
break; break;
case LogType.UserUnbanned: case LogType.UserUnbanned:
newLogSetting.UserUnbannedId = null;
break; break;
case LogType.UserUpdated: case LogType.UserUpdated:
newLogSetting.UserUpdatedId = null;
break;
case LogType.UserMuted:
newLogSetting.UserMutedId = null;
break; break;
case LogType.ChannelCreated: case LogType.ChannelCreated:
newLogSetting.ChannelCreatedId = null;
break; break;
case LogType.ChannelDestroyed: case LogType.ChannelDestroyed:
newLogSetting.ChannelDestroyedId = null;
break; break;
case LogType.ChannelUpdated: case LogType.ChannelUpdated:
newLogSetting.ChannelUpdatedId = null;
break; break;
case LogType.UserPresence: case LogType.UserPresence:
newLogSetting.LogUserPresenceId = null;
break; break;
case LogType.VoicePresence: case LogType.VoicePresence:
newLogSetting.LogVoicePresenceId = null;
break; break;
case LogType.VoicePresenceTTS: case LogType.VoicePresenceTTS:
newLogSetting.LogVoicePresenceTTSId = null;
break; break;
default: default:
break; break;

View File

@ -29,7 +29,7 @@ namespace NadekoBot.Modules.Gambling
var ar = new AnimalRace(channel.Guild.Id, channel); var ar = new AnimalRace(channel.Guild.Id, channel);
if (ar.Fail) if (ar.Fail)
await channel.SendErrorAsync("🏁 `Failed starting a race. Another race is probably running.`"); await channel.SendErrorAsync("Animal Race", "Failed starting. Another race is probably running.").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -45,7 +45,7 @@ namespace NadekoBot.Modules.Gambling
AnimalRace ar; AnimalRace ar;
if (!AnimalRaces.TryGetValue(channel.Guild.Id, out ar)) if (!AnimalRaces.TryGetValue(channel.Guild.Id, out ar))
{ {
await channel.SendErrorAsync("No race exists on this server"); await channel.SendErrorAsync("No race exists on this server").ConfigureAwait(false);
return; return;
} }
await ar.JoinRace(umsg.Author as IGuildUser, amount); await ar.JoinRace(umsg.Author as IGuildUser, amount);
@ -90,21 +90,29 @@ namespace NadekoBot.Modules.Gambling
{ {
try try
{ {
try { await raceChannel.SendConfirmAsync($"🏁`Race is starting in 20 seconds or when the room is full. Type {NadekoBot.ModulePrefixes[typeof(Gambling).Name]}jr to join the race.`"); } catch (Exception ex) { _log.Warn(ex); } try
{
await raceChannel.SendConfirmAsync("Animal Race", $"Starting in 20 seconds or when the room is full.",
footer: $"Type {NadekoBot.ModulePrefixes[typeof(Gambling).Name]}jr to join the race.");
}
catch (Exception ex)
{
_log.Warn(ex);
}
var t = await Task.WhenAny(Task.Delay(20000, token), fullgame); var t = await Task.WhenAny(Task.Delay(20000, token), fullgame);
Started = true; Started = true;
cancelSource.Cancel(); cancelSource.Cancel();
if (t == fullgame) if (t == fullgame)
{ {
try { await raceChannel.SendConfirmAsync("🏁`Race full, starting right now!`"); } catch (Exception ex) { _log.Warn(ex); } try { await raceChannel.SendConfirmAsync("Animal Race", "Full! Starting immediately."); } catch (Exception ex) { _log.Warn(ex); }
} }
else if (participants.Count > 1) else if (participants.Count > 1)
{ {
try { await raceChannel.SendConfirmAsync("🏁`Game starting with " + participants.Count + " participants.`"); } catch (Exception ex) { _log.Warn(ex); } try { await raceChannel.SendConfirmAsync("Animal Race", "Starting with " + participants.Count + " participants."); } catch (Exception ex) { _log.Warn(ex); }
} }
else else
{ {
try { await raceChannel.SendErrorAsync("🏁`Race failed to start since there was not enough participants.`"); } catch (Exception ex) { _log.Warn(ex); } try { await raceChannel.SendErrorAsync("Animal Race", "Failed to start since there was not enough participants."); } catch (Exception ex) { _log.Warn(ex); }
var p = participants.FirstOrDefault(); var p = participants.FirstOrDefault();
if (p != null && p.AmountBet > 0) if (p != null && p.AmountBet > 0)
@ -141,19 +149,26 @@ namespace NadekoBot.Modules.Gambling
participants.ForEach(p => participants.ForEach(p =>
{ {
p.Total += 1 + rng.Next(0, 10); p.Total += 1 + rng.Next(0, 10);
if (p.Total > 60)
{
p.Total = 60;
if (winner == null)
{
winner = p;
}
if (p.Place == 0)
p.Place = place++;
}
}); });
participants
.OrderByDescending(p => p.Total)
.ForEach(p =>
{
if (p.Total > 60)
{
if (winner == null)
{
winner = p;
}
p.Total = 60;
if (p.Place == 0)
p.Place = place++;
}
});
//draw the state //draw the state
var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚| var text = $@"|🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🏁🔚|
@ -185,11 +200,11 @@ namespace NadekoBot.Modules.Gambling
var wonAmount = winner.AmountBet * (participants.Count - 1); var wonAmount = winner.AmountBet * (participants.Count - 1);
await CurrencyHandler.AddCurrencyAsync(winner.User, "Won a Race", wonAmount, false).ConfigureAwait(false); await CurrencyHandler.AddCurrencyAsync(winner.User, "Won a Race", wonAmount, false).ConfigureAwait(false);
await raceChannel.SendConfirmAsync($"🏁 {winner.User.Mention} as {winner.Animal} **Won the race and {wonAmount}{CurrencySign}!**").ConfigureAwait(false); await raceChannel.SendConfirmAsync("Animal Race", $"{winner.User.Mention} as {winner.Animal} **Won the race and {wonAmount}{CurrencySign}!**").ConfigureAwait(false);
} }
else else
{ {
await raceChannel.SendConfirmAsync($"🏁 {winner.User.Mention} as {winner.Animal} **Won the race!**"); await raceChannel.SendConfirmAsync("Animal Race", $"{winner.User.Mention} as {winner.Animal} **Won the race!**").ConfigureAwait(false);
} }
} }
@ -218,18 +233,18 @@ namespace NadekoBot.Modules.Gambling
var animal = ""; var animal = "";
if (!animals.TryDequeue(out animal)) if (!animals.TryDequeue(out animal))
{ {
await raceChannel.SendErrorAsync($"{u.Mention} `There is no running race on this server.`"); await raceChannel.SendErrorAsync($"{u.Mention} `There is no running race on this server.`").ConfigureAwait(false);
return; return;
} }
var p = new Participant(u, animal, amount); var p = new Participant(u, animal, amount);
if (participants.Contains(p)) if (participants.Contains(p))
{ {
await raceChannel.SendErrorAsync($"{u.Mention} `You already joined this race.`"); await raceChannel.SendErrorAsync($"{u.Mention} `You already joined this race.`").ConfigureAwait(false);
return; return;
} }
if (Started) if (Started)
{ {
await raceChannel.SendErrorAsync($"{u.Mention} `Race is already started`"); await raceChannel.SendErrorAsync($"{u.Mention} `Race is already started`").ConfigureAwait(false);
return; return;
} }
if (amount > 0) if (amount > 0)
@ -239,7 +254,8 @@ namespace NadekoBot.Modules.Gambling
return; return;
} }
participants.Add(p); participants.Add(p);
await raceChannel.SendConfirmAsync($"{u.Mention} **joined the race as a {p.Animal}" + (amount > 0 ? $" and bet {amount} {CurrencySign}!**" : "**")); await raceChannel.SendConfirmAsync("Animal Race", $"{u.Mention} **joined as a {p.Animal}" + (amount > 0 ? $" and bet {amount} {CurrencySign}!**" : "**"))
.ConfigureAwait(false);
} }
} }

View File

@ -22,7 +22,7 @@ namespace NadekoBot.Modules.Games.Trivia
private int QuestionDurationMiliseconds { get; } = 30000; private int QuestionDurationMiliseconds { get; } = 30000;
private int HintTimeoutMiliseconds { get; } = 6000; private int HintTimeoutMiliseconds { get; } = 6000;
public bool ShowHints { get; set; } = true; public bool ShowHints { get; } = true;
private CancellationTokenSource triviaCancelSource { get; set; } private CancellationTokenSource triviaCancelSource { get; set; }
public TriviaQuestion CurrentQuestion { get; private set; } public TriviaQuestion CurrentQuestion { get; private set; }
@ -35,95 +35,111 @@ namespace NadekoBot.Modules.Games.Trivia
public int WinRequirement { get; } = 10; public int WinRequirement { get; } = 10;
public TriviaGame(IGuild guild, ITextChannel channel, bool showHints, int winReq = 10) public TriviaGame(IGuild guild, ITextChannel channel, bool showHints, int winReq)
{ {
_log = LogManager.GetCurrentClassLogger(); this._log = LogManager.GetCurrentClassLogger();
ShowHints = showHints;
this.ShowHints = showHints;
this.guild = guild; this.guild = guild;
this.channel = channel; this.channel = channel;
WinRequirement = winReq; this.WinRequirement = winReq;
Task.Run(async () => { try { await StartGame().ConfigureAwait(false); } catch { } });
} }
private async Task StartGame() public async Task StartGame()
{ {
while (!ShouldStopGame) while (!ShouldStopGame)
{ {
// reset the cancellation source // reset the cancellation source
triviaCancelSource = new CancellationTokenSource(); triviaCancelSource = new CancellationTokenSource();
var token = triviaCancelSource.Token;
// load question // load question
CurrentQuestion = TriviaQuestionPool.Instance.GetRandomQuestion(oldQuestions); CurrentQuestion = TriviaQuestionPool.Instance.GetRandomQuestion(oldQuestions);
if (CurrentQuestion == null) if (CurrentQuestion == null)
{ {
try { await channel.SendErrorAsync($":exclamation: Failed loading a trivia question.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } await channel.SendErrorAsync("Trivia Game", "Failed loading a question.").ConfigureAwait(false);
await End().ConfigureAwait(false);
return; return;
} }
oldQuestions.Add(CurrentQuestion); //add it to exclusion list so it doesn't show up again oldQuestions.Add(CurrentQuestion); //add it to exclusion list so it doesn't show up again
//sendquestion
try { await channel.SendConfirmAsync($":question: Question",$"**{CurrentQuestion.Question}**").ConfigureAwait(false); }
catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound || ex.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
break;
}
catch (Exception ex) { _log.Warn(ex); }
//receive messages
NadekoBot.Client.MessageReceived += PotentialGuess;
//allow people to guess
GameActive = true;
EmbedBuilder questionEmbed;
IUserMessage questionMessage;
try try
{ {
//hint questionEmbed = new EmbedBuilder().WithOkColor()
await Task.Delay(HintTimeoutMiliseconds, token).ConfigureAwait(false); .WithTitle("Trivia Game")
if (ShowHints) .AddField(eab => eab.WithName("Category").WithValue(CurrentQuestion.Category))
try { await channel.SendConfirmAsync($":exclamation: Hint", CurrentQuestion.GetHint()).ConfigureAwait(false); } .AddField(eab => eab.WithName("Question").WithValue(CurrentQuestion.Question));
catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound || ex.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
break;
}
catch (Exception ex) { _log.Warn(ex); }
//timeout
await Task.Delay(QuestionDurationMiliseconds - HintTimeoutMiliseconds, token).ConfigureAwait(false);
questionMessage = await channel.EmbedAsync(questionEmbed.Build()).ConfigureAwait(false);
}
catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound || ex.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
return;
}
catch (Exception ex)
{
_log.Warn(ex);
await Task.Delay(2000).ConfigureAwait(false);
continue;
}
//receive messages
try
{
NadekoBot.Client.MessageReceived += PotentialGuess;
//allow people to guess
GameActive = true;
try
{
//hint
await Task.Delay(HintTimeoutMiliseconds, triviaCancelSource.Token).ConfigureAwait(false);
if (ShowHints)
try
{
await questionMessage.ModifyAsync(m => m.Embed = questionEmbed.WithFooter(efb => efb.WithText(CurrentQuestion.GetHint())).Build())
.ConfigureAwait(false);
}
catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound || ex.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
break;
}
catch (Exception ex) { _log.Warn(ex); }
//timeout
await Task.Delay(QuestionDurationMiliseconds - HintTimeoutMiliseconds, triviaCancelSource.Token).ConfigureAwait(false);
}
catch (TaskCanceledException) { } //means someone guessed the answer
}
finally
{
GameActive = false;
NadekoBot.Client.MessageReceived -= PotentialGuess;
} }
catch (TaskCanceledException) { } //means someone guessed the answer
GameActive = false;
if (!triviaCancelSource.IsCancellationRequested) if (!triviaCancelSource.IsCancellationRequested)
try { await channel.SendConfirmAsync($":clock2: :question: **Time's up!** The correct answer was **{CurrentQuestion.Answer}**").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } try { await channel.SendErrorAsync("Trivia Game", $"**Time's up!** The correct answer was **{CurrentQuestion.Answer}**").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
NadekoBot.Client.MessageReceived -= PotentialGuess;
// load next question if game is still running
await Task.Delay(2000).ConfigureAwait(false); await Task.Delay(2000).ConfigureAwait(false);
} }
try { NadekoBot.Client.MessageReceived -= PotentialGuess; } catch { }
GameActive = false;
await End().ConfigureAwait(false);
} }
public async Task End() public async Task EnsureStopped()
{ {
ShouldStopGame = true; ShouldStopGame = true;
TriviaGame throwaway;
Games.TriviaCommands.RunningTrivias.TryRemove(channel.Guild.Id, out throwaway); await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
try .WithAuthor(eab => eab.WithName("Trivia Game Ended"))
{ .WithTitle("Final Results")
await channel.EmbedAsync(new EmbedBuilder().WithOkColor() .WithDescription(GetLeaderboard())
.WithTitle("Leaderboard") .Build()).ConfigureAwait(false);
.WithDescription(GetLeaderboard())
.Build(), "Trivia game ended.").ConfigureAwait(false);
}
catch { }
} }
public async Task StopGame() public async Task StopGame()
{ {
if (!ShouldStopGame) var old = ShouldStopGame;
try { await channel.SendConfirmAsync(":exclamation: Trivia will stop after this question.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
ShouldStopGame = true; ShouldStopGame = true;
if (!old)
try { await channel.SendConfirmAsync("Trivia Game", "Stopping after this question.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
} }
private Task PotentialGuess(IMessage imsg) private Task PotentialGuess(IMessage imsg)
@ -137,11 +153,11 @@ namespace NadekoBot.Modules.Games.Trivia
{ {
try try
{ {
if (!(umsg.Channel is IGuildChannel && umsg.Channel is ITextChannel)) return; var textChannel = umsg.Channel as ITextChannel;
if ((umsg.Channel as ITextChannel).Guild != guild) return; if (textChannel == null || textChannel.Guild != guild)
if (umsg.Author.Id == NadekoBot.Client.GetCurrentUser().Id) return; return;
var guildUser = umsg.Author as IGuildUser; var guildUser = (IGuildUser)umsg.Author;
var guess = false; var guess = false;
await _guessLock.WaitAsync().ConfigureAwait(false); await _guessLock.WaitAsync().ConfigureAwait(false);
@ -156,10 +172,15 @@ namespace NadekoBot.Modules.Games.Trivia
finally { _guessLock.Release(); } finally { _guessLock.Release(); }
if (!guess) return; if (!guess) return;
triviaCancelSource.Cancel(); triviaCancelSource.Cancel();
try { await channel.SendConfirmAsync($"☑️ {guildUser.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); }
if (Users[guildUser] != WinRequirement) return;
ShouldStopGame = true; if (Users[guildUser] == WinRequirement) {
await channel.SendConfirmAsync($":exclamation: We have a winner! It's {guildUser.Mention}.").ConfigureAwait(false); ShouldStopGame = true;
await channel.SendConfirmAsync("Trivia Game", $"{guildUser.Mention} guessed it and WON the game! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false);
return;
}
await channel.SendConfirmAsync("Trivia Game", $"{guildUser.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false);
} }
catch (Exception ex) { _log.Warn(ex); } catch (Exception ex) { _log.Warn(ex); }
}); });
@ -169,7 +190,7 @@ namespace NadekoBot.Modules.Games.Trivia
public string GetLeaderboard() public string GetLeaderboard()
{ {
if (Users.Count == 0) if (Users.Count == 0)
return ""; return "No results.";
var sb = new StringBuilder(); var sb = new StringBuilder();

View File

@ -18,9 +18,9 @@ namespace NadekoBot.Modules.Games.Trivia
}; };
public static int maxStringLength = 22; public static int maxStringLength = 22;
public string Category; public string Category { get; set; }
public string Question; public string Question { get; set; }
public string Answer; public string Answer { get; set; }
public TriviaQuestion(string q, string a, string c) public TriviaQuestion(string q, string a, string c)
{ {
@ -79,9 +79,6 @@ namespace NadekoBot.Modules.Games.Trivia
return str; return str;
} }
public override string ToString() =>
"Question: **" + this.Question + "?**";
private static string Scramble(string word) private static string Scramble(string word)
{ {
var letters = word.ToArray(); var letters = word.ToArray();
@ -101,7 +98,7 @@ namespace NadekoBot.Modules.Games.Trivia
if (letters[i] != ' ') if (letters[i] != ' ')
letters[i] = '_'; letters[i] = '_';
} }
return "`" + string.Join(" ", letters) + "`"; return string.Join(" ", letters);
} }
} }
} }

View File

@ -1,5 +1,6 @@
using NadekoBot.Extensions; using NadekoBot.Extensions;
using NadekoBot.Services; using NadekoBot.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -11,36 +12,31 @@ namespace NadekoBot.Modules.Games.Trivia
{ {
public class TriviaQuestionPool public class TriviaQuestionPool
{ {
public static TriviaQuestionPool Instance { get; } = new TriviaQuestionPool(); private static TriviaQuestionPool _instance;
public ConcurrentHashSet<TriviaQuestion> pool = new ConcurrentHashSet<TriviaQuestion>(); public static TriviaQuestionPool Instance { get; } = _instance ?? (_instance = new TriviaQuestionPool());
private const string questionsFile = "data/trivia_questions.json";
private Random rng { get; } = new NadekoRandom(); private Random rng { get; } = new NadekoRandom();
private TriviaQuestion[] pool { get; }
static TriviaQuestionPool() { } static TriviaQuestionPool() { }
private TriviaQuestionPool() private TriviaQuestionPool()
{ {
Reload(); pool = JsonConvert.DeserializeObject<TriviaQuestion[]>(File.ReadAllText(questionsFile));
} }
public TriviaQuestion GetRandomQuestion(IEnumerable<TriviaQuestion> exclude) public TriviaQuestion GetRandomQuestion(HashSet<TriviaQuestion> exclude)
{ {
var list = pool.Except(exclude).ToList(); if (pool.Length == 0)
var rand = rng.Next(0, list.Count); return null;
return list[rand];
}
public void Reload() TriviaQuestion randomQuestion;
{ while (exclude.Contains(randomQuestion = pool[rng.Next(0, pool.Length)])) ;
var arr = JArray.Parse(File.ReadAllText("data/questions.json"));
foreach (var item in arr) return randomQuestion;
{
var tq = new TriviaQuestion(item["Question"].ToString().SanitizeMentions(), item["Answer"].ToString().SanitizeMentions(), item["Category"]?.ToString());
pool.Add(tq);
}
var r = new NadekoRandom();
pool = new ConcurrentHashSet<TriviaQuestion>(pool.OrderBy(x => r.Next()));
} }
} }
} }

View File

@ -20,29 +20,33 @@ namespace NadekoBot.Modules.Games
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Trivia(IUserMessage umsg, params string[] args) public Task Trivia(IUserMessage umsg, [Remainder] string additionalArgs = "")
=> Trivia(umsg, 10, additionalArgs);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Trivia(IUserMessage umsg, int winReq = 10, [Remainder] string additionalArgs = "")
{ {
var channel = (ITextChannel)umsg.Channel; var channel = (ITextChannel)umsg.Channel;
TriviaGame trivia; var showHints = !additionalArgs.Contains("nohint");
if (!RunningTrivias.TryGetValue(channel.Guild.Id, out trivia))
TriviaGame trivia = new TriviaGame(channel.Guild, channel, showHints, winReq);
if (RunningTrivias.TryAdd(channel.Guild.Id, trivia))
{ {
var showHints = !args.Contains("nohint"); try
var number = args.Select(s =>
{ {
int num; await trivia.StartGame().ConfigureAwait(false);
return new Tuple<bool, int>(int.TryParse(s, out num), num); }
}).Where(t => t.Item1).Select(t => t.Item2).FirstOrDefault(); finally
if (number < 0) {
return; RunningTrivias.TryRemove(channel.Guild.Id, out trivia);
var triviaGame = new TriviaGame(channel.Guild, (ITextChannel)umsg.Channel, showHints, number == 0 ? 10 : number); await trivia.EnsureStopped().ConfigureAwait(false);
if (RunningTrivias.TryAdd(channel.Guild.Id, triviaGame)) }
await channel.SendConfirmAsync($"**Trivia game started! {triviaGame.WinRequirement} points needed to win.**").ConfigureAwait(false); return;
else
await triviaGame.StopGame().ConfigureAwait(false);
} }
else
await channel.SendErrorAsync("Trivia game is already running on this server.\n" + trivia.CurrentQuestion).ConfigureAwait(false); await channel.SendErrorAsync("Trivia game is already running on this server.\n" + trivia.CurrentQuestion).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -53,9 +57,12 @@ namespace NadekoBot.Modules.Games
TriviaGame trivia; TriviaGame trivia;
if (RunningTrivias.TryGetValue(channel.Guild.Id, out trivia)) if (RunningTrivias.TryGetValue(channel.Guild.Id, out trivia))
{
await channel.SendConfirmAsync("Leaderboard", trivia.GetLeaderboard()).ConfigureAwait(false); await channel.SendConfirmAsync("Leaderboard", trivia.GetLeaderboard()).ConfigureAwait(false);
else return;
await channel.SendErrorAsync("No trivia is running on this server.").ConfigureAwait(false); }
await channel.SendErrorAsync("No trivia is running on this server.").ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -68,9 +75,10 @@ namespace NadekoBot.Modules.Games
if (RunningTrivias.TryGetValue(channel.Guild.Id, out trivia)) if (RunningTrivias.TryGetValue(channel.Guild.Id, out trivia))
{ {
await trivia.StopGame().ConfigureAwait(false); await trivia.StopGame().ConfigureAwait(false);
return;
} }
else
await channel.SendErrorAsync("No trivia is running on this server.").ConfigureAwait(false); await channel.SendErrorAsync("No trivia is running on this server.").ConfigureAwait(false);
} }
} }
} }

View File

@ -30,6 +30,17 @@ namespace NadekoBot.Modules.Music.Classes
{ {
private IAudioClient audioClient { get; set; } private IAudioClient audioClient { get; set; }
/// <summary>
/// Player will prioritize different queuer name
/// over the song position in the playlist
/// </summary>
public bool FairPlay { get; set; } = true;
/// <summary>
/// Users who recently got their music wish
/// </summary>
private ConcurrentHashSet<string> recentlyPlayedUsers { get; } = new ConcurrentHashSet<string>();
private readonly List<Song> playlist = new List<Song>(); private readonly List<Song> playlist = new List<Song>();
public IReadOnlyCollection<Song> Playlist => playlist; public IReadOnlyCollection<Song> Playlist => playlist;
@ -107,11 +118,13 @@ namespace NadekoBot.Modules.Music.Classes
} }
CurrentSong = GetNextSong(); CurrentSong = GetNextSong();
RemoveSongAt(0);
if (CurrentSong == null) if (CurrentSong == null)
continue; continue;
var index = playlist.IndexOf(CurrentSong);
if (index != -1)
RemoveSongAt(index);
OnStarted(this, CurrentSong); OnStarted(this, CurrentSong);
await CurrentSong.Play(audioClient, cancelToken); await CurrentSong.Play(audioClient, cancelToken);
@ -183,8 +196,26 @@ namespace NadekoBot.Modules.Music.Classes
return volume; return volume;
} }
private Song GetNextSong() => private Song GetNextSong()
playlist.FirstOrDefault(); {
if (!FairPlay)
{
return playlist.FirstOrDefault();
}
var song = playlist.FirstOrDefault(c => !recentlyPlayedUsers.Contains(c.QueuerName))
?? playlist.FirstOrDefault();
if (song == null)
return null;
if (recentlyPlayedUsers.Contains(song.QueuerName))
{
recentlyPlayedUsers.Clear();
}
recentlyPlayedUsers.Add(song.QueuerName);
return song;
}
public void Shuffle() public void Shuffle()
{ {

View File

@ -126,6 +126,20 @@ namespace NadekoBot.Modules.Music
return Task.CompletedTask; return Task.CompletedTask;
} }
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Fairplay(IUserMessage umsg)
{
var channel = (ITextChannel)umsg.Channel;
MusicPlayer musicPlayer;
if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) return;
if (((IGuildUser)umsg.Author).VoiceChannel != musicPlayer.PlaybackVoiceChannel)
return;
var val = musicPlayer.FairPlay = !musicPlayer.FairPlay;
await channel.SendConfirmAsync("Fair play " + (val ? "enabled" : "disabled") + ".").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Queue(IUserMessage umsg, [Remainder] string query) public async Task Queue(IUserMessage umsg, [Remainder] string query)

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Searches.Commands.Models
{
public struct GoogleSearchResult
{
public string Title { get; }
public string Link { get; }
public string Text { get; }
public GoogleSearchResult(string title, string link, string text)
{
this.Title = title;
this.Link = link;
this.Text = text;
}
}
}

View File

@ -18,6 +18,10 @@ using NadekoBot.Extensions;
using System.IO; using System.IO;
using NadekoBot.Modules.Searches.Commands.OMDB; using NadekoBot.Modules.Searches.Commands.OMDB;
using NadekoBot.Modules.Searches.Commands.Models; using NadekoBot.Modules.Searches.Commands.Models;
using AngleSharp.Parser.Html;
using AngleSharp;
using AngleSharp.Dom.Html;
using AngleSharp.Dom;
namespace NadekoBot.Modules.Searches namespace NadekoBot.Modules.Searches
{ {
@ -41,7 +45,7 @@ namespace NadekoBot.Modules.Searches
var embed = new EmbedBuilder() var embed = new EmbedBuilder()
.AddField(fb => fb.WithName("🌍 **Location**").WithValue(data.name + ", " + data.sys.country).WithIsInline(true)) .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("📏 **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("☁ **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("😓 **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("💨 **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("🌡 **Temperature**").WithValue(data.main.temp + "°C").WithIsInline(true))
@ -214,6 +218,9 @@ namespace NadekoBot.Modules.Searches
.ConfigureAwait(false); .ConfigureAwait(false);
} }
//private readonly Regex googleSearchRegex = new Regex(@"<h3 class=""r""><a href=""(?:\/url?q=)?(?<link>.*?)"".*?>(?<title>.*?)<\/a>.*?class=""st"">(?<text>.*?)<\/span>", RegexOptions.Compiled);
//private readonly Regex htmlReplace = new Regex(@"(?:<b>(.*?)<\/b>|<em>(.*?)<\/em>)", RegexOptions.Compiled);
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]
public async Task Google(IUserMessage umsg, [Remainder] string terms = null) public async Task Google(IUserMessage umsg, [Remainder] string terms = null)
@ -224,8 +231,50 @@ namespace NadekoBot.Modules.Searches
if (string.IsNullOrWhiteSpace(terms)) if (string.IsNullOrWhiteSpace(terms))
return; return;
await channel.SendMessageAsync($"https://google.com/search?q={ WebUtility.UrlEncode(terms).Replace(' ', '+') }") terms = WebUtility.UrlEncode(terms).Replace(' ', '+');
.ConfigureAwait(false);
var fullQueryLink = $"https://www.google.com/search?q={ terms }&gws_rd=cr,ssl";
var config = Configuration.Default.WithDefaultLoader();
var document = await BrowsingContext.New(config).OpenAsync(fullQueryLink);
var elems = document.QuerySelectorAll("div.g");
var resultsElem = document.QuerySelectorAll("#resultStats").FirstOrDefault();
var totalResults = resultsElem?.TextContent;
//var time = resultsElem.Children.FirstOrDefault()?.TextContent
//^ this doesn't work for some reason, <nobr> is completely missing in parsed collection
if (!elems.Any())
return;
var results = elems.Select<IElement, GoogleSearchResult?>(elem =>
{
var aTag = (elem.Children.FirstOrDefault().Children.FirstOrDefault() as IHtmlAnchorElement); // <h3> -> <a>
var href = aTag?.Href;
var name = aTag?.TextContent;
if (href == null || name == null)
return null;
var txt = elem.QuerySelectorAll(".st").FirstOrDefault()?.TextContent;
if (txt == null)
return null;
return new GoogleSearchResult(name, href, txt);
}).Where(x => x != null).Take(5);
var embed = new EmbedBuilder()
.WithOkColor()
.WithAuthor(eab => eab.WithName("Search For: " + terms)
.WithUrl(fullQueryLink)
.WithIconUrl("http://i.imgur.com/G46fm8J.png"))
.WithTitle(umsg.Author.Mention)
.WithFooter(efb => efb.WithText(totalResults));
string desc = "";
foreach (GoogleSearchResult res in results)
{
desc += $"[{Format.Bold(res.Title)}]({res.Link})\n{res.Text}\n\n";
}
await channel.EmbedAsync(embed.WithDescription(desc).Build()).ConfigureAwait(false);
} }
[NadekoCommand, Usage, Description, Aliases] [NadekoCommand, Usage, Description, Aliases]
@ -265,7 +314,7 @@ namespace NadekoBot.Modules.Searches
.AddField(efb => efb.WithName("Store Url").WithValue(storeUrl).WithIsInline(true)) .AddField(efb => efb.WithName("Store Url").WithValue(storeUrl).WithIsInline(true))
.AddField(efb => efb.WithName("Cost").WithValue(cost).WithIsInline(true)) .AddField(efb => efb.WithName("Cost").WithValue(cost).WithIsInline(true))
.AddField(efb => efb.WithName("Types").WithValue(types).WithIsInline(true)); .AddField(efb => efb.WithName("Types").WithValue(types).WithIsInline(true));
//.AddField(efb => efb.WithName("Store Url").WithValue(await NadekoBot.Google.ShortenUrl(items[0]["store_url"].ToString())).WithIsInline(true)); //.AddField(efb => efb.WithName("Store Url").WithValue(await NadekoBot.Google.ShortenUrl(items[0]["store_url"].ToString())).WithIsInline(true));
await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); await channel.EmbedAsync(embed.Build()).ConfigureAwait(false);
} }

View File

@ -20,6 +20,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<DnxInvisibleContent Include="data\questions.json" /> <DnxInvisibleContent Include="data\questions.json" />
<DnxInvisibleContent Include="data\questions2.json" />
</ItemGroup> </ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project> </Project>

View File

@ -1374,7 +1374,7 @@ namespace NadekoBot.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to `{0}cm &quot;module name&quot; enable SomeChannel`. /// Looks up a localized string similar to `{0}cm ModuleName enable SomeChannel`.
/// </summary> /// </summary>
public static string chnlmdl_usage { public static string chnlmdl_usage {
get { get {
@ -2435,6 +2435,33 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to fairplay fp.
/// </summary>
public static string fairplay_cmd {
get {
return ResourceManager.GetString("fairplay_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Toggles fairplay. While enabled, music player will prioritize songs from users who didn&apos;t have their song recently played instead of the song&apos;s position in the queue..
/// </summary>
public static string fairplay_desc {
get {
return ResourceManager.GetString("fairplay_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}fp`.
/// </summary>
public static string fairplay_usage {
get {
return ResourceManager.GetString("fairplay_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to fw. /// Looks up a localized string similar to fw.
/// </summary> /// </summary>
@ -5856,7 +5883,7 @@ namespace NadekoBot.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to `{0}rm &quot;module name&quot; enable MyRole`. /// Looks up a localized string similar to `{0}rm ModuleName enable MyRole`.
/// </summary> /// </summary>
public static string rolemdl_usage { public static string rolemdl_usage {
get { get {
@ -5901,7 +5928,7 @@ namespace NadekoBot.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y.. /// Looks up a localized string similar to Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. Y can be a letter &apos;F&apos; if you want to roll fate dice instead of dnd..
/// </summary> /// </summary>
public static string roll_desc { public static string roll_desc {
get { get {
@ -5910,7 +5937,7 @@ namespace NadekoBot.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to `{0}roll` or `{0}roll 7` or `{0}roll 3d5`. /// Looks up a localized string similar to `{0}roll` or `{0}roll 7` or `{0}roll 3d5` or `{0}roll 5dF`.
/// </summary> /// </summary>
public static string roll_usage { public static string roll_usage {
get { get {
@ -6909,7 +6936,7 @@ namespace NadekoBot.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to `{0}sm &quot;module name&quot; enable`. /// Looks up a localized string similar to `{0}sm ModuleName enable`.
/// </summary> /// </summary>
public static string srvrmdl_usage { public static string srvrmdl_usage {
get { get {
@ -7692,7 +7719,7 @@ namespace NadekoBot.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to `{0}um &quot;module name&quot; enable SomeUsername`. /// Looks up a localized string similar to `{0}um ModuleName enable SomeUsername`.
/// </summary> /// </summary>
public static string usrmdl_usage { public static string usrmdl_usage {
get { get {

View File

@ -2817,4 +2817,13 @@
<data name="log_usage" xml:space="preserve"> <data name="log_usage" xml:space="preserve">
<value>`{0}log userpresence` or `{0}log userbanned`</value> <value>`{0}log userpresence` or `{0}log userbanned`</value>
</data> </data>
<data name="fairplay_cmd" xml:space="preserve">
<value>fairplay fp</value>
</data>
<data name="fairplay_desc" xml:space="preserve">
<value>Toggles fairplay. While enabled, music player will prioritize songs from users who didn't have their song recently played instead of the song's position in the queue.</value>
</data>
<data name="fairplay_usage" xml:space="preserve">
<value>`{0}fp`</value>
</data>
</root> </root>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -25,8 +25,10 @@
"Discord.Net": { "Discord.Net": {
"target": "project" "target": "project"
}, },
"AngleSharp": "0.9.9",
"Google.Apis.Urlshortener.v1": "1.19.0.138", "Google.Apis.Urlshortener.v1": "1.19.0.138",
"Google.Apis.YouTube.v3": "1.20.0.701", "Google.Apis.YouTube.v3": "1.20.0.701",
"Google.Apis.Customsearch.v1": "1.20.0.466",
"ImageSharp": "1.0.0-alpha-000079", "ImageSharp": "1.0.0-alpha-000079",
"Microsoft.EntityFrameworkCore": "1.1.0", "Microsoft.EntityFrameworkCore": "1.1.0",
"Microsoft.EntityFrameworkCore.Design": "1.1.0", "Microsoft.EntityFrameworkCore.Design": "1.1.0",