diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8510da..e857feb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - name: Link staging branch if: github.repository == 'stainless-sdks/x-twitter-scraper-cli' run: | - ./scripts/link 'github.com/stainless-sdks/x-twitter-scraper-go@${{ github.ref_name }}' || go mod edit -dropreplace='github.com/stainless-sdks/x-twitter-scraper-go' + ./scripts/link 'github.com/stainless-sdks/x-twitter-scraper-go@${{ github.ref_name }}' || true - name: Bootstrap run: ./scripts/bootstrap @@ -60,7 +60,7 @@ jobs: - name: Link staging branch if: github.repository == 'stainless-sdks/x-twitter-scraper-cli' run: | - ./scripts/link 'github.com/stainless-sdks/x-twitter-scraper-go@${{ github.ref_name }}' || go mod edit -dropreplace='github.com/stainless-sdks/x-twitter-scraper-go' + ./scripts/link 'github.com/stainless-sdks/x-twitter-scraper-go@${{ github.ref_name }}' || true - name: Bootstrap run: ./scripts/bootstrap @@ -107,7 +107,7 @@ jobs: - name: Link staging branch if: github.repository == 'stainless-sdks/x-twitter-scraper-cli' run: | - ./scripts/link 'github.com/stainless-sdks/x-twitter-scraper-go@${{ github.ref_name }}' || go mod edit -dropreplace='github.com/stainless-sdks/x-twitter-scraper-go' + ./scripts/link 'github.com/stainless-sdks/x-twitter-scraper-go@${{ github.ref_name }}' || true - name: Bootstrap run: ./scripts/bootstrap diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 263859c..0858063 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -10,6 +10,7 @@ on: push: tags: - "v*" + workflow_dispatch: {} jobs: goreleaser: runs-on: ubuntu-latest diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6b7b74c..da59f99 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.3.0" + ".": "0.4.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index db87955..d470135 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 117 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/xquik%2Fx-twitter-scraper-d40c57a05527faf060d21c0e013729f371d88017b10680cea7c8fd6780ffaef5.yml -openapi_spec_hash: 597ebc460cf86740b9f6f7c95478dece -config_hash: 30ce23c9cfbf8fb8be9e5dd28a2124fa +configured_endpoints: 110 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/xquik%2Fx-twitter-scraper-2adc33156b4b42a4be18cc20c0205b38f0432d7958da99c65ee9b3f6a555ea0e.yml +openapi_spec_hash: be760f5620a268521d6793f65576a61f +config_hash: 320a9cb2f1293d1a7b73c63ab5865af5 diff --git a/CHANGELOG.md b/CHANGELOG.md index e4a67cb..ab52538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # Changelog +## 0.4.0 (2026-04-25) + +Full Changelog: [v0.3.0...v0.4.0](https://github.com/Xquik-dev/x-twitter-scraper-cli/compare/v0.3.0...v0.4.0) + +### Features + +* **api:** api update ([e9d6bea](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/e9d6beaf3b434b0c063fb0a25d92da9aebc8c970)) +* **api:** api update ([607c495](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/607c49591bc8d2f0c93a99ba360f46540373b4c4)) +* **api:** api update ([7197e8f](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/7197e8f7ae0b37957eec880dd03a9786eb23caa8)) +* **cli:** add `--raw-output`/`-r` option to print raw (non-JSON) strings ([28be4fa](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/28be4fac015d4fa532e8714b15634baafab111c0)) +* **cli:** alias parameters in data with `x-stainless-cli-data-alias` ([96d1bfc](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/96d1bfca315fcfa3b602b6007e6452efc7ef869b)) +* **cli:** send filename and content type when reading input from files ([4dc5824](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/4dc58248eec53b50faf98eb2869e27ddf15618d4)) + + +### Bug Fixes + +* escape ampersand in OpenAPI summaries for C# XML docs ([6d5da37](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/6d5da374e3a40fcaf3d31945666770843d537ee5)) +* fix for failing to drop invalid module replace in link script ([a086faf](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/a086fafbb77d57fb4b2c1330e526c299b2c8e0de)) + + +### Chores + +* add documentation for ./scripts/link ([1fe38e3](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/1fe38e359d3863fc12c588dcd2db8164a67a10e1)) +* **ci:** support manually triggering release workflow ([c947c68](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/c947c68b51f8141b8ed6030b641f34ef783c53e3)) +* **cli:** additional test cases for `ShowJSONIterator` ([3d20d69](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/3d20d69c87f77ece9fb46b3f58da0948fa21f601)) +* **cli:** fall back to JSON when using default "explore" with non-TTY ([f48c1f0](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/f48c1f013e9c850b7780e4d1760dca7ac16f62b8)) +* **cli:** let `--format raw` be used in conjunction with `--transform` ([5a0010a](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/5a0010abd09ae3e5d51b8a3536a4b662b328bd8e)) +* **cli:** switch long lists of positional args over to param structs ([21a0bc9](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/21a0bc997e853879adc640cc2114861ab9f4018a)) +* **cli:** use `ShowJSONOpts` as argument to `formatJSON` instead of many positionals ([9a34472](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/9a344724e7f20169211f27eb1f3d3122a7aa78ca)) +* **internal:** codegen related update ([f783c02](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/f783c028f7ca4fbd5dd87b83488930c007598a8f)) +* **internal:** more robust bootstrap script ([262e4d3](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/262e4d3ee4b8d64258ffda8ca1f8d283bdbc35bf)) +* sync OpenAPI spec ([0f0687b](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/0f0687b9a0ea1e35149eb38d2899c649a1991195)) +* wire production_repo for all targets ([d46e8ec](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/d46e8ecbc08aac3f02d31b6d2674a19f27393662)) + + +### Documentation + +* add Contributor Covenant 2.1 Code of Conduct ([#2192](https://github.com/Xquik-dev/x-twitter-scraper-cli/issues/2192)) ([c253bcb](https://github.com/Xquik-dev/x-twitter-scraper-cli/commit/c253bcbbef2f104a891f63807058763a505162a3)) + ## 0.3.0 (2026-04-08) Full Changelog: [v0.2.0...v0.3.0](https://github.com/Xquik-dev/x-twitter-scraper-cli/compare/v0.2.0...v0.3.0) diff --git a/README.md b/README.md index 51e8893..6d195f2 100644 --- a/README.md +++ b/README.md @@ -115,3 +115,23 @@ base64-encoding). Note that absolute paths will begin with `@file://` or ```bash x-twitter-scraper --arg @data://file.txt ``` + +## Linking different Go SDK versions + +You can link the CLI against a different version of the X Twitter Scraper Go SDK +for development purposes using the `./scripts/link` script. + +To link to a specific version from a repository (version can be a branch, +git tag, or commit hash): + +```bash +./scripts/link github.com/org/repo@version +``` + +To link to a local copy of the SDK: + +```bash +./scripts/link ../path/to/xtwitterscraper-go +``` + +If you run the link script without any arguments, it will default to `../xtwitterscraper-go`. diff --git a/cmd/x-twitter-scraper/main.go b/cmd/x-twitter-scraper/main.go index 783786f..1f83760 100644 --- a/cmd/x-twitter-scraper/main.go +++ b/cmd/x-twitter-scraper/main.go @@ -43,7 +43,12 @@ func main() { fmt.Fprintf(os.Stderr, "%s %q: %d %s\n", apierr.Request.Method, apierr.Request.URL, apierr.Response.StatusCode, http.StatusText(apierr.Response.StatusCode)) format := app.String("format-error") json := gjson.Parse(apierr.RawJSON()) - show_err := cmd.ShowJSON(os.Stdout, "Error", json, format, app.String("transform-error")) + show_err := cmd.ShowJSON(json, cmd.ShowJSONOpts{ + ExplicitFormat: app.IsSet("format-error"), + Format: format, + Title: "Error", + Transform: app.String("transform-error"), + }) if show_err != nil { // Just print the original error: fmt.Fprintf(os.Stderr, "%s\n", err.Error()) diff --git a/go.mod b/go.mod index 6fd30ad..49e11a9 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Xquik-dev/x-twitter-scraper-cli go 1.25 require ( - github.com/Xquik-dev/x-twitter-scraper-go v0.3.0 + github.com/Xquik-dev/x-twitter-scraper-go v0.4.0 github.com/charmbracelet/bubbles v0.21.0 github.com/charmbracelet/bubbletea v1.3.6 github.com/charmbracelet/lipgloss v1.1.0 diff --git a/go.sum b/go.sum index f8d633f..16352a5 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Xquik-dev/x-twitter-scraper-go v0.3.0 h1:Iu2ejXyVKqikAWAlCgfGNeav5Q+3/V6lscdmUKYGB/8= -github.com/Xquik-dev/x-twitter-scraper-go v0.3.0/go.mod h1:OHW3aIR8E3+ANa/mjFTZs1sG7ePzrBEmW0a8JUN+NvI= +github.com/Xquik-dev/x-twitter-scraper-go v0.4.0 h1:np1LceO4i48617H41xpm6kH7e57ZV11RMwCAdeVhTzc= +github.com/Xquik-dev/x-twitter-scraper-go v0.4.0/go.mod h1:OHW3aIR8E3+ANa/mjFTZs1sG7ePzrBEmW0a8JUN+NvI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= diff --git a/internal/requestflag/innerflag.go b/internal/requestflag/innerflag.go index 102624f..eeeb8bc 100644 --- a/internal/requestflag/innerflag.go +++ b/internal/requestflag/innerflag.go @@ -22,14 +22,29 @@ type InnerFlag[ Aliases []string // aliases that are allowed for this flag Validator func(T) error // custom function to validate this flag value - OuterFlag cli.Flag // The flag on which this inner flag will set values - InnerField string // The inner field which this flag will set + OuterFlag cli.Flag // The flag on which this inner flag will set values + InnerField string // The inner field which this flag will set + DataAliases []string // alternate names recognized in YAML values passed as the outer flag +} + +// GetDataAliases returns the aliases recognized when parsing inner field keys from piped or flag YAML. +func (f *InnerFlag[T]) GetDataAliases() []string { + return f.DataAliases +} + +// GetInnerField returns the API field name that this inner flag sets on its outer flag's value. +// For example, the flag --parent.foo targeting a parameter whose OpenAPI property name is "foo" +// would return "foo". This is distinct from the flag's CLI name and from any DataAliases entries. +func (f *InnerFlag[T]) GetInnerField() string { + return f.InnerField } type HasOuterFlag interface { cli.Flag SetOuterFlag(cli.Flag) GetOuterFlag() cli.Flag + GetInnerField() string + GetDataAliases() []string } func (f *InnerFlag[T]) SetOuterFlag(flag cli.Flag) { diff --git a/internal/requestflag/requestflag.go b/internal/requestflag/requestflag.go index bdef64f..bfaf064 100644 --- a/internal/requestflag/requestflag.go +++ b/internal/requestflag/requestflag.go @@ -48,6 +48,10 @@ type Flag[ // binary` in the OpenAPI spec. FileInput bool + // DataAliases is a list of alternate names for this parameter recognized when parsing piped YAML/JSON + // input. Values keyed by any alias are translated to the canonical API name before being sent. + DataAliases []string + // unexported fields for internal use count int // number of times the flag has been set hasBeenSet bool // whether the flag has been set from env or file @@ -65,6 +69,7 @@ type InRequest interface { GetBodyPath() string IsBodyRoot() bool IsFileInput() bool + GetDataAliases() []string } func (f Flag[T]) GetQueryPath() string { @@ -87,6 +92,10 @@ func (f Flag[T]) IsFileInput() bool { return f.FileInput } +func (f Flag[T]) GetDataAliases() []string { + return f.DataAliases +} + // The values that will be sent in different parts of a request. type RequestContents struct { Queries map[string]any diff --git a/pkg/cmd/account.go b/pkg/cmd/account.go index 207bb00..537d90e 100644 --- a/pkg/cmd/account.go +++ b/pkg/cmd/account.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -84,8 +83,15 @@ func handleAccountRetrieve(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "account retrieve", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "account retrieve", + Transform: transform, + }) } func handleAccountSetXUsername(ctx context.Context, cmd *cli.Command) error { @@ -118,8 +124,15 @@ func handleAccountSetXUsername(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "account set-x-username", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "account set-x-username", + Transform: transform, + }) } func handleAccountUpdateLocale(ctx context.Context, cmd *cli.Command) error { @@ -152,6 +165,13 @@ func handleAccountUpdateLocale(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "account update-locale", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "account update-locale", + Transform: transform, + }) } diff --git a/pkg/cmd/account_test.go b/pkg/cmd/account_test.go index 4933e5a..c89541c 100644 --- a/pkg/cmd/account_test.go +++ b/pkg/cmd/account_test.go @@ -14,7 +14,6 @@ func TestAccountRetrieve(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "account", "retrieve", ) }) @@ -26,7 +25,6 @@ func TestAccountSetXUsername(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "account", "set-x-username", "--username", "elonmusk", ) @@ -38,7 +36,6 @@ func TestAccountSetXUsername(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "account", "set-x-username", ) }) @@ -50,7 +47,6 @@ func TestAccountUpdateLocale(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "account", "update-locale", "--locale", "en", ) @@ -62,7 +58,6 @@ func TestAccountUpdateLocale(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "account", "update-locale", ) }) diff --git a/pkg/cmd/apikey.go b/pkg/cmd/apikey.go index 92dae72..5a47f5c 100644 --- a/pkg/cmd/apikey.go +++ b/pkg/cmd/apikey.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -82,8 +81,15 @@ func handleAPIKeysCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "api-keys create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "api-keys create", + Transform: transform, + }) } func handleAPIKeysList(ctx context.Context, cmd *cli.Command) error { @@ -114,8 +120,15 @@ func handleAPIKeysList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "api-keys list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "api-keys list", + Transform: transform, + }) } func handleAPIKeysRevoke(ctx context.Context, cmd *cli.Command) error { @@ -149,6 +162,13 @@ func handleAPIKeysRevoke(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "api-keys revoke", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "api-keys revoke", + Transform: transform, + }) } diff --git a/pkg/cmd/apikey_test.go b/pkg/cmd/apikey_test.go index 5ed6b9a..a54f91e 100644 --- a/pkg/cmd/apikey_test.go +++ b/pkg/cmd/apikey_test.go @@ -14,7 +14,6 @@ func TestAPIKeysCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "api-keys", "create", "--name", "My API Key", ) @@ -26,7 +25,6 @@ func TestAPIKeysCreate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "api-keys", "create", ) }) @@ -38,7 +36,6 @@ func TestAPIKeysList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "api-keys", "list", ) }) @@ -50,7 +47,6 @@ func TestAPIKeysRevoke(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "api-keys", "revoke", "--id", "id", ) diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 3c4cff4..6ce0d67 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -73,6 +73,11 @@ func init() { Name: "transform-error", Usage: "The GJSON transformation for errors.", }, + &cli.BoolFlag{ + Name: "raw-output", + Aliases: []string{"r"}, + Usage: "If the result is a string, print it without JSON quotes. This can be useful for making output transforms talk to non-JSON-based systems.", + }, &requestflag.Flag[string]{ Name: "api-key", Sources: cli.EnvVars("X_TWITTER_SCRAPER_API_KEY"), @@ -210,20 +215,6 @@ func init() { &webhooksTest, }, }, - { - Name: "integrations", - Category: "API RESOURCE", - Suggest: true, - Commands: []*cli.Command{ - &integrationsCreate, - &integrationsRetrieve, - &integrationsUpdate, - &integrationsList, - &integrationsDelete, - &integrationsListDeliveries, - &integrationsSendTest, - }, - }, { Name: "x", Category: "API RESOURCE", diff --git a/pkg/cmd/cmdutil.go b/pkg/cmd/cmdutil.go index 7366efb..1d2ebda 100644 --- a/pkg/cmd/cmdutil.go +++ b/pkg/cmd/cmdutil.go @@ -314,21 +314,29 @@ func shouldUseColors(w io.Writer) bool { return isTerminal(w) } -func formatJSON(expectedOutput *os.File, title string, res gjson.Result, format string, transform string) ([]byte, error) { - if format != "raw" && transform != "" { - transformed := res.Get(transform) +func formatJSON(res gjson.Result, opts ShowJSONOpts) ([]byte, error) { + if opts.Transform != "" { + transformed := res.Get(opts.Transform) if transformed.Exists() { res = transformed } } - switch strings.ToLower(format) { + // Modeled after `jq -r` (`--raw-output`): if the result is a string, print it without JSON quotes so that + // it's easier to pipe into other programs. + if opts.RawOutput && res.Type == gjson.String { + return []byte(res.Str + "\n"), nil + } + switch strings.ToLower(opts.Format) { case "auto": - return formatJSON(expectedOutput, title, res, "json", "") + autoOpts := opts + autoOpts.Format = "json" + autoOpts.Transform = "" + return formatJSON(res, autoOpts) case "pretty": - return []byte(jsonview.RenderJSON(title, res) + "\n"), nil + return []byte(jsonview.RenderJSON(opts.Title, res) + "\n"), nil case "json": prettyJSON := pretty.Pretty([]byte(res.Raw)) - if shouldUseColors(expectedOutput) { + if shouldUseColors(opts.Stdout) { return pretty.Color(prettyJSON, pretty.TerminalStyle), nil } else { return prettyJSON, nil @@ -336,7 +344,7 @@ func formatJSON(expectedOutput *os.File, title string, res gjson.Result, format case "jsonl": // @ugly is gjson syntax for "no whitespace", so it fits on one line oneLineJSON := res.Get("@ugly").Raw - if shouldUseColors(expectedOutput) { + if shouldUseColors(opts.Stdout) { bytes := append(pretty.Color([]byte(oneLineJSON), pretty.TerminalStyle), '\n') return bytes, nil } else { @@ -350,34 +358,67 @@ func formatJSON(expectedOutput *os.File, title string, res gjson.Result, format if err := json2yaml.Convert(&yaml, input); err != nil { return nil, err } - _, err := expectedOutput.Write([]byte(yaml.String())) + _, err := opts.Stdout.Write([]byte(yaml.String())) return nil, err default: - return nil, fmt.Errorf("Invalid format: %s, valid formats are: %s", format, strings.Join(OutputFormats, ", ")) + return nil, fmt.Errorf("Invalid format: %s, valid formats are: %s", opts.Format, strings.Join(OutputFormats, ", ")) } } -// Display JSON to the user in various different formats -func ShowJSON(out *os.File, title string, res gjson.Result, format string, transform string) error { - if format != "raw" && transform != "" { - transformed := res.Get(transform) - if transformed.Exists() { - res = transformed - } +const warningExploreNotSupported = "Warning: Output format 'explore' not supported for non-terminal output; falling back to 'json'\n" + +// ShowJSONOpts configures how JSON output is displayed. +type ShowJSONOpts struct { + ExplicitFormat bool // true if the user explicitly passed --format + Format string // output format (auto, explore, json, jsonl, pretty, raw, yaml) + RawOutput bool // like jq -r: print strings without JSON quotes + Stderr io.Writer // stderr for warnings; injectable for testing; defaults to os.Stderr + Stdout *os.File // stdout (or pager); injectable for testing; defaults to os.Stdout + Title string // display title + Transform string // GJSON path to extract before displaying +} + +func (o *ShowJSONOpts) setDefaults() { + if o.Stderr == nil { + o.Stderr = os.Stderr + } + if o.Stdout == nil { + o.Stdout = os.Stdout } +} + +// ShowJSON displays a single JSON result to the user. +func ShowJSON(res gjson.Result, opts ShowJSONOpts) error { + opts.setDefaults() - switch strings.ToLower(format) { + switch strings.ToLower(opts.Format) { case "auto": - return ShowJSON(out, title, res, "json", "") + autoOpts := opts + autoOpts.Format = "json" + return ShowJSON(res, autoOpts) case "explore": - return jsonview.ExploreJSON(title, res) + if !isTerminal(opts.Stdout) { + if opts.ExplicitFormat { + fmt.Fprint(opts.Stderr, warningExploreNotSupported) + } + jsonOpts := opts + jsonOpts.Format = "json" + return ShowJSON(res, jsonOpts) + } + if opts.Transform != "" { + transformed := res.Get(opts.Transform) + if transformed.Exists() { + res = transformed + } + } + return jsonview.ExploreJSON(opts.Title, res) default: - bytes, err := formatJSON(out, title, res, format, transform) + bytes, err := formatJSON(res, opts) if err != nil { return err } - _, err = out.Write(bytes) + _, err = opts.Stdout.Write(bytes) return err } } @@ -391,12 +432,18 @@ type hasRawJSON interface { RawJSON() string } -// For an iterator over different value types, display its values to the user in -// different formats. -// -1 is used to signal no limit of items to display -func ShowJSONIterator[T any](stdout *os.File, title string, iter jsonview.Iterator[T], format string, transform string, itemsToDisplay int64) error { - if format == "explore" { - return jsonview.ExploreJSONStream(title, iter) +// ShowJSONIterator displays an iterator of values to the user. Use itemsToDisplay = -1 for no limit. +func ShowJSONIterator[T any](iter jsonview.Iterator[T], itemsToDisplay int64, opts ShowJSONOpts) error { + opts.setDefaults() + + if opts.Format == "explore" { + if isTerminal(opts.Stdout) { + return jsonview.ExploreJSONStream(opts.Title, iter) + } + if opts.ExplicitFormat { + fmt.Fprint(opts.Stderr, warningExploreNotSupported) + } + opts.Format = "json" } terminalWidth, terminalHeight, err := term.GetSize(os.Stdout.Fd()) @@ -422,7 +469,7 @@ func ShowJSONIterator[T any](stdout *os.File, title string, iter jsonview.Iterat } obj = gjson.ParseBytes(jsonData) } - json, err := formatJSON(stdout, title, obj, format, transform) + json, err := formatJSON(obj, opts) if err != nil { return err } @@ -439,7 +486,7 @@ func ShowJSONIterator[T any](stdout *os.File, title string, iter jsonview.Iterat } if !usePager { - _, err := stdout.Write(output) + _, err := opts.Stdout.Write(output) if err != nil { return err } @@ -447,13 +494,15 @@ func ShowJSONIterator[T any](stdout *os.File, title string, iter jsonview.Iterat return iter.Err() } - return streamOutput(title, func(pager *os.File) error { - // Write the output we used during the initial terminal size computation + return streamOutput(opts.Title, func(pager *os.File) error { _, err := pager.Write(output) if err != nil { return err } + pagerOpts := opts + pagerOpts.Stdout = pager + for iter.Next() { if itemsToDisplay == 0 { break @@ -469,7 +518,7 @@ func ShowJSONIterator[T any](stdout *os.File, title string, iter jsonview.Iterat } obj = gjson.ParseBytes(jsonData) } - if err := ShowJSON(pager, title, obj, format, transform); err != nil { + if err := ShowJSON(obj, pagerOpts); err != nil { return err } itemsToDisplay -= 1 diff --git a/pkg/cmd/cmdutil_test.go b/pkg/cmd/cmdutil_test.go index 8eca397..4b89694 100644 --- a/pkg/cmd/cmdutil_test.go +++ b/pkg/cmd/cmdutil_test.go @@ -10,6 +10,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" + + "github.com/Xquik-dev/x-twitter-scraper-cli/internal/jsonview" ) func TestStreamOutput(t *testing.T) { @@ -148,3 +151,238 @@ func TestValidateBaseURL(t *testing.T) { assert.Contains(t, err.Error(), "--base-url") }) } + +func TestFormatJSON(t *testing.T) { + t.Parallel() + + t.Run("RawWithTransform", func(t *testing.T) { + t.Parallel() + + res := gjson.Parse(`{"id":"abc123","name":"test"}`) + formatted, err := formatJSON(res, ShowJSONOpts{Format: "raw", Stdout: os.Stdout, Transform: "id"}) + require.NoError(t, err) + require.Equal(t, `"abc123"`+"\n", string(formatted)) + }) + + t.Run("RawWithoutTransform", func(t *testing.T) { + t.Parallel() + + res := gjson.Parse(`{"id":"abc123","name":"test"}`) + formatted, err := formatJSON(res, ShowJSONOpts{Format: "raw", Stdout: os.Stdout}) + require.NoError(t, err) + require.Equal(t, `{"id":"abc123","name":"test"}`+"\n", string(formatted)) + }) + + t.Run("RawWithNestedTransform", func(t *testing.T) { + t.Parallel() + + res := gjson.Parse(`{"data":{"items":[1,2,3]}}`) + formatted, err := formatJSON(res, ShowJSONOpts{Format: "raw", Stdout: os.Stdout, Transform: "data.items"}) + require.NoError(t, err) + require.Equal(t, "[1,2,3]\n", string(formatted)) + }) + + t.Run("RawWithNonexistentTransform", func(t *testing.T) { + t.Parallel() + + res := gjson.Parse(`{"id":"abc123"}`) + formatted, err := formatJSON(res, ShowJSONOpts{Format: "raw", Stdout: os.Stdout, Transform: "missing"}) + require.NoError(t, err) + // Transform path doesn't exist, so original result is returned + require.Equal(t, `{"id":"abc123"}`+"\n", string(formatted)) + }) + + t.Run("RawOutputString", func(t *testing.T) { + t.Parallel() + + res := gjson.Parse(`{"id":"abc123","name":"test"}`) + formatted, err := formatJSON(res, ShowJSONOpts{Format: "json", Stdout: os.Stdout, Transform: "id", RawOutput: true}) + require.NoError(t, err) + require.Equal(t, "abc123\n", string(formatted)) + }) + + t.Run("RawOutputNonString", func(t *testing.T) { + t.Parallel() + + // --raw-output has no effect on non-string values + res := gjson.Parse(`{"count":42}`) + formatted, err := formatJSON(res, ShowJSONOpts{Format: "raw", Stdout: os.Stdout, Transform: "count", RawOutput: true}) + require.NoError(t, err) + require.Equal(t, "42\n", string(formatted)) + }) + + t.Run("RawOutputObject", func(t *testing.T) { + t.Parallel() + + // --raw-output has no effect on objects + res := gjson.Parse(`{"nested":{"a":1}}`) + formatted, err := formatJSON(res, ShowJSONOpts{Format: "raw", Stdout: os.Stdout, Transform: "nested", RawOutput: true}) + require.NoError(t, err) + require.Equal(t, `{"a":1}`+"\n", string(formatted)) + }) +} + +func TestShowJSONIterator(t *testing.T) { + t.Parallel() + + t.Run("RawMultipleItems", func(t *testing.T) { + t.Parallel() + + iter := &sliceIterator[map[string]any]{items: []map[string]any{ + {"id": "abc", "name": "first"}, + {"id": "def", "name": "second"}, + }} + captured := captureShowJSONIterator(t, iter, "raw", "", -1) + assert.Equal(t, `{"id":"abc","name":"first"}`+"\n"+`{"id":"def","name":"second"}`+"\n", captured) + }) + + t.Run("RawWithTransform", func(t *testing.T) { + t.Parallel() + + iter := &sliceIterator[map[string]any]{items: []map[string]any{ + {"id": "abc", "name": "first"}, + {"id": "def", "name": "second"}, + }} + captured := captureShowJSONIterator(t, iter, "raw", "id", -1) + assert.Equal(t, `"abc"`+"\n"+`"def"`+"\n", captured) + }) + + t.Run("LimitItems", func(t *testing.T) { + t.Parallel() + + iter := &sliceIterator[map[string]any]{items: []map[string]any{ + {"id": "abc"}, + {"id": "def"}, + {"id": "ghi"}, + }} + captured := captureShowJSONIterator(t, iter, "raw", "", 2) + assert.Equal(t, `{"id":"abc"}`+"\n"+`{"id":"def"}`+"\n", captured) + }) +} + +func TestExploreFallback(t *testing.T) { + t.Parallel() + + t.Run("ShowJSONFallsBackToJsonOnNonTTY", func(t *testing.T) { + t.Parallel() + + // os.Pipe() produces a *os.File that isn't a terminal, so explore should fall back. + r, w, err := os.Pipe() + require.NoError(t, err) + defer r.Close() + + var stderr bytes.Buffer + res := gjson.Parse(`{"id":"abc"}`) + err = ShowJSON(res, ShowJSONOpts{ + Format: "explore", + Stderr: &stderr, + Stdout: w, + Title: "test", + }) + w.Close() + require.NoError(t, err) + + var buf bytes.Buffer + _, _ = buf.ReadFrom(r) + assert.Contains(t, buf.String(), `"id"`) + assert.Contains(t, buf.String(), `"abc"`) + }) + + t.Run("ShowJSONIteratorFallsBackToJsonOnNonTTY", func(t *testing.T) { + t.Parallel() + + iter := &sliceIterator[map[string]any]{items: []map[string]any{ + {"id": "abc"}, + }} + captured := captureShowJSONIterator(t, iter, "explore", "", -1) + assert.Contains(t, captured, `"id"`) + assert.Contains(t, captured, `"abc"`) + }) + + t.Run("ShowJSONWarnsWhenExplicitFormatOnNonTTY", func(t *testing.T) { + t.Parallel() + + r, w, err := os.Pipe() + require.NoError(t, err) + defer r.Close() + + var stderr bytes.Buffer + res := gjson.Parse(`{"id":"abc"}`) + err = ShowJSON(res, ShowJSONOpts{ + ExplicitFormat: true, + Format: "explore", + Stderr: &stderr, + Stdout: w, + Title: "test", + }) + w.Close() + require.NoError(t, err) + + assert.Equal(t, warningExploreNotSupported, stderr.String()) + }) + + t.Run("ShowJSONSilentWhenDefaultFormatOnNonTTY", func(t *testing.T) { + t.Parallel() + + r, w, err := os.Pipe() + require.NoError(t, err) + defer r.Close() + + var stderr bytes.Buffer + res := gjson.Parse(`{"id":"abc"}`) + err = ShowJSON(res, ShowJSONOpts{ + Format: "explore", + Stderr: &stderr, + Stdout: w, + Title: "test", + }) + w.Close() + require.NoError(t, err) + + assert.Empty(t, stderr.String(), "no warning expected when format was not explicit") + }) +} + +// sliceIterator is a simple iterator over a slice for testing. +type sliceIterator[T any] struct { + index int + items []T +} + +func (it *sliceIterator[T]) Next() bool { + it.index++ + return it.index <= len(it.items) +} + +func (it *sliceIterator[T]) Current() T { + return it.items[it.index-1] +} + +func (it *sliceIterator[T]) Err() error { + return nil +} + +var _ jsonview.Iterator[any] = (*sliceIterator[any])(nil) + +// captureShowJSONIterator runs ShowJSONIterator and captures the output written to a file. +func captureShowJSONIterator[T any](t *testing.T, iter jsonview.Iterator[T], format, transform string, itemsToDisplay int64) string { + t.Helper() + + r, w, err := os.Pipe() + require.NoError(t, err) + defer r.Close() + + err = ShowJSONIterator(iter, itemsToDisplay, ShowJSONOpts{ + Format: format, + Stderr: io.Discard, + Stdout: w, + Title: "test", + Transform: transform, + }) + w.Close() + require.NoError(t, err) + + var buf bytes.Buffer + _, _ = buf.ReadFrom(r) + return buf.String() +} diff --git a/pkg/cmd/compose.go b/pkg/cmd/compose.go index 9114bc1..067e836 100644 --- a/pkg/cmd/compose.go +++ b/pkg/cmd/compose.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -111,6 +110,13 @@ func handleComposeCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "compose create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "compose create", + Transform: transform, + }) } diff --git a/pkg/cmd/compose_test.go b/pkg/cmd/compose_test.go index 92a2720..917a458 100644 --- a/pkg/cmd/compose_test.go +++ b/pkg/cmd/compose_test.go @@ -14,7 +14,6 @@ func TestComposeCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "compose", "create", "--step", "compose", "--additional-context", "https://x.com/elonmusk/status/1234567890", @@ -47,7 +46,6 @@ func TestComposeCreate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "compose", "create", ) }) diff --git a/pkg/cmd/credit.go b/pkg/cmd/credit.go index a77579b..dda0d04 100644 --- a/pkg/cmd/credit.go +++ b/pkg/cmd/credit.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -68,8 +67,15 @@ func handleCreditsRetrieveBalance(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "credits retrieve-balance", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "credits retrieve-balance", + Transform: transform, + }) } func handleCreditsTopupBalance(ctx context.Context, cmd *cli.Command) error { @@ -102,6 +108,13 @@ func handleCreditsTopupBalance(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "credits topup-balance", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "credits topup-balance", + Transform: transform, + }) } diff --git a/pkg/cmd/credit_test.go b/pkg/cmd/credit_test.go index dfa396b..b7e55d0 100644 --- a/pkg/cmd/credit_test.go +++ b/pkg/cmd/credit_test.go @@ -14,7 +14,6 @@ func TestCreditsRetrieveBalance(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "credits", "retrieve-balance", ) }) @@ -26,7 +25,6 @@ func TestCreditsTopupBalance(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "credits", "topup-balance", "--amount", "10000", ) @@ -38,7 +36,6 @@ func TestCreditsTopupBalance(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "credits", "topup-balance", ) }) diff --git a/pkg/cmd/draft.go b/pkg/cmd/draft.go index 5c994eb..83ace59 100644 --- a/pkg/cmd/draft.go +++ b/pkg/cmd/draft.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -118,8 +117,15 @@ func handleDraftsCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "drafts create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "drafts create", + Transform: transform, + }) } func handleDraftsRetrieve(ctx context.Context, cmd *cli.Command) error { @@ -153,8 +159,15 @@ func handleDraftsRetrieve(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "drafts retrieve", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "drafts retrieve", + Transform: transform, + }) } func handleDraftsList(ctx context.Context, cmd *cli.Command) error { @@ -187,8 +200,15 @@ func handleDraftsList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "drafts list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "drafts list", + Transform: transform, + }) } func handleDraftsDelete(ctx context.Context, cmd *cli.Command) error { diff --git a/pkg/cmd/draft_test.go b/pkg/cmd/draft_test.go index 053211f..4788172 100644 --- a/pkg/cmd/draft_test.go +++ b/pkg/cmd/draft_test.go @@ -14,7 +14,6 @@ func TestDraftsCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "drafts", "create", "--text", "AI is the future of productivity", "--goal", "engagement", @@ -31,7 +30,6 @@ func TestDraftsCreate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "drafts", "create", ) }) @@ -43,7 +41,6 @@ func TestDraftsRetrieve(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "drafts", "retrieve", "--id", "id", ) @@ -56,7 +53,6 @@ func TestDraftsList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "drafts", "list", "--after-cursor", "afterCursor", "--limit", "1", @@ -70,7 +66,6 @@ func TestDraftsDelete(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "drafts", "delete", "--id", "id", ) diff --git a/pkg/cmd/draw.go b/pkg/cmd/draw.go index cd67a03..5b3ae7e 100644 --- a/pkg/cmd/draw.go +++ b/pkg/cmd/draw.go @@ -172,8 +172,15 @@ func handleDrawsRetrieve(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "draws retrieve", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "draws retrieve", + Transform: transform, + }) } func handleDrawsList(ctx context.Context, cmd *cli.Command) error { @@ -206,8 +213,15 @@ func handleDrawsList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "draws list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "draws list", + Transform: transform, + }) } func handleDrawsExport(ctx context.Context, cmd *cli.Command) error { @@ -280,6 +294,13 @@ func handleDrawsRun(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "draws run", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "draws run", + Transform: transform, + }) } diff --git a/pkg/cmd/draw_test.go b/pkg/cmd/draw_test.go index bdeb91e..c2a9d73 100644 --- a/pkg/cmd/draw_test.go +++ b/pkg/cmd/draw_test.go @@ -14,7 +14,6 @@ func TestDrawsRetrieve(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "draws", "retrieve", "--id", "id", ) @@ -27,7 +26,6 @@ func TestDrawsList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "draws", "list", "--after", "after", "--limit", "1", @@ -41,7 +39,6 @@ func TestDrawsExport(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "draws", "export", "--id", "id", "--format", "csv", @@ -57,7 +54,6 @@ func TestDrawsRun(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "draws", "run", "--tweet-url", "https://x.com/elonmusk/status/1234567890", "--backup-count", "2", @@ -95,7 +91,6 @@ func TestDrawsRun(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "draws", "run", ) }) diff --git a/pkg/cmd/event.go b/pkg/cmd/event.go index d9a4647..c9b951f 100644 --- a/pkg/cmd/event.go +++ b/pkg/cmd/event.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -91,8 +90,15 @@ func handleEventsRetrieve(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "events retrieve", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "events retrieve", + Transform: transform, + }) } func handleEventsList(ctx context.Context, cmd *cli.Command) error { @@ -125,6 +131,13 @@ func handleEventsList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "events list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "events list", + Transform: transform, + }) } diff --git a/pkg/cmd/event_test.go b/pkg/cmd/event_test.go index 5bf9e8c..0b258a8 100644 --- a/pkg/cmd/event_test.go +++ b/pkg/cmd/event_test.go @@ -14,7 +14,6 @@ func TestEventsRetrieve(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "events", "retrieve", "--id", "id", ) @@ -27,7 +26,6 @@ func TestEventsList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "events", "list", "--after", "after", "--event-type", "tweet.new", diff --git a/pkg/cmd/extraction.go b/pkg/cmd/extraction.go index 8937188..17626a5 100644 --- a/pkg/cmd/extraction.go +++ b/pkg/cmd/extraction.go @@ -244,8 +244,15 @@ func handleExtractionsRetrieve(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "extractions retrieve", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "extractions retrieve", + Transform: transform, + }) } func handleExtractionsList(ctx context.Context, cmd *cli.Command) error { @@ -278,8 +285,15 @@ func handleExtractionsList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "extractions list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "extractions list", + Transform: transform, + }) } func handleExtractionsEstimateCost(ctx context.Context, cmd *cli.Command) error { @@ -312,8 +326,15 @@ func handleExtractionsEstimateCost(ctx context.Context, cmd *cli.Command) error obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "extractions estimate-cost", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "extractions estimate-cost", + Transform: transform, + }) } func handleExtractionsExportResults(ctx context.Context, cmd *cli.Command) error { @@ -386,6 +407,13 @@ func handleExtractionsRun(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "extractions run", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "extractions run", + Transform: transform, + }) } diff --git a/pkg/cmd/extraction_test.go b/pkg/cmd/extraction_test.go index 4b8da14..918222c 100644 --- a/pkg/cmd/extraction_test.go +++ b/pkg/cmd/extraction_test.go @@ -14,7 +14,6 @@ func TestExtractionsRetrieve(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "extractions", "retrieve", "--id", "id", "--after", "after", @@ -29,7 +28,6 @@ func TestExtractionsList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "extractions", "list", "--after", "after", "--limit", "1", @@ -45,7 +43,6 @@ func TestExtractionsEstimateCost(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "extractions", "estimate-cost", "--tool-type", "follower_explorer", "--advanced-query", "min_faves:100", @@ -76,7 +73,6 @@ func TestExtractionsEstimateCost(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "extractions", "estimate-cost", ) }) @@ -88,7 +84,6 @@ func TestExtractionsExportResults(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "extractions", "export-results", "--id", "id", "--format", "csv", @@ -103,7 +98,6 @@ func TestExtractionsRun(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "extractions", "run", "--tool-type", "follower_explorer", "--advanced-query", "min_faves:100", @@ -134,7 +128,6 @@ func TestExtractionsRun(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "extractions", "run", ) }) diff --git a/pkg/cmd/flagoptions.go b/pkg/cmd/flagoptions.go index 8db0a4d..623bc8b 100644 --- a/pkg/cmd/flagoptions.go +++ b/pkg/cmd/flagoptions.go @@ -7,9 +7,11 @@ import ( "fmt" "io" "maps" + "mime" "mime/multipart" "net/http" "os" + "path/filepath" "reflect" "strings" "unicode/utf8" @@ -36,7 +38,14 @@ const ( type FileEmbedStyle int const ( + // EmbedText reads referenced files fully into memory and substitutes the file's contents back into the + // value as a string. Binary files are base64-encoded. Used for JSON request bodies and for headers and + // query parameters, where the file contents need to be serialized inline. EmbedText FileEmbedStyle = iota + + // EmbedIOReader replaces file references with an io.Reader that streams the file's contents. Used for + // `multipart/form-data` and `application/octet-stream` request bodies, where files are uploaded as binary + // parts rather than embedded into a text value. EmbedIOReader ) @@ -142,6 +151,20 @@ func embedFilesValue(v reflect.Value, embedStyle FileEmbedStyle, stdin *onceStdi if s == "" { return v, nil } + if embedStyle == EmbedIOReader { + if isStdinPath(s) { + r, err := stdin.read() + if err != nil { + return v, err + } + return reflect.ValueOf(io.NopCloser(r)), nil + } + upload, err := openFileUpload(s) + if err != nil { + return v, err + } + return reflect.ValueOf(upload), nil + } if isStdinPath(s) { content, err := stdin.readAll() if err != nil { @@ -250,7 +273,7 @@ func embedFilesValue(v reflect.Value, embedStyle FileEmbedStyle, stdin *onceStdi return reflect.ValueOf(io.NopCloser(r)), nil } - file, err := os.Open(filename) + upload, err := openFileUpload(filename) if err != nil { if !expectsFile { // For strings that start with "@" and don't look like a filename, return the string @@ -258,7 +281,7 @@ func embedFilesValue(v reflect.Value, embedStyle FileEmbedStyle, stdin *onceStdi } return v, err } - return reflect.ValueOf(file), nil + return reflect.ValueOf(upload), nil } } return v, nil @@ -309,6 +332,12 @@ func flagOptions( requestContents := requestflag.ExtractRequestContents(cmd) + // Translate inner-field aliases in YAML values that came from flags (e.g. + // `--parent '{"alias": val}'` resolving to the canonical inner field). + if bodyMap, ok := requestContents.Body.(map[string]any); ok { + applyDataAliases(cmd, bodyMap) + } + stdinConsumedByPipe := false if (bodyType == MultipartFormEncoded || bodyType == ApplicationJSON) && !ignoreStdin && isInputPiped() { pipeData, err := io.ReadAll(os.Stdin) @@ -323,6 +352,7 @@ func flagOptions( return nil, fmt.Errorf("Failed to parse piped data as YAML/JSON:\n%w", err) } if bodyMap, ok := bodyData.(map[string]any); ok { + applyDataAliases(cmd, bodyMap) if flagMap, ok := requestContents.Body.(map[string]any); ok { maps.Copy(bodyMap, flagMap) requestContents.Body = bodyMap @@ -485,6 +515,84 @@ func flagOptions( // as a file path without needing the "@" prefix. type FilePathValue string +// fileUpload wraps an io.Reader with filename and content-type metadata for +// use as a multipart form part. The apiform encoder detects the Filename and +// ContentType methods and uses them to populate the Content-Disposition +// filename and the Content-Type header on the part. +type fileUpload struct { + io.Reader // apiform checks for reader and reads its contents during encode + filename string + contentType string +} + +func (f fileUpload) Filename() string { return f.filename } +func (f fileUpload) ContentType() string { return f.contentType } +func (f fileUpload) Close() error { + if c, ok := f.Reader.(io.Closer); ok { + return c.Close() + } + return nil +} + +// openFileUpload opens the file at path and returns a fileUpload whose filename +// is the path's basename and whose content type is derived from the file +// extension (falling back to application/octet-stream when unknown). +func openFileUpload(path string) (fileUpload, error) { + file, err := os.Open(path) + if err != nil { + return fileUpload{}, err + } + contentType := mime.TypeByExtension(filepath.Ext(path)) + if contentType == "" { + contentType = "application/octet-stream" + } + return fileUpload{ + Reader: file, + filename: filepath.Base(path), + contentType: contentType, + }, nil +} + +// applyDataAliases rewrites keys in a body map based on flag `DataAliases` metadata. For top-level flags, +// `{alias: value}` becomes `{canonical: value}`. For inner flags (those registered under an outer flag +// via WithInnerFlags), the alias translation is also applied to the nested map under the outer flag's +// body path, so values like `--parent '{"alias": val}'` resolve to the canonical inner field name. +func applyDataAliases(cmd *cli.Command, bodyMap map[string]any) { + for _, flag := range cmd.Flags { + // Inner flags: rewrite aliases inside the nested map under the outer flag's body path. + if inner, ok := flag.(requestflag.HasOuterFlag); ok { + outer, outerOk := inner.GetOuterFlag().(requestflag.InRequest) + if !outerOk { + continue + } + if nested, ok := bodyMap[outer.GetBodyPath()].(map[string]any); ok && inner.GetInnerField() != "" { + rewriteAliases(nested, inner.GetInnerField(), inner.GetDataAliases()) + } + continue + } + // Top-level flags: rewrite aliases in the body map. + if inReq, ok := flag.(requestflag.InRequest); ok && inReq.GetBodyPath() != "" { + rewriteAliases(bodyMap, inReq.GetBodyPath(), inReq.GetDataAliases()) + } + } +} + +// rewriteAliases replaces each alias key in m with the canonical key, preserving the value. The +// "canonical" key is the name the API itself expects (the OpenAPI property/field name) — e.g. for +// a top-level flag, the parameter's BodyPath; for an inner flag, the inner field name. Aliases are +// the user-facing alternate names declared via x-stainless-cli-data-alias. +func rewriteAliases(m map[string]any, canonical string, aliases []string) { + for _, alias := range aliases { + if alias == "" || alias == canonical { + continue + } + if val, exists := m[alias]; exists { + m[canonical] = val + delete(m, alias) + } + } +} + // wrapFileInputValues replaces string values for FileInput flags (type: string, format: binary) with // FilePathValue sentinel values. embedFilesValue recognizes FilePathValue and reads the file contents // directly, so the user doesn't need to type the "@" prefix. This handles both values set via explicit diff --git a/pkg/cmd/flagoptions_test.go b/pkg/cmd/flagoptions_test.go index 039b9ff..00734ca 100644 --- a/pkg/cmd/flagoptions_test.go +++ b/pkg/cmd/flagoptions_test.go @@ -8,7 +8,6 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -31,7 +30,7 @@ func TestIsUTF8TextFile(t *testing.T) { } for _, tt := range tests { - assert.Equal(t, tt.expected, isUTF8TextFile(tt.content)) + require.Equal(t, tt.expected, isUTF8TextFile(tt.content)) } } @@ -226,10 +225,10 @@ func TestEmbedFiles(t *testing.T) { got, err := embedFiles(tt.input, EmbedText, nil) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) } else { require.NoError(t, err) - assert.Equal(t, tt.want, got) + require.Equal(t, tt.want, got) } }) @@ -238,7 +237,7 @@ func TestEmbedFiles(t *testing.T) { _, err := embedFiles(tt.input, EmbedIOReader, nil) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) } else { require.NoError(t, err) } @@ -333,6 +332,56 @@ func TestEmbedFilesStdin(t *testing.T) { }) } +// TestEmbedFilesUploadMetadata verifies that EmbedIOReader mode wraps file readers with filename and +// content-type metadata so the multipart encoder populates `Content-Disposition` and `Content-Type` headers. +func TestEmbedFilesUploadMetadata(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + writeTestFile(t, tmpDir, "hello.txt", "hi") + writeTestFile(t, tmpDir, "page.html", "") + writeTestFile(t, tmpDir, "blob.bin", "\x00\x01") + + cases := []struct { + basename string + wantContentType string + }{ + {"hello.txt", "text/plain; charset=utf-8"}, + {"page.html", "text/html; charset=utf-8"}, + {"blob.bin", "application/octet-stream"}, + } + + for _, tc := range cases { + t.Run("AtPrefix_"+tc.basename, func(t *testing.T) { + t.Parallel() + + path := filepath.Join(tmpDir, tc.basename) + withEmbedded, err := embedFiles(map[string]any{"file": "@" + path}, EmbedIOReader, nil) + require.NoError(t, err) + + upload, ok := withEmbedded.(map[string]any)["file"].(fileUpload) + require.True(t, ok, "expected fileUpload, got %T", withEmbedded.(map[string]any)["file"]) + require.Equal(t, tc.basename, upload.Filename()) + require.Equal(t, upload.ContentType(), tc.wantContentType) + require.NoError(t, upload.Close()) + }) + + t.Run("FilePathValue_"+tc.basename, func(t *testing.T) { + t.Parallel() + + path := filepath.Join(tmpDir, tc.basename) + withEmbedded, err := embedFiles(map[string]any{"file": FilePathValue(path)}, EmbedIOReader, nil) + require.NoError(t, err) + + upload, ok := withEmbedded.(map[string]any)["file"].(fileUpload) + require.True(t, ok, "expected fileUpload, got %T", withEmbedded.(map[string]any)["file"]) + require.Equal(t, tc.basename, upload.Filename()) + require.Equal(t, upload.ContentType(), tc.wantContentType) + require.NoError(t, upload.Close()) + }) + } +} + func writeTestFile(t *testing.T, dir, filename, content string) { t.Helper() diff --git a/pkg/cmd/integration.go b/pkg/cmd/integration.go deleted file mode 100644 index 1e6fca0..0000000 --- a/pkg/cmd/integration.go +++ /dev/null @@ -1,428 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - "os" - - "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" - "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" - "github.com/Xquik-dev/x-twitter-scraper-go" - "github.com/Xquik-dev/x-twitter-scraper-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var integrationsCreate = requestflag.WithInnerFlags(cli.Command{ - Name: "create", - Usage: "Create integration", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[map[string]any]{ - Name: "config", - Usage: "Integration config (e.g. Telegram chatId)", - Required: true, - BodyPath: "config", - }, - &requestflag.Flag[[]string]{ - Name: "event-type", - Usage: "Array of event types to subscribe to.", - Required: true, - BodyPath: "eventTypes", - }, - &requestflag.Flag[string]{ - Name: "name", - Required: true, - BodyPath: "name", - }, - &requestflag.Flag[string]{ - Name: "type", - Usage: `Allowed values: "telegram".`, - Default: "telegram", - Const: true, - BodyPath: "type", - }, - }, - Action: handleIntegrationsCreate, - HideHelpCommand: true, -}, map[string][]requestflag.HasOuterFlag{ - "config": { - &requestflag.InnerFlag[string]{ - Name: "config.chat-id", - InnerField: "chatId", - }, - }, -}) - -var integrationsRetrieve = cli.Command{ - Name: "retrieve", - Usage: "Get integration details", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "id", - Required: true, - }, - }, - Action: handleIntegrationsRetrieve, - HideHelpCommand: true, -} - -var integrationsUpdate = cli.Command{ - Name: "update", - Usage: "Update integration", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "id", - Required: true, - }, - &requestflag.Flag[[]string]{ - Name: "event-type", - Usage: "Array of event types to subscribe to.", - BodyPath: "eventTypes", - }, - &requestflag.Flag[map[string]any]{ - Name: "filters", - Usage: "Event filter rules (JSON)", - BodyPath: "filters", - }, - &requestflag.Flag[bool]{ - Name: "is-active", - BodyPath: "isActive", - }, - &requestflag.Flag[map[string]any]{ - Name: "message-template", - Usage: "Custom message template (JSON)", - BodyPath: "messageTemplate", - }, - &requestflag.Flag[string]{ - Name: "name", - BodyPath: "name", - }, - &requestflag.Flag[bool]{ - Name: "scope-all-monitors", - BodyPath: "scopeAllMonitors", - }, - &requestflag.Flag[bool]{ - Name: "silent-push", - BodyPath: "silentPush", - }, - }, - Action: handleIntegrationsUpdate, - HideHelpCommand: true, -} - -var integrationsList = cli.Command{ - Name: "list", - Usage: "List integrations", - Suggest: true, - Flags: []cli.Flag{}, - Action: handleIntegrationsList, - HideHelpCommand: true, -} - -var integrationsDelete = cli.Command{ - Name: "delete", - Usage: "Delete integration", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "id", - Required: true, - }, - }, - Action: handleIntegrationsDelete, - HideHelpCommand: true, -} - -var integrationsListDeliveries = cli.Command{ - Name: "list-deliveries", - Usage: "List integration delivery history", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "id", - Required: true, - }, - &requestflag.Flag[int64]{ - Name: "limit", - Usage: "Maximum number of items to return (1-100, default 50)", - Default: 50, - QueryPath: "limit", - }, - }, - Action: handleIntegrationsListDeliveries, - HideHelpCommand: true, -} - -var integrationsSendTest = cli.Command{ - Name: "send-test", - Usage: "Send test delivery", - Suggest: true, - Flags: []cli.Flag{ - &requestflag.Flag[string]{ - Name: "id", - Required: true, - }, - }, - Action: handleIntegrationsSendTest, - HideHelpCommand: true, -} - -func handleIntegrationsCreate(ctx context.Context, cmd *cli.Command) error { - client := xtwitterscraper.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - params := xtwitterscraper.IntegrationNewParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - false, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Integrations.New(ctx, params, options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "integrations create", obj, format, transform) -} - -func handleIntegrationsRetrieve(ctx context.Context, cmd *cli.Command) error { - client := xtwitterscraper.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - EmptyBody, - false, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Integrations.Get(ctx, cmd.Value("id").(string), options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "integrations retrieve", obj, format, transform) -} - -func handleIntegrationsUpdate(ctx context.Context, cmd *cli.Command) error { - client := xtwitterscraper.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - params := xtwitterscraper.IntegrationUpdateParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - false, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Integrations.Update( - ctx, - cmd.Value("id").(string), - params, - options..., - ) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "integrations update", obj, format, transform) -} - -func handleIntegrationsList(ctx context.Context, cmd *cli.Command) error { - client := xtwitterscraper.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - EmptyBody, - false, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Integrations.List(ctx, options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "integrations list", obj, format, transform) -} - -func handleIntegrationsDelete(ctx context.Context, cmd *cli.Command) error { - client := xtwitterscraper.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - EmptyBody, - false, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Integrations.Delete(ctx, cmd.Value("id").(string), options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "integrations delete", obj, format, transform) -} - -func handleIntegrationsListDeliveries(ctx context.Context, cmd *cli.Command) error { - client := xtwitterscraper.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - params := xtwitterscraper.IntegrationListDeliveriesParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - EmptyBody, - false, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Integrations.ListDeliveries( - ctx, - cmd.Value("id").(string), - params, - options..., - ) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "integrations list-deliveries", obj, format, transform) -} - -func handleIntegrationsSendTest(ctx context.Context, cmd *cli.Command) error { - client := xtwitterscraper.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - EmptyBody, - false, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Integrations.SendTest(ctx, cmd.Value("id").(string), options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "integrations send-test", obj, format, transform) -} diff --git a/pkg/cmd/integration_test.go b/pkg/cmd/integration_test.go deleted file mode 100644 index c66dc5f..0000000 --- a/pkg/cmd/integration_test.go +++ /dev/null @@ -1,168 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "testing" - - "github.com/Xquik-dev/x-twitter-scraper-cli/internal/mocktest" - "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" -) - -func TestIntegrationsCreate(t *testing.T) { - t.Skip("Mock server tests are disabled") - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "--bearer-token", "string", - "integrations", "create", - "--config", "{chatId: '-1001234567890'}", - "--event-type", "tweet.new", - "--event-type", "follower.gained", - "--name", "My Telegram Bot", - "--type", "telegram", - ) - }) - - t.Run("inner flags", func(t *testing.T) { - // Check that inner flags have been set up correctly - requestflag.CheckInnerFlags(integrationsCreate) - - // Alternative argument passing style using inner flags - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "--bearer-token", "string", - "integrations", "create", - "--config.chat-id", "-1001234567890", - "--event-type", "tweet.new", - "--event-type", "follower.gained", - "--name", "My Telegram Bot", - "--type", "telegram", - ) - }) - - t.Run("piping data", func(t *testing.T) { - // Test piping YAML data over stdin - pipeData := []byte("" + - "config:\n" + - " chatId: '-1001234567890'\n" + - "eventTypes:\n" + - " - tweet.new\n" + - " - follower.gained\n" + - "name: My Telegram Bot\n" + - "type: telegram\n") - mocktest.TestRunMockTestWithPipeAndFlags( - t, pipeData, - "--api-key", "string", - "--bearer-token", "string", - "integrations", "create", - ) - }) -} - -func TestIntegrationsRetrieve(t *testing.T) { - t.Skip("Mock server tests are disabled") - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "--bearer-token", "string", - "integrations", "retrieve", - "--id", "id", - ) - }) -} - -func TestIntegrationsUpdate(t *testing.T) { - t.Skip("Mock server tests are disabled") - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "--bearer-token", "string", - "integrations", "update", - "--id", "id", - "--event-type", "tweet.new", - "--filters", "{}", - "--is-active=true", - "--message-template", "{}", - "--name", "My Telegram Bot", - "--scope-all-monitors=true", - "--silent-push=false", - ) - }) - - t.Run("piping data", func(t *testing.T) { - // Test piping YAML data over stdin - pipeData := []byte("" + - "eventTypes:\n" + - " - tweet.new\n" + - "filters: {}\n" + - "isActive: true\n" + - "messageTemplate: {}\n" + - "name: My Telegram Bot\n" + - "scopeAllMonitors: true\n" + - "silentPush: false\n") - mocktest.TestRunMockTestWithPipeAndFlags( - t, pipeData, - "--api-key", "string", - "--bearer-token", "string", - "integrations", "update", - "--id", "id", - ) - }) -} - -func TestIntegrationsList(t *testing.T) { - t.Skip("Mock server tests are disabled") - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "--bearer-token", "string", - "integrations", "list", - ) - }) -} - -func TestIntegrationsDelete(t *testing.T) { - t.Skip("Mock server tests are disabled") - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "--bearer-token", "string", - "integrations", "delete", - "--id", "id", - ) - }) -} - -func TestIntegrationsListDeliveries(t *testing.T) { - t.Skip("Mock server tests are disabled") - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "--bearer-token", "string", - "integrations", "list-deliveries", - "--id", "id", - "--limit", "1", - ) - }) -} - -func TestIntegrationsSendTest(t *testing.T) { - t.Skip("Mock server tests are disabled") - t.Run("regular flags", func(t *testing.T) { - mocktest.TestRunMockTestWithFlags( - t, - "--api-key", "string", - "--bearer-token", "string", - "integrations", "send-test", - "--id", "id", - ) - }) -} diff --git a/pkg/cmd/monitor.go b/pkg/cmd/monitor.go index f79a39e..f49e203 100644 --- a/pkg/cmd/monitor.go +++ b/pkg/cmd/monitor.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -127,8 +126,15 @@ func handleMonitorsCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "monitors create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "monitors create", + Transform: transform, + }) } func handleMonitorsRetrieve(ctx context.Context, cmd *cli.Command) error { @@ -162,8 +168,15 @@ func handleMonitorsRetrieve(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "monitors retrieve", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "monitors retrieve", + Transform: transform, + }) } func handleMonitorsUpdate(ctx context.Context, cmd *cli.Command) error { @@ -204,8 +217,15 @@ func handleMonitorsUpdate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "monitors update", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "monitors update", + Transform: transform, + }) } func handleMonitorsList(ctx context.Context, cmd *cli.Command) error { @@ -236,8 +256,15 @@ func handleMonitorsList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "monitors list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "monitors list", + Transform: transform, + }) } func handleMonitorsDeactivate(ctx context.Context, cmd *cli.Command) error { @@ -271,6 +298,13 @@ func handleMonitorsDeactivate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "monitors deactivate", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "monitors deactivate", + Transform: transform, + }) } diff --git a/pkg/cmd/monitor_test.go b/pkg/cmd/monitor_test.go index 407774c..24ae6c9 100644 --- a/pkg/cmd/monitor_test.go +++ b/pkg/cmd/monitor_test.go @@ -14,10 +14,9 @@ func TestMonitorsCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "monitors", "create", "--event-type", "tweet.new", - "--event-type", "follower.gained", + "--event-type", "tweet.reply", "--username", "elonmusk", ) }) @@ -27,12 +26,11 @@ func TestMonitorsCreate(t *testing.T) { pipeData := []byte("" + "eventTypes:\n" + " - tweet.new\n" + - " - follower.gained\n" + + " - tweet.reply\n" + "username: elonmusk\n") mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "monitors", "create", ) }) @@ -44,7 +42,6 @@ func TestMonitorsRetrieve(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "monitors", "retrieve", "--id", "id", ) @@ -57,7 +54,6 @@ func TestMonitorsUpdate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "monitors", "update", "--id", "id", "--event-type", "tweet.new", @@ -74,7 +70,6 @@ func TestMonitorsUpdate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "monitors", "update", "--id", "id", ) @@ -87,7 +82,6 @@ func TestMonitorsList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "monitors", "list", ) }) @@ -99,7 +93,6 @@ func TestMonitorsDeactivate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "monitors", "deactivate", "--id", "id", ) diff --git a/pkg/cmd/radar.go b/pkg/cmd/radar.go index 284f6ea..9df8455 100644 --- a/pkg/cmd/radar.go +++ b/pkg/cmd/radar.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -20,21 +19,28 @@ var radarRetrieveTrendingTopics = cli.Command{ Usage: "Get trending topics from curated sources", Suggest: true, Flags: []cli.Flag{ + &requestflag.Flag[string]{ + Name: "after", + Usage: "Cursor for pagination (from prior response nextCursor).", + QueryPath: "after", + }, &requestflag.Flag[string]{ Name: "category", - Usage: "Filter by category (general, tech, dev, etc.)", + Usage: "Filter by category.", QueryPath: "category", }, - &requestflag.Flag[int64]{ - Name: "count", - Usage: "Number of items to return", - QueryPath: "count", - }, &requestflag.Flag[int64]{ Name: "hours", - Usage: "Lookback window in hours", + Usage: "Lookback window in hours (1-168, default 24).", + Default: 24, QueryPath: "hours", }, + &requestflag.Flag[int64]{ + Name: "limit", + Usage: "Number of items to return (1-100, default 50).", + Default: 50, + QueryPath: "limit", + }, &requestflag.Flag[string]{ Name: "region", Usage: "Region filter (us, global, etc.)", @@ -80,6 +86,13 @@ func handleRadarRetrieveTrendingTopics(ctx context.Context, cmd *cli.Command) er obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "radar retrieve-trending-topics", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "radar retrieve-trending-topics", + Transform: transform, + }) } diff --git a/pkg/cmd/radar_test.go b/pkg/cmd/radar_test.go index 1174b6a..3de36e4 100644 --- a/pkg/cmd/radar_test.go +++ b/pkg/cmd/radar_test.go @@ -14,11 +14,11 @@ func TestRadarRetrieveTrendingTopics(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "radar", "retrieve-trending-topics", - "--category", "category", - "--count", "0", - "--hours", "0", + "--after", "after", + "--category", "general", + "--hours", "1", + "--limit", "1", "--region", "region", "--source", "github", ) diff --git a/pkg/cmd/style.go b/pkg/cmd/style.go index e0b65fe..23f22df 100644 --- a/pkg/cmd/style.go +++ b/pkg/cmd/style.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -168,8 +167,15 @@ func handleStylesRetrieve(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "styles retrieve", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "styles retrieve", + Transform: transform, + }) } func handleStylesUpdate(ctx context.Context, cmd *cli.Command) error { @@ -210,8 +216,15 @@ func handleStylesUpdate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "styles update", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "styles update", + Transform: transform, + }) } func handleStylesList(ctx context.Context, cmd *cli.Command) error { @@ -242,8 +255,15 @@ func handleStylesList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "styles list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "styles list", + Transform: transform, + }) } func handleStylesDelete(ctx context.Context, cmd *cli.Command) error { @@ -301,8 +321,15 @@ func handleStylesAnalyze(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "styles analyze", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "styles analyze", + Transform: transform, + }) } func handleStylesCompare(ctx context.Context, cmd *cli.Command) error { @@ -335,8 +362,15 @@ func handleStylesCompare(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "styles compare", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "styles compare", + Transform: transform, + }) } func handleStylesGetPerformance(ctx context.Context, cmd *cli.Command) error { @@ -370,6 +404,13 @@ func handleStylesGetPerformance(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "styles get-performance", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "styles get-performance", + Transform: transform, + }) } diff --git a/pkg/cmd/style_test.go b/pkg/cmd/style_test.go index 563dd75..1a97518 100644 --- a/pkg/cmd/style_test.go +++ b/pkg/cmd/style_test.go @@ -15,7 +15,6 @@ func TestStylesRetrieve(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "styles", "retrieve", "--id", "id", ) @@ -28,7 +27,6 @@ func TestStylesUpdate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "styles", "update", "--id", "id", "--label", "Professional Voice", @@ -44,7 +42,6 @@ func TestStylesUpdate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "styles", "update", "--id", "id", "--label", "Professional Voice", @@ -61,7 +58,6 @@ func TestStylesUpdate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "styles", "update", "--id", "id", ) @@ -74,7 +70,6 @@ func TestStylesList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "styles", "list", ) }) @@ -86,7 +81,6 @@ func TestStylesDelete(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "styles", "delete", "--id", "id", ) @@ -99,7 +93,6 @@ func TestStylesAnalyze(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "styles", "analyze", "--username", "elonmusk", ) @@ -111,7 +104,6 @@ func TestStylesAnalyze(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "styles", "analyze", ) }) @@ -123,7 +115,6 @@ func TestStylesCompare(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "styles", "compare", "--username1", "username1", "--username2", "username2", @@ -137,7 +128,6 @@ func TestStylesGetPerformance(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "styles", "get-performance", "--id", "id", ) diff --git a/pkg/cmd/subscribe.go b/pkg/cmd/subscribe.go index 9c913e6..6796498 100644 --- a/pkg/cmd/subscribe.go +++ b/pkg/cmd/subscribe.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-go" @@ -51,6 +50,13 @@ func handleSubscribeCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "subscribe create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "subscribe create", + Transform: transform, + }) } diff --git a/pkg/cmd/subscribe_test.go b/pkg/cmd/subscribe_test.go index 7b289c5..9ddd648 100644 --- a/pkg/cmd/subscribe_test.go +++ b/pkg/cmd/subscribe_test.go @@ -14,7 +14,6 @@ func TestSubscribeCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "subscribe", "create", ) }) diff --git a/pkg/cmd/supportticket.go b/pkg/cmd/supportticket.go index 77d3898..563cbc5 100644 --- a/pkg/cmd/supportticket.go +++ b/pkg/cmd/supportticket.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -127,8 +126,15 @@ func handleSupportTicketsCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "support:tickets create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "support:tickets create", + Transform: transform, + }) } func handleSupportTicketsRetrieve(ctx context.Context, cmd *cli.Command) error { @@ -162,8 +168,15 @@ func handleSupportTicketsRetrieve(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "support:tickets retrieve", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "support:tickets retrieve", + Transform: transform, + }) } func handleSupportTicketsUpdate(ctx context.Context, cmd *cli.Command) error { @@ -204,8 +217,15 @@ func handleSupportTicketsUpdate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "support:tickets update", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "support:tickets update", + Transform: transform, + }) } func handleSupportTicketsList(ctx context.Context, cmd *cli.Command) error { @@ -236,8 +256,15 @@ func handleSupportTicketsList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "support:tickets list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "support:tickets list", + Transform: transform, + }) } func handleSupportTicketsReply(ctx context.Context, cmd *cli.Command) error { @@ -278,6 +305,13 @@ func handleSupportTicketsReply(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "support:tickets reply", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "support:tickets reply", + Transform: transform, + }) } diff --git a/pkg/cmd/supportticket_test.go b/pkg/cmd/supportticket_test.go index 4fbc0cc..c623261 100644 --- a/pkg/cmd/supportticket_test.go +++ b/pkg/cmd/supportticket_test.go @@ -14,7 +14,6 @@ func TestSupportTicketsCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "support:tickets", "create", "--body", "I am unable to connect my X account. Please help.", "--subject", "Cannot connect X account", @@ -29,7 +28,6 @@ func TestSupportTicketsCreate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "support:tickets", "create", ) }) @@ -41,7 +39,6 @@ func TestSupportTicketsRetrieve(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "support:tickets", "retrieve", "--id", "messages_value", ) @@ -54,7 +51,6 @@ func TestSupportTicketsUpdate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "support:tickets", "update", "--id", "id", "--status", "resolved", @@ -67,7 +63,6 @@ func TestSupportTicketsUpdate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "support:tickets", "update", "--id", "id", ) @@ -80,7 +75,6 @@ func TestSupportTicketsList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "support:tickets", "list", ) }) @@ -92,7 +86,6 @@ func TestSupportTicketsReply(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "support:tickets", "reply", "--id", "id", "--body", "Thank you for the update.", @@ -105,7 +98,6 @@ func TestSupportTicketsReply(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "support:tickets", "reply", "--id", "id", ) diff --git a/pkg/cmd/trend.go b/pkg/cmd/trend.go index 5d2c3bc..1c0efbd 100644 --- a/pkg/cmd/trend.go +++ b/pkg/cmd/trend.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -17,7 +16,7 @@ import ( var trendsList = cli.Command{ Name: "list", - Usage: "Get regional trending topics", + Usage: "Get trending hashtags and topics by region (alias)", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[int64]{ @@ -67,6 +66,13 @@ func handleTrendsList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "trends list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "trends list", + Transform: transform, + }) } diff --git a/pkg/cmd/trend_test.go b/pkg/cmd/trend_test.go index 35e10e6..2896db4 100644 --- a/pkg/cmd/trend_test.go +++ b/pkg/cmd/trend_test.go @@ -14,7 +14,6 @@ func TestTrendsList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "trends", "list", "--count", "1", "--woeid", "0", diff --git a/pkg/cmd/version.go b/pkg/cmd/version.go index 5266c84..b113456 100644 --- a/pkg/cmd/version.go +++ b/pkg/cmd/version.go @@ -2,4 +2,4 @@ package cmd -const Version = "0.3.0" // x-release-please-version +const Version = "0.4.0" // x-release-please-version diff --git a/pkg/cmd/webhook.go b/pkg/cmd/webhook.go index 1b19419..2dae537 100644 --- a/pkg/cmd/webhook.go +++ b/pkg/cmd/webhook.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -145,8 +144,15 @@ func handleWebhooksCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "webhooks create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "webhooks create", + Transform: transform, + }) } func handleWebhooksUpdate(ctx context.Context, cmd *cli.Command) error { @@ -187,8 +193,15 @@ func handleWebhooksUpdate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "webhooks update", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "webhooks update", + Transform: transform, + }) } func handleWebhooksList(ctx context.Context, cmd *cli.Command) error { @@ -219,8 +232,15 @@ func handleWebhooksList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "webhooks list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "webhooks list", + Transform: transform, + }) } func handleWebhooksDeactivate(ctx context.Context, cmd *cli.Command) error { @@ -254,8 +274,15 @@ func handleWebhooksDeactivate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "webhooks deactivate", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "webhooks deactivate", + Transform: transform, + }) } func handleWebhooksListDeliveries(ctx context.Context, cmd *cli.Command) error { @@ -289,8 +316,15 @@ func handleWebhooksListDeliveries(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "webhooks list-deliveries", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "webhooks list-deliveries", + Transform: transform, + }) } func handleWebhooksTest(ctx context.Context, cmd *cli.Command) error { @@ -324,6 +358,13 @@ func handleWebhooksTest(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "webhooks test", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "webhooks test", + Transform: transform, + }) } diff --git a/pkg/cmd/webhook_test.go b/pkg/cmd/webhook_test.go index fc59953..0e6438d 100644 --- a/pkg/cmd/webhook_test.go +++ b/pkg/cmd/webhook_test.go @@ -14,10 +14,9 @@ func TestWebhooksCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "webhooks", "create", "--event-type", "tweet.new", - "--event-type", "follower.gained", + "--event-type", "tweet.reply", "--url", "https://example.com/webhook", ) }) @@ -27,12 +26,11 @@ func TestWebhooksCreate(t *testing.T) { pipeData := []byte("" + "eventTypes:\n" + " - tweet.new\n" + - " - follower.gained\n" + + " - tweet.reply\n" + "url: https://example.com/webhook\n") mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "webhooks", "create", ) }) @@ -44,7 +42,6 @@ func TestWebhooksUpdate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "webhooks", "update", "--id", "id", "--event-type", "tweet.new", @@ -63,7 +60,6 @@ func TestWebhooksUpdate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "webhooks", "update", "--id", "id", ) @@ -76,7 +72,6 @@ func TestWebhooksList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "webhooks", "list", ) }) @@ -88,7 +83,6 @@ func TestWebhooksDeactivate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "webhooks", "deactivate", "--id", "id", ) @@ -101,7 +95,6 @@ func TestWebhooksListDeliveries(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "webhooks", "list-deliveries", "--id", "id", ) @@ -114,7 +107,6 @@ func TestWebhooksTest(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "webhooks", "test", "--id", "id", ) diff --git a/pkg/cmd/x.go b/pkg/cmd/x.go index abe0f9a..30d8b5b 100644 --- a/pkg/cmd/x.go +++ b/pkg/cmd/x.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -71,10 +70,23 @@ var xGetNotifications = cli.Command{ } var xGetTrends = cli.Command{ - Name: "get-trends", - Usage: "Get trending topics", - Suggest: true, - Flags: []cli.Flag{}, + Name: "get-trends", + Usage: "Get trending hashtags and topics from X by region", + Suggest: true, + Flags: []cli.Flag{ + &requestflag.Flag[int64]{ + Name: "count", + Usage: "Number of trending topics to return (1-50, default 30)", + Default: 30, + QueryPath: "count", + }, + &requestflag.Flag[int64]{ + Name: "woeid", + Usage: "Region WOEID (1=Worldwide, 23424977=US, 23424975=UK, 23424969=Turkey)", + Default: 1, + QueryPath: "woeid", + }, + }, Action: handleXGetTrends, HideHelpCommand: true, } @@ -110,8 +122,15 @@ func handleXGetArticle(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x get-article", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x get-article", + Transform: transform, + }) } func handleXGetHomeTimeline(ctx context.Context, cmd *cli.Command) error { @@ -144,8 +163,15 @@ func handleXGetHomeTimeline(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x get-home-timeline", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x get-home-timeline", + Transform: transform, + }) } func handleXGetNotifications(ctx context.Context, cmd *cli.Command) error { @@ -178,8 +204,15 @@ func handleXGetNotifications(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x get-notifications", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x get-notifications", + Transform: transform, + }) } func handleXGetTrends(ctx context.Context, cmd *cli.Command) error { @@ -190,6 +223,8 @@ func handleXGetTrends(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) } + params := xtwitterscraper.XGetTrendsParams{} + options, err := flagOptions( cmd, apiquery.NestedQueryFormatBrackets, @@ -203,13 +238,20 @@ func handleXGetTrends(ctx context.Context, cmd *cli.Command) error { var res []byte options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.X.GetTrends(ctx, options...) + _, err = client.X.GetTrends(ctx, params, options...) if err != nil { return err } obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x get-trends", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x get-trends", + Transform: transform, + }) } diff --git a/pkg/cmd/x_test.go b/pkg/cmd/x_test.go index 12d6672..6270edb 100644 --- a/pkg/cmd/x_test.go +++ b/pkg/cmd/x_test.go @@ -14,7 +14,6 @@ func TestXGetArticle(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x", "get-article", "--tweet-id", "tweetId", ) @@ -27,7 +26,6 @@ func TestXGetHomeTimeline(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x", "get-home-timeline", "--cursor", "cursor", "--seen-tweet-ids", "seenTweetIds", @@ -41,7 +39,6 @@ func TestXGetNotifications(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x", "get-notifications", "--cursor", "cursor", "--type", "All", @@ -55,8 +52,9 @@ func TestXGetTrends(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x", "get-trends", + "--count", "1", + "--woeid", "0", ) }) } diff --git a/pkg/cmd/xaccount.go b/pkg/cmd/xaccount.go index 0a4115d..ea2ecaf 100644 --- a/pkg/cmd/xaccount.go +++ b/pkg/cmd/xaccount.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -114,6 +113,16 @@ var xAccountsReauth = cli.Command{ Required: true, BodyPath: "password", }, + &requestflag.Flag[string]{ + Name: "email", + Usage: "Email for the X account (updates stored email)", + BodyPath: "email", + }, + &requestflag.Flag[string]{ + Name: "proxy-country", + Usage: "Two-letter country code for login proxy region", + BodyPath: "proxy_country", + }, &requestflag.Flag[string]{ Name: "totp-secret", Usage: "TOTP secret for 2FA re-authentication", @@ -154,8 +163,15 @@ func handleXAccountsCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:accounts create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:accounts create", + Transform: transform, + }) } func handleXAccountsRetrieve(ctx context.Context, cmd *cli.Command) error { @@ -189,8 +205,15 @@ func handleXAccountsRetrieve(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:accounts retrieve", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:accounts retrieve", + Transform: transform, + }) } func handleXAccountsList(ctx context.Context, cmd *cli.Command) error { @@ -221,8 +244,15 @@ func handleXAccountsList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:accounts list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:accounts list", + Transform: transform, + }) } func handleXAccountsDelete(ctx context.Context, cmd *cli.Command) error { @@ -256,8 +286,15 @@ func handleXAccountsDelete(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:accounts delete", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:accounts delete", + Transform: transform, + }) } func handleXAccountsBulkRetry(ctx context.Context, cmd *cli.Command) error { @@ -288,8 +325,15 @@ func handleXAccountsBulkRetry(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:accounts bulk-retry", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:accounts bulk-retry", + Transform: transform, + }) } func handleXAccountsReauth(ctx context.Context, cmd *cli.Command) error { @@ -330,6 +374,13 @@ func handleXAccountsReauth(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:accounts reauth", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:accounts reauth", + Transform: transform, + }) } diff --git a/pkg/cmd/xaccount_test.go b/pkg/cmd/xaccount_test.go index 5bf47fe..5025dbe 100644 --- a/pkg/cmd/xaccount_test.go +++ b/pkg/cmd/xaccount_test.go @@ -14,7 +14,6 @@ func TestXAccountsCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:accounts", "create", "--email", "user@example.com", "--password", "s3cur3Pa$$w0rd", @@ -35,7 +34,6 @@ func TestXAccountsCreate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:accounts", "create", ) }) @@ -47,7 +45,6 @@ func TestXAccountsRetrieve(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:accounts", "retrieve", "--id", "id", ) @@ -60,7 +57,6 @@ func TestXAccountsList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:accounts", "list", ) }) @@ -72,7 +68,6 @@ func TestXAccountsDelete(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:accounts", "delete", "--id", "id", ) @@ -85,7 +80,6 @@ func TestXAccountsBulkRetry(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:accounts", "bulk-retry", ) }) @@ -97,10 +91,11 @@ func TestXAccountsReauth(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:accounts", "reauth", "--id", "id", "--password", "password_value", + "--email", "user@example.com", + "--proxy-country", "US", "--totp-secret", "totp_secret_value", ) }) @@ -109,11 +104,12 @@ func TestXAccountsReauth(t *testing.T) { // Test piping YAML data over stdin pipeData := []byte("" + "password: password_value\n" + + "email: user@example.com\n" + + "proxy_country: US\n" + "totp_secret: totp_secret_value\n") mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:accounts", "reauth", "--id", "id", ) diff --git a/pkg/cmd/xbookmark.go b/pkg/cmd/xbookmark.go index 9f50e8c..edce2b3 100644 --- a/pkg/cmd/xbookmark.go +++ b/pkg/cmd/xbookmark.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -74,8 +73,15 @@ func handleXBookmarksList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:bookmarks list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:bookmarks list", + Transform: transform, + }) } func handleXBookmarksRetrieveFolders(ctx context.Context, cmd *cli.Command) error { @@ -106,6 +112,13 @@ func handleXBookmarksRetrieveFolders(ctx context.Context, cmd *cli.Command) erro obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:bookmarks retrieve-folders", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:bookmarks retrieve-folders", + Transform: transform, + }) } diff --git a/pkg/cmd/xbookmark_test.go b/pkg/cmd/xbookmark_test.go index 6a0ae14..a9e7de4 100644 --- a/pkg/cmd/xbookmark_test.go +++ b/pkg/cmd/xbookmark_test.go @@ -14,7 +14,6 @@ func TestXBookmarksList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:bookmarks", "list", "--cursor", "folders_value", "--folder-id", "folderId", @@ -28,7 +27,6 @@ func TestXBookmarksRetrieveFolders(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:bookmarks", "retrieve-folders", ) }) diff --git a/pkg/cmd/xcommunity.go b/pkg/cmd/xcommunity.go index b4e9091..142e6ef 100644 --- a/pkg/cmd/xcommunity.go +++ b/pkg/cmd/xcommunity.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -70,7 +69,7 @@ var xCommunitiesDelete = cli.Command{ var xCommunitiesRetrieveInfo = cli.Command{ Name: "retrieve-info", - Usage: "Get community details", + Usage: "Get community name, description and member count", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -84,7 +83,7 @@ var xCommunitiesRetrieveInfo = cli.Command{ var xCommunitiesRetrieveMembers = cli.Command{ Name: "retrieve-members", - Usage: "Get community members", + Usage: "List members of a community", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -103,7 +102,7 @@ var xCommunitiesRetrieveMembers = cli.Command{ var xCommunitiesRetrieveModerators = cli.Command{ Name: "retrieve-moderators", - Usage: "Get community moderators", + Usage: "List moderators of a community", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -122,7 +121,7 @@ var xCommunitiesRetrieveModerators = cli.Command{ var xCommunitiesRetrieveSearch = cli.Command{ Name: "retrieve-search", - Usage: "Search tweets across communities", + Usage: "Search for communities by keyword", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -176,8 +175,15 @@ func handleXCommunitiesCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:communities create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:communities create", + Transform: transform, + }) } func handleXCommunitiesDelete(ctx context.Context, cmd *cli.Command) error { @@ -218,8 +224,15 @@ func handleXCommunitiesDelete(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:communities delete", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:communities delete", + Transform: transform, + }) } func handleXCommunitiesRetrieveInfo(ctx context.Context, cmd *cli.Command) error { @@ -253,8 +266,15 @@ func handleXCommunitiesRetrieveInfo(ctx context.Context, cmd *cli.Command) error obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:communities retrieve-info", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:communities retrieve-info", + Transform: transform, + }) } func handleXCommunitiesRetrieveMembers(ctx context.Context, cmd *cli.Command) error { @@ -295,8 +315,15 @@ func handleXCommunitiesRetrieveMembers(ctx context.Context, cmd *cli.Command) er obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:communities retrieve-members", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:communities retrieve-members", + Transform: transform, + }) } func handleXCommunitiesRetrieveModerators(ctx context.Context, cmd *cli.Command) error { @@ -337,8 +364,15 @@ func handleXCommunitiesRetrieveModerators(ctx context.Context, cmd *cli.Command) obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:communities retrieve-moderators", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:communities retrieve-moderators", + Transform: transform, + }) } func handleXCommunitiesRetrieveSearch(ctx context.Context, cmd *cli.Command) error { @@ -371,6 +405,13 @@ func handleXCommunitiesRetrieveSearch(ctx context.Context, cmd *cli.Command) err obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:communities retrieve-search", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:communities retrieve-search", + Transform: transform, + }) } diff --git a/pkg/cmd/xcommunity_test.go b/pkg/cmd/xcommunity_test.go index b21384c..28be39d 100644 --- a/pkg/cmd/xcommunity_test.go +++ b/pkg/cmd/xcommunity_test.go @@ -14,7 +14,6 @@ func TestXCommunitiesCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:communities", "create", "--account", "@elonmusk", "--name", "Example Name", @@ -31,7 +30,6 @@ func TestXCommunitiesCreate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:communities", "create", ) }) @@ -43,7 +41,6 @@ func TestXCommunitiesDelete(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:communities", "delete", "--id", "id", "--account", "@elonmusk", @@ -59,7 +56,6 @@ func TestXCommunitiesDelete(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:communities", "delete", "--id", "id", ) @@ -72,7 +68,6 @@ func TestXCommunitiesRetrieveInfo(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:communities", "retrieve-info", "--id", "id", ) @@ -85,7 +80,6 @@ func TestXCommunitiesRetrieveMembers(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:communities", "retrieve-members", "--id", "id", "--cursor", "cursor", @@ -99,7 +93,6 @@ func TestXCommunitiesRetrieveModerators(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:communities", "retrieve-moderators", "--id", "id", "--cursor", "cursor", @@ -113,7 +106,6 @@ func TestXCommunitiesRetrieveSearch(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:communities", "retrieve-search", "--q", "q", "--cursor", "cursor", diff --git a/pkg/cmd/xcommunityjoin.go b/pkg/cmd/xcommunityjoin.go index c38c84f..994f75a 100644 --- a/pkg/cmd/xcommunityjoin.go +++ b/pkg/cmd/xcommunityjoin.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -93,8 +92,15 @@ func handleXCommunitiesJoinCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:communities:join create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:communities:join create", + Transform: transform, + }) } func handleXCommunitiesJoinDeleteAll(ctx context.Context, cmd *cli.Command) error { @@ -135,6 +141,13 @@ func handleXCommunitiesJoinDeleteAll(ctx context.Context, cmd *cli.Command) erro obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:communities:join delete-all", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:communities:join delete-all", + Transform: transform, + }) } diff --git a/pkg/cmd/xcommunityjoin_test.go b/pkg/cmd/xcommunityjoin_test.go index 97b9d9a..26a2982 100644 --- a/pkg/cmd/xcommunityjoin_test.go +++ b/pkg/cmd/xcommunityjoin_test.go @@ -14,7 +14,6 @@ func TestXCommunitiesJoinCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:communities:join", "create", "--id", "id", "--account", "@elonmusk", @@ -27,7 +26,6 @@ func TestXCommunitiesJoinCreate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:communities:join", "create", "--id", "id", ) @@ -40,7 +38,6 @@ func TestXCommunitiesJoinDeleteAll(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:communities:join", "delete-all", "--id", "id", "--account", "@elonmusk", @@ -53,7 +50,6 @@ func TestXCommunitiesJoinDeleteAll(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:communities:join", "delete-all", "--id", "id", ) diff --git a/pkg/cmd/xcommunitytweet.go b/pkg/cmd/xcommunitytweet.go index a93bcbc..af513f8 100644 --- a/pkg/cmd/xcommunitytweet.go +++ b/pkg/cmd/xcommunitytweet.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -17,7 +16,7 @@ import ( var xCommunitiesTweetsList = cli.Command{ Name: "list", - Usage: "Search tweets across all communities", + Usage: "List tweets across all communities", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -43,7 +42,7 @@ var xCommunitiesTweetsList = cli.Command{ var xCommunitiesTweetsListByCommunity = cli.Command{ Name: "list-by-community", - Usage: "Get community tweets", + Usage: "List tweets posted in a community", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -90,8 +89,15 @@ func handleXCommunitiesTweetsList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:communities:tweets list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:communities:tweets list", + Transform: transform, + }) } func handleXCommunitiesTweetsListByCommunity(ctx context.Context, cmd *cli.Command) error { @@ -132,6 +138,13 @@ func handleXCommunitiesTweetsListByCommunity(ctx context.Context, cmd *cli.Comma obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:communities:tweets list-by-community", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:communities:tweets list-by-community", + Transform: transform, + }) } diff --git a/pkg/cmd/xcommunitytweet_test.go b/pkg/cmd/xcommunitytweet_test.go index 3432101..e116815 100644 --- a/pkg/cmd/xcommunitytweet_test.go +++ b/pkg/cmd/xcommunitytweet_test.go @@ -14,7 +14,6 @@ func TestXCommunitiesTweetsList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:communities:tweets", "list", "--q", "q", "--cursor", "cursor", @@ -29,7 +28,6 @@ func TestXCommunitiesTweetsListByCommunity(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:communities:tweets", "list-by-community", "--id", "id", "--cursor", "cursor", diff --git a/pkg/cmd/xdm.go b/pkg/cmd/xdm.go index 0ab0196..aee7092 100644 --- a/pkg/cmd/xdm.go +++ b/pkg/cmd/xdm.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -110,8 +109,15 @@ func handleXDmRetrieveHistory(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:dm retrieve-history", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:dm retrieve-history", + Transform: transform, + }) } func handleXDmSend(ctx context.Context, cmd *cli.Command) error { @@ -152,6 +158,13 @@ func handleXDmSend(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:dm send", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:dm send", + Transform: transform, + }) } diff --git a/pkg/cmd/xdm_test.go b/pkg/cmd/xdm_test.go index f53bd38..176aea1 100644 --- a/pkg/cmd/xdm_test.go +++ b/pkg/cmd/xdm_test.go @@ -14,7 +14,6 @@ func TestXDmRetrieveHistory(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:dm", "retrieve-history", "--user-id", "userId", "--cursor", "cursor", @@ -29,7 +28,6 @@ func TestXDmSend(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:dm", "send", "--user-id", "userId", "--account", "@elonmusk", @@ -50,7 +48,6 @@ func TestXDmSend(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:dm", "send", "--user-id", "userId", ) diff --git a/pkg/cmd/xfollower.go b/pkg/cmd/xfollower.go index 7f4093f..3a55af4 100644 --- a/pkg/cmd/xfollower.go +++ b/pkg/cmd/xfollower.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -17,7 +16,7 @@ import ( var xFollowersCheck = cli.Command{ Name: "check", - Usage: "Check follow relationship", + Usage: "Check if one user follows another", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -67,6 +66,13 @@ func handleXFollowersCheck(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:followers check", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:followers check", + Transform: transform, + }) } diff --git a/pkg/cmd/xfollower_test.go b/pkg/cmd/xfollower_test.go index ed7e5f2..a6524b0 100644 --- a/pkg/cmd/xfollower_test.go +++ b/pkg/cmd/xfollower_test.go @@ -14,7 +14,6 @@ func TestXFollowersCheck(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:followers", "check", "--source", "source", "--target", "target", diff --git a/pkg/cmd/xlist.go b/pkg/cmd/xlist.go index 044f6db..86b28ec 100644 --- a/pkg/cmd/xlist.go +++ b/pkg/cmd/xlist.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -17,7 +16,7 @@ import ( var xListsRetrieveFollowers = cli.Command{ Name: "retrieve-followers", - Usage: "Get list followers", + Usage: "List followers of an X List", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -36,7 +35,7 @@ var xListsRetrieveFollowers = cli.Command{ var xListsRetrieveMembers = cli.Command{ Name: "retrieve-members", - Usage: "Get list members", + Usage: "List members of an X List", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -55,7 +54,7 @@ var xListsRetrieveMembers = cli.Command{ var xListsRetrieveTweets = cli.Command{ Name: "retrieve-tweets", - Usage: "Get list tweets", + Usage: "List tweets from an X List", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -125,8 +124,15 @@ func handleXListsRetrieveFollowers(ctx context.Context, cmd *cli.Command) error obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:lists retrieve-followers", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:lists retrieve-followers", + Transform: transform, + }) } func handleXListsRetrieveMembers(ctx context.Context, cmd *cli.Command) error { @@ -167,8 +173,15 @@ func handleXListsRetrieveMembers(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:lists retrieve-members", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:lists retrieve-members", + Transform: transform, + }) } func handleXListsRetrieveTweets(ctx context.Context, cmd *cli.Command) error { @@ -209,6 +222,13 @@ func handleXListsRetrieveTweets(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:lists retrieve-tweets", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:lists retrieve-tweets", + Transform: transform, + }) } diff --git a/pkg/cmd/xlist_test.go b/pkg/cmd/xlist_test.go index 4f5aec5..f57ece1 100644 --- a/pkg/cmd/xlist_test.go +++ b/pkg/cmd/xlist_test.go @@ -14,7 +14,6 @@ func TestXListsRetrieveFollowers(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:lists", "retrieve-followers", "--id", "id", "--cursor", "cursor", @@ -28,7 +27,6 @@ func TestXListsRetrieveMembers(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:lists", "retrieve-members", "--id", "id", "--cursor", "cursor", @@ -42,7 +40,6 @@ func TestXListsRetrieveTweets(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:lists", "retrieve-tweets", "--id", "id", "--cursor", "cursor", diff --git a/pkg/cmd/xmedia.go b/pkg/cmd/xmedia.go index 4d48df4..8f66b33 100644 --- a/pkg/cmd/xmedia.go +++ b/pkg/cmd/xmedia.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -17,7 +16,7 @@ import ( var xMediaDownload = cli.Command{ Name: "download", - Usage: "Download tweet media", + Usage: "Download images and videos from tweets", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[[]string]{ @@ -92,8 +91,15 @@ func handleXMediaDownload(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:media download", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:media download", + Transform: transform, + }) } func handleXMediaUpload(ctx context.Context, cmd *cli.Command) error { @@ -126,6 +132,13 @@ func handleXMediaUpload(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:media upload", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:media upload", + Transform: transform, + }) } diff --git a/pkg/cmd/xmedia_test.go b/pkg/cmd/xmedia_test.go index 675bc23..c8cfb9c 100644 --- a/pkg/cmd/xmedia_test.go +++ b/pkg/cmd/xmedia_test.go @@ -15,7 +15,6 @@ func TestXMediaDownload(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:media", "download", "--tweet-id", "1234567890", "--tweet-id", "1234567891", @@ -33,7 +32,6 @@ func TestXMediaDownload(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:media", "download", ) }) @@ -45,7 +43,6 @@ func TestXMediaUpload(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:media", "upload", "--account", "@elonmusk", "--file", mocktest.TestFile(t, "Example data"), @@ -65,7 +62,6 @@ func TestXMediaUpload(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:media", "upload", ) }) diff --git a/pkg/cmd/xprofile.go b/pkg/cmd/xprofile.go index 65acfee..25636db 100644 --- a/pkg/cmd/xprofile.go +++ b/pkg/cmd/xprofile.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -126,8 +125,15 @@ func handleXProfileUpdate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:profile update", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:profile update", + Transform: transform, + }) } func handleXProfileUpdateAvatar(ctx context.Context, cmd *cli.Command) error { @@ -160,8 +166,15 @@ func handleXProfileUpdateAvatar(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:profile update-avatar", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:profile update-avatar", + Transform: transform, + }) } func handleXProfileUpdateBanner(ctx context.Context, cmd *cli.Command) error { @@ -194,6 +207,13 @@ func handleXProfileUpdateBanner(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:profile update-banner", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:profile update-banner", + Transform: transform, + }) } diff --git a/pkg/cmd/xprofile_test.go b/pkg/cmd/xprofile_test.go index 8a4ac1a..02641ef 100644 --- a/pkg/cmd/xprofile_test.go +++ b/pkg/cmd/xprofile_test.go @@ -15,7 +15,6 @@ func TestXProfileUpdate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:profile", "update", "--account", "@elonmusk", "--description", "description_value", @@ -36,7 +35,6 @@ func TestXProfileUpdate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:profile", "update", ) }) @@ -48,7 +46,6 @@ func TestXProfileUpdateAvatar(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:profile", "update-avatar", "--account", "@elonmusk", "--file", mocktest.TestFile(t, "Example data"), @@ -66,7 +63,6 @@ func TestXProfileUpdateAvatar(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:profile", "update-avatar", ) }) @@ -78,7 +74,6 @@ func TestXProfileUpdateBanner(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:profile", "update-banner", "--account", "@elonmusk", "--file", mocktest.TestFile(t, "Example data"), @@ -96,7 +91,6 @@ func TestXProfileUpdateBanner(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:profile", "update-banner", ) }) diff --git a/pkg/cmd/xtweet.go b/pkg/cmd/xtweet.go index e6a01a0..02477dd 100644 --- a/pkg/cmd/xtweet.go +++ b/pkg/cmd/xtweet.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -26,11 +25,6 @@ var xTweetsCreate = cli.Command{ Required: true, BodyPath: "account", }, - &requestflag.Flag[string]{ - Name: "text", - Required: true, - BodyPath: "text", - }, &requestflag.Flag[string]{ Name: "attachment-url", BodyPath: "attachment_url", @@ -43,14 +37,25 @@ var xTweetsCreate = cli.Command{ Name: "is-note-tweet", BodyPath: "is_note_tweet", }, + &requestflag.Flag[[]string]{ + Name: "media", + Usage: "Array of media URLs to attach (mutually exclusive with media_ids)", + BodyPath: "media", + }, &requestflag.Flag[[]string]{ Name: "media-id", + Usage: "Array of media IDs to attach (mutually exclusive with media)", BodyPath: "media_ids", }, &requestflag.Flag[string]{ Name: "reply-to-tweet-id", BodyPath: "reply_to_tweet_id", }, + &requestflag.Flag[string]{ + Name: "text", + Usage: "Tweet text (optional when media is provided)", + BodyPath: "text", + }, }, Action: handleXTweetsCreate, HideHelpCommand: true, @@ -58,7 +63,7 @@ var xTweetsCreate = cli.Command{ var xTweetsRetrieve = cli.Command{ Name: "retrieve", - Usage: "Look up tweet", + Usage: "Get tweet with full text, author, metrics and media", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -108,7 +113,7 @@ var xTweetsDelete = cli.Command{ var xTweetsGetFavoriters = cli.Command{ Name: "get-favoriters", - Usage: "Get users who liked a tweet", + Usage: "List users who liked a tweet", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -127,7 +132,7 @@ var xTweetsGetFavoriters = cli.Command{ var xTweetsGetQuotes = cli.Command{ Name: "get-quotes", - Usage: "Get quote tweets of a tweet", + Usage: "List quote tweets of a tweet", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -161,7 +166,7 @@ var xTweetsGetQuotes = cli.Command{ var xTweetsGetReplies = cli.Command{ Name: "get-replies", - Usage: "Get replies to a tweet", + Usage: "List replies to a tweet", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -190,7 +195,7 @@ var xTweetsGetReplies = cli.Command{ var xTweetsGetRetweeters = cli.Command{ Name: "get-retweeters", - Usage: "Get users who retweeted a tweet", + Usage: "List users who retweeted a tweet", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -209,7 +214,7 @@ var xTweetsGetRetweeters = cli.Command{ var xTweetsGetThread = cli.Command{ Name: "get-thread", - Usage: "Get thread context for a tweet", + Usage: "Get full conversation thread for a tweet", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -228,7 +233,7 @@ var xTweetsGetThread = cli.Command{ var xTweetsSearch = cli.Command{ Name: "search", - Usage: "Search tweets", + Usage: "Search tweets with X query operators and pagination", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -299,8 +304,15 @@ func handleXTweetsCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets create", + Transform: transform, + }) } func handleXTweetsRetrieve(ctx context.Context, cmd *cli.Command) error { @@ -334,8 +346,15 @@ func handleXTweetsRetrieve(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets retrieve", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets retrieve", + Transform: transform, + }) } func handleXTweetsList(ctx context.Context, cmd *cli.Command) error { @@ -368,8 +387,15 @@ func handleXTweetsList(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets list", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets list", + Transform: transform, + }) } func handleXTweetsDelete(ctx context.Context, cmd *cli.Command) error { @@ -410,8 +436,15 @@ func handleXTweetsDelete(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets delete", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets delete", + Transform: transform, + }) } func handleXTweetsGetFavoriters(ctx context.Context, cmd *cli.Command) error { @@ -452,8 +485,15 @@ func handleXTweetsGetFavoriters(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets get-favoriters", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets get-favoriters", + Transform: transform, + }) } func handleXTweetsGetQuotes(ctx context.Context, cmd *cli.Command) error { @@ -494,8 +534,15 @@ func handleXTweetsGetQuotes(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets get-quotes", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets get-quotes", + Transform: transform, + }) } func handleXTweetsGetReplies(ctx context.Context, cmd *cli.Command) error { @@ -536,8 +583,15 @@ func handleXTweetsGetReplies(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets get-replies", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets get-replies", + Transform: transform, + }) } func handleXTweetsGetRetweeters(ctx context.Context, cmd *cli.Command) error { @@ -578,8 +632,15 @@ func handleXTweetsGetRetweeters(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets get-retweeters", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets get-retweeters", + Transform: transform, + }) } func handleXTweetsGetThread(ctx context.Context, cmd *cli.Command) error { @@ -620,8 +681,15 @@ func handleXTweetsGetThread(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets get-thread", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets get-thread", + Transform: transform, + }) } func handleXTweetsSearch(ctx context.Context, cmd *cli.Command) error { @@ -654,6 +722,13 @@ func handleXTweetsSearch(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets search", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets search", + Transform: transform, + }) } diff --git a/pkg/cmd/xtweet_test.go b/pkg/cmd/xtweet_test.go index 56c57f0..7c83b96 100644 --- a/pkg/cmd/xtweet_test.go +++ b/pkg/cmd/xtweet_test.go @@ -14,15 +14,15 @@ func TestXTweetsCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets", "create", "--account", "@elonmusk", - "--text", "Just launched our new feature!", "--attachment-url", "https://x.com/elonmusk/status/1234567890", "--community-id", "1500000000000000000", "--is-note-tweet=false", + "--media", "https://example.com/image.jpg", "--media-id", "1234567890123456789", "--reply-to-tweet-id", "1234567890", + "--text", "Just launched our new feature!", ) }) @@ -30,17 +30,18 @@ func TestXTweetsCreate(t *testing.T) { // Test piping YAML data over stdin pipeData := []byte("" + "account: '@elonmusk'\n" + - "text: Just launched our new feature!\n" + "attachment_url: https://x.com/elonmusk/status/1234567890\n" + "community_id: '1500000000000000000'\n" + "is_note_tweet: false\n" + + "media:\n" + + " - https://example.com/image.jpg\n" + "media_ids:\n" + " - '1234567890123456789'\n" + - "reply_to_tweet_id: '1234567890'\n") + "reply_to_tweet_id: '1234567890'\n" + + "text: Just launched our new feature!\n") mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:tweets", "create", ) }) @@ -52,7 +53,6 @@ func TestXTweetsRetrieve(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets", "retrieve", "--id", "id", ) @@ -65,7 +65,6 @@ func TestXTweetsList(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets", "list", "--ids", "ids", ) @@ -78,7 +77,6 @@ func TestXTweetsDelete(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets", "delete", "--id", "id", "--account", "@elonmusk", @@ -91,7 +89,6 @@ func TestXTweetsDelete(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:tweets", "delete", "--id", "id", ) @@ -104,7 +101,6 @@ func TestXTweetsGetFavoriters(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets", "get-favoriters", "--id", "id", "--cursor", "cursor", @@ -118,7 +114,6 @@ func TestXTweetsGetQuotes(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets", "get-quotes", "--id", "id", "--cursor", "cursor", @@ -135,7 +130,6 @@ func TestXTweetsGetReplies(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets", "get-replies", "--id", "id", "--cursor", "cursor", @@ -151,7 +145,6 @@ func TestXTweetsGetRetweeters(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets", "get-retweeters", "--id", "id", "--cursor", "cursor", @@ -165,7 +158,6 @@ func TestXTweetsGetThread(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets", "get-thread", "--id", "id", "--cursor", "cursor", @@ -179,7 +171,6 @@ func TestXTweetsSearch(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets", "search", "--q", "q", "--cursor", "cursor", diff --git a/pkg/cmd/xtweetlike.go b/pkg/cmd/xtweetlike.go index 313c0a0..816e17f 100644 --- a/pkg/cmd/xtweetlike.go +++ b/pkg/cmd/xtweetlike.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -93,8 +92,15 @@ func handleXTweetsLikeCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets:like create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets:like create", + Transform: transform, + }) } func handleXTweetsLikeDelete(ctx context.Context, cmd *cli.Command) error { @@ -135,6 +141,13 @@ func handleXTweetsLikeDelete(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets:like delete", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets:like delete", + Transform: transform, + }) } diff --git a/pkg/cmd/xtweetlike_test.go b/pkg/cmd/xtweetlike_test.go index 2359908..81d2957 100644 --- a/pkg/cmd/xtweetlike_test.go +++ b/pkg/cmd/xtweetlike_test.go @@ -14,7 +14,6 @@ func TestXTweetsLikeCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets:like", "create", "--id", "id", "--account", "@elonmusk", @@ -27,7 +26,6 @@ func TestXTweetsLikeCreate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:tweets:like", "create", "--id", "id", ) @@ -40,7 +38,6 @@ func TestXTweetsLikeDelete(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets:like", "delete", "--id", "id", "--account", "@elonmusk", @@ -53,7 +50,6 @@ func TestXTweetsLikeDelete(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:tweets:like", "delete", "--id", "id", ) diff --git a/pkg/cmd/xtweetretweet.go b/pkg/cmd/xtweetretweet.go index afb2217..d700ecf 100644 --- a/pkg/cmd/xtweetretweet.go +++ b/pkg/cmd/xtweetretweet.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -93,8 +92,15 @@ func handleXTweetsRetweetCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets:retweet create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets:retweet create", + Transform: transform, + }) } func handleXTweetsRetweetDelete(ctx context.Context, cmd *cli.Command) error { @@ -135,6 +141,13 @@ func handleXTweetsRetweetDelete(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:tweets:retweet delete", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:tweets:retweet delete", + Transform: transform, + }) } diff --git a/pkg/cmd/xtweetretweet_test.go b/pkg/cmd/xtweetretweet_test.go index 5dc412e..4f4d2e8 100644 --- a/pkg/cmd/xtweetretweet_test.go +++ b/pkg/cmd/xtweetretweet_test.go @@ -14,7 +14,6 @@ func TestXTweetsRetweetCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets:retweet", "create", "--id", "id", "--account", "@elonmusk", @@ -27,7 +26,6 @@ func TestXTweetsRetweetCreate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:tweets:retweet", "create", "--id", "id", ) @@ -40,7 +38,6 @@ func TestXTweetsRetweetDelete(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:tweets:retweet", "delete", "--id", "id", "--account", "@elonmusk", @@ -53,7 +50,6 @@ func TestXTweetsRetweetDelete(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:tweets:retweet", "delete", "--id", "id", ) diff --git a/pkg/cmd/xuser.go b/pkg/cmd/xuser.go index ccd0f92..767fd28 100644 --- a/pkg/cmd/xuser.go +++ b/pkg/cmd/xuser.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -17,7 +16,7 @@ import ( var xUsersRetrieve = cli.Command{ Name: "retrieve", - Usage: "Look up X user", + Usage: "Get user profile with follower counts and verification", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -31,7 +30,7 @@ var xUsersRetrieve = cli.Command{ var xUsersRetrieveBatch = cli.Command{ Name: "retrieve-batch", - Usage: "Get multiple users by IDs", + Usage: "Look up multiple users by IDs in one call", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -47,7 +46,7 @@ var xUsersRetrieveBatch = cli.Command{ var xUsersRetrieveFollowers = cli.Command{ Name: "retrieve-followers", - Usage: "Get user followers", + Usage: "List followers of a user", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -71,7 +70,7 @@ var xUsersRetrieveFollowers = cli.Command{ var xUsersRetrieveFollowersYouKnow = cli.Command{ Name: "retrieve-followers-you-know", - Usage: "Get followers you know for a user", + Usage: "List mutual followers between you and a user", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -90,7 +89,7 @@ var xUsersRetrieveFollowersYouKnow = cli.Command{ var xUsersRetrieveFollowing = cli.Command{ Name: "retrieve-following", - Usage: "Get users this user follows", + Usage: "List accounts a user follows", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -114,7 +113,7 @@ var xUsersRetrieveFollowing = cli.Command{ var xUsersRetrieveLikes = cli.Command{ Name: "retrieve-likes", - Usage: "Get tweets liked by a user", + Usage: "List tweets liked by a user", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -133,7 +132,7 @@ var xUsersRetrieveLikes = cli.Command{ var xUsersRetrieveMedia = cli.Command{ Name: "retrieve-media", - Usage: "Get media tweets by a user", + Usage: "List media tweets posted by a user", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -152,7 +151,7 @@ var xUsersRetrieveMedia = cli.Command{ var xUsersRetrieveMentions = cli.Command{ Name: "retrieve-mentions", - Usage: "Get tweets mentioning a user", + Usage: "List tweets mentioning a user", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -202,7 +201,7 @@ var xUsersRetrieveSearch = cli.Command{ var xUsersRetrieveTweets = cli.Command{ Name: "retrieve-tweets", - Usage: "Get recent tweets by a user", + Usage: "List recent tweets posted by a user", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -233,7 +232,7 @@ var xUsersRetrieveTweets = cli.Command{ var xUsersRetrieveVerifiedFollowers = cli.Command{ Name: "retrieve-verified-followers", - Usage: "Get verified followers", + Usage: "List verified followers of a user", Suggest: true, Flags: []cli.Flag{ &requestflag.Flag[string]{ @@ -281,8 +280,15 @@ func handleXUsersRetrieve(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:users retrieve", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:users retrieve", + Transform: transform, + }) } func handleXUsersRetrieveBatch(ctx context.Context, cmd *cli.Command) error { @@ -315,8 +321,15 @@ func handleXUsersRetrieveBatch(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:users retrieve-batch", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:users retrieve-batch", + Transform: transform, + }) } func handleXUsersRetrieveFollowers(ctx context.Context, cmd *cli.Command) error { @@ -357,8 +370,15 @@ func handleXUsersRetrieveFollowers(ctx context.Context, cmd *cli.Command) error obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:users retrieve-followers", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:users retrieve-followers", + Transform: transform, + }) } func handleXUsersRetrieveFollowersYouKnow(ctx context.Context, cmd *cli.Command) error { @@ -399,8 +419,15 @@ func handleXUsersRetrieveFollowersYouKnow(ctx context.Context, cmd *cli.Command) obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:users retrieve-followers-you-know", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:users retrieve-followers-you-know", + Transform: transform, + }) } func handleXUsersRetrieveFollowing(ctx context.Context, cmd *cli.Command) error { @@ -441,8 +468,15 @@ func handleXUsersRetrieveFollowing(ctx context.Context, cmd *cli.Command) error obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:users retrieve-following", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:users retrieve-following", + Transform: transform, + }) } func handleXUsersRetrieveLikes(ctx context.Context, cmd *cli.Command) error { @@ -483,8 +517,15 @@ func handleXUsersRetrieveLikes(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:users retrieve-likes", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:users retrieve-likes", + Transform: transform, + }) } func handleXUsersRetrieveMedia(ctx context.Context, cmd *cli.Command) error { @@ -525,8 +566,15 @@ func handleXUsersRetrieveMedia(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:users retrieve-media", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:users retrieve-media", + Transform: transform, + }) } func handleXUsersRetrieveMentions(ctx context.Context, cmd *cli.Command) error { @@ -567,8 +615,15 @@ func handleXUsersRetrieveMentions(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:users retrieve-mentions", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:users retrieve-mentions", + Transform: transform, + }) } func handleXUsersRetrieveSearch(ctx context.Context, cmd *cli.Command) error { @@ -601,8 +656,15 @@ func handleXUsersRetrieveSearch(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:users retrieve-search", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:users retrieve-search", + Transform: transform, + }) } func handleXUsersRetrieveTweets(ctx context.Context, cmd *cli.Command) error { @@ -643,8 +705,15 @@ func handleXUsersRetrieveTweets(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:users retrieve-tweets", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:users retrieve-tweets", + Transform: transform, + }) } func handleXUsersRetrieveVerifiedFollowers(ctx context.Context, cmd *cli.Command) error { @@ -685,6 +754,13 @@ func handleXUsersRetrieveVerifiedFollowers(ctx context.Context, cmd *cli.Command obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:users retrieve-verified-followers", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:users retrieve-verified-followers", + Transform: transform, + }) } diff --git a/pkg/cmd/xuser_test.go b/pkg/cmd/xuser_test.go index 2f5ce8a..df51f17 100644 --- a/pkg/cmd/xuser_test.go +++ b/pkg/cmd/xuser_test.go @@ -14,7 +14,6 @@ func TestXUsersRetrieve(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:users", "retrieve", "--id", "id", ) @@ -27,7 +26,6 @@ func TestXUsersRetrieveBatch(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:users", "retrieve-batch", "--ids", "ids", ) @@ -40,7 +38,6 @@ func TestXUsersRetrieveFollowers(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:users", "retrieve-followers", "--id", "id", "--cursor", "cursor", @@ -55,7 +52,6 @@ func TestXUsersRetrieveFollowersYouKnow(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:users", "retrieve-followers-you-know", "--id", "id", "--cursor", "cursor", @@ -69,7 +65,6 @@ func TestXUsersRetrieveFollowing(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:users", "retrieve-following", "--id", "id", "--cursor", "cursor", @@ -84,7 +79,6 @@ func TestXUsersRetrieveLikes(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:users", "retrieve-likes", "--id", "id", "--cursor", "cursor", @@ -98,7 +92,6 @@ func TestXUsersRetrieveMedia(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:users", "retrieve-media", "--id", "id", "--cursor", "cursor", @@ -112,7 +105,6 @@ func TestXUsersRetrieveMentions(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:users", "retrieve-mentions", "--id", "id", "--cursor", "cursor", @@ -128,7 +120,6 @@ func TestXUsersRetrieveSearch(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:users", "retrieve-search", "--q", "q", "--cursor", "cursor", @@ -142,7 +133,6 @@ func TestXUsersRetrieveTweets(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:users", "retrieve-tweets", "--id", "id", "--cursor", "cursor", @@ -158,7 +148,6 @@ func TestXUsersRetrieveVerifiedFollowers(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:users", "retrieve-verified-followers", "--id", "id", "--cursor", "cursor", diff --git a/pkg/cmd/xuserfollow.go b/pkg/cmd/xuserfollow.go index feca28c..057d634 100644 --- a/pkg/cmd/xuserfollow.go +++ b/pkg/cmd/xuserfollow.go @@ -5,7 +5,6 @@ package cmd import ( "context" "fmt" - "os" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/apiquery" "github.com/Xquik-dev/x-twitter-scraper-cli/internal/requestflag" @@ -93,8 +92,15 @@ func handleXUsersFollowCreate(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:users:follow create", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:users:follow create", + Transform: transform, + }) } func handleXUsersFollowDeleteAll(ctx context.Context, cmd *cli.Command) error { @@ -135,6 +141,13 @@ func handleXUsersFollowDeleteAll(ctx context.Context, cmd *cli.Command) error { obj := gjson.ParseBytes(res) format := cmd.Root().String("format") + explicitFormat := cmd.Root().IsSet("format") transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "x:users:follow delete-all", obj, format, transform) + return ShowJSON(obj, ShowJSONOpts{ + ExplicitFormat: explicitFormat, + Format: format, + RawOutput: cmd.Root().Bool("raw-output"), + Title: "x:users:follow delete-all", + Transform: transform, + }) } diff --git a/pkg/cmd/xuserfollow_test.go b/pkg/cmd/xuserfollow_test.go index 9785107..ec3b44f 100644 --- a/pkg/cmd/xuserfollow_test.go +++ b/pkg/cmd/xuserfollow_test.go @@ -14,7 +14,6 @@ func TestXUsersFollowCreate(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:users:follow", "create", "--id", "id", "--account", "@elonmusk", @@ -27,7 +26,6 @@ func TestXUsersFollowCreate(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:users:follow", "create", "--id", "id", ) @@ -40,7 +38,6 @@ func TestXUsersFollowDeleteAll(t *testing.T) { mocktest.TestRunMockTestWithFlags( t, "--api-key", "string", - "--bearer-token", "string", "x:users:follow", "delete-all", "--id", "id", "--account", "@elonmusk", @@ -53,7 +50,6 @@ func TestXUsersFollowDeleteAll(t *testing.T) { mocktest.TestRunMockTestWithPipeAndFlags( t, pipeData, "--api-key", "string", - "--bearer-token", "string", "x:users:follow", "delete-all", "--id", "id", ) diff --git a/scripts/bootstrap b/scripts/bootstrap index 9ebb7d3..bbc786d 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/scripts/link b/scripts/link index 3666e7a..609474e 100755 --- a/scripts/link +++ b/scripts/link @@ -9,5 +9,9 @@ export GOPRIVATE="${GOPRIVATE:+$GOPRIVATE,}github.com/Xquik-dev/x-twitter-scrape REPLACEMENT="${1:-"../x-twitter-scraper-go"}" echo "==> Replacing Go SDK with $REPLACEMENT" -go mod edit -replace github.com/Xquik-dev/x-twitter-scraper-go="$REPLACEMENT" -go mod tidy -e +if [[ -d "$REPLACEMENT" ]] || go list -m "$REPLACEMENT" >/dev/null; then + go mod edit -replace github.com/Xquik-dev/x-twitter-scraper-go="$REPLACEMENT" + go mod tidy -e +else + echo "Skipping Go SDK replacement (branch may not exist on Go SDK)" +fi