diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index fbcc46a..b483d6e 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -25,4 +25,4 @@ jobs: run: go test -v ./... - name: Run lint - run: go fmt ./... \ No newline at end of file + run: gofmt -l . \ No newline at end of file diff --git a/api/media.go b/api/media.go index 4aa2a7d..9b2da85 100644 --- a/api/media.go +++ b/api/media.go @@ -27,10 +27,17 @@ type MediaUploader struct { authType string username string headers []string + trace bool +} + +type InitRequest struct { + TotalBytes int64 `json:"total_bytes"` + MediaType string `json:"media_type"` + MediaCategory string `json:"media_category"` } // NewMediaUploader creates a new MediaUploader -func NewMediaUploader(client Client, filePath string, verbose bool, authType string, username string, headers []string) (*MediaUploader, error) { +func NewMediaUploader(client Client, filePath string, verbose, trace bool, authType string, username string, headers []string) (*MediaUploader, error) { fileInfo, err := os.Stat(filePath) if err != nil { return nil, fmt.Errorf("error accessing file: %v", err) @@ -49,16 +56,18 @@ func NewMediaUploader(client Client, filePath string, verbose bool, authType str authType: authType, username: username, headers: headers, + trace: trace, }, nil } -func NewMediaUploaderWithoutFile(client Client, verbose bool, authType string, username string, headers []string) *MediaUploader { +func NewMediaUploaderWithoutFile(client Client, verbose, trace bool, authType string, username string, headers []string) *MediaUploader { return &MediaUploader{ client: client, verbose: verbose, authType: authType, username: username, headers: headers, + trace: trace, } } @@ -69,19 +78,27 @@ func (m *MediaUploader) Init(mediaType string, mediaCategory string) error { } finalUrl := MediaEndpoint + - "?command=INIT" + - "&total_bytes=" + strconv.FormatInt(m.fileSize, 10) + - "&media_type=" + mediaType + - "&media_category=" + mediaCategory + "/initialize" + + body := InitRequest{ + TotalBytes: m.fileSize, + MediaType: mediaType, + MediaCategory: mediaCategory, + } + jsonData, err := json.Marshal(body) + if err != nil { + return fmt.Errorf("error marshalling body: %v", err) + } requestOptions := RequestOptions{ Method: "POST", Endpoint: finalUrl, Headers: m.headers, - Data: "", + Data: string(jsonData), AuthType: m.authType, Username: m.username, Verbose: m.verbose, + Trace: m.trace, } response, clientErr := m.client.SendRequest(requestOptions) @@ -142,27 +159,27 @@ func (m *MediaUploader) Append() error { return fmt.Errorf("error reading file: %v", err) } + finalUrl := MediaEndpoint + fmt.Sprintf("/%s/append", m.mediaID) + // Prepare form fields formFields := map[string]string{ - "command": "APPEND", - "media_id": m.mediaID, "segment_index": strconv.Itoa(segmentIndex), } requestOptions := RequestOptions{ Method: "POST", - Endpoint: MediaEndpoint, + Endpoint: finalUrl, Headers: m.headers, Data: "", AuthType: m.authType, Username: m.username, Verbose: m.verbose, + Trace: m.trace, } multipartOptions := MultipartOptions{ RequestOptions: requestOptions, FormFields: formFields, FileField: "media", - FilePath: m.filePath, FileName: filepath.Base(m.filePath), FileData: buffer[:bytesRead], } @@ -199,7 +216,7 @@ func (m *MediaUploader) Finalize() (json.RawMessage, error) { fmt.Printf("\033[32mFinalizing media upload...\033[0m\n") } - finalUrl := MediaEndpoint + "?command=FINALIZE&media_id=" + m.mediaID + finalUrl := MediaEndpoint + fmt.Sprintf("/%s/finalize", m.mediaID) requestOptions := RequestOptions{ Method: "POST", Endpoint: finalUrl, @@ -208,6 +225,7 @@ func (m *MediaUploader) Finalize() (json.RawMessage, error) { AuthType: m.authType, Username: m.username, Verbose: m.verbose, + Trace: m.trace, } response, clientErr := m.client.SendRequest(requestOptions) if clientErr != nil { @@ -237,6 +255,7 @@ func (m *MediaUploader) CheckStatus() (json.RawMessage, error) { AuthType: m.authType, Username: m.username, Verbose: m.verbose, + Trace: m.trace, } response, clientErr := m.client.SendRequest(requestOptions) if clientErr != nil { @@ -316,8 +335,8 @@ func (m *MediaUploader) SetMediaID(mediaID string) { } // ExecuteMediaUpload handles the media upload command execution -func ExecuteMediaUpload(filePath, mediaType, mediaCategory, authType, username string, verbose, waitForProcessing bool, headers []string, client Client) error { - uploader, err := NewMediaUploader(client, filePath, verbose, authType, username, headers) +func ExecuteMediaUpload(filePath, mediaType, mediaCategory, authType, username string, verbose, waitForProcessing, trace bool, headers []string, client Client) error { + uploader, err := NewMediaUploader(client, filePath, verbose, trace, authType, username, headers) if err != nil { return fmt.Errorf("error: %v", err) } @@ -352,8 +371,8 @@ func ExecuteMediaUpload(filePath, mediaType, mediaCategory, authType, username s } // ExecuteMediaStatus handles the media status command execution -func ExecuteMediaStatus(mediaID, authType, username string, verbose, wait bool, headers []string, client Client) error { - uploader := NewMediaUploaderWithoutFile(client, verbose, authType, username, headers) +func ExecuteMediaStatus(mediaID, authType, username string, verbose, wait, trace bool, headers []string, client Client) error { + uploader := NewMediaUploaderWithoutFile(client, verbose, trace, authType, username, headers) uploader.SetMediaID(mediaID) @@ -386,19 +405,25 @@ func ExecuteMediaStatus(mediaID, authType, username string, verbose, wait bool, // HandleMediaAppendRequest handles a media append request with a file func HandleMediaAppendRequest(options RequestOptions, mediaFile string, client Client) (json.RawMessage, error) { - mediaID := ExtractMediaID(options.Endpoint, options.Data) + // TODO: This function is in a weird state since append accepts either a multipart request or a json request + // Right now, this function takes in segment_index from the json request and sends a multipart request + // We should refactor this to handle both cases (by adding curl-like multipart request support) + // example usage: + // xurl -X POST "/2/media/upload/{id}/append" \ + // -H "Content-Type: multipart/form-data" \ + // -F "media=@/path/to/your/file.mp4" \ + // -F "segment_index=0" + mediaID := ExtractMediaID(options.Endpoint) if mediaID == "" { - return nil, fmt.Errorf("media_id is required for APPEND command") + return nil, fmt.Errorf("media_id is required for append endpoint") } - segmentIndex := ExtractSegmentIndex(options.Endpoint, options.Data) + segmentIndex := ExtractSegmentIndex(options.Data) if segmentIndex == "" { segmentIndex = "0" } formFields := map[string]string{ - "command": "APPEND", - "media_id": mediaID, "segment_index": segmentIndex, } @@ -421,62 +446,83 @@ func HandleMediaAppendRequest(options RequestOptions, mediaFile string, client C } // ExtractMediaID extracts media_id from URL or data -func ExtractMediaID(url string, data string) string { - if strings.Contains(url, "media_id=") { - parts := strings.Split(url, "media_id=") +func ExtractMediaID(url string) string { + if url == "" { + return "" + } + + if !strings.Contains(url, "/2/media/upload") { + return "" + } + + if strings.HasSuffix(url, "/2/media/upload/initialize") { + return "" + } + + // Extract media ID from path for append/finalize endpoints + if strings.Contains(url, "/2/media/upload/") { + parts := strings.Split(url, "/2/media/upload/") if len(parts) > 1 { - mediaID := parts[1] - if idx := strings.Index(mediaID, "&"); idx != -1 { - mediaID = mediaID[:idx] + path := parts[1] + for _, suffix := range []string{"/append", "/finalize"} { + if idx := strings.Index(path, suffix); idx != -1 { + return path[:idx] + } } - return mediaID } } - if strings.Contains(data, "media_id=") { - parts := strings.Split(data, "media_id=") - if len(parts) > 1 { - mediaID := parts[1] - if idx := strings.Index(mediaID, "&"); idx != -1 { - mediaID = mediaID[:idx] + if strings.Contains(url, "?") { + queryParams := strings.Split(url, "?") + if len(queryParams) > 1 { + params := strings.Split(queryParams[1], "&") + for _, param := range params { + if strings.HasPrefix(param, "media_id=") { + return strings.Split(param, "=")[1] + } } - return mediaID } } return "" } -// ExtractSegmentIndex extracts segment_index from URL or data -func ExtractSegmentIndex(url string, data string) string { - if strings.Contains(url, "segment_index=") { - parts := strings.Split(url, "segment_index=") +// extracts command from URL +func ExtractCommand(url string) string { + if strings.Contains(url, "/2/media/upload/") { + parts := strings.Split(url, "/2/media/upload/") if len(parts) > 1 { - segmentIndex := parts[1] - if idx := strings.Index(segmentIndex, "&"); idx != -1 { - segmentIndex = segmentIndex[:idx] + path := parts[1] + if strings.Contains(path, "/append") { + return "append" + } + if strings.Contains(path, "/finalize") { + return "finalize" + } + if path == "initialize" { + return "initialize" } - return segmentIndex } + return "status" } - if strings.Contains(data, "segment_index=") { - parts := strings.Split(data, "segment_index=") - if len(parts) > 1 { - segmentIndex := parts[1] - if idx := strings.Index(segmentIndex, "&"); idx != -1 { - segmentIndex = segmentIndex[:idx] - } + return "" +} + +// ExtractSegmentIndex extracts segment_index from URL or data +func ExtractSegmentIndex(data string) string { + var jsonData map[string]string + if err := json.Unmarshal([]byte(data), &jsonData); err == nil { + if segmentIndex, ok := jsonData["segment_index"]; ok { return segmentIndex } } - return "" } // IsMediaAppendRequest checks if the request is a media append request func IsMediaAppendRequest(url string, mediaFile string) bool { return strings.Contains(url, "/2/media/upload") && - strings.Contains(url, "command=APPEND") && + strings.Contains(url, "append") && mediaFile != "" } diff --git a/api/media_test.go b/api/media_test.go index 23edf04..308145b 100644 --- a/api/media_test.go +++ b/api/media_test.go @@ -2,10 +2,12 @@ package api import ( "encoding/json" + "fmt" "net/http" "net/http/httptest" "os" "path/filepath" + "strings" "testing" "time" @@ -72,7 +74,7 @@ func TestNewMediaUploader(t *testing.T) { tempFile, _ := createTempTestFile(t, 1024) defer os.Remove(tempFile) - uploader, err := NewMediaUploader(mockClient, tempFile, true, "oauth2", "testuser", []string{}) + uploader, err := NewMediaUploader(mockClient, tempFile, true, false, "oauth2", "testuser", []string{}) assert.NoError(t, err) assert.NotNil(t, uploader) assert.Equal(t, tempFile, uploader.filePath) @@ -81,7 +83,7 @@ func TestNewMediaUploader(t *testing.T) { assert.Equal(t, "oauth2", uploader.authType) assert.Equal(t, "testuser", uploader.username) - uploader, err = NewMediaUploader(mockClient, "nonexistent.txt", false, "oauth2", "testuser", []string{}) + uploader, err = NewMediaUploader(mockClient, "nonexistent.txt", false, false, "oauth2", "testuser", []string{}) assert.Error(t, err) assert.Nil(t, uploader) @@ -91,7 +93,7 @@ func TestNewMediaUploader(t *testing.T) { } defer os.RemoveAll(tempDir) - uploader, err = NewMediaUploader(mockClient, tempDir, false, "oauth2", "testuser", []string{}) + uploader, err = NewMediaUploader(mockClient, tempDir, false, false, "oauth2", "testuser", []string{}) assert.Error(t, err) assert.Nil(t, uploader) } @@ -102,7 +104,7 @@ func TestMediaUploader_Init(t *testing.T) { tempFile, _ := createTempTestFile(t, 1024) defer os.Remove(tempFile) - uploader, err := NewMediaUploader(mockClient, tempFile, false, "oauth2", "testuser", []string{}) + uploader, err := NewMediaUploader(mockClient, tempFile, false, false, "oauth2", "testuser", []string{}) assert.NoError(t, err) initResponse := json.RawMessage(`{ @@ -113,12 +115,21 @@ func TestMediaUploader_Init(t *testing.T) { } }`) - expectedUrl := MediaEndpoint + "?command=INIT&total_bytes=1024&media_type=image/jpeg&media_category=tweet_image" + expectedUrl := MediaEndpoint + "/initialize" + data := InitRequest{ + TotalBytes: 1024, + MediaType: "image/jpeg", + MediaCategory: "tweet_image", + } + jsonData, err := json.Marshal(data) + if err != nil { + t.Fatalf("Failed to marshal jsonData: %v", err) + } requestOptions := RequestOptions{ Method: "POST", Endpoint: expectedUrl, Headers: []string{}, - Data: "", + Data: string(jsonData), AuthType: "oauth2", Username: "testuser", Verbose: false, @@ -133,7 +144,7 @@ func TestMediaUploader_Init(t *testing.T) { mockClient.AssertExpectations(t) mockClient = new(MockApiClient) - uploader, err = NewMediaUploader(mockClient, tempFile, false, "oauth2", "testuser", []string{}) + uploader, err = NewMediaUploader(mockClient, tempFile, false, false, "oauth2", "testuser", []string{}) assert.NoError(t, err) mockClient.On("SendRequest", requestOptions).Return(json.RawMessage("{}"), assert.AnError) @@ -151,14 +162,16 @@ func TestMediaUploader_Append(t *testing.T) { tempFile, data := createTempTestFile(t, fileSize) defer os.Remove(tempFile) - uploader, err := NewMediaUploader(mockClient, tempFile, false, "oauth2", "testuser", []string{}) + uploader, err := NewMediaUploader(mockClient, tempFile, false, false, "oauth2", "testuser", []string{}) assert.NoError(t, err) - uploader.SetMediaID("test_media_id") + mediaID := "test_media_id" + + uploader.SetMediaID(mediaID) requestOptions := RequestOptions{ Method: "POST", - Endpoint: MediaEndpoint, + Endpoint: MediaEndpoint + "/" + mediaID + "/append", Headers: []string{}, Data: "", AuthType: "oauth2", @@ -168,18 +181,16 @@ func TestMediaUploader_Append(t *testing.T) { multipartOptions := MultipartOptions{ RequestOptions: requestOptions, - FormFields: map[string]string{"command": "APPEND", "media_id": "test_media_id", "segment_index": "0"}, + FormFields: map[string]string{"segment_index": "0"}, FileField: "media", - FilePath: tempFile, FileName: filepath.Base(tempFile), FileData: data[:4*1024*1024], } multipartOptions1 := MultipartOptions{ RequestOptions: requestOptions, - FormFields: map[string]string{"command": "APPEND", "media_id": "test_media_id", "segment_index": "1"}, + FormFields: map[string]string{"segment_index": "1"}, FileField: "media", - FilePath: tempFile, FileName: filepath.Base(tempFile), FileData: data[4*1024*1024:], } @@ -204,7 +215,7 @@ func TestMediaUploader_Finalize(t *testing.T) { tempFile, _ := createTempTestFile(t, 1024) defer os.Remove(tempFile) - uploader, err := NewMediaUploader(mockClient, tempFile, false, "oauth2", "testuser", []string{}) + uploader, err := NewMediaUploader(mockClient, tempFile, false, false, "oauth2", "testuser", []string{}) assert.NoError(t, err) uploader.SetMediaID("test_media_id") @@ -216,7 +227,7 @@ func TestMediaUploader_Finalize(t *testing.T) { } }`) - expectedUrl := MediaEndpoint + "?command=FINALIZE&media_id=test_media_id" + expectedUrl := MediaEndpoint + fmt.Sprintf("/%s/finalize", uploader.GetMediaID()) requestOptions := RequestOptions{ Method: "POST", Endpoint: expectedUrl, @@ -248,7 +259,7 @@ func TestMediaUploader_CheckStatus(t *testing.T) { tempFile, _ := createTempTestFile(t, 1024) defer os.Remove(tempFile) - uploader, err := NewMediaUploader(mockClient, tempFile, false, "oauth2", "testuser", []string{}) + uploader, err := NewMediaUploader(mockClient, tempFile, false, false, "oauth2", "testuser", []string{}) assert.NoError(t, err) uploader.SetMediaID("test_media_id") @@ -296,7 +307,7 @@ func TestMediaUploader_WaitForProcessing(t *testing.T) { tempFile, _ := createTempTestFile(t, 1024) defer os.Remove(tempFile) - uploader, err := NewMediaUploader(mockClient, tempFile, false, "oauth2", "testuser", []string{}) + uploader, err := NewMediaUploader(mockClient, tempFile, false, false, "oauth2", "testuser", []string{}) assert.NoError(t, err) uploader.SetMediaID("test_media_id") @@ -382,13 +393,13 @@ func TestMediaUploader_WaitForProcessing(t *testing.T) { func TestExecuteMediaUpload(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == MediaEndpoint { - command := r.FormValue("command") + if strings.Contains(r.URL.Path, MediaEndpoint) { + command := ExtractCommand(r.URL.Path) switch command { - case "INIT": + case "initialize": w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusAccepted) + w.WriteHeader(http.StatusOK) w.Write([]byte(`{ "data": { "id": "test_media_id", @@ -396,10 +407,10 @@ func TestExecuteMediaUpload(t *testing.T) { "media_key": "test_media_key" } }`)) - case "APPEND": - w.WriteHeader(http.StatusNoContent) + case "append": + w.WriteHeader(http.StatusOK) w.Write([]byte(`{}`)) - case "FINALIZE": + case "finalize": w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{ @@ -408,7 +419,7 @@ func TestExecuteMediaUpload(t *testing.T) { "media_key": "test_media_key" } }`)) - case "STATUS": + case "status": w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{ @@ -438,10 +449,10 @@ func TestExecuteMediaUpload(t *testing.T) { tempFile, _ := createTempTestFile(t, 1024) defer os.Remove(tempFile) - err := ExecuteMediaUpload(tempFile, "image/jpeg", "tweet_image", "oauth2", "testuser", false, false, []string{}, client) + err := ExecuteMediaUpload(tempFile, "image/jpeg", "tweet_image", "oauth2", "testuser", false, false, false, []string{}, client) assert.NoError(t, err) - err = ExecuteMediaUpload("nonexistent.txt", "image/jpeg", "tweet_image", "oauth2", "testuser", false, false, []string{}, client) + err = ExecuteMediaUpload("nonexistent.txt", "image/jpeg", "tweet_image", "oauth2", "testuser", false, false, false, []string{}, client) assert.Error(t, err) } @@ -471,26 +482,28 @@ func TestExecuteMediaStatus(t *testing.T) { client: &http.Client{Timeout: 30 * time.Second}, } - err := ExecuteMediaStatus("test_media_id", "oauth2", "testuser", false, false, []string{}, client) + err := ExecuteMediaStatus("test_media_id", "oauth2", "testuser", false, false, false, []string{}, client) assert.NoError(t, err) } func TestExtractMediaID(t *testing.T) { testCases := []struct { url string - data string expected string }{ - {"/2/media/upload?command=APPEND&media_id=123456", "", "123456"}, - {"/2/media/upload?media_id=123456&command=APPEND", "", "123456"}, - {"", "media_id=123456&segment_index=0", "123456"}, - {"/2/media/upload", "command=APPEND&media_id=123456", "123456"}, - {"/2/media/upload", "", ""}, - {"", "", ""}, + {"/2/media/upload/123456/append", "123456"}, + {"/2/media/upload/123456/finalize", "123456"}, + {"/2/media/upload?command=STATUS&media_id=123456", "123456"}, + {"/2/media/upload/initialize", ""}, + {"/2/media/upload", ""}, + {"api.x.com/2/media/upload/123456/append", "123456"}, + {"api.x.com/2/media/upload/123456/finalize", "123456"}, + {"api.x.com/2/media/upload?command=STATUS&media_id=123456", "123456"}, + {"", ""}, } for _, tc := range testCases { - result := ExtractMediaID(tc.url, tc.data) + result := ExtractMediaID(tc.url) assert.Equal(t, tc.expected, result) } } @@ -501,16 +514,12 @@ func TestExtractSegmentIndex(t *testing.T) { data string expected string }{ - {"/2/media/upload?command=APPEND&segment_index=1", "", "1"}, - {"/2/media/upload?segment_index=1&command=APPEND", "", "1"}, - {"", "segment_index=1&media_id=123456", "1"}, - {"/2/media/upload", "command=APPEND&segment_index=1", "1"}, - {"/2/media/upload", "", ""}, - {"", "", ""}, + {"/2/media/upload/123/append", "", ""}, + {"/2/media/upload/123/append", "{\"segment_index\": \"1\"}", "1"}, } for _, tc := range testCases { - result := ExtractSegmentIndex(tc.url, tc.data) + result := ExtractSegmentIndex(tc.data) assert.Equal(t, tc.expected, result) } } @@ -521,9 +530,9 @@ func TestIsMediaAppendRequest(t *testing.T) { mediaFile string expected bool }{ - {"/2/media/upload?command=APPEND", "file.jpg", true}, - {"/2/media/upload?command=INIT", "file.jpg", false}, - {"/2/media/upload?command=APPEND", "", false}, + {"/2/media/upload/123/append", "file.jpg", true}, + {"/2/media/upload/initialize", "file.jpg", false}, + {"/2/media/upload/123/append", "", false}, {"/2/users/me", "file.jpg", false}, {"", "", false}, } @@ -542,7 +551,7 @@ func TestHandleMediaAppendRequest(t *testing.T) { mockResponse := json.RawMessage(`{}`) - url := "/2/media/upload?command=APPEND&media_id=123456" + url := "/2/media/upload/123456/append" requestOptions := RequestOptions{ Method: "POST", Endpoint: url, @@ -554,7 +563,7 @@ func TestHandleMediaAppendRequest(t *testing.T) { } multipartOptions := MultipartOptions{ RequestOptions: requestOptions, - FormFields: map[string]string{"command": "APPEND", "media_id": "123456", "segment_index": "0"}, + FormFields: map[string]string{"segment_index": "0"}, FileField: "media", FilePath: tempFile, FileName: filepath.Base(tempFile), diff --git a/cli/media.go b/cli/media.go index bffb080..10d1aac 100644 --- a/cli/media.go +++ b/cli/media.go @@ -41,10 +41,11 @@ func createMediaUploadCmd(auth *auth.Auth) *cobra.Command { username, _ := cmd.Flags().GetString("username") verbose, _ := cmd.Flags().GetBool("verbose") headers, _ := cmd.Flags().GetStringArray("header") + trace, _ := cmd.Flags().GetBool("trace") config := config.NewConfig() client := api.NewApiClient(config, auth) - err := api.ExecuteMediaUpload(filePath, mediaType, mediaCategory, authType, username, verbose, waitForProcessing, headers, client) + err := api.ExecuteMediaUpload(filePath, mediaType, mediaCategory, authType, username, verbose, trace, waitForProcessing, headers, client) if err != nil { fmt.Printf("\033[31m%v\033[0m\n", err) os.Exit(1) @@ -58,6 +59,7 @@ func createMediaUploadCmd(auth *auth.Auth) *cobra.Command { cmd.Flags().String("auth", "", "Authentication type (oauth1 or oauth2)") cmd.Flags().StringP("username", "u", "", "Username for OAuth2 authentication") cmd.Flags().BoolP("verbose", "v", false, "Print verbose information") + cmd.Flags().BoolP("trace", "t", false, "Add trace header to request") cmd.Flags().StringArrayP("header", "H", []string{}, "Request headers") return cmd @@ -76,11 +78,12 @@ func createMediaStatusCmd(auth *auth.Auth) *cobra.Command { username, _ := cmd.Flags().GetString("username") verbose, _ := cmd.Flags().GetBool("verbose") wait, _ := cmd.Flags().GetBool("wait") + trace, _ := cmd.Flags().GetBool("trace") headers, _ := cmd.Flags().GetStringArray("header") config := config.NewConfig() client := api.NewApiClient(config, auth) - err := api.ExecuteMediaStatus(mediaID, authType, username, verbose, wait, headers, client) + err := api.ExecuteMediaStatus(mediaID, authType, username, verbose, wait, trace, headers, client) if err != nil { fmt.Printf("\033[31m%v\033[0m\n", err) os.Exit(1) @@ -90,8 +93,9 @@ func createMediaStatusCmd(auth *auth.Auth) *cobra.Command { cmd.Flags().String("auth", "", "Authentication type (oauth1 or oauth2)") cmd.Flags().StringP("username", "u", "", "Username for OAuth2 authentication") - cmd.Flags().Bool("verbose", false, "Print verbose information") + cmd.Flags().BoolP("verbose", "v", false, "Print verbose information") cmd.Flags().BoolP("wait", "w", false, "Wait for media processing to complete") + cmd.Flags().BoolP("trace", "t", false, "Add trace header to request") cmd.Flags().StringArrayP("header", "H", []string{}, "Request headers") return cmd } diff --git a/cli/webhook.go b/cli/webhook.go index 44b675a..bce868b 100644 --- a/cli/webhook.go +++ b/cli/webhook.go @@ -14,18 +14,19 @@ import ( "os" "strings" + "xurl/auth" + "github.com/fatih/color" "github.com/spf13/cobra" "github.com/tidwall/pretty" "golang.ngrok.com/ngrok" "golang.ngrok.com/ngrok/config" - "xurl/auth" ) var webhookPort int var outputFileName string // To store the output file name from the flag -var quietMode bool // To store the quiet flag state -var prettyMode bool // To store the pretty-print flag state +var quietMode bool // To store the quiet flag state +var prettyMode bool // To store the pretty-print flag state // CreateWebhookCommand creates the webhook command and its subcommands. func CreateWebhookCommand(authInstance *auth.Auth) *cobra.Command { @@ -96,7 +97,7 @@ func CreateWebhookCommand(authInstance *auth.Auth) *cobra.Command { os.Exit(1) } defer ngrokListener.Close() - + color.Green("Ngrok tunnel established!") fmt.Printf(" Forwarding URL: %s -> %s\n", color.HiGreenString(ngrokListener.URL()), color.MagentaString(forwardToAddr)) color.Yellow("Use this URL for your X API webhook registration: %s/webhook", color.HiGreenString(ngrokListener.URL())) @@ -190,4 +191,4 @@ func CreateWebhookCommand(authInstance *auth.Auth) *cobra.Command { webhookCmd.AddCommand(webhookStartCmd) return webhookCmd -} \ No newline at end of file +}