[feature] Allow users to submit `interaction_policy` on new statuses (#3314)
* [feature] Parse `interaction_policy` on status submission * beep boop * swagger? i barely know er
This commit is contained in:
parent
f819229988
commit
c378ad2bb3
|
@ -8826,11 +8826,27 @@ paths:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
- application/xml
|
|
||||||
- application/x-www-form-urlencoded
|
- application/x-www-form-urlencoded
|
||||||
description: |-
|
description: |-
|
||||||
The parameters can also be given in the body of the request, as JSON, if the content-type is set to 'application/json'.
|
The parameters can also be given in the body of the request, as JSON, if the content-type is set to 'application/json'.
|
||||||
The parameters can also be given in the body of the request, as XML, if the content-type is set to 'application/xml'.
|
|
||||||
|
The 'interaction_policy' field can be used to set an interaction policy for this status.
|
||||||
|
|
||||||
|
If submitting using form data, use the following pattern to set an interaction policy:
|
||||||
|
|
||||||
|
`interaction_policy[INTERACTION_TYPE][CONDITION][INDEX]=Value`
|
||||||
|
|
||||||
|
For example: `interaction_policy[can_reply][always][0]=author`
|
||||||
|
|
||||||
|
Using `curl` this might look something like:
|
||||||
|
|
||||||
|
`curl -F 'interaction_policy[can_reply][always][0]=author' -F 'interaction_policy[can_reply][always][1]=followers' [... other form fields ...]`
|
||||||
|
|
||||||
|
The JSON equivalent would be:
|
||||||
|
|
||||||
|
`curl -H 'Content-Type: application/json' -d '{"interaction_policy":{"can_reply":{"always":["author","followers"]}} [... other json fields ...]}'`
|
||||||
|
|
||||||
|
The server will perform some normalization on the submitted policy so that you can't submit something totally invalid.
|
||||||
operationId: statusCreate
|
operationId: statusCreate
|
||||||
parameters:
|
parameters:
|
||||||
- description: |-
|
- description: |-
|
||||||
|
@ -8944,6 +8960,30 @@ paths:
|
||||||
name: content_type
|
name: content_type
|
||||||
type: string
|
type: string
|
||||||
x-go-name: ContentType
|
x-go-name: ContentType
|
||||||
|
- description: Nth entry for interaction_policy.can_favourite.always.
|
||||||
|
in: formData
|
||||||
|
name: interaction_policy[can_favourite][always][0]
|
||||||
|
type: string
|
||||||
|
- description: Nth entry for interaction_policy.can_favourite.with_approval.
|
||||||
|
in: formData
|
||||||
|
name: interaction_policy[can_favourite][with_approval][0]
|
||||||
|
type: string
|
||||||
|
- description: Nth entry for interaction_policy.can_reply.always.
|
||||||
|
in: formData
|
||||||
|
name: interaction_policy[can_reply][always][0]
|
||||||
|
type: string
|
||||||
|
- description: Nth entry for interaction_policy.can_reply.with_approval.
|
||||||
|
in: formData
|
||||||
|
name: interaction_policy[can_reply][with_approval][0]
|
||||||
|
type: string
|
||||||
|
- description: Nth entry for interaction_policy.can_reblog.always.
|
||||||
|
in: formData
|
||||||
|
name: interaction_policy[can_reblog][always][0]
|
||||||
|
type: string
|
||||||
|
- description: Nth entry for interaction_policy.can_reblog.with_approval.
|
||||||
|
in: formData
|
||||||
|
name: interaction_policy[can_reblog][with_approval][0]
|
||||||
|
type: string
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
|
@ -8966,7 +9006,7 @@ paths:
|
||||||
security:
|
security:
|
||||||
- OAuth2 Bearer:
|
- OAuth2 Bearer:
|
||||||
- write:statuses
|
- write:statuses
|
||||||
summary: Create a new status.
|
summary: Create a new status using the given form field parameters.
|
||||||
tags:
|
tags:
|
||||||
- statuses
|
- statuses
|
||||||
/api/v1/statuses/{id}:
|
/api/v1/statuses/{id}:
|
||||||
|
|
|
@ -222,7 +222,7 @@ func (m *Module) PoliciesDefaultsPATCHHandler(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
form, err := parseUpdateAccountForm(c)
|
form, err := parseUpdatePoliciesForm(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||||
return
|
return
|
||||||
|
@ -290,7 +290,7 @@ func customBind(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateInteractionPoliciesRequest, error) {
|
func parseUpdatePoliciesForm(c *gin.Context) (*apimodel.UpdateInteractionPoliciesRequest, error) {
|
||||||
form := new(apimodel.UpdateInteractionPoliciesRequest)
|
form := new(apimodel.UpdateInteractionPoliciesRequest)
|
||||||
|
|
||||||
switch ct := c.ContentType(); ct {
|
switch ct := c.ContentType(); ct {
|
||||||
|
|
|
@ -24,6 +24,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"github.com/go-playground/form/v4"
|
||||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
@ -35,10 +37,27 @@ import (
|
||||||
|
|
||||||
// StatusCreatePOSTHandler swagger:operation POST /api/v1/statuses statusCreate
|
// StatusCreatePOSTHandler swagger:operation POST /api/v1/statuses statusCreate
|
||||||
//
|
//
|
||||||
// Create a new status.
|
// Create a new status using the given form field parameters.
|
||||||
//
|
//
|
||||||
// The parameters can also be given in the body of the request, as JSON, if the content-type is set to 'application/json'.
|
// The parameters can also be given in the body of the request, as JSON, if the content-type is set to 'application/json'.
|
||||||
// The parameters can also be given in the body of the request, as XML, if the content-type is set to 'application/xml'.
|
//
|
||||||
|
// The 'interaction_policy' field can be used to set an interaction policy for this status.
|
||||||
|
//
|
||||||
|
// If submitting using form data, use the following pattern to set an interaction policy:
|
||||||
|
//
|
||||||
|
// `interaction_policy[INTERACTION_TYPE][CONDITION][INDEX]=Value`
|
||||||
|
//
|
||||||
|
// For example: `interaction_policy[can_reply][always][0]=author`
|
||||||
|
//
|
||||||
|
// Using `curl` this might look something like:
|
||||||
|
//
|
||||||
|
// `curl -F 'interaction_policy[can_reply][always][0]=author' -F 'interaction_policy[can_reply][always][1]=followers' [... other form fields ...]`
|
||||||
|
//
|
||||||
|
// The JSON equivalent would be:
|
||||||
|
//
|
||||||
|
// `curl -H 'Content-Type: application/json' -d '{"interaction_policy":{"can_reply":{"always":["author","followers"]}} [... other json fields ...]}'`
|
||||||
|
//
|
||||||
|
// The server will perform some normalization on the submitted policy so that you can't submit something totally invalid.
|
||||||
//
|
//
|
||||||
// ---
|
// ---
|
||||||
// tags:
|
// tags:
|
||||||
|
@ -46,7 +65,6 @@ import (
|
||||||
//
|
//
|
||||||
// consumes:
|
// consumes:
|
||||||
// - application/json
|
// - application/json
|
||||||
// - application/xml
|
|
||||||
// - application/x-www-form-urlencoded
|
// - application/x-www-form-urlencoded
|
||||||
//
|
//
|
||||||
// parameters:
|
// parameters:
|
||||||
|
@ -181,6 +199,36 @@ import (
|
||||||
// - text/plain
|
// - text/plain
|
||||||
// - text/markdown
|
// - text/markdown
|
||||||
// in: formData
|
// in: formData
|
||||||
|
// -
|
||||||
|
// name: interaction_policy[can_favourite][always][0]
|
||||||
|
// in: formData
|
||||||
|
// description: Nth entry for interaction_policy.can_favourite.always.
|
||||||
|
// type: string
|
||||||
|
// -
|
||||||
|
// name: interaction_policy[can_favourite][with_approval][0]
|
||||||
|
// in: formData
|
||||||
|
// description: Nth entry for interaction_policy.can_favourite.with_approval.
|
||||||
|
// type: string
|
||||||
|
// -
|
||||||
|
// name: interaction_policy[can_reply][always][0]
|
||||||
|
// in: formData
|
||||||
|
// description: Nth entry for interaction_policy.can_reply.always.
|
||||||
|
// type: string
|
||||||
|
// -
|
||||||
|
// name: interaction_policy[can_reply][with_approval][0]
|
||||||
|
// in: formData
|
||||||
|
// description: Nth entry for interaction_policy.can_reply.with_approval.
|
||||||
|
// type: string
|
||||||
|
// -
|
||||||
|
// name: interaction_policy[can_reblog][always][0]
|
||||||
|
// in: formData
|
||||||
|
// description: Nth entry for interaction_policy.can_reblog.always.
|
||||||
|
// type: string
|
||||||
|
// -
|
||||||
|
// name: interaction_policy[can_reblog][with_approval][0]
|
||||||
|
// in: formData
|
||||||
|
// description: Nth entry for interaction_policy.can_reblog.with_approval.
|
||||||
|
// type: string
|
||||||
//
|
//
|
||||||
// produces:
|
// produces:
|
||||||
// - application/json
|
// - application/json
|
||||||
|
@ -223,8 +271,8 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
form := &apimodel.StatusCreateRequest{}
|
form, err := parseStatusCreateForm(c)
|
||||||
if err := c.ShouldBind(form); err != nil {
|
if err != nil {
|
||||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -257,6 +305,75 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, apiStatus)
|
c.JSON(http.StatusOK, apiStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// intPolicyFormBinding satisfies gin's binding.Binding interface.
|
||||||
|
// Should only be used specifically for multipart/form-data MIME type.
|
||||||
|
type intPolicyFormBinding struct{}
|
||||||
|
|
||||||
|
func (i intPolicyFormBinding) Name() string {
|
||||||
|
return "InteractionPolicy"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (intPolicyFormBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change default namespace prefix and suffix to
|
||||||
|
// allow correct parsing of the field attributes.
|
||||||
|
decoder := form.NewDecoder()
|
||||||
|
decoder.SetNamespacePrefix("[")
|
||||||
|
decoder.SetNamespaceSuffix("]")
|
||||||
|
|
||||||
|
return decoder.Decode(obj, req.Form)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStatusCreateForm(c *gin.Context) (*apimodel.StatusCreateRequest, error) {
|
||||||
|
form := new(apimodel.StatusCreateRequest)
|
||||||
|
|
||||||
|
switch ct := c.ContentType(); ct {
|
||||||
|
case binding.MIMEJSON:
|
||||||
|
// Just bind with default json binding.
|
||||||
|
if err := c.ShouldBindWith(form, binding.JSON); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case binding.MIMEPOSTForm:
|
||||||
|
// Bind with default form binding first.
|
||||||
|
if err := c.ShouldBindWith(form, binding.FormPost); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now do custom binding.
|
||||||
|
intReqForm := new(apimodel.StatusInteractionPolicyForm)
|
||||||
|
if err := c.ShouldBindWith(intReqForm, intPolicyFormBinding{}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
form.InteractionPolicy = intReqForm.InteractionPolicy
|
||||||
|
|
||||||
|
case binding.MIMEMultipartPOSTForm:
|
||||||
|
// Bind with default form binding first.
|
||||||
|
if err := c.ShouldBindWith(form, binding.FormMultipart); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now do custom binding.
|
||||||
|
intReqForm := new(apimodel.StatusInteractionPolicyForm)
|
||||||
|
if err := c.ShouldBindWith(intReqForm, intPolicyFormBinding{}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
form.InteractionPolicy = intReqForm.InteractionPolicy
|
||||||
|
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"content-type %s not supported for this endpoint; supported content-types are %s, %s, %s",
|
||||||
|
ct, binding.MIMEJSON, binding.MIMEPOSTForm, binding.MIMEMultipartPOSTForm,
|
||||||
|
)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return form, nil
|
||||||
|
}
|
||||||
|
|
||||||
// validateNormalizeCreateStatus checks the form
|
// validateNormalizeCreateStatus checks the form
|
||||||
// for disallowed combinations of attachments and
|
// for disallowed combinations of attachments and
|
||||||
// overlength inputs.
|
// overlength inputs.
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -196,33 +196,44 @@ type StatusCreateRequest struct {
|
||||||
// Text content of the status.
|
// Text content of the status.
|
||||||
// If media_ids is provided, this becomes optional.
|
// If media_ids is provided, this becomes optional.
|
||||||
// Attaching a poll is optional while status is provided.
|
// Attaching a poll is optional while status is provided.
|
||||||
Status string `form:"status" json:"status" xml:"status"`
|
Status string `form:"status" json:"status"`
|
||||||
// Array of Attachment ids to be attached as media.
|
// Array of Attachment ids to be attached as media.
|
||||||
// If provided, status becomes optional, and poll cannot be used.
|
// If provided, status becomes optional, and poll cannot be used.
|
||||||
MediaIDs []string `form:"media_ids[]" json:"media_ids" xml:"media_ids"`
|
MediaIDs []string `form:"media_ids[]" json:"media_ids"`
|
||||||
// Poll to include with this status.
|
// Poll to include with this status.
|
||||||
Poll *PollRequest `form:"poll" json:"poll" xml:"poll"`
|
Poll *PollRequest `form:"poll" json:"poll"`
|
||||||
// ID of the status being replied to, if status is a reply.
|
// ID of the status being replied to, if status is a reply.
|
||||||
InReplyToID string `form:"in_reply_to_id" json:"in_reply_to_id" xml:"in_reply_to_id"`
|
InReplyToID string `form:"in_reply_to_id" json:"in_reply_to_id"`
|
||||||
// Status and attached media should be marked as sensitive.
|
// Status and attached media should be marked as sensitive.
|
||||||
Sensitive bool `form:"sensitive" json:"sensitive" xml:"sensitive"`
|
Sensitive bool `form:"sensitive" json:"sensitive"`
|
||||||
// Text to be shown as a warning or subject before the actual content.
|
// Text to be shown as a warning or subject before the actual content.
|
||||||
// Statuses are generally collapsed behind this field.
|
// Statuses are generally collapsed behind this field.
|
||||||
SpoilerText string `form:"spoiler_text" json:"spoiler_text" xml:"spoiler_text"`
|
SpoilerText string `form:"spoiler_text" json:"spoiler_text"`
|
||||||
// Visibility of the posted status.
|
// Visibility of the posted status.
|
||||||
Visibility Visibility `form:"visibility" json:"visibility" xml:"visibility"`
|
Visibility Visibility `form:"visibility" json:"visibility"`
|
||||||
// Set to "true" if this status should not be federated, ie. it should be a "local only" status.
|
// Set to "true" if this status should not be federated, ie. it should be a "local only" status.
|
||||||
LocalOnly *bool `form:"local_only"`
|
LocalOnly *bool `form:"local_only" json:"local_only"`
|
||||||
// Deprecated: Only used if LocalOnly is not set.
|
// Deprecated: Only used if LocalOnly is not set.
|
||||||
Federated *bool `form:"federated"`
|
Federated *bool `form:"federated" json:"federated"`
|
||||||
// ISO 8601 Datetime at which to schedule a status.
|
// ISO 8601 Datetime at which to schedule a status.
|
||||||
// Providing this parameter will cause ScheduledStatus to be returned instead of Status.
|
// Providing this parameter will cause ScheduledStatus to be returned instead of Status.
|
||||||
// Must be at least 5 minutes in the future.
|
// Must be at least 5 minutes in the future.
|
||||||
ScheduledAt string `form:"scheduled_at" json:"scheduled_at" xml:"scheduled_at"`
|
ScheduledAt string `form:"scheduled_at" json:"scheduled_at"`
|
||||||
// ISO 639 language code for this status.
|
// ISO 639 language code for this status.
|
||||||
Language string `form:"language" json:"language" xml:"language"`
|
Language string `form:"language" json:"language"`
|
||||||
// Content type to use when parsing this status.
|
// Content type to use when parsing this status.
|
||||||
ContentType StatusContentType `form:"content_type" json:"content_type" xml:"content_type"`
|
ContentType StatusContentType `form:"content_type" json:"content_type"`
|
||||||
|
// Interaction policy to use for this status.
|
||||||
|
InteractionPolicy *InteractionPolicy `form:"-" json:"interaction_policy"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate form for parsing interaction
|
||||||
|
// policy on status create requests.
|
||||||
|
//
|
||||||
|
// swagger:ignore
|
||||||
|
type StatusInteractionPolicyForm struct {
|
||||||
|
// Interaction policy to use for this status.
|
||||||
|
InteractionPolicy *InteractionPolicy `form:"interaction_policy" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visibility models the visibility of a status.
|
// Visibility models the visibility of a status.
|
||||||
|
|
|
@ -117,14 +117,14 @@ func (p *Processor) Create(
|
||||||
return nil, errWithCode
|
return nil, errWithCode
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := processVisibility(form, requester.Settings.Privacy, status); err != nil {
|
if err := p.processVisibility(ctx, form, requester.Settings.Privacy, status); err != nil {
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, gtserror.NewErrorInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process policy AFTER visibility as it
|
// Process policy AFTER visibility as it relies
|
||||||
// relies on status.Visibility being set.
|
// on status.Visibility and form.Visibility being set.
|
||||||
if err := processInteractionPolicy(form, requester.Settings, status); err != nil {
|
if errWithCode := processInteractionPolicy(form, requester.Settings, status); errWithCode != nil {
|
||||||
return nil, gtserror.NewErrorInternalError(err)
|
return nil, errWithCode
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := processLanguage(form, requester.Settings.Language, status); err != nil {
|
if err := processLanguage(form, requester.Settings.Language, status); err != nil {
|
||||||
|
@ -337,7 +337,8 @@ func (p *Processor) processMediaIDs(ctx context.Context, form *apimodel.StatusCr
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func processVisibility(
|
func (p *Processor) processVisibility(
|
||||||
|
ctx context.Context,
|
||||||
form *apimodel.StatusCreateRequest,
|
form *apimodel.StatusCreateRequest,
|
||||||
accountDefaultVis gtsmodel.Visibility,
|
accountDefaultVis gtsmodel.Visibility,
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
|
@ -347,13 +348,17 @@ func processVisibility(
|
||||||
case form.Visibility != "":
|
case form.Visibility != "":
|
||||||
status.Visibility = typeutils.APIVisToVis(form.Visibility)
|
status.Visibility = typeutils.APIVisToVis(form.Visibility)
|
||||||
|
|
||||||
// Fall back to account default.
|
// Fall back to account default, set
|
||||||
|
// this back on the form for later use.
|
||||||
case accountDefaultVis != "":
|
case accountDefaultVis != "":
|
||||||
status.Visibility = accountDefaultVis
|
status.Visibility = accountDefaultVis
|
||||||
|
form.Visibility = p.converter.VisToAPIVis(ctx, accountDefaultVis)
|
||||||
|
|
||||||
// What? Fall back to global default.
|
// What? Fall back to global default, set
|
||||||
|
// this back on the form for later use.
|
||||||
default:
|
default:
|
||||||
status.Visibility = gtsmodel.VisibilityDefault
|
status.Visibility = gtsmodel.VisibilityDefault
|
||||||
|
form.Visibility = p.converter.VisToAPIVis(ctx, gtsmodel.VisibilityDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set federated according to "local_only" field,
|
// Set federated according to "local_only" field,
|
||||||
|
@ -365,17 +370,32 @@ func processVisibility(
|
||||||
}
|
}
|
||||||
|
|
||||||
func processInteractionPolicy(
|
func processInteractionPolicy(
|
||||||
_ *apimodel.StatusCreateRequest,
|
form *apimodel.StatusCreateRequest,
|
||||||
settings *gtsmodel.AccountSettings,
|
settings *gtsmodel.AccountSettings,
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
) error {
|
) gtserror.WithCode {
|
||||||
// TODO: parse policy for this
|
|
||||||
// status from form and prefer this.
|
|
||||||
|
|
||||||
|
// If policy is set on the
|
||||||
|
// form then prefer this.
|
||||||
|
//
|
||||||
// TODO: prevent scope widening by
|
// TODO: prevent scope widening by
|
||||||
// limiting interaction policy if
|
// limiting interaction policy if
|
||||||
// inReplyTo status has a stricter
|
// inReplyTo status has a stricter
|
||||||
// interaction policy than this one.
|
// interaction policy than this one.
|
||||||
|
if form.InteractionPolicy != nil {
|
||||||
|
p, err := typeutils.APIInteractionPolicyToInteractionPolicy(
|
||||||
|
form.InteractionPolicy,
|
||||||
|
form.Visibility,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errWithCode := gtserror.NewErrorBadRequest(err, err.Error())
|
||||||
|
return errWithCode
|
||||||
|
}
|
||||||
|
|
||||||
|
status.InteractionPolicy = p
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
switch status.Visibility {
|
switch status.Visibility {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue