mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 00:34:26 -05:00 
			
		
		
		
	Created VotesApi project nad re-worked vote rewards handling
This commit is contained in:
		@@ -13,7 +13,7 @@ namespace NadekoBot.Common
 | 
			
		||||
            OwnerIds = new List<ulong>();
 | 
			
		||||
            TotalShards = 1;
 | 
			
		||||
            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);
 | 
			
		||||
            BotListToken = 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;
 | 
			
		||||
        [YamlIgnore]
 | 
			
		||||
        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)")]
 | 
			
		||||
        public string RapidApiKey { get; set; }
 | 
			
		||||
@@ -143,19 +138,44 @@ Windows default
 | 
			
		||||
                ClientSecret = clientSecret;
 | 
			
		||||
                CampaignId = campaignId;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public PatreonSettings()
 | 
			
		||||
            {
 | 
			
		||||
                
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public sealed record VotesSettings
 | 
			
		||||
        {
 | 
			
		||||
            [Comment(@"")]
 | 
			
		||||
            public string Url { get; set; }
 | 
			
		||||
            [Comment(@"")]
 | 
			
		||||
            public string Key { get; set; }
 | 
			
		||||
            [Comment(@"top.gg votes service url
 | 
			
		||||
This is the url of your instance of the NadekoBot.Votes api
 | 
			
		||||
Example: https://votes.my.cool.bot.com")]
 | 
			
		||||
            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 CleverbotApiKey { get; }
 | 
			
		||||
        RestartConfig RestartCommand { get; }
 | 
			
		||||
        string VotesUrl { get; }
 | 
			
		||||
        string VotesToken { get; }
 | 
			
		||||
        Creds.VotesSettings Votes { get; }
 | 
			
		||||
        string BotListToken { get; }
 | 
			
		||||
        string RedisOptions { get; }
 | 
			
		||||
        string LocationIqApiKey { get; }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ namespace NadekoBot.Common.Yml
 | 
			
		||||
            .WithTypeConverter(new Rgba32Converter())
 | 
			
		||||
            .WithTypeConverter(new CultureInfoConverter())
 | 
			
		||||
            .WithTypeConverter(new UriConverter())
 | 
			
		||||
            .IgnoreUnmatchedProperties()
 | 
			
		||||
            .Build();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -23,7 +23,7 @@ namespace NadekoBot.Modules.Gambling.Common
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Comment(@"DO NOT CHANGE")]
 | 
			
		||||
        public int Version { get; set; } = 1;
 | 
			
		||||
        public int Version { get; set; } = 2;
 | 
			
		||||
 | 
			
		||||
        [Comment(@"Currency settings")]
 | 
			
		||||
        public CurrencyConfig Currency { get; set; }
 | 
			
		||||
@@ -60,6 +60,10 @@ Set 0 for unlimited")]
 | 
			
		||||
        [Comment(@"Amount of currency selfhosters will get PER pledged dollar CENT.
 | 
			
		||||
1 = 100 currency per $. Used almost exclusively on public nadeko.")]
 | 
			
		||||
        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
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,6 @@ using System.Threading.Tasks;
 | 
			
		||||
using System;
 | 
			
		||||
using NadekoBot.Services.Database.Models;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using NadekoBot.Modules.Gambling.Services;
 | 
			
		||||
using Serilog;
 | 
			
		||||
 | 
			
		||||
@@ -17,76 +15,22 @@ namespace NadekoBot.Modules.Gambling.Services
 | 
			
		||||
{
 | 
			
		||||
    public class CurrencyEventsService : INService
 | 
			
		||||
    {
 | 
			
		||||
        public class VoteModel
 | 
			
		||||
        {
 | 
			
		||||
            public ulong User { get; set; }
 | 
			
		||||
            public long Date { get; set; }
 | 
			
		||||
        }
 | 
			
		||||
        private readonly DiscordSocketClient _client;
 | 
			
		||||
        private readonly ICurrencyService _cs;
 | 
			
		||||
        private readonly IBotCredentials _creds;
 | 
			
		||||
        private readonly IHttpClientFactory _http;
 | 
			
		||||
        private readonly GamblingConfigService _configService;
 | 
			
		||||
 | 
			
		||||
        private readonly ConcurrentDictionary<ulong, ICurrencyEvent> _events =
 | 
			
		||||
            new ConcurrentDictionary<ulong, ICurrencyEvent>();
 | 
			
		||||
 | 
			
		||||
        public CurrencyEventsService(DiscordSocketClient client,
 | 
			
		||||
            IBotCredentials creds, ICurrencyService cs,
 | 
			
		||||
            IHttpClientFactory http, GamblingConfigService configService)
 | 
			
		||||
 | 
			
		||||
        public CurrencyEventsService(
 | 
			
		||||
            DiscordSocketClient client,
 | 
			
		||||
            ICurrencyService cs,
 | 
			
		||||
            GamblingConfigService configService)
 | 
			
		||||
        {
 | 
			
		||||
            _client = client;
 | 
			
		||||
            _cs = cs;
 | 
			
		||||
            _creds = creds;
 | 
			
		||||
            _http = http;
 | 
			
		||||
            _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,
 | 
			
		||||
@@ -127,6 +71,7 @@ namespace NadekoBot.Modules.Gambling.Services
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return added;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -136,4 +81,4 @@ namespace NadekoBot.Modules.Gambling.Services
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -63,6 +63,14 @@ namespace NadekoBot.Modules.Gambling.Services
 | 
			
		||||
                    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,
 | 
			
		||||
                        oldCreds.PatreonCampaignId),
 | 
			
		||||
                    Votes = new Creds.VotesSettings(oldCreds.VotesUrl, oldCreds.VotesToken),
 | 
			
		||||
                    Votes = new(oldCreds.VotesUrl,
 | 
			
		||||
                        oldCreds.VotesToken,
 | 
			
		||||
                        string.Empty,
 | 
			
		||||
                        string.Empty),
 | 
			
		||||
                    BotListToken = oldCreds.BotListToken,
 | 
			
		||||
                    RedisOptions = oldCreds.RedisOptions,
 | 
			
		||||
                    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");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,8 +14,20 @@ totalShards: 1
 | 
			
		||||
googleApiKey: ''
 | 
			
		||||
# Settings for voting system for discordbots. Meant for use on global Nadeko.
 | 
			
		||||
votes:
 | 
			
		||||
  url: ''
 | 
			
		||||
  key: ''
 | 
			
		||||
# top.gg votes service url
 | 
			
		||||
# 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.
 | 
			
		||||
# go to https://www.patreon.com/portal -> my clients -> create client
 | 
			
		||||
patreon:
 | 
			
		||||
 
 | 
			
		||||
@@ -237,3 +237,6 @@ waifu:
 | 
			
		||||
# Amount of currency selfhosters will get PER pledged dollar CENT.
 | 
			
		||||
# 1 = 100 currency per $. Used almost exclusively on public nadeko.
 | 
			
		||||
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