Split Geekbot.net into src/Bot, src/Core, and src/Web

This commit is contained in:
runebaas 2020-08-08 22:24:01 +02:00
parent 7b6dd2d2f9
commit fc0af492ad
No known key found for this signature in database
GPG key ID: 2677AF508D0300D6
197 changed files with 542 additions and 498 deletions

7
src/Web/ApiError.cs Normal file
View file

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

View file

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Discord.WebSocket;
using Geekbot.Core.GlobalSettings;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace Geekbot.Web.Controllers.Callback
{
public class CallbackController : Controller
{
private readonly DiscordSocketClient _client;
private readonly IGlobalSettings _globalSettings;
public CallbackController(DiscordSocketClient client, IGlobalSettings globalSettings)
{
_client = client;
_globalSettings = globalSettings;
}
[Route("/callback")]
public async Task<IActionResult> DoCallback([FromQuery] string code)
{
var token = "";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://discordapp.com");
var appInfo = await _client.GetApplicationInfoAsync();
var accessToken = _globalSettings.GetKey("OAuthToken");
var callbackUrl = _globalSettings.GetKey("OAuthCallbackUrl");
var form = new Dictionary<string, string>
{
{"client_id", appInfo.Id.ToString()},
{"client_secret", accessToken},
{"grant_type", "authorization_code"},
{"code", code},
{"scope", "identify email guilds"},
{"redirect_uri", callbackUrl}
};
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
var result = await client.PostAsync("/api/oauth2/token", new FormUrlEncodedContent(form));
result.EnsureSuccessStatusCode();
var stringResponse = await result.Content.ReadAsStringAsync();
var responseData = JsonConvert.DeserializeObject<CallbackTokenResponseDto>(stringResponse);
token = responseData.access_token;
}
return new RedirectResult($"https://geekbot.pizzaandcoffee.rocks/login?token={token}", false);
}
}
}

View file

@ -0,0 +1,11 @@
namespace Geekbot.Web.Controllers.Callback
{
public class CallbackTokenResponseDto
{
public string access_token { get; set; }
public string token_type { get; set; }
public int expires_in { get; set; }
public string refresh_token { get; set; }
public string scope { get; set; }
}
}

View file

@ -0,0 +1,41 @@
using System.Linq;
using Discord.Commands;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace Geekbot.Web.Controllers.Commands
{
[EnableCors("AllowSpecificOrigin")]
public class CommandController : Controller
{
private readonly CommandService _commands;
public CommandController(CommandService commands)
{
_commands = commands;
}
[Route("/v1/commands")]
public IActionResult GetCommands()
{
var commandList = (from cmd in _commands.Commands
let cmdParamsObj = cmd.Parameters.Select(cmdParam => new CommandParamDto
{
Summary = cmdParam.Summary,
Default = cmdParam.DefaultValue?.ToString(),
Type = cmdParam.Type?.ToString()
})
.ToList()
let param = string.Join(", !", cmd.Aliases)
select new CommandDto
{
Name = cmd.Name,
Summary = cmd.Summary,
IsAdminCommand = param.Contains("admin") || param.Contains("owner"),
Aliases = cmd.Aliases.ToList(),
Params = cmdParamsObj
}).ToList();
return Ok(commandList.FindAll(e => !e.Aliases[0].StartsWith("owner")));
}
}
}

View file

@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace Geekbot.Web.Controllers.Commands
{
public class CommandDto
{
public string Name { get; set; }
public string Summary { get; set; }
public bool IsAdminCommand { get; set; }
public List<string> Aliases { get; set; }
public List<CommandParamDto> Params { get; set; }
}
}

View file

@ -0,0 +1,9 @@
namespace Geekbot.Web.Controllers.Commands
{
public class CommandParamDto
{
public string Summary { get; set; }
public string Default { get; set; }
public string Type { get; set; }
}
}

View file

@ -0,0 +1,56 @@
using System.Collections.Generic;
using Geekbot.Core.Highscores;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace Geekbot.Web.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.Core.Highscores;
namespace Geekbot.Web.Controllers.Highscores
{
public class HighscoreControllerPostBodyDto
{
[Required]
public ulong GuildId { get; set; }
public HighscoreTypes Type { get; } = HighscoreTypes.messages;
[Range(1, 150)]
public int Amount { get; } = 50;
}
}

View file

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

View file

@ -0,0 +1,9 @@
namespace Geekbot.Web.Controllers.Status
{
public class ApiStatusDto
{
public string GeekbotVersion { get; set; }
public string ApiVersion { get; set; }
public string Status { get; set; }
}
}

View file

@ -0,0 +1,23 @@
using System.Globalization;
using Geekbot.Core;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace Geekbot.Web.Controllers.Status
{
[EnableCors("AllowSpecificOrigin")]
public class StatusController : Controller
{
[Route("/")]
public IActionResult GetCommands()
{
var responseBody = new ApiStatusDto
{
GeekbotVersion = Constants.BotVersion(),
ApiVersion = Constants.ApiVersion.ToString(CultureInfo.InvariantCulture),
Status = "Online"
};
return Ok(responseBody);
}
}
}

View file

@ -0,0 +1,28 @@
using System.Collections.Concurrent;
using Geekbot.Core.Logger;
using Microsoft.Extensions.Logging;
namespace Geekbot.Web.Logging
{
public class AspLogProvider : ILoggerProvider
{
private readonly IGeekbotLogger _geekbotLogger;
private readonly ConcurrentDictionary<string, AspLogger> _loggers = new ConcurrentDictionary<string, AspLogger>();
public AspLogProvider(IGeekbotLogger geekbotLogger)
{
_geekbotLogger = geekbotLogger;
}
public void Dispose()
{
_loggers.Clear();
}
public ILogger CreateLogger(string categoryName)
{
return _loggers.GetOrAdd(categoryName, name => new AspLogger(categoryName, _geekbotLogger));
}
}
}

View file

@ -0,0 +1,71 @@
using System;
using Geekbot.Core.Logger;
using Microsoft.Extensions.Logging;
namespace Geekbot.Web.Logging
{
public class AspLogger : ILogger
{
private readonly string _categoryName;
private readonly IGeekbotLogger _geekbotLogger;
public AspLogger(string categoryName, IGeekbotLogger geekbotLogger)
{
geekbotLogger.Trace(LogSource.Api, $"Adding {categoryName}");
_categoryName = categoryName;
_geekbotLogger = geekbotLogger;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
switch (logLevel)
{
case LogLevel.Trace:
_geekbotLogger.Trace(LogSource.Api, $"{eventId.Id} - {_categoryName} - {state}");
break;
case LogLevel.Debug:
_geekbotLogger.Debug(LogSource.Api, $"{eventId.Id} - {_categoryName} - {state}");
break;
case LogLevel.Information:
_geekbotLogger.Information(LogSource.Api, $"{eventId.Id} - {_categoryName} - {state}");
break;
case LogLevel.Warning:
_geekbotLogger.Warning(LogSource.Api, $"{eventId.Id} - {_categoryName} - {state}", exception);
break;
case LogLevel.Error:
case LogLevel.Critical:
_geekbotLogger.Error(LogSource.Api, $"{eventId.Id} - {_categoryName} - {state}", exception);
break;
case LogLevel.None:
break;
default:
throw new ArgumentOutOfRangeException(nameof(logLevel));
}
}
public bool IsEnabled(LogLevel logLevel)
{
return !_geekbotLogger.LogAsJson() && _geekbotLogger.GetNLogger().IsEnabled(ToGeekbotLogLevel(logLevel));
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
private static NLog.LogLevel ToGeekbotLogLevel(LogLevel level)
{
return level switch
{
LogLevel.Trace => NLog.LogLevel.Trace,
LogLevel.Debug => NLog.LogLevel.Debug,
LogLevel.Information => NLog.LogLevel.Info,
LogLevel.Warning => NLog.LogLevel.Warn,
LogLevel.Error => NLog.LogLevel.Error,
LogLevel.Critical => NLog.LogLevel.Fatal,
LogLevel.None => NLog.LogLevel.Off,
_ => throw new ArgumentOutOfRangeException(nameof(level))
};
}
}
}

26
src/Web/Web.csproj Normal file
View file

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>
<Version Condition=" '$(VersionSuffix)' == '' ">0.0.0-DEV</Version>
<RootNamespace>Geekbot.Web</RootNamespace>
<AssemblyName>Geekbot.Web</AssemblyName>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Cors" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup>
</Project>

58
src/Web/WebApiStartup.cs Normal file
View file

@ -0,0 +1,58 @@
using System.Net;
using System.Reflection;
using Discord.Commands;
using Discord.WebSocket;
using Geekbot.Core;
using Geekbot.Core.Database;
using Geekbot.Core.GlobalSettings;
using Geekbot.Core.Highscores;
using Geekbot.Core.Logger;
using Geekbot.Web.Logging;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Geekbot.Web
{
public static class WebApiStartup
{
public static void StartWebApi(IGeekbotLogger logger, RunParameters runParameters, CommandService commandService,
DatabaseContext databaseContext, DiscordSocketClient client, IGlobalSettings globalSettings, IHighscoreManager highscoreManager)
{
WebHost.CreateDefaultBuilder()
.UseKestrel(options =>
{
options.Listen(IPAddress.Any, int.Parse(runParameters.ApiPort));
})
.ConfigureServices(services =>
{
services.AddMvc();
services.AddSingleton(commandService);
services.AddSingleton(databaseContext);
services.AddSingleton(client);
services.AddSingleton(globalSettings);
services.AddSingleton(highscoreManager);
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
});
})
.Configure(app =>
{
app.UseMvc();
app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().Build());
})
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Debug);
logging.AddProvider(new AspLogProvider(logger));
})
.UseSetting(WebHostDefaults.ApplicationKey, typeof(WebApiStartup).GetTypeInfo().Assembly.FullName)
.Build().Run();
}
}
}