Sets up rewards api and handling.
This commit is contained in:
parent
391a2653d9
commit
2f10b30283
|
@ -1,4 +1,4 @@
|
|||
namespace ContinuousTests
|
||||
namespace Utils
|
||||
{
|
||||
public class TaskFactory
|
||||
{
|
|
@ -13,6 +13,11 @@
|
|||
return task.Result;
|
||||
}
|
||||
|
||||
public static void Wait(Task task)
|
||||
{
|
||||
task.Wait();
|
||||
}
|
||||
|
||||
public static string FormatDuration(TimeSpan d)
|
||||
{
|
||||
var result = "";
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{
|
||||
public EthAddress(string address)
|
||||
{
|
||||
Address = address;
|
||||
Address = address.ToLowerInvariant();
|
||||
}
|
||||
|
||||
public string Address { get; }
|
||||
|
|
|
@ -3,6 +3,7 @@ using DistTestCore.Logs;
|
|||
using Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Utils;
|
||||
using TaskFactory = Utils.TaskFactory;
|
||||
|
||||
namespace ContinuousTests
|
||||
{
|
||||
|
|
|
@ -6,6 +6,7 @@ using CodexPlugin;
|
|||
using DistTestCore.Logs;
|
||||
using Core;
|
||||
using KubernetesWorkflow.Types;
|
||||
using TaskFactory = Utils.TaskFactory;
|
||||
|
||||
namespace ContinuousTests
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using DistTestCore.Logs;
|
||||
using Logging;
|
||||
using TaskFactory = Utils.TaskFactory;
|
||||
|
||||
namespace ContinuousTests
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using Discord.WebSocket;
|
||||
using Discord;
|
||||
using Newtonsoft.Json;
|
||||
using BiblioTech.Rewards;
|
||||
|
||||
namespace BiblioTech
|
||||
{
|
||||
|
@ -25,6 +26,9 @@ namespace BiblioTech
|
|||
Program.AdminChecker.SetGuild(guild);
|
||||
Program.Log.Log($"Initializing for guild: '{guild.Name}'");
|
||||
|
||||
var roleController = new RoleController(guild);
|
||||
var rewardsApi = new RewardsApi(roleController);
|
||||
|
||||
var adminChannels = guild.TextChannels.Where(Program.AdminChecker.IsAdminChannel).ToArray();
|
||||
if (adminChannels == null || !adminChannels.Any()) throw new Exception("No admin message channel");
|
||||
Program.AdminChecker.SetAdminChannel(adminChannels.First());
|
||||
|
@ -58,6 +62,8 @@ namespace BiblioTech
|
|||
var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented);
|
||||
Program.Log.Error(json);
|
||||
}
|
||||
|
||||
rewardsApi.Start();
|
||||
}
|
||||
|
||||
private async Task SlashCommandHandler(SocketSlashCommand command)
|
||||
|
|
|
@ -19,6 +19,10 @@ namespace BiblioTech
|
|||
[Uniform("admin-channel-name", "ac", "ADMINCHANNELNAME", true, "Name of the Discord server channel where admin commands are allowed.")]
|
||||
public string AdminChannelName { get; set; } = "admin-channel";
|
||||
|
||||
[Uniform("rewards-channel-name", "ac", "REWARDSCHANNELNAME", false, "Name of the Discord server channel where participation rewards will be announced.")]
|
||||
public string RewardsChannelName { get; set; } = "";
|
||||
|
||||
|
||||
public string EndpointsPath
|
||||
{
|
||||
get
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using ArgsUniform;
|
||||
using BiblioTech.Commands;
|
||||
using BiblioTech.Rewards;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using Logging;
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
namespace BiblioTech.Rewards
|
||||
{
|
||||
public class GiveRewards
|
||||
{
|
||||
public Reward[] Rewards { get; set; } = Array.Empty<Reward>();
|
||||
}
|
||||
|
||||
public class Reward
|
||||
{
|
||||
public ulong RewardId { get; set; }
|
||||
public string[] UserAddresses { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
using Newtonsoft.Json;
|
||||
using System.Net;
|
||||
using TaskFactory = Utils.TaskFactory;
|
||||
|
||||
namespace BiblioTech.Rewards
|
||||
{
|
||||
public interface IDiscordRoleController
|
||||
{
|
||||
void GiveRole(ulong roleId, UserData userData);
|
||||
}
|
||||
|
||||
public class RewardsApi
|
||||
{
|
||||
private readonly HttpListener listener = new HttpListener();
|
||||
private readonly TaskFactory taskFactory = new TaskFactory();
|
||||
private readonly IDiscordRoleController roleController;
|
||||
private CancellationTokenSource cts = new CancellationTokenSource();
|
||||
|
||||
public RewardsApi(IDiscordRoleController roleController)
|
||||
{
|
||||
this.roleController = roleController;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
cts = new CancellationTokenSource();
|
||||
listener.Prefixes.Add($"http://*:31080/");
|
||||
listener.Start();
|
||||
taskFactory.Run(ConnectionDispatcher, nameof(ConnectionDispatcher));
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
listener.Stop();
|
||||
cts.Cancel();
|
||||
taskFactory.WaitAll();
|
||||
}
|
||||
|
||||
private void ConnectionDispatcher()
|
||||
{
|
||||
while (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
var wait = listener.GetContextAsync();
|
||||
wait.Wait(cts.Token);
|
||||
if (wait.IsCompletedSuccessfully)
|
||||
{
|
||||
taskFactory.Run(() =>
|
||||
{
|
||||
var context = wait.Result;
|
||||
try
|
||||
{
|
||||
HandleConnection(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Log.Error("Exception during HTTP handler: " + ex);
|
||||
}
|
||||
// Whatever happens, everything's always OK.
|
||||
context.Response.StatusCode = 200;
|
||||
context.Response.OutputStream.Close();
|
||||
}, nameof(HandleConnection));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleConnection(HttpListenerContext context)
|
||||
{
|
||||
var reader = new StreamReader(context.Request.InputStream);
|
||||
var content = reader.ReadToEnd();
|
||||
|
||||
var rewards = JsonConvert.DeserializeObject<GiveRewards>(content);
|
||||
if (rewards != null) ProcessRewards(rewards);
|
||||
}
|
||||
|
||||
private void ProcessRewards(GiveRewards rewards)
|
||||
{
|
||||
foreach (var reward in rewards.Rewards) ProcessReward(reward);
|
||||
}
|
||||
|
||||
private void ProcessReward(Reward reward)
|
||||
{
|
||||
foreach (var userAddress in reward.UserAddresses) GiveRoleToUser(reward.RewardId, userAddress);
|
||||
}
|
||||
|
||||
private void GiveRoleToUser(ulong rewardId, string userAddress)
|
||||
{
|
||||
var userData = Program.UserRepo.GetUserDataForAddress(new GethPlugin.EthAddress(userAddress));
|
||||
if (userData == null) return;
|
||||
|
||||
roleController.GiveRole(rewardId, userData);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
using Discord.WebSocket;
|
||||
using Utils;
|
||||
|
||||
namespace BiblioTech.Rewards
|
||||
{
|
||||
public class RoleController : IDiscordRoleController
|
||||
{
|
||||
private const string UsernameTag = "<USER>";
|
||||
private readonly SocketGuild guild;
|
||||
private readonly SocketTextChannel? rewardsChannel;
|
||||
|
||||
private readonly RoleReward[] roleRewards = new[]
|
||||
{
|
||||
new RoleReward(1187039439558541498, $"Congratulations {UsernameTag}, you got the test-reward!")
|
||||
};
|
||||
|
||||
public RoleController(SocketGuild guild)
|
||||
{
|
||||
this.guild = guild;
|
||||
|
||||
if (!string.IsNullOrEmpty(Program.Config.RewardsChannelName))
|
||||
{
|
||||
rewardsChannel = guild.TextChannels.SingleOrDefault(c => c.Name == Program.Config.RewardsChannelName);
|
||||
}
|
||||
}
|
||||
|
||||
public void GiveRole(ulong roleId, UserData userData)
|
||||
{
|
||||
var reward = roleRewards.SingleOrDefault(r => r.RoleId == roleId);
|
||||
if (reward == null) return;
|
||||
|
||||
var user = guild.Users.SingleOrDefault(u => u.Id == userData.DiscordId);
|
||||
if (user == null) return;
|
||||
|
||||
var role = guild.Roles.SingleOrDefault(r => r.Id == roleId);
|
||||
if (role == null) return;
|
||||
|
||||
|
||||
GiveRole(user, role);
|
||||
SendNotification(reward, userData, user, role);
|
||||
}
|
||||
|
||||
private void GiveRole(SocketGuildUser user, SocketRole role)
|
||||
{
|
||||
try
|
||||
{
|
||||
Time.Wait(user.AddRoleAsync(role));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Log.Error($"Failed to give role '{role.Name}' to user '{user.DisplayName}': {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private void SendNotification(RoleReward reward, UserData userData, SocketGuildUser user, SocketRole role)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (userData.NotificationsEnabled && rewardsChannel != null)
|
||||
{
|
||||
Time.Wait(rewardsChannel.SendMessageAsync(reward.Message.Replace(UsernameTag, user.DisplayName)));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Log.Error($"Failed to notify user '{user.DisplayName}' about role '{role.Name}': {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class RoleReward
|
||||
{
|
||||
public RoleReward(ulong roleId, string message)
|
||||
{
|
||||
RoleId = roleId;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public ulong RoleId { get; }
|
||||
public string Message { get; }
|
||||
}
|
||||
}
|
|
@ -96,6 +96,29 @@ namespace BiblioTech
|
|||
return userData.CreateOverview();
|
||||
}
|
||||
|
||||
public UserData? GetUserDataForAddress(EthAddress? address)
|
||||
{
|
||||
if (address == null) return null;
|
||||
|
||||
// If this becomes a performance problem, switch to in-memory cached list.
|
||||
var files = Directory.GetFiles(Program.Config.UserDataPath);
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = JsonConvert.DeserializeObject<UserData>(File.ReadAllText(file))!;
|
||||
if (user.CurrentAddress != null &&
|
||||
user.CurrentAddress.Address == address.Address)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool SetUserAddress(IUser user, EthAddress? address)
|
||||
{
|
||||
if (GetUserDataForAddress(address) != null)
|
||||
|
@ -132,34 +155,11 @@ namespace BiblioTech
|
|||
|
||||
private UserData CreateAndSaveNewUserData(IUser user)
|
||||
{
|
||||
var newUser = new UserData(user.Id, user.GlobalName, DateTime.UtcNow, null, new List<UserAssociateAddressEvent>(), new List<UserMintEvent>());
|
||||
var newUser = new UserData(user.Id, user.GlobalName, DateTime.UtcNow, null, new List<UserAssociateAddressEvent>(), new List<UserMintEvent>(), true);
|
||||
SaveUserData(newUser);
|
||||
return newUser;
|
||||
}
|
||||
|
||||
private UserData? GetUserDataForAddress(EthAddress? address)
|
||||
{
|
||||
if (address == null) return null;
|
||||
|
||||
// If this becomes a performance problem, switch to in-memory cached list.
|
||||
var files = Directory.GetFiles(Program.Config.UserDataPath);
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = JsonConvert.DeserializeObject<UserData>(File.ReadAllText(file))!;
|
||||
if (user.CurrentAddress != null &&
|
||||
user.CurrentAddress.Address == address.Address)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void SaveUserData(UserData userData)
|
||||
{
|
||||
var filename = GetFilename(userData);
|
||||
|
@ -185,7 +185,7 @@ namespace BiblioTech
|
|||
|
||||
public class UserData
|
||||
{
|
||||
public UserData(ulong discordId, string name, DateTime createdUtc, EthAddress? currentAddress, List<UserAssociateAddressEvent> associateEvents, List<UserMintEvent> mintEvents)
|
||||
public UserData(ulong discordId, string name, DateTime createdUtc, EthAddress? currentAddress, List<UserAssociateAddressEvent> associateEvents, List<UserMintEvent> mintEvents, bool notificationsEnabled)
|
||||
{
|
||||
DiscordId = discordId;
|
||||
Name = name;
|
||||
|
@ -193,6 +193,7 @@ namespace BiblioTech
|
|||
CurrentAddress = currentAddress;
|
||||
AssociateEvents = associateEvents;
|
||||
MintEvents = mintEvents;
|
||||
NotificationsEnabled = notificationsEnabled;
|
||||
}
|
||||
|
||||
public ulong DiscordId { get; }
|
||||
|
@ -201,6 +202,7 @@ namespace BiblioTech
|
|||
public EthAddress? CurrentAddress { get; set; }
|
||||
public List<UserAssociateAddressEvent> AssociateEvents { get; }
|
||||
public List<UserMintEvent> MintEvents { get; }
|
||||
public bool NotificationsEnabled { get; }
|
||||
|
||||
public string[] CreateOverview()
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue