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 MessageContext _messageContext;
|
||||||
|
|
||||||
private readonly IDataStore _data;
|
private readonly IDataStore _data;
|
||||||
|
private readonly IDatabase _db;
|
||||||
private readonly PKSystem _senderSystem;
|
private readonly PKSystem _senderSystem;
|
||||||
private readonly IMetrics _metrics;
|
private readonly IMetrics _metrics;
|
||||||
|
|
||||||
@ -40,6 +41,7 @@ namespace PluralKit.Bot
|
|||||||
_data = provider.Resolve<IDataStore>();
|
_data = provider.Resolve<IDataStore>();
|
||||||
_senderSystem = senderSystem;
|
_senderSystem = senderSystem;
|
||||||
_messageContext = messageContext;
|
_messageContext = messageContext;
|
||||||
|
_db = provider.Resolve<IDatabase>();
|
||||||
_metrics = provider.Resolve<IMetrics>();
|
_metrics = provider.Resolve<IMetrics>();
|
||||||
_provider = provider;
|
_provider = provider;
|
||||||
_parameters = new Parameters(message.Content.Substring(commandParseOffset));
|
_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/?
|
// TODO: this is just here so the extension methods can access it; should it be public/private/?
|
||||||
internal IDataStore DataStore => _data;
|
internal IDataStore DataStore => _data;
|
||||||
|
internal IDatabase Database => _db;
|
||||||
|
|
||||||
public Task<DiscordMessage> Reply(string text = null, DiscordEmbed embed = null, IEnumerable<IMention> mentions = null)
|
public Task<DiscordMessage> Reply(string text = null, DiscordEmbed embed = null, IEnumerable<IMention> mentions = null)
|
||||||
{
|
{
|
||||||
|
@ -98,6 +98,26 @@ namespace PluralKit.Bot
|
|||||||
return member;
|
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)
|
public static string CreateMemberNotFoundError(this Context ctx, string input)
|
||||||
{
|
{
|
||||||
// TODO: does this belong here?
|
// TODO: does this belong here?
|
||||||
|
@ -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 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 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 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 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 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");
|
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);
|
return HandleSystemCommand(ctx);
|
||||||
if (ctx.Match("member", "m"))
|
if (ctx.Match("member", "m"))
|
||||||
return HandleMemberCommand(ctx);
|
return HandleMemberCommand(ctx);
|
||||||
|
if (ctx.Match("group", "g"))
|
||||||
|
return HandleGroupCommand(ctx);
|
||||||
if (ctx.Match("switch", "sw"))
|
if (ctx.Match("switch", "sw"))
|
||||||
return HandleSwitchCommand(ctx);
|
return HandleSwitchCommand(ctx);
|
||||||
if (ctx.Match("ap", "autoproxy", "auto"))
|
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);
|
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)
|
private async Task HandleSwitchCommand(Context ctx)
|
||||||
{
|
{
|
||||||
if (ctx.Match("out"))
|
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<CommandTree>().AsSelf();
|
||||||
builder.RegisterType<Autoproxy>().AsSelf();
|
builder.RegisterType<Autoproxy>().AsSelf();
|
||||||
builder.RegisterType<Fun>().AsSelf();
|
builder.RegisterType<Fun>().AsSelf();
|
||||||
|
builder.RegisterType<Groups>().AsSelf();
|
||||||
builder.RegisterType<Help>().AsSelf();
|
builder.RegisterType<Help>().AsSelf();
|
||||||
builder.RegisterType<ImportExport>().AsSelf();
|
builder.RegisterType<ImportExport>().AsSelf();
|
||||||
builder.RegisterType<Member>().AsSelf();
|
builder.RegisterType<Member>().AsSelf();
|
||||||
|
@ -20,7 +20,7 @@ namespace PluralKit.Core
|
|||||||
internal class Database: IDatabase
|
internal class Database: IDatabase
|
||||||
{
|
{
|
||||||
private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files
|
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 CoreConfig _config;
|
||||||
private readonly ILogger _logger;
|
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<SystemId, int>(i => new SystemId(i)));
|
||||||
SqlMapper.AddTypeHandler(new NumericIdHandler<MemberId, int>(i => new MemberId(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<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<SystemId, int>(i => new SystemId(i)));
|
||||||
SqlMapper.AddTypeHandler(new NumericIdArrayHandler<MemberId, int>(i => new MemberId(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<SwitchId, int>(i => new SwitchId(i)));
|
||||||
|
SqlMapper.AddTypeHandler(new NumericIdArrayHandler<GroupId, int>(i => new GroupId(i)));
|
||||||
|
|
||||||
// Register our custom types to Npgsql
|
// Register our custom types to Npgsql
|
||||||
// Without these it'll still *work* but break at the first launch + probably cause other small issues
|
// Without these it'll still *work* but break at the first launch + probably cause other small issues
|
||||||
|
@ -112,3 +112,13 @@ begin
|
|||||||
end loop;
|
end loop;
|
||||||
end
|
end
|
||||||
$$ language plpgsql volatile;
|
$$ 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;
|
@ -11,3 +11,4 @@ drop function if exists proxy_members;
|
|||||||
drop function if exists generate_hid;
|
drop function if exists generate_hid;
|
||||||
drop function if exists find_free_system_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) =>
|
public static Task<PKMember?> QueryMemberByHid(this IPKConnection conn, string hid) =>
|
||||||
conn.QueryFirstOrDefaultAsync<PKMember?>("select * from members where hid = @hid", new {hid = hid.ToLowerInvariant()});
|
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) =>
|
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});
|
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();
|
.Build();
|
||||||
return conn.ExecuteAsync(query, pms);
|
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 MaxMembersWarnThreshold = MaxMemberCount - 50;
|
||||||
public static readonly int MaxDescriptionLength = 1000;
|
public static readonly int MaxDescriptionLength = 1000;
|
||||||
public static readonly int MaxMemberNameLength = 100; // Fair bit larger than MaxProxyNameLength for bookkeeping
|
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 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 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;
|
public static readonly long AvatarFileSizeLimit = 1024 * 1024;
|
||||||
|
Loading…
Reference in New Issue
Block a user