grpc xpshop api
This commit is contained in:
parent
2f740e96b8
commit
b3d2785cec
13 changed files with 281 additions and 80 deletions
src
EllieBot.GrpcApiBase/protos
EllieBot
Modules
Administration
Xp
Services/GrpcApi
strings/responses
|
@ -12,6 +12,8 @@ service GrpcXp {
|
|||
|
||||
rpc AddReward(AddRewardRequest) returns (AddRewardReply);
|
||||
rpc DeleteReward(DeleteRewardRequest) returns (DeleteRewardReply);
|
||||
|
||||
rpc GetUserXp(GetUserXpRequest) returns (GetUserXpReply);
|
||||
}
|
||||
|
||||
message GetXpLbRequest {
|
||||
|
@ -75,4 +77,18 @@ message DeleteRewardRequest {
|
|||
|
||||
message DeleteRewardReply {
|
||||
bool success = 1;
|
||||
}
|
||||
|
||||
message GetUserXpRequest {
|
||||
uint64 guildId = 1;
|
||||
uint64 userId = 2;
|
||||
}
|
||||
|
||||
message GetUserXpReply {
|
||||
int64 xp = 1;
|
||||
int64 requiredXp = 2;
|
||||
int64 level = 3;
|
||||
string club = 4;
|
||||
string clubIcon = 5;
|
||||
int32 rank = 6;
|
||||
}
|
71
src/EllieBot.GrpcApiBase/protos/xpshop.proto
Normal file
71
src/EllieBot.GrpcApiBase/protos/xpshop.proto
Normal file
|
@ -0,0 +1,71 @@
|
|||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "EllieBot.GrpcApi";
|
||||
|
||||
package greet;
|
||||
|
||||
service GrpcXpShop {
|
||||
rpc AddXpShopItem (AddXpShopItemRequest) returns (AddXpShopItemReply);
|
||||
rpc GetShopItems (GetShopItemsRequest) returns (GetShopItemsReply);
|
||||
rpc UseShopItem (UseShopItemRequest) returns (UseShopItemReply);
|
||||
rpc BuyShopItem (BuyShopItemRequest) returns (BuyShopItemReply);
|
||||
}
|
||||
|
||||
message UseShopItemRequest {
|
||||
uint64 userId = 1;
|
||||
string uniqueName = 2;
|
||||
GrpcXpShopItemType itemType = 3;
|
||||
}
|
||||
|
||||
message UseShopItemReply {
|
||||
bool success = 1;
|
||||
}
|
||||
|
||||
message BuyShopItemRequest {
|
||||
uint64 userId = 1;
|
||||
string uniqueName = 2;
|
||||
GrpcXpShopItemType itemType = 3;
|
||||
}
|
||||
|
||||
message BuyShopItemReply {
|
||||
bool success = 1;
|
||||
optional BuyShopItemError Error = 2;
|
||||
}
|
||||
|
||||
enum BuyShopItemError {
|
||||
NotEnough = 0;
|
||||
AlreadyOwned = 1;
|
||||
Unknown = 2;
|
||||
}
|
||||
|
||||
message AddXpShopItemRequest {
|
||||
XpShopItem item = 1;
|
||||
string uniqueName = 2;
|
||||
GrpcXpShopItemType itemType = 3;
|
||||
}
|
||||
|
||||
message AddXpShopItemReply {
|
||||
bool success = 1;
|
||||
}
|
||||
|
||||
message GetShopItemsRequest {
|
||||
|
||||
}
|
||||
|
||||
message GetShopItemsReply {
|
||||
repeated XpShopItem bgs = 1;
|
||||
repeated XpShopItem frames = 2;
|
||||
}
|
||||
|
||||
message XpShopItem {
|
||||
string Name = 1;
|
||||
string Description = 2;
|
||||
int64 Price = 3;
|
||||
string FullUrl = 4;
|
||||
string PreviewUrl = 5;
|
||||
}
|
||||
|
||||
enum GrpcXpShopItemType {
|
||||
Bg = 0;
|
||||
Frame = 1;
|
||||
}
|
|
@ -40,7 +40,9 @@ public partial class Administration
|
|||
var progressMsg = await Response().Pending(strs.prune_progress(0, 100)).SendAsync();
|
||||
var progress = GetProgressTracker(progressMsg);
|
||||
|
||||
var result = await _service.PruneWhere(ctx.Channel,
|
||||
var result = await _service.PruneWhere(
|
||||
ctx.User.Id,
|
||||
ctx.Channel,
|
||||
100,
|
||||
x => x.Author.Id == ctx.Client.CurrentUser.Id,
|
||||
progress);
|
||||
|
@ -66,13 +68,17 @@ public partial class Administration
|
|||
|
||||
PruneResult result;
|
||||
if (opts.Safe)
|
||||
result = await _service.PruneWhere((ITextChannel)ctx.Channel,
|
||||
result = await _service.PruneWhere(
|
||||
ctx.User.Id,
|
||||
(ITextChannel)ctx.Channel,
|
||||
100,
|
||||
x => x.Author.Id == user.Id && !x.IsPinned,
|
||||
progress,
|
||||
opts.After);
|
||||
else
|
||||
result = await _service.PruneWhere((ITextChannel)ctx.Channel,
|
||||
result = await _service.PruneWhere(
|
||||
ctx.User.Id,
|
||||
(ITextChannel)ctx.Channel,
|
||||
100,
|
||||
x => x.Author.Id == user.Id,
|
||||
progress,
|
||||
|
@ -107,13 +113,17 @@ public partial class Administration
|
|||
|
||||
PruneResult result;
|
||||
if (opts.Safe)
|
||||
result = await _service.PruneWhere((ITextChannel)ctx.Channel,
|
||||
result = await _service.PruneWhere(
|
||||
ctx.User.Id,
|
||||
ctx.Channel,
|
||||
count,
|
||||
x => !x.IsPinned && x.Id != progressMsg.Id,
|
||||
progress,
|
||||
opts.After);
|
||||
else
|
||||
result = await _service.PruneWhere((ITextChannel)ctx.Channel,
|
||||
result = await _service.PruneWhere(
|
||||
ctx.User.Id,
|
||||
ctx.Channel,
|
||||
count,
|
||||
x => x.Id != progressMsg.Id,
|
||||
progress,
|
||||
|
@ -133,13 +143,14 @@ public partial class Administration
|
|||
await progressMsg.ModifyAsync(props =>
|
||||
{
|
||||
props.Embed = CreateEmbed()
|
||||
.WithPendingColor()
|
||||
.WithDescription(GetText(strs.prune_progress(deleted, total)))
|
||||
.Build();
|
||||
.WithPendingColor()
|
||||
.WithDescription(GetText(strs.prune_progress(deleted, total)))
|
||||
.Build();
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -182,7 +193,9 @@ public partial class Administration
|
|||
PruneResult result;
|
||||
if (opts.Safe)
|
||||
{
|
||||
result = await _service.PruneWhere((ITextChannel)ctx.Channel,
|
||||
result = await _service.PruneWhere(
|
||||
ctx.User.Id,
|
||||
ctx.Channel,
|
||||
count,
|
||||
m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < _twoWeeks && !m.IsPinned,
|
||||
progress,
|
||||
|
@ -191,7 +204,9 @@ public partial class Administration
|
|||
}
|
||||
else
|
||||
{
|
||||
result = await _service.PruneWhere((ITextChannel)ctx.Channel,
|
||||
result = await _service.PruneWhere(
|
||||
ctx.User.Id,
|
||||
ctx.Channel,
|
||||
count,
|
||||
m => m.Author.Id == userId && DateTime.UtcNow - m.CreatedAt < _twoWeeks,
|
||||
progress,
|
||||
|
@ -233,7 +248,7 @@ public partial class Administration
|
|||
msg.DeleteAfter(5);
|
||||
break;
|
||||
case PruneResult.FeatureLimit:
|
||||
var msg2 = await Response().Pending(strs.feature_limit_reached_owner).SendAsync();
|
||||
var msg2 = await Response().Pending(strs.prune_patron).SendAsync();
|
||||
msg2.DeleteAfter(10);
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -1,23 +1,13 @@
|
|||
#nullable disable
|
||||
using EllieBot.Modules.Patronage;
|
||||
|
||||
namespace EllieBot.Modules.Administration.Services;
|
||||
|
||||
public class PruneService : IEService
|
||||
public class PruneService(ILogCommandService logService) : IEService
|
||||
{
|
||||
//channelids where prunes are currently occuring
|
||||
private readonly ConcurrentDictionary<ulong, CancellationTokenSource> _pruningGuilds = new();
|
||||
private readonly TimeSpan _twoWeeks = TimeSpan.FromDays(14);
|
||||
private readonly ILogCommandService _logService;
|
||||
private readonly IPatronageService _ps;
|
||||
|
||||
public PruneService(ILogCommandService logService, IPatronageService ps)
|
||||
{
|
||||
_logService = logService;
|
||||
_ps = ps;
|
||||
}
|
||||
|
||||
public async Task<PruneResult> PruneWhere(
|
||||
ulong runningUserId,
|
||||
IMessageChannel channel,
|
||||
int amount,
|
||||
Func<IMessage, bool> predicate,
|
||||
|
@ -37,11 +27,6 @@ public class PruneService : IEService
|
|||
|
||||
try
|
||||
{
|
||||
if (channel is ITextChannel tc && !await _ps.LimitHitAsync(LimitedFeatureName.Prune, tc.Guild.OwnerId))
|
||||
{
|
||||
return PruneResult.FeatureLimit;
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
IMessage[] msgs;
|
||||
IMessage lastMessage = null;
|
||||
|
@ -67,7 +52,7 @@ public class PruneService : IEService
|
|||
var singleDeletable = new List<IMessage>();
|
||||
foreach (var x in msgs)
|
||||
{
|
||||
_logService.AddDeleteIgnore(x.Id);
|
||||
logService.AddDeleteIgnore(x.Id);
|
||||
|
||||
if (now - x.CreatedAt < _twoWeeks)
|
||||
bulkDeletable.Add(x);
|
||||
|
|
|
@ -57,8 +57,7 @@ public partial class Administration
|
|||
_ => ctx.OkAsync(),
|
||||
async fl =>
|
||||
{
|
||||
_ = msg.RemoveReactionAsync(emote, ctx.Client.CurrentUser);
|
||||
await Response().Pending(strs.feature_limit_reached_owner).SendAsync();
|
||||
await msg.RemoveReactionAsync(emote, ctx.Client.CurrentUser);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -7,5 +7,4 @@ public enum BuyResult
|
|||
AlreadyOwned,
|
||||
InsufficientFunds,
|
||||
UnknownItem,
|
||||
InsufficientPatronTier,
|
||||
}
|
|
@ -327,12 +327,6 @@ public partial class Xp : EllieModule<XpService>
|
|||
if (!string.IsNullOrWhiteSpace(item.Desc))
|
||||
eb.AddField(GetText(strs.desc), item.Desc);
|
||||
|
||||
var tier = _service.GetXpShopTierRequirement(type);
|
||||
if (tier != PatronTier.None)
|
||||
{
|
||||
eb.WithFooter(GetText(strs.xp_shop_buy_required_tier(tier.ToString())));
|
||||
}
|
||||
|
||||
return eb;
|
||||
})
|
||||
.Interaction(async current =>
|
||||
|
@ -407,7 +401,6 @@ public partial class Xp : EllieModule<XpService>
|
|||
BuyResult.AlreadyOwned =>
|
||||
await Response().Error(strs.xpshop_already_owned).Interaction(GetUseInteraction()).SendAsync(),
|
||||
BuyResult.UnknownItem => await Response().Error(strs.xpshop_item_not_found).SendAsync(),
|
||||
BuyResult.InsufficientPatronTier => await Response().Error(strs.patron_insuff_tier).SendAsync(),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
return;
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace EllieBot.Modules.Xp;
|
|||
public sealed partial class XpConfig : ICloneable<XpConfig>
|
||||
{
|
||||
[Comment("""DO NOT CHANGE""")]
|
||||
public int Version { get; set; } = 10;
|
||||
public int Version { get; set; } = 11;
|
||||
|
||||
[Comment("""How much XP will the users receive per message""")]
|
||||
public int TextXpPerMessage { get; set; } = 3;
|
||||
|
@ -36,18 +36,6 @@ public sealed partial class XpConfig : ICloneable<XpConfig>
|
|||
""")]
|
||||
public bool IsEnabled { get; set; } = false;
|
||||
|
||||
[Comment("""
|
||||
Which patron tier do users need in order to use the .xpshop bgs command
|
||||
Leave at 'None' if patron system is disabled or you don't want any restrictions
|
||||
""")]
|
||||
public PatronTier BgsTierRequirement { get; set; } = PatronTier.None;
|
||||
|
||||
[Comment("""
|
||||
Which patron tier do users need in order to use the .xpshop frames command
|
||||
Leave at 'None' if patron system is disabled or you don't want any restrictions
|
||||
""")]
|
||||
public PatronTier FramesTierRequirement { get; set; } = PatronTier.None;
|
||||
|
||||
[Comment("""
|
||||
Frames available for sale. Keys are unique IDs.
|
||||
Do not change keys as they are not publicly visible. Only change properties (name, price, id)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#nullable disable
|
||||
using EllieBot.Common.Configs;
|
||||
using EllieBot.Db.Models;
|
||||
|
||||
namespace EllieBot.Modules.Xp.Services;
|
||||
|
||||
|
@ -48,12 +49,27 @@ public sealed class XpConfigService : ConfigServiceBase<XpConfig>
|
|||
|
||||
private void Migrate()
|
||||
{
|
||||
if (data.Version < 10)
|
||||
if (data.Version < 11)
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
c.Version = 10;
|
||||
});
|
||||
ModifyConfig(c => { c.Version = 11; });
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> AddItemAsync(string uniqueName, XpShopItemType itemType, XpConfig.ShopItemInfo shopItemInfo)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
var success = false;
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
var items = itemType == XpShopItemType.Background
|
||||
? c.Shop.Bgs
|
||||
: c.Shop.Frames;
|
||||
|
||||
if (items is not null)
|
||||
success = items.TryAdd(uniqueName, shopItemInfo);
|
||||
});
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
|
@ -989,18 +989,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
if (!conf.Shop.IsEnabled)
|
||||
return BuyResult.XpShopDisabled;
|
||||
|
||||
var req = type == XpShopItemType.Background
|
||||
? conf.Shop.BgsTierRequirement
|
||||
: conf.Shop.FramesTierRequirement;
|
||||
|
||||
if (req != PatronTier.None && !_creds.IsOwner(userId))
|
||||
{
|
||||
var patron = await _ps.GetPatronAsync(userId);
|
||||
|
||||
if (patron is null || (int)patron.Value.Tier < (int)req)
|
||||
return BuyResult.InsufficientPatronTier;
|
||||
}
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
try
|
||||
{
|
||||
|
@ -1127,13 +1115,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
return false;
|
||||
}
|
||||
|
||||
public PatronTier GetXpShopTierRequirement(Xp.XpShopInputType type)
|
||||
=> type switch
|
||||
{
|
||||
Xp.XpShopInputType.F => _xpConfig.Data.Shop.FramesTierRequirement,
|
||||
_ => PatronTier.None,
|
||||
};
|
||||
|
||||
public bool IsShopEnabled()
|
||||
=> _xpConfig.Data.Shop.IsEnabled;
|
||||
|
||||
|
|
93
src/EllieBot/Services/GrpcApi/XpShopSvc.cs
Normal file
93
src/EllieBot/Services/GrpcApi/XpShopSvc.cs
Normal file
|
@ -0,0 +1,93 @@
|
|||
using Grpc.Core;
|
||||
using EllieBot.Db.Models;
|
||||
using EllieBot.Modules.Xp;
|
||||
using EllieBot.Modules.Xp.Services;
|
||||
|
||||
namespace EllieBot.GrpcApi;
|
||||
|
||||
public class XpShopSvc(XpService xp, XpConfigService xpConfig) : GrpcXpShop.GrpcXpShopBase, IGrpcSvc, IEService
|
||||
{
|
||||
public ServerServiceDefinition Bind()
|
||||
=> GrpcXpShop.BindService(this);
|
||||
|
||||
public override async Task<BuyShopItemReply> BuyShopItem(BuyShopItemRequest request, ServerCallContext context)
|
||||
{
|
||||
var result = await xp.BuyShopItemAsync(request.UserId, (XpShopItemType)request.ItemType, request.UniqueName);
|
||||
|
||||
var res = new BuyShopItemReply();
|
||||
|
||||
if (result == BuyResult.Success)
|
||||
{
|
||||
res.Success = true;
|
||||
return res;
|
||||
}
|
||||
|
||||
res.Error = result switch
|
||||
{
|
||||
BuyResult.AlreadyOwned => BuyShopItemError.AlreadyOwned,
|
||||
BuyResult.InsufficientFunds => BuyShopItemError.NotEnough,
|
||||
_ => BuyShopItemError.Unknown
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public override async Task<UseShopItemReply> UseShopItem(UseShopItemRequest request, ServerCallContext context)
|
||||
{
|
||||
var result = await xp.UseShopItemAsync(request.UserId, (XpShopItemType)request.ItemType, request.UniqueName);
|
||||
|
||||
var res = new UseShopItemReply
|
||||
{
|
||||
Success = result
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public override async Task<GetShopItemsReply> GetShopItems(GetShopItemsRequest request, ServerCallContext context)
|
||||
{
|
||||
var bgsTask = Task.Run(async () => await xp.GetShopBgs());
|
||||
var frsTask = Task.Run(async () => await xp.GetShopFrames());
|
||||
|
||||
var bgs = await bgsTask.Fmap(x => x?.Map(y => MapItemToGrpcItem(y.Value, y.Key)) ?? []);
|
||||
var frs = await frsTask.Fmap(z => z?.Map(y => MapItemToGrpcItem(y.Value, y.Key)) ?? []);
|
||||
|
||||
var res = new GetShopItemsReply();
|
||||
|
||||
res.Bgs.AddRange(bgs);
|
||||
res.Frames.AddRange(frs);
|
||||
|
||||
return res;
|
||||
|
||||
static XpShopItem MapItemToGrpcItem(XpConfig.ShopItemInfo item, string uniqueName)
|
||||
{
|
||||
return new XpShopItem()
|
||||
{
|
||||
Name = item.Name,
|
||||
Price = item.Price,
|
||||
Description = item.Desc,
|
||||
FullUrl = item.Url,
|
||||
PreviewUrl = item.Preview,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task<AddXpShopItemReply> AddXpShopItem(AddXpShopItemRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
var result = await xpConfig.AddItemAsync(request.UniqueName, (XpShopItemType)request.ItemType,
|
||||
new XpConfig.ShopItemInfo()
|
||||
{
|
||||
Name = request.Item.Name,
|
||||
Price = 3000,
|
||||
Desc = request.Item.Description,
|
||||
Url = request.Item.FullUrl,
|
||||
Preview = request.Item.PreviewUrl,
|
||||
});
|
||||
|
||||
return new AddXpShopItemReply()
|
||||
{
|
||||
Success = result,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -193,4 +193,51 @@ public class XpSvc : GrpcXp.GrpcXpBase, IGrpcSvc, IEService
|
|||
|
||||
return reply;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets XP information for a specific user in a guild
|
||||
/// </summary>
|
||||
public override async Task<GetUserXpReply> GetUserXp(
|
||||
GetUserXpRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
var guild = _client.GetGuild(request.GuildId);
|
||||
|
||||
if (guild is null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
|
||||
|
||||
var user = guild.GetUser(request.UserId);
|
||||
|
||||
if (user is null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "User not found"));
|
||||
|
||||
var reply = new GetUserXpReply();
|
||||
|
||||
// Get user stats from the XP service
|
||||
var stats = await _xp.GetUserStatsAsync(user);
|
||||
var levelStats = stats.Guild;
|
||||
|
||||
// Get user's rank in guild
|
||||
var guildRank = stats.GuildRanking;
|
||||
|
||||
// Fill the response with user XP data
|
||||
reply.Xp = levelStats.LevelXp;
|
||||
reply.RequiredXp = levelStats.RequiredXp;
|
||||
reply.Level = levelStats.Level;
|
||||
reply.Rank = guildRank;
|
||||
|
||||
// Add club information if available
|
||||
if (stats.User.Club is not null)
|
||||
{
|
||||
reply.Club = stats.User.Club.ToString();
|
||||
reply.ClubIcon = stats.User.Club.ImageUrl ?? string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
reply.Club = string.Empty;
|
||||
reply.ClubIcon = string.Empty;
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
}
|
|
@ -1045,10 +1045,8 @@
|
|||
"bank_withdraw_insuff": "You don't have sufficient {0} in your bank account.",
|
||||
"cmd_group_commands": "'{0}' command group",
|
||||
"limit_reached": "Feature limit of {0} reached.",
|
||||
"feature_limit_reached_you": "You've reached the limit of {0} for the {1} feature. You may be able to increase this limit by upgrading your patron tier.",
|
||||
"feature_limit_reached_owner": "Feature limit reached. Server owner may upgrade patron level to increase the limit.",
|
||||
"feature_limit_reached_either": "The limit of {0} for the {1} feature has been reached. Either you or the server owner may able to upgrade this limit by upgrading the patron tier.",
|
||||
"xp_shop_buy_required_tier": "Buying items from this shop requires Patron Tier {0} or higher.",
|
||||
"feature_limit": "The limit of {0} for the {1} feature has been reached. Server owner may be able to increase the limit by upgrading the Patron Tier.",
|
||||
"prune_patron": "Deleting messages 2 weeks old or older requires [Patron Tier X](https://patreon.com/join/elliebot) or higher.",
|
||||
"available_commands": "Available Commands",
|
||||
"tier": "Tier",
|
||||
"pledge": "Pledge",
|
||||
|
|
Loading…
Add table
Reference in a new issue