mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Merge branch 'v3-dev' into 'v3'
Created VotesApi project nad re-worked vote rewards handling See merge request Kwoth/nadekobot!172
This commit is contained in:
@@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Coordinator", "sr
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Generators", "src\NadekoBot.Generators\NadekoBot.Generators.csproj", "{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.Generators", "src\NadekoBot.Generators\NadekoBot.Generators.csproj", "{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot.VotesApi", "src\NadekoBot.VotesApi\NadekoBot.VotesApi.csproj", "{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -62,6 +64,12 @@ Global
|
|||||||
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.GlobalNadeko|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.GlobalNadeko|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -73,6 +81,7 @@ Global
|
|||||||
{DB448DD4-C97F-40E9-8BD3-F605FF1FF833} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
{DB448DD4-C97F-40E9-8BD3-F605FF1FF833} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
{AE9B7F8C-81D7-4401-83A3-643B38258374} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
{AE9B7F8C-81D7-4401-83A3-643B38258374} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
{3BC3BDF8-1A0B-45EB-AB2B-C0891D4D37B8} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
|
{3BC82CFE-BEE7-451F-986B-17EDD1570C4F} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}
|
SolutionGuid = {5F3F555C-855F-4BE8-B526-D062D3E8ACA4}
|
||||||
|
25
src/NadekoBot.VotesApi/.dockerignore
Normal file
25
src/NadekoBot.VotesApi/.dockerignore
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/.idea
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
1
src/NadekoBot.VotesApi/.gitignore
vendored
Normal file
1
src/NadekoBot.VotesApi/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
store/
|
44
src/NadekoBot.VotesApi/Common/AuthHandler.cs
Normal file
44
src/NadekoBot.VotesApi/Common/AuthHandler.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using NadekoBot.VotesApi.Controllers;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public class AuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||||
|
{
|
||||||
|
public const string SchemeName = "AUTHORIZATION_SCHEME";
|
||||||
|
public const string DiscordsClaim = "DISCORDS_CLAIM";
|
||||||
|
public const string TopggClaim = "TOPGG_CLAIM";
|
||||||
|
|
||||||
|
private readonly IConfiguration _conf;
|
||||||
|
|
||||||
|
public AuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||||
|
ILoggerFactory logger,
|
||||||
|
UrlEncoder encoder,
|
||||||
|
ISystemClock clock,
|
||||||
|
IConfiguration conf)
|
||||||
|
: base(options, logger, encoder, clock)
|
||||||
|
{
|
||||||
|
_conf = conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||||
|
{
|
||||||
|
var claims = new List<Claim>();
|
||||||
|
|
||||||
|
if (_conf[ConfKeys.DISCORDS_KEY].Trim() == Request.Headers["Authorization"].ToString().Trim())
|
||||||
|
claims.Add(new(DiscordsClaim, "true"));
|
||||||
|
|
||||||
|
if (_conf[ConfKeys.TOPGG_KEY] == Request.Headers["Authorization"].ToString().Trim())
|
||||||
|
claims.Add(new Claim(TopggClaim, "true"));
|
||||||
|
|
||||||
|
return Task.FromResult(AuthenticateResult.Success(new(new(new ClaimsIdentity(claims)), SchemeName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/NadekoBot.VotesApi/Common/ConfKeys.cs
Normal file
8
src/NadekoBot.VotesApi/Common/ConfKeys.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public static class ConfKeys
|
||||||
|
{
|
||||||
|
public const string DISCORDS_KEY = "DiscordsKey";
|
||||||
|
public const string TOPGG_KEY = "TopGGKey";
|
||||||
|
}
|
||||||
|
}
|
26
src/NadekoBot.VotesApi/Common/DiscordsVoteWebhookModel.cs
Normal file
26
src/NadekoBot.VotesApi/Common/DiscordsVoteWebhookModel.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public class DiscordsVoteWebhookModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the user who voted
|
||||||
|
/// </summary>
|
||||||
|
public string User { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the bot which recieved the vote
|
||||||
|
/// </summary>
|
||||||
|
public string Bot { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains totalVotes, votesMonth, votes24, hasVoted - a list of IDs of users who have voted this month, and
|
||||||
|
/// Voted24 - a list of IDs of users who have voted today
|
||||||
|
/// </summary>
|
||||||
|
public string Votes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of event, whether it is a vote event or test event
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
|
}
|
8
src/NadekoBot.VotesApi/Common/Policies.cs
Normal file
8
src/NadekoBot.VotesApi/Common/Policies.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public static class Policies
|
||||||
|
{
|
||||||
|
public const string DiscordsAuth = "DiscordsAuth";
|
||||||
|
public const string TopggAuth = "TopggAuth";
|
||||||
|
}
|
||||||
|
}
|
30
src/NadekoBot.VotesApi/Common/TopggVoteWebhookModel.cs
Normal file
30
src/NadekoBot.VotesApi/Common/TopggVoteWebhookModel.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public class TopggVoteWebhookModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Discord ID of the bot that received a vote.
|
||||||
|
/// </summary>
|
||||||
|
public string Bot { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discord ID of the user who voted.
|
||||||
|
/// </summary>
|
||||||
|
public string User { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the vote (should always be "upvote" except when using the test button it's "test").
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the weekend multiplier is in effect, meaning users votes count as two.
|
||||||
|
/// </summary>
|
||||||
|
public bool Weekend { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query string params found on the /bot/:ID/vote page. Example: ?a=1&b=2.
|
||||||
|
/// </summary>
|
||||||
|
public string Query { get; set; }
|
||||||
|
}
|
||||||
|
}
|
33
src/NadekoBot.VotesApi/Controllers/DiscordsController.cs
Normal file
33
src/NadekoBot.VotesApi/Controllers/DiscordsController.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NadekoBot.VotesApi.Services;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class DiscordsController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<TopGgController> _logger;
|
||||||
|
private readonly IVotesCache _cache;
|
||||||
|
|
||||||
|
public DiscordsController(ILogger<TopGgController> logger, IVotesCache cache)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("new")]
|
||||||
|
[Authorize(Policy = Policies.DiscordsAuth)]
|
||||||
|
public async Task<IEnumerable<Vote>> New()
|
||||||
|
{
|
||||||
|
var votes = await _cache.GetNewDiscordsVotesAsync();
|
||||||
|
if(votes.Count > 0)
|
||||||
|
_logger.LogInformation("Sending {NewDiscordsVotes} new discords votes.", votes.Count);
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/NadekoBot.VotesApi/Controllers/TopGgController.cs
Normal file
34
src/NadekoBot.VotesApi/Controllers/TopGgController.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NadekoBot.VotesApi.Services;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class TopGgController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<TopGgController> _logger;
|
||||||
|
private readonly IVotesCache _cache;
|
||||||
|
|
||||||
|
public TopGgController(ILogger<TopGgController> logger, IVotesCache cache)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("new")]
|
||||||
|
[Authorize(Policy = Policies.TopggAuth)]
|
||||||
|
public async Task<IEnumerable<Vote>> New()
|
||||||
|
{
|
||||||
|
var votes = await _cache.GetNewTopGgVotesAsync();
|
||||||
|
if(votes.Count > 0)
|
||||||
|
_logger.LogInformation("Sending {NewTopggVotes} new topgg votes.", votes.Count);
|
||||||
|
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
src/NadekoBot.VotesApi/Controllers/WebhookController.cs
Normal file
52
src/NadekoBot.VotesApi/Controllers/WebhookController.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NadekoBot.VotesApi.Services;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
public class WebhookController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<WebhookController> _logger;
|
||||||
|
private readonly IVotesCache _votesCache;
|
||||||
|
private readonly IConfiguration _conf;
|
||||||
|
|
||||||
|
public WebhookController(ILogger<WebhookController> logger, IVotesCache votesCache, IConfiguration conf)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_votesCache = votesCache;
|
||||||
|
_conf = conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("/discordswebhook")]
|
||||||
|
[Authorize(Policy = Policies.DiscordsAuth)]
|
||||||
|
public async Task<IActionResult> DiscordsWebhook([FromBody]DiscordsVoteWebhookModel data)
|
||||||
|
{
|
||||||
|
|
||||||
|
_logger.LogInformation("User {UserId} has voted for Bot {BotId} on {Platform}",
|
||||||
|
data.User,
|
||||||
|
data.Bot,
|
||||||
|
"discords.com");
|
||||||
|
|
||||||
|
await _votesCache.AddNewDiscordsVote(data.User);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("/topggwebhook")]
|
||||||
|
[Authorize(Policy = Policies.TopggAuth)]
|
||||||
|
public async Task<IActionResult> TopggWebhook([FromBody] TopggVoteWebhookModel data)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("User {UserId} has voted for Bot {BotId} on {Platform}",
|
||||||
|
data.User,
|
||||||
|
data.Bot,
|
||||||
|
"top.gg");
|
||||||
|
|
||||||
|
await _votesCache.AddNewTopggVote(data.User);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/NadekoBot.VotesApi/Dockerfile
Normal file
20
src/NadekoBot.VotesApi/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 443
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj", "NadekoBot.VotesApi/"]
|
||||||
|
RUN dotnet restore "src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/NadekoBot.VotesApi"
|
||||||
|
RUN dotnet build "NadekoBot.VotesApi.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
RUN dotnet publish "NadekoBot.VotesApi.csproj" -c Release -o /app/publish
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "NadekoBot.VotesApi.dll"]
|
13
src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj
Normal file
13
src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MorseCode.ITask" Version="2.0.3" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
23
src/NadekoBot.VotesApi/Program.cs
Normal file
23
src/NadekoBot.VotesApi/Program.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
CreateHostBuilder(args).Build().Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||||
|
Host.CreateDefaultBuilder(args)
|
||||||
|
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
|
||||||
|
}
|
||||||
|
}
|
31
src/NadekoBot.VotesApi/Properties/launchSettings.json
Normal file
31
src/NadekoBot.VotesApi/Properties/launchSettings.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:16451",
|
||||||
|
"sslPort": 44323
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"NadekoBot.VotesApi": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": "true",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/NadekoBot.VotesApi/README.md
Normal file
46
src/NadekoBot.VotesApi/README.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
## Votes Api
|
||||||
|
|
||||||
|
This api is used if you want your bot to be able to reward users who vote for it on discords.com or top.gg
|
||||||
|
|
||||||
|
#### [GET] `/discords/new`
|
||||||
|
Get the discords votes received after previous call to this endpoint.
|
||||||
|
Input full url of this endpoint in your creds.yml file under Discords url field.
|
||||||
|
For example "https://api.my.cool.bot/discords/new"
|
||||||
|
#### [GET] `/topgg/new`
|
||||||
|
Get the topgg votes received after previous call to this endpoint.
|
||||||
|
Input full url of this endpoint in your creds.yml file under Topgg url field.
|
||||||
|
For example "https://api.my.cool.bot/topgg/new"
|
||||||
|
|
||||||
|
#### [POST] `/discordswebhook`
|
||||||
|
Input this endpoint as the webhook on discords.com bot edit page
|
||||||
|
model: https://docs.botsfordiscord.com/methods/receiving-votes
|
||||||
|
For example "https://api.my.cool.bot/topggwebhook"
|
||||||
|
#### [POST] `/topggwebhook`
|
||||||
|
Input this endpoint as the webhook https://top.gg/bot/:your-bot-id/webhooks (replace :your-bot-id with your bot's id)
|
||||||
|
model: https://docs.top.gg/resources/webhooks/#schema
|
||||||
|
For example "https://api.my.cool.bot/discordswebhook"
|
||||||
|
|
||||||
|
Input your super-secret header value in appsettings.json's DiscordsKey and TopGGKey fields
|
||||||
|
They must match your DiscordsKey and TopGG key respectively, as well as your secrets in the discords.com and top.gg webhook setup pages
|
||||||
|
|
||||||
|
Full Example:
|
||||||
|
|
||||||
|
⚠ Change TopggKey and DiscordsKey to a secure long string
|
||||||
|
⚠ You can use https://www.random.org/strings/?num=1&len=20&digits=on&upperalpha=on&loweralpha=on&unique=on&format=html&rnd=new to generate it
|
||||||
|
|
||||||
|
`creds.yml`
|
||||||
|
```yml
|
||||||
|
votes:
|
||||||
|
TopggServiceUrl: "https://api.my.cool.bot/topgg"
|
||||||
|
TopggKey: "my_topgg_key"
|
||||||
|
DiscordsServiceUrl: "https://api.my.cool.bot/discords"
|
||||||
|
DiscordsKey: "my_discords_key"
|
||||||
|
```
|
||||||
|
|
||||||
|
`appsettings.json`
|
||||||
|
```json
|
||||||
|
...
|
||||||
|
"DiscordsKey": "my_discords_key",
|
||||||
|
"TopGGKey": "my_topgg_key",
|
||||||
|
...
|
||||||
|
```
|
105
src/NadekoBot.VotesApi/Services/FileVotesCache.cs
Normal file
105
src/NadekoBot.VotesApi/Services/FileVotesCache.cs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using MorseCode.ITask;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi.Services
|
||||||
|
{
|
||||||
|
public class FileVotesCache : IVotesCache
|
||||||
|
{
|
||||||
|
private const string statsFile = "store/stats.json";
|
||||||
|
private const string topggFile = "store/topgg.json";
|
||||||
|
private const string discordsFile = "store/discords.json";
|
||||||
|
|
||||||
|
private readonly SemaphoreSlim locker = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
public FileVotesCache()
|
||||||
|
{
|
||||||
|
if (!Directory.Exists("store"))
|
||||||
|
Directory.CreateDirectory("store");
|
||||||
|
|
||||||
|
if(!File.Exists(topggFile))
|
||||||
|
File.WriteAllText(topggFile, "[]");
|
||||||
|
|
||||||
|
if(!File.Exists(discordsFile))
|
||||||
|
File.WriteAllText(discordsFile, "[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITask AddNewTopggVote(string userId)
|
||||||
|
{
|
||||||
|
return AddNewVote(topggFile, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITask AddNewDiscordsVote(string userId)
|
||||||
|
{
|
||||||
|
return AddNewVote(discordsFile, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ITask AddNewVote(string file, string userId)
|
||||||
|
{
|
||||||
|
await locker.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var votes = await GetVotesAsync(file);
|
||||||
|
votes.Add(userId);
|
||||||
|
await File.WriteAllTextAsync(file , JsonSerializer.Serialize(votes));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
locker.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ITask<IList<Vote>> GetNewTopGgVotesAsync()
|
||||||
|
{
|
||||||
|
var votes = await EvictTopggVotes();
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ITask<IList<Vote>> GetNewDiscordsVotesAsync()
|
||||||
|
{
|
||||||
|
var votes = await EvictDiscordsVotes();
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ITask<List<Vote>> EvictTopggVotes()
|
||||||
|
=> EvictVotes(topggFile);
|
||||||
|
|
||||||
|
private ITask<List<Vote>> EvictDiscordsVotes()
|
||||||
|
=> EvictVotes(discordsFile);
|
||||||
|
|
||||||
|
private async ITask<List<Vote>> EvictVotes(string file)
|
||||||
|
{
|
||||||
|
await locker.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
var ids = await GetVotesAsync(file);
|
||||||
|
await File.WriteAllTextAsync(file, "[]");
|
||||||
|
|
||||||
|
return ids?
|
||||||
|
.Select(x => (Ok: ulong.TryParse(x, out var r), Id: r))
|
||||||
|
.Where(x => x.Ok)
|
||||||
|
.Select(x => new Vote
|
||||||
|
{
|
||||||
|
UserId = x.Id
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
locker.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ITask<IList<string>> GetVotesAsync(string file)
|
||||||
|
{
|
||||||
|
await using var fs = File.Open(file, FileMode.Open);
|
||||||
|
var votes = await JsonSerializer.DeserializeAsync<List<string>>(fs);
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/NadekoBot.VotesApi/Services/IVotesCache.cs
Normal file
13
src/NadekoBot.VotesApi/Services/IVotesCache.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using MorseCode.ITask;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi.Services
|
||||||
|
{
|
||||||
|
public interface IVotesCache
|
||||||
|
{
|
||||||
|
ITask<IList<Vote>> GetNewTopGgVotesAsync();
|
||||||
|
ITask<IList<Vote>> GetNewDiscordsVotesAsync();
|
||||||
|
ITask AddNewTopggVote(string userId);
|
||||||
|
ITask AddNewDiscordsVote(string userId);
|
||||||
|
}
|
||||||
|
}
|
69
src/NadekoBot.VotesApi/Startup.cs
Normal file
69
src/NadekoBot.VotesApi/Startup.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using NadekoBot.VotesApi.Services;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddControllers();
|
||||||
|
services.AddSingleton<IVotesCache, FileVotesCache>();
|
||||||
|
services.AddSwaggerGen(c =>
|
||||||
|
{
|
||||||
|
c.SwaggerDoc("v1", new OpenApiInfo { Title = "NadekoBot.VotesApi", Version = "v1" });
|
||||||
|
});
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddAuthentication(opts =>
|
||||||
|
{
|
||||||
|
opts.DefaultScheme = AuthHandler.SchemeName;
|
||||||
|
opts.AddScheme<AuthHandler>(AuthHandler.SchemeName, AuthHandler.SchemeName);
|
||||||
|
});
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddAuthorization(opts =>
|
||||||
|
{
|
||||||
|
opts.DefaultPolicy = new AuthorizationPolicyBuilder(AuthHandler.SchemeName)
|
||||||
|
.RequireAssertion(x => false)
|
||||||
|
.Build();
|
||||||
|
opts.AddPolicy(Policies.DiscordsAuth, policy => policy.RequireClaim(AuthHandler.DiscordsClaim));
|
||||||
|
opts.AddPolicy(Policies.TopggAuth, policy => policy.RequireClaim(AuthHandler.TopggClaim));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
{
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "NadekoBot.VotesApi v1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/NadekoBot.VotesApi/WeatherForecast.cs
Normal file
9
src/NadekoBot.VotesApi/WeatherForecast.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace NadekoBot.VotesApi
|
||||||
|
{
|
||||||
|
public class Vote
|
||||||
|
{
|
||||||
|
public ulong UserId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
9
src/NadekoBot.VotesApi/appsettings.Development.json
Normal file
9
src/NadekoBot.VotesApi/appsettings.Development.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/NadekoBot.VotesApi/appsettings.json
Normal file
12
src/NadekoBot.VotesApi/appsettings.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DiscordsKey": "my_discords_key",
|
||||||
|
"TopGGKey": "my_topgg_key",
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
@@ -13,7 +13,7 @@ namespace NadekoBot.Common
|
|||||||
OwnerIds = new List<ulong>();
|
OwnerIds = new List<ulong>();
|
||||||
TotalShards = 1;
|
TotalShards = 1;
|
||||||
GoogleApiKey = string.Empty;
|
GoogleApiKey = string.Empty;
|
||||||
Votes = new(string.Empty, string.Empty);
|
Votes = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
||||||
Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
Patreon = new(string.Empty, string.Empty, string.Empty, string.Empty);
|
||||||
BotListToken = string.Empty;
|
BotListToken = string.Empty;
|
||||||
CleverbotApiKey = string.Empty;
|
CleverbotApiKey = string.Empty;
|
||||||
@@ -77,11 +77,6 @@ Change only if you've changed the coordinator address or port.")]
|
|||||||
public string PatreonCampaignId => Patreon?.CampaignId;
|
public string PatreonCampaignId => Patreon?.CampaignId;
|
||||||
[YamlIgnore]
|
[YamlIgnore]
|
||||||
public string PatreonAccessToken => Patreon?.AccessToken;
|
public string PatreonAccessToken => Patreon?.AccessToken;
|
||||||
|
|
||||||
[YamlIgnore]
|
|
||||||
public string VotesUrl => Votes?.Url;
|
|
||||||
[YamlIgnore]
|
|
||||||
public string VotesToken => Votes.Key;
|
|
||||||
|
|
||||||
[Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
|
[Comment(@"Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)")]
|
||||||
public string RapidApiKey { get; set; }
|
public string RapidApiKey { get; set; }
|
||||||
@@ -143,19 +138,44 @@ Windows default
|
|||||||
ClientSecret = clientSecret;
|
ClientSecret = clientSecret;
|
||||||
CampaignId = campaignId;
|
CampaignId = campaignId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PatreonSettings()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record VotesSettings
|
public sealed record VotesSettings
|
||||||
{
|
{
|
||||||
[Comment(@"")]
|
[Comment(@"top.gg votes service url
|
||||||
public string Url { get; set; }
|
This is the url of your instance of the NadekoBot.Votes api
|
||||||
[Comment(@"")]
|
Example: https://votes.my.cool.bot.com")]
|
||||||
public string Key { get; set; }
|
public string TopggServiceUrl { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Authorization header value sent to the TopGG service url with each request
|
||||||
|
This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file")]
|
||||||
|
public string TopggKey { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"discords.com votes service url
|
||||||
|
This is the url of your instance of the NadekoBot.Votes api
|
||||||
|
Example: https://votes.my.cool.bot.com")]
|
||||||
|
public string DiscordsServiceUrl { get; set; }
|
||||||
|
|
||||||
|
[Comment(@"Authorization header value sent to the Discords service url with each request
|
||||||
|
This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file")]
|
||||||
|
public string DiscordsKey { get; set; }
|
||||||
|
|
||||||
public VotesSettings(string url, string key)
|
public VotesSettings()
|
||||||
{
|
{
|
||||||
Url = url;
|
|
||||||
Key = key;
|
}
|
||||||
|
|
||||||
|
public VotesSettings(string topggServiceUrl, string topggKey, string discordsServiceUrl, string discordsKey)
|
||||||
|
{
|
||||||
|
TopggServiceUrl = topggServiceUrl;
|
||||||
|
TopggKey = topggKey;
|
||||||
|
DiscordsServiceUrl = discordsServiceUrl;
|
||||||
|
DiscordsKey = discordsKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,8 +20,7 @@ namespace NadekoBot
|
|||||||
string PatreonCampaignId { get; }
|
string PatreonCampaignId { get; }
|
||||||
string CleverbotApiKey { get; }
|
string CleverbotApiKey { get; }
|
||||||
RestartConfig RestartCommand { get; }
|
RestartConfig RestartCommand { get; }
|
||||||
string VotesUrl { get; }
|
Creds.VotesSettings Votes { get; }
|
||||||
string VotesToken { get; }
|
|
||||||
string BotListToken { get; }
|
string BotListToken { get; }
|
||||||
string RedisOptions { get; }
|
string RedisOptions { get; }
|
||||||
string LocationIqApiKey { get; }
|
string LocationIqApiKey { get; }
|
||||||
|
@@ -20,6 +20,7 @@ namespace NadekoBot.Common.Yml
|
|||||||
.WithTypeConverter(new Rgba32Converter())
|
.WithTypeConverter(new Rgba32Converter())
|
||||||
.WithTypeConverter(new CultureInfoConverter())
|
.WithTypeConverter(new CultureInfoConverter())
|
||||||
.WithTypeConverter(new UriConverter())
|
.WithTypeConverter(new UriConverter())
|
||||||
|
.IgnoreUnmatchedProperties()
|
||||||
.Build();
|
.Build();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -23,7 +23,7 @@ namespace NadekoBot.Modules.Gambling.Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Comment(@"DO NOT CHANGE")]
|
[Comment(@"DO NOT CHANGE")]
|
||||||
public int Version { get; set; } = 1;
|
public int Version { get; set; } = 2;
|
||||||
|
|
||||||
[Comment(@"Currency settings")]
|
[Comment(@"Currency settings")]
|
||||||
public CurrencyConfig Currency { get; set; }
|
public CurrencyConfig Currency { get; set; }
|
||||||
@@ -60,6 +60,10 @@ Set 0 for unlimited")]
|
|||||||
[Comment(@"Amount of currency selfhosters will get PER pledged dollar CENT.
|
[Comment(@"Amount of currency selfhosters will get PER pledged dollar CENT.
|
||||||
1 = 100 currency per $. Used almost exclusively on public nadeko.")]
|
1 = 100 currency per $. Used almost exclusively on public nadeko.")]
|
||||||
public decimal PatreonCurrencyPerCent { get; set; } = 1;
|
public decimal PatreonCurrencyPerCent { get; set; } = 1;
|
||||||
|
|
||||||
|
[Comment(@"Currency reward per vote.
|
||||||
|
This will work only if you've set up VotesApi and correct credentials for topgg and/or discords voting")]
|
||||||
|
public long VoteReward { get; set; } = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CurrencyConfig
|
public class CurrencyConfig
|
||||||
|
@@ -8,8 +8,6 @@ using System.Threading.Tasks;
|
|||||||
using System;
|
using System;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Linq;
|
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@@ -17,76 +15,22 @@ namespace NadekoBot.Modules.Gambling.Services
|
|||||||
{
|
{
|
||||||
public class CurrencyEventsService : INService
|
public class CurrencyEventsService : INService
|
||||||
{
|
{
|
||||||
public class VoteModel
|
|
||||||
{
|
|
||||||
public ulong User { get; set; }
|
|
||||||
public long Date { get; set; }
|
|
||||||
}
|
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private readonly ICurrencyService _cs;
|
private readonly ICurrencyService _cs;
|
||||||
private readonly IBotCredentials _creds;
|
|
||||||
private readonly IHttpClientFactory _http;
|
|
||||||
private readonly GamblingConfigService _configService;
|
private readonly GamblingConfigService _configService;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<ulong, ICurrencyEvent> _events =
|
private readonly ConcurrentDictionary<ulong, ICurrencyEvent> _events =
|
||||||
new ConcurrentDictionary<ulong, ICurrencyEvent>();
|
new ConcurrentDictionary<ulong, ICurrencyEvent>();
|
||||||
|
|
||||||
public CurrencyEventsService(DiscordSocketClient client,
|
|
||||||
IBotCredentials creds, ICurrencyService cs,
|
public CurrencyEventsService(
|
||||||
IHttpClientFactory http, GamblingConfigService configService)
|
DiscordSocketClient client,
|
||||||
|
ICurrencyService cs,
|
||||||
|
GamblingConfigService configService)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_cs = cs;
|
_cs = cs;
|
||||||
_creds = creds;
|
|
||||||
_http = http;
|
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
|
|
||||||
if (_client.ShardId == 0)
|
|
||||||
{
|
|
||||||
Task t = BotlistUpvoteLoop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo future use votes api directly?
|
|
||||||
private async Task BotlistUpvoteLoop()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(_creds.VotesUrl))
|
|
||||||
return;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
await Task.Delay(TimeSpan.FromHours(1)).ConfigureAwait(false);
|
|
||||||
await TriggerVoteCheck().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task TriggerVoteCheck()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var req = new HttpRequestMessage(HttpMethod.Get, _creds.VotesUrl))
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(_creds.VotesToken))
|
|
||||||
req.Headers.Add("Authorization", _creds.VotesToken);
|
|
||||||
using (var http = _http.CreateClient())
|
|
||||||
using (var res = await http.SendAsync(req).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
if (!res.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
Log.Warning("Botlist API not reached.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var resStr = await res.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
var ids = JsonConvert.DeserializeObject<VoteModel[]>(resStr)
|
|
||||||
.Select(x => x.User)
|
|
||||||
.Distinct();
|
|
||||||
await _cs.AddBulkAsync(ids, ids.Select(x => "Voted - <https://discordbots.org/bot/nadeko/vote>"), ids.Select(x => 10L), true).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Warning(ex, "Error in TriggerVoteCheck");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> TryCreateEventAsync(ulong guildId, ulong channelId, CurrencyEvent.Type type,
|
public async Task<bool> TryCreateEventAsync(ulong guildId, ulong channelId, CurrencyEvent.Type type,
|
||||||
@@ -127,6 +71,7 @@ namespace NadekoBot.Modules.Gambling.Services
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return added;
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,4 +81,4 @@ namespace NadekoBot.Modules.Gambling.Services
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -63,6 +63,14 @@ namespace NadekoBot.Modules.Gambling.Services
|
|||||||
c.Version = 2;
|
c.Version = 2;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_data.Version < 3)
|
||||||
|
{
|
||||||
|
ModifyConfig(c =>
|
||||||
|
{
|
||||||
|
c.VoteReward = 100;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
122
src/NadekoBot/Modules/Gambling/Services/VoteRewardService.cs
Normal file
122
src/NadekoBot/Modules/Gambling/Services/VoteRewardService.cs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NadekoBot.Common.ModuleBehaviors;
|
||||||
|
using NadekoBot.Services;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Gambling.Services
|
||||||
|
{
|
||||||
|
public class VoteModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("userId")]
|
||||||
|
public ulong UserId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VoteRewardService : INService, IReadyExecutor
|
||||||
|
{
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly IBotCredentials _creds;
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly ICurrencyService _currencyService;
|
||||||
|
private readonly GamblingConfigService _gamb;
|
||||||
|
private HttpClient _http;
|
||||||
|
|
||||||
|
public VoteRewardService(
|
||||||
|
DiscordSocketClient client,
|
||||||
|
IBotCredentials creds,
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
ICurrencyService currencyService,
|
||||||
|
GamblingConfigService gamb)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
_creds = creds;
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_currencyService = currencyService;
|
||||||
|
_gamb = gamb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
if (_client.ShardId != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_http = new HttpClient(new HttpClientHandler()
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = false,
|
||||||
|
ServerCertificateCustomValidationCallback = delegate { return true; }
|
||||||
|
});
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
await Task.Delay(30000);
|
||||||
|
|
||||||
|
var topggKey = _creds.Votes?.TopggKey;
|
||||||
|
var topggServiceUrl = _creds.Votes?.TopggServiceUrl;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(topggKey)
|
||||||
|
&& !string.IsNullOrWhiteSpace(topggServiceUrl))
|
||||||
|
{
|
||||||
|
_http.DefaultRequestHeaders.Authorization = new(topggKey);
|
||||||
|
var uri = new Uri(new(topggServiceUrl), "topgg/new");
|
||||||
|
var res = await _http.GetStringAsync(uri);
|
||||||
|
var data = JsonSerializer.Deserialize<List<VoteModel>>(res);
|
||||||
|
|
||||||
|
if (data is { Count: > 0 })
|
||||||
|
{
|
||||||
|
var ids = data.Select(x => x.UserId).ToList();
|
||||||
|
|
||||||
|
await _currencyService.AddBulkAsync(ids,
|
||||||
|
data.Select(_ => "top.gg vote reward"),
|
||||||
|
data.Select(x => _gamb.Data.VoteReward),
|
||||||
|
true);
|
||||||
|
|
||||||
|
Log.Information("Rewarding {Count} top.gg voters", ids.Count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Critical error loading top.gg vote rewards.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var discordsKey = _creds.Votes?.DiscordsKey;
|
||||||
|
var discordsServiceUrl = _creds.Votes?.DiscordsServiceUrl;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(discordsKey)
|
||||||
|
&& !string.IsNullOrWhiteSpace(discordsServiceUrl))
|
||||||
|
{
|
||||||
|
_http.DefaultRequestHeaders.Authorization = new(discordsKey);
|
||||||
|
var res = await _http.GetStringAsync(new Uri(new(discordsServiceUrl), "discords/new"));
|
||||||
|
var data = JsonSerializer.Deserialize<List<VoteModel>>(res);
|
||||||
|
|
||||||
|
if (data is { Count: > 0 })
|
||||||
|
{
|
||||||
|
var ids = data.Select(x => x.UserId).ToList();
|
||||||
|
|
||||||
|
await _currencyService.AddBulkAsync(ids,
|
||||||
|
data.Select(_ => "discords.com vote reward"),
|
||||||
|
data.Select(x => _gamb.Data.VoteReward),
|
||||||
|
true);
|
||||||
|
|
||||||
|
Log.Information("Rewarding {Count} discords.com voters", ids.Count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Critical error loading discords.com vote rewards.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -128,7 +128,10 @@ namespace NadekoBot.Services
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
oldCreds.PatreonCampaignId),
|
oldCreds.PatreonCampaignId),
|
||||||
Votes = new Creds.VotesSettings(oldCreds.VotesUrl, oldCreds.VotesToken),
|
Votes = new(oldCreds.VotesUrl,
|
||||||
|
oldCreds.VotesToken,
|
||||||
|
string.Empty,
|
||||||
|
string.Empty),
|
||||||
BotListToken = oldCreds.BotListToken,
|
BotListToken = oldCreds.BotListToken,
|
||||||
RedisOptions = oldCreds.RedisOptions,
|
RedisOptions = oldCreds.RedisOptions,
|
||||||
LocationIqApiKey = oldCreds.LocationIqApiKey,
|
LocationIqApiKey = oldCreds.LocationIqApiKey,
|
||||||
@@ -141,6 +144,17 @@ namespace NadekoBot.Services
|
|||||||
|
|
||||||
Log.Warning("Data from credentials.json has been moved to creds.yml\nPlease inspect your creds.yml for correctness");
|
Log.Warning("Data from credentials.json has been moved to creds.yml\nPlease inspect your creds.yml for correctness");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (File.Exists(_credsFileName))
|
||||||
|
{
|
||||||
|
var creds = Yaml.Deserializer.Deserialize<Creds>(File.ReadAllText(_credsFileName));
|
||||||
|
if (creds.Version <= 1)
|
||||||
|
{
|
||||||
|
creds.Version = 2;
|
||||||
|
File.WriteAllText(_credsFileName, Yaml.Serializer.Serialize(creds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Creds GetCreds() => _creds;
|
public Creds GetCreds() => _creds;
|
||||||
|
@@ -14,8 +14,20 @@ totalShards: 1
|
|||||||
googleApiKey: ''
|
googleApiKey: ''
|
||||||
# Settings for voting system for discordbots. Meant for use on global Nadeko.
|
# Settings for voting system for discordbots. Meant for use on global Nadeko.
|
||||||
votes:
|
votes:
|
||||||
url: ''
|
# top.gg votes service url
|
||||||
key: ''
|
# This is the url of your instance of the NadekoBot.Votes api
|
||||||
|
# Example: https://votes.my.cool.bot.com
|
||||||
|
topggServiceUrl: ''
|
||||||
|
# Authorization header value sent to the TopGG service url with each request
|
||||||
|
# This should be equivalent to the TopggKey in your NadekoBot.Votes api appsettings.json file
|
||||||
|
topggKey: ''
|
||||||
|
# discords.com votes service url
|
||||||
|
# This is the url of your instance of the NadekoBot.Votes api
|
||||||
|
# Example: https://votes.my.cool.bot.com
|
||||||
|
discordsServiceUrl: ''
|
||||||
|
# Authorization header value sent to the Discords service url with each request
|
||||||
|
# This should be equivalent to the DiscordsKey in your NadekoBot.Votes api appsettings.json file
|
||||||
|
discordsKey: ''
|
||||||
# Patreon auto reward system settings.
|
# Patreon auto reward system settings.
|
||||||
# go to https://www.patreon.com/portal -> my clients -> create client
|
# go to https://www.patreon.com/portal -> my clients -> create client
|
||||||
patreon:
|
patreon:
|
||||||
|
@@ -237,3 +237,6 @@ waifu:
|
|||||||
# Amount of currency selfhosters will get PER pledged dollar CENT.
|
# Amount of currency selfhosters will get PER pledged dollar CENT.
|
||||||
# 1 = 100 currency per $. Used almost exclusively on public nadeko.
|
# 1 = 100 currency per $. Used almost exclusively on public nadeko.
|
||||||
patreonCurrencyPerCent: 1
|
patreonCurrencyPerCent: 1
|
||||||
|
# Currency reward per vote.
|
||||||
|
# This will work only if you've set up VotesApi and correct credentials for topgg and/or discords voting
|
||||||
|
voteReward: 100
|
||||||
|
Reference in New Issue
Block a user