diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 981a1e3..7bca50d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,19 +21,19 @@ jobs: permissions: contents: read id-token: write - runs-on: ${{ github.repository == 'stainless-sdks/post-for-me-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: |- github.repository == 'stainless-sdks/post-for-me-go' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Get GitHub OIDC Token if: |- github.repository == 'stainless-sdks/post-for-me-go' && !startsWith(github.ref, 'refs/heads/stl/') id: github-oidc - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: core.setOutput('github_token', await core.getIDToken()); @@ -49,14 +49,14 @@ jobs: lint: timeout-minutes: 10 name: lint - runs-on: ${{ github.repository == 'stainless-sdks/post-for-me-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version-file: ./go.mod @@ -65,13 +65,13 @@ jobs: test: timeout-minutes: 10 name: test - runs-on: ${{ github.repository == 'stainless-sdks/post-for-me-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version-file: ./go.mod diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b386bef..fac1407 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.19" + ".": "0.1.0-alpha.20" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 0a2314b..aedb62a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 15 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/day-moon-development%2Fpost-for-me-d7bde21e6d3328e90ec781ff8e2629faeaae4bb5d8e0d350703326ec8aadf898.yml -openapi_spec_hash: dcb2130480c4476fe08fcb080e369ce0 -config_hash: 0ec19602e41aea0526548245a59d4253 +configured_endpoints: 21 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/day-moon-development/post-for-me-04c0892827f2117f5c5f9695f01c32a90d82cbde12dbb395eaabe2ccee11fee0.yml +openapi_spec_hash: 150dc8541a9a47afe9154eac3c8caa6d +config_hash: 600d71be044aa28b6759ce7f55a86948 diff --git a/CHANGELOG.md b/CHANGELOG.md index 225535d..acc4b70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +## 0.1.0-alpha.20 (2026-07-29) + +Full Changelog: [v0.1.0-alpha.19...v0.1.0-alpha.20](https://github.com/DayMoonDevelopment/post-for-me-go/compare/v0.1.0-alpha.19...v0.1.0-alpha.20) + +### Features + +* **api:** api update ([de55563](https://github.com/DayMoonDevelopment/post-for-me-go/commit/de5556350c3e468a0b1985c65d48e686811d45ef)) +* **api:** api update ([8ad0cf9](https://github.com/DayMoonDevelopment/post-for-me-go/commit/8ad0cf93b836982f56460a2f324bd9016cf6d57b)) +* **api:** api update ([595c7bd](https://github.com/DayMoonDevelopment/post-for-me-go/commit/595c7bd43f66ef41185419117b4d9acffe407a52)) +* **api:** api update ([5d6118a](https://github.com/DayMoonDevelopment/post-for-me-go/commit/5d6118a831eca768a8c85c04923fea174334dc26)) +* **api:** api update ([b336f84](https://github.com/DayMoonDevelopment/post-for-me-go/commit/b336f84678fa224fe754fd26e77435296dbac9a0)) +* **api:** api update ([c187a30](https://github.com/DayMoonDevelopment/post-for-me-go/commit/c187a30062131aeb2e0958259c4d5cac0e4a23ce)) +* **api:** api update ([e77c224](https://github.com/DayMoonDevelopment/post-for-me-go/commit/e77c22405255fd52a77decb1b57fe44536fa99ce)) +* **api:** api update ([54e435e](https://github.com/DayMoonDevelopment/post-for-me-go/commit/54e435e71a49af71d03b3b9c1081af90932868a5)) +* **api:** api update ([4b69dac](https://github.com/DayMoonDevelopment/post-for-me-go/commit/4b69daca7ab31f9323d2386e8c4f1f77dce2c8cb)) +* **api:** manual updates ([904cdd6](https://github.com/DayMoonDevelopment/post-for-me-go/commit/904cdd684a5a5ad7cd08e3e311a951242b805028)) +* **client:** optimize json encoder for internal types ([aec710d](https://github.com/DayMoonDevelopment/post-for-me-go/commit/aec710d5660db9fceeb7e13ffc5af30bd5427ae5)) +* **go:** add default http client with timeout ([2a04edf](https://github.com/DayMoonDevelopment/post-for-me-go/commit/2a04edfa9f61585467e6bc9e68fe89cd7dabe81d)) +* **stlc:** configurable CI runner and private-production-repo support in workflow templates ([d5c3148](https://github.com/DayMoonDevelopment/post-for-me-go/commit/d5c31487694adfc71f1f53a87f4ef814dba2fd3c)) +* support setting headers via env ([d4a370c](https://github.com/DayMoonDevelopment/post-for-me-go/commit/d4a370c0c7b918de89fbd80aa63449c1cae37484)) + + +### Bug Fixes + +* **go:** avoid panic when http.DefaultTransport is wrapped ([67d16a2](https://github.com/DayMoonDevelopment/post-for-me-go/commit/67d16a2f154e64315c1e296c3bb881f6ce55b230)) + + +### Chores + +* avoid embedding reflect.Type for dead code elimination ([60bf831](https://github.com/DayMoonDevelopment/post-for-me-go/commit/60bf8313819046eeac502a21c45d69a278b9efb2)) +* **internal:** more robust bootstrap script ([d1d617b](https://github.com/DayMoonDevelopment/post-for-me-go/commit/d1d617ba64baefa88888fc8d77903dc9423c6031)) +* redact api-key headers in debug logs ([9c501f0](https://github.com/DayMoonDevelopment/post-for-me-go/commit/9c501f0b91e12b1bd1ccdda00b0caca913f2bccf)) + ## 0.1.0-alpha.19 (2026-04-11) Full Changelog: [v0.1.0-alpha.18...v0.1.0-alpha.19](https://github.com/DayMoonDevelopment/post-for-me-go/compare/v0.1.0-alpha.18...v0.1.0-alpha.19) diff --git a/README.md b/README.md index fa819f6..0d59786 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Or to pin the version: ```sh -go get -u 'github.com/DayMoonDevelopment/post-for-me-go@v0.1.0-alpha.19' +go get -u 'github.com/DayMoonDevelopment/post-for-me-go@v0.1.0-alpha.20' ``` diff --git a/api.md b/api.md index 4609d54..57570ca 100644 --- a/api.md +++ b/api.md @@ -12,6 +12,7 @@ Methods: Params Types: +- postforme.AccountConfigurationParam - postforme.BlueskyConfigurationDtoParam - postforme.CreateSocialPostParam - postforme.FacebookConfigurationDtoParam @@ -19,26 +20,31 @@ Params Types: - postforme.LinkedinConfigurationDtoParam - postforme.PinterestConfigurationDtoParam - postforme.PlatformConfigurationsDtoParam +- postforme.SocialPostMediaParam - postforme.ThreadsConfigurationDtoParam - postforme.TiktokConfigurationParam - postforme.TwitterConfigurationDtoParam +- postforme.TwitterPollParam - postforme.YoutubeConfigurationDtoParam Response Types: +- postforme.AccountConfiguration - postforme.BlueskyConfigurationDto +- postforme.DeleteEntityResponse - postforme.FacebookConfigurationDto - postforme.InstagramConfigurationDto - postforme.LinkedinConfigurationDto - postforme.PinterestConfigurationDto - postforme.PlatformConfigurationsDto - postforme.SocialPost +- postforme.SocialPostMedia - postforme.ThreadsConfigurationDto - postforme.TiktokConfiguration - postforme.TwitterConfigurationDto +- postforme.TwitterPoll - postforme.YoutubeConfigurationDto - postforme.SocialPostListResponse -- postforme.SocialPostDeleteResponse Methods: @@ -46,7 +52,7 @@ Methods: - client.SocialPosts.Get(ctx context.Context, id string) (\*postforme.SocialPost, error) - client.SocialPosts.Update(ctx context.Context, id string, body postforme.SocialPostUpdateParams) (\*postforme.SocialPost, error) - client.SocialPosts.List(ctx context.Context, query postforme.SocialPostListParams) (\*postforme.SocialPostListResponse, error) -- client.SocialPosts.Delete(ctx context.Context, id string) (\*postforme.SocialPostDeleteResponse, error) +- client.SocialPosts.Delete(ctx context.Context, id string) (\*postforme.DeleteEntityResponse, error) # SocialPostResults @@ -62,9 +68,14 @@ Methods: # SocialAccounts +Params Types: + +- postforme.SocialAccountMetadata + Response Types: - postforme.SocialAccount +- postforme.SocialAccountMetadata - postforme.SocialAccountListResponse - postforme.SocialAccountNewAuthURLResponse - postforme.SocialAccountDisconnectResponse @@ -82,9 +93,44 @@ Methods: Response Types: +- postforme.FacebookActivityByActionType +- postforme.FacebookVideoRetentionGraph +- postforme.FacebookVideoViewTimeByDemographic +- postforme.PinterestMetricsWindow - postforme.PlatformPost +- postforme.TiktokBusinessVideoMetricPercentage +- postforme.YoutubePostPlatformData - postforme.SocialAccountFeedListResponse Methods: - client.SocialAccountFeeds.List(ctx context.Context, socialAccountID string, query postforme.SocialAccountFeedListParams) (\*postforme.SocialAccountFeedListResponse, error) + +# Webhooks + +Response Types: + +- postforme.Webhook +- postforme.WebhookListResponse + +Methods: + +- client.Webhooks.New(ctx context.Context, body postforme.WebhookNewParams) (\*postforme.Webhook, error) +- client.Webhooks.Get(ctx context.Context, id string) (\*postforme.Webhook, error) +- client.Webhooks.Update(ctx context.Context, id string, body postforme.WebhookUpdateParams) (\*postforme.Webhook, error) +- client.Webhooks.List(ctx context.Context, query postforme.WebhookListParams) (\*postforme.WebhookListResponse, error) +- client.Webhooks.Delete(ctx context.Context, id string) (\*postforme.DeleteEntityResponse, error) + +# SocialPostPreviews + +Params Types: + +- postforme.CreateSocialPostPreviewParam + +Response Types: + +- postforme.SocialPostPreview + +Methods: + +- client.SocialPostPreviews.New(ctx context.Context, body postforme.SocialPostPreviewNewParams) (\*[]postforme.SocialPostPreview, error) diff --git a/client.go b/client.go index 84bd8dc..8c008fa 100644 --- a/client.go +++ b/client.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "slices" + "strings" "github.com/DayMoonDevelopment/post-for-me-go/internal/requestconfig" "github.com/DayMoonDevelopment/post-for-me-go/option" @@ -60,6 +61,9 @@ type Client struct { // - Post information including caption, url, media, etc.. // - When passing **expand=metrics**, Metrics information including views, likes, // follows, etc.. + // - For **Facebook** feeds with **expand=metrics**, page size is capped by the + // server-side **FacebookFeedMetricsLimitCap** setting (default: **10**) to + // protect API memory usage. // // Note: Currently the following platforms are supported: // @@ -77,18 +81,61 @@ type Client struct { // currently stopped giving permission for personal page analytics, we are on the // waitlist for when they resume. SocialAccountFeeds SocialAccountFeedService + // Webhooks enable you to subscribe to certain events. This involves Post for Me + // making a POST request to the URL of any webhooks you create. Only the events you + // subscribe to will be sent to your webhook URL. + // + // ## Payload + // + // When an event happens that your webhook is subscribed to, we will make a POST + // request with the following JSON body + // + // ``` + // + // { + // "event_type": "", + // "data": {} + // } + // + // ``` + // + // The event_type will be the event that triggered the webhook POST, data will be + // the resulting entity from the event + // + // ## Security + // + // To verify the POST to your webhook URL is from us we will include a secret in + // the header "Post-For-Me-Webhook-Secret". When you create a webhook you will + // receive the secret in the response. + // + // ## Retries + // + // If your server fails to respond with a 2XX code, requests to it will be retried + // with exponential backoff around 8 times over the course of just over a day. + Webhooks WebhookService + // Social Post Previews allow you to see what a Social Post will create for each + // account in the post. + SocialPostPreviews SocialPostPreviewService } // DefaultClientOptions read from the environment (POST_FOR_ME_API_KEY, // POST_FOR_ME_BASE_URL). This should be used to initialize new clients. func DefaultClientOptions() []option.RequestOption { - defaults := []option.RequestOption{option.WithEnvironmentProduction()} + defaults := []option.RequestOption{option.WithHTTPClient(defaultHTTPClient()), option.WithEnvironmentProduction()} if o, ok := os.LookupEnv("POST_FOR_ME_BASE_URL"); ok { defaults = append(defaults, option.WithBaseURL(o)) } if o, ok := os.LookupEnv("POST_FOR_ME_API_KEY"); ok { defaults = append(defaults, option.WithAPIKey(o)) } + if o, ok := os.LookupEnv("POST_FOR_ME_CUSTOM_HEADERS"); ok { + for _, line := range strings.Split(o, "\n") { + colon := strings.Index(line, ":") + if colon >= 0 { + defaults = append(defaults, option.WithHeader(strings.TrimSpace(line[:colon]), strings.TrimSpace(line[colon+1:]))) + } + } + } return defaults } @@ -106,6 +153,8 @@ func NewClient(opts ...option.RequestOption) (r Client) { r.SocialPostResults = NewSocialPostResultService(opts...) r.SocialAccounts = NewSocialAccountService(opts...) r.SocialAccountFeeds = NewSocialAccountFeedService(opts...) + r.Webhooks = NewWebhookService(opts...) + r.SocialPostPreviews = NewSocialPostPreviewService(opts...) return } diff --git a/default_http_client.go b/default_http_client.go new file mode 100644 index 0000000..14420df --- /dev/null +++ b/default_http_client.go @@ -0,0 +1,30 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package postforme + +import ( + "net/http" + "time" +) + +// defaultResponseHeaderTimeout bounds the time between a fully written request +// and the server's response headers. It does not apply to the response body, +// so long-running streams are unaffected. Without this, a server that accepts +// the connection but never responds would hang the request indefinitely. +const defaultResponseHeaderTimeout = 10 * time.Minute + +// defaultHTTPClient returns an [*http.Client] used when the caller does not +// supply one via [option.WithHTTPClient]. When [http.DefaultTransport] is the +// stdlib [*http.Transport], it is cloned and a [http.Transport.ResponseHeaderTimeout] +// is set so stuck connections fail fast instead of compounding across retries. +// If [http.DefaultTransport] has been wrapped (for example by otelhttp for +// distributed tracing), the wrapping is preserved and the header timeout is +// skipped. +func defaultHTTPClient() *http.Client { + if t, ok := http.DefaultTransport.(*http.Transport); ok { + t = t.Clone() + t.ResponseHeaderTimeout = defaultResponseHeaderTimeout + return &http.Client{Transport: t} + } + return &http.Client{Transport: http.DefaultTransport} +} diff --git a/internal/apiform/encoder.go b/internal/apiform/encoder.go index 51db3dd..e47e87a 100644 --- a/internal/apiform/encoder.go +++ b/internal/apiform/encoder.go @@ -58,7 +58,7 @@ type encoderField struct { } type encoderEntry struct { - reflect.Type + typ reflect.Type dateFormat string arrayFmt string root bool @@ -76,7 +76,7 @@ func (e *encoder) marshal(value any, writer *multipart.Writer) error { func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { entry := encoderEntry{ - Type: t, + typ: t, dateFormat: e.dateFormat, arrayFmt: e.arrayFmt, root: e.root, diff --git a/internal/apijson/decoder.go b/internal/apijson/decoder.go index c8bd684..17b6fdf 100644 --- a/internal/apijson/decoder.go +++ b/internal/apijson/decoder.go @@ -80,7 +80,7 @@ type decoderField struct { } type decoderEntry struct { - reflect.Type + typ reflect.Type dateFormat string root bool } @@ -108,7 +108,7 @@ func (d *decoderBuilder) unmarshalWithExactness(raw []byte, to any) (exactness, func (d *decoderBuilder) typeDecoder(t reflect.Type) decoderFunc { entry := decoderEntry{ - Type: t, + typ: t, dateFormat: d.dateFormat, root: d.root, } diff --git a/internal/apijson/encoder.go b/internal/apijson/encoder.go index f3d6f2c..fc903de 100644 --- a/internal/apijson/encoder.go +++ b/internal/apijson/encoder.go @@ -46,7 +46,7 @@ type encoderField struct { } type encoderEntry struct { - reflect.Type + typ reflect.Type dateFormat string root bool } @@ -63,7 +63,7 @@ func (e *encoder) marshal(value any) ([]byte, error) { func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { entry := encoderEntry{ - Type: t, + typ: t, dateFormat: e.dateFormat, root: e.root, } diff --git a/internal/apiquery/encoder.go b/internal/apiquery/encoder.go index 9a2627a..0388a42 100644 --- a/internal/apiquery/encoder.go +++ b/internal/apiquery/encoder.go @@ -29,7 +29,7 @@ type encoderField struct { } type encoderEntry struct { - reflect.Type + typ reflect.Type dateFormat string root bool settings QuerySettings @@ -42,7 +42,7 @@ type Pair struct { func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { entry := encoderEntry{ - Type: t, + typ: t, dateFormat: e.dateFormat, root: e.root, settings: e.settings, diff --git a/internal/encoding/json/encode.go b/internal/encoding/json/encode.go index 12a3f8a..28a681a 100644 --- a/internal/encoding/json/encode.go +++ b/internal/encoding/json/encode.go @@ -173,15 +173,21 @@ import ( // JSON cannot represent cyclic data structures and Marshal does not // handle them. Passing cyclic structures to Marshal will result in // an error. -func Marshal(v any) ([]byte, error) { +// EDIT(begin): add optimization options +func Marshal(v any, opts ...Option) ([]byte, error) { + // EDIT(end): add optimization options e := newEncodeState() defer encodeStatePool.Put(e) - // SHIM(begin): don't escape HTML by default - err := e.marshal(v, encOpts{escapeHTML: shims.EscapeHTMLByDefault}) + // EDIT(begin): don't escape HTML by default, and apply options + encOpts := encOpts{escapeHTML: shims.EscapeHTMLByDefault} + if opts != nil { + encOpts = encOpts.apply(opts...) + } + err := e.marshal(v, encOpts) // ORIGINAL: // err := e.marshal(v, encOpts{escapeHTML: true}) - // SHIM(end) + // EDIT(end) if err != nil { return nil, err } @@ -352,6 +358,9 @@ type encOpts struct { // EDIT(begin): save the timefmt timefmt string // EDIT(end) + // EDIT(begin): add optimization to skip compaction + skipCompaction bool + // EDIT(end) } type encoderFunc func(e *encodeState, v reflect.Value, opts encOpts) @@ -483,7 +492,7 @@ func marshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { if err == nil { e.Grow(len(b)) out := e.AvailableBuffer() - out, err = appendCompact(out, b, opts.escapeHTML) + out, err = appendCompact(out, b, opts) e.Buffer.Write(out) } if err != nil { @@ -509,7 +518,7 @@ func addrMarshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { if err == nil { e.Grow(len(b)) out := e.AvailableBuffer() - out, err = appendCompact(out, b, opts.escapeHTML) + out, err = appendCompact(out, b, opts) e.Buffer.Write(out) } if err != nil { diff --git a/internal/encoding/json/indent.go b/internal/encoding/json/indent.go index 01bfdf6..c9d6ca5 100644 --- a/internal/encoding/json/indent.go +++ b/internal/encoding/json/indent.go @@ -4,7 +4,9 @@ package json -import "bytes" +import ( + "bytes" +) // HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029 // characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029 @@ -41,12 +43,21 @@ func appendHTMLEscape(dst, src []byte) []byte { func Compact(dst *bytes.Buffer, src []byte) error { dst.Grow(len(src)) b := dst.AvailableBuffer() - b, err := appendCompact(b, src, false) + b, err := appendCompact(b, src, encOpts{}) dst.Write(b) return err } -func appendCompact(dst, src []byte, escape bool) ([]byte, error) { +func appendCompact(dst, src []byte, opts encOpts) ([]byte, error) { + // EDIT(begin): optimize for skipCompaction + if opts.skipCompaction { + dst = append(dst, src...) + return dst, nil + } + + escape := opts.escapeHTML + // EDIT(end) + origLen := len(dst) scan := newScanner() defer freeScanner(scan) diff --git a/internal/encoding/json/opt.go b/internal/encoding/json/opt.go new file mode 100644 index 0000000..fd6f8d2 --- /dev/null +++ b/internal/encoding/json/opt.go @@ -0,0 +1,24 @@ +// EDIT(begin): add custom options for JSON encoding +package json + +type Option func(*encOpts) + +// Every time a sub-type of [json.Marshaler] is encountered, +// skip a redundant and costly compaction step, trust it to self-compact. +// +// This is a divergence from the standard library behavior, and is only guaranteed +// safe with SDK types. +func WithSkipCompaction(b bool) Option { + return func(eos *encOpts) { + eos.skipCompaction = true + } +} + +func (eos encOpts) apply(opts ...Option) encOpts { + for _, opt := range opts { + opt(&eos) + } + return eos +} + +// EDIT(end) diff --git a/internal/encoding/json/stream.go b/internal/encoding/json/stream.go index e2d9470..652522c 100644 --- a/internal/encoding/json/stream.go +++ b/internal/encoding/json/stream.go @@ -6,7 +6,6 @@ package json import ( "bytes" - "errors" "io" ) @@ -253,30 +252,34 @@ func (enc *Encoder) SetEscapeHTML(on bool) { enc.escapeHTML = on } -// RawMessage is a raw encoded JSON value. -// It implements [Marshaler] and [Unmarshaler] and can -// be used to delay JSON decoding or precompute a JSON encoding. -type RawMessage []byte - -// MarshalJSON returns m as the JSON encoding of m. -func (m RawMessage) MarshalJSON() ([]byte, error) { - if m == nil { - return []byte("null"), nil - } - return m, nil -} - -// UnmarshalJSON sets *m to a copy of data. -func (m *RawMessage) UnmarshalJSON(data []byte) error { - if m == nil { - return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") - } - *m = append((*m)[0:0], data...) - return nil -} - -var _ Marshaler = (*RawMessage)(nil) -var _ Unmarshaler = (*RawMessage)(nil) +// EDIT(begin): remove RawMessage +// +// // RawMessage is a raw encoded JSON value. +// // It implements [Marshaler] and [Unmarshaler] and can +// // be used to delay JSON decoding or precompute a JSON encoding. +// type RawMessage []byte +// +// // MarshalJSON returns m as the JSON encoding of m. +// func (m RawMessage) MarshalJSON() ([]byte, error) { +// if m == nil { +// return []byte("null"), nil +// } +// return m, nil +// } +// +// // UnmarshalJSON sets *m to a copy of data. +// func (m *RawMessage) UnmarshalJSON(data []byte) error { +// if m == nil { +// return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") +// } +// *m = append((*m)[0:0], data...) +// return nil +// } +// +// var _ Marshaler = (*RawMessage)(nil) +// var _ Unmarshaler = (*RawMessage)(nil) +// +// EDIT(end) // A Token holds a value of one of these types: // diff --git a/internal/encoding/json/time.go b/internal/encoding/json/time.go index e20ee3e..6e08962 100644 --- a/internal/encoding/json/time.go +++ b/internal/encoding/json/time.go @@ -50,7 +50,7 @@ func timeMarshalEncoder(e *encodeState, v reflect.Value, opts encOpts) bool { if b != nil { e.Grow(len(b)) out := e.AvailableBuffer() - out, _ = appendCompact(out, b, opts.escapeHTML) + out, _ = appendCompact(out, b, opts) e.Buffer.Write(out) return true } diff --git a/internal/version.go b/internal/version.go index 296d72b..985cfb5 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.1.0-alpha.19" // x-release-please-version +const PackageVersion = "0.1.0-alpha.20" // x-release-please-version diff --git a/option/middleware.go b/option/middleware.go index 8ec9dd6..4be0987 100644 --- a/option/middleware.go +++ b/option/middleware.go @@ -8,6 +8,10 @@ import ( "net/http/httputil" ) +// sensitiveLogHeaders are redacted before request and response content is +// written to the debug logger. +var sensitiveLogHeaders = []string{"authorization", "api-key", "x-api-key", "cookie", "set-cookie"} + // WithDebugLog logs the HTTP request and response content. // If the logger parameter is nil, it uses the default logger. // @@ -20,7 +24,7 @@ func WithDebugLog(logger *log.Logger) RequestOption { logger = log.Default() } - if reqBytes, err := httputil.DumpRequest(req, true); err == nil { + if reqBytes, err := dumpRedactedRequest(req); err == nil { logger.Printf("Request Content:\n%s\n", reqBytes) } @@ -29,10 +33,48 @@ func WithDebugLog(logger *log.Logger) RequestOption { return resp, err } - if respBytes, err := httputil.DumpResponse(resp, true); err == nil { + if respBytes, err := dumpRedactedResponse(resp); err == nil { logger.Printf("Response Content:\n%s\n", respBytes) } return resp, err }) } + +// dumpRedactedRequest dumps req with sensitive headers replaced. The +// original headers are restored via defer so a panic in DumpRequest cannot +// leak the placeholder map into the live request sent downstream. +func dumpRedactedRequest(req *http.Request) ([]byte, error) { + origHeaders := req.Header + req.Header = redactDebugHeaders(origHeaders) + defer func() { req.Header = origHeaders }() + return httputil.DumpRequest(req, true) +} + +func dumpRedactedResponse(resp *http.Response) ([]byte, error) { + origHeaders := resp.Header + resp.Header = redactDebugHeaders(origHeaders) + defer func() { resp.Header = origHeaders }() + return httputil.DumpResponse(resp, true) +} + +func redactDebugHeaders(headers http.Header) http.Header { + var redacted http.Header + for _, name := range sensitiveLogHeaders { + values := headers.Values(name) + if len(values) == 0 { + continue + } + if redacted == nil { + redacted = headers.Clone() + } + redacted.Del(name) + for range values { + redacted.Add(name, "***") + } + } + if redacted == nil { + return headers + } + return redacted +} diff --git a/packages/param/encoder.go b/packages/param/encoder.go index 54af2cd..f879b4b 100644 --- a/packages/param/encoder.go +++ b/packages/param/encoder.go @@ -66,7 +66,7 @@ func MarshalWithExtras[T ParamStruct, R any](f T, underlying any, extras map[str } else if ovr, ok := f.Overrides(); ok { return shimjson.Marshal(ovr) } else { - return shimjson.Marshal(underlying) + return shimjson.Marshal(underlying, shimjson.WithSkipCompaction(true)) } } @@ -96,7 +96,7 @@ func MarshalUnion[T ParamStruct](metadata T, variants ...any) ([]byte, error) { Err: fmt.Errorf("expected union to have only one present variant, got %d", nPresent), } } - return shimjson.Marshal(variants[presentIdx]) + return shimjson.Marshal(variants[presentIdx], shimjson.WithSkipCompaction(true)) } // typeFor is shimmed from Go 1.23 "reflect" package diff --git a/packages/param/encoder_test.go b/packages/param/encoder_test.go index 6355b02..94516e9 100644 --- a/packages/param/encoder_test.go +++ b/packages/param/encoder_test.go @@ -1,10 +1,13 @@ package param_test import ( + "bytes" "encoding/json" + "reflect" "testing" "time" + shimjson "github.com/DayMoonDevelopment/post-for-me-go/internal/encoding/json" "github.com/DayMoonDevelopment/post-for-me-go/packages/param" ) @@ -375,3 +378,176 @@ func TestNullStructUnion(t *testing.T) { t.Fatalf("expected null, received %s", string(b)) } } + +// +// Compaction optimization +// + +type NonCompactedDoubleParent struct { + Prop string `json:"prop"` + Parent NonCompactedParent `json:"parent"` + + param.APIObject +} + +type NonCompactedParent struct { + BadChild NonCompacted `json:"bad_child"` + + param.APIObject +} + +type NonCompacted struct { + Raw string + + param.APIObject +} + +func (a NonCompactedDoubleParent) MarshalJSON() ([]byte, error) { + type shadow NonCompactedDoubleParent + return param.MarshalObject(a, (*shadow)(&a)) +} + +func (a NonCompactedParent) MarshalJSON() ([]byte, error) { + type shadow NonCompactedParent + return param.MarshalObject(a, (*shadow)(&a)) +} + +func (a NonCompacted) MarshalJSON() ([]byte, error) { + if a.Raw == "" { + a.Raw = nonCompactedRaw + } + return []byte(a.Raw), nil +} + +var nonCompactedRaw string = ` { "foo": "bar" } ` + +func TestAppendCompactBroken(t *testing.T) { + tests := map[string]struct { + value json.Marshaler + }{ + "red/illegal-json": { + NonCompacted{Raw: `{ "broken": "json" `}, + }, + "red/nested-with-illegal-json": { + NonCompactedParent{BadChild: NonCompacted{ + Raw: `{ "broken": "json" `, + }}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + v, err := json.Marshal(test.value) + if err == nil { + t.Fatal("expected error got", v) + } + }) + } +} + +// TestAppendCompact validates an optimization for internal SDK types to +// avoid O(keys^2) iteration over each JSON object. +// +// It's possible to intentionally trigger this behavior as both a user and +// SDK developer. However, the edge case is quite pathological and requires +// calling [json.Marshaler.MarshalJSON] rather than [json.Marshal]. +func TestAppendCompact(t *testing.T) { + + tests := map[string]struct { + value json.Marshaler + expected string + }{ + // + // Non-compacted cases + // + // Note this is how to exploit the compacter to fail, you must call [json.Marshaler.MarshalJSON] rather than [json.Marshal]. + // The type must also embed [param.APIObject] and return non-compacted JSON. + // + + "no-compact/fails-compaction": { + NonCompacted{Raw: nonCompactedRaw}, + nonCompactedRaw, + }, + "no-compact/nested-with-bad-child": { + NonCompactedParent{BadChild: NonCompacted{ + Raw: nonCompactedRaw, + }}, + `{"bad_child":` + nonCompactedRaw + `}`, + }, + "no-compact/double-nested-with-bad-child": { + NonCompactedDoubleParent{Prop: "1", Parent: NonCompactedParent{BadChild: NonCompacted{ + Raw: nonCompactedRaw, + }}}, + `{"prop":"1","parent":{"bad_child":` + nonCompactedRaw + `}}`, + }, + + // + // Compacted cases + // + + "override/spaces-within": { + param.Override[NonCompactedDoubleParent](json.RawMessage(`{"com": "pact"}`)), + `{"com":"pact"}`, + }, + "override/spaces-after": { + param.Override[NonCompactedDoubleParent](json.RawMessage(`{"com":"pact"} `)), + `{"com":"pact"}`, + }, + "override/spaces-before": { + param.Override[NonCompactedDoubleParent](json.RawMessage(` {"com":"pact"}`)), + `{"com":"pact"}`, + }, + "override/spaces-around": { + param.Override[NonCompactedDoubleParent](json.RawMessage(` { "com": "pact" }`)), + `{"com":"pact"}`, + }, + "override/override-with-nested": { + param.Override[NonCompactedDoubleParent](NonCompactedParent{}), + `{"bad_child":{"foo":"bar"}}`, + }, + "override/override-with-non-compacted": { + param.Override[NonCompactedDoubleParent](NonCompacted{}), + `{"foo":"bar"}`, + }, + } + + for name, test := range tests { + t.Run(name+"/marshal-json", func(t *testing.T) { + b, err := test.value.MarshalJSON() + if err != nil { + t.Fatalf("didn't expect error %v, expected %s", err, test.expected) + } + if string(b) != test.expected { + t.Fatalf("expected %s (%s), received %s", test.expected, reflect.TypeOf(test.value), string(b)) + } + }) + + t.Run(name+"/json-marshal", func(t *testing.T) { + b, err := json.Marshal(test.value) + if err != nil { + t.Fatalf("didn't expect error %v, expected %s", err, test.expected) + } + + // expected output of JSON Marshal should always be compacted + var compactedExpected bytes.Buffer + err = json.Compact(&compactedExpected, []byte(test.expected)) + if err != nil { + t.Fatalf("didn't expect error %v, expected %s", err, test.expected) + } + + if string(b) != compactedExpected.String() { + t.Fatalf("expected %s (%s), received %s", test.expected, reflect.TypeOf(test.value), string(b)) + } + }) + + t.Run(name+"/shimjson-marshal", func(t *testing.T) { + b, err := shimjson.Marshal(test.value) + if err != nil { + t.Fatalf("didn't expect error %v, expected %s", err, test.expected) + } + if string(b) != test.expected { + t.Logf("expected %s (%s), received %s", test.expected, reflect.TypeOf(test.value), string(b)) + } + }) + } +} diff --git a/scripts/bootstrap b/scripts/bootstrap index 5ab3066..46547f1 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,7 +4,7 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { echo -n "==> Install Homebrew dependencies? (y/N): " read -r response diff --git a/socialaccount.go b/socialaccount.go index 33fc13f..0fcd313 100644 --- a/socialaccount.go +++ b/socialaccount.go @@ -87,7 +87,11 @@ func (r *SocialAccountService) List(ctx context.Context, query SocialAccountList // Generates a URL that initiates the authentication flow for a user's social media // account. When visited, the user is redirected to the selected social platform's // login/authorization page. Upon successful authentication, they are redirected -// back to your application +// back to your application. +// +// For Quickstart projects using Post for Me system credentials, +// `redirect_url_override` is not accepted. Configure the project redirect URL in +// the dashboard instead. func (r *SocialAccountService) NewAuthURL(ctx context.Context, body SocialAccountNewAuthURLParams, opts ...option.RequestOption) (res *SocialAccountNewAuthURLResponse, err error) { opts = slices.Concat(r.Options, opts) path := "v1/social-accounts/auth-url" @@ -119,7 +123,7 @@ type SocialAccount struct { // The external id of the social account ExternalID string `json:"external_id" api:"required"` // The metadata of the social account - Metadata any `json:"metadata" api:"required"` + Metadata SocialAccountMetadata `json:"metadata" api:"required"` // The platform of the social account Platform string `json:"platform" api:"required"` // The platform's profile photo of the social account @@ -169,6 +173,8 @@ const ( SocialAccountStatusDisconnected SocialAccountStatus = "disconnected" ) +type SocialAccountMetadata = any + type SocialAccountListResponse struct { Data []SocialAccount `json:"data" api:"required"` Meta SocialAccountListResponseMeta `json:"meta" api:"required"` @@ -243,7 +249,7 @@ type SocialAccountDisconnectResponse struct { // The external id of the social account ExternalID string `json:"external_id" api:"required"` // The metadata of the social account - Metadata any `json:"metadata" api:"required"` + Metadata SocialAccountMetadata `json:"metadata" api:"required"` // The platform of the social account Platform string `json:"platform" api:"required"` // The platform's profile photo of the social account @@ -313,7 +319,7 @@ type SocialAccountNewParams struct { // The platform's username of the social account Username param.Opt[string] `json:"username,omitzero"` // The metadata of the social account - Metadata any `json:"metadata,omitzero"` + Metadata SocialAccountMetadata `json:"metadata,omitzero"` paramObj } @@ -399,7 +405,7 @@ type SocialAccountNewAuthURLParams struct { // Override the default redirect URL for the OAuth flow. If provided, this URL will // be used instead of our redirect URL. Make sure this URL is included in your // app's authorized redirect urls. This override will not work when using our - // system credientals. + // system credentials; configure the project redirect URL in the dashboard instead. RedirectURLOverride param.Opt[string] `json:"redirect_url_override,omitzero"` // List of permissions you want to allow. Will default to only post permissions. // You must include the "feeds" permission to request an account feed and metrics @@ -437,6 +443,8 @@ type SocialAccountNewAuthURLParamsPlatformData struct { Tiktok SocialAccountNewAuthURLParamsPlatformDataTiktok `json:"tiktok,omitzero"` // Additional data for connecting TikTok Business accounts TiktokBusiness SocialAccountNewAuthURLParamsPlatformDataTiktokBusiness `json:"tiktok_business,omitzero"` + // Additional data for connecting X accounts + X SocialAccountNewAuthURLParamsPlatformDataX `json:"x,omitzero"` // Additional data for connecting YouTube accounts Youtube SocialAccountNewAuthURLParamsPlatformDataYoutube `json:"youtube,omitzero"` paramObj @@ -474,7 +482,7 @@ type SocialAccountNewAuthURLParamsPlatformDataFacebook struct { // Override the default permissions/scopes requested during OAuth. Default scopes: // public_profile, pages_show_list, pages_read_engagement, pages_manage_posts, // business_management - PermissionOverrides [][]any `json:"permission_overrides,omitzero"` + PermissionOverrides []string `json:"permission_overrides,omitzero"` paramObj } @@ -499,7 +507,7 @@ type SocialAccountNewAuthURLParamsPlatformDataInstagram struct { // instagram scopes: instagram_business_basic, instagram_business_content_publish. // Default facebook scopes: instagram_basic, instagram_content_publish, // pages_show_list, public_profile, business_management - PermissionOverrides [][]any `json:"permission_overrides,omitzero"` + PermissionOverrides []string `json:"permission_overrides,omitzero"` paramObj } @@ -522,7 +530,7 @@ func init() { // The property ConnectionType is required. type SocialAccountNewAuthURLParamsPlatformDataLinkedin struct { // The type of connection; If using our provided credentials always use - // "organization". If using your own crednetials then only use "organization" if + // "organization". If using your own credentials then only use "organization" if // you are using the Community API // // Any of "personal", "organization". @@ -531,7 +539,7 @@ type SocialAccountNewAuthURLParamsPlatformDataLinkedin struct { // scopes: openid, w_member_social, profile, email. Default organization scopes: // r_basicprofile, w_member_social, r_organization_social, w_organization_social, // rw_organization_admin - PermissionOverrides [][]any `json:"permission_overrides,omitzero"` + PermissionOverrides []string `json:"permission_overrides,omitzero"` paramObj } @@ -553,7 +561,7 @@ func init() { type SocialAccountNewAuthURLParamsPlatformDataPinterest struct { // Override the default permissions/scopes requested during OAuth. Default scopes: // boards:read, boards:write, pins:read, pins:write, user_accounts:read - PermissionOverrides [][]any `json:"permission_overrides,omitzero"` + PermissionOverrides []string `json:"permission_overrides,omitzero"` paramObj } @@ -569,7 +577,7 @@ func (r *SocialAccountNewAuthURLParamsPlatformDataPinterest) UnmarshalJSON(data type SocialAccountNewAuthURLParamsPlatformDataThreads struct { // Override the default permissions/scopes requested during OAuth. Default scopes: // threads_basic, threads_content_publish - PermissionOverrides [][]any `json:"permission_overrides,omitzero"` + PermissionOverrides []string `json:"permission_overrides,omitzero"` paramObj } @@ -585,7 +593,7 @@ func (r *SocialAccountNewAuthURLParamsPlatformDataThreads) UnmarshalJSON(data [] type SocialAccountNewAuthURLParamsPlatformDataTiktok struct { // Override the default permissions/scopes requested during OAuth. Default scopes: // user.info.basic, video.list, video.upload, video.publish - PermissionOverrides [][]any `json:"permission_overrides,omitzero"` + PermissionOverrides []string `json:"permission_overrides,omitzero"` paramObj } @@ -604,7 +612,7 @@ type SocialAccountNewAuthURLParamsPlatformDataTiktokBusiness struct { // user.account.type, user.insights, video.list, video.insights, comment.list, // comment.list.manage, video.publish, video.upload, biz.spark.auth, // discovery.search.words - PermissionOverrides [][]any `json:"permission_overrides,omitzero"` + PermissionOverrides []string `json:"permission_overrides,omitzero"` paramObj } @@ -616,6 +624,32 @@ func (r *SocialAccountNewAuthURLParamsPlatformDataTiktokBusiness) UnmarshalJSON( return apijson.UnmarshalRoot(data, r) } +// Additional data for connecting X accounts +// +// The property ConnectionType is required. +type SocialAccountNewAuthURLParamsPlatformDataX struct { + // The type of connection; oauth1 for OAuth 1.0a app credentials, oauth2 for OAuth + // 2.0 app credentials. + // + // Any of "oauth1", "oauth2". + ConnectionType string `json:"connection_type,omitzero" api:"required"` + paramObj +} + +func (r SocialAccountNewAuthURLParamsPlatformDataX) MarshalJSON() (data []byte, err error) { + type shadow SocialAccountNewAuthURLParamsPlatformDataX + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *SocialAccountNewAuthURLParamsPlatformDataX) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +func init() { + apijson.RegisterFieldValidator[SocialAccountNewAuthURLParamsPlatformDataX]( + "connection_type", "oauth1", "oauth2", + ) +} + // Additional data for connecting YouTube accounts type SocialAccountNewAuthURLParamsPlatformDataYoutube struct { // Override the default permissions/scopes requested during OAuth. Default scopes: @@ -623,7 +657,7 @@ type SocialAccountNewAuthURLParamsPlatformDataYoutube struct { // https://www.googleapis.com/auth/youtube.upload, // https://www.googleapis.com/auth/youtube.readonly, // https://www.googleapis.com/auth/userinfo.profile - PermissionOverrides [][]any `json:"permission_overrides,omitzero"` + PermissionOverrides []string `json:"permission_overrides,omitzero"` paramObj } diff --git a/socialaccount_test.go b/socialaccount_test.go index a03b76d..b530984 100644 --- a/socialaccount_test.go +++ b/socialaccount_test.go @@ -154,30 +154,33 @@ func TestSocialAccountNewAuthURLWithOptionalParams(t *testing.T) { Handle: "handle", }, Facebook: postforme.SocialAccountNewAuthURLParamsPlatformDataFacebook{ - PermissionOverrides: [][]any{{map[string]any{}}}, + PermissionOverrides: []string{"string"}, }, Instagram: postforme.SocialAccountNewAuthURLParamsPlatformDataInstagram{ ConnectionType: "instagram", - PermissionOverrides: [][]any{{map[string]any{}}}, + PermissionOverrides: []string{"string"}, }, Linkedin: postforme.SocialAccountNewAuthURLParamsPlatformDataLinkedin{ ConnectionType: "personal", - PermissionOverrides: [][]any{{map[string]any{}}}, + PermissionOverrides: []string{"string"}, }, Pinterest: postforme.SocialAccountNewAuthURLParamsPlatformDataPinterest{ - PermissionOverrides: [][]any{{map[string]any{}}}, + PermissionOverrides: []string{"string"}, }, Threads: postforme.SocialAccountNewAuthURLParamsPlatformDataThreads{ - PermissionOverrides: [][]any{{map[string]any{}}}, + PermissionOverrides: []string{"string"}, }, Tiktok: postforme.SocialAccountNewAuthURLParamsPlatformDataTiktok{ - PermissionOverrides: [][]any{{map[string]any{}}}, + PermissionOverrides: []string{"string"}, }, TiktokBusiness: postforme.SocialAccountNewAuthURLParamsPlatformDataTiktokBusiness{ - PermissionOverrides: [][]any{{map[string]any{}}}, + PermissionOverrides: []string{"string"}, + }, + X: postforme.SocialAccountNewAuthURLParamsPlatformDataX{ + ConnectionType: "oauth1", }, Youtube: postforme.SocialAccountNewAuthURLParamsPlatformDataYoutube{ - PermissionOverrides: [][]any{{map[string]any{}}}, + PermissionOverrides: []string{"string"}, }, }, RedirectURLOverride: postforme.String("redirect_url_override"), diff --git a/socialaccountfeed.go b/socialaccountfeed.go index da00b34..72bdd59 100644 --- a/socialaccountfeed.go +++ b/socialaccountfeed.go @@ -30,6 +30,9 @@ import ( // - Post information including caption, url, media, etc.. // - When passing **expand=metrics**, Metrics information including views, likes, // follows, etc.. +// - For **Facebook** feeds with **expand=metrics**, page size is capped by the +// server-side **FacebookFeedMetricsLimitCap** setting (default: **10**) to +// protect API memory usage. // // Note: Currently the following platforms are supported: // @@ -78,6 +81,122 @@ func (r *SocialAccountFeedService) List(ctx context.Context, socialAccountID str return res, err } +type FacebookActivityByActionType struct { + // Action type (e.g., like, comment, share) + ActionType string `json:"action_type" api:"required"` + // Number of actions + Value float64 `json:"value" api:"required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + ActionType respjson.Field + Value respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r FacebookActivityByActionType) RawJSON() string { return r.JSON.raw } +func (r *FacebookActivityByActionType) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type FacebookVideoRetentionGraph struct { + // Percentage of viewers at this time + Rate float64 `json:"rate" api:"required"` + // Time in seconds + Time float64 `json:"time" api:"required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Rate respjson.Field + Time respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r FacebookVideoRetentionGraph) RawJSON() string { return r.JSON.raw } +func (r *FacebookVideoRetentionGraph) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type FacebookVideoViewTimeByDemographic struct { + // Demographic key (e.g., age_gender, region, country) + Key string `json:"key" api:"required"` + // Total view time in milliseconds + Value float64 `json:"value" api:"required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Key respjson.Field + Value respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r FacebookVideoViewTimeByDemographic) RawJSON() string { return r.JSON.raw } +func (r *FacebookVideoViewTimeByDemographic) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type PinterestMetricsWindow struct { + // Number of comments on the Pin + Comment float64 `json:"comment"` + // Number of times the Pin was shown (impressions) + Impression float64 `json:"impression"` + // The last time Pinterest updated these metrics + LastUpdated string `json:"last_updated"` + // Number of clicks from the Pin to an external destination (outbound clicks) + OutboundClick float64 `json:"outbound_click"` + // Number of clicks on the Pin to view it in closeup (Pin clicks) + PinClick float64 `json:"pin_click"` + // Number of visits to the author's profile driven from the Pin + ProfileVisit any `json:"profile_visit" api:"nullable"` + // Total number of reactions on the Pin + Reaction float64 `json:"reaction"` + // Number of saves of the Pin + Save float64 `json:"save"` + // Number of follows driven from the Pin + UserFollow any `json:"user_follow" api:"nullable"` + // Number of video views of at least 10 seconds + Video10sViews float64 `json:"video_10s_views"` + // Average watch time for the video + VideoAverageTime float64 `json:"video_average_time"` + // Number of video views that reached 95% completion + VideoP95Views float64 `json:"video_p95_views"` + // Total watch time for the video + VideoTotalTime float64 `json:"video_total_time"` + // Number of video views + VideoViews float64 `json:"video_views"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Comment respjson.Field + Impression respjson.Field + LastUpdated respjson.Field + OutboundClick respjson.Field + PinClick respjson.Field + ProfileVisit respjson.Field + Reaction respjson.Field + Save respjson.Field + UserFollow respjson.Field + Video10sViews respjson.Field + VideoAverageTime respjson.Field + VideoP95Views respjson.Field + VideoTotalTime respjson.Field + VideoViews respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r PinterestMetricsWindow) RawJSON() string { return r.JSON.raw } +func (r *PinterestMetricsWindow) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + type PlatformPost struct { // Caption or text content of the post Caption string `json:"caption" api:"required"` @@ -100,7 +219,7 @@ type PlatformPost struct { // Post metrics and analytics data Metrics PlatformPostMetricsUnion `json:"metrics"` // Platform-specific data for the post - PlatformData PlatformPostPlatformData `json:"platform_data"` + PlatformData YoutubePostPlatformData `json:"platform_data"` // Date the post was published PostedAt time.Time `json:"posted_at" format:"date-time"` // ID of the social post @@ -166,7 +285,7 @@ type PlatformPostMetricsUnion struct { // This field is from variant [PlatformPostMetricsTikTokBusinessMetricsDto]. EmailClicks float64 `json:"email_clicks"` // This field is from variant [PlatformPostMetricsTikTokBusinessMetricsDto]. - EngagementLikes []PlatformPostMetricsTikTokBusinessMetricsDtoEngagementLike `json:"engagement_likes"` + EngagementLikes []TiktokBusinessVideoMetricPercentage `json:"engagement_likes"` // This field is from variant [PlatformPostMetricsTikTokBusinessMetricsDto]. Favorites float64 `json:"favorites"` // This field is from variant [PlatformPostMetricsTikTokBusinessMetricsDto]. @@ -187,8 +306,8 @@ type PlatformPostMetricsUnion struct { // This field is from variant [PlatformPostMetricsTikTokBusinessMetricsDto]. TotalTimeWatched float64 `json:"total_time_watched"` // This field is from variant [PlatformPostMetricsTikTokBusinessMetricsDto]. - VideoViewRetention []PlatformPostMetricsTikTokBusinessMetricsDtoVideoViewRetention `json:"video_view_retention"` - VideoViews float64 `json:"video_views"` + VideoViewRetention []TiktokBusinessVideoMetricPercentage `json:"video_view_retention"` + VideoViews float64 `json:"video_views"` // This field is from variant [PlatformPostMetricsTikTokBusinessMetricsDto]. WebsiteClicks float64 `json:"website_clicks"` CommentCount float64 `json:"comment_count"` @@ -263,20 +382,12 @@ type PlatformPostMetricsUnion struct { // This field is from variant [PlatformPostMetricsYouTubePostMetricsDto]. VideosRemovedFromPlaylists float64 `json:"videosRemovedFromPlaylists"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - ActivityByActionType []PlatformPostMetricsFacebookPostMetricsDtoActivityByActionType `json:"activity_by_action_type"` - // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - ActivityByActionTypeUnique []PlatformPostMetricsFacebookPostMetricsDtoActivityByActionTypeUnique `json:"activity_by_action_type_unique"` + ActivityByActionType []FacebookActivityByActionType `json:"activity_by_action_type"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - FanReach float64 `json:"fan_reach"` + ActivityByActionTypeUnique []FacebookActivityByActionType `json:"activity_by_action_type_unique"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. MediaViews float64 `json:"media_views"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - NonviralReach float64 `json:"nonviral_reach"` - // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - OrganicReach float64 `json:"organic_reach"` - // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - PaidReach float64 `json:"paid_reach"` - // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. ReactionsAnger float64 `json:"reactions_anger"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. ReactionsByType any `json:"reactions_by_type"` @@ -305,21 +416,21 @@ type PlatformPostMetricsUnion struct { // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. VideoLength float64 `json:"video_length"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - VideoRetentionGraphAutoplayed []PlatformPostMetricsFacebookPostMetricsDtoVideoRetentionGraphAutoplayed `json:"video_retention_graph_autoplayed"` + VideoRetentionGraphAutoplayed []FacebookVideoRetentionGraph `json:"video_retention_graph_autoplayed"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - VideoRetentionGraphClickedToPlay []PlatformPostMetricsFacebookPostMetricsDtoVideoRetentionGraphClickedToPlay `json:"video_retention_graph_clicked_to_play"` + VideoRetentionGraphClickedToPlay []FacebookVideoRetentionGraph `json:"video_retention_graph_clicked_to_play"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. VideoSocialActionsUnique float64 `json:"video_social_actions_unique"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. VideoViewTime float64 `json:"video_view_time"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - VideoViewTimeByAgeGender []PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByAgeGender `json:"video_view_time_by_age_gender"` + VideoViewTimeByAgeGender []FacebookVideoViewTimeByDemographic `json:"video_view_time_by_age_gender"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - VideoViewTimeByCountry []PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByCountry `json:"video_view_time_by_country"` + VideoViewTimeByCountry []FacebookVideoViewTimeByDemographic `json:"video_view_time_by_country"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. VideoViewTimeByDistributionType any `json:"video_view_time_by_distribution_type"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - VideoViewTimeByRegion []PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByRegion `json:"video_view_time_by_region"` + VideoViewTimeByRegion []FacebookVideoViewTimeByDemographic `json:"video_view_time_by_region"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. VideoViewTimeOrganic float64 `json:"video_view_time_organic"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. @@ -335,17 +446,9 @@ type PlatformPostMetricsUnion struct { // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. VideoViewsOrganic float64 `json:"video_views_organic"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - VideoViewsOrganicUnique float64 `json:"video_views_organic_unique"` - // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. VideoViewsPaid float64 `json:"video_views_paid"` // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - VideoViewsPaidUnique float64 `json:"video_views_paid_unique"` - // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. VideoViewsSoundOn float64 `json:"video_views_sound_on"` - // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - VideoViewsUnique float64 `json:"video_views_unique"` - // This field is from variant [PlatformPostMetricsFacebookPostMetricsDto]. - ViralReach float64 `json:"viral_reach"` // This field is from variant [PlatformPostMetricsTwitterPostMetricsDto]. NonPublicMetrics PlatformPostMetricsTwitterPostMetricsDtoNonPublicMetrics `json:"non_public_metrics"` // This field is from variant [PlatformPostMetricsTwitterPostMetricsDto]. @@ -377,9 +480,9 @@ type PlatformPostMetricsUnion struct { // This field is from variant [PlatformPostMetricsBlueskyPostMetricsDto]. RepostCount float64 `json:"repostCount"` // This field is from variant [PlatformPostMetricsPinterestPostMetricsDto]. - Number90d PlatformPostMetricsPinterestPostMetricsDto90d `json:"90d"` + Number90d PinterestMetricsWindow `json:"90d"` // This field is from variant [PlatformPostMetricsPinterestPostMetricsDto]. - LifetimeMetrics PlatformPostMetricsPinterestPostMetricsDtoLifetimeMetrics `json:"lifetime_metrics"` + LifetimeMetrics PinterestMetricsWindow `json:"lifetime_metrics"` JSON struct { AddressClicks respjson.Field AppDownloadClicks respjson.Field @@ -445,11 +548,7 @@ type PlatformPostMetricsUnion struct { VideosRemovedFromPlaylists respjson.Field ActivityByActionType respjson.Field ActivityByActionTypeUnique respjson.Field - FanReach respjson.Field MediaViews respjson.Field - NonviralReach respjson.Field - OrganicReach respjson.Field - PaidReach respjson.Field ReactionsAnger respjson.Field ReactionsByType respjson.Field ReactionsHaha respjson.Field @@ -479,12 +578,8 @@ type PlatformPostMetricsUnion struct { VideoViewsByDistributionType respjson.Field VideoViewsClickedToPlay respjson.Field VideoViewsOrganic respjson.Field - VideoViewsOrganicUnique respjson.Field VideoViewsPaid respjson.Field - VideoViewsPaidUnique respjson.Field VideoViewsSoundOn respjson.Field - VideoViewsUnique respjson.Field - ViralReach respjson.Field NonPublicMetrics respjson.Field OrganicMetrics respjson.Field PublicMetrics respjson.Field @@ -583,7 +678,7 @@ type PlatformPostMetricsTikTokBusinessMetricsDto struct { // Number of email clicks EmailClicks float64 `json:"email_clicks" api:"required"` // Engagement likes data by percentage and time - EngagementLikes []PlatformPostMetricsTikTokBusinessMetricsDtoEngagementLike `json:"engagement_likes" api:"required"` + EngagementLikes []TiktokBusinessVideoMetricPercentage `json:"engagement_likes" api:"required"` // Number of favorites on the post Favorites float64 `json:"favorites" api:"required"` // Rate of full video watches as a percentage @@ -607,7 +702,7 @@ type PlatformPostMetricsTikTokBusinessMetricsDto struct { // Total time watched in seconds TotalTimeWatched float64 `json:"total_time_watched" api:"required"` // Video view retention data by percentage and time - VideoViewRetention []PlatformPostMetricsTikTokBusinessMetricsDtoVideoViewRetention `json:"video_view_retention" api:"required"` + VideoViewRetention []TiktokBusinessVideoMetricPercentage `json:"video_view_retention" api:"required"` // Total number of video views VideoViews float64 `json:"video_views" api:"required"` // Number of website clicks @@ -733,28 +828,6 @@ func (r *PlatformPostMetricsTikTokBusinessMetricsDtoAudienceType) UnmarshalJSON( return apijson.UnmarshalRoot(data, r) } -type PlatformPostMetricsTikTokBusinessMetricsDtoEngagementLike struct { - // Percentage value for the metric - Percentage float64 `json:"percentage" api:"required"` - // Time in seconds for the metric - Second string `json:"second" api:"required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Percentage respjson.Field - Second respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r PlatformPostMetricsTikTokBusinessMetricsDtoEngagementLike) RawJSON() string { - return r.JSON.raw -} -func (r *PlatformPostMetricsTikTokBusinessMetricsDtoEngagementLike) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - type PlatformPostMetricsTikTokBusinessMetricsDtoImpressionSource struct { // Name of the impression source ImpressionSource string `json:"impression_source" api:"required"` @@ -777,28 +850,6 @@ func (r *PlatformPostMetricsTikTokBusinessMetricsDtoImpressionSource) UnmarshalJ return apijson.UnmarshalRoot(data, r) } -type PlatformPostMetricsTikTokBusinessMetricsDtoVideoViewRetention struct { - // Percentage value for the metric - Percentage float64 `json:"percentage" api:"required"` - // Time in seconds for the metric - Second string `json:"second" api:"required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Percentage respjson.Field - Second respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r PlatformPostMetricsTikTokBusinessMetricsDtoVideoViewRetention) RawJSON() string { - return r.JSON.raw -} -func (r *PlatformPostMetricsTikTokBusinessMetricsDtoVideoViewRetention) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - type PlatformPostMetricsTikTokPostMetricsDto struct { // Number of comments on the video CommentCount float64 `json:"comment_count" api:"required"` @@ -981,22 +1032,14 @@ func (r *PlatformPostMetricsYouTubePostMetricsDto) UnmarshalJSON(data []byte) er type PlatformPostMetricsFacebookPostMetricsDto struct { // Total activity breakdown by action type - ActivityByActionType []PlatformPostMetricsFacebookPostMetricsDtoActivityByActionType `json:"activity_by_action_type"` + ActivityByActionType []FacebookActivityByActionType `json:"activity_by_action_type"` // Unique users activity breakdown by action type - ActivityByActionTypeUnique []PlatformPostMetricsFacebookPostMetricsDtoActivityByActionTypeUnique `json:"activity_by_action_type_unique"` + ActivityByActionTypeUnique []FacebookActivityByActionType `json:"activity_by_action_type_unique"` // Number of comments (from post object) Comments float64 `json:"comments"` - // Number of fans who saw the post - FanReach float64 `json:"fan_reach"` // Number of times the photo or video was viewed MediaViews float64 `json:"media_views"` - // Number of people who saw the post via non-viral distribution - NonviralReach float64 `json:"nonviral_reach"` - // Number of people who saw the post via organic distribution - OrganicReach float64 `json:"organic_reach"` - // Number of people who saw the post via paid distribution - PaidReach float64 `json:"paid_reach"` - // Total number of unique people who saw the post + // Total number of unique people who viewed the post media Reach float64 `json:"reach"` // Number of anger reactions ReactionsAnger float64 `json:"reactions_anger"` @@ -1029,21 +1072,21 @@ type PlatformPostMetricsFacebookPostMetricsDto struct { // Length of the video in milliseconds VideoLength float64 `json:"video_length"` // Video retention graph for autoplayed views - VideoRetentionGraphAutoplayed []PlatformPostMetricsFacebookPostMetricsDtoVideoRetentionGraphAutoplayed `json:"video_retention_graph_autoplayed"` + VideoRetentionGraphAutoplayed []FacebookVideoRetentionGraph `json:"video_retention_graph_autoplayed"` // Video retention graph for clicked-to-play views - VideoRetentionGraphClickedToPlay []PlatformPostMetricsFacebookPostMetricsDtoVideoRetentionGraphClickedToPlay `json:"video_retention_graph_clicked_to_play"` + VideoRetentionGraphClickedToPlay []FacebookVideoRetentionGraph `json:"video_retention_graph_clicked_to_play"` // Number of unique people who performed social actions on the video VideoSocialActionsUnique float64 `json:"video_social_actions_unique"` // Total time video was viewed in milliseconds VideoViewTime float64 `json:"video_view_time"` // Video view time breakdown by age and gender - VideoViewTimeByAgeGender []PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByAgeGender `json:"video_view_time_by_age_gender"` + VideoViewTimeByAgeGender []FacebookVideoViewTimeByDemographic `json:"video_view_time_by_age_gender"` // Video view time breakdown by country - VideoViewTimeByCountry []PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByCountry `json:"video_view_time_by_country"` + VideoViewTimeByCountry []FacebookVideoViewTimeByDemographic `json:"video_view_time_by_country"` // Video view time breakdown by distribution type VideoViewTimeByDistributionType any `json:"video_view_time_by_distribution_type"` // Video view time breakdown by region - VideoViewTimeByRegion []PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByRegion `json:"video_view_time_by_region"` + VideoViewTimeByRegion []FacebookVideoViewTimeByDemographic `json:"video_view_time_by_region"` // Total time video was viewed in milliseconds via organic distribution VideoViewTimeOrganic float64 `json:"video_view_time_organic"` // Number of times video was viewed for 3+ seconds @@ -1061,29 +1104,16 @@ type PlatformPostMetricsFacebookPostMetricsDto struct { VideoViewsClickedToPlay float64 `json:"video_views_clicked_to_play"` // Number of times video was viewed for 3+ seconds organically VideoViewsOrganic float64 `json:"video_views_organic"` - // Number of unique people who viewed the video for 3+ seconds organically - VideoViewsOrganicUnique float64 `json:"video_views_organic_unique"` // Number of times video was viewed for 3+ seconds via paid distribution VideoViewsPaid float64 `json:"video_views_paid"` - // Number of unique people who viewed the video for 3+ seconds via paid - // distribution - VideoViewsPaidUnique float64 `json:"video_views_paid_unique"` // Number of times video was viewed with sound on VideoViewsSoundOn float64 `json:"video_views_sound_on"` - // Number of unique people who viewed the video for 3+ seconds - VideoViewsUnique float64 `json:"video_views_unique"` - // Number of people who saw the post in News Feed via viral reach - ViralReach float64 `json:"viral_reach"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { ActivityByActionType respjson.Field ActivityByActionTypeUnique respjson.Field Comments respjson.Field - FanReach respjson.Field MediaViews respjson.Field - NonviralReach respjson.Field - OrganicReach respjson.Field - PaidReach respjson.Field Reach respjson.Field ReactionsAnger respjson.Field ReactionsByType respjson.Field @@ -1116,12 +1146,8 @@ type PlatformPostMetricsFacebookPostMetricsDto struct { VideoViewsByDistributionType respjson.Field VideoViewsClickedToPlay respjson.Field VideoViewsOrganic respjson.Field - VideoViewsOrganicUnique respjson.Field VideoViewsPaid respjson.Field - VideoViewsPaidUnique respjson.Field VideoViewsSoundOn respjson.Field - VideoViewsUnique respjson.Field - ViralReach respjson.Field ExtraFields map[string]respjson.Field raw string } `json:"-"` @@ -1133,160 +1159,6 @@ func (r *PlatformPostMetricsFacebookPostMetricsDto) UnmarshalJSON(data []byte) e return apijson.UnmarshalRoot(data, r) } -type PlatformPostMetricsFacebookPostMetricsDtoActivityByActionType struct { - // Action type (e.g., like, comment, share) - ActionType string `json:"action_type" api:"required"` - // Number of actions - Value float64 `json:"value" api:"required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ActionType respjson.Field - Value respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r PlatformPostMetricsFacebookPostMetricsDtoActivityByActionType) RawJSON() string { - return r.JSON.raw -} -func (r *PlatformPostMetricsFacebookPostMetricsDtoActivityByActionType) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type PlatformPostMetricsFacebookPostMetricsDtoActivityByActionTypeUnique struct { - // Action type (e.g., like, comment, share) - ActionType string `json:"action_type" api:"required"` - // Number of actions - Value float64 `json:"value" api:"required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ActionType respjson.Field - Value respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r PlatformPostMetricsFacebookPostMetricsDtoActivityByActionTypeUnique) RawJSON() string { - return r.JSON.raw -} -func (r *PlatformPostMetricsFacebookPostMetricsDtoActivityByActionTypeUnique) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type PlatformPostMetricsFacebookPostMetricsDtoVideoRetentionGraphAutoplayed struct { - // Percentage of viewers at this time - Rate float64 `json:"rate" api:"required"` - // Time in seconds - Time float64 `json:"time" api:"required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Rate respjson.Field - Time respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r PlatformPostMetricsFacebookPostMetricsDtoVideoRetentionGraphAutoplayed) RawJSON() string { - return r.JSON.raw -} -func (r *PlatformPostMetricsFacebookPostMetricsDtoVideoRetentionGraphAutoplayed) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type PlatformPostMetricsFacebookPostMetricsDtoVideoRetentionGraphClickedToPlay struct { - // Percentage of viewers at this time - Rate float64 `json:"rate" api:"required"` - // Time in seconds - Time float64 `json:"time" api:"required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Rate respjson.Field - Time respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r PlatformPostMetricsFacebookPostMetricsDtoVideoRetentionGraphClickedToPlay) RawJSON() string { - return r.JSON.raw -} -func (r *PlatformPostMetricsFacebookPostMetricsDtoVideoRetentionGraphClickedToPlay) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByAgeGender struct { - // Demographic key (e.g., age_gender, region, country) - Key string `json:"key" api:"required"` - // Total view time in milliseconds - Value float64 `json:"value" api:"required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Key respjson.Field - Value respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByAgeGender) RawJSON() string { - return r.JSON.raw -} -func (r *PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByAgeGender) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByCountry struct { - // Demographic key (e.g., age_gender, region, country) - Key string `json:"key" api:"required"` - // Total view time in milliseconds - Value float64 `json:"value" api:"required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Key respjson.Field - Value respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByCountry) RawJSON() string { - return r.JSON.raw -} -func (r *PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByCountry) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByRegion struct { - // Demographic key (e.g., age_gender, region, country) - Key string `json:"key" api:"required"` - // Total view time in milliseconds - Value float64 `json:"value" api:"required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Key respjson.Field - Value respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByRegion) RawJSON() string { - return r.JSON.raw -} -func (r *PlatformPostMetricsFacebookPostMetricsDtoVideoViewTimeByRegion) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - type PlatformPostMetricsTwitterPostMetricsDto struct { // Non-public metrics for the Tweet (available to the Tweet owner or advertisers) NonPublicMetrics PlatformPostMetricsTwitterPostMetricsDtoNonPublicMetrics `json:"non_public_metrics"` @@ -1514,9 +1386,9 @@ func (r *PlatformPostMetricsBlueskyPostMetricsDto) UnmarshalJSON(data []byte) er type PlatformPostMetricsPinterestPostMetricsDto struct { // Last 90 days of Pin metrics - Number90d PlatformPostMetricsPinterestPostMetricsDto90d `json:"90d"` + Number90d PinterestMetricsWindow `json:"90d"` // Lifetime Pin metrics - LifetimeMetrics PlatformPostMetricsPinterestPostMetricsDtoLifetimeMetrics `json:"lifetime_metrics"` + LifetimeMetrics PinterestMetricsWindow `json:"lifetime_metrics"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { Number90d respjson.Field @@ -1532,124 +1404,27 @@ func (r *PlatformPostMetricsPinterestPostMetricsDto) UnmarshalJSON(data []byte) return apijson.UnmarshalRoot(data, r) } -// Last 90 days of Pin metrics -type PlatformPostMetricsPinterestPostMetricsDto90d struct { - // Number of comments on the Pin - Comment float64 `json:"comment"` - // Number of times the Pin was shown (impressions) - Impression float64 `json:"impression"` - // The last time Pinterest updated these metrics - LastUpdated string `json:"last_updated"` - // Number of clicks from the Pin to an external destination (outbound clicks) - OutboundClick float64 `json:"outbound_click"` - // Number of clicks on the Pin to view it in closeup (Pin clicks) - PinClick float64 `json:"pin_click"` - // Number of visits to the author's profile driven from the Pin - ProfileVisit any `json:"profile_visit" api:"nullable"` - // Total number of reactions on the Pin - Reaction float64 `json:"reaction"` - // Number of saves of the Pin - Save float64 `json:"save"` - // Number of follows driven from the Pin - UserFollow any `json:"user_follow" api:"nullable"` - // Number of video views of at least 10 seconds - Video10sViews float64 `json:"video_10s_views"` - // Average watch time for the video - VideoAverageTime float64 `json:"video_average_time"` - // Number of video views that reached 95% completion - VideoP95Views float64 `json:"video_p95_views"` - // Total watch time for the video - VideoTotalTime float64 `json:"video_total_time"` - // Number of video views - VideoViews float64 `json:"video_views"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Comment respjson.Field - Impression respjson.Field - LastUpdated respjson.Field - OutboundClick respjson.Field - PinClick respjson.Field - ProfileVisit respjson.Field - Reaction respjson.Field - Save respjson.Field - UserFollow respjson.Field - Video10sViews respjson.Field - VideoAverageTime respjson.Field - VideoP95Views respjson.Field - VideoTotalTime respjson.Field - VideoViews respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r PlatformPostMetricsPinterestPostMetricsDto90d) RawJSON() string { return r.JSON.raw } -func (r *PlatformPostMetricsPinterestPostMetricsDto90d) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// Lifetime Pin metrics -type PlatformPostMetricsPinterestPostMetricsDtoLifetimeMetrics struct { - // Number of comments on the Pin - Comment float64 `json:"comment"` - // Number of times the Pin was shown (impressions) - Impression float64 `json:"impression"` - // The last time Pinterest updated these metrics - LastUpdated string `json:"last_updated"` - // Number of clicks from the Pin to an external destination (outbound clicks) - OutboundClick float64 `json:"outbound_click"` - // Number of clicks on the Pin to view it in closeup (Pin clicks) - PinClick float64 `json:"pin_click"` - // Number of visits to the author's profile driven from the Pin - ProfileVisit any `json:"profile_visit" api:"nullable"` - // Total number of reactions on the Pin - Reaction float64 `json:"reaction"` - // Number of saves of the Pin - Save float64 `json:"save"` - // Number of follows driven from the Pin - UserFollow any `json:"user_follow" api:"nullable"` - // Number of video views of at least 10 seconds - Video10sViews float64 `json:"video_10s_views"` - // Average watch time for the video - VideoAverageTime float64 `json:"video_average_time"` - // Number of video views that reached 95% completion - VideoP95Views float64 `json:"video_p95_views"` - // Total watch time for the video - VideoTotalTime float64 `json:"video_total_time"` - // Number of video views - VideoViews float64 `json:"video_views"` +type TiktokBusinessVideoMetricPercentage struct { + // Percentage value for the metric + Percentage float64 `json:"percentage" api:"required"` + // Time in seconds for the metric + Second string `json:"second" api:"required"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - Comment respjson.Field - Impression respjson.Field - LastUpdated respjson.Field - OutboundClick respjson.Field - PinClick respjson.Field - ProfileVisit respjson.Field - Reaction respjson.Field - Save respjson.Field - UserFollow respjson.Field - Video10sViews respjson.Field - VideoAverageTime respjson.Field - VideoP95Views respjson.Field - VideoTotalTime respjson.Field - VideoViews respjson.Field - ExtraFields map[string]respjson.Field - raw string + Percentage respjson.Field + Second respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } // Returns the unmodified JSON received from the API -func (r PlatformPostMetricsPinterestPostMetricsDtoLifetimeMetrics) RawJSON() string { - return r.JSON.raw -} -func (r *PlatformPostMetricsPinterestPostMetricsDtoLifetimeMetrics) UnmarshalJSON(data []byte) error { +func (r TiktokBusinessVideoMetricPercentage) RawJSON() string { return r.JSON.raw } +func (r *TiktokBusinessVideoMetricPercentage) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// Platform-specific data for the post -type PlatformPostPlatformData struct { +type YoutubePostPlatformData struct { // Title of the post Title string `json:"title" api:"required"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. @@ -1661,8 +1436,8 @@ type PlatformPostPlatformData struct { } // Returns the unmodified JSON received from the API -func (r PlatformPostPlatformData) RawJSON() string { return r.JSON.raw } -func (r *PlatformPostPlatformData) UnmarshalJSON(data []byte) error { +func (r YoutubePostPlatformData) RawJSON() string { return r.JSON.raw } +func (r *YoutubePostPlatformData) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } diff --git a/socialpost.go b/socialpost.go index 9714d3c..34f2749 100644 --- a/socialpost.go +++ b/socialpost.go @@ -93,7 +93,7 @@ func (r *SocialPostService) List(ctx context.Context, query SocialPostListParams } // Delete Post -func (r *SocialPostService) Delete(ctx context.Context, id string, opts ...option.RequestOption) (res *SocialPostDeleteResponse, err error) { +func (r *SocialPostService) Delete(ctx context.Context, id string, opts ...option.RequestOption) (res *DeleteEntityResponse, err error) { opts = slices.Concat(r.Options, opts) if id == "" { err = errors.New("missing required id parameter") @@ -104,244 +104,265 @@ func (r *SocialPostService) Delete(ctx context.Context, id string, opts ...optio return res, err } -type BlueskyConfigurationDto struct { - // Overrides the `caption` from the post - Caption any `json:"caption" api:"nullable"` - // Overrides the `media` from the post - Media []BlueskyConfigurationDtoMedia `json:"media" api:"nullable"` +type AccountConfiguration struct { + // Configuration for the social account + Configuration AccountConfigurationConfiguration `json:"configuration" api:"required"` + // ID of the social account, you want to apply the configuration to + SocialAccountID string `json:"social_account_id" api:"required"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - Caption respjson.Field - Media respjson.Field - ExtraFields map[string]respjson.Field - raw string + Configuration respjson.Field + SocialAccountID respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } // Returns the unmodified JSON received from the API -func (r BlueskyConfigurationDto) RawJSON() string { return r.JSON.raw } -func (r *BlueskyConfigurationDto) UnmarshalJSON(data []byte) error { +func (r AccountConfiguration) RawJSON() string { return r.JSON.raw } +func (r *AccountConfiguration) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// ToParam converts this BlueskyConfigurationDto to a BlueskyConfigurationDtoParam. +// ToParam converts this AccountConfiguration to a AccountConfigurationParam. // // Warning: the fields of the param type will not be present. ToParam should only // be used at the last possible moment before sending a request. Test for this with -// BlueskyConfigurationDtoParam.Overrides() -func (r BlueskyConfigurationDto) ToParam() BlueskyConfigurationDtoParam { - return param.Override[BlueskyConfigurationDtoParam](json.RawMessage(r.RawJSON())) +// AccountConfigurationParam.Overrides() +func (r AccountConfiguration) ToParam() AccountConfigurationParam { + return param.Override[AccountConfigurationParam](json.RawMessage(r.RawJSON())) } -type BlueskyConfigurationDtoMedia struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing bool `json:"skip_processing" api:"nullable"` - // List of tags to attach to the media - Tags []BlueskyConfigurationDtoMediaTag `json:"tags" api:"nullable"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms" api:"nullable"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url" api:"nullable"` +// Configuration for the social account +type AccountConfigurationConfiguration struct { + // Per-language localizations for the video title and description. Keys are BCP-47 + // language tags (e.g. "fr", "es"). Maps to localizations on the YouTube Data API + // videos resource. + Localizations map[string]AccountConfigurationConfigurationLocalization `json:"localizations" api:"required"` + // Allow comments on TikTok + AllowComment bool `json:"allow_comment" api:"nullable"` + // Allow duets on TikTok + AllowDuet bool `json:"allow_duet" api:"nullable"` + // Allow stitch on TikTok + AllowStitch bool `json:"allow_stitch" api:"nullable"` + // Display name for the audio track on Instagram Reels. Only honored on Reels + // uploads, and only when the audio is original (Meta silently ignores it on + // licensed/fingerprinted tracks). + AudioName string `json:"audio_name" api:"nullable"` + // Will automatically add music to photo posts on TikTok + AutoAddMusic bool `json:"auto_add_music" api:"nullable"` + // Pinterest board IDs + BoardIDs []string `json:"board_ids" api:"nullable"` + // Overrides the `caption` from the post + Caption any `json:"caption" api:"nullable"` + // YouTube video category id (maps to snippet.categoryId; see YouTube Data API + // videoCategories.list) + CategoryID string `json:"category_id" api:"nullable"` + // List of page ids or users to invite as collaborators for a Video Reel (Instagram + // and Facebook) + Collaborators [][]any `json:"collaborators" api:"nullable"` + // Id of the twitter community to post to + CommunityID string `json:"community_id"` + // If true, marks the YouTube video as containing altered or synthetic content per + // YouTube's disclosure policy. Sets status.containsSyntheticMedia on the + // videos.insert call; YouTube adds a "How this content was made" label to the + // description automatically. + ContainsSyntheticMedia bool `json:"contains_synthetic_media" api:"nullable"` + // Default language of the video (BCP-47 language tag, e.g. "en"). Maps to + // snippet.defaultLanguage. + DefaultLanguage string `json:"default_language" api:"nullable"` + // Disclose branded content on TikTok + DiscloseBrandedContent bool `json:"disclose_branded_content" api:"nullable"` + // Disclose your brand on TikTok + DiscloseYourBrand bool `json:"disclose_your_brand" api:"nullable"` + // If true the video can be embedded on other websites (maps to status.embeddable). + // Defaults to true. + Embeddable bool `json:"embeddable" api:"nullable"` + // Flag content as AI generated on TikTok + IsAIGenerated bool `json:"is_ai_generated" api:"nullable"` + // Will create a draft upload to TikTok, posting will need to be completed from + // within the app + IsDraft bool `json:"is_draft" api:"nullable"` + // The video's license (maps to status.license). "youtube" is the standard YouTube + // license; "creativeCommon" is Creative Commons. + // + // Any of "youtube", "creativeCommon". + License string `json:"license" api:"nullable"` + // Pinterest post link + Link string `json:"link" api:"nullable"` + // Page id with a location that you want to tag the image or video with (Instagram + // and Facebook) + Location string `json:"location" api:"nullable"` + // If true will notify YouTube the video is intended for kids, defaults to false + MadeForKids bool `json:"made_for_kids" api:"nullable"` + // Overrides the `media` from the post + Media []SocialPostMedia `json:"media" api:"nullable"` + // Post placement for Facebook/Instagram/Threads + // + // Any of "reels", "timeline", "stories". + Placement string `json:"placement" api:"nullable"` + // Poll options for the twitter + Poll TwitterPoll `json:"poll"` + // Sets the privacy status for TikTok (private, public), or YouTube (private, + // public, unlisted) + // + // Any of "public", "private", "unlisted". + PrivacyStatus string `json:"privacy_status" api:"nullable"` + // If true, the extended video statistics are publicly viewable (maps to + // status.publicStatsViewable). Defaults to true. + PublicStatsViewable bool `json:"public_stats_viewable" api:"nullable"` + // ISO 8601 datetime at which the video should be published. Only honoured when + // privacy_status is "private" (maps to status.publishAt). + PublishAt string `json:"publish_at" api:"nullable"` + // Id of the tweet you want to quote + QuoteTweetID string `json:"quote_tweet_id"` + // ISO 8601 date (YYYY-MM-DD) or datetime when the video was recorded (maps to + // recordingDetails.recordingDate). + RecordingDate string `json:"recording_date" api:"nullable"` + // Who can reply to the tweet + // + // Any of "following", "mentionedUsers", "subscribers", "verified". + ReplySettings string `json:"reply_settings" api:"nullable"` + // LinkedIn UGC post id to reshare. The caption is used as the reshare commentary. + ResharePostID string `json:"reshare_post_id" api:"nullable"` + // If true, include the caption on each image in a Facebook carousel upload; if + // false, only include it on the final carousel post + SetCaptionForEachImage bool `json:"set_caption_for_each_image" api:"nullable"` + // If false Instagram video posts will only be shown in the Reels tab + ShareToFeed bool `json:"share_to_feed" api:"nullable"` + // YouTube video tags + Tags []string `json:"tags" api:"nullable"` + // Overrides the `title` from the post (Pinterest, TikTok, YouTube) + Title string `json:"title" api:"nullable"` + // Instagram trial reel type, when passed will be created as a trial reel. If + // manual the trial reel can be manually graduated in the native app. If perfomance + // the trial reel will be automatically graduated if the trial reel performs well. + // + // Any of "manual", "performance". + TrialReelType string `json:"trial_reel_type" api:"nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - URL respjson.Field - SkipProcessing respjson.Field - Tags respjson.Field - ThumbnailTimestampMs respjson.Field - ThumbnailURL respjson.Field - ExtraFields map[string]respjson.Field - raw string + Localizations respjson.Field + AllowComment respjson.Field + AllowDuet respjson.Field + AllowStitch respjson.Field + AudioName respjson.Field + AutoAddMusic respjson.Field + BoardIDs respjson.Field + Caption respjson.Field + CategoryID respjson.Field + Collaborators respjson.Field + CommunityID respjson.Field + ContainsSyntheticMedia respjson.Field + DefaultLanguage respjson.Field + DiscloseBrandedContent respjson.Field + DiscloseYourBrand respjson.Field + Embeddable respjson.Field + IsAIGenerated respjson.Field + IsDraft respjson.Field + License respjson.Field + Link respjson.Field + Location respjson.Field + MadeForKids respjson.Field + Media respjson.Field + Placement respjson.Field + Poll respjson.Field + PrivacyStatus respjson.Field + PublicStatsViewable respjson.Field + PublishAt respjson.Field + QuoteTweetID respjson.Field + RecordingDate respjson.Field + ReplySettings respjson.Field + ResharePostID respjson.Field + SetCaptionForEachImage respjson.Field + ShareToFeed respjson.Field + Tags respjson.Field + Title respjson.Field + TrialReelType respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } // Returns the unmodified JSON received from the API -func (r BlueskyConfigurationDtoMedia) RawJSON() string { return r.JSON.raw } -func (r *BlueskyConfigurationDtoMedia) UnmarshalJSON(data []byte) error { +func (r AccountConfigurationConfiguration) RawJSON() string { return r.JSON.raw } +func (r *AccountConfigurationConfiguration) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type BlueskyConfigurationDtoMediaTag struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X float64 `json:"x"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y float64 `json:"y"` +type AccountConfigurationConfigurationLocalization struct { + Description string `json:"description" api:"nullable"` + Title string `json:"title" api:"nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - ID respjson.Field - Platform respjson.Field - Type respjson.Field - X respjson.Field - Y respjson.Field + Description respjson.Field + Title respjson.Field ExtraFields map[string]respjson.Field raw string } `json:"-"` } // Returns the unmodified JSON received from the API -func (r BlueskyConfigurationDtoMediaTag) RawJSON() string { return r.JSON.raw } -func (r *BlueskyConfigurationDtoMediaTag) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type BlueskyConfigurationDtoParam struct { - // Overrides the `caption` from the post - Caption any `json:"caption,omitzero"` - // Overrides the `media` from the post - Media []BlueskyConfigurationDtoMediaParam `json:"media,omitzero"` - paramObj -} - -func (r BlueskyConfigurationDtoParam) MarshalJSON() (data []byte, err error) { - type shadow BlueskyConfigurationDtoParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *BlueskyConfigurationDtoParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// The property URL is required. -type BlueskyConfigurationDtoMediaParam struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing param.Opt[bool] `json:"skip_processing,omitzero"` - // List of tags to attach to the media - Tags []BlueskyConfigurationDtoMediaTagParam `json:"tags,omitzero"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms,omitzero"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url,omitzero"` - paramObj -} - -func (r BlueskyConfigurationDtoMediaParam) MarshalJSON() (data []byte, err error) { - type shadow BlueskyConfigurationDtoMediaParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *BlueskyConfigurationDtoMediaParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// The properties ID, Platform, Type are required. -type BlueskyConfigurationDtoMediaTagParam struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform,omitzero" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type,omitzero" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X param.Opt[float64] `json:"x,omitzero"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y param.Opt[float64] `json:"y,omitzero"` - paramObj -} - -func (r BlueskyConfigurationDtoMediaTagParam) MarshalJSON() (data []byte, err error) { - type shadow BlueskyConfigurationDtoMediaTagParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *BlueskyConfigurationDtoMediaTagParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -func init() { - apijson.RegisterFieldValidator[BlueskyConfigurationDtoMediaTagParam]( - "platform", "facebook", "instagram", - ) - apijson.RegisterFieldValidator[BlueskyConfigurationDtoMediaTagParam]( - "type", "user", "product", - ) -} - -// The properties Caption, SocialAccounts are required. -type CreateSocialPostParam struct { - // Caption text for the post - Caption string `json:"caption" api:"required"` - // Array of social account IDs for posting - SocialAccounts []string `json:"social_accounts,omitzero" api:"required"` - // Array of social account IDs for posting - ExternalID param.Opt[string] `json:"external_id,omitzero"` - // If isDraft is set then the post will not be processed - IsDraft param.Opt[bool] `json:"isDraft,omitzero"` - // Scheduled date and time for the post, setting to null or undefined will post - // instantly - ScheduledAt param.Opt[time.Time] `json:"scheduled_at,omitzero" format:"date-time"` - // Account-specific configurations for the post - AccountConfigurations []CreateSocialPostAccountConfigurationParam `json:"account_configurations,omitzero"` - // Array of media associated with the post. If multiple media items are provided - // and the placement is `stories`, individual posts are created per media item. - Media []CreateSocialPostMediaParam `json:"media,omitzero"` - // Platform-specific configurations for the post - PlatformConfigurations PlatformConfigurationsDtoParam `json:"platform_configurations,omitzero"` - paramObj -} - -func (r CreateSocialPostParam) MarshalJSON() (data []byte, err error) { - type shadow CreateSocialPostParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *CreateSocialPostParam) UnmarshalJSON(data []byte) error { +func (r AccountConfigurationConfigurationLocalization) RawJSON() string { return r.JSON.raw } +func (r *AccountConfigurationConfigurationLocalization) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } // The properties Configuration, SocialAccountID are required. -type CreateSocialPostAccountConfigurationParam struct { +type AccountConfigurationParam struct { // Configuration for the social account - Configuration CreateSocialPostAccountConfigurationConfigurationParam `json:"configuration,omitzero" api:"required"` + Configuration AccountConfigurationConfigurationParam `json:"configuration,omitzero" api:"required"` // ID of the social account, you want to apply the configuration to SocialAccountID string `json:"social_account_id" api:"required"` paramObj } -func (r CreateSocialPostAccountConfigurationParam) MarshalJSON() (data []byte, err error) { - type shadow CreateSocialPostAccountConfigurationParam +func (r AccountConfigurationParam) MarshalJSON() (data []byte, err error) { + type shadow AccountConfigurationParam return param.MarshalObject(r, (*shadow)(&r)) } -func (r *CreateSocialPostAccountConfigurationParam) UnmarshalJSON(data []byte) error { +func (r *AccountConfigurationParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } // Configuration for the social account -type CreateSocialPostAccountConfigurationConfigurationParam struct { +// +// The property Localizations is required. +type AccountConfigurationConfigurationParam struct { + // Per-language localizations for the video title and description. Keys are BCP-47 + // language tags (e.g. "fr", "es"). Maps to localizations on the YouTube Data API + // videos resource. + Localizations map[string]AccountConfigurationConfigurationLocalizationParam `json:"localizations,omitzero" api:"required"` // Allow comments on TikTok AllowComment param.Opt[bool] `json:"allow_comment,omitzero"` // Allow duets on TikTok AllowDuet param.Opt[bool] `json:"allow_duet,omitzero"` // Allow stitch on TikTok AllowStitch param.Opt[bool] `json:"allow_stitch,omitzero"` + // Display name for the audio track on Instagram Reels. Only honored on Reels + // uploads, and only when the audio is original (Meta silently ignores it on + // licensed/fingerprinted tracks). + AudioName param.Opt[string] `json:"audio_name,omitzero"` // Will automatically add music to photo posts on TikTok AutoAddMusic param.Opt[bool] `json:"auto_add_music,omitzero"` + // YouTube video category id (maps to snippet.categoryId; see YouTube Data API + // videoCategories.list) + CategoryID param.Opt[string] `json:"category_id,omitzero"` + // If true, marks the YouTube video as containing altered or synthetic content per + // YouTube's disclosure policy. Sets status.containsSyntheticMedia on the + // videos.insert call; YouTube adds a "How this content was made" label to the + // description automatically. + ContainsSyntheticMedia param.Opt[bool] `json:"contains_synthetic_media,omitzero"` + // Default language of the video (BCP-47 language tag, e.g. "en"). Maps to + // snippet.defaultLanguage. + DefaultLanguage param.Opt[string] `json:"default_language,omitzero"` // Disclose branded content on TikTok DiscloseBrandedContent param.Opt[bool] `json:"disclose_branded_content,omitzero"` // Disclose your brand on TikTok DiscloseYourBrand param.Opt[bool] `json:"disclose_your_brand,omitzero"` + // If true the video can be embedded on other websites (maps to status.embeddable). + // Defaults to true. + Embeddable param.Opt[bool] `json:"embeddable,omitzero"` // Flag content as AI generated on TikTok IsAIGenerated param.Opt[bool] `json:"is_ai_generated,omitzero"` // Will create a draft upload to TikTok, posting will need to be completed from @@ -354,12 +375,23 @@ type CreateSocialPostAccountConfigurationConfigurationParam struct { Location param.Opt[string] `json:"location,omitzero"` // If true will notify YouTube the video is intended for kids, defaults to false MadeForKids param.Opt[bool] `json:"made_for_kids,omitzero"` + // If true, the extended video statistics are publicly viewable (maps to + // status.publicStatsViewable). Defaults to true. + PublicStatsViewable param.Opt[bool] `json:"public_stats_viewable,omitzero"` + // ISO 8601 datetime at which the video should be published. Only honoured when + // privacy_status is "private" (maps to status.publishAt). + PublishAt param.Opt[string] `json:"publish_at,omitzero"` + // ISO 8601 date (YYYY-MM-DD) or datetime when the video was recorded (maps to + // recordingDetails.recordingDate). + RecordingDate param.Opt[string] `json:"recording_date,omitzero"` + // LinkedIn UGC post id to reshare. The caption is used as the reshare commentary. + ResharePostID param.Opt[string] `json:"reshare_post_id,omitzero"` // If true, include the caption on each image in a Facebook carousel upload; if // false, only include it on the final carousel post SetCaptionForEachImage param.Opt[bool] `json:"set_caption_for_each_image,omitzero"` // If false Instagram video posts will only be shown in the Reels tab ShareToFeed param.Opt[bool] `json:"share_to_feed,omitzero"` - // Overrides the `title` from the post + // Overrides the `title` from the post (Pinterest, TikTok, YouTube) Title param.Opt[string] `json:"title,omitzero"` // Id of the twitter community to post to CommunityID param.Opt[string] `json:"community_id,omitzero"` @@ -372,8 +404,13 @@ type CreateSocialPostAccountConfigurationConfigurationParam struct { // List of page ids or users to invite as collaborators for a Video Reel (Instagram // and Facebook) Collaborators [][]any `json:"collaborators,omitzero"` + // The video's license (maps to status.license). "youtube" is the standard YouTube + // license; "creativeCommon" is Creative Commons. + // + // Any of "youtube", "creativeCommon". + License string `json:"license,omitzero"` // Overrides the `media` from the post - Media []CreateSocialPostAccountConfigurationConfigurationMediaParam `json:"media,omitzero"` + Media []SocialPostMediaParam `json:"media,omitzero"` // Post placement for Facebook/Instagram/Threads // // Any of "reels", "timeline", "stories". @@ -387,6 +424,8 @@ type CreateSocialPostAccountConfigurationConfigurationParam struct { // // Any of "following", "mentionedUsers", "subscribers", "verified". ReplySettings string `json:"reply_settings,omitzero"` + // YouTube video tags + Tags []string `json:"tags,omitzero"` // Instagram trial reel type, when passed will be created as a trial reel. If // manual the trial reel can be manually graduated in the native app. If perfomance // the trial reel will be automatically graduated if the trial reel performs well. @@ -394,190 +433,143 @@ type CreateSocialPostAccountConfigurationConfigurationParam struct { // Any of "manual", "performance". TrialReelType string `json:"trial_reel_type,omitzero"` // Poll options for the twitter - Poll CreateSocialPostAccountConfigurationConfigurationPollParam `json:"poll,omitzero"` + Poll TwitterPollParam `json:"poll,omitzero"` paramObj } -func (r CreateSocialPostAccountConfigurationConfigurationParam) MarshalJSON() (data []byte, err error) { - type shadow CreateSocialPostAccountConfigurationConfigurationParam +func (r AccountConfigurationConfigurationParam) MarshalJSON() (data []byte, err error) { + type shadow AccountConfigurationConfigurationParam return param.MarshalObject(r, (*shadow)(&r)) } -func (r *CreateSocialPostAccountConfigurationConfigurationParam) UnmarshalJSON(data []byte) error { +func (r *AccountConfigurationConfigurationParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } func init() { - apijson.RegisterFieldValidator[CreateSocialPostAccountConfigurationConfigurationParam]( + apijson.RegisterFieldValidator[AccountConfigurationConfigurationParam]( + "license", "youtube", "creativeCommon", + ) + apijson.RegisterFieldValidator[AccountConfigurationConfigurationParam]( "placement", "reels", "timeline", "stories", ) - apijson.RegisterFieldValidator[CreateSocialPostAccountConfigurationConfigurationParam]( + apijson.RegisterFieldValidator[AccountConfigurationConfigurationParam]( "privacy_status", "public", "private", "unlisted", ) - apijson.RegisterFieldValidator[CreateSocialPostAccountConfigurationConfigurationParam]( + apijson.RegisterFieldValidator[AccountConfigurationConfigurationParam]( "reply_settings", "following", "mentionedUsers", "subscribers", "verified", ) - apijson.RegisterFieldValidator[CreateSocialPostAccountConfigurationConfigurationParam]( + apijson.RegisterFieldValidator[AccountConfigurationConfigurationParam]( "trial_reel_type", "manual", "performance", ) } -// The property URL is required. -type CreateSocialPostAccountConfigurationConfigurationMediaParam struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing param.Opt[bool] `json:"skip_processing,omitzero"` - // List of tags to attach to the media - Tags []CreateSocialPostAccountConfigurationConfigurationMediaTagParam `json:"tags,omitzero"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms,omitzero"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url,omitzero"` +type AccountConfigurationConfigurationLocalizationParam struct { + Description param.Opt[string] `json:"description,omitzero"` + Title param.Opt[string] `json:"title,omitzero"` paramObj } -func (r CreateSocialPostAccountConfigurationConfigurationMediaParam) MarshalJSON() (data []byte, err error) { - type shadow CreateSocialPostAccountConfigurationConfigurationMediaParam +func (r AccountConfigurationConfigurationLocalizationParam) MarshalJSON() (data []byte, err error) { + type shadow AccountConfigurationConfigurationLocalizationParam return param.MarshalObject(r, (*shadow)(&r)) } -func (r *CreateSocialPostAccountConfigurationConfigurationMediaParam) UnmarshalJSON(data []byte) error { +func (r *AccountConfigurationConfigurationLocalizationParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// The properties ID, Platform, Type are required. -type CreateSocialPostAccountConfigurationConfigurationMediaTagParam struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform,omitzero" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type,omitzero" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X param.Opt[float64] `json:"x,omitzero"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y param.Opt[float64] `json:"y,omitzero"` - paramObj +type BlueskyConfigurationDto struct { + // Overrides the `caption` from the post + Caption any `json:"caption" api:"nullable"` + // Overrides the `media` from the post + Media []SocialPostMedia `json:"media" api:"nullable"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Caption respjson.Field + Media respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` } -func (r CreateSocialPostAccountConfigurationConfigurationMediaTagParam) MarshalJSON() (data []byte, err error) { - type shadow CreateSocialPostAccountConfigurationConfigurationMediaTagParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *CreateSocialPostAccountConfigurationConfigurationMediaTagParam) UnmarshalJSON(data []byte) error { +// Returns the unmodified JSON received from the API +func (r BlueskyConfigurationDto) RawJSON() string { return r.JSON.raw } +func (r *BlueskyConfigurationDto) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -func init() { - apijson.RegisterFieldValidator[CreateSocialPostAccountConfigurationConfigurationMediaTagParam]( - "platform", "facebook", "instagram", - ) - apijson.RegisterFieldValidator[CreateSocialPostAccountConfigurationConfigurationMediaTagParam]( - "type", "user", "product", - ) +// ToParam converts this BlueskyConfigurationDto to a BlueskyConfigurationDtoParam. +// +// Warning: the fields of the param type will not be present. ToParam should only +// be used at the last possible moment before sending a request. Test for this with +// BlueskyConfigurationDtoParam.Overrides() +func (r BlueskyConfigurationDto) ToParam() BlueskyConfigurationDtoParam { + return param.Override[BlueskyConfigurationDtoParam](json.RawMessage(r.RawJSON())) } -// Poll options for the twitter -// -// The properties DurationMinutes, Options are required. -type CreateSocialPostAccountConfigurationConfigurationPollParam struct { - // Duration of the poll in minutes - DurationMinutes float64 `json:"duration_minutes" api:"required"` - // The choices of the poll, requiring 2-4 options - Options []string `json:"options,omitzero" api:"required"` - // Who can reply to the tweet - // - // Any of "following", "mentionedUsers", "subscribers", "verified". - ReplySettings string `json:"reply_settings,omitzero"` +type BlueskyConfigurationDtoParam struct { + // Overrides the `caption` from the post + Caption any `json:"caption,omitzero"` + // Overrides the `media` from the post + Media []SocialPostMediaParam `json:"media,omitzero"` paramObj } -func (r CreateSocialPostAccountConfigurationConfigurationPollParam) MarshalJSON() (data []byte, err error) { - type shadow CreateSocialPostAccountConfigurationConfigurationPollParam +func (r BlueskyConfigurationDtoParam) MarshalJSON() (data []byte, err error) { + type shadow BlueskyConfigurationDtoParam return param.MarshalObject(r, (*shadow)(&r)) } -func (r *CreateSocialPostAccountConfigurationConfigurationPollParam) UnmarshalJSON(data []byte) error { +func (r *BlueskyConfigurationDtoParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -func init() { - apijson.RegisterFieldValidator[CreateSocialPostAccountConfigurationConfigurationPollParam]( - "reply_settings", "following", "mentionedUsers", "subscribers", "verified", - ) -} - -// The property URL is required. -type CreateSocialPostMediaParam struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing param.Opt[bool] `json:"skip_processing,omitzero"` - // List of tags to attach to the media - Tags []CreateSocialPostMediaTagParam `json:"tags,omitzero"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms,omitzero"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url,omitzero"` +// The properties Caption, SocialAccounts are required. +type CreateSocialPostParam struct { + // Caption text for the post + Caption string `json:"caption" api:"required"` + // Array of social account IDs for posting + SocialAccounts []string `json:"social_accounts,omitzero" api:"required"` + // Array of social account IDs for posting + ExternalID param.Opt[string] `json:"external_id,omitzero"` + // If isDraft is set then the post will not be processed + IsDraft param.Opt[bool] `json:"isDraft,omitzero"` + // Scheduled date and time for the post, setting to null or undefined will post + // instantly + ScheduledAt param.Opt[time.Time] `json:"scheduled_at,omitzero" format:"date-time"` + // Account-specific configurations for the post + AccountConfigurations []AccountConfigurationParam `json:"account_configurations,omitzero"` + // Array of media associated with the post. If multiple media items are provided + // and the placement is `stories`, individual posts are created per media item. + Media []SocialPostMediaParam `json:"media,omitzero"` + // Platform-specific configurations for the post + PlatformConfigurations PlatformConfigurationsDtoParam `json:"platform_configurations,omitzero"` paramObj } -func (r CreateSocialPostMediaParam) MarshalJSON() (data []byte, err error) { - type shadow CreateSocialPostMediaParam +func (r CreateSocialPostParam) MarshalJSON() (data []byte, err error) { + type shadow CreateSocialPostParam return param.MarshalObject(r, (*shadow)(&r)) } -func (r *CreateSocialPostMediaParam) UnmarshalJSON(data []byte) error { +func (r *CreateSocialPostParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// The properties ID, Platform, Type are required. -type CreateSocialPostMediaTagParam struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform,omitzero" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type,omitzero" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X param.Opt[float64] `json:"x,omitzero"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y param.Opt[float64] `json:"y,omitzero"` - paramObj +type DeleteEntityResponse struct { + // Whether or not the entity was deleted + Success bool `json:"success" api:"required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Success respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` } -func (r CreateSocialPostMediaTagParam) MarshalJSON() (data []byte, err error) { - type shadow CreateSocialPostMediaTagParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *CreateSocialPostMediaTagParam) UnmarshalJSON(data []byte) error { +// Returns the unmodified JSON received from the API +func (r DeleteEntityResponse) RawJSON() string { return r.JSON.raw } +func (r *DeleteEntityResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -func init() { - apijson.RegisterFieldValidator[CreateSocialPostMediaTagParam]( - "platform", "facebook", "instagram", - ) - apijson.RegisterFieldValidator[CreateSocialPostMediaTagParam]( - "type", "user", "product", - ) -} - type FacebookConfigurationDto struct { // Overrides the `caption` from the post Caption any `json:"caption" api:"nullable"` @@ -586,7 +578,7 @@ type FacebookConfigurationDto struct { // Page id with a location that you want to tag the image or video with Location string `json:"location" api:"nullable"` // Overrides the `media` from the post - Media []FacebookConfigurationDtoMedia `json:"media" api:"nullable"` + Media []SocialPostMedia `json:"media" api:"nullable"` // Facebook post placement // // Any of "reels", "stories", "timeline". @@ -623,73 +615,6 @@ func (r FacebookConfigurationDto) ToParam() FacebookConfigurationDtoParam { return param.Override[FacebookConfigurationDtoParam](json.RawMessage(r.RawJSON())) } -type FacebookConfigurationDtoMedia struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing bool `json:"skip_processing" api:"nullable"` - // List of tags to attach to the media - Tags []FacebookConfigurationDtoMediaTag `json:"tags" api:"nullable"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms" api:"nullable"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url" api:"nullable"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - URL respjson.Field - SkipProcessing respjson.Field - Tags respjson.Field - ThumbnailTimestampMs respjson.Field - ThumbnailURL respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r FacebookConfigurationDtoMedia) RawJSON() string { return r.JSON.raw } -func (r *FacebookConfigurationDtoMedia) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type FacebookConfigurationDtoMediaTag struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X float64 `json:"x"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y float64 `json:"y"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - Platform respjson.Field - Type respjson.Field - X respjson.Field - Y respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r FacebookConfigurationDtoMediaTag) RawJSON() string { return r.JSON.raw } -func (r *FacebookConfigurationDtoMediaTag) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - // Facebook post placement type FacebookConfigurationDtoPlacement string @@ -710,7 +635,7 @@ type FacebookConfigurationDtoParam struct { // List of page ids to invite as collaborators for a Video Reel Collaborators [][]any `json:"collaborators,omitzero"` // Overrides the `media` from the post - Media []FacebookConfigurationDtoMediaParam `json:"media,omitzero"` + Media []SocialPostMediaParam `json:"media,omitzero"` // Facebook post placement // // Any of "reels", "stories", "timeline". @@ -726,93 +651,34 @@ func (r *FacebookConfigurationDtoParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// The property URL is required. -type FacebookConfigurationDtoMediaParam struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing param.Opt[bool] `json:"skip_processing,omitzero"` - // List of tags to attach to the media - Tags []FacebookConfigurationDtoMediaTagParam `json:"tags,omitzero"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms,omitzero"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url,omitzero"` - paramObj -} - -func (r FacebookConfigurationDtoMediaParam) MarshalJSON() (data []byte, err error) { - type shadow FacebookConfigurationDtoMediaParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *FacebookConfigurationDtoMediaParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// The properties ID, Platform, Type are required. -type FacebookConfigurationDtoMediaTagParam struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags +type InstagramConfigurationDto struct { + // Display name for the audio track on Instagram Reels. Only honored on Reels + // uploads, and only when the audio is original (Meta silently ignores it on + // licensed/fingerprinted tracks). + AudioName string `json:"audio_name" api:"nullable"` + // Overrides the `caption` from the post + Caption any `json:"caption" api:"nullable"` + // Instagram usernames to be tagged as a collaborator + Collaborators []string `json:"collaborators" api:"nullable"` + // Page id with a location that you want to tag the image or video with + Location string `json:"location" api:"nullable"` + // Overrides the `media` from the post + Media []SocialPostMedia `json:"media" api:"nullable"` + // Instagram post placement // - // Any of "facebook", "instagram". - Platform string `json:"platform,omitzero" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type,omitzero" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X param.Opt[float64] `json:"x,omitzero"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y param.Opt[float64] `json:"y,omitzero"` - paramObj -} - -func (r FacebookConfigurationDtoMediaTagParam) MarshalJSON() (data []byte, err error) { - type shadow FacebookConfigurationDtoMediaTagParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *FacebookConfigurationDtoMediaTagParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -func init() { - apijson.RegisterFieldValidator[FacebookConfigurationDtoMediaTagParam]( - "platform", "facebook", "instagram", - ) - apijson.RegisterFieldValidator[FacebookConfigurationDtoMediaTagParam]( - "type", "user", "product", - ) -} - -type InstagramConfigurationDto struct { - // Overrides the `caption` from the post - Caption any `json:"caption" api:"nullable"` - // Instagram usernames to be tagged as a collaborator - Collaborators []string `json:"collaborators" api:"nullable"` - // Page id with a location that you want to tag the image or video with - Location string `json:"location" api:"nullable"` - // Overrides the `media` from the post - Media []InstagramConfigurationDtoMedia `json:"media" api:"nullable"` - // Instagram post placement - // - // Any of "reels", "stories", "timeline". - Placement InstagramConfigurationDtoPlacement `json:"placement" api:"nullable"` - // If false video posts will only be shown in the Reels tab - ShareToFeed bool `json:"share_to_feed" api:"nullable"` - // Instagram trial reel type, when passed will be created as a trial reel. If - // manual the trial reel can be manually graduated in the native app. If perfomance - // the trial reel will be automatically graduated if the trial reel performs well. + // Any of "reels", "stories", "timeline". + Placement InstagramConfigurationDtoPlacement `json:"placement" api:"nullable"` + // If false video posts will only be shown in the Reels tab + ShareToFeed bool `json:"share_to_feed" api:"nullable"` + // Instagram trial reel type, when passed will be created as a trial reel. If + // manual the trial reel can be manually graduated in the native app. If perfomance + // the trial reel will be automatically graduated if the trial reel performs well. // // Any of "manual", "performance". TrialReelType InstagramConfigurationDtoTrialReelType `json:"trial_reel_type" api:"nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { + AudioName respjson.Field Caption respjson.Field Collaborators respjson.Field Location respjson.Field @@ -841,73 +707,6 @@ func (r InstagramConfigurationDto) ToParam() InstagramConfigurationDtoParam { return param.Override[InstagramConfigurationDtoParam](json.RawMessage(r.RawJSON())) } -type InstagramConfigurationDtoMedia struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing bool `json:"skip_processing" api:"nullable"` - // List of tags to attach to the media - Tags []InstagramConfigurationDtoMediaTag `json:"tags" api:"nullable"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms" api:"nullable"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url" api:"nullable"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - URL respjson.Field - SkipProcessing respjson.Field - Tags respjson.Field - ThumbnailTimestampMs respjson.Field - ThumbnailURL respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r InstagramConfigurationDtoMedia) RawJSON() string { return r.JSON.raw } -func (r *InstagramConfigurationDtoMedia) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type InstagramConfigurationDtoMediaTag struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X float64 `json:"x"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y float64 `json:"y"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - Platform respjson.Field - Type respjson.Field - X respjson.Field - Y respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r InstagramConfigurationDtoMediaTag) RawJSON() string { return r.JSON.raw } -func (r *InstagramConfigurationDtoMediaTag) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - // Instagram post placement type InstagramConfigurationDtoPlacement string @@ -928,6 +727,10 @@ const ( ) type InstagramConfigurationDtoParam struct { + // Display name for the audio track on Instagram Reels. Only honored on Reels + // uploads, and only when the audio is original (Meta silently ignores it on + // licensed/fingerprinted tracks). + AudioName param.Opt[string] `json:"audio_name,omitzero"` // Page id with a location that you want to tag the image or video with Location param.Opt[string] `json:"location,omitzero"` // If false video posts will only be shown in the Reels tab @@ -937,7 +740,7 @@ type InstagramConfigurationDtoParam struct { // Instagram usernames to be tagged as a collaborator Collaborators []string `json:"collaborators,omitzero"` // Overrides the `media` from the post - Media []InstagramConfigurationDtoMediaParam `json:"media,omitzero"` + Media []SocialPostMediaParam `json:"media,omitzero"` // Instagram post placement // // Any of "reels", "stories", "timeline". @@ -959,81 +762,20 @@ func (r *InstagramConfigurationDtoParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// The property URL is required. -type InstagramConfigurationDtoMediaParam struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing param.Opt[bool] `json:"skip_processing,omitzero"` - // List of tags to attach to the media - Tags []InstagramConfigurationDtoMediaTagParam `json:"tags,omitzero"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms,omitzero"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url,omitzero"` - paramObj -} - -func (r InstagramConfigurationDtoMediaParam) MarshalJSON() (data []byte, err error) { - type shadow InstagramConfigurationDtoMediaParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *InstagramConfigurationDtoMediaParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// The properties ID, Platform, Type are required. -type InstagramConfigurationDtoMediaTagParam struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform,omitzero" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type,omitzero" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X param.Opt[float64] `json:"x,omitzero"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y param.Opt[float64] `json:"y,omitzero"` - paramObj -} - -func (r InstagramConfigurationDtoMediaTagParam) MarshalJSON() (data []byte, err error) { - type shadow InstagramConfigurationDtoMediaTagParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *InstagramConfigurationDtoMediaTagParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -func init() { - apijson.RegisterFieldValidator[InstagramConfigurationDtoMediaTagParam]( - "platform", "facebook", "instagram", - ) - apijson.RegisterFieldValidator[InstagramConfigurationDtoMediaTagParam]( - "type", "user", "product", - ) -} - type LinkedinConfigurationDto struct { // Overrides the `caption` from the post Caption any `json:"caption" api:"nullable"` // Overrides the `media` from the post - Media []LinkedinConfigurationDtoMedia `json:"media" api:"nullable"` + Media []SocialPostMedia `json:"media" api:"nullable"` + // LinkedIn UGC post id to reshare. The caption is used as the reshare commentary. + ResharePostID string `json:"reshare_post_id" api:"nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - Caption respjson.Field - Media respjson.Field - ExtraFields map[string]respjson.Field - raw string + Caption respjson.Field + Media respjson.Field + ResharePostID respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } @@ -1053,340 +795,85 @@ func (r LinkedinConfigurationDto) ToParam() LinkedinConfigurationDtoParam { return param.Override[LinkedinConfigurationDtoParam](json.RawMessage(r.RawJSON())) } -type LinkedinConfigurationDtoMedia struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing bool `json:"skip_processing" api:"nullable"` - // List of tags to attach to the media - Tags []LinkedinConfigurationDtoMediaTag `json:"tags" api:"nullable"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms" api:"nullable"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url" api:"nullable"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - URL respjson.Field - SkipProcessing respjson.Field - Tags respjson.Field - ThumbnailTimestampMs respjson.Field - ThumbnailURL respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +type LinkedinConfigurationDtoParam struct { + // LinkedIn UGC post id to reshare. The caption is used as the reshare commentary. + ResharePostID param.Opt[string] `json:"reshare_post_id,omitzero"` + // Overrides the `caption` from the post + Caption any `json:"caption,omitzero"` + // Overrides the `media` from the post + Media []SocialPostMediaParam `json:"media,omitzero"` + paramObj } -// Returns the unmodified JSON received from the API -func (r LinkedinConfigurationDtoMedia) RawJSON() string { return r.JSON.raw } -func (r *LinkedinConfigurationDtoMedia) UnmarshalJSON(data []byte) error { +func (r LinkedinConfigurationDtoParam) MarshalJSON() (data []byte, err error) { + type shadow LinkedinConfigurationDtoParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *LinkedinConfigurationDtoParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type LinkedinConfigurationDtoMediaTag struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X float64 `json:"x"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y float64 `json:"y"` +type PinterestConfigurationDto struct { + // Pinterest board IDs + BoardIDs []string `json:"board_ids" api:"nullable"` + // Overrides the `caption` from the post + Caption any `json:"caption" api:"nullable"` + // Pinterest post link + Link string `json:"link" api:"nullable"` + // Overrides the `media` from the post + Media []SocialPostMedia `json:"media" api:"nullable"` + // Overrides the `title` from the post for Pinterest + Title string `json:"title" api:"nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - ID respjson.Field - Platform respjson.Field - Type respjson.Field - X respjson.Field - Y respjson.Field + BoardIDs respjson.Field + Caption respjson.Field + Link respjson.Field + Media respjson.Field + Title respjson.Field ExtraFields map[string]respjson.Field raw string } `json:"-"` } // Returns the unmodified JSON received from the API -func (r LinkedinConfigurationDtoMediaTag) RawJSON() string { return r.JSON.raw } -func (r *LinkedinConfigurationDtoMediaTag) UnmarshalJSON(data []byte) error { +func (r PinterestConfigurationDto) RawJSON() string { return r.JSON.raw } +func (r *PinterestConfigurationDto) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type LinkedinConfigurationDtoParam struct { +// ToParam converts this PinterestConfigurationDto to a +// PinterestConfigurationDtoParam. +// +// Warning: the fields of the param type will not be present. ToParam should only +// be used at the last possible moment before sending a request. Test for this with +// PinterestConfigurationDtoParam.Overrides() +func (r PinterestConfigurationDto) ToParam() PinterestConfigurationDtoParam { + return param.Override[PinterestConfigurationDtoParam](json.RawMessage(r.RawJSON())) +} + +type PinterestConfigurationDtoParam struct { + // Pinterest post link + Link param.Opt[string] `json:"link,omitzero"` + // Overrides the `title` from the post for Pinterest + Title param.Opt[string] `json:"title,omitzero"` + // Pinterest board IDs + BoardIDs []string `json:"board_ids,omitzero"` // Overrides the `caption` from the post Caption any `json:"caption,omitzero"` // Overrides the `media` from the post - Media []LinkedinConfigurationDtoMediaParam `json:"media,omitzero"` + Media []SocialPostMediaParam `json:"media,omitzero"` paramObj } -func (r LinkedinConfigurationDtoParam) MarshalJSON() (data []byte, err error) { - type shadow LinkedinConfigurationDtoParam +func (r PinterestConfigurationDtoParam) MarshalJSON() (data []byte, err error) { + type shadow PinterestConfigurationDtoParam return param.MarshalObject(r, (*shadow)(&r)) } -func (r *LinkedinConfigurationDtoParam) UnmarshalJSON(data []byte) error { +func (r *PinterestConfigurationDtoParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// The property URL is required. -type LinkedinConfigurationDtoMediaParam struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing param.Opt[bool] `json:"skip_processing,omitzero"` - // List of tags to attach to the media - Tags []LinkedinConfigurationDtoMediaTagParam `json:"tags,omitzero"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms,omitzero"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url,omitzero"` - paramObj -} - -func (r LinkedinConfigurationDtoMediaParam) MarshalJSON() (data []byte, err error) { - type shadow LinkedinConfigurationDtoMediaParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *LinkedinConfigurationDtoMediaParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// The properties ID, Platform, Type are required. -type LinkedinConfigurationDtoMediaTagParam struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform,omitzero" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type,omitzero" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X param.Opt[float64] `json:"x,omitzero"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y param.Opt[float64] `json:"y,omitzero"` - paramObj -} - -func (r LinkedinConfigurationDtoMediaTagParam) MarshalJSON() (data []byte, err error) { - type shadow LinkedinConfigurationDtoMediaTagParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *LinkedinConfigurationDtoMediaTagParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -func init() { - apijson.RegisterFieldValidator[LinkedinConfigurationDtoMediaTagParam]( - "platform", "facebook", "instagram", - ) - apijson.RegisterFieldValidator[LinkedinConfigurationDtoMediaTagParam]( - "type", "user", "product", - ) -} - -type PinterestConfigurationDto struct { - // Pinterest board IDs - BoardIDs []string `json:"board_ids" api:"nullable"` - // Overrides the `caption` from the post - Caption any `json:"caption" api:"nullable"` - // Pinterest post link - Link string `json:"link" api:"nullable"` - // Overrides the `media` from the post - Media []PinterestConfigurationDtoMedia `json:"media" api:"nullable"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - BoardIDs respjson.Field - Caption respjson.Field - Link respjson.Field - Media respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r PinterestConfigurationDto) RawJSON() string { return r.JSON.raw } -func (r *PinterestConfigurationDto) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// ToParam converts this PinterestConfigurationDto to a -// PinterestConfigurationDtoParam. -// -// Warning: the fields of the param type will not be present. ToParam should only -// be used at the last possible moment before sending a request. Test for this with -// PinterestConfigurationDtoParam.Overrides() -func (r PinterestConfigurationDto) ToParam() PinterestConfigurationDtoParam { - return param.Override[PinterestConfigurationDtoParam](json.RawMessage(r.RawJSON())) -} - -type PinterestConfigurationDtoMedia struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing bool `json:"skip_processing" api:"nullable"` - // List of tags to attach to the media - Tags []PinterestConfigurationDtoMediaTag `json:"tags" api:"nullable"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms" api:"nullable"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url" api:"nullable"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - URL respjson.Field - SkipProcessing respjson.Field - Tags respjson.Field - ThumbnailTimestampMs respjson.Field - ThumbnailURL respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r PinterestConfigurationDtoMedia) RawJSON() string { return r.JSON.raw } -func (r *PinterestConfigurationDtoMedia) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type PinterestConfigurationDtoMediaTag struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X float64 `json:"x"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y float64 `json:"y"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - Platform respjson.Field - Type respjson.Field - X respjson.Field - Y respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r PinterestConfigurationDtoMediaTag) RawJSON() string { return r.JSON.raw } -func (r *PinterestConfigurationDtoMediaTag) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type PinterestConfigurationDtoParam struct { - // Pinterest post link - Link param.Opt[string] `json:"link,omitzero"` - // Pinterest board IDs - BoardIDs []string `json:"board_ids,omitzero"` - // Overrides the `caption` from the post - Caption any `json:"caption,omitzero"` - // Overrides the `media` from the post - Media []PinterestConfigurationDtoMediaParam `json:"media,omitzero"` - paramObj -} - -func (r PinterestConfigurationDtoParam) MarshalJSON() (data []byte, err error) { - type shadow PinterestConfigurationDtoParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *PinterestConfigurationDtoParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// The property URL is required. -type PinterestConfigurationDtoMediaParam struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing param.Opt[bool] `json:"skip_processing,omitzero"` - // List of tags to attach to the media - Tags []PinterestConfigurationDtoMediaTagParam `json:"tags,omitzero"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms,omitzero"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url,omitzero"` - paramObj -} - -func (r PinterestConfigurationDtoMediaParam) MarshalJSON() (data []byte, err error) { - type shadow PinterestConfigurationDtoMediaParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *PinterestConfigurationDtoMediaParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// The properties ID, Platform, Type are required. -type PinterestConfigurationDtoMediaTagParam struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform,omitzero" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type,omitzero" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X param.Opt[float64] `json:"x,omitzero"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y param.Opt[float64] `json:"y,omitzero"` - paramObj -} - -func (r PinterestConfigurationDtoMediaTagParam) MarshalJSON() (data []byte, err error) { - type shadow PinterestConfigurationDtoMediaTagParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *PinterestConfigurationDtoMediaTagParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -func init() { - apijson.RegisterFieldValidator[PinterestConfigurationDtoMediaTagParam]( - "platform", "facebook", "instagram", - ) - apijson.RegisterFieldValidator[PinterestConfigurationDtoMediaTagParam]( - "type", "user", "product", - ) -} - type PlatformConfigurationsDto struct { // Bluesky configuration Bluesky BlueskyConfigurationDto `json:"bluesky" api:"nullable"` @@ -1477,7 +964,7 @@ type SocialPost struct { // Unique identifier of the post ID string `json:"id" api:"required"` // Account-specific configurations for the post - AccountConfigurations []SocialPostAccountConfiguration `json:"account_configurations" api:"required"` + AccountConfigurations []AccountConfiguration `json:"account_configurations" api:"required"` // Caption text for the post Caption string `json:"caption" api:"required"` // Timestamp when the post was created @@ -1500,577 +987,39 @@ type SocialPost struct { UpdatedAt string `json:"updated_at" api:"required"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - ID respjson.Field - AccountConfigurations respjson.Field - Caption respjson.Field - CreatedAt respjson.Field - ExternalID respjson.Field - Media respjson.Field - PlatformConfigurations respjson.Field - ScheduledAt respjson.Field - SocialAccounts respjson.Field - Status respjson.Field - UpdatedAt respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r SocialPost) RawJSON() string { return r.JSON.raw } -func (r *SocialPost) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type SocialPostAccountConfiguration struct { - // Configuration for the social account - Configuration SocialPostAccountConfigurationConfiguration `json:"configuration" api:"required"` - // ID of the social account, you want to apply the configuration to - SocialAccountID string `json:"social_account_id" api:"required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Configuration respjson.Field - SocialAccountID respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r SocialPostAccountConfiguration) RawJSON() string { return r.JSON.raw } -func (r *SocialPostAccountConfiguration) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// Configuration for the social account -type SocialPostAccountConfigurationConfiguration struct { - // Allow comments on TikTok - AllowComment bool `json:"allow_comment" api:"nullable"` - // Allow duets on TikTok - AllowDuet bool `json:"allow_duet" api:"nullable"` - // Allow stitch on TikTok - AllowStitch bool `json:"allow_stitch" api:"nullable"` - // Will automatically add music to photo posts on TikTok - AutoAddMusic bool `json:"auto_add_music" api:"nullable"` - // Pinterest board IDs - BoardIDs []string `json:"board_ids" api:"nullable"` - // Overrides the `caption` from the post - Caption any `json:"caption" api:"nullable"` - // List of page ids or users to invite as collaborators for a Video Reel (Instagram - // and Facebook) - Collaborators [][]any `json:"collaborators" api:"nullable"` - // Id of the twitter community to post to - CommunityID string `json:"community_id"` - // Disclose branded content on TikTok - DiscloseBrandedContent bool `json:"disclose_branded_content" api:"nullable"` - // Disclose your brand on TikTok - DiscloseYourBrand bool `json:"disclose_your_brand" api:"nullable"` - // Flag content as AI generated on TikTok - IsAIGenerated bool `json:"is_ai_generated" api:"nullable"` - // Will create a draft upload to TikTok, posting will need to be completed from - // within the app - IsDraft bool `json:"is_draft" api:"nullable"` - // Pinterest post link - Link string `json:"link" api:"nullable"` - // Page id with a location that you want to tag the image or video with (Instagram - // and Facebook) - Location string `json:"location" api:"nullable"` - // If true will notify YouTube the video is intended for kids, defaults to false - MadeForKids bool `json:"made_for_kids" api:"nullable"` - // Overrides the `media` from the post - Media []SocialPostAccountConfigurationConfigurationMedia `json:"media" api:"nullable"` - // Post placement for Facebook/Instagram/Threads - // - // Any of "reels", "timeline", "stories". - Placement string `json:"placement" api:"nullable"` - // Poll options for the twitter - Poll SocialPostAccountConfigurationConfigurationPoll `json:"poll"` - // Sets the privacy status for TikTok (private, public), or YouTube (private, - // public, unlisted) - // - // Any of "public", "private", "unlisted". - PrivacyStatus string `json:"privacy_status" api:"nullable"` - // Id of the tweet you want to quote - QuoteTweetID string `json:"quote_tweet_id"` - // Who can reply to the tweet - // - // Any of "following", "mentionedUsers", "subscribers", "verified". - ReplySettings string `json:"reply_settings" api:"nullable"` - // If true, include the caption on each image in a Facebook carousel upload; if - // false, only include it on the final carousel post - SetCaptionForEachImage bool `json:"set_caption_for_each_image" api:"nullable"` - // If false Instagram video posts will only be shown in the Reels tab - ShareToFeed bool `json:"share_to_feed" api:"nullable"` - // Overrides the `title` from the post - Title string `json:"title" api:"nullable"` - // Instagram trial reel type, when passed will be created as a trial reel. If - // manual the trial reel can be manually graduated in the native app. If perfomance - // the trial reel will be automatically graduated if the trial reel performs well. - // - // Any of "manual", "performance". - TrialReelType string `json:"trial_reel_type" api:"nullable"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - AllowComment respjson.Field - AllowDuet respjson.Field - AllowStitch respjson.Field - AutoAddMusic respjson.Field - BoardIDs respjson.Field - Caption respjson.Field - Collaborators respjson.Field - CommunityID respjson.Field - DiscloseBrandedContent respjson.Field - DiscloseYourBrand respjson.Field - IsAIGenerated respjson.Field - IsDraft respjson.Field - Link respjson.Field - Location respjson.Field - MadeForKids respjson.Field - Media respjson.Field - Placement respjson.Field - Poll respjson.Field - PrivacyStatus respjson.Field - QuoteTweetID respjson.Field - ReplySettings respjson.Field - SetCaptionForEachImage respjson.Field - ShareToFeed respjson.Field - Title respjson.Field - TrialReelType respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r SocialPostAccountConfigurationConfiguration) RawJSON() string { return r.JSON.raw } -func (r *SocialPostAccountConfigurationConfiguration) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type SocialPostAccountConfigurationConfigurationMedia struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing bool `json:"skip_processing" api:"nullable"` - // List of tags to attach to the media - Tags []SocialPostAccountConfigurationConfigurationMediaTag `json:"tags" api:"nullable"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms" api:"nullable"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url" api:"nullable"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - URL respjson.Field - SkipProcessing respjson.Field - Tags respjson.Field - ThumbnailTimestampMs respjson.Field - ThumbnailURL respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r SocialPostAccountConfigurationConfigurationMedia) RawJSON() string { return r.JSON.raw } -func (r *SocialPostAccountConfigurationConfigurationMedia) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type SocialPostAccountConfigurationConfigurationMediaTag struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X float64 `json:"x"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y float64 `json:"y"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - Platform respjson.Field - Type respjson.Field - X respjson.Field - Y respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r SocialPostAccountConfigurationConfigurationMediaTag) RawJSON() string { return r.JSON.raw } -func (r *SocialPostAccountConfigurationConfigurationMediaTag) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// Poll options for the twitter -type SocialPostAccountConfigurationConfigurationPoll struct { - // Duration of the poll in minutes - DurationMinutes float64 `json:"duration_minutes" api:"required"` - // The choices of the poll, requiring 2-4 options - Options []string `json:"options" api:"required"` - // Who can reply to the tweet - // - // Any of "following", "mentionedUsers", "subscribers", "verified". - ReplySettings string `json:"reply_settings"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - DurationMinutes respjson.Field - Options respjson.Field - ReplySettings respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r SocialPostAccountConfigurationConfigurationPoll) RawJSON() string { return r.JSON.raw } -func (r *SocialPostAccountConfigurationConfigurationPoll) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type SocialPostMedia struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing bool `json:"skip_processing" api:"nullable"` - // List of tags to attach to the media - Tags []SocialPostMediaTag `json:"tags" api:"nullable"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms" api:"nullable"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url" api:"nullable"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - URL respjson.Field - SkipProcessing respjson.Field - Tags respjson.Field - ThumbnailTimestampMs respjson.Field - ThumbnailURL respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r SocialPostMedia) RawJSON() string { return r.JSON.raw } -func (r *SocialPostMedia) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type SocialPostMediaTag struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X float64 `json:"x"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y float64 `json:"y"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - Platform respjson.Field - Type respjson.Field - X respjson.Field - Y respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r SocialPostMediaTag) RawJSON() string { return r.JSON.raw } -func (r *SocialPostMediaTag) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// Current status of the post: draft, processed, scheduled, or processing -type SocialPostStatus string - -const ( - SocialPostStatusDraft SocialPostStatus = "draft" - SocialPostStatusScheduled SocialPostStatus = "scheduled" - SocialPostStatusProcessing SocialPostStatus = "processing" - SocialPostStatusProcessed SocialPostStatus = "processed" -) - -type ThreadsConfigurationDto struct { - // Overrides the `caption` from the post - Caption any `json:"caption" api:"nullable"` - // Overrides the `media` from the post - Media []ThreadsConfigurationDtoMedia `json:"media" api:"nullable"` - // Threads post placement - // - // Any of "reels", "timeline". - Placement ThreadsConfigurationDtoPlacement `json:"placement" api:"nullable"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Caption respjson.Field - Media respjson.Field - Placement respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ThreadsConfigurationDto) RawJSON() string { return r.JSON.raw } -func (r *ThreadsConfigurationDto) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// ToParam converts this ThreadsConfigurationDto to a ThreadsConfigurationDtoParam. -// -// Warning: the fields of the param type will not be present. ToParam should only -// be used at the last possible moment before sending a request. Test for this with -// ThreadsConfigurationDtoParam.Overrides() -func (r ThreadsConfigurationDto) ToParam() ThreadsConfigurationDtoParam { - return param.Override[ThreadsConfigurationDtoParam](json.RawMessage(r.RawJSON())) -} - -type ThreadsConfigurationDtoMedia struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing bool `json:"skip_processing" api:"nullable"` - // List of tags to attach to the media - Tags []ThreadsConfigurationDtoMediaTag `json:"tags" api:"nullable"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms" api:"nullable"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url" api:"nullable"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - URL respjson.Field - SkipProcessing respjson.Field - Tags respjson.Field - ThumbnailTimestampMs respjson.Field - ThumbnailURL respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ThreadsConfigurationDtoMedia) RawJSON() string { return r.JSON.raw } -func (r *ThreadsConfigurationDtoMedia) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type ThreadsConfigurationDtoMediaTag struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X float64 `json:"x"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y float64 `json:"y"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - Platform respjson.Field - Type respjson.Field - X respjson.Field - Y respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r ThreadsConfigurationDtoMediaTag) RawJSON() string { return r.JSON.raw } -func (r *ThreadsConfigurationDtoMediaTag) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// Threads post placement -type ThreadsConfigurationDtoPlacement string - -const ( - ThreadsConfigurationDtoPlacementReels ThreadsConfigurationDtoPlacement = "reels" - ThreadsConfigurationDtoPlacementTimeline ThreadsConfigurationDtoPlacement = "timeline" -) - -type ThreadsConfigurationDtoParam struct { - // Overrides the `caption` from the post - Caption any `json:"caption,omitzero"` - // Overrides the `media` from the post - Media []ThreadsConfigurationDtoMediaParam `json:"media,omitzero"` - // Threads post placement - // - // Any of "reels", "timeline". - Placement ThreadsConfigurationDtoPlacement `json:"placement,omitzero"` - paramObj -} - -func (r ThreadsConfigurationDtoParam) MarshalJSON() (data []byte, err error) { - type shadow ThreadsConfigurationDtoParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *ThreadsConfigurationDtoParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// The property URL is required. -type ThreadsConfigurationDtoMediaParam struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing param.Opt[bool] `json:"skip_processing,omitzero"` - // List of tags to attach to the media - Tags []ThreadsConfigurationDtoMediaTagParam `json:"tags,omitzero"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms,omitzero"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url,omitzero"` - paramObj -} - -func (r ThreadsConfigurationDtoMediaParam) MarshalJSON() (data []byte, err error) { - type shadow ThreadsConfigurationDtoMediaParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *ThreadsConfigurationDtoMediaParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// The properties ID, Platform, Type are required. -type ThreadsConfigurationDtoMediaTagParam struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform,omitzero" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type,omitzero" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X param.Opt[float64] `json:"x,omitzero"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y param.Opt[float64] `json:"y,omitzero"` - paramObj -} - -func (r ThreadsConfigurationDtoMediaTagParam) MarshalJSON() (data []byte, err error) { - type shadow ThreadsConfigurationDtoMediaTagParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *ThreadsConfigurationDtoMediaTagParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -func init() { - apijson.RegisterFieldValidator[ThreadsConfigurationDtoMediaTagParam]( - "platform", "facebook", "instagram", - ) - apijson.RegisterFieldValidator[ThreadsConfigurationDtoMediaTagParam]( - "type", "user", "product", - ) -} - -type TiktokConfiguration struct { - // Allow comments on TikTok - AllowComment bool `json:"allow_comment" api:"nullable"` - // Allow duets on TikTok - AllowDuet bool `json:"allow_duet" api:"nullable"` - // Allow stitch on TikTok - AllowStitch bool `json:"allow_stitch" api:"nullable"` - // Will automatically add music to photo posts - AutoAddMusic bool `json:"auto_add_music" api:"nullable"` - // Overrides the `caption` from the post - Caption any `json:"caption" api:"nullable"` - // Disclose branded content on TikTok - DiscloseBrandedContent bool `json:"disclose_branded_content" api:"nullable"` - // Disclose your brand on TikTok - DiscloseYourBrand bool `json:"disclose_your_brand" api:"nullable"` - // Flag content as AI generated on TikTok - IsAIGenerated bool `json:"is_ai_generated" api:"nullable"` - // Will create a draft upload to TikTok, posting will need to be completed from - // within the app - IsDraft bool `json:"is_draft" api:"nullable"` - // Overrides the `media` from the post - Media []TiktokConfigurationMedia `json:"media" api:"nullable"` - // Sets the privacy status for TikTok (private, public) - PrivacyStatus string `json:"privacy_status" api:"nullable"` - // Overrides the `title` from the post - Title string `json:"title" api:"nullable"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - AllowComment respjson.Field - AllowDuet respjson.Field - AllowStitch respjson.Field - AutoAddMusic respjson.Field - Caption respjson.Field - DiscloseBrandedContent respjson.Field - DiscloseYourBrand respjson.Field - IsAIGenerated respjson.Field - IsDraft respjson.Field + ID respjson.Field + AccountConfigurations respjson.Field + Caption respjson.Field + CreatedAt respjson.Field + ExternalID respjson.Field Media respjson.Field - PrivacyStatus respjson.Field - Title respjson.Field + PlatformConfigurations respjson.Field + ScheduledAt respjson.Field + SocialAccounts respjson.Field + Status respjson.Field + UpdatedAt respjson.Field ExtraFields map[string]respjson.Field raw string } `json:"-"` } // Returns the unmodified JSON received from the API -func (r TiktokConfiguration) RawJSON() string { return r.JSON.raw } -func (r *TiktokConfiguration) UnmarshalJSON(data []byte) error { +func (r SocialPost) RawJSON() string { return r.JSON.raw } +func (r *SocialPost) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// ToParam converts this TiktokConfiguration to a TiktokConfigurationParam. -// -// Warning: the fields of the param type will not be present. ToParam should only -// be used at the last possible moment before sending a request. Test for this with -// TiktokConfigurationParam.Overrides() -func (r TiktokConfiguration) ToParam() TiktokConfigurationParam { - return param.Override[TiktokConfigurationParam](json.RawMessage(r.RawJSON())) -} +// Current status of the post: draft, processed, scheduled, or processing +type SocialPostStatus string + +const ( + SocialPostStatusDraft SocialPostStatus = "draft" + SocialPostStatusScheduled SocialPostStatus = "scheduled" + SocialPostStatusProcessing SocialPostStatus = "processing" + SocialPostStatusProcessed SocialPostStatus = "processed" +) -type TiktokConfigurationMedia struct { +type SocialPostMedia struct { // Public URL of the media URL string `json:"url" api:"required"` // If true the media will not be processed at all and instead be posted as is, this @@ -2078,7 +1027,7 @@ type TiktokConfigurationMedia struct { // requirements. Best used for larger files. SkipProcessing bool `json:"skip_processing" api:"nullable"` // List of tags to attach to the media - Tags []TiktokConfigurationMediaTag `json:"tags" api:"nullable"` + Tags []SocialPostMediaTag `json:"tags" api:"nullable"` // Timestamp in milliseconds of frame to use as thumbnail for the media ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms" api:"nullable"` // Public URL of the thumbnail for the media @@ -2096,12 +1045,21 @@ type TiktokConfigurationMedia struct { } // Returns the unmodified JSON received from the API -func (r TiktokConfigurationMedia) RawJSON() string { return r.JSON.raw } -func (r *TiktokConfigurationMedia) UnmarshalJSON(data []byte) error { +func (r SocialPostMedia) RawJSON() string { return r.JSON.raw } +func (r *SocialPostMedia) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type TiktokConfigurationMediaTag struct { +// ToParam converts this SocialPostMedia to a SocialPostMediaParam. +// +// Warning: the fields of the param type will not be present. ToParam should only +// be used at the last possible moment before sending a request. Test for this with +// SocialPostMediaParam.Overrides() +func (r SocialPostMedia) ToParam() SocialPostMediaParam { + return param.Override[SocialPostMediaParam](json.RawMessage(r.RawJSON())) +} + +type SocialPostMediaTag struct { // Facebook User ID, Instagram Username or Instagram product id to tag ID string `json:"id" api:"required"` // The platform for the tags @@ -2132,50 +1090,13 @@ type TiktokConfigurationMediaTag struct { } // Returns the unmodified JSON received from the API -func (r TiktokConfigurationMediaTag) RawJSON() string { return r.JSON.raw } -func (r *TiktokConfigurationMediaTag) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type TiktokConfigurationParam struct { - // Allow comments on TikTok - AllowComment param.Opt[bool] `json:"allow_comment,omitzero"` - // Allow duets on TikTok - AllowDuet param.Opt[bool] `json:"allow_duet,omitzero"` - // Allow stitch on TikTok - AllowStitch param.Opt[bool] `json:"allow_stitch,omitzero"` - // Will automatically add music to photo posts - AutoAddMusic param.Opt[bool] `json:"auto_add_music,omitzero"` - // Disclose branded content on TikTok - DiscloseBrandedContent param.Opt[bool] `json:"disclose_branded_content,omitzero"` - // Disclose your brand on TikTok - DiscloseYourBrand param.Opt[bool] `json:"disclose_your_brand,omitzero"` - // Flag content as AI generated on TikTok - IsAIGenerated param.Opt[bool] `json:"is_ai_generated,omitzero"` - // Will create a draft upload to TikTok, posting will need to be completed from - // within the app - IsDraft param.Opt[bool] `json:"is_draft,omitzero"` - // Sets the privacy status for TikTok (private, public) - PrivacyStatus param.Opt[string] `json:"privacy_status,omitzero"` - // Overrides the `title` from the post - Title param.Opt[string] `json:"title,omitzero"` - // Overrides the `caption` from the post - Caption any `json:"caption,omitzero"` - // Overrides the `media` from the post - Media []TiktokConfigurationMediaParam `json:"media,omitzero"` - paramObj -} - -func (r TiktokConfigurationParam) MarshalJSON() (data []byte, err error) { - type shadow TiktokConfigurationParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *TiktokConfigurationParam) UnmarshalJSON(data []byte) error { +func (r SocialPostMediaTag) RawJSON() string { return r.JSON.raw } +func (r *SocialPostMediaTag) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } // The property URL is required. -type TiktokConfigurationMediaParam struct { +type SocialPostMediaParam struct { // Public URL of the media URL string `json:"url" api:"required"` // If true the media will not be processed at all and instead be posted as is, this @@ -2183,7 +1104,7 @@ type TiktokConfigurationMediaParam struct { // requirements. Best used for larger files. SkipProcessing param.Opt[bool] `json:"skip_processing,omitzero"` // List of tags to attach to the media - Tags []TiktokConfigurationMediaTagParam `json:"tags,omitzero"` + Tags []SocialPostMediaTagParam `json:"tags,omitzero"` // Timestamp in milliseconds of frame to use as thumbnail for the media ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms,omitzero"` // Public URL of the thumbnail for the media @@ -2191,16 +1112,16 @@ type TiktokConfigurationMediaParam struct { paramObj } -func (r TiktokConfigurationMediaParam) MarshalJSON() (data []byte, err error) { - type shadow TiktokConfigurationMediaParam +func (r SocialPostMediaParam) MarshalJSON() (data []byte, err error) { + type shadow SocialPostMediaParam return param.MarshalObject(r, (*shadow)(&r)) } -func (r *TiktokConfigurationMediaParam) UnmarshalJSON(data []byte) error { +func (r *SocialPostMediaParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } // The properties ID, Platform, Type are required. -type TiktokConfigurationMediaTagParam struct { +type SocialPostMediaTagParam struct { // Facebook User ID, Instagram Username or Instagram product id to tag ID string `json:"id" api:"required"` // The platform for the tags @@ -2221,159 +1142,225 @@ type TiktokConfigurationMediaTagParam struct { paramObj } -func (r TiktokConfigurationMediaTagParam) MarshalJSON() (data []byte, err error) { - type shadow TiktokConfigurationMediaTagParam +func (r SocialPostMediaTagParam) MarshalJSON() (data []byte, err error) { + type shadow SocialPostMediaTagParam return param.MarshalObject(r, (*shadow)(&r)) } -func (r *TiktokConfigurationMediaTagParam) UnmarshalJSON(data []byte) error { +func (r *SocialPostMediaTagParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } func init() { - apijson.RegisterFieldValidator[TiktokConfigurationMediaTagParam]( + apijson.RegisterFieldValidator[SocialPostMediaTagParam]( "platform", "facebook", "instagram", ) - apijson.RegisterFieldValidator[TiktokConfigurationMediaTagParam]( + apijson.RegisterFieldValidator[SocialPostMediaTagParam]( "type", "user", "product", ) } -type TwitterConfigurationDto struct { +type ThreadsConfigurationDto struct { // Overrides the `caption` from the post Caption any `json:"caption" api:"nullable"` - // Id of the community to post to - CommunityID string `json:"community_id"` // Overrides the `media` from the post - Media []TwitterConfigurationDtoMedia `json:"media" api:"nullable"` - // Poll options for the tweet - Poll TwitterConfigurationDtoPoll `json:"poll"` - // Id of the tweet you want to quote - QuoteTweetID string `json:"quote_tweet_id"` - // Who can reply to the tweet + Media []SocialPostMedia `json:"media" api:"nullable"` + // Threads post placement // - // Any of "following", "mentionedUsers", "subscribers", "verified". - ReplySettings TwitterConfigurationDtoReplySettings `json:"reply_settings" api:"nullable"` + // Any of "reels", "timeline". + Placement ThreadsConfigurationDtoPlacement `json:"placement" api:"nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - Caption respjson.Field - CommunityID respjson.Field - Media respjson.Field - Poll respjson.Field - QuoteTweetID respjson.Field - ReplySettings respjson.Field - ExtraFields map[string]respjson.Field - raw string + Caption respjson.Field + Media respjson.Field + Placement respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } // Returns the unmodified JSON received from the API -func (r TwitterConfigurationDto) RawJSON() string { return r.JSON.raw } -func (r *TwitterConfigurationDto) UnmarshalJSON(data []byte) error { +func (r ThreadsConfigurationDto) RawJSON() string { return r.JSON.raw } +func (r *ThreadsConfigurationDto) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// ToParam converts this TwitterConfigurationDto to a TwitterConfigurationDtoParam. +// ToParam converts this ThreadsConfigurationDto to a ThreadsConfigurationDtoParam. // // Warning: the fields of the param type will not be present. ToParam should only // be used at the last possible moment before sending a request. Test for this with -// TwitterConfigurationDtoParam.Overrides() -func (r TwitterConfigurationDto) ToParam() TwitterConfigurationDtoParam { - return param.Override[TwitterConfigurationDtoParam](json.RawMessage(r.RawJSON())) +// ThreadsConfigurationDtoParam.Overrides() +func (r ThreadsConfigurationDto) ToParam() ThreadsConfigurationDtoParam { + return param.Override[ThreadsConfigurationDtoParam](json.RawMessage(r.RawJSON())) } -type TwitterConfigurationDtoMedia struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing bool `json:"skip_processing" api:"nullable"` - // List of tags to attach to the media - Tags []TwitterConfigurationDtoMediaTag `json:"tags" api:"nullable"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms" api:"nullable"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url" api:"nullable"` +// Threads post placement +type ThreadsConfigurationDtoPlacement string + +const ( + ThreadsConfigurationDtoPlacementReels ThreadsConfigurationDtoPlacement = "reels" + ThreadsConfigurationDtoPlacementTimeline ThreadsConfigurationDtoPlacement = "timeline" +) + +type ThreadsConfigurationDtoParam struct { + // Overrides the `caption` from the post + Caption any `json:"caption,omitzero"` + // Overrides the `media` from the post + Media []SocialPostMediaParam `json:"media,omitzero"` + // Threads post placement + // + // Any of "reels", "timeline". + Placement ThreadsConfigurationDtoPlacement `json:"placement,omitzero"` + paramObj +} + +func (r ThreadsConfigurationDtoParam) MarshalJSON() (data []byte, err error) { + type shadow ThreadsConfigurationDtoParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *ThreadsConfigurationDtoParam) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type TiktokConfiguration struct { + // Allow comments on TikTok + AllowComment bool `json:"allow_comment" api:"nullable"` + // Allow duets on TikTok + AllowDuet bool `json:"allow_duet" api:"nullable"` + // Allow stitch on TikTok + AllowStitch bool `json:"allow_stitch" api:"nullable"` + // Will automatically add music to photo posts + AutoAddMusic bool `json:"auto_add_music" api:"nullable"` + // Overrides the `caption` from the post + Caption any `json:"caption" api:"nullable"` + // Disclose branded content on TikTok + DiscloseBrandedContent bool `json:"disclose_branded_content" api:"nullable"` + // Disclose your brand on TikTok + DiscloseYourBrand bool `json:"disclose_your_brand" api:"nullable"` + // Flag content as AI generated on TikTok + IsAIGenerated bool `json:"is_ai_generated" api:"nullable"` + // Will create a draft upload to TikTok, posting will need to be completed from + // within the app + IsDraft bool `json:"is_draft" api:"nullable"` + // Overrides the `media` from the post + Media []SocialPostMedia `json:"media" api:"nullable"` + // Sets the privacy status for TikTok (private, public) + PrivacyStatus string `json:"privacy_status" api:"nullable"` + // Overrides the `title` from the post + Title string `json:"title" api:"nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - URL respjson.Field - SkipProcessing respjson.Field - Tags respjson.Field - ThumbnailTimestampMs respjson.Field - ThumbnailURL respjson.Field - ExtraFields map[string]respjson.Field - raw string + AllowComment respjson.Field + AllowDuet respjson.Field + AllowStitch respjson.Field + AutoAddMusic respjson.Field + Caption respjson.Field + DiscloseBrandedContent respjson.Field + DiscloseYourBrand respjson.Field + IsAIGenerated respjson.Field + IsDraft respjson.Field + Media respjson.Field + PrivacyStatus respjson.Field + Title respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } // Returns the unmodified JSON received from the API -func (r TwitterConfigurationDtoMedia) RawJSON() string { return r.JSON.raw } -func (r *TwitterConfigurationDtoMedia) UnmarshalJSON(data []byte) error { +func (r TiktokConfiguration) RawJSON() string { return r.JSON.raw } +func (r *TiktokConfiguration) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type TwitterConfigurationDtoMediaTag struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X float64 `json:"x"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y float64 `json:"y"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - Platform respjson.Field - Type respjson.Field - X respjson.Field - Y respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` +// ToParam converts this TiktokConfiguration to a TiktokConfigurationParam. +// +// Warning: the fields of the param type will not be present. ToParam should only +// be used at the last possible moment before sending a request. Test for this with +// TiktokConfigurationParam.Overrides() +func (r TiktokConfiguration) ToParam() TiktokConfigurationParam { + return param.Override[TiktokConfigurationParam](json.RawMessage(r.RawJSON())) +} + +type TiktokConfigurationParam struct { + // Allow comments on TikTok + AllowComment param.Opt[bool] `json:"allow_comment,omitzero"` + // Allow duets on TikTok + AllowDuet param.Opt[bool] `json:"allow_duet,omitzero"` + // Allow stitch on TikTok + AllowStitch param.Opt[bool] `json:"allow_stitch,omitzero"` + // Will automatically add music to photo posts + AutoAddMusic param.Opt[bool] `json:"auto_add_music,omitzero"` + // Disclose branded content on TikTok + DiscloseBrandedContent param.Opt[bool] `json:"disclose_branded_content,omitzero"` + // Disclose your brand on TikTok + DiscloseYourBrand param.Opt[bool] `json:"disclose_your_brand,omitzero"` + // Flag content as AI generated on TikTok + IsAIGenerated param.Opt[bool] `json:"is_ai_generated,omitzero"` + // Will create a draft upload to TikTok, posting will need to be completed from + // within the app + IsDraft param.Opt[bool] `json:"is_draft,omitzero"` + // Sets the privacy status for TikTok (private, public) + PrivacyStatus param.Opt[string] `json:"privacy_status,omitzero"` + // Overrides the `title` from the post + Title param.Opt[string] `json:"title,omitzero"` + // Overrides the `caption` from the post + Caption any `json:"caption,omitzero"` + // Overrides the `media` from the post + Media []SocialPostMediaParam `json:"media,omitzero"` + paramObj } -// Returns the unmodified JSON received from the API -func (r TwitterConfigurationDtoMediaTag) RawJSON() string { return r.JSON.raw } -func (r *TwitterConfigurationDtoMediaTag) UnmarshalJSON(data []byte) error { +func (r TiktokConfigurationParam) MarshalJSON() (data []byte, err error) { + type shadow TiktokConfigurationParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *TiktokConfigurationParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// Poll options for the tweet -type TwitterConfigurationDtoPoll struct { - // Duration of the poll in minutes - DurationMinutes float64 `json:"duration_minutes" api:"required"` - // The choices of the poll, requiring 2-4 options - Options []string `json:"options" api:"required"` +type TwitterConfigurationDto struct { + // Overrides the `caption` from the post + Caption any `json:"caption" api:"nullable"` + // Id of the community to post to + CommunityID string `json:"community_id"` + // Overrides the `media` from the post + Media []SocialPostMedia `json:"media" api:"nullable"` + // Poll options for the tweet + Poll TwitterPoll `json:"poll"` + // Id of the tweet you want to quote + QuoteTweetID string `json:"quote_tweet_id"` // Who can reply to the tweet // // Any of "following", "mentionedUsers", "subscribers", "verified". - ReplySettings string `json:"reply_settings"` + ReplySettings TwitterConfigurationDtoReplySettings `json:"reply_settings" api:"nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - DurationMinutes respjson.Field - Options respjson.Field - ReplySettings respjson.Field - ExtraFields map[string]respjson.Field - raw string + Caption respjson.Field + CommunityID respjson.Field + Media respjson.Field + Poll respjson.Field + QuoteTweetID respjson.Field + ReplySettings respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } // Returns the unmodified JSON received from the API -func (r TwitterConfigurationDtoPoll) RawJSON() string { return r.JSON.raw } -func (r *TwitterConfigurationDtoPoll) UnmarshalJSON(data []byte) error { +func (r TwitterConfigurationDto) RawJSON() string { return r.JSON.raw } +func (r *TwitterConfigurationDto) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } +// ToParam converts this TwitterConfigurationDto to a TwitterConfigurationDtoParam. +// +// Warning: the fields of the param type will not be present. ToParam should only +// be used at the last possible moment before sending a request. Test for this with +// TwitterConfigurationDtoParam.Overrides() +func (r TwitterConfigurationDto) ToParam() TwitterConfigurationDtoParam { + return param.Override[TwitterConfigurationDtoParam](json.RawMessage(r.RawJSON())) +} + // Who can reply to the tweet type TwitterConfigurationDtoReplySettings string @@ -2392,13 +1379,13 @@ type TwitterConfigurationDtoParam struct { // Overrides the `caption` from the post Caption any `json:"caption,omitzero"` // Overrides the `media` from the post - Media []TwitterConfigurationDtoMediaParam `json:"media,omitzero"` + Media []SocialPostMediaParam `json:"media,omitzero"` // Who can reply to the tweet // // Any of "following", "mentionedUsers", "subscribers", "verified". ReplySettings TwitterConfigurationDtoReplySettings `json:"reply_settings,omitzero"` // Poll options for the tweet - Poll TwitterConfigurationDtoPollParam `json:"poll,omitzero"` + Poll TwitterPollParam `json:"poll,omitzero"` paramObj } @@ -2410,74 +1397,52 @@ func (r *TwitterConfigurationDtoParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// The property URL is required. -type TwitterConfigurationDtoMediaParam struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing param.Opt[bool] `json:"skip_processing,omitzero"` - // List of tags to attach to the media - Tags []TwitterConfigurationDtoMediaTagParam `json:"tags,omitzero"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms,omitzero"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url,omitzero"` - paramObj +type TwitterPoll struct { + // Duration of the poll in minutes + DurationMinutes float64 `json:"duration_minutes" api:"required"` + // The choices of the poll, requiring 2-4 options + Options []string `json:"options" api:"required"` + // Who can reply to the tweet + // + // Any of "following", "mentionedUsers", "subscribers", "verified". + ReplySettings TwitterPollReplySettings `json:"reply_settings"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + DurationMinutes respjson.Field + Options respjson.Field + ReplySettings respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` } -func (r TwitterConfigurationDtoMediaParam) MarshalJSON() (data []byte, err error) { - type shadow TwitterConfigurationDtoMediaParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *TwitterConfigurationDtoMediaParam) UnmarshalJSON(data []byte) error { +// Returns the unmodified JSON received from the API +func (r TwitterPoll) RawJSON() string { return r.JSON.raw } +func (r *TwitterPoll) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// The properties ID, Platform, Type are required. -type TwitterConfigurationDtoMediaTagParam struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform,omitzero" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type,omitzero" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X param.Opt[float64] `json:"x,omitzero"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y param.Opt[float64] `json:"y,omitzero"` - paramObj +// ToParam converts this TwitterPoll to a TwitterPollParam. +// +// Warning: the fields of the param type will not be present. ToParam should only +// be used at the last possible moment before sending a request. Test for this with +// TwitterPollParam.Overrides() +func (r TwitterPoll) ToParam() TwitterPollParam { + return param.Override[TwitterPollParam](json.RawMessage(r.RawJSON())) } -func (r TwitterConfigurationDtoMediaTagParam) MarshalJSON() (data []byte, err error) { - type shadow TwitterConfigurationDtoMediaTagParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *TwitterConfigurationDtoMediaTagParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} +// Who can reply to the tweet +type TwitterPollReplySettings string -func init() { - apijson.RegisterFieldValidator[TwitterConfigurationDtoMediaTagParam]( - "platform", "facebook", "instagram", - ) - apijson.RegisterFieldValidator[TwitterConfigurationDtoMediaTagParam]( - "type", "user", "product", - ) -} +const ( + TwitterPollReplySettingsFollowing TwitterPollReplySettings = "following" + TwitterPollReplySettingsMentionedUsers TwitterPollReplySettings = "mentionedUsers" + TwitterPollReplySettingsSubscribers TwitterPollReplySettings = "subscribers" + TwitterPollReplySettingsVerified TwitterPollReplySettings = "verified" +) -// Poll options for the tweet -// // The properties DurationMinutes, Options are required. -type TwitterConfigurationDtoPollParam struct { +type TwitterPollParam struct { // Duration of the poll in minutes DurationMinutes float64 `json:"duration_minutes" api:"required"` // The choices of the poll, requiring 2-4 options @@ -2485,46 +1450,89 @@ type TwitterConfigurationDtoPollParam struct { // Who can reply to the tweet // // Any of "following", "mentionedUsers", "subscribers", "verified". - ReplySettings string `json:"reply_settings,omitzero"` + ReplySettings TwitterPollReplySettings `json:"reply_settings,omitzero"` paramObj } -func (r TwitterConfigurationDtoPollParam) MarshalJSON() (data []byte, err error) { - type shadow TwitterConfigurationDtoPollParam +func (r TwitterPollParam) MarshalJSON() (data []byte, err error) { + type shadow TwitterPollParam return param.MarshalObject(r, (*shadow)(&r)) } -func (r *TwitterConfigurationDtoPollParam) UnmarshalJSON(data []byte) error { +func (r *TwitterPollParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -func init() { - apijson.RegisterFieldValidator[TwitterConfigurationDtoPollParam]( - "reply_settings", "following", "mentionedUsers", "subscribers", "verified", - ) -} - type YoutubeConfigurationDto struct { + // Per-language localizations for the video title and description. Keys are BCP-47 + // language tags (e.g. "fr", "es"). Maps to localizations on the YouTube Data API + // videos resource. + Localizations map[string]YoutubeConfigurationDtoLocalization `json:"localizations" api:"required"` // Overrides the `caption` from the post Caption any `json:"caption" api:"nullable"` - // If true will notify YouTube the video is intended for kids, defaults to false + // YouTube video category id (maps to snippet.categoryId; see YouTube Data API + // videoCategories.list) + CategoryID string `json:"category_id" api:"nullable"` + // If true, marks the video as containing altered or synthetic content per + // YouTube's disclosure policy (maps to status.containsSyntheticMedia). YouTube + // adds a "How this content was made" label to the description automatically. + ContainsSyntheticMedia bool `json:"contains_synthetic_media" api:"nullable"` + // Default language of the video (BCP-47 language tag, e.g. "en"). Maps to + // snippet.defaultLanguage. + DefaultLanguage string `json:"default_language" api:"nullable"` + // Description for the YouTube video (maps to snippet.description). Falls back to + // the post caption when not provided. + Description string `json:"description" api:"nullable"` + // If true the video can be embedded on other websites (maps to status.embeddable). + // Defaults to true. + Embeddable bool `json:"embeddable" api:"nullable"` + // The video's license (maps to status.license). "youtube" is the standard YouTube + // license; "creativeCommon" is Creative Commons. + // + // Any of "youtube", "creativeCommon". + License YoutubeConfigurationDtoLicense `json:"license" api:"nullable"` + // If true will notify YouTube the video is intended for kids (maps to + // status.selfDeclaredMadeForKids), defaults to false MadeForKids bool `json:"made_for_kids" api:"nullable"` // Overrides the `media` from the post - Media []YoutubeConfigurationDtoMedia `json:"media" api:"nullable"` - // Sets the privacy status of the video, will default to public + Media []SocialPostMedia `json:"media" api:"nullable"` + // Sets the privacy status of the video (maps to status.privacyStatus), will + // default to public // // Any of "public", "private", "unlisted". PrivacyStatus YoutubeConfigurationDtoPrivacyStatus `json:"privacy_status" api:"nullable"` - // Overrides the `title` from the post + // If true, the extended video statistics are publicly viewable (maps to + // status.publicStatsViewable). Defaults to true. + PublicStatsViewable bool `json:"public_stats_viewable" api:"nullable"` + // ISO 8601 datetime at which the video should be published. Only honoured when + // privacy_status is "private" (maps to status.publishAt). + PublishAt string `json:"publish_at" api:"nullable"` + // ISO 8601 date (YYYY-MM-DD) or datetime when the video was recorded (maps to + // recordingDetails.recordingDate). + RecordingDate string `json:"recording_date" api:"nullable"` + // YouTube video tags (maps to snippet.tags) + Tags []string `json:"tags" api:"nullable"` + // Overrides the `title` from the post (maps to snippet.title) Title string `json:"title" api:"nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - Caption respjson.Field - MadeForKids respjson.Field - Media respjson.Field - PrivacyStatus respjson.Field - Title respjson.Field - ExtraFields map[string]respjson.Field - raw string + Localizations respjson.Field + Caption respjson.Field + CategoryID respjson.Field + ContainsSyntheticMedia respjson.Field + DefaultLanguage respjson.Field + Description respjson.Field + Embeddable respjson.Field + License respjson.Field + MadeForKids respjson.Field + Media respjson.Field + PrivacyStatus respjson.Field + PublicStatsViewable respjson.Field + PublishAt respjson.Field + RecordingDate respjson.Field + Tags respjson.Field + Title respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } @@ -2543,74 +1551,35 @@ func (r YoutubeConfigurationDto) ToParam() YoutubeConfigurationDtoParam { return param.Override[YoutubeConfigurationDtoParam](json.RawMessage(r.RawJSON())) } -type YoutubeConfigurationDtoMedia struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing bool `json:"skip_processing" api:"nullable"` - // List of tags to attach to the media - Tags []YoutubeConfigurationDtoMediaTag `json:"tags" api:"nullable"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms" api:"nullable"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url" api:"nullable"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - URL respjson.Field - SkipProcessing respjson.Field - Tags respjson.Field - ThumbnailTimestampMs respjson.Field - ThumbnailURL respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r YoutubeConfigurationDtoMedia) RawJSON() string { return r.JSON.raw } -func (r *YoutubeConfigurationDtoMedia) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type YoutubeConfigurationDtoMediaTag struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X float64 `json:"x"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y float64 `json:"y"` +type YoutubeConfigurationDtoLocalization struct { + Description string `json:"description" api:"nullable"` + Title string `json:"title" api:"nullable"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - ID respjson.Field - Platform respjson.Field - Type respjson.Field - X respjson.Field - Y respjson.Field + Description respjson.Field + Title respjson.Field ExtraFields map[string]respjson.Field raw string } `json:"-"` } // Returns the unmodified JSON received from the API -func (r YoutubeConfigurationDtoMediaTag) RawJSON() string { return r.JSON.raw } -func (r *YoutubeConfigurationDtoMediaTag) UnmarshalJSON(data []byte) error { +func (r YoutubeConfigurationDtoLocalization) RawJSON() string { return r.JSON.raw } +func (r *YoutubeConfigurationDtoLocalization) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// Sets the privacy status of the video, will default to public +// The video's license (maps to status.license). "youtube" is the standard YouTube +// license; "creativeCommon" is Creative Commons. +type YoutubeConfigurationDtoLicense string + +const ( + YoutubeConfigurationDtoLicenseYoutube YoutubeConfigurationDtoLicense = "youtube" + YoutubeConfigurationDtoLicenseCreativeCommon YoutubeConfigurationDtoLicense = "creativeCommon" +) + +// Sets the privacy status of the video (maps to status.privacyStatus), will +// default to public type YoutubeConfigurationDtoPrivacyStatus string const ( @@ -2619,19 +1588,58 @@ const ( YoutubeConfigurationDtoPrivacyStatusUnlisted YoutubeConfigurationDtoPrivacyStatus = "unlisted" ) +// The property Localizations is required. type YoutubeConfigurationDtoParam struct { - // If true will notify YouTube the video is intended for kids, defaults to false + // Per-language localizations for the video title and description. Keys are BCP-47 + // language tags (e.g. "fr", "es"). Maps to localizations on the YouTube Data API + // videos resource. + Localizations map[string]YoutubeConfigurationDtoLocalizationParam `json:"localizations,omitzero" api:"required"` + // YouTube video category id (maps to snippet.categoryId; see YouTube Data API + // videoCategories.list) + CategoryID param.Opt[string] `json:"category_id,omitzero"` + // If true, marks the video as containing altered or synthetic content per + // YouTube's disclosure policy (maps to status.containsSyntheticMedia). YouTube + // adds a "How this content was made" label to the description automatically. + ContainsSyntheticMedia param.Opt[bool] `json:"contains_synthetic_media,omitzero"` + // Default language of the video (BCP-47 language tag, e.g. "en"). Maps to + // snippet.defaultLanguage. + DefaultLanguage param.Opt[string] `json:"default_language,omitzero"` + // Description for the YouTube video (maps to snippet.description). Falls back to + // the post caption when not provided. + Description param.Opt[string] `json:"description,omitzero"` + // If true the video can be embedded on other websites (maps to status.embeddable). + // Defaults to true. + Embeddable param.Opt[bool] `json:"embeddable,omitzero"` + // If true will notify YouTube the video is intended for kids (maps to + // status.selfDeclaredMadeForKids), defaults to false MadeForKids param.Opt[bool] `json:"made_for_kids,omitzero"` - // Overrides the `title` from the post + // If true, the extended video statistics are publicly viewable (maps to + // status.publicStatsViewable). Defaults to true. + PublicStatsViewable param.Opt[bool] `json:"public_stats_viewable,omitzero"` + // ISO 8601 datetime at which the video should be published. Only honoured when + // privacy_status is "private" (maps to status.publishAt). + PublishAt param.Opt[string] `json:"publish_at,omitzero"` + // ISO 8601 date (YYYY-MM-DD) or datetime when the video was recorded (maps to + // recordingDetails.recordingDate). + RecordingDate param.Opt[string] `json:"recording_date,omitzero"` + // Overrides the `title` from the post (maps to snippet.title) Title param.Opt[string] `json:"title,omitzero"` // Overrides the `caption` from the post Caption any `json:"caption,omitzero"` + // The video's license (maps to status.license). "youtube" is the standard YouTube + // license; "creativeCommon" is Creative Commons. + // + // Any of "youtube", "creativeCommon". + License YoutubeConfigurationDtoLicense `json:"license,omitzero"` // Overrides the `media` from the post - Media []YoutubeConfigurationDtoMediaParam `json:"media,omitzero"` - // Sets the privacy status of the video, will default to public + Media []SocialPostMediaParam `json:"media,omitzero"` + // Sets the privacy status of the video (maps to status.privacyStatus), will + // default to public // // Any of "public", "private", "unlisted". PrivacyStatus YoutubeConfigurationDtoPrivacyStatus `json:"privacy_status,omitzero"` + // YouTube video tags (maps to snippet.tags) + Tags []string `json:"tags,omitzero"` paramObj } @@ -2643,70 +1651,20 @@ func (r *YoutubeConfigurationDtoParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// The property URL is required. -type YoutubeConfigurationDtoMediaParam struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing param.Opt[bool] `json:"skip_processing,omitzero"` - // List of tags to attach to the media - Tags []YoutubeConfigurationDtoMediaTagParam `json:"tags,omitzero"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms,omitzero"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url,omitzero"` - paramObj -} - -func (r YoutubeConfigurationDtoMediaParam) MarshalJSON() (data []byte, err error) { - type shadow YoutubeConfigurationDtoMediaParam - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *YoutubeConfigurationDtoMediaParam) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// The properties ID, Platform, Type are required. -type YoutubeConfigurationDtoMediaTagParam struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform,omitzero" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type,omitzero" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X param.Opt[float64] `json:"x,omitzero"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y param.Opt[float64] `json:"y,omitzero"` +type YoutubeConfigurationDtoLocalizationParam struct { + Description param.Opt[string] `json:"description,omitzero"` + Title param.Opt[string] `json:"title,omitzero"` paramObj } -func (r YoutubeConfigurationDtoMediaTagParam) MarshalJSON() (data []byte, err error) { - type shadow YoutubeConfigurationDtoMediaTagParam +func (r YoutubeConfigurationDtoLocalizationParam) MarshalJSON() (data []byte, err error) { + type shadow YoutubeConfigurationDtoLocalizationParam return param.MarshalObject(r, (*shadow)(&r)) } -func (r *YoutubeConfigurationDtoMediaTagParam) UnmarshalJSON(data []byte) error { +func (r *YoutubeConfigurationDtoLocalizationParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -func init() { - apijson.RegisterFieldValidator[YoutubeConfigurationDtoMediaTagParam]( - "platform", "facebook", "instagram", - ) - apijson.RegisterFieldValidator[YoutubeConfigurationDtoMediaTagParam]( - "type", "user", "product", - ) -} - type SocialPostListResponse struct { Data []SocialPost `json:"data" api:"required"` Meta SocialPostListResponseMeta `json:"meta" api:"required"` @@ -2751,23 +1709,6 @@ func (r *SocialPostListResponseMeta) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type SocialPostDeleteResponse struct { - // Whether or not the entity was deleted - Success bool `json:"success" api:"required"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Success respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r SocialPostDeleteResponse) RawJSON() string { return r.JSON.raw } -func (r *SocialPostDeleteResponse) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - type SocialPostNewParams struct { CreateSocialPost CreateSocialPostParam paramObj diff --git a/socialpost_test.go b/socialpost_test.go index 2ae85ff..0cb4862 100644 --- a/socialpost_test.go +++ b/socialpost_test.go @@ -31,27 +31,39 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { CreateSocialPost: postforme.CreateSocialPostParam{ Caption: "caption", SocialAccounts: []string{"string"}, - AccountConfigurations: []postforme.CreateSocialPostAccountConfigurationParam{{ - Configuration: postforme.CreateSocialPostAccountConfigurationConfigurationParam{ + AccountConfigurations: []postforme.AccountConfigurationParam{{ + Configuration: postforme.AccountConfigurationConfigurationParam{ + Localizations: map[string]postforme.AccountConfigurationConfigurationLocalizationParam{ + "foo": { + Description: postforme.String("description"), + Title: postforme.String("title"), + }, + }, AllowComment: postforme.Bool(true), AllowDuet: postforme.Bool(true), AllowStitch: postforme.Bool(true), + AudioName: postforme.String("audio_name"), AutoAddMusic: postforme.Bool(true), BoardIDs: []string{"string"}, Caption: map[string]any{}, + CategoryID: postforme.String("category_id"), Collaborators: [][]any{{map[string]any{}}}, CommunityID: postforme.String("community_id"), + ContainsSyntheticMedia: postforme.Bool(true), + DefaultLanguage: postforme.String("default_language"), DiscloseBrandedContent: postforme.Bool(true), DiscloseYourBrand: postforme.Bool(true), + Embeddable: postforme.Bool(true), IsAIGenerated: postforme.Bool(true), IsDraft: postforme.Bool(true), + License: "youtube", Link: postforme.String("link"), Location: postforme.String("location"), MadeForKids: postforme.Bool(true), - Media: []postforme.CreateSocialPostAccountConfigurationConfigurationMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.CreateSocialPostAccountConfigurationConfigurationMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -62,16 +74,21 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { ThumbnailURL: map[string]any{}, }}, Placement: "reels", - Poll: postforme.CreateSocialPostAccountConfigurationConfigurationPollParam{ + Poll: postforme.TwitterPollParam{ DurationMinutes: 0, Options: []string{"string"}, - ReplySettings: "following", + ReplySettings: postforme.TwitterPollReplySettingsFollowing, }, PrivacyStatus: "public", + PublicStatsViewable: postforme.Bool(true), + PublishAt: postforme.String("publish_at"), QuoteTweetID: postforme.String("quote_tweet_id"), + RecordingDate: postforme.String("recording_date"), ReplySettings: "following", + ResharePostID: postforme.String("reshare_post_id"), SetCaptionForEachImage: postforme.Bool(true), ShareToFeed: postforme.Bool(true), + Tags: []string{"string"}, Title: postforme.String("title"), TrialReelType: "manual", }, @@ -79,10 +96,10 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { }}, ExternalID: postforme.String("external_id"), IsDraft: postforme.Bool(true), - Media: []postforme.CreateSocialPostMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.CreateSocialPostMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -95,10 +112,10 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { PlatformConfigurations: postforme.PlatformConfigurationsDtoParam{ Bluesky: postforme.BlueskyConfigurationDtoParam{ Caption: map[string]any{}, - Media: []postforme.BlueskyConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.BlueskyConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -113,10 +130,10 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { Caption: map[string]any{}, Collaborators: [][]any{{map[string]any{}}}, Location: postforme.String("location"), - Media: []postforme.FacebookConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.FacebookConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -130,13 +147,14 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { SetCaptionForEachImage: postforme.Bool(true), }, Instagram: postforme.InstagramConfigurationDtoParam{ + AudioName: postforme.String("audio_name"), Caption: map[string]any{}, Collaborators: []string{"string"}, Location: postforme.String("location"), - Media: []postforme.InstagramConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.InstagramConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -152,10 +170,10 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { }, Linkedin: postforme.LinkedinConfigurationDtoParam{ Caption: map[string]any{}, - Media: []postforme.LinkedinConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.LinkedinConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -165,15 +183,16 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { ThumbnailTimestampMs: map[string]any{}, ThumbnailURL: map[string]any{}, }}, + ResharePostID: postforme.String("reshare_post_id"), }, Pinterest: postforme.PinterestConfigurationDtoParam{ BoardIDs: []string{"string"}, Caption: map[string]any{}, Link: postforme.String("link"), - Media: []postforme.PinterestConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.PinterestConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -183,13 +202,14 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { ThumbnailTimestampMs: map[string]any{}, ThumbnailURL: map[string]any{}, }}, + Title: postforme.String("title"), }, Threads: postforme.ThreadsConfigurationDtoParam{ Caption: map[string]any{}, - Media: []postforme.ThreadsConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.ThreadsConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -211,10 +231,10 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { DiscloseYourBrand: postforme.Bool(true), IsAIGenerated: postforme.Bool(true), IsDraft: postforme.Bool(true), - Media: []postforme.TiktokConfigurationMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.TiktokConfigurationMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -237,10 +257,10 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { DiscloseYourBrand: postforme.Bool(true), IsAIGenerated: postforme.Bool(true), IsDraft: postforme.Bool(true), - Media: []postforme.TiktokConfigurationMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.TiktokConfigurationMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -256,10 +276,10 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { X: postforme.TwitterConfigurationDtoParam{ Caption: map[string]any{}, CommunityID: postforme.String("community_id"), - Media: []postforme.TwitterConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.TwitterConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -269,21 +289,33 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { ThumbnailTimestampMs: map[string]any{}, ThumbnailURL: map[string]any{}, }}, - Poll: postforme.TwitterConfigurationDtoPollParam{ + Poll: postforme.TwitterPollParam{ DurationMinutes: 0, Options: []string{"string"}, - ReplySettings: "following", + ReplySettings: postforme.TwitterPollReplySettingsFollowing, }, QuoteTweetID: postforme.String("quote_tweet_id"), ReplySettings: postforme.TwitterConfigurationDtoReplySettingsFollowing, }, Youtube: postforme.YoutubeConfigurationDtoParam{ - Caption: map[string]any{}, - MadeForKids: postforme.Bool(true), - Media: []postforme.YoutubeConfigurationDtoMediaParam{{ + Localizations: map[string]postforme.YoutubeConfigurationDtoLocalizationParam{ + "foo": { + Description: postforme.String("description"), + Title: postforme.String("title"), + }, + }, + Caption: map[string]any{}, + CategoryID: postforme.String("category_id"), + ContainsSyntheticMedia: postforme.Bool(true), + DefaultLanguage: postforme.String("default_language"), + Description: postforme.String("description"), + Embeddable: postforme.Bool(true), + License: postforme.YoutubeConfigurationDtoLicenseYoutube, + MadeForKids: postforme.Bool(true), + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.YoutubeConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -293,8 +325,12 @@ func TestSocialPostNewWithOptionalParams(t *testing.T) { ThumbnailTimestampMs: map[string]any{}, ThumbnailURL: map[string]any{}, }}, - PrivacyStatus: postforme.YoutubeConfigurationDtoPrivacyStatusPublic, - Title: postforme.String("title"), + PrivacyStatus: postforme.YoutubeConfigurationDtoPrivacyStatusPublic, + PublicStatsViewable: postforme.Bool(true), + PublishAt: postforme.String("publish_at"), + RecordingDate: postforme.String("recording_date"), + Tags: []string{"string"}, + Title: postforme.String("title"), }, }, ScheduledAt: postforme.Time(time.Now()), @@ -352,27 +388,39 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { CreateSocialPost: postforme.CreateSocialPostParam{ Caption: "caption", SocialAccounts: []string{"string"}, - AccountConfigurations: []postforme.CreateSocialPostAccountConfigurationParam{{ - Configuration: postforme.CreateSocialPostAccountConfigurationConfigurationParam{ + AccountConfigurations: []postforme.AccountConfigurationParam{{ + Configuration: postforme.AccountConfigurationConfigurationParam{ + Localizations: map[string]postforme.AccountConfigurationConfigurationLocalizationParam{ + "foo": { + Description: postforme.String("description"), + Title: postforme.String("title"), + }, + }, AllowComment: postforme.Bool(true), AllowDuet: postforme.Bool(true), AllowStitch: postforme.Bool(true), + AudioName: postforme.String("audio_name"), AutoAddMusic: postforme.Bool(true), BoardIDs: []string{"string"}, Caption: map[string]any{}, + CategoryID: postforme.String("category_id"), Collaborators: [][]any{{map[string]any{}}}, CommunityID: postforme.String("community_id"), + ContainsSyntheticMedia: postforme.Bool(true), + DefaultLanguage: postforme.String("default_language"), DiscloseBrandedContent: postforme.Bool(true), DiscloseYourBrand: postforme.Bool(true), + Embeddable: postforme.Bool(true), IsAIGenerated: postforme.Bool(true), IsDraft: postforme.Bool(true), + License: "youtube", Link: postforme.String("link"), Location: postforme.String("location"), MadeForKids: postforme.Bool(true), - Media: []postforme.CreateSocialPostAccountConfigurationConfigurationMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.CreateSocialPostAccountConfigurationConfigurationMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -383,16 +431,21 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { ThumbnailURL: map[string]any{}, }}, Placement: "reels", - Poll: postforme.CreateSocialPostAccountConfigurationConfigurationPollParam{ + Poll: postforme.TwitterPollParam{ DurationMinutes: 0, Options: []string{"string"}, - ReplySettings: "following", + ReplySettings: postforme.TwitterPollReplySettingsFollowing, }, PrivacyStatus: "public", + PublicStatsViewable: postforme.Bool(true), + PublishAt: postforme.String("publish_at"), QuoteTweetID: postforme.String("quote_tweet_id"), + RecordingDate: postforme.String("recording_date"), ReplySettings: "following", + ResharePostID: postforme.String("reshare_post_id"), SetCaptionForEachImage: postforme.Bool(true), ShareToFeed: postforme.Bool(true), + Tags: []string{"string"}, Title: postforme.String("title"), TrialReelType: "manual", }, @@ -400,10 +453,10 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { }}, ExternalID: postforme.String("external_id"), IsDraft: postforme.Bool(true), - Media: []postforme.CreateSocialPostMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.CreateSocialPostMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -416,10 +469,10 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { PlatformConfigurations: postforme.PlatformConfigurationsDtoParam{ Bluesky: postforme.BlueskyConfigurationDtoParam{ Caption: map[string]any{}, - Media: []postforme.BlueskyConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.BlueskyConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -434,10 +487,10 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { Caption: map[string]any{}, Collaborators: [][]any{{map[string]any{}}}, Location: postforme.String("location"), - Media: []postforme.FacebookConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.FacebookConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -451,13 +504,14 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { SetCaptionForEachImage: postforme.Bool(true), }, Instagram: postforme.InstagramConfigurationDtoParam{ + AudioName: postforme.String("audio_name"), Caption: map[string]any{}, Collaborators: []string{"string"}, Location: postforme.String("location"), - Media: []postforme.InstagramConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.InstagramConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -473,10 +527,10 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { }, Linkedin: postforme.LinkedinConfigurationDtoParam{ Caption: map[string]any{}, - Media: []postforme.LinkedinConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.LinkedinConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -486,15 +540,16 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { ThumbnailTimestampMs: map[string]any{}, ThumbnailURL: map[string]any{}, }}, + ResharePostID: postforme.String("reshare_post_id"), }, Pinterest: postforme.PinterestConfigurationDtoParam{ BoardIDs: []string{"string"}, Caption: map[string]any{}, Link: postforme.String("link"), - Media: []postforme.PinterestConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.PinterestConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -504,13 +559,14 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { ThumbnailTimestampMs: map[string]any{}, ThumbnailURL: map[string]any{}, }}, + Title: postforme.String("title"), }, Threads: postforme.ThreadsConfigurationDtoParam{ Caption: map[string]any{}, - Media: []postforme.ThreadsConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.ThreadsConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -532,10 +588,10 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { DiscloseYourBrand: postforme.Bool(true), IsAIGenerated: postforme.Bool(true), IsDraft: postforme.Bool(true), - Media: []postforme.TiktokConfigurationMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.TiktokConfigurationMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -558,10 +614,10 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { DiscloseYourBrand: postforme.Bool(true), IsAIGenerated: postforme.Bool(true), IsDraft: postforme.Bool(true), - Media: []postforme.TiktokConfigurationMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.TiktokConfigurationMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -577,10 +633,10 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { X: postforme.TwitterConfigurationDtoParam{ Caption: map[string]any{}, CommunityID: postforme.String("community_id"), - Media: []postforme.TwitterConfigurationDtoMediaParam{{ + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.TwitterConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -590,21 +646,33 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { ThumbnailTimestampMs: map[string]any{}, ThumbnailURL: map[string]any{}, }}, - Poll: postforme.TwitterConfigurationDtoPollParam{ + Poll: postforme.TwitterPollParam{ DurationMinutes: 0, Options: []string{"string"}, - ReplySettings: "following", + ReplySettings: postforme.TwitterPollReplySettingsFollowing, }, QuoteTweetID: postforme.String("quote_tweet_id"), ReplySettings: postforme.TwitterConfigurationDtoReplySettingsFollowing, }, Youtube: postforme.YoutubeConfigurationDtoParam{ - Caption: map[string]any{}, - MadeForKids: postforme.Bool(true), - Media: []postforme.YoutubeConfigurationDtoMediaParam{{ + Localizations: map[string]postforme.YoutubeConfigurationDtoLocalizationParam{ + "foo": { + Description: postforme.String("description"), + Title: postforme.String("title"), + }, + }, + Caption: map[string]any{}, + CategoryID: postforme.String("category_id"), + ContainsSyntheticMedia: postforme.Bool(true), + DefaultLanguage: postforme.String("default_language"), + Description: postforme.String("description"), + Embeddable: postforme.Bool(true), + License: postforme.YoutubeConfigurationDtoLicenseYoutube, + MadeForKids: postforme.Bool(true), + Media: []postforme.SocialPostMediaParam{{ URL: "url", SkipProcessing: postforme.Bool(true), - Tags: []postforme.YoutubeConfigurationDtoMediaTagParam{{ + Tags: []postforme.SocialPostMediaTagParam{{ ID: "id", Platform: "facebook", Type: "user", @@ -614,8 +682,12 @@ func TestSocialPostUpdateWithOptionalParams(t *testing.T) { ThumbnailTimestampMs: map[string]any{}, ThumbnailURL: map[string]any{}, }}, - PrivacyStatus: postforme.YoutubeConfigurationDtoPrivacyStatusPublic, - Title: postforme.String("title"), + PrivacyStatus: postforme.YoutubeConfigurationDtoPrivacyStatusPublic, + PublicStatsViewable: postforme.Bool(true), + PublishAt: postforme.String("publish_at"), + RecordingDate: postforme.String("recording_date"), + Tags: []string{"string"}, + Title: postforme.String("title"), }, }, ScheduledAt: postforme.Time(time.Now()), diff --git a/socialpostpreview.go b/socialpostpreview.go new file mode 100644 index 0000000..dbce927 --- /dev/null +++ b/socialpostpreview.go @@ -0,0 +1,136 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package postforme + +import ( + "context" + "net/http" + "slices" + + "github.com/DayMoonDevelopment/post-for-me-go/internal/apijson" + shimjson "github.com/DayMoonDevelopment/post-for-me-go/internal/encoding/json" + "github.com/DayMoonDevelopment/post-for-me-go/internal/requestconfig" + "github.com/DayMoonDevelopment/post-for-me-go/option" + "github.com/DayMoonDevelopment/post-for-me-go/packages/param" + "github.com/DayMoonDevelopment/post-for-me-go/packages/respjson" +) + +// Social Post Previews allow you to see what a Social Post will create for each +// account in the post. +// +// SocialPostPreviewService contains methods and other services that help with +// interacting with the post-for-me API. +// +// Note, unlike clients, this service does not read variables from the environment +// automatically. You should not instantiate this service directly, and instead use +// the [NewSocialPostPreviewService] method instead. +type SocialPostPreviewService struct { + Options []option.RequestOption +} + +// NewSocialPostPreviewService generates a new service that applies the given +// options to each request. These options are applied after the parent client's +// options (if there is one), and before any request-specific options. +func NewSocialPostPreviewService(opts ...option.RequestOption) (r SocialPostPreviewService) { + r = SocialPostPreviewService{} + r.Options = opts + return +} + +// Create Post Previews +func (r *SocialPostPreviewService) New(ctx context.Context, body SocialPostPreviewNewParams, opts ...option.RequestOption) (res *[]SocialPostPreview, err error) { + opts = slices.Concat(r.Options, opts) + path := "v1/social-post-previews" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return res, err +} + +// The properties Caption, PreviewSocialAccounts are required. +type CreateSocialPostPreviewParam struct { + // Caption text for the post + Caption string `json:"caption" api:"required"` + // Array of social accounts. Can preview non connected accounts, just specify a + // random ID + PreviewSocialAccounts []CreateSocialPostPreviewPreviewSocialAccountParam `json:"preview_social_accounts,omitzero" api:"required"` + // Account-specific configurations for the post + AccountConfigurations []AccountConfigurationParam `json:"account_configurations,omitzero"` + // Array of media URLs associated with the post + Media []SocialPostMediaParam `json:"media,omitzero"` + // Platform-specific configurations for the post + PlatformConfigurations PlatformConfigurationsDtoParam `json:"platform_configurations,omitzero"` + paramObj +} + +func (r CreateSocialPostPreviewParam) MarshalJSON() (data []byte, err error) { + type shadow CreateSocialPostPreviewParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *CreateSocialPostPreviewParam) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// The properties ID, Platform are required. +type CreateSocialPostPreviewPreviewSocialAccountParam struct { + // ID of the social account, ex: spc_12312 + ID string `json:"id" api:"required"` + // Platform of the social account + Platform string `json:"platform" api:"required"` + // username of the social account + Username param.Opt[string] `json:"username,omitzero"` + paramObj +} + +func (r CreateSocialPostPreviewPreviewSocialAccountParam) MarshalJSON() (data []byte, err error) { + type shadow CreateSocialPostPreviewPreviewSocialAccountParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *CreateSocialPostPreviewPreviewSocialAccountParam) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type SocialPostPreview struct { + // Caption text for the post + Caption string `json:"caption" api:"required"` + // Platform of the post + Platform string `json:"platform" api:"required"` + // Id of the social account + SocialAccountID string `json:"social_account_id" api:"required"` + // Additional configuration for this platform + Configuration any `json:"configuration"` + // Array of media URLs associated with the post + Media []SocialPostMedia `json:"media" api:"nullable"` + // Url of the social account profile picture + SocialAccountProfilePictureURL any `json:"social_account_profile_picture_url"` + // Username of the social account + SocialAccountUsername any `json:"social_account_username"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Caption respjson.Field + Platform respjson.Field + SocialAccountID respjson.Field + Configuration respjson.Field + Media respjson.Field + SocialAccountProfilePictureURL respjson.Field + SocialAccountUsername respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r SocialPostPreview) RawJSON() string { return r.JSON.raw } +func (r *SocialPostPreview) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type SocialPostPreviewNewParams struct { + CreateSocialPostPreview CreateSocialPostPreviewParam + paramObj +} + +func (r SocialPostPreviewNewParams) MarshalJSON() (data []byte, err error) { + return shimjson.Marshal(r.CreateSocialPostPreview) +} +func (r *SocialPostPreviewNewParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} diff --git a/socialpostpreview_test.go b/socialpostpreview_test.go new file mode 100644 index 0000000..ec8571d --- /dev/null +++ b/socialpostpreview_test.go @@ -0,0 +1,346 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package postforme_test + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/DayMoonDevelopment/post-for-me-go" + "github.com/DayMoonDevelopment/post-for-me-go/internal/testutil" + "github.com/DayMoonDevelopment/post-for-me-go/option" +) + +func TestSocialPostPreviewNewWithOptionalParams(t *testing.T) { + t.Skip("Mock server tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := postforme.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + _, err := client.SocialPostPreviews.New(context.TODO(), postforme.SocialPostPreviewNewParams{ + CreateSocialPostPreview: postforme.CreateSocialPostPreviewParam{ + Caption: "caption", + PreviewSocialAccounts: []postforme.CreateSocialPostPreviewPreviewSocialAccountParam{{ + ID: "id", + Platform: "platform", + Username: postforme.String("username"), + }}, + AccountConfigurations: []postforme.AccountConfigurationParam{{ + Configuration: postforme.AccountConfigurationConfigurationParam{ + Localizations: map[string]postforme.AccountConfigurationConfigurationLocalizationParam{ + "foo": { + Description: postforme.String("description"), + Title: postforme.String("title"), + }, + }, + AllowComment: postforme.Bool(true), + AllowDuet: postforme.Bool(true), + AllowStitch: postforme.Bool(true), + AudioName: postforme.String("audio_name"), + AutoAddMusic: postforme.Bool(true), + BoardIDs: []string{"string"}, + Caption: map[string]any{}, + CategoryID: postforme.String("category_id"), + Collaborators: [][]any{{map[string]any{}}}, + CommunityID: postforme.String("community_id"), + ContainsSyntheticMedia: postforme.Bool(true), + DefaultLanguage: postforme.String("default_language"), + DiscloseBrandedContent: postforme.Bool(true), + DiscloseYourBrand: postforme.Bool(true), + Embeddable: postforme.Bool(true), + IsAIGenerated: postforme.Bool(true), + IsDraft: postforme.Bool(true), + License: "youtube", + Link: postforme.String("link"), + Location: postforme.String("location"), + MadeForKids: postforme.Bool(true), + Media: []postforme.SocialPostMediaParam{{ + URL: "url", + SkipProcessing: postforme.Bool(true), + Tags: []postforme.SocialPostMediaTagParam{{ + ID: "id", + Platform: "facebook", + Type: "user", + X: postforme.Float(0), + Y: postforme.Float(0), + }}, + ThumbnailTimestampMs: map[string]any{}, + ThumbnailURL: map[string]any{}, + }}, + Placement: "reels", + Poll: postforme.TwitterPollParam{ + DurationMinutes: 0, + Options: []string{"string"}, + ReplySettings: postforme.TwitterPollReplySettingsFollowing, + }, + PrivacyStatus: "public", + PublicStatsViewable: postforme.Bool(true), + PublishAt: postforme.String("publish_at"), + QuoteTweetID: postforme.String("quote_tweet_id"), + RecordingDate: postforme.String("recording_date"), + ReplySettings: "following", + ResharePostID: postforme.String("reshare_post_id"), + SetCaptionForEachImage: postforme.Bool(true), + ShareToFeed: postforme.Bool(true), + Tags: []string{"string"}, + Title: postforme.String("title"), + TrialReelType: "manual", + }, + SocialAccountID: "social_account_id", + }}, + Media: []postforme.SocialPostMediaParam{{ + URL: "url", + SkipProcessing: postforme.Bool(true), + Tags: []postforme.SocialPostMediaTagParam{{ + ID: "id", + Platform: "facebook", + Type: "user", + X: postforme.Float(0), + Y: postforme.Float(0), + }}, + ThumbnailTimestampMs: map[string]any{}, + ThumbnailURL: map[string]any{}, + }}, + PlatformConfigurations: postforme.PlatformConfigurationsDtoParam{ + Bluesky: postforme.BlueskyConfigurationDtoParam{ + Caption: map[string]any{}, + Media: []postforme.SocialPostMediaParam{{ + URL: "url", + SkipProcessing: postforme.Bool(true), + Tags: []postforme.SocialPostMediaTagParam{{ + ID: "id", + Platform: "facebook", + Type: "user", + X: postforme.Float(0), + Y: postforme.Float(0), + }}, + ThumbnailTimestampMs: map[string]any{}, + ThumbnailURL: map[string]any{}, + }}, + }, + Facebook: postforme.FacebookConfigurationDtoParam{ + Caption: map[string]any{}, + Collaborators: [][]any{{map[string]any{}}}, + Location: postforme.String("location"), + Media: []postforme.SocialPostMediaParam{{ + URL: "url", + SkipProcessing: postforme.Bool(true), + Tags: []postforme.SocialPostMediaTagParam{{ + ID: "id", + Platform: "facebook", + Type: "user", + X: postforme.Float(0), + Y: postforme.Float(0), + }}, + ThumbnailTimestampMs: map[string]any{}, + ThumbnailURL: map[string]any{}, + }}, + Placement: postforme.FacebookConfigurationDtoPlacementReels, + SetCaptionForEachImage: postforme.Bool(true), + }, + Instagram: postforme.InstagramConfigurationDtoParam{ + AudioName: postforme.String("audio_name"), + Caption: map[string]any{}, + Collaborators: []string{"string"}, + Location: postforme.String("location"), + Media: []postforme.SocialPostMediaParam{{ + URL: "url", + SkipProcessing: postforme.Bool(true), + Tags: []postforme.SocialPostMediaTagParam{{ + ID: "id", + Platform: "facebook", + Type: "user", + X: postforme.Float(0), + Y: postforme.Float(0), + }}, + ThumbnailTimestampMs: map[string]any{}, + ThumbnailURL: map[string]any{}, + }}, + Placement: postforme.InstagramConfigurationDtoPlacementReels, + ShareToFeed: postforme.Bool(true), + TrialReelType: postforme.InstagramConfigurationDtoTrialReelTypeManual, + }, + Linkedin: postforme.LinkedinConfigurationDtoParam{ + Caption: map[string]any{}, + Media: []postforme.SocialPostMediaParam{{ + URL: "url", + SkipProcessing: postforme.Bool(true), + Tags: []postforme.SocialPostMediaTagParam{{ + ID: "id", + Platform: "facebook", + Type: "user", + X: postforme.Float(0), + Y: postforme.Float(0), + }}, + ThumbnailTimestampMs: map[string]any{}, + ThumbnailURL: map[string]any{}, + }}, + ResharePostID: postforme.String("reshare_post_id"), + }, + Pinterest: postforme.PinterestConfigurationDtoParam{ + BoardIDs: []string{"string"}, + Caption: map[string]any{}, + Link: postforme.String("link"), + Media: []postforme.SocialPostMediaParam{{ + URL: "url", + SkipProcessing: postforme.Bool(true), + Tags: []postforme.SocialPostMediaTagParam{{ + ID: "id", + Platform: "facebook", + Type: "user", + X: postforme.Float(0), + Y: postforme.Float(0), + }}, + ThumbnailTimestampMs: map[string]any{}, + ThumbnailURL: map[string]any{}, + }}, + Title: postforme.String("title"), + }, + Threads: postforme.ThreadsConfigurationDtoParam{ + Caption: map[string]any{}, + Media: []postforme.SocialPostMediaParam{{ + URL: "url", + SkipProcessing: postforme.Bool(true), + Tags: []postforme.SocialPostMediaTagParam{{ + ID: "id", + Platform: "facebook", + Type: "user", + X: postforme.Float(0), + Y: postforme.Float(0), + }}, + ThumbnailTimestampMs: map[string]any{}, + ThumbnailURL: map[string]any{}, + }}, + Placement: postforme.ThreadsConfigurationDtoPlacementReels, + }, + Tiktok: postforme.TiktokConfigurationParam{ + AllowComment: postforme.Bool(true), + AllowDuet: postforme.Bool(true), + AllowStitch: postforme.Bool(true), + AutoAddMusic: postforme.Bool(true), + Caption: map[string]any{}, + DiscloseBrandedContent: postforme.Bool(true), + DiscloseYourBrand: postforme.Bool(true), + IsAIGenerated: postforme.Bool(true), + IsDraft: postforme.Bool(true), + Media: []postforme.SocialPostMediaParam{{ + URL: "url", + SkipProcessing: postforme.Bool(true), + Tags: []postforme.SocialPostMediaTagParam{{ + ID: "id", + Platform: "facebook", + Type: "user", + X: postforme.Float(0), + Y: postforme.Float(0), + }}, + ThumbnailTimestampMs: map[string]any{}, + ThumbnailURL: map[string]any{}, + }}, + PrivacyStatus: postforme.String("privacy_status"), + Title: postforme.String("title"), + }, + TiktokBusiness: postforme.TiktokConfigurationParam{ + AllowComment: postforme.Bool(true), + AllowDuet: postforme.Bool(true), + AllowStitch: postforme.Bool(true), + AutoAddMusic: postforme.Bool(true), + Caption: map[string]any{}, + DiscloseBrandedContent: postforme.Bool(true), + DiscloseYourBrand: postforme.Bool(true), + IsAIGenerated: postforme.Bool(true), + IsDraft: postforme.Bool(true), + Media: []postforme.SocialPostMediaParam{{ + URL: "url", + SkipProcessing: postforme.Bool(true), + Tags: []postforme.SocialPostMediaTagParam{{ + ID: "id", + Platform: "facebook", + Type: "user", + X: postforme.Float(0), + Y: postforme.Float(0), + }}, + ThumbnailTimestampMs: map[string]any{}, + ThumbnailURL: map[string]any{}, + }}, + PrivacyStatus: postforme.String("privacy_status"), + Title: postforme.String("title"), + }, + X: postforme.TwitterConfigurationDtoParam{ + Caption: map[string]any{}, + CommunityID: postforme.String("community_id"), + Media: []postforme.SocialPostMediaParam{{ + URL: "url", + SkipProcessing: postforme.Bool(true), + Tags: []postforme.SocialPostMediaTagParam{{ + ID: "id", + Platform: "facebook", + Type: "user", + X: postforme.Float(0), + Y: postforme.Float(0), + }}, + ThumbnailTimestampMs: map[string]any{}, + ThumbnailURL: map[string]any{}, + }}, + Poll: postforme.TwitterPollParam{ + DurationMinutes: 0, + Options: []string{"string"}, + ReplySettings: postforme.TwitterPollReplySettingsFollowing, + }, + QuoteTweetID: postforme.String("quote_tweet_id"), + ReplySettings: postforme.TwitterConfigurationDtoReplySettingsFollowing, + }, + Youtube: postforme.YoutubeConfigurationDtoParam{ + Localizations: map[string]postforme.YoutubeConfigurationDtoLocalizationParam{ + "foo": { + Description: postforme.String("description"), + Title: postforme.String("title"), + }, + }, + Caption: map[string]any{}, + CategoryID: postforme.String("category_id"), + ContainsSyntheticMedia: postforme.Bool(true), + DefaultLanguage: postforme.String("default_language"), + Description: postforme.String("description"), + Embeddable: postforme.Bool(true), + License: postforme.YoutubeConfigurationDtoLicenseYoutube, + MadeForKids: postforme.Bool(true), + Media: []postforme.SocialPostMediaParam{{ + URL: "url", + SkipProcessing: postforme.Bool(true), + Tags: []postforme.SocialPostMediaTagParam{{ + ID: "id", + Platform: "facebook", + Type: "user", + X: postforme.Float(0), + Y: postforme.Float(0), + }}, + ThumbnailTimestampMs: map[string]any{}, + ThumbnailURL: map[string]any{}, + }}, + PrivacyStatus: postforme.YoutubeConfigurationDtoPrivacyStatusPublic, + PublicStatsViewable: postforme.Bool(true), + PublishAt: postforme.String("publish_at"), + RecordingDate: postforme.String("recording_date"), + Tags: []string{"string"}, + Title: postforme.String("title"), + }, + }, + }, + }) + if err != nil { + var apierr *postforme.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} diff --git a/socialpostresult.go b/socialpostresult.go index 1babe05..bca26ca 100644 --- a/socialpostresult.go +++ b/socialpostresult.go @@ -72,7 +72,7 @@ type SocialPostResult struct { // Error message if the post failed Error any `json:"error" api:"required"` // Array of media URLs associated with the post - Media []SocialPostResultMedia `json:"media" api:"required"` + Media []SocialPostMedia `json:"media" api:"required"` // Platform-specific data PlatformData SocialPostResultPlatformData `json:"platform_data" api:"required"` // The ID of the associated post @@ -102,73 +102,6 @@ func (r *SocialPostResult) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type SocialPostResultMedia struct { - // Public URL of the media - URL string `json:"url" api:"required"` - // If true the media will not be processed at all and instead be posted as is, this - // may increase chance of post failure if media does not meet platform's - // requirements. Best used for larger files. - SkipProcessing bool `json:"skip_processing" api:"nullable"` - // List of tags to attach to the media - Tags []SocialPostResultMediaTag `json:"tags" api:"nullable"` - // Timestamp in milliseconds of frame to use as thumbnail for the media - ThumbnailTimestampMs any `json:"thumbnail_timestamp_ms" api:"nullable"` - // Public URL of the thumbnail for the media - ThumbnailURL any `json:"thumbnail_url" api:"nullable"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - URL respjson.Field - SkipProcessing respjson.Field - Tags respjson.Field - ThumbnailTimestampMs respjson.Field - ThumbnailURL respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r SocialPostResultMedia) RawJSON() string { return r.JSON.raw } -func (r *SocialPostResultMedia) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type SocialPostResultMediaTag struct { - // Facebook User ID, Instagram Username or Instagram product id to tag - ID string `json:"id" api:"required"` - // The platform for the tags - // - // Any of "facebook", "instagram". - Platform string `json:"platform" api:"required"` - // The type of tag, user to tag accounts, product to tag products (only supported - // for instagram) - // - // Any of "user", "product". - Type string `json:"type" api:"required"` - // Percentage distance from left edge of the image, Not required for videos or - // stories - X float64 `json:"x"` - // Percentage distance from top edge of the image, Not required for videos or - // stories - Y float64 `json:"y"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - ID respjson.Field - Platform respjson.Field - Type respjson.Field - X respjson.Field - Y respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r SocialPostResultMediaTag) RawJSON() string { return r.JSON.raw } -func (r *SocialPostResultMediaTag) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - // Platform-specific data type SocialPostResultPlatformData struct { // Platform-specific ID diff --git a/webhook.go b/webhook.go new file mode 100644 index 0000000..6bcb96a --- /dev/null +++ b/webhook.go @@ -0,0 +1,257 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package postforme + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "slices" + + "github.com/DayMoonDevelopment/post-for-me-go/internal/apijson" + "github.com/DayMoonDevelopment/post-for-me-go/internal/apiquery" + "github.com/DayMoonDevelopment/post-for-me-go/internal/requestconfig" + "github.com/DayMoonDevelopment/post-for-me-go/option" + "github.com/DayMoonDevelopment/post-for-me-go/packages/param" + "github.com/DayMoonDevelopment/post-for-me-go/packages/respjson" +) + +// Webhooks enable you to subscribe to certain events. This involves Post for Me +// making a POST request to the URL of any webhooks you create. Only the events you +// subscribe to will be sent to your webhook URL. +// +// ## Payload +// +// When an event happens that your webhook is subscribed to, we will make a POST +// request with the following JSON body +// +// ``` +// +// { +// "event_type": "", +// "data": {} +// } +// +// ``` +// +// The event_type will be the event that triggered the webhook POST, data will be +// the resulting entity from the event +// +// ## Security +// +// To verify the POST to your webhook URL is from us we will include a secret in +// the header "Post-For-Me-Webhook-Secret". When you create a webhook you will +// receive the secret in the response. +// +// ## Retries +// +// If your server fails to respond with a 2XX code, requests to it will be retried +// with exponential backoff around 8 times over the course of just over a day. +// +// WebhookService contains methods and other services that help with interacting +// with the post-for-me API. +// +// Note, unlike clients, this service does not read variables from the environment +// automatically. You should not instantiate this service directly, and instead use +// the [NewWebhookService] method instead. +type WebhookService struct { + Options []option.RequestOption +} + +// NewWebhookService generates a new service that applies the given options to each +// request. These options are applied after the parent client's options (if there +// is one), and before any request-specific options. +func NewWebhookService(opts ...option.RequestOption) (r WebhookService) { + r = WebhookService{} + r.Options = opts + return +} + +// Create Webhook +func (r *WebhookService) New(ctx context.Context, body WebhookNewParams, opts ...option.RequestOption) (res *Webhook, err error) { + opts = slices.Concat(r.Options, opts) + path := "v1/webhooks" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return res, err +} + +// Get webhook by ID +func (r *WebhookService) Get(ctx context.Context, id string, opts ...option.RequestOption) (res *Webhook, err error) { + opts = slices.Concat(r.Options, opts) + if id == "" { + err = errors.New("missing required id parameter") + return nil, err + } + path := fmt.Sprintf("v1/webhooks/%s", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return res, err +} + +// Update Webhook +func (r *WebhookService) Update(ctx context.Context, id string, body WebhookUpdateParams, opts ...option.RequestOption) (res *Webhook, err error) { + opts = slices.Concat(r.Options, opts) + if id == "" { + err = errors.New("missing required id parameter") + return nil, err + } + path := fmt.Sprintf("v1/webhooks/%s", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...) + return res, err +} + +// Get a paginated result for webhooks based on the applied filters +func (r *WebhookService) List(ctx context.Context, query WebhookListParams, opts ...option.RequestOption) (res *WebhookListResponse, err error) { + opts = slices.Concat(r.Options, opts) + path := "v1/webhooks" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...) + return res, err +} + +// Delete Webhook +func (r *WebhookService) Delete(ctx context.Context, id string, opts ...option.RequestOption) (res *DeleteEntityResponse, err error) { + opts = slices.Concat(r.Options, opts) + if id == "" { + err = errors.New("missing required id parameter") + return nil, err + } + path := fmt.Sprintf("v1/webhooks/%s", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, &res, opts...) + return res, err +} + +type Webhook struct { + // The unique identifier of the webhook + ID string `json:"id" api:"required"` + // Events that will be sent to the webhook + EventTypes []string `json:"event_types" api:"required"` + // Secret key used to verify webhook post + Secret string `json:"secret" api:"required"` + // The public webhook url + URL string `json:"url" api:"required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + ID respjson.Field + EventTypes respjson.Field + Secret respjson.Field + URL respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r Webhook) RawJSON() string { return r.JSON.raw } +func (r *Webhook) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type WebhookListResponse struct { + Data []Webhook `json:"data" api:"required"` + Meta WebhookListResponseMeta `json:"meta" api:"required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Data respjson.Field + Meta respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r WebhookListResponse) RawJSON() string { return r.JSON.raw } +func (r *WebhookListResponse) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type WebhookListResponseMeta struct { + // Maximum number of items returned. + Limit float64 `json:"limit" api:"required"` + // URL to the next page of results, or null if none. + Next string `json:"next" api:"required"` + // Number of items skipped. + Offset float64 `json:"offset" api:"required"` + // Total number of items available. + Total float64 `json:"total" api:"required"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Limit respjson.Field + Next respjson.Field + Offset respjson.Field + Total respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r WebhookListResponseMeta) RawJSON() string { return r.JSON.raw } +func (r *WebhookListResponseMeta) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type WebhookNewParams struct { + // List of events the webhook will recieve + // + // Any of "social.post.created", "social.post.updated", "social.post.deleted", + // "social.post.result.created", "social.account.created", + // "social.account.updated". + EventTypes []string `json:"event_types,omitzero" api:"required"` + // Public url to recieve event data + URL string `json:"url" api:"required"` + paramObj +} + +func (r WebhookNewParams) MarshalJSON() (data []byte, err error) { + type shadow WebhookNewParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *WebhookNewParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type WebhookUpdateParams struct { + // Public url to recieve event data + URL param.Opt[string] `json:"url,omitzero"` + // List of events the webhook will recieve + // + // Any of "social.post.created", "social.post.updated", "social.post.deleted", + // "social.post.result.created", "social.account.created", + // "social.account.updated". + EventTypes []string `json:"event_types,omitzero"` + paramObj +} + +func (r WebhookUpdateParams) MarshalJSON() (data []byte, err error) { + type shadow WebhookUpdateParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *WebhookUpdateParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type WebhookListParams struct { + // Number of items to return + Limit param.Opt[float64] `query:"limit,omitzero" json:"-"` + // Number of items to skip + Offset param.Opt[float64] `query:"offset,omitzero" json:"-"` + // Filter by id(s). Multiple values imply OR logic (e.g., + // ?id=wbh_xxxxxx&id=wbh_yyyyyy). + ID []string `query:"id,omitzero" json:"-"` + // Filter by event type(s). Multiple values imply OR logic (e.g., + // ?event_type=social.post.created&event_type=social.post.updated). + EventType []string `query:"event_type,omitzero" json:"-"` + // Filter by url(s). Multiple values imply OR logic (e.g., + // ?url=https://example.com&url=https://postforme.dev). + URL []string `query:"url,omitzero" json:"-"` + paramObj +} + +// URLQuery serializes [WebhookListParams]'s query parameters as `url.Values`. +func (r WebhookListParams) URLQuery() (v url.Values, err error) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatComma, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} diff --git a/webhook_test.go b/webhook_test.go new file mode 100644 index 0000000..64027fb --- /dev/null +++ b/webhook_test.go @@ -0,0 +1,145 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package postforme_test + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/DayMoonDevelopment/post-for-me-go" + "github.com/DayMoonDevelopment/post-for-me-go/internal/testutil" + "github.com/DayMoonDevelopment/post-for-me-go/option" +) + +func TestWebhookNew(t *testing.T) { + t.Skip("Mock server tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := postforme.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + _, err := client.Webhooks.New(context.TODO(), postforme.WebhookNewParams{ + EventTypes: []string{"social.post.created"}, + URL: "url", + }) + if err != nil { + var apierr *postforme.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestWebhookGet(t *testing.T) { + t.Skip("Mock server tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := postforme.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + _, err := client.Webhooks.Get(context.TODO(), "id") + if err != nil { + var apierr *postforme.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestWebhookUpdateWithOptionalParams(t *testing.T) { + t.Skip("Mock server tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := postforme.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + _, err := client.Webhooks.Update( + context.TODO(), + "id", + postforme.WebhookUpdateParams{ + EventTypes: []string{"social.post.created"}, + URL: postforme.String("url"), + }, + ) + if err != nil { + var apierr *postforme.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestWebhookListWithOptionalParams(t *testing.T) { + t.Skip("Mock server tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := postforme.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + _, err := client.Webhooks.List(context.TODO(), postforme.WebhookListParams{ + ID: []string{"string"}, + EventType: []string{"string"}, + Limit: postforme.Float(0), + Offset: postforme.Float(0), + URL: []string{"string"}, + }) + if err != nil { + var apierr *postforme.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestWebhookDelete(t *testing.T) { + t.Skip("Mock server tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := postforme.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + _, err := client.Webhooks.Delete(context.TODO(), "id") + if err != nil { + var apierr *postforme.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +}