Add super basic group model/command
This commit is contained in:
parent
0fadc81cda
commit
253ae43c7f
@ -25,6 +25,7 @@ namespace PluralKit.Bot
|
||||
private readonly MessageContext _messageContext;
|
||||
|
||||
private readonly IDataStore _data;
|
||||
private readonly IDatabase _db;
|
||||
private readonly PKSystem _senderSystem;
|
||||
private readonly IMetrics _metrics;
|
||||
|
||||
@ -40,6 +41,7 @@ namespace PluralKit.Bot
|
||||
_data = provider.Resolve<IDataStore>();
|
||||
_senderSystem = senderSystem;
|
||||
_messageContext = messageContext;
|
||||
_db = provider.Resolve<IDatabase>();
|
||||
_metrics = provider.Resolve<IMetrics>();
|
||||
_provider = provider;
|
||||
_parameters = new Parameters(message.Content.Substring(commandParseOffset));
|
||||
@ -61,6 +63,7 @@ namespace PluralKit.Bot
|
||||
|
||||
// TODO: this is just here so the extension methods can access it; should it be public/private/?
|
||||
internal IDataStore DataStore => _data;
|
||||
internal IDatabase Database => _db;
|
||||
|
||||
public Task<DiscordMessage> Reply(string text = null, DiscordEmbed embed = null, IEnumerable<IMention> mentions = null)
|
||||
{
|
||||
|
@ -97,6 +97,26 @@ namespace PluralKit.Bot
|
||||
// Finally, we return the member value.
|
||||
return member;
|
||||
}
|
||||
|
||||
public static async Task<PKGroup> PeekGroup(this Context ctx)
|
||||
{
|
||||
var input = ctx.PeekArgument();
|
||||
|
||||
await using var conn = await ctx.Database.Obtain();
|
||||
if (await conn.QueryGroupByName(input) is {} byName)
|
||||
return byName;
|
||||
if (await conn.QueryGroupByHid(input) is {} byHid)
|
||||
return byHid;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static async Task<PKGroup> MatchGroup(this Context ctx)
|
||||
{
|
||||
var group = await ctx.PeekGroup();
|
||||
if (group != null) ctx.PopArgument();
|
||||
return group;
|
||||
}
|
||||
|
||||
public static string CreateMemberNotFoundError(this Context ctx, string input)
|
||||
{
|
||||
|
@ -45,6 +45,8 @@ namespace PluralKit.Bot
|
||||
public static Command MemberKeepProxy = new Command("member keepproxy", "member <member> keepproxy [on|off]", "Sets whether to include a member's proxy tags when proxying");
|
||||
public static Command MemberRandom = new Command("random", "random", "Looks up a random member from your system");
|
||||
public static Command MemberPrivacy = new Command("member privacy", "member <member> privacy <name|description|birthday|pronouns|metadata|visibility|all> <public|private>", "Changes a members's privacy settings");
|
||||
public static Command GroupInfo = new Command("group", "group <name>", "Looks up information about a group");
|
||||
public static Command GroupNew = new Command("group new", "group new <name>", "Creates a new group");
|
||||
public static Command Switch = new Command("switch", "switch <member> [member 2] [member 3...]", "Registers a switch");
|
||||
public static Command SwitchOut = new Command("switch out", "switch out", "Registers a switch with no members");
|
||||
public static Command SwitchMove = new Command("switch move", "switch move <date/time>", "Moves the latest switch in time");
|
||||
@ -97,6 +99,8 @@ namespace PluralKit.Bot
|
||||
return HandleSystemCommand(ctx);
|
||||
if (ctx.Match("member", "m"))
|
||||
return HandleMemberCommand(ctx);
|
||||
if (ctx.Match("group", "g"))
|
||||
return HandleGroupCommand(ctx);
|
||||
if (ctx.Match("switch", "sw"))
|
||||
return HandleSwitchCommand(ctx);
|
||||
if (ctx.Match("ap", "autoproxy", "auto"))
|
||||
@ -312,6 +316,19 @@ namespace PluralKit.Bot
|
||||
await PrintCommandNotFoundError(ctx, MemberInfo, MemberRename, MemberDisplayName, MemberServerName ,MemberDesc, MemberPronouns, MemberColor, MemberBirthday, MemberProxy, MemberDelete, MemberAvatar, SystemList);
|
||||
}
|
||||
|
||||
private async Task HandleGroupCommand(Context ctx)
|
||||
{
|
||||
// Commands with no group argument
|
||||
if (ctx.Match("n", "new"))
|
||||
await ctx.Execute<Groups>(GroupNew, g => g.CreateGroup(ctx));
|
||||
|
||||
if (await ctx.MatchGroup() is {} group)
|
||||
{
|
||||
// Commands with group argument
|
||||
await ctx.Execute<Groups>(GroupInfo, g => g.ShowGroupCard(ctx, group));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleSwitchCommand(Context ctx)
|
||||
{
|
||||
if (ctx.Match("out"))
|
||||
|
58
PluralKit.Bot/Commands/Groups.cs
Normal file
58
PluralKit.Bot/Commands/Groups.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using DSharpPlus.Entities;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.Bot
|
||||
{
|
||||
public class Groups
|
||||
{
|
||||
private readonly IDatabase _db;
|
||||
|
||||
public Groups(IDatabase db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task CreateGroup(Context ctx)
|
||||
{
|
||||
ctx.CheckSystem();
|
||||
|
||||
var groupName = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a group name.");
|
||||
if (groupName.Length > Limits.MaxGroupNameLength)
|
||||
throw new PKError($"Group name too long ({groupName.Length}/{Limits.MaxMemberNameLength} characters).");
|
||||
|
||||
await using var conn = await _db.Obtain();
|
||||
var newGroup = await conn.CreateGroup(ctx.System.Id, groupName);
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Group \"**{groupName}**\" (`{newGroup.Hid}`) registered!\nYou can now start adding members to the group:\n- **pk;group {newGroup.Hid} add <members...>**");
|
||||
}
|
||||
|
||||
public async Task ShowGroupCard(Context ctx, PKGroup target)
|
||||
{
|
||||
await using var conn = await _db.Obtain();
|
||||
|
||||
var system = await GetGroupSystem(ctx, target, conn);
|
||||
|
||||
var nameField = target.Name;
|
||||
if (system.Name != null)
|
||||
nameField = $"{nameField} ({system.Name})";
|
||||
|
||||
var eb = new DiscordEmbedBuilder()
|
||||
.WithAuthor(nameField)
|
||||
.WithDescription(target.Description)
|
||||
.WithFooter($"System ID: {system.Hid} | Group ID: {target.Hid} | Created on {target.Created.FormatZoned(system)}");
|
||||
|
||||
await ctx.Reply(embed: eb.Build());
|
||||
}
|
||||
|
||||
private static async Task<PKSystem> GetGroupSystem(Context ctx, PKGroup target, IPKConnection conn)
|
||||
{
|
||||
var system = ctx.System;
|
||||
if (system?.Id == target.System)
|
||||
return system;
|
||||
return await conn.QuerySystem(target.System)!;
|
||||
}
|
||||
}
|
||||
}
|
@ -33,6 +33,7 @@ namespace PluralKit.Bot
|
||||
builder.RegisterType<CommandTree>().AsSelf();
|
||||
builder.RegisterType<Autoproxy>().AsSelf();
|
||||
builder.RegisterType<Fun>().AsSelf();
|
||||
builder.RegisterType<Groups>().AsSelf();
|
||||
builder.RegisterType<Help>().AsSelf();
|
||||
builder.RegisterType<ImportExport>().AsSelf();
|
||||
builder.RegisterType<Member>().AsSelf();
|
||||
|
@ -20,7 +20,7 @@ namespace PluralKit.Core
|
||||
internal class Database: IDatabase
|
||||
{
|
||||
private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files
|
||||
private const int TargetSchemaVersion = 8;
|
||||
private const int TargetSchemaVersion = 9;
|
||||
|
||||
private readonly CoreConfig _config;
|
||||
private readonly ILogger _logger;
|
||||
@ -58,9 +58,11 @@ namespace PluralKit.Core
|
||||
SqlMapper.AddTypeHandler(new NumericIdHandler<SystemId, int>(i => new SystemId(i)));
|
||||
SqlMapper.AddTypeHandler(new NumericIdHandler<MemberId, int>(i => new MemberId(i)));
|
||||
SqlMapper.AddTypeHandler(new NumericIdHandler<SwitchId, int>(i => new SwitchId(i)));
|
||||
SqlMapper.AddTypeHandler(new NumericIdHandler<GroupId, int>(i => new GroupId(i)));
|
||||
SqlMapper.AddTypeHandler(new NumericIdArrayHandler<SystemId, int>(i => new SystemId(i)));
|
||||
SqlMapper.AddTypeHandler(new NumericIdArrayHandler<MemberId, int>(i => new MemberId(i)));
|
||||
SqlMapper.AddTypeHandler(new NumericIdArrayHandler<SwitchId, int>(i => new SwitchId(i)));
|
||||
SqlMapper.AddTypeHandler(new NumericIdArrayHandler<GroupId, int>(i => new GroupId(i)));
|
||||
|
||||
// Register our custom types to Npgsql
|
||||
// Without these it'll still *work* but break at the first launch + probably cause other small issues
|
||||
|
@ -111,4 +111,14 @@ begin
|
||||
if not exists (select 1 from members where hid = new_hid) then return new_hid; end if;
|
||||
end loop;
|
||||
end
|
||||
$$ language plpgsql volatile;
|
||||
|
||||
create function find_free_group_hid() returns char(5) as $$
|
||||
declare new_hid char(5);
|
||||
begin
|
||||
loop
|
||||
new_hid := generate_hid();
|
||||
if not exists (select 1 from groups where hid = new_hid) then return new_hid; end if;
|
||||
end loop;
|
||||
end
|
||||
$$ language plpgsql volatile;
|
17
PluralKit.Core/Database/Migrations/9.sql
Normal file
17
PluralKit.Core/Database/Migrations/9.sql
Normal file
@ -0,0 +1,17 @@
|
||||
-- SCHEMA VERSION 9: 2020-xx-xx --
|
||||
|
||||
create table groups (
|
||||
id int primary key generated always as identity,
|
||||
hid char(5) unique not null,
|
||||
system int not null references systems(id) on delete cascade,
|
||||
name text not null,
|
||||
description text,
|
||||
created timestamp with time zone not null default (current_timestamp at time zone 'utc')
|
||||
);
|
||||
|
||||
create table group_members (
|
||||
group_id int not null references groups(id) on delete cascade,
|
||||
member_id int not null references members(id) on delete cascade
|
||||
);
|
||||
|
||||
update info set schema_version = 9;
|
@ -10,4 +10,5 @@ drop function if exists message_context;
|
||||
drop function if exists proxy_members;
|
||||
drop function if exists generate_hid;
|
||||
drop function if exists find_free_system_hid;
|
||||
drop function if exists find_free_member_hid;
|
||||
drop function if exists find_free_member_hid;
|
||||
drop function if exists find_free_group_hid;
|
26
PluralKit.Core/Models/GroupId.cs
Normal file
26
PluralKit.Core/Models/GroupId.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace PluralKit.Core
|
||||
{
|
||||
public readonly struct GroupId: INumericId<GroupId, int>
|
||||
{
|
||||
public int Value { get; }
|
||||
|
||||
public GroupId(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public bool Equals(GroupId other) => Value == other.Value;
|
||||
|
||||
public override bool Equals(object obj) => obj is GroupId other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => Value;
|
||||
|
||||
public static bool operator ==(GroupId left, GroupId right) => left.Equals(right);
|
||||
|
||||
public static bool operator !=(GroupId left, GroupId right) => !left.Equals(right);
|
||||
|
||||
public int CompareTo(GroupId other) => Value.CompareTo(other.Value);
|
||||
|
||||
public override string ToString() => $"Member #{Value}";
|
||||
}
|
||||
}
|
@ -29,6 +29,12 @@ namespace PluralKit.Core
|
||||
public static Task<PKMember?> QueryMemberByHid(this IPKConnection conn, string hid) =>
|
||||
conn.QueryFirstOrDefaultAsync<PKMember?>("select * from members where hid = @hid", new {hid = hid.ToLowerInvariant()});
|
||||
|
||||
public static Task<PKGroup?> QueryGroupByName(this IPKConnection conn, string name) =>
|
||||
conn.QueryFirstOrDefaultAsync<PKGroup?>("select * from groups where lower(name) = lower(@name)", new {name = name});
|
||||
|
||||
public static Task<PKGroup?> QueryGroupByHid(this IPKConnection conn, string hid) =>
|
||||
conn.QueryFirstOrDefaultAsync<PKGroup?>("select * from groups where hid = @hid", new {hid = hid.ToLowerInvariant()});
|
||||
|
||||
public static Task<GuildConfig> QueryOrInsertGuildConfig(this IPKConnection conn, ulong guild) =>
|
||||
conn.QueryFirstAsync<GuildConfig>("insert into servers (id) values (@guild) on conflict (id) do update set id = @guild returning *", new {guild});
|
||||
|
||||
|
17
PluralKit.Core/Models/PKGroup.cs
Normal file
17
PluralKit.Core/Models/PKGroup.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using NodaTime;
|
||||
|
||||
#nullable enable
|
||||
namespace PluralKit.Core
|
||||
{
|
||||
public class PKGroup
|
||||
{
|
||||
public GroupId Id { get; }
|
||||
public string Hid { get; } = null!;
|
||||
public SystemId System { get; }
|
||||
|
||||
public string Name { get; } = null!;
|
||||
public string? Description { get; }
|
||||
|
||||
public Instant Created { get; }
|
||||
}
|
||||
}
|
13
PluralKit.Core/Models/Patch/GroupPatch.cs
Normal file
13
PluralKit.Core/Models/Patch/GroupPatch.cs
Normal file
@ -0,0 +1,13 @@
|
||||
#nullable enable
|
||||
namespace PluralKit.Core
|
||||
{
|
||||
public class GroupPatch: PatchObject
|
||||
{
|
||||
public Partial<string> Name { get; set; }
|
||||
public Partial<string?> Description { get; set; }
|
||||
|
||||
public override UpdateQueryBuilder Apply(UpdateQueryBuilder b) => b
|
||||
.With("name", Name)
|
||||
.With("description", Description);
|
||||
}
|
||||
}
|
@ -60,5 +60,10 @@ namespace PluralKit.Core
|
||||
.Build();
|
||||
return conn.ExecuteAsync(query, pms);
|
||||
}
|
||||
|
||||
public static Task<PKGroup> CreateGroup(this IPKConnection conn, SystemId system, string name) =>
|
||||
conn.QueryFirstAsync<PKGroup>(
|
||||
"insert into groups (hid, system, name) values (find_free_group_hid(), @System, @Name) returning *",
|
||||
new {System = system, Name = name});
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ namespace PluralKit.Core {
|
||||
public static readonly int MaxMembersWarnThreshold = MaxMemberCount - 50;
|
||||
public static readonly int MaxDescriptionLength = 1000;
|
||||
public static readonly int MaxMemberNameLength = 100; // Fair bit larger than MaxProxyNameLength for bookkeeping
|
||||
public static readonly int MaxGroupNameLength = 100;
|
||||
public static readonly int MaxPronounsLength = 100;
|
||||
public static readonly int MaxUriLength = 256; // May need to be set higher, I know there are URLs longer than this in prod (they can rehost, I guess...)
|
||||
public static readonly long AvatarFileSizeLimit = 1024 * 1024;
|
||||
|
Loading…
Reference in New Issue
Block a user