コード例 #1
0
    public async Task <IActionResult> DoGroupPatch(string groupRef, [FromBody] JObject data)
    {
        var system = await ResolveSystem("@me");

        var group = await ResolveGroup(groupRef);

        if (group == null)
        {
            throw Errors.GroupNotFound;
        }
        if (group.System != system.Id)
        {
            throw Errors.NotOwnGroupError;
        }

        var patch = GroupPatch.FromJson(data);

        patch.AssertIsValid();
        if (patch.Errors.Count > 0)
        {
            throw new ModelParseError(patch.Errors);
        }

        var newGroup = await _repo.UpdateGroup(group.Id, patch);

        return(Ok(newGroup.ToJson(LookupContext.ByOwner)));
    }
コード例 #2
0
ファイル: GroupPrivacySubject.cs プロジェクト: xSke/PluralKit
 public static GroupPatch WithAllPrivacy(this GroupPatch member, PrivacyLevel level)
 {
     foreach (var subject in Enum.GetValues(typeof(GroupPrivacySubject)))
     {
         member.WithPrivacy((GroupPrivacySubject)subject, level);
     }
     return(member);
 }
コード例 #3
0
        public async Task GroupDescription(Context ctx, PKGroup target)
        {
            if (await ctx.MatchClear("this group's description"))
            {
                ctx.CheckOwnGroup(target);

                var patch = new GroupPatch {
                    Description = Partial <string> .Null()
                };
                await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch));

                await ctx.Reply($"{Emojis.Success} Group description cleared.");
            }
            else if (!ctx.HasNext())
            {
                if (target.Description == null)
                {
                    if (ctx.System?.Id == target.System)
                    {
                        await ctx.Reply($"This group does not have a description set. To set one, type `pk;group {target.Reference()} description <description>`.");
                    }
                    else
                    {
                        await ctx.Reply("This group does not have a description set.");
                    }
                }
                else if (ctx.MatchFlag("r", "raw"))
                {
                    await ctx.Reply($"```\n{target.Description}\n```");
                }
                else
                {
                    await ctx.Reply(embed : new DiscordEmbedBuilder()
                                    .WithTitle("Group description")
                                    .WithDescription(target.Description)
                                    .AddField("\u200B", $"To print the description with formatting, type `pk;group {target.Reference()} description -raw`."
                                              + (ctx.System?.Id == target.System ? $" To clear it, type `pk;group {target.Reference()} description -clear`." : ""))
                                    .Build());
                }
            }
            else
            {
                ctx.CheckOwnGroup(target);

                var description = ctx.RemainderOrNull().NormalizeLineEndSpacing();
                if (description.IsLongerThan(Limits.MaxDescriptionLength))
                {
                    throw Errors.DescriptionTooLongError(description.Length);
                }

                var patch = new GroupPatch {
                    Description = Partial <string> .Present(description)
                };
                await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch));

                await ctx.Reply($"{Emojis.Success} Group description changed.");
            }
        }
コード例 #4
0
    public async Task <PKGroup> UpdateGroup(GroupId id, GroupPatch patch, IPKConnection?conn = null)
    {
        _logger.Information("Updated {GroupId}: {@GroupPatch}", id, patch);
        var query = patch.Apply(new Query("groups").Where("id", id));
        var group = await _db.QueryFirst <PKGroup>(conn, query, "returning *");

        if (conn == null)
        {
            _ = _dispatch.Dispatch(id,
                                   new UpdateDispatchData {
                Event = DispatchEvent.UPDATE_GROUP, EventData = patch.ToJson()
            });
        }
        return(group);
    }
コード例 #5
0
ファイル: GroupPrivacySubject.cs プロジェクト: xSke/PluralKit
    public static GroupPatch WithPrivacy(this GroupPatch group, GroupPrivacySubject subject, PrivacyLevel level)
    {
        // what do you mean switch expressions can't be statements >.>
        _ = subject switch
        {
            GroupPrivacySubject.Name => group.NamePrivacy = level,
            GroupPrivacySubject.Description => group.DescriptionPrivacy = level,
            GroupPrivacySubject.Icon => group.IconPrivacy         = level,
            GroupPrivacySubject.List => group.ListPrivacy         = level,
            GroupPrivacySubject.Metadata => group.MetadataPrivacy = level,
            GroupPrivacySubject.Visibility => group.Visibility    = level,
            _ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}")
        };

        return(group);
    }
コード例 #6
0
    public async Task <IActionResult> GroupCreate([FromBody] JObject data)
    {
        var system = await ResolveSystem("@me");

        var config = await _repo.GetSystemConfig(system.Id);

        // Check group cap
        var existingGroupCount = await _repo.GetSystemGroupCount(system.Id);

        var groupLimit = config.GroupLimitOverride ?? Limits.MaxGroupCount;

        if (existingGroupCount >= groupLimit)
        {
            throw Errors.GroupLimitReached;
        }

        var patch = GroupPatch.FromJson(data);

        patch.AssertIsValid();
        if (!patch.Name.IsPresent)
        {
            patch.Errors.Add(new ValidationError("name", "Key 'name' is required when creating new group."));
        }
        if (patch.Errors.Count > 0)
        {
            throw new ModelParseError(patch.Errors);
        }

        using var conn = await _db.Obtain();

        using var tx = await conn.BeginTransactionAsync();

        var newGroup = await _repo.CreateGroup(system.Id, patch.Name.Value, conn);

        newGroup = await _repo.UpdateGroup(newGroup.Id, patch, conn);

        _ = _dispatch.Dispatch(newGroup.Id, new UpdateDispatchData()
        {
            Event     = DispatchEvent.CREATE_GROUP,
            EventData = patch.ToJson(),
        });

        await tx.CommitAsync();

        return(Ok(newGroup.ToJson(LookupContext.ByOwner)));
    }
コード例 #7
0
        public async Task GroupDisplayName(Context ctx, PKGroup target)
        {
            if (await ctx.MatchClear("this group's display name"))
            {
                ctx.CheckOwnGroup(target);

                var patch = new GroupPatch {
                    DisplayName = Partial <string> .Null()
                };
                await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch));

                await ctx.Reply($"{Emojis.Success} Group display name cleared.");
            }
            else if (!ctx.HasNext())
            {
                // No perms check, display name isn't covered by member privacy
                var eb = new DiscordEmbedBuilder()
                         .AddField("Name", target.Name)
                         .AddField("Display Name", target.DisplayName ?? "*(none)*");

                if (ctx.System?.Id == target.System)
                {
                    eb.WithDescription($"To change display name, type `pk;group {target.Reference()} displayname <display name>`.\nTo clear it, type `pk;group {target.Reference()} displayname -clear`.");
                }

                await ctx.Reply(embed : eb.Build());
            }
            else
            {
                ctx.CheckOwnGroup(target);

                var newDisplayName = ctx.RemainderOrNull();

                var patch = new GroupPatch {
                    DisplayName = Partial <string> .Present(newDisplayName)
                };
                await _db.Execute(conn => _repo.UpdateGroup(conn, target.Id, patch));

                await ctx.Reply($"{Emojis.Success} Group display name changed.");
            }
        }
コード例 #8
0
    public async Task <IActionResult> BulkGroupPrivacy([FromBody] JObject inner)
    {
        HttpContext.Items.TryGetValue("SystemId", out var systemId);
        if (systemId == null)
        {
            throw Errors.GenericAuthError;
        }

        var data = new JObject();

        data.Add("privacy", inner);

        var patch = GroupPatch.FromJson(data);

        patch.AssertIsValid();
        if (patch.Errors.Count > 0)
        {
            throw new ModelParseError(patch.Errors);
        }

        await _db.ExecuteQuery(patch.Apply(new Query("groups").Where("system", systemId)));

        return(NoContent());
    }
コード例 #9
0
    private async Task ImportGroup(JObject group)
    {
        var id   = group.Value <string>("id");
        var name = group.Value <string>("name");

        var(found, isHidExisting) = TryGetExistingGroup(id, name);
        var isNewGroup    = found == null;
        var referenceName = isHidExisting ? id : name;

        _logger.Debug(
            "Importing group with identifier {FileId} to system {System} (is creating new group? {IsCreatingNewGroup})",
            referenceName, _system.Id, isNewGroup
            );

        var patch = GroupPatch.FromJson(group);

        patch.AssertIsValid();
        if (patch.Errors.Count > 0)
        {
            var err = patch.Errors[0];
            if (err is FieldTooLongError)
            {
                throw new ImportException($"Field {err.Key} in group {name} is too long "
                                          + $"({(err as FieldTooLongError).ActualLength} > {(err as FieldTooLongError).MaxLength}).");
            }
            if (err.Text != null)
            {
                throw new ImportException($"group {name}: {err.Text}");
            }
            throw new ImportException($"Field {err.Key} in group {name} is invalid.");
        }

        var groupId = found;

        if (isNewGroup)
        {
            var newGroup = await _repo.CreateGroup(_system.Id, patch.Name.Value, _conn);

            groupId = newGroup.Id;
        }

        _knownGroupIdentifiers[id] = groupId.Value;

        await _repo.UpdateGroup(groupId.Value, patch, _conn);

        var groupMembers        = group.Value <JArray>("members");
        var currentGroupMembers = (await _conn.QueryAsync <MemberId>(
                                       "select member_id from group_members where group_id = @groupId",
                                       new { groupId = groupId.Value }
                                       )).ToList();

        await using (var importer =
                         _conn.BeginBinaryImport("copy group_members (group_id, member_id) from stdin (format binary)"))
        {
            foreach (var memberIdentifier in groupMembers)
            {
                if (!_knownMemberIdentifiers.TryGetValue(memberIdentifier.ToString(), out var memberId))
                {
                    throw new Exception(
                              $"Attempted to import group member with member identifier {memberIdentifier} but could not find a recently imported member with this id!");
                }

                if (currentGroupMembers.Contains(memberId))
                {
                    continue;
                }

                await importer.StartRowAsync();

                await importer.WriteAsync(groupId.Value.Value, NpgsqlDbType.Integer);

                await importer.WriteAsync(memberId.Value, NpgsqlDbType.Integer);
            }

            await importer.CompleteAsync();
        }
    }
コード例 #10
0
    public async Task CreateGroup(Context ctx)
    {
        ctx.CheckSystem();

        // Check group name length
        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.MaxGroupNameLength} characters).");
        }

        // Check group cap
        var existingGroupCount = await ctx.Repository.GetSystemGroupCount(ctx.System.Id);

        var groupLimit = ctx.Config.GroupLimitOverride ?? Limits.MaxGroupCount;

        if (existingGroupCount >= groupLimit)
        {
            throw new PKError(
                      $"System has reached the maximum number of groups ({groupLimit}). Please delete unused groups first in order to create new ones.");
        }

        // Warn if there's already a group by this name
        var existingGroup = await ctx.Repository.GetGroupByName(ctx.System.Id, groupName);

        if (existingGroup != null)
        {
            var msg =
                $"{Emojis.Warn} You already have a group in your system with the name \"{existingGroup.Name}\" (with ID `{existingGroup.Hid}`). Do you want to create another group with the same name?";
            if (!await ctx.PromptYesNo(msg, "Create"))
            {
                throw new PKError("Group creation cancelled.");
            }
        }

        // todo: this is supposed to be a transaction, but it's not used in any useful way
        // consider removing it?
        using var conn = await ctx.Database.Obtain();

        var newGroup = await ctx.Repository.CreateGroup(ctx.System.Id, groupName);

        var dispatchData = new JObject();

        dispatchData.Add("name", groupName);

        if (ctx.Config.GroupDefaultPrivate)
        {
            var patch = new GroupPatch().WithAllPrivacy(PrivacyLevel.Private);
            await ctx.Repository.UpdateGroup(newGroup.Id, patch, conn);

            dispatchData.Merge(patch.ToJson());
        }

        _ = _dispatch.Dispatch(newGroup.Id, new UpdateDispatchData
        {
            Event     = DispatchEvent.CREATE_GROUP,
            EventData = dispatchData
        });

        var reference = newGroup.Reference(ctx);

        var eb = new EmbedBuilder()
                 .Description(
            $"Your new group, **{groupName}**, has been created, with the group ID **`{newGroup.Hid}`**.\nBelow are a couple of useful commands:")
                 .Field(new Embed.Field("View the group card", $"> pk;group **{reference}**"))
                 .Field(new Embed.Field("Add members to the group",
                                        $"> pk;group **{reference}** add **MemberName**\n> pk;group **{reference}** add **Member1** **Member2** **Member3** (and so on...)"))
                 .Field(new Embed.Field("Set the description",
                                        $"> pk;group **{reference}** description **This is my new group, and here is the description!**"))
                 .Field(new Embed.Field("Set the group icon",
                                        $"> pk;group **{reference}** icon\n*(with an image attached)*"));
        await ctx.Reply($"{Emojis.Success} Group created!", eb.Build());

        if (existingGroupCount >= Limits.WarnThreshold(groupLimit))
        {
            await ctx.Reply(
                $"{Emojis.Warn} You are approaching the per-system group limit ({existingGroupCount} / {groupLimit} groups). Please review your group list for unused or duplicate groups.");
        }
    }
コード例 #11
0
    public async Task GroupDescription(Context ctx, PKGroup target)
    {
        ctx.CheckSystemPrivacy(target.System, target.DescriptionPrivacy);

        var noDescriptionSetMessage = "This group does not have a description set.";

        if (ctx.System?.Id == target.System)
        {
            noDescriptionSetMessage +=
                $" To set one, type `pk;group {target.Reference(ctx)} description <description>`.";
        }

        if (ctx.MatchRaw())
        {
            if (target.Description == null)
            {
                await ctx.Reply(noDescriptionSetMessage);
            }
            else
            {
                await ctx.Reply($"```\n{target.Description}\n```");
            }
            return;
        }

        if (!ctx.HasNext(false))
        {
            if (target.Description == null)
            {
                await ctx.Reply(noDescriptionSetMessage);
            }
            else
            {
                await ctx.Reply(embed : new EmbedBuilder()
                                .Title("Group description")
                                .Description(target.Description)
                                .Field(new Embed.Field("\u200B",
                                                       $"To print the description with formatting, type `pk;group {target.Reference(ctx)} description -raw`."
                                                       + (ctx.System?.Id == target.System
                            ? $" To clear it, type `pk;group {target.Reference(ctx)} description -clear`."
                            : "")))
                                .Build());
            }
            return;
        }

        ctx.CheckOwnGroup(target);

        if (await ctx.MatchClear("this group's description"))
        {
            var patch = new GroupPatch {
                Description = Partial <string> .Null()
            };
            await ctx.Repository.UpdateGroup(target.Id, patch);

            await ctx.Reply($"{Emojis.Success} Group description cleared.");
        }
        else
        {
            var description = ctx.RemainderOrNull(false).NormalizeLineEndSpacing();
            if (description.IsLongerThan(Limits.MaxDescriptionLength))
            {
                throw Errors.StringTooLongError("Description", description.Length, Limits.MaxDescriptionLength);
            }

            var patch = new GroupPatch {
                Description = Partial <string> .Present(description)
            };
            await ctx.Repository.UpdateGroup(target.Id, patch);

            await ctx.Reply($"{Emojis.Success} Group description changed.");
        }
    }
コード例 #12
0
    public async Task GroupDisplayName(Context ctx, PKGroup target)
    {
        var noDisplayNameSetMessage = "This group does not have a display name set.";

        if (ctx.System?.Id == target.System)
        {
            noDisplayNameSetMessage +=
                $" To set one, type `pk;group {target.Reference(ctx)} displayname <display name>`.";
        }

        // No perms check, display name isn't covered by member privacy

        if (ctx.MatchRaw())
        {
            if (target.DisplayName == null)
            {
                await ctx.Reply(noDisplayNameSetMessage);
            }
            else
            {
                await ctx.Reply($"```\n{target.DisplayName}\n```");
            }
            return;
        }

        if (!ctx.HasNext(false))
        {
            if (target.DisplayName == null)
            {
                await ctx.Reply(noDisplayNameSetMessage);
            }
            else
            {
                var eb = new EmbedBuilder()
                         .Field(new Embed.Field("Name", target.Name))
                         .Field(new Embed.Field("Display Name", target.DisplayName));

                var reference = target.Reference(ctx);

                if (ctx.System?.Id == target.System)
                {
                    eb.Description(
                        $"To change display name, type `pk;group {reference} displayname <display name>`."
                        + $"To clear it, type `pk;group {reference} displayname -clear`."
                        + $"To print the raw display name, type `pk;group {reference} displayname -raw`.");
                }

                await ctx.Reply(embed : eb.Build());
            }

            return;
        }

        ctx.CheckOwnGroup(target);

        if (await ctx.MatchClear("this group's display name"))
        {
            var patch = new GroupPatch {
                DisplayName = Partial <string> .Null()
            };
            await ctx.Repository.UpdateGroup(target.Id, patch);

            await ctx.Reply($"{Emojis.Success} Group display name cleared.");

            if (target.NamePrivacy == PrivacyLevel.Private)
            {
                await ctx.Reply($"{Emojis.Warn} Since this group no longer has a display name set, their name privacy **can no longer take effect**.");
            }
        }
        else
        {
            var newDisplayName = ctx.RemainderOrNull(false).NormalizeLineEndSpacing();

            var patch = new GroupPatch {
                DisplayName = Partial <string> .Present(newDisplayName)
            };
            await ctx.Repository.UpdateGroup(target.Id, patch);

            await ctx.Reply($"{Emojis.Success} Group display name changed.");
        }
    }