Refactor !rank, add HighscoreManager and add /v1/highscore to the api

This commit is contained in:
runebaas 2018-09-05 22:55:45 +02:00
parent a5c70859a4
commit 99245b9ead
No known key found for this signature in database
GPG key ID: 2677AF508D0300D6
15 changed files with 261 additions and 109 deletions

View file

@ -5,31 +5,30 @@ using System.Text;
using System.Threading.Tasks;
using Discord.Commands;
using Geekbot.net.Database;
using Geekbot.net.Lib.AlmostRedis;
using Geekbot.net.Lib.Converters;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.Extensions;
using Geekbot.net.Lib.Highscores;
using Geekbot.net.Lib.UserRepository;
using StackExchange.Redis;
namespace Geekbot.net.Commands.User.Ranking
{
public class Rank : ModuleBase
{
private readonly IEmojiConverter _emojiConverter;
private readonly IHighscoreManager _highscoreManager;
private readonly IErrorHandler _errorHandler;
private readonly DatabaseContext _database;
private readonly IUserRepository _userRepository;
private readonly IAlmostRedis _redis;
public Rank(DatabaseContext database, IErrorHandler errorHandler, IUserRepository userRepository,
IEmojiConverter emojiConverter, IAlmostRedis redis)
IEmojiConverter emojiConverter, IHighscoreManager highscoreManager)
{
_database = database;
_errorHandler = errorHandler;
_userRepository = userRepository;
_emojiConverter = emojiConverter;
_redis = redis;
_highscoreManager = highscoreManager;
}
[Command("rank", RunMode = RunMode.Async)]
@ -38,10 +37,10 @@ namespace Geekbot.net.Commands.User.Ranking
{
try
{
RankType type;
HighscoreTypes type;
try
{
type = Enum.Parse<RankType>(typeUnformated.ToLower());
type = Enum.Parse<HighscoreTypes>(typeUnformated.ToLower());
}
catch
{
@ -49,7 +48,6 @@ namespace Geekbot.net.Commands.User.Ranking
return;
}
var replyBuilder = new StringBuilder();
if (amount > 20)
{
@ -57,69 +55,28 @@ namespace Geekbot.net.Commands.User.Ranking
amount = 20;
}
Dictionary<ulong, int> list;
switch (type)
var guildId = Context.Guild.Id;
Dictionary<HighscoreUserDto, int> highscoreUsers;
try
{
case RankType.messages:
list = GetMessageList(amount);
break;
case RankType.karma:
list = GetKarmaList(amount);
break;
case RankType.rolls:
list = GetRollsList(amount);
break;
default:
await ReplyAsync("Valid types are '`messages`' '`karma`', '`rolls`'");
return;
highscoreUsers = _highscoreManager.GetHighscoresWithUserData(type, guildId, amount);
}
if (!list.Any())
catch (HighscoreListEmptyException)
{
await ReplyAsync($"No {type} found on this server");
return;
}
int guildMessages = 0;
if (type == RankType.messages)
if (type == HighscoreTypes.messages)
{
// guildMessages = _database.Messages
// .Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong()))
// .Select(e => e.MessageCount)
// .Sum();
guildMessages = (int) _redis.Db.HashGet($"{Context.Guild.Id}:Messages", 0.ToString());
guildMessages = _database.Messages
.Where(e => e.GuildId.Equals(Context.Guild.Id.AsLong()))
.Select(e => e.MessageCount)
.Sum();
}
var highscoreUsers = new Dictionary<RankUserDto, int>();
var failedToRetrieveUser = false;
foreach (var user in list)
{
try
{
var guildUser = _userRepository.Get(user.Key);
if (guildUser?.Username != null)
{
highscoreUsers.Add(new RankUserDto
{
Username = guildUser.Username,
Discriminator = guildUser.Discriminator
}, user.Value);
}
else
{
highscoreUsers.Add(new RankUserDto
{
Id = user.Key.ToString()
}, user.Value);
failedToRetrieveUser = true;
}
}
catch
{
// ignore
}
}
var failedToRetrieveUser = highscoreUsers.Any(e => string.IsNullOrEmpty(e.Key.Username));
if (failedToRetrieveUser) replyBuilder.AppendLine(":warning: I couldn't find all usernames. Maybe they left the server?\n");
replyBuilder.AppendLine($":bar_chart: **{type.ToString().CapitalizeFirst()} Highscore for {Context.Guild.Name}**");
@ -134,7 +91,7 @@ namespace Geekbot.net.Commands.User.Ranking
? $"**{user.Key.Username}#{user.Key.Discriminator}**"
: $"**{user.Key.Id}**");
replyBuilder.Append(type == RankType.messages
replyBuilder.Append(type == HighscoreTypes.messages
? $" - {user.Value} {type} - {Math.Round((double) (100 * user.Value) / guildMessages, digits: 2)}%\n"
: $" - {user.Value} {type}\n");
@ -148,38 +105,5 @@ namespace Geekbot.net.Commands.User.Ranking
await _errorHandler.HandleCommandException(e, Context);
}
}
private Dictionary<ulong, int> GetMessageList(int amount)
{
return _database.Messages
.Where(k => k.GuildId.Equals(Context.Guild.Id.AsLong()))
.OrderByDescending(o => o.MessageCount)
.Take(amount)
.ToDictionary(key => key.UserId.AsUlong(), key => key.MessageCount);
// return _redis.Db
// .HashGetAll($"{Context.Guild.Id}:Messages")
// .Where(user => !user.Name.Equals(0))
// .OrderByDescending(s => s.Value)
// .Take(amount)
// .ToDictionary(user => ulong.Parse(user.Name), user => int.Parse(user.Value));
}
private Dictionary<ulong, int> GetKarmaList(int amount)
{
return _database.Karma
.Where(k => k.GuildId.Equals(Context.Guild.Id.AsLong()))
.OrderByDescending(o => o.Karma)
.Take(amount)
.ToDictionary(key => key.UserId.AsUlong(), key => key.Karma);
}
private Dictionary<ulong, int> GetRollsList(int amount)
{
return _database.Rolls
.Where(k => k.GuildId.Equals(Context.Guild.Id.AsLong()))
.OrderByDescending(o => o.Rolls)
.Take(amount)
.ToDictionary(key => key.UserId.AsUlong(), key => key.Rolls);
}
}
}

View file

@ -1,9 +0,0 @@
namespace Geekbot.net.Commands.User.Ranking
{
public enum RankType
{
messages,
karma,
rolls
}
}

View file

@ -43,7 +43,7 @@ namespace Geekbot.net.Database
Console.WriteLine(g.Name);
allGuilds.Add(g);
}
catch (Exception e)
catch (Exception)
{
// ignore
}

View file

@ -0,0 +1,13 @@
using System;
namespace Geekbot.net.Lib.Highscores
{
public class HighscoreListEmptyException : Exception
{
public HighscoreListEmptyException() {}
public HighscoreListEmptyException(string message) : base(message) {}
public HighscoreListEmptyException(string message, Exception inner) : base(message, inner) {}
}
}

View file

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Geekbot.net.Database;
using Geekbot.net.Lib.Extensions;
using Geekbot.net.Lib.Logger;
using Geekbot.net.Lib.UserRepository;
namespace Geekbot.net.Lib.Highscores
{
public class HighscoreManager : IHighscoreManager
{
private readonly DatabaseContext _database;
private readonly IUserRepository _userRepository;
public HighscoreManager(DatabaseContext databaseContext, IUserRepository userRepository)
{
_database = databaseContext;
_userRepository = userRepository;
}
public Dictionary<HighscoreUserDto, int> GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount)
{
Dictionary<ulong, int> list;
switch (type)
{
case HighscoreTypes.messages:
list = GetMessageList(guildId, amount);
break;
case HighscoreTypes.karma:
list = GetKarmaList(guildId, amount);
break;
case HighscoreTypes.rolls:
list = GetRollsList(guildId, amount);
break;
default:
list = new Dictionary<ulong, int>();
break;
}
if (!list.Any())
{
throw new HighscoreListEmptyException($"No {type} found for guild {guildId}");
}
var highscoreUsers = new Dictionary<HighscoreUserDto, int>();
foreach (var user in list)
{
try
{
var guildUser = _userRepository.Get(user.Key);
if (guildUser?.Username != null)
{
highscoreUsers.Add(new HighscoreUserDto
{
Username = guildUser.Username,
Discriminator = guildUser.Discriminator,
Avatar = guildUser.AvatarUrl
}, user.Value);
}
else
{
highscoreUsers.Add(new HighscoreUserDto
{
Id = user.Key.ToString()
}, user.Value);
}
}
catch
{
// ignore
}
}
return highscoreUsers;
}
public Dictionary<ulong, int> GetMessageList(ulong guildId, int amount)
{
return _database.Messages
.Where(k => k.GuildId.Equals(guildId.AsLong()))
.OrderByDescending(o => o.MessageCount)
.Take(amount)
.ToDictionary(key => key.UserId.AsUlong(), key => key.MessageCount);
}
public Dictionary<ulong, int> GetKarmaList(ulong guildId, int amount)
{
return _database.Karma
.Where(k => k.GuildId.Equals(guildId.AsLong()))
.OrderByDescending(o => o.Karma)
.Take(amount)
.ToDictionary(key => key.UserId.AsUlong(), key => key.Karma);
}
public Dictionary<ulong, int> GetRollsList(ulong guildId, int amount)
{
return _database.Rolls
.Where(k => k.GuildId.Equals(guildId.AsLong()))
.OrderByDescending(o => o.Rolls)
.Take(amount)
.ToDictionary(key => key.UserId.AsUlong(), key => key.Rolls);
}
}
}

View file

@ -0,0 +1,9 @@
namespace Geekbot.net.Lib.Highscores
{
public enum HighscoreTypes
{
messages,
karma,
rolls
}
}

View file

@ -1,8 +1,9 @@
namespace Geekbot.net.Commands.User.Ranking
namespace Geekbot.net.Lib.Highscores
{
internal class RankUserDto
public class HighscoreUserDto
{
public string Username { get; set; }
public string Avatar { get; set; }
public string Discriminator { get; set; }
public string Id { get; set; }
}

View file

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace Geekbot.net.Lib.Highscores
{
public interface IHighscoreManager
{
Dictionary<HighscoreUserDto, int> GetHighscoresWithUserData(HighscoreTypes type, ulong guildId, int amount);
Dictionary<ulong, int> GetMessageList(ulong guildId, int amount);
Dictionary<ulong, int> GetKarmaList(ulong guildId, int amount);
Dictionary<ulong, int> GetRollsList(ulong guildId, int amount);
}
}

View file

@ -17,6 +17,7 @@ namespace Geekbot.net.Lib.Logger
Command,
Api,
Migration,
HighscoreManager,
Other
}
}

View file

@ -7,7 +7,6 @@ using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.net.Database;
using Geekbot.net.Database.LoggingAdapter;
using Geekbot.net.Lib;
using Geekbot.net.Lib.AlmostRedis;
using Geekbot.net.Lib.Audio;
@ -15,6 +14,7 @@ using Geekbot.net.Lib.Clients;
using Geekbot.net.Lib.Converters;
using Geekbot.net.Lib.ErrorHandling;
using Geekbot.net.Lib.GlobalSettings;
using Geekbot.net.Lib.Highscores;
using Geekbot.net.Lib.Levels;
using Geekbot.net.Lib.Localization;
using Geekbot.net.Lib.Logger;
@ -40,6 +40,7 @@ namespace Geekbot.net
private IUserRepository _userRepository;
private RunParameters _runParameters;
private IAlmostRedis _redis;
private IHighscoreManager _highscoreManager;
private static void Main(string[] args)
{
@ -123,6 +124,7 @@ namespace Geekbot.net
var mtgManaConverter = new MtgManaConverter();
var wikipediaClient = new WikipediaClient();
var audioUtils = new AudioUtils();
_highscoreManager = new HighscoreManager(_databaseInitializer.Initialize(), _userRepository);
_services.AddSingleton<IAlmostRedis>(_redis);
_services.AddSingleton<IUserRepository>(_userRepository);
@ -135,6 +137,7 @@ namespace Geekbot.net
_services.AddSingleton<IMtgManaConverter>(mtgManaConverter);
_services.AddSingleton<IWikipediaClient>(wikipediaClient);
_services.AddSingleton<IAudioUtils>(audioUtils);
_services.AddSingleton<IHighscoreManager>(_highscoreManager);
_services.AddSingleton<IGlobalSettings>(_globalSettings);
_services.AddTransient<DatabaseContext>((e) => _databaseInitializer.Initialize());
@ -203,7 +206,7 @@ namespace Geekbot.net
private Task StartWebApi()
{
_logger.Information(LogSource.Api, "Starting Webserver");
WebApi.WebApiStartup.StartWebApi(_logger, _runParameters, _commands, _databaseInitializer.Initialize(), _client, _globalSettings);
WebApi.WebApiStartup.StartWebApi(_logger, _runParameters, _commands, _databaseInitializer.Initialize(), _client, _globalSettings, _highscoreManager);
return Task.CompletedTask;
}
}

View file

@ -0,0 +1,7 @@
namespace Geekbot.net.WebApi
{
public class ApiError
{
public string Message { get; set; }
}
}

View file

@ -0,0 +1,56 @@
using System.Collections.Generic;
using Geekbot.net.Lib.Highscores;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace Geekbot.net.WebApi.Controllers.Highscores
{
[EnableCors("AllowSpecificOrigin")]
public class HighscoreController : Controller
{
private readonly IHighscoreManager _highscoreManager;
public HighscoreController(IHighscoreManager highscoreManager)
{
_highscoreManager = highscoreManager;
}
[HttpPost]
[Route("/v1/highscore")]
public IActionResult GetHighscores([FromBody] HighscoreControllerPostBodyDto body)
{
if (!ModelState.IsValid || body == null)
{
var error = new SerializableError(ModelState);
return BadRequest(error);
}
Dictionary<HighscoreUserDto, int> list;
try
{
list = _highscoreManager.GetHighscoresWithUserData(body.Type, body.GuildId, body.Amount);
}
catch (HighscoreListEmptyException)
{
return NotFound(new ApiError
{
Message = $"No {body.Type} found on this server"
});
}
var response = new List<HighscoreControllerReponseBody>();
var counter = 1;
foreach (var item in list)
{
response.Add(new HighscoreControllerReponseBody
{
count = item.Value,
rank = counter,
user = item.Key
});
counter++;
}
return Ok(response);
}
}
}

View file

@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
using Geekbot.net.Lib.Highscores;
namespace Geekbot.net.WebApi.Controllers.Highscores
{
public class HighscoreControllerPostBodyDto
{
[Required]
public ulong GuildId { get; set; }
public HighscoreTypes Type { get; set; } = HighscoreTypes.messages;
[Range(1, 150)]
public int Amount { get; set; } = 50;
}
}

View file

@ -0,0 +1,11 @@
using Geekbot.net.Lib.Highscores;
namespace Geekbot.net.WebApi.Controllers.Highscores
{
public class HighscoreControllerReponseBody
{
public int rank { get; set; }
public HighscoreUserDto user { get; set; }
public int count { get; set; }
}
}

View file

@ -5,6 +5,7 @@ using Discord.WebSocket;
using Geekbot.net.Database;
using Geekbot.net.Lib;
using Geekbot.net.Lib.GlobalSettings;
using Geekbot.net.Lib.Highscores;
using Geekbot.net.Lib.Logger;
using Geekbot.net.WebApi.Logging;
using Microsoft.AspNetCore;
@ -18,7 +19,7 @@ namespace Geekbot.net.WebApi
public static class WebApiStartup
{
public static void StartWebApi(IGeekbotLogger logger, RunParameters runParameters, CommandService commandService,
DatabaseContext databaseContext, DiscordSocketClient client, IGlobalSettings globalSettings)
DatabaseContext databaseContext, DiscordSocketClient client, IGlobalSettings globalSettings, IHighscoreManager highscoreManager)
{
WebHost.CreateDefaultBuilder()
.UseKestrel(options =>
@ -32,6 +33,7 @@ namespace Geekbot.net.WebApi
services.AddSingleton<DatabaseContext>(databaseContext);
services.AddSingleton<DiscordSocketClient>(client);
services.AddSingleton<IGlobalSettings>(globalSettings);
services.AddSingleton<IHighscoreManager>(highscoreManager);
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",