From 6cd3d2941cf91fab7c6739d6ceed1c35328deb71 Mon Sep 17 00:00:00 2001 From: devhindo Date: Sun, 7 Apr 2024 00:39:50 +0200 Subject: [PATCH 1/9] init cobra --- src/cli/cmd/future.go | 1 + src/cli/cmd/root.go | 26 ++++++++++++++++++++++++++ src/cli/go.mod | 6 ++++++ src/cli/go.sum | 10 ++++++++++ src/cli/x/run.go | 7 ++++++- 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/cli/cmd/future.go create mode 100644 src/cli/cmd/root.go diff --git a/src/cli/cmd/future.go b/src/cli/cmd/future.go new file mode 100644 index 0000000..1d619dd --- /dev/null +++ b/src/cli/cmd/future.go @@ -0,0 +1 @@ +package cmd diff --git a/src/cli/cmd/root.go b/src/cli/cmd/root.go new file mode 100644 index 0000000..719bd42 --- /dev/null +++ b/src/cli/cmd/root.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "x", + Short: "x is a CLI tool for posting tweets", + Long: `x is a CLI tool for posting tweets. + You can post tweets now or in the future. + You can also clear your credentials and start over.`, + Run: func(cmd *cobra.Command, args []string) { + // Do Stuff Here + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} \ No newline at end of file diff --git a/src/cli/go.mod b/src/cli/go.mod index a9028b8..7999d7f 100644 --- a/src/cli/go.mod +++ b/src/cli/go.mod @@ -3,3 +3,9 @@ module github.com/devhindo/x/src/cli go 1.22.2 require github.com/google/uuid v1.3.1 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect +) diff --git a/src/cli/go.sum b/src/cli/go.sum index a43f94d..779870e 100644 --- a/src/cli/go.sum +++ b/src/cli/go.sum @@ -1,2 +1,12 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/cli/x/run.go b/src/cli/x/run.go index a7f707f..6ee0b4e 100644 --- a/src/cli/x/run.go +++ b/src/cli/x/run.go @@ -1,5 +1,10 @@ package x +import ( + "github.com/devhindo/x/src/cli/cmd" +) + func Run() { - HandleArgs() + // HandleArgs() + cmd.Execute() } \ No newline at end of file From 2fc3cd7376cc0111551f97caf162ab7fbe10ed74 Mon Sep 17 00:00:00 2001 From: devhindo Date: Sun, 7 Apr 2024 00:53:28 +0200 Subject: [PATCH 2/9] added future flags --- src/cli/cmd/future.go | 28 ++++++++++++++++++++++++++++ src/cli/cmd/root.go | 4 ++++ 2 files changed, 32 insertions(+) diff --git a/src/cli/cmd/future.go b/src/cli/cmd/future.go index 1d619dd..3e86c58 100644 --- a/src/cli/cmd/future.go +++ b/src/cli/cmd/future.go @@ -1 +1,29 @@ package cmd + +import ( + "github.com/devhindo/x/src/cli/tweet" + + "github.com/spf13/cobra" +) + +var ( + wait string + date string + time string + content string +) + +func init() { + rootCmd.AddCommand(futureCmd) +} + +var futureCmd = &cobra.Command{ + Use: "-f", + Short: "Post future tweets", + Long: `Post future tweets.`, + Run: futureCmdRun, +} + +func futureCmdRun(cmd *cobra.Command, args []string) { + tweet.PostFutureTweet(args) +} diff --git a/src/cli/cmd/root.go b/src/cli/cmd/root.go index 719bd42..003a33e 100644 --- a/src/cli/cmd/root.go +++ b/src/cli/cmd/root.go @@ -23,4 +23,8 @@ func Execute() { fmt.Fprintln(os.Stderr, err) os.Exit(1) } +} + +func init() { + } \ No newline at end of file From af8a6f2a5df023d78a947e406a7fa9c733c98ae7 Mon Sep 17 00:00:00 2001 From: devhindo Date: Mon, 15 Apr 2024 05:02:14 +0200 Subject: [PATCH 3/9] init version cmd --- src/cli/cmd/update/update.go | 63 ++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/cli/cmd/update/update.go diff --git a/src/cli/cmd/update/update.go b/src/cli/cmd/update/update.go new file mode 100644 index 0000000..8c4488a --- /dev/null +++ b/src/cli/cmd/update/update.go @@ -0,0 +1,63 @@ +package cmd + +import ( + "fmt" + "os" + "os/exec" + "strings" + + "github.com/spf13/cobra" +) + +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Update the CLI to the latest version", + Run: func(cmd *cobra.Command, args []string) { + cmdUpdate() + }, +} + +func cmdUpdate() { + fmt.Println("Updating CLI...") + + if !isGoInstalled() { + fmt.Println("") + } + + cmd := exec.Command("go", "get", "-u", "github.com/bradford-hamilton/monkey") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println("CLI updated successfully!") +} + +func isGoInstalled() bool { + _, err := exec.LookPath("go") + if err != nil { + return false + } + return true +} + +func updateUsingGo() error { + cmd := exec.Command("go", "get", "-u", "github.com/bradford-hamilton/monkey") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + fmt.Println(err) + return err + } + fmt.Println("CLI updated successfully!") + return nil +} + +func init() { + rootCmd.AddCommand(updateCmd) +} + +//TODO: x update -v 1.1.1 (default for v:latest) \ No newline at end of file From 9dd73bf3dce0e5b42e2f098569851b3cc3492ee0 Mon Sep 17 00:00:00 2001 From: devhindo Date: Mon, 15 Apr 2024 05:22:11 +0200 Subject: [PATCH 4/9] added the base No command given | try 'x help' command --- src/cli/cmd/root.go | 29 ++++++++++++++++++++++------- src/cli/main.go | 5 +++-- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/cli/cmd/root.go b/src/cli/cmd/root.go index 003a33e..8e3c58d 100644 --- a/src/cli/cmd/root.go +++ b/src/cli/cmd/root.go @@ -7,14 +7,29 @@ import ( "github.com/spf13/cobra" ) +var ( + version = "1.1.3" + man = fmt.Sprintf(`interact with x (twitter) from terminal. version: %s. + + USAGE + x + + Commands + -h show this help + auth start authorizing your X account + auth --url get auth url if it didn't open browser after running 'x auth' + auth -v verify authorization after running 'x auth' + -t "text" post a tweet + -v show version (%s) + -c clear authorized account`, version, version) +) + var rootCmd = &cobra.Command{ Use: "x", Short: "x is a CLI tool for posting tweets", - Long: `x is a CLI tool for posting tweets. - You can post tweets now or in the future. - You can also clear your credentials and start over.`, + Long: man, Run: func(cmd *cobra.Command, args []string) { - // Do Stuff Here + fmt.Println(man) }, } @@ -22,9 +37,9 @@ func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) - } + } } func init() { - -} \ No newline at end of file + +} diff --git a/src/cli/main.go b/src/cli/main.go index c70a161..9abc3fd 100644 --- a/src/cli/main.go +++ b/src/cli/main.go @@ -1,8 +1,9 @@ package main -import "github.com/devhindo/x/src/cli/x" +import "github.com/devhindo/x/src/cli/cmd" func main() { - x.Run() + //x.Run() + cmd.Execute() } From 898f3fdd3fd004d2380e72dc4191311c0f6a9699 Mon Sep 17 00:00:00 2001 From: devhindo Date: Mon, 15 Apr 2024 07:59:39 +0200 Subject: [PATCH 5/9] added some update functionality --- src/x/.goreleaser.yaml | 39 +++++++++ src/x/auth/auth.go | 29 +++++++ src/x/auth/crud.go | 59 ++++++++++++++ src/x/auth/geturl.go | 21 +++++ src/x/auth/license.go | 30 +++++++ src/x/auth/lock.go | 18 +++++ src/x/auth/oauth.go | 62 ++++++++++++++ src/x/auth/server.go | 11 +++ src/x/auth/url.go | 13 +++ src/x/auth/user.go | 46 +++++++++++ src/x/auth/validate.go | 7 ++ src/x/auth/verify.go | 67 +++++++++++++++ src/x/clear/startover.go | 67 +++++++++++++++ src/x/cmd/future.go | 29 +++++++ src/x/cmd/root.go | 45 +++++++++++ src/x/cmd/update.go | 118 +++++++++++++++++++++++++++ src/x/cmd/version.go | 22 +++++ src/x/env/load.go | 19 +++++ src/x/go.mod | 11 +++ src/x/go.sum | 12 +++ src/x/gotwi/post.go | 37 +++++++++ src/x/help/help.go | 25 ++++++ src/x/lock/key.go | 77 ++++++++++++++++++ src/x/main.go | 9 +++ src/x/release.md | 4 + src/x/tweet/future.go | 170 +++++++++++++++++++++++++++++++++++++++ src/x/tweet/post.go | 74 +++++++++++++++++ src/x/utils/crud.go | 83 +++++++++++++++++++ src/x/utils/open.go | 33 ++++++++ src/x/x/args.go | 100 +++++++++++++++++++++++ src/x/x/run.go | 10 +++ src/x/x/version.go | 7 ++ 32 files changed, 1354 insertions(+) create mode 100644 src/x/.goreleaser.yaml create mode 100644 src/x/auth/auth.go create mode 100644 src/x/auth/crud.go create mode 100644 src/x/auth/geturl.go create mode 100644 src/x/auth/license.go create mode 100644 src/x/auth/lock.go create mode 100644 src/x/auth/oauth.go create mode 100644 src/x/auth/server.go create mode 100644 src/x/auth/url.go create mode 100644 src/x/auth/user.go create mode 100644 src/x/auth/validate.go create mode 100644 src/x/auth/verify.go create mode 100644 src/x/clear/startover.go create mode 100644 src/x/cmd/future.go create mode 100644 src/x/cmd/root.go create mode 100644 src/x/cmd/update.go create mode 100644 src/x/cmd/version.go create mode 100644 src/x/env/load.go create mode 100644 src/x/go.mod create mode 100644 src/x/go.sum create mode 100644 src/x/gotwi/post.go create mode 100644 src/x/help/help.go create mode 100644 src/x/lock/key.go create mode 100644 src/x/main.go create mode 100644 src/x/release.md create mode 100644 src/x/tweet/future.go create mode 100644 src/x/tweet/post.go create mode 100644 src/x/utils/crud.go create mode 100644 src/x/utils/open.go create mode 100644 src/x/x/args.go create mode 100644 src/x/x/run.go create mode 100644 src/x/x/version.go diff --git a/src/x/.goreleaser.yaml b/src/x/.goreleaser.yaml new file mode 100644 index 0000000..c60e469 --- /dev/null +++ b/src/x/.goreleaser.yaml @@ -0,0 +1,39 @@ +project_name: x +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com + +# The lines bellow are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/need to use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + # you may remove this if you don't need go generate + - go generate ./... + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + format: zip + wrap_in_directory: x diff --git a/src/x/auth/auth.go b/src/x/auth/auth.go new file mode 100644 index 0000000..e8541de --- /dev/null +++ b/src/x/auth/auth.go @@ -0,0 +1,29 @@ +package auth + +import ( + "fmt" + "os" + + "github.com/devhindo/x/src/cli/lock" +) + +// func check_authentication() {} + +func Auth() { + + checkIfUserExists() + + u := newUser() + u.add_user_to_db() + u.open_browser_to_auth_url() + fmt.Println("please authorize X CLI in your browser then run 'x auth --verify'") + fmt.Println("if the browser does not open, run 'x auth --url` to get the authorization url") +} + +func checkIfUserExists() { + _, err := lock.ReadLicenseKeyFromFile() + if err == nil { + fmt.Println("a user is already logged in | try 'x -h'") + os.Exit(0) + } +} diff --git a/src/x/auth/crud.go b/src/x/auth/crud.go new file mode 100644 index 0000000..7bf1fef --- /dev/null +++ b/src/x/auth/crud.go @@ -0,0 +1,59 @@ +package auth + +import ( + "bytes" + "fmt" + "io" + "net/http" + "encoding/json" + "os" +) + +func Post(url string, u User) int { + + jsonBytes, err := json.Marshal(u) + if err != nil { + panic(err) + } + + resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBytes)) + + if err != nil { + fmt.Println("can't reach server to perform authentication") + os.Exit(0) + } + + defer resp.Body.Close() + + return resp.StatusCode +} + +func GET(url string) { + // Create a new HTTP request object. + req, err := http.NewRequest("GET", url, nil) + if err != nil { + fmt.Println(err) + return + } + + // Send the request and receive the response. + resp, err := http.DefaultClient.Do(req) + if err != nil { + fmt.Println(err) + return + } + + // Read the response body. + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println(err) + return + } + + // Close the response body. + defer resp.Body.Close() + + // Print the response body. + fmt.Println(string(body)) + +} diff --git a/src/x/auth/geturl.go b/src/x/auth/geturl.go new file mode 100644 index 0000000..70d94c7 --- /dev/null +++ b/src/x/auth/geturl.go @@ -0,0 +1,21 @@ +package auth + +import ( + "fmt" + "os" + + "github.com/devhindo/x/src/cli/lock" +) + +func Get_url_db() { + l, err := lock.ReadLicenseKeyFromFile() + if err != nil { + fmt.Println("you are not authenticated | try 'x auth'") + os.Exit(1) + } + + url := "https://x-blush.vercel.app/api/user/url" + k := License{License: l} + + postL(url, k) +} diff --git a/src/x/auth/license.go b/src/x/auth/license.go new file mode 100644 index 0000000..31640d8 --- /dev/null +++ b/src/x/auth/license.go @@ -0,0 +1,30 @@ +package auth + +import ( + "os" + "fmt" + "io" + "path/filepath" +) + +func (u *User) RetrieveLicenseKey() (string, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("Error getting user home directory: %v", err) + } + + licenseFilePath := filepath.Join(homeDir, ".tempxcli") + licenseFile, err := os.Open(licenseFilePath) + if err != nil { + return "", fmt.Errorf("Error opening license file: %v", err) + } + defer licenseFile.Close() + + licenseFileBytes, err := io.ReadAll(licenseFile) + if err != nil { + return "", fmt.Errorf("Error reading license file: %v", err) + } + + licenseKey := string(licenseFileBytes) + return licenseKey, nil +} \ No newline at end of file diff --git a/src/x/auth/lock.go b/src/x/auth/lock.go new file mode 100644 index 0000000..55e62ab --- /dev/null +++ b/src/x/auth/lock.go @@ -0,0 +1,18 @@ +package auth + +import ( + "github.com/devhindo/x/src/cli/lock" +) + +func (u *User) Lock() { + licenseKey, err := lock.GenerateLicenseKey() + if err != nil { + panic(err) + } + + //err = lock.WriteLicenseKeyToFile(licenseKey) + //if err != nil { + // panic(err) + //} + u.License = licenseKey +} \ No newline at end of file diff --git a/src/x/auth/oauth.go b/src/x/auth/oauth.go new file mode 100644 index 0000000..6826e88 --- /dev/null +++ b/src/x/auth/oauth.go @@ -0,0 +1,62 @@ +package auth + +import ( + "crypto/rand" + "crypto/sha256" + "math/big" + "encoding/base64" +) + +/* +* code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) +*/ + +func (u *User) generate_code_challenge() { + // Base64-URL-encoded string of the SHA256 hash of the code verifier + //u.Code_challenge = base64.RawURLEncoding.EncodeToString((hash_sha256(u.Code_verifier))) + u.Code_challenge = base64.RawURLEncoding.EncodeToString((hash_sha256(u.Code_verifier))) + +} + +func hash_sha256(s string) []byte { + h := sha256.New() + h.Write([]byte(s)) + return h.Sum(nil) +} + +func (u *User) generate_code_verifier() { + const ( + chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" + min = 43 + max = 128 + ) + + length, err := rand.Int(rand.Reader, big.NewInt(max-min+1)) + if err != nil { + panic(err) + } + length.Add(length, big.NewInt(min)) + + b := make([]byte, length.Int64()) + for i := range b { + n, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars)))) + if err != nil { + panic(err) + } + b[i] = chars[n.Int64()] + } + + u.Code_verifier = string(b) +} + +func (u *User) generate_state(stateLength int) { + b := make([]byte, stateLength) + _, err := rand.Read(b) + if err != nil { + panic(err) + } + + state := base64.URLEncoding.EncodeToString(b) + + u.State = state +} \ No newline at end of file diff --git a/src/x/auth/server.go b/src/x/auth/server.go new file mode 100644 index 0000000..f3b1e03 --- /dev/null +++ b/src/x/auth/server.go @@ -0,0 +1,11 @@ +package auth + +import ( + +) + +// todo: send state to the server +// todo: actually send a one request containing everything need for requesting the access token + + + diff --git a/src/x/auth/url.go b/src/x/auth/url.go new file mode 100644 index 0000000..0674567 --- /dev/null +++ b/src/x/auth/url.go @@ -0,0 +1,13 @@ +package auth + +func (u *User) generate_auth_url() { + auth_url := "" + auth_scopes := "tweet.read%20tweet.write%20users.read%20users.read%20follows.read%20follows.write%20offline.access" + auth_url += "https://twitter.com/i/oauth2/authorize?response_type=code&client_id=" + auth_url += "emJHZzZHMUdHMF9QRlRIdk45QjY6MTpjaQ" + redirect_url := "https://x-blush.vercel.app/api/auth" + auth_url += "&redirect_uri=" + redirect_url + auth_url += "&scope=" + auth_scopes + auth_url += "&state=" + u.State + "&code_challenge=" + u.Code_challenge + "&code_challenge_method=S256" + u.Auth_URL = auth_url +} diff --git a/src/x/auth/user.go b/src/x/auth/user.go new file mode 100644 index 0000000..820d14b --- /dev/null +++ b/src/x/auth/user.go @@ -0,0 +1,46 @@ +package auth + +import ( + "fmt" + + "github.com/devhindo/x/src/cli/utils" + "github.com/devhindo/x/src/cli/lock" +) + +type User struct { + State string `json:"state"` + Auth_URL string `json:"auth_url"` + Code_verifier string `json:"code_verifier"` + Code_challenge string `json:"code_challenge"` + License string `json:"license"` +} + +func newUser() *User { + u := new(User) + u.Lock() + u.generate_code_verifier() + u.generate_code_challenge() + u.generate_state(127) + u.generate_auth_url() + + return u +} + +func (u *User) add_user_to_db() { + status := Post("https://x-blush.vercel.app/api/auth/add", *u) + + if status != 200 { + fmt.Println("error adding user") + } else { + + err := lock.WriteLicenseKeyToFile(u.License) + if err != nil { + fmt.Println("coudln't write license key to file") + return + } + } +} + +func (u *User) open_browser_to_auth_url() { + utils.OpenBrowser(u.Auth_URL) +} diff --git a/src/x/auth/validate.go b/src/x/auth/validate.go new file mode 100644 index 0000000..7ccca22 --- /dev/null +++ b/src/x/auth/validate.go @@ -0,0 +1,7 @@ +package auth + +import () + +func IsAuthenticated() bool { + return false +} \ No newline at end of file diff --git a/src/x/auth/verify.go b/src/x/auth/verify.go new file mode 100644 index 0000000..e760b5a --- /dev/null +++ b/src/x/auth/verify.go @@ -0,0 +1,67 @@ +package auth + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "os" + "io" + + "github.com/devhindo/x/src/cli/lock" +) + +type License struct { + License string `json:"license"` +} + +func Verify() bool { + l, err := lock.ReadLicenseKeyFromFile() + if err != nil { + fmt.Println("you are not authenticated | try 'x auth'") + os.Exit(1) + } + + k := License{License: l} + + url := "https://x-blush.vercel.app/api/auth/verify" + + postL(url, k) + + + return true +} + +type response struct { + Message string `json:"message"` +} + +func postL(url string, l License) { + + jsonBytes, err := json.Marshal(l) + if err != nil { + panic(err) + } + + resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBytes)) + + if err != nil { + fmt.Println("can't reach server to verify user") + os.Exit(0) + } + + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + //Failed to read response. + panic(err) + } + + var r response + + err = json.Unmarshal(body, &r) + + //Convert bytes to String and print + fmt.Println(r.Message) +} \ No newline at end of file diff --git a/src/x/clear/startover.go b/src/x/clear/startover.go new file mode 100644 index 0000000..a0522a7 --- /dev/null +++ b/src/x/clear/startover.go @@ -0,0 +1,67 @@ +package clear + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "os" + + "github.com/devhindo/x/src/cli/lock" +) + +func StartOver() { + + license, err := lock.ReadLicenseKeyFromFile() + + if err != nil { + fmt.Println("no user logged in") + return + } + + delete_user_from_db(license) + + lock.ClearLicenseFile() + + fmt.Println("user deleted successfully") + +} + +// is there anyway better to pass license? +type License struct { + License string `json:"license"` +} + +func delete_user_from_db(license string) { + + l := License{ + License: license, + } + + url := "https://x-blush.vercel.app/api/user/delete" + + status := post(url, l) + + if status != 200 { + fmt.Println("error deleting user from db") + } +} + +func post(url string, l License) int { + + jsonBytes, err := json.Marshal(l) + if err != nil { + panic(err) + } + + resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBytes)) + + if err != nil { + fmt.Println("can't reach server to clear user") + os.Exit(0) + } + + defer resp.Body.Close() + + return resp.StatusCode +} \ No newline at end of file diff --git a/src/x/cmd/future.go b/src/x/cmd/future.go new file mode 100644 index 0000000..3e86c58 --- /dev/null +++ b/src/x/cmd/future.go @@ -0,0 +1,29 @@ +package cmd + +import ( + "github.com/devhindo/x/src/cli/tweet" + + "github.com/spf13/cobra" +) + +var ( + wait string + date string + time string + content string +) + +func init() { + rootCmd.AddCommand(futureCmd) +} + +var futureCmd = &cobra.Command{ + Use: "-f", + Short: "Post future tweets", + Long: `Post future tweets.`, + Run: futureCmdRun, +} + +func futureCmdRun(cmd *cobra.Command, args []string) { + tweet.PostFutureTweet(args) +} diff --git a/src/x/cmd/root.go b/src/x/cmd/root.go new file mode 100644 index 0000000..8e3c58d --- /dev/null +++ b/src/x/cmd/root.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var ( + version = "1.1.3" + man = fmt.Sprintf(`interact with x (twitter) from terminal. version: %s. + + USAGE + x + + Commands + -h show this help + auth start authorizing your X account + auth --url get auth url if it didn't open browser after running 'x auth' + auth -v verify authorization after running 'x auth' + -t "text" post a tweet + -v show version (%s) + -c clear authorized account`, version, version) +) + +var rootCmd = &cobra.Command{ + Use: "x", + Short: "x is a CLI tool for posting tweets", + Long: man, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(man) + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func init() { + +} diff --git a/src/x/cmd/update.go b/src/x/cmd/update.go new file mode 100644 index 0000000..9c01ec9 --- /dev/null +++ b/src/x/cmd/update.go @@ -0,0 +1,118 @@ +package cmd + +import ( + "fmt" + "os" + "os/exec" + "regexp" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(updateCmd) + + updateCmd.Flags().StringVarP(&vFlag, "version", "v", "latest", "Update the CLI to specific version") +} + +var ( + vFlag string + + updateCmd = &cobra.Command{ + Use: "update", + Short: "Update the CLI to specific version ", + Long: `Update the CLI to the latest version or specify a version using -v flag + Example: x update -v 1.1.1 + default: x update -v latest + `, + RunE: update, + } +) + +func update(cmd *cobra.Command, args []string) error { + + fmt.Println("Updating CLI...") + + vFlag, err := cmd.Flags().GetString("version") + if err != nil { + err = fmt.Errorf("error getting version flag: %v", err) + return err + } + + // validate version + if vFlag != "latest" { + err = validateVersion(vFlag) + if err != nil { + return err + } + } + + if isGoInstalled() { + err = updateUsingGo() + if err != nil { + return err + } + return nil + } + + return nil +} + +func validateVersion(v string) error { + // Regular expression pattern for semantic versioning + pattern := `^v\d+\.\d+\.\d+$` + match, err := regexp.MatchString(pattern, v) + if err != nil { + return err + } + if !match { + return fmt.Errorf("invalid version format. Please use semantic versioning like v1.1.1") + } + return nil +} + +func cmdUpdate() { + if !isGoInstalled() { + fmt.Println("") + } + + cmd := exec.Command("go", "get", "-u", "github.com/bradford-hamilton/monkey") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if isGoInstalled() { + fmt.Println("Go is installed...") + + return + } + + fmt.Println("Go is not installed...") + + fmt.Println("CLI updated successfully!") +} + +func isGoInstalled() bool { + _, err := exec.LookPath("go") + + return err == nil +} + +func updateUsingGo() error { + cmd := exec.Command("go", "get", "-u", "github.com/devhindo/x/src/cli/cmd") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + fmt.Println(err) + return err + } + fmt.Println("CLI updated successfully!") + return nil +} + +//TODO: x update -v 1.1.1 (default for v:latest) diff --git a/src/x/cmd/version.go b/src/x/cmd/version.go new file mode 100644 index 0000000..5158fcc --- /dev/null +++ b/src/x/cmd/version.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +//TODO: make it for -v and --version too + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version of the CLI", + Long: `Print the version of the CLI`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(version) + }, +} \ No newline at end of file diff --git a/src/x/env/load.go b/src/x/env/load.go new file mode 100644 index 0000000..cd0d1e8 --- /dev/null +++ b/src/x/env/load.go @@ -0,0 +1,19 @@ +package env +/* + +import ( + "github.com/joho/godotenv" + "os" +) + +func Load() { + // Set Twitter API Key + err := godotenv.Load(".env") + if err != nil { + panic(err) + } + + os.Getenv("TWITTER_API_KEY") + os.Getenv("TWITTER_API_SECRET_KEY") +} +*/ \ No newline at end of file diff --git a/src/x/go.mod b/src/x/go.mod new file mode 100644 index 0000000..9e5a6e1 --- /dev/null +++ b/src/x/go.mod @@ -0,0 +1,11 @@ +module github.com/devhindo/x/src/x + +go 1.22.2 + +require github.com/google/uuid v1.3.1 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect +) diff --git a/src/x/go.sum b/src/x/go.sum new file mode 100644 index 0000000..779870e --- /dev/null +++ b/src/x/go.sum @@ -0,0 +1,12 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/x/gotwi/post.go b/src/x/gotwi/post.go new file mode 100644 index 0000000..de084ef --- /dev/null +++ b/src/x/gotwi/post.go @@ -0,0 +1,37 @@ +package gotwi +/* +import ( + "github.com/michimani/gotwi" + "github.com/michimani/gotwi/tweet/managetweet" + "github.com/michimani/gotwi/tweet/managetweet/types" + "fmt" + "context" +) + +func PostTweet(t string) (string, error) { + + in := &gotwi.NewClientWithAccessTokenInput{ + AccessToken: "#", + } + + c, err := gotwi.NewClientWithAccessToken(in) + + if err != nil { + fmt.Println(err) + } else { + fmt.Println(c, "done") + } + + tweet := &types.CreateInput { + Text: gotwi.String(t), + } + + res, err := managetweet.Create(context.Background(), c, tweet) + + if err != nil { + return "", err + } + + return gotwi.StringValue(res.Data.ID), nil +} +*/ \ No newline at end of file diff --git a/src/x/help/help.go b/src/x/help/help.go new file mode 100644 index 0000000..33f6aa8 --- /dev/null +++ b/src/x/help/help.go @@ -0,0 +1,25 @@ +package help + +import ( + "fmt" +) + +func Help() { + fmt.Println() + fmt.Println("interact with x (twitter) from terminal.") + fmt.Println() + fmt.Println("USAGE") + fmt.Println(" x ") + fmt.Println() + fmt.Println("Commands") + fmt.Println(" -h show this help") + fmt.Println(" auth start authorizing your X account") + fmt.Println(" auth --url get auth url if it didn't open browser after running 'x auth'") + fmt.Println(" auth -v verify authorization after running 'x auth'") + fmt.Println(" -t \"text\" post a tweet") + fmt.Println(" -v show version") + fmt.Println(" -c clear authorized account") + fmt.Println() + fmt.Println("LEARN MORE") + fmt.Println(" Cheack source code at: https://github.com/devhindo/x") +} \ No newline at end of file diff --git a/src/x/lock/key.go b/src/x/lock/key.go new file mode 100644 index 0000000..a0415eb --- /dev/null +++ b/src/x/lock/key.go @@ -0,0 +1,77 @@ +package lock + +import ( + "encoding/base64" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/google/uuid" +) + +func GenerateLicenseKey() (string, error) { + uuid := uuid.New() + uuidBytes := uuid[:] + licenseKeyBytes := append(uuidBytes) + licenseKey := base64.StdEncoding.EncodeToString(licenseKeyBytes) + return licenseKey, nil +} + +func WriteLicenseKeyToFile(licenseKey string) error { + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("Error getting user home directory: %v", err) + } + + licenseFilePath := filepath.Join(homeDir, ".tempxcli") + licenseFile, err := os.Create(licenseFilePath) + if err != nil { + return fmt.Errorf("Error creating license file: %v", err) + } + defer licenseFile.Close() + + _, err = licenseFile.WriteString(licenseKey) + if err != nil { + return fmt.Errorf("Error writing license key to file: %v", err) + } + + return nil +} + +func ReadLicenseKeyFromFile() (string, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("Error getting user home directory: %v", err) + } + + licenseFilePath := filepath.Join(homeDir, ".tempxcli") + licenseFile, err := os.Open(licenseFilePath) + if err != nil { + return "", fmt.Errorf("Error opening license file: %v", err) + } + defer licenseFile.Close() + + licenseFileBytes, err := io.ReadAll(licenseFile) + if err != nil { + return "", fmt.Errorf("Error reading license file: %v", err) + } + + licenseKey := string(licenseFileBytes) + return licenseKey, nil +} + +func ClearLicenseFile() error { + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("Error getting user home directory: %v", err) + } + + licenseFilePath := filepath.Join(homeDir, ".tempxcli") + err = os.Remove(licenseFilePath) + if err != nil { + return fmt.Errorf("Error deleting license file: %v", err) + } + + return nil +} \ No newline at end of file diff --git a/src/x/main.go b/src/x/main.go new file mode 100644 index 0000000..9abc3fd --- /dev/null +++ b/src/x/main.go @@ -0,0 +1,9 @@ +package main + +import "github.com/devhindo/x/src/cli/cmd" + + +func main() { + //x.Run() + cmd.Execute() +} diff --git a/src/x/release.md b/src/x/release.md new file mode 100644 index 0000000..577dcbf --- /dev/null +++ b/src/x/release.md @@ -0,0 +1,4 @@ +# First Release + +- tweet from terminal using `x -t "first tweet from terminal!"` +- check `README.md` for more info diff --git a/src/x/tweet/future.go b/src/x/tweet/future.go new file mode 100644 index 0000000..f39c27b --- /dev/null +++ b/src/x/tweet/future.go @@ -0,0 +1,170 @@ +package tweet + +// x t "hi" 5h6m7s + +import ( + "fmt" + "log" + "strconv" + "strings" + "bytes" + "encoding/json" + "io" + "net/http" + + "github.com/devhindo/x/src/cli/lock" +) + +type FutureTweet struct { + License string `json:"license"` + Tweet string `json:"tweet"` + Hours int `json:"hours"` + Minutes int `json:"minutes"` +} + +func PostFutureTweet(c []string) { + + url := "http://localhost:3000/api/tweets/future" + + // x t "hi" 5h6m7s + + tweetText, tweetTime, err := handleFutureTweetArgs(c) + if err != nil { + log.SetFlags(0) + log.Fatal(err) + } + + hrs, mins, err := handleTweetTime(tweetTime) + + if err != nil { + log.SetFlags(0) + log.Fatal(err) + } + + license, err := lock.ReadLicenseKeyFromFile() + + if err != nil { + fmt.Println("you are not authenticated | try 'x auth'") + return + } + + tweet := FutureTweet{ + License: license, + Tweet: tweetText, + Hours: hrs, + Minutes: mins, + } + + err = postFutureTweetToServer(url, tweet) + + if err != nil { + log.SetFlags(0) + log.Fatal(err) + } +} + + +func postFutureTweetToServer(url string, t FutureTweet) error { + fmt.Println("unmarchalling") + jsonBytes, err := json.Marshal(t) + if err != nil { + panic(err) + } + + resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBytes)) + fmt.Println("posting") + if err != nil { + return fmt.Errorf("can't reach server to post a tweet") + } + fmt.Println("before defer") + defer resp.Body.Close() + fmt.Println("after defer") + _, err = io.ReadAll(resp.Body) + if err != nil { + //Failed to read response. + return fmt.Errorf("can't read server response") + } + + var r response + + + //Convert bytes to String and print + fmt.Println(r.Message) + + return nil +} + +func handleFutureTweetArgs(c []string) (string, string, error) { + if len(c) < 3 { + return "", "", fmt.Errorf("no tweet given | try 'x f --help'") + } + + if c[2] == "-h" || c[2] == "--help" { + fmt.Println("post future tweets") + fmt.Println("using delayed times in this form") + fmt.Println("x f \"hi\" 2h3m") + fmt.Println("h -> hours") + fmt.Println("m -> minutes") + fmt.Println("this tweet would be scheduled to be posted after 2 hours and 3 minuets") + return c[2], c[3], nil + } + + if len(c) < 4 { + fmt.Println("No schedule time is given | try 'x f --help'") + } + return c[2], c[3], nil +} + +func handleTweetTime(t string) (int, int, error) { + hrs := 0 + mins := 0 + + // Check if the string is empty + if len(t) == 0 { + return hrs, mins, fmt.Errorf("empty time string") + } + + containsH := strings.Contains(t, "h") + containsM := strings.Contains(t, "m") + + if containsH && containsM { + // Split the string into hours and minutes + timeParts := strings.Split(t, "h") + if len(timeParts) != 2 { + return hrs, mins, fmt.Errorf("invalid time string") + } + + hours, err := strconv.Atoi(timeParts[0]) + if err != nil { + return hrs, mins, fmt.Errorf("invalid time string") + } + + minutes, err := strconv.Atoi(strings.TrimSuffix(timeParts[1], "m")) + if err != nil { + return hrs, mins, fmt.Errorf("invalid time string") + } + + hrs = hours + mins = minutes + } else if containsH { + // Extract the hours from the string + hours, err := strconv.Atoi(strings.TrimSuffix(t, "h")) + if err != nil { + return hrs, mins, fmt.Errorf("invalid time string") + } + + hrs = hours + } else if containsM { + // Extract the minutes from the string + minutes, err := strconv.Atoi(strings.TrimSuffix(t, "m")) + if err != nil { + return hrs, mins, fmt.Errorf("invalid time string") + } + + mins = minutes + } else { + return hrs, mins, fmt.Errorf("invalid time string") + } + + return hrs, mins, nil +} \ No newline at end of file diff --git a/src/x/tweet/post.go b/src/x/tweet/post.go new file mode 100644 index 0000000..28acde2 --- /dev/null +++ b/src/x/tweet/post.go @@ -0,0 +1,74 @@ +package tweet + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + + "github.com/devhindo/x/src/cli/lock" +) + +type Tweet struct { + License string `json:"license"` + Tweet string `json:"tweet"` +} + +func POST_tweet(t string) { + + license, err := lock.ReadLicenseKeyFromFile() + + if err != nil { + fmt.Println("you are not authenticated | try 'x auth'") + os.Exit(1) + } + + url := "https://x-blush.vercel.app/api/tweets/post" + tweet := Tweet{ + License: license, + Tweet: t, + } + + postT(url, tweet) +} + +type response struct { + Message string `json:"message"` +} + +func postT(url string, t Tweet) { + + jsonBytes, err := json.Marshal(t) + if err != nil { + panic(err) + } + + resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBytes)) + if err != nil { + fmt.Println("can't reach server to post a tweet") + os.Exit(0) + } + + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + //Failed to read response. + panic(err) + } + + + var r response + err = json.Unmarshal(body, &r) + if err != nil { + fmt.Printf("Failed to unmarshal response") + //Failed to unmarshal response. + return + } + + + //Convert bytes to String and print + fmt.Println(r.Message) +} \ No newline at end of file diff --git a/src/x/utils/crud.go b/src/x/utils/crud.go new file mode 100644 index 0000000..ab2f68d --- /dev/null +++ b/src/x/utils/crud.go @@ -0,0 +1,83 @@ +package utils + +import ( + "bytes" + "fmt" + "io" + "net/http" + "log" + "encoding/json" + +) + +type User struct { + State string `json:"state"` + Auth_URL string `json:"auth_url"` + Code_verifier string `json:"code_verifier"` + Code_challenge string `json:"code_challenge"` +} + +func POST(url string, user User) { + // Create a new HTTP request object. + req, err := http.NewRequest("POST", url, nil) + if err != nil { + fmt.Println(err) + return + } + + jsonBytes, err := json.Marshal(user) + if err != nil { + fmt.Println(err) + return + } + + + req.Body = io.NopCloser(bytes.NewBuffer(jsonBytes)) + + req.Header.Set("Content-Type", "application/json") + + // Send the request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + + // Handle the response + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + fmt.Println(string(body)) +} + +func GET(url string) { + // Create a new HTTP request object. + req, err := http.NewRequest("GET", url, nil) + if err != nil { + fmt.Println(err) + return + } + + // Send the request and receive the response. + resp, err := http.DefaultClient.Do(req) + if err != nil { + fmt.Println(err) + return + } + + // Read the response body. + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println(err) + return + } + + // Close the response body. + defer resp.Body.Close() + + // Print the response body. + fmt.Println(string(body)) + +} diff --git a/src/x/utils/open.go b/src/x/utils/open.go new file mode 100644 index 0000000..c93ef5c --- /dev/null +++ b/src/x/utils/open.go @@ -0,0 +1,33 @@ +package utils + +import ( + "os/exec" + "runtime" +) + +func OpenBrowser(url string) { + err := open(url) + if err != nil { + panic(err) + } +} + +func open(url string) error { + + // source : https://gist.github.com/sevkin/9798d67b2cb9d07cb05f89f14ba682f8 + + var cmd string + var args []string + + switch runtime.GOOS { + case "windows": + cmd = "rundll32" + args = []string{"url.dll,FileProtocolHandler"} + case "darwin": + cmd = "open" + default: // "linux", "freebsd", "openbsd", "netbsd" + cmd = "xdg-open" + } + args = append(args, url) + return exec.Command(cmd, args...).Start() +} \ No newline at end of file diff --git a/src/x/x/args.go b/src/x/x/args.go new file mode 100644 index 0000000..0fc3cab --- /dev/null +++ b/src/x/x/args.go @@ -0,0 +1,100 @@ +package x + +import ( + "os" + "fmt" + + "github.com/devhindo/x/src/cli/help" + "github.com/devhindo/x/src/cli/auth" + "github.com/devhindo/x/src/cli/tweet" + "github.com/devhindo/x/src/cli/clear" + +) + +func HandleArgs() { + checkArgs() + switch os.Args[1] { + case "help": + checkArgsequals2() + help.Help() + case "--help": + checkArgsequals2() + help.Help() + case "-h": + checkArgsequals2() + help.Help() + case "auth": + if len(os.Args) == 2 { + auth.Auth() + } else if len(os.Args) == 3 && (os.Args[2] == "--verify" || os.Args[2] == "-v") { + auth.Verify() + } else if len(os.Args) == 3 && (os.Args[2] == "--clear" || os.Args[2] == "-c") { + clear.StartOver() + } else if len(os.Args) == 3 && os.Args[2] == "--url" { + auth.Get_url_db() + } else { + fmt.Println("Unknown command | try 'x help'") + os.Exit(0) + } + case "t": + checkTweetArgs() + tweet.POST_tweet(os.Args[2]) + case "-t": + checkTweetArgs() + tweet.POST_tweet(os.Args[2]) + case "tweet": + checkTweetArgs() + tweet.POST_tweet(os.Args[2]) + case "version": + checkArgsequals2() + Version() + case "v": + checkArgsequals2() + Version() + case "f": // x -t "hi" 5h6m7s + checkArgsequals2() + tweet.PostFutureTweet(os.Args) + case "-f": + checkArgsequals2() + tweet.PostFutureTweet(os.Args) + default: + + if len(os.Args) != 2 { + fmt.Println("Unknown command | try 'x help'") + os.Exit(0) + } + + tweet.POST_tweet(os.Args[1]) + + } +} + +func checkTweetArgs() { + if len(os.Args) < 3 { + fmt.Println("No tweet given | try 'x help'") + os.Exit(0) + } +} + +func checkArgs() { + if len(os.Args) < 2 { + fmt.Println("No command given | try 'x help'") + os.Exit(0) + } +} + +func checkArgsequals2() { + if len(os.Args) != 2 { + fmt.Println("Unknown command | try 'x help'") + os.Exit(0) + } +} + +func checkFutureTweetArgs() { + if len(os.Args) < 4 { + fmt.Println("No tweet given | try 'x help'") + os.Exit(0) + } + +} + diff --git a/src/x/x/run.go b/src/x/x/run.go new file mode 100644 index 0000000..6ee0b4e --- /dev/null +++ b/src/x/x/run.go @@ -0,0 +1,10 @@ +package x + +import ( + "github.com/devhindo/x/src/cli/cmd" +) + +func Run() { + // HandleArgs() + cmd.Execute() +} \ No newline at end of file diff --git a/src/x/x/version.go b/src/x/x/version.go new file mode 100644 index 0000000..950441d --- /dev/null +++ b/src/x/x/version.go @@ -0,0 +1,7 @@ +package x + +import "fmt" + +func Version() { + fmt.Println("x CLI v1.1.2") +} From 709912cc0c58cda1e4454894c4cfe935579596a8 Mon Sep 17 00:00:00 2001 From: devhindo Date: Mon, 15 Apr 2024 08:00:30 +0200 Subject: [PATCH 6/9] changed back to cli --- src/cli/cmd/update.go | 118 +++++++++++++++++++++++++++++++++++ src/cli/cmd/update/update.go | 63 ------------------- src/cli/cmd/version.go | 22 +++++++ 3 files changed, 140 insertions(+), 63 deletions(-) create mode 100644 src/cli/cmd/update.go delete mode 100644 src/cli/cmd/update/update.go create mode 100644 src/cli/cmd/version.go diff --git a/src/cli/cmd/update.go b/src/cli/cmd/update.go new file mode 100644 index 0000000..9c01ec9 --- /dev/null +++ b/src/cli/cmd/update.go @@ -0,0 +1,118 @@ +package cmd + +import ( + "fmt" + "os" + "os/exec" + "regexp" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(updateCmd) + + updateCmd.Flags().StringVarP(&vFlag, "version", "v", "latest", "Update the CLI to specific version") +} + +var ( + vFlag string + + updateCmd = &cobra.Command{ + Use: "update", + Short: "Update the CLI to specific version ", + Long: `Update the CLI to the latest version or specify a version using -v flag + Example: x update -v 1.1.1 + default: x update -v latest + `, + RunE: update, + } +) + +func update(cmd *cobra.Command, args []string) error { + + fmt.Println("Updating CLI...") + + vFlag, err := cmd.Flags().GetString("version") + if err != nil { + err = fmt.Errorf("error getting version flag: %v", err) + return err + } + + // validate version + if vFlag != "latest" { + err = validateVersion(vFlag) + if err != nil { + return err + } + } + + if isGoInstalled() { + err = updateUsingGo() + if err != nil { + return err + } + return nil + } + + return nil +} + +func validateVersion(v string) error { + // Regular expression pattern for semantic versioning + pattern := `^v\d+\.\d+\.\d+$` + match, err := regexp.MatchString(pattern, v) + if err != nil { + return err + } + if !match { + return fmt.Errorf("invalid version format. Please use semantic versioning like v1.1.1") + } + return nil +} + +func cmdUpdate() { + if !isGoInstalled() { + fmt.Println("") + } + + cmd := exec.Command("go", "get", "-u", "github.com/bradford-hamilton/monkey") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + if isGoInstalled() { + fmt.Println("Go is installed...") + + return + } + + fmt.Println("Go is not installed...") + + fmt.Println("CLI updated successfully!") +} + +func isGoInstalled() bool { + _, err := exec.LookPath("go") + + return err == nil +} + +func updateUsingGo() error { + cmd := exec.Command("go", "get", "-u", "github.com/devhindo/x/src/cli/cmd") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + fmt.Println(err) + return err + } + fmt.Println("CLI updated successfully!") + return nil +} + +//TODO: x update -v 1.1.1 (default for v:latest) diff --git a/src/cli/cmd/update/update.go b/src/cli/cmd/update/update.go deleted file mode 100644 index 8c4488a..0000000 --- a/src/cli/cmd/update/update.go +++ /dev/null @@ -1,63 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "os/exec" - "strings" - - "github.com/spf13/cobra" -) - -var updateCmd = &cobra.Command{ - Use: "update", - Short: "Update the CLI to the latest version", - Run: func(cmd *cobra.Command, args []string) { - cmdUpdate() - }, -} - -func cmdUpdate() { - fmt.Println("Updating CLI...") - - if !isGoInstalled() { - fmt.Println("") - } - - cmd := exec.Command("go", "get", "-u", "github.com/bradford-hamilton/monkey") - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - fmt.Println("CLI updated successfully!") -} - -func isGoInstalled() bool { - _, err := exec.LookPath("go") - if err != nil { - return false - } - return true -} - -func updateUsingGo() error { - cmd := exec.Command("go", "get", "-u", "github.com/bradford-hamilton/monkey") - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - if err != nil { - fmt.Println(err) - return err - } - fmt.Println("CLI updated successfully!") - return nil -} - -func init() { - rootCmd.AddCommand(updateCmd) -} - -//TODO: x update -v 1.1.1 (default for v:latest) \ No newline at end of file diff --git a/src/cli/cmd/version.go b/src/cli/cmd/version.go new file mode 100644 index 0000000..5158fcc --- /dev/null +++ b/src/cli/cmd/version.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +//TODO: make it for -v and --version too + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version of the CLI", + Long: `Print the version of the CLI`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(version) + }, +} \ No newline at end of file From 23cd06c72ccecc0fd409bec49b453728ae29913a Mon Sep 17 00:00:00 2001 From: devhindo Date: Mon, 15 Apr 2024 08:00:51 +0200 Subject: [PATCH 7/9] changed dir --- src/x/.goreleaser.yaml | 39 --------- src/x/auth/auth.go | 29 ------- src/x/auth/crud.go | 59 -------------- src/x/auth/geturl.go | 21 ----- src/x/auth/license.go | 30 ------- src/x/auth/lock.go | 18 ----- src/x/auth/oauth.go | 62 -------------- src/x/auth/server.go | 11 --- src/x/auth/url.go | 13 --- src/x/auth/user.go | 46 ----------- src/x/auth/validate.go | 7 -- src/x/auth/verify.go | 67 --------------- src/x/clear/startover.go | 67 --------------- src/x/cmd/future.go | 29 ------- src/x/cmd/root.go | 45 ----------- src/x/cmd/update.go | 118 --------------------------- src/x/cmd/version.go | 22 ----- src/x/env/load.go | 19 ----- src/x/go.mod | 11 --- src/x/go.sum | 12 --- src/x/gotwi/post.go | 37 --------- src/x/help/help.go | 25 ------ src/x/lock/key.go | 77 ------------------ src/x/main.go | 9 --- src/x/release.md | 4 - src/x/tweet/future.go | 170 --------------------------------------- src/x/tweet/post.go | 74 ----------------- src/x/utils/crud.go | 83 ------------------- src/x/utils/open.go | 33 -------- src/x/x/args.go | 100 ----------------------- src/x/x/run.go | 10 --- src/x/x/version.go | 7 -- 32 files changed, 1354 deletions(-) delete mode 100644 src/x/.goreleaser.yaml delete mode 100644 src/x/auth/auth.go delete mode 100644 src/x/auth/crud.go delete mode 100644 src/x/auth/geturl.go delete mode 100644 src/x/auth/license.go delete mode 100644 src/x/auth/lock.go delete mode 100644 src/x/auth/oauth.go delete mode 100644 src/x/auth/server.go delete mode 100644 src/x/auth/url.go delete mode 100644 src/x/auth/user.go delete mode 100644 src/x/auth/validate.go delete mode 100644 src/x/auth/verify.go delete mode 100644 src/x/clear/startover.go delete mode 100644 src/x/cmd/future.go delete mode 100644 src/x/cmd/root.go delete mode 100644 src/x/cmd/update.go delete mode 100644 src/x/cmd/version.go delete mode 100644 src/x/env/load.go delete mode 100644 src/x/go.mod delete mode 100644 src/x/go.sum delete mode 100644 src/x/gotwi/post.go delete mode 100644 src/x/help/help.go delete mode 100644 src/x/lock/key.go delete mode 100644 src/x/main.go delete mode 100644 src/x/release.md delete mode 100644 src/x/tweet/future.go delete mode 100644 src/x/tweet/post.go delete mode 100644 src/x/utils/crud.go delete mode 100644 src/x/utils/open.go delete mode 100644 src/x/x/args.go delete mode 100644 src/x/x/run.go delete mode 100644 src/x/x/version.go diff --git a/src/x/.goreleaser.yaml b/src/x/.goreleaser.yaml deleted file mode 100644 index c60e469..0000000 --- a/src/x/.goreleaser.yaml +++ /dev/null @@ -1,39 +0,0 @@ -project_name: x -# This is an example .goreleaser.yml file with some sensible defaults. -# Make sure to check the documentation at https://goreleaser.com - -# The lines bellow are called `modelines`. See `:help modeline` -# Feel free to remove those if you don't want/need to use them. -# yaml-language-server: $schema=https://goreleaser.com/static/schema.json -# vim: set ts=2 sw=2 tw=0 fo=cnqoj - -before: - hooks: - # You may remove this if you don't use go modules. - - go mod tidy - # you may remove this if you don't need go generate - - go generate ./... - -builds: - - env: - - CGO_ENABLED=0 - goos: - - linux - - windows - - darwin - -archives: - - format: tar.gz - # this name template makes the OS and Arch compatible with the results of `uname`. - name_template: >- - {{ .ProjectName }}_ - {{- title .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} - {{- if .Arm }}v{{ .Arm }}{{ end }} - # use zip for windows archives - format_overrides: - - goos: windows - format: zip - wrap_in_directory: x diff --git a/src/x/auth/auth.go b/src/x/auth/auth.go deleted file mode 100644 index e8541de..0000000 --- a/src/x/auth/auth.go +++ /dev/null @@ -1,29 +0,0 @@ -package auth - -import ( - "fmt" - "os" - - "github.com/devhindo/x/src/cli/lock" -) - -// func check_authentication() {} - -func Auth() { - - checkIfUserExists() - - u := newUser() - u.add_user_to_db() - u.open_browser_to_auth_url() - fmt.Println("please authorize X CLI in your browser then run 'x auth --verify'") - fmt.Println("if the browser does not open, run 'x auth --url` to get the authorization url") -} - -func checkIfUserExists() { - _, err := lock.ReadLicenseKeyFromFile() - if err == nil { - fmt.Println("a user is already logged in | try 'x -h'") - os.Exit(0) - } -} diff --git a/src/x/auth/crud.go b/src/x/auth/crud.go deleted file mode 100644 index 7bf1fef..0000000 --- a/src/x/auth/crud.go +++ /dev/null @@ -1,59 +0,0 @@ -package auth - -import ( - "bytes" - "fmt" - "io" - "net/http" - "encoding/json" - "os" -) - -func Post(url string, u User) int { - - jsonBytes, err := json.Marshal(u) - if err != nil { - panic(err) - } - - resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBytes)) - - if err != nil { - fmt.Println("can't reach server to perform authentication") - os.Exit(0) - } - - defer resp.Body.Close() - - return resp.StatusCode -} - -func GET(url string) { - // Create a new HTTP request object. - req, err := http.NewRequest("GET", url, nil) - if err != nil { - fmt.Println(err) - return - } - - // Send the request and receive the response. - resp, err := http.DefaultClient.Do(req) - if err != nil { - fmt.Println(err) - return - } - - // Read the response body. - body, err := io.ReadAll(resp.Body) - if err != nil { - fmt.Println(err) - return - } - - // Close the response body. - defer resp.Body.Close() - - // Print the response body. - fmt.Println(string(body)) - -} diff --git a/src/x/auth/geturl.go b/src/x/auth/geturl.go deleted file mode 100644 index 70d94c7..0000000 --- a/src/x/auth/geturl.go +++ /dev/null @@ -1,21 +0,0 @@ -package auth - -import ( - "fmt" - "os" - - "github.com/devhindo/x/src/cli/lock" -) - -func Get_url_db() { - l, err := lock.ReadLicenseKeyFromFile() - if err != nil { - fmt.Println("you are not authenticated | try 'x auth'") - os.Exit(1) - } - - url := "https://x-blush.vercel.app/api/user/url" - k := License{License: l} - - postL(url, k) -} diff --git a/src/x/auth/license.go b/src/x/auth/license.go deleted file mode 100644 index 31640d8..0000000 --- a/src/x/auth/license.go +++ /dev/null @@ -1,30 +0,0 @@ -package auth - -import ( - "os" - "fmt" - "io" - "path/filepath" -) - -func (u *User) RetrieveLicenseKey() (string, error) { - homeDir, err := os.UserHomeDir() - if err != nil { - return "", fmt.Errorf("Error getting user home directory: %v", err) - } - - licenseFilePath := filepath.Join(homeDir, ".tempxcli") - licenseFile, err := os.Open(licenseFilePath) - if err != nil { - return "", fmt.Errorf("Error opening license file: %v", err) - } - defer licenseFile.Close() - - licenseFileBytes, err := io.ReadAll(licenseFile) - if err != nil { - return "", fmt.Errorf("Error reading license file: %v", err) - } - - licenseKey := string(licenseFileBytes) - return licenseKey, nil -} \ No newline at end of file diff --git a/src/x/auth/lock.go b/src/x/auth/lock.go deleted file mode 100644 index 55e62ab..0000000 --- a/src/x/auth/lock.go +++ /dev/null @@ -1,18 +0,0 @@ -package auth - -import ( - "github.com/devhindo/x/src/cli/lock" -) - -func (u *User) Lock() { - licenseKey, err := lock.GenerateLicenseKey() - if err != nil { - panic(err) - } - - //err = lock.WriteLicenseKeyToFile(licenseKey) - //if err != nil { - // panic(err) - //} - u.License = licenseKey -} \ No newline at end of file diff --git a/src/x/auth/oauth.go b/src/x/auth/oauth.go deleted file mode 100644 index 6826e88..0000000 --- a/src/x/auth/oauth.go +++ /dev/null @@ -1,62 +0,0 @@ -package auth - -import ( - "crypto/rand" - "crypto/sha256" - "math/big" - "encoding/base64" -) - -/* -* code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) -*/ - -func (u *User) generate_code_challenge() { - // Base64-URL-encoded string of the SHA256 hash of the code verifier - //u.Code_challenge = base64.RawURLEncoding.EncodeToString((hash_sha256(u.Code_verifier))) - u.Code_challenge = base64.RawURLEncoding.EncodeToString((hash_sha256(u.Code_verifier))) - -} - -func hash_sha256(s string) []byte { - h := sha256.New() - h.Write([]byte(s)) - return h.Sum(nil) -} - -func (u *User) generate_code_verifier() { - const ( - chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" - min = 43 - max = 128 - ) - - length, err := rand.Int(rand.Reader, big.NewInt(max-min+1)) - if err != nil { - panic(err) - } - length.Add(length, big.NewInt(min)) - - b := make([]byte, length.Int64()) - for i := range b { - n, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars)))) - if err != nil { - panic(err) - } - b[i] = chars[n.Int64()] - } - - u.Code_verifier = string(b) -} - -func (u *User) generate_state(stateLength int) { - b := make([]byte, stateLength) - _, err := rand.Read(b) - if err != nil { - panic(err) - } - - state := base64.URLEncoding.EncodeToString(b) - - u.State = state -} \ No newline at end of file diff --git a/src/x/auth/server.go b/src/x/auth/server.go deleted file mode 100644 index f3b1e03..0000000 --- a/src/x/auth/server.go +++ /dev/null @@ -1,11 +0,0 @@ -package auth - -import ( - -) - -// todo: send state to the server -// todo: actually send a one request containing everything need for requesting the access token - - - diff --git a/src/x/auth/url.go b/src/x/auth/url.go deleted file mode 100644 index 0674567..0000000 --- a/src/x/auth/url.go +++ /dev/null @@ -1,13 +0,0 @@ -package auth - -func (u *User) generate_auth_url() { - auth_url := "" - auth_scopes := "tweet.read%20tweet.write%20users.read%20users.read%20follows.read%20follows.write%20offline.access" - auth_url += "https://twitter.com/i/oauth2/authorize?response_type=code&client_id=" - auth_url += "emJHZzZHMUdHMF9QRlRIdk45QjY6MTpjaQ" - redirect_url := "https://x-blush.vercel.app/api/auth" - auth_url += "&redirect_uri=" + redirect_url - auth_url += "&scope=" + auth_scopes - auth_url += "&state=" + u.State + "&code_challenge=" + u.Code_challenge + "&code_challenge_method=S256" - u.Auth_URL = auth_url -} diff --git a/src/x/auth/user.go b/src/x/auth/user.go deleted file mode 100644 index 820d14b..0000000 --- a/src/x/auth/user.go +++ /dev/null @@ -1,46 +0,0 @@ -package auth - -import ( - "fmt" - - "github.com/devhindo/x/src/cli/utils" - "github.com/devhindo/x/src/cli/lock" -) - -type User struct { - State string `json:"state"` - Auth_URL string `json:"auth_url"` - Code_verifier string `json:"code_verifier"` - Code_challenge string `json:"code_challenge"` - License string `json:"license"` -} - -func newUser() *User { - u := new(User) - u.Lock() - u.generate_code_verifier() - u.generate_code_challenge() - u.generate_state(127) - u.generate_auth_url() - - return u -} - -func (u *User) add_user_to_db() { - status := Post("https://x-blush.vercel.app/api/auth/add", *u) - - if status != 200 { - fmt.Println("error adding user") - } else { - - err := lock.WriteLicenseKeyToFile(u.License) - if err != nil { - fmt.Println("coudln't write license key to file") - return - } - } -} - -func (u *User) open_browser_to_auth_url() { - utils.OpenBrowser(u.Auth_URL) -} diff --git a/src/x/auth/validate.go b/src/x/auth/validate.go deleted file mode 100644 index 7ccca22..0000000 --- a/src/x/auth/validate.go +++ /dev/null @@ -1,7 +0,0 @@ -package auth - -import () - -func IsAuthenticated() bool { - return false -} \ No newline at end of file diff --git a/src/x/auth/verify.go b/src/x/auth/verify.go deleted file mode 100644 index e760b5a..0000000 --- a/src/x/auth/verify.go +++ /dev/null @@ -1,67 +0,0 @@ -package auth - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "os" - "io" - - "github.com/devhindo/x/src/cli/lock" -) - -type License struct { - License string `json:"license"` -} - -func Verify() bool { - l, err := lock.ReadLicenseKeyFromFile() - if err != nil { - fmt.Println("you are not authenticated | try 'x auth'") - os.Exit(1) - } - - k := License{License: l} - - url := "https://x-blush.vercel.app/api/auth/verify" - - postL(url, k) - - - return true -} - -type response struct { - Message string `json:"message"` -} - -func postL(url string, l License) { - - jsonBytes, err := json.Marshal(l) - if err != nil { - panic(err) - } - - resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBytes)) - - if err != nil { - fmt.Println("can't reach server to verify user") - os.Exit(0) - } - - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - //Failed to read response. - panic(err) - } - - var r response - - err = json.Unmarshal(body, &r) - - //Convert bytes to String and print - fmt.Println(r.Message) -} \ No newline at end of file diff --git a/src/x/clear/startover.go b/src/x/clear/startover.go deleted file mode 100644 index a0522a7..0000000 --- a/src/x/clear/startover.go +++ /dev/null @@ -1,67 +0,0 @@ -package clear - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "os" - - "github.com/devhindo/x/src/cli/lock" -) - -func StartOver() { - - license, err := lock.ReadLicenseKeyFromFile() - - if err != nil { - fmt.Println("no user logged in") - return - } - - delete_user_from_db(license) - - lock.ClearLicenseFile() - - fmt.Println("user deleted successfully") - -} - -// is there anyway better to pass license? -type License struct { - License string `json:"license"` -} - -func delete_user_from_db(license string) { - - l := License{ - License: license, - } - - url := "https://x-blush.vercel.app/api/user/delete" - - status := post(url, l) - - if status != 200 { - fmt.Println("error deleting user from db") - } -} - -func post(url string, l License) int { - - jsonBytes, err := json.Marshal(l) - if err != nil { - panic(err) - } - - resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBytes)) - - if err != nil { - fmt.Println("can't reach server to clear user") - os.Exit(0) - } - - defer resp.Body.Close() - - return resp.StatusCode -} \ No newline at end of file diff --git a/src/x/cmd/future.go b/src/x/cmd/future.go deleted file mode 100644 index 3e86c58..0000000 --- a/src/x/cmd/future.go +++ /dev/null @@ -1,29 +0,0 @@ -package cmd - -import ( - "github.com/devhindo/x/src/cli/tweet" - - "github.com/spf13/cobra" -) - -var ( - wait string - date string - time string - content string -) - -func init() { - rootCmd.AddCommand(futureCmd) -} - -var futureCmd = &cobra.Command{ - Use: "-f", - Short: "Post future tweets", - Long: `Post future tweets.`, - Run: futureCmdRun, -} - -func futureCmdRun(cmd *cobra.Command, args []string) { - tweet.PostFutureTweet(args) -} diff --git a/src/x/cmd/root.go b/src/x/cmd/root.go deleted file mode 100644 index 8e3c58d..0000000 --- a/src/x/cmd/root.go +++ /dev/null @@ -1,45 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" -) - -var ( - version = "1.1.3" - man = fmt.Sprintf(`interact with x (twitter) from terminal. version: %s. - - USAGE - x - - Commands - -h show this help - auth start authorizing your X account - auth --url get auth url if it didn't open browser after running 'x auth' - auth -v verify authorization after running 'x auth' - -t "text" post a tweet - -v show version (%s) - -c clear authorized account`, version, version) -) - -var rootCmd = &cobra.Command{ - Use: "x", - Short: "x is a CLI tool for posting tweets", - Long: man, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println(man) - }, -} - -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } -} - -func init() { - -} diff --git a/src/x/cmd/update.go b/src/x/cmd/update.go deleted file mode 100644 index 9c01ec9..0000000 --- a/src/x/cmd/update.go +++ /dev/null @@ -1,118 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "os/exec" - "regexp" - - "github.com/spf13/cobra" -) - -func init() { - rootCmd.AddCommand(updateCmd) - - updateCmd.Flags().StringVarP(&vFlag, "version", "v", "latest", "Update the CLI to specific version") -} - -var ( - vFlag string - - updateCmd = &cobra.Command{ - Use: "update", - Short: "Update the CLI to specific version ", - Long: `Update the CLI to the latest version or specify a version using -v flag - Example: x update -v 1.1.1 - default: x update -v latest - `, - RunE: update, - } -) - -func update(cmd *cobra.Command, args []string) error { - - fmt.Println("Updating CLI...") - - vFlag, err := cmd.Flags().GetString("version") - if err != nil { - err = fmt.Errorf("error getting version flag: %v", err) - return err - } - - // validate version - if vFlag != "latest" { - err = validateVersion(vFlag) - if err != nil { - return err - } - } - - if isGoInstalled() { - err = updateUsingGo() - if err != nil { - return err - } - return nil - } - - return nil -} - -func validateVersion(v string) error { - // Regular expression pattern for semantic versioning - pattern := `^v\d+\.\d+\.\d+$` - match, err := regexp.MatchString(pattern, v) - if err != nil { - return err - } - if !match { - return fmt.Errorf("invalid version format. Please use semantic versioning like v1.1.1") - } - return nil -} - -func cmdUpdate() { - if !isGoInstalled() { - fmt.Println("") - } - - cmd := exec.Command("go", "get", "-u", "github.com/bradford-hamilton/monkey") - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - if isGoInstalled() { - fmt.Println("Go is installed...") - - return - } - - fmt.Println("Go is not installed...") - - fmt.Println("CLI updated successfully!") -} - -func isGoInstalled() bool { - _, err := exec.LookPath("go") - - return err == nil -} - -func updateUsingGo() error { - cmd := exec.Command("go", "get", "-u", "github.com/devhindo/x/src/cli/cmd") - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - if err != nil { - fmt.Println(err) - return err - } - fmt.Println("CLI updated successfully!") - return nil -} - -//TODO: x update -v 1.1.1 (default for v:latest) diff --git a/src/x/cmd/version.go b/src/x/cmd/version.go deleted file mode 100644 index 5158fcc..0000000 --- a/src/x/cmd/version.go +++ /dev/null @@ -1,22 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -func init() { - rootCmd.AddCommand(versionCmd) -} - -//TODO: make it for -v and --version too - -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print the version of the CLI", - Long: `Print the version of the CLI`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println(version) - }, -} \ No newline at end of file diff --git a/src/x/env/load.go b/src/x/env/load.go deleted file mode 100644 index cd0d1e8..0000000 --- a/src/x/env/load.go +++ /dev/null @@ -1,19 +0,0 @@ -package env -/* - -import ( - "github.com/joho/godotenv" - "os" -) - -func Load() { - // Set Twitter API Key - err := godotenv.Load(".env") - if err != nil { - panic(err) - } - - os.Getenv("TWITTER_API_KEY") - os.Getenv("TWITTER_API_SECRET_KEY") -} -*/ \ No newline at end of file diff --git a/src/x/go.mod b/src/x/go.mod deleted file mode 100644 index 9e5a6e1..0000000 --- a/src/x/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module github.com/devhindo/x/src/x - -go 1.22.2 - -require github.com/google/uuid v1.3.1 - -require ( - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect -) diff --git a/src/x/go.sum b/src/x/go.sum deleted file mode 100644 index 779870e..0000000 --- a/src/x/go.sum +++ /dev/null @@ -1,12 +0,0 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/x/gotwi/post.go b/src/x/gotwi/post.go deleted file mode 100644 index de084ef..0000000 --- a/src/x/gotwi/post.go +++ /dev/null @@ -1,37 +0,0 @@ -package gotwi -/* -import ( - "github.com/michimani/gotwi" - "github.com/michimani/gotwi/tweet/managetweet" - "github.com/michimani/gotwi/tweet/managetweet/types" - "fmt" - "context" -) - -func PostTweet(t string) (string, error) { - - in := &gotwi.NewClientWithAccessTokenInput{ - AccessToken: "#", - } - - c, err := gotwi.NewClientWithAccessToken(in) - - if err != nil { - fmt.Println(err) - } else { - fmt.Println(c, "done") - } - - tweet := &types.CreateInput { - Text: gotwi.String(t), - } - - res, err := managetweet.Create(context.Background(), c, tweet) - - if err != nil { - return "", err - } - - return gotwi.StringValue(res.Data.ID), nil -} -*/ \ No newline at end of file diff --git a/src/x/help/help.go b/src/x/help/help.go deleted file mode 100644 index 33f6aa8..0000000 --- a/src/x/help/help.go +++ /dev/null @@ -1,25 +0,0 @@ -package help - -import ( - "fmt" -) - -func Help() { - fmt.Println() - fmt.Println("interact with x (twitter) from terminal.") - fmt.Println() - fmt.Println("USAGE") - fmt.Println(" x ") - fmt.Println() - fmt.Println("Commands") - fmt.Println(" -h show this help") - fmt.Println(" auth start authorizing your X account") - fmt.Println(" auth --url get auth url if it didn't open browser after running 'x auth'") - fmt.Println(" auth -v verify authorization after running 'x auth'") - fmt.Println(" -t \"text\" post a tweet") - fmt.Println(" -v show version") - fmt.Println(" -c clear authorized account") - fmt.Println() - fmt.Println("LEARN MORE") - fmt.Println(" Cheack source code at: https://github.com/devhindo/x") -} \ No newline at end of file diff --git a/src/x/lock/key.go b/src/x/lock/key.go deleted file mode 100644 index a0415eb..0000000 --- a/src/x/lock/key.go +++ /dev/null @@ -1,77 +0,0 @@ -package lock - -import ( - "encoding/base64" - "fmt" - "io" - "os" - "path/filepath" - - "github.com/google/uuid" -) - -func GenerateLicenseKey() (string, error) { - uuid := uuid.New() - uuidBytes := uuid[:] - licenseKeyBytes := append(uuidBytes) - licenseKey := base64.StdEncoding.EncodeToString(licenseKeyBytes) - return licenseKey, nil -} - -func WriteLicenseKeyToFile(licenseKey string) error { - homeDir, err := os.UserHomeDir() - if err != nil { - return fmt.Errorf("Error getting user home directory: %v", err) - } - - licenseFilePath := filepath.Join(homeDir, ".tempxcli") - licenseFile, err := os.Create(licenseFilePath) - if err != nil { - return fmt.Errorf("Error creating license file: %v", err) - } - defer licenseFile.Close() - - _, err = licenseFile.WriteString(licenseKey) - if err != nil { - return fmt.Errorf("Error writing license key to file: %v", err) - } - - return nil -} - -func ReadLicenseKeyFromFile() (string, error) { - homeDir, err := os.UserHomeDir() - if err != nil { - return "", fmt.Errorf("Error getting user home directory: %v", err) - } - - licenseFilePath := filepath.Join(homeDir, ".tempxcli") - licenseFile, err := os.Open(licenseFilePath) - if err != nil { - return "", fmt.Errorf("Error opening license file: %v", err) - } - defer licenseFile.Close() - - licenseFileBytes, err := io.ReadAll(licenseFile) - if err != nil { - return "", fmt.Errorf("Error reading license file: %v", err) - } - - licenseKey := string(licenseFileBytes) - return licenseKey, nil -} - -func ClearLicenseFile() error { - homeDir, err := os.UserHomeDir() - if err != nil { - return fmt.Errorf("Error getting user home directory: %v", err) - } - - licenseFilePath := filepath.Join(homeDir, ".tempxcli") - err = os.Remove(licenseFilePath) - if err != nil { - return fmt.Errorf("Error deleting license file: %v", err) - } - - return nil -} \ No newline at end of file diff --git a/src/x/main.go b/src/x/main.go deleted file mode 100644 index 9abc3fd..0000000 --- a/src/x/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import "github.com/devhindo/x/src/cli/cmd" - - -func main() { - //x.Run() - cmd.Execute() -} diff --git a/src/x/release.md b/src/x/release.md deleted file mode 100644 index 577dcbf..0000000 --- a/src/x/release.md +++ /dev/null @@ -1,4 +0,0 @@ -# First Release - -- tweet from terminal using `x -t "first tweet from terminal!"` -- check `README.md` for more info diff --git a/src/x/tweet/future.go b/src/x/tweet/future.go deleted file mode 100644 index f39c27b..0000000 --- a/src/x/tweet/future.go +++ /dev/null @@ -1,170 +0,0 @@ -package tweet - -// x t "hi" 5h6m7s - -import ( - "fmt" - "log" - "strconv" - "strings" - "bytes" - "encoding/json" - "io" - "net/http" - - "github.com/devhindo/x/src/cli/lock" -) - -type FutureTweet struct { - License string `json:"license"` - Tweet string `json:"tweet"` - Hours int `json:"hours"` - Minutes int `json:"minutes"` -} - -func PostFutureTweet(c []string) { - - url := "http://localhost:3000/api/tweets/future" - - // x t "hi" 5h6m7s - - tweetText, tweetTime, err := handleFutureTweetArgs(c) - if err != nil { - log.SetFlags(0) - log.Fatal(err) - } - - hrs, mins, err := handleTweetTime(tweetTime) - - if err != nil { - log.SetFlags(0) - log.Fatal(err) - } - - license, err := lock.ReadLicenseKeyFromFile() - - if err != nil { - fmt.Println("you are not authenticated | try 'x auth'") - return - } - - tweet := FutureTweet{ - License: license, - Tweet: tweetText, - Hours: hrs, - Minutes: mins, - } - - err = postFutureTweetToServer(url, tweet) - - if err != nil { - log.SetFlags(0) - log.Fatal(err) - } -} - - -func postFutureTweetToServer(url string, t FutureTweet) error { - fmt.Println("unmarchalling") - jsonBytes, err := json.Marshal(t) - if err != nil { - panic(err) - } - - resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBytes)) - fmt.Println("posting") - if err != nil { - return fmt.Errorf("can't reach server to post a tweet") - } - fmt.Println("before defer") - defer resp.Body.Close() - fmt.Println("after defer") - _, err = io.ReadAll(resp.Body) - if err != nil { - //Failed to read response. - return fmt.Errorf("can't read server response") - } - - var r response - - - //Convert bytes to String and print - fmt.Println(r.Message) - - return nil -} - -func handleFutureTweetArgs(c []string) (string, string, error) { - if len(c) < 3 { - return "", "", fmt.Errorf("no tweet given | try 'x f --help'") - } - - if c[2] == "-h" || c[2] == "--help" { - fmt.Println("post future tweets") - fmt.Println("using delayed times in this form") - fmt.Println("x f \"hi\" 2h3m") - fmt.Println("h -> hours") - fmt.Println("m -> minutes") - fmt.Println("this tweet would be scheduled to be posted after 2 hours and 3 minuets") - return c[2], c[3], nil - } - - if len(c) < 4 { - fmt.Println("No schedule time is given | try 'x f --help'") - } - return c[2], c[3], nil -} - -func handleTweetTime(t string) (int, int, error) { - hrs := 0 - mins := 0 - - // Check if the string is empty - if len(t) == 0 { - return hrs, mins, fmt.Errorf("empty time string") - } - - containsH := strings.Contains(t, "h") - containsM := strings.Contains(t, "m") - - if containsH && containsM { - // Split the string into hours and minutes - timeParts := strings.Split(t, "h") - if len(timeParts) != 2 { - return hrs, mins, fmt.Errorf("invalid time string") - } - - hours, err := strconv.Atoi(timeParts[0]) - if err != nil { - return hrs, mins, fmt.Errorf("invalid time string") - } - - minutes, err := strconv.Atoi(strings.TrimSuffix(timeParts[1], "m")) - if err != nil { - return hrs, mins, fmt.Errorf("invalid time string") - } - - hrs = hours - mins = minutes - } else if containsH { - // Extract the hours from the string - hours, err := strconv.Atoi(strings.TrimSuffix(t, "h")) - if err != nil { - return hrs, mins, fmt.Errorf("invalid time string") - } - - hrs = hours - } else if containsM { - // Extract the minutes from the string - minutes, err := strconv.Atoi(strings.TrimSuffix(t, "m")) - if err != nil { - return hrs, mins, fmt.Errorf("invalid time string") - } - - mins = minutes - } else { - return hrs, mins, fmt.Errorf("invalid time string") - } - - return hrs, mins, nil -} \ No newline at end of file diff --git a/src/x/tweet/post.go b/src/x/tweet/post.go deleted file mode 100644 index 28acde2..0000000 --- a/src/x/tweet/post.go +++ /dev/null @@ -1,74 +0,0 @@ -package tweet - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - - "github.com/devhindo/x/src/cli/lock" -) - -type Tweet struct { - License string `json:"license"` - Tweet string `json:"tweet"` -} - -func POST_tweet(t string) { - - license, err := lock.ReadLicenseKeyFromFile() - - if err != nil { - fmt.Println("you are not authenticated | try 'x auth'") - os.Exit(1) - } - - url := "https://x-blush.vercel.app/api/tweets/post" - tweet := Tweet{ - License: license, - Tweet: t, - } - - postT(url, tweet) -} - -type response struct { - Message string `json:"message"` -} - -func postT(url string, t Tweet) { - - jsonBytes, err := json.Marshal(t) - if err != nil { - panic(err) - } - - resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBytes)) - if err != nil { - fmt.Println("can't reach server to post a tweet") - os.Exit(0) - } - - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - //Failed to read response. - panic(err) - } - - - var r response - err = json.Unmarshal(body, &r) - if err != nil { - fmt.Printf("Failed to unmarshal response") - //Failed to unmarshal response. - return - } - - - //Convert bytes to String and print - fmt.Println(r.Message) -} \ No newline at end of file diff --git a/src/x/utils/crud.go b/src/x/utils/crud.go deleted file mode 100644 index ab2f68d..0000000 --- a/src/x/utils/crud.go +++ /dev/null @@ -1,83 +0,0 @@ -package utils - -import ( - "bytes" - "fmt" - "io" - "net/http" - "log" - "encoding/json" - -) - -type User struct { - State string `json:"state"` - Auth_URL string `json:"auth_url"` - Code_verifier string `json:"code_verifier"` - Code_challenge string `json:"code_challenge"` -} - -func POST(url string, user User) { - // Create a new HTTP request object. - req, err := http.NewRequest("POST", url, nil) - if err != nil { - fmt.Println(err) - return - } - - jsonBytes, err := json.Marshal(user) - if err != nil { - fmt.Println(err) - return - } - - - req.Body = io.NopCloser(bytes.NewBuffer(jsonBytes)) - - req.Header.Set("Content-Type", "application/json") - - // Send the request - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - } - defer resp.Body.Close() - - // Handle the response - body, err := io.ReadAll(resp.Body) - if err != nil { - log.Fatal(err) - } - fmt.Println(string(body)) -} - -func GET(url string) { - // Create a new HTTP request object. - req, err := http.NewRequest("GET", url, nil) - if err != nil { - fmt.Println(err) - return - } - - // Send the request and receive the response. - resp, err := http.DefaultClient.Do(req) - if err != nil { - fmt.Println(err) - return - } - - // Read the response body. - body, err := io.ReadAll(resp.Body) - if err != nil { - fmt.Println(err) - return - } - - // Close the response body. - defer resp.Body.Close() - - // Print the response body. - fmt.Println(string(body)) - -} diff --git a/src/x/utils/open.go b/src/x/utils/open.go deleted file mode 100644 index c93ef5c..0000000 --- a/src/x/utils/open.go +++ /dev/null @@ -1,33 +0,0 @@ -package utils - -import ( - "os/exec" - "runtime" -) - -func OpenBrowser(url string) { - err := open(url) - if err != nil { - panic(err) - } -} - -func open(url string) error { - - // source : https://gist.github.com/sevkin/9798d67b2cb9d07cb05f89f14ba682f8 - - var cmd string - var args []string - - switch runtime.GOOS { - case "windows": - cmd = "rundll32" - args = []string{"url.dll,FileProtocolHandler"} - case "darwin": - cmd = "open" - default: // "linux", "freebsd", "openbsd", "netbsd" - cmd = "xdg-open" - } - args = append(args, url) - return exec.Command(cmd, args...).Start() -} \ No newline at end of file diff --git a/src/x/x/args.go b/src/x/x/args.go deleted file mode 100644 index 0fc3cab..0000000 --- a/src/x/x/args.go +++ /dev/null @@ -1,100 +0,0 @@ -package x - -import ( - "os" - "fmt" - - "github.com/devhindo/x/src/cli/help" - "github.com/devhindo/x/src/cli/auth" - "github.com/devhindo/x/src/cli/tweet" - "github.com/devhindo/x/src/cli/clear" - -) - -func HandleArgs() { - checkArgs() - switch os.Args[1] { - case "help": - checkArgsequals2() - help.Help() - case "--help": - checkArgsequals2() - help.Help() - case "-h": - checkArgsequals2() - help.Help() - case "auth": - if len(os.Args) == 2 { - auth.Auth() - } else if len(os.Args) == 3 && (os.Args[2] == "--verify" || os.Args[2] == "-v") { - auth.Verify() - } else if len(os.Args) == 3 && (os.Args[2] == "--clear" || os.Args[2] == "-c") { - clear.StartOver() - } else if len(os.Args) == 3 && os.Args[2] == "--url" { - auth.Get_url_db() - } else { - fmt.Println("Unknown command | try 'x help'") - os.Exit(0) - } - case "t": - checkTweetArgs() - tweet.POST_tweet(os.Args[2]) - case "-t": - checkTweetArgs() - tweet.POST_tweet(os.Args[2]) - case "tweet": - checkTweetArgs() - tweet.POST_tweet(os.Args[2]) - case "version": - checkArgsequals2() - Version() - case "v": - checkArgsequals2() - Version() - case "f": // x -t "hi" 5h6m7s - checkArgsequals2() - tweet.PostFutureTweet(os.Args) - case "-f": - checkArgsequals2() - tweet.PostFutureTweet(os.Args) - default: - - if len(os.Args) != 2 { - fmt.Println("Unknown command | try 'x help'") - os.Exit(0) - } - - tweet.POST_tweet(os.Args[1]) - - } -} - -func checkTweetArgs() { - if len(os.Args) < 3 { - fmt.Println("No tweet given | try 'x help'") - os.Exit(0) - } -} - -func checkArgs() { - if len(os.Args) < 2 { - fmt.Println("No command given | try 'x help'") - os.Exit(0) - } -} - -func checkArgsequals2() { - if len(os.Args) != 2 { - fmt.Println("Unknown command | try 'x help'") - os.Exit(0) - } -} - -func checkFutureTweetArgs() { - if len(os.Args) < 4 { - fmt.Println("No tweet given | try 'x help'") - os.Exit(0) - } - -} - diff --git a/src/x/x/run.go b/src/x/x/run.go deleted file mode 100644 index 6ee0b4e..0000000 --- a/src/x/x/run.go +++ /dev/null @@ -1,10 +0,0 @@ -package x - -import ( - "github.com/devhindo/x/src/cli/cmd" -) - -func Run() { - // HandleArgs() - cmd.Execute() -} \ No newline at end of file diff --git a/src/x/x/version.go b/src/x/x/version.go deleted file mode 100644 index 950441d..0000000 --- a/src/x/x/version.go +++ /dev/null @@ -1,7 +0,0 @@ -package x - -import "fmt" - -func Version() { - fmt.Println("x CLI v1.1.2") -} From 9ad32a903f957ea01be147a05c21991762563df2 Mon Sep 17 00:00:00 2001 From: devhindo Date: Mon, 15 Apr 2024 19:04:52 +0200 Subject: [PATCH 8/9] removed x without args --- src/cli/x/args.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/x/args.go b/src/cli/x/args.go index 0fc3cab..08c8ba4 100644 --- a/src/cli/x/args.go +++ b/src/cli/x/args.go @@ -64,7 +64,7 @@ func HandleArgs() { os.Exit(0) } - tweet.POST_tweet(os.Args[1]) + //tweet.POST_tweet(os.Args[1]) x tweetText } } From d2970d10abae13635f46aab4cf7aceb6e34298bc Mon Sep 17 00:00:00 2001 From: devhindo Date: Thu, 18 Apr 2024 09:43:47 +0200 Subject: [PATCH 9/9] feat: added --- src/cli/cmd/future.go | 1 - src/cli/cmd/tweet.go | 98 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/cli/cmd/tweet.go diff --git a/src/cli/cmd/future.go b/src/cli/cmd/future.go index 3e86c58..a455377 100644 --- a/src/cli/cmd/future.go +++ b/src/cli/cmd/future.go @@ -9,7 +9,6 @@ import ( var ( wait string date string - time string content string ) diff --git a/src/cli/cmd/tweet.go b/src/cli/cmd/tweet.go new file mode 100644 index 0000000..3b3b9bd --- /dev/null +++ b/src/cli/cmd/tweet.go @@ -0,0 +1,98 @@ +package cmd + +import ( + "fmt" + "regexp" + "strconv" + "time" + + "github.com/spf13/cobra" +) + +/* + x tweet -c "hi" -w 2d5h6m7s -d 2020-12-12 -t 3:34pm +*/ + +func init() { + rootCmd.AddCommand(tweetCmd) + + tweetCmd.Flags().StringP("content", "c", "", "Tweet message") + tweetCmd.Flags().StringP("wait", "w", "0", "When to post the tweet") + tweetCmd.Flags().StringP("date", "d", "0", "Date to post the tweet") + tweetCmd.Flags().StringP("time", "t", "0", "Time to post the tweet") + // tweetCmd.Flags().BoolP("media", "m", false, "Add media to the tweet") TODO: feat: post media content +} + +var ( + tweetCmd = &cobra.Command{ + Use: "tweet", + Short: "Post a tweet", + Long: `Post a tweet.`, + RunE: tweetCmdRun, + } +) + +func tweetCmdRun(cmd *cobra.Command, args []string) error { + content, err := cmd.Flags().GetString("content") + if err != nil { + err = fmt.Errorf("error getting content flag: %v", err) + return err + } + + wait, err := cmd.Flags().GetString("wait") + h, m, s, ms, err := handleWaitArg(wait) + if err != nil { + err = fmt.Errorf("error getting wait flag: %v", err) + return err + } + fmt.Println(h, m, s, ms) + + fmt.Println(content) + + return nil +} + +func calcWaitingTime() { + UTCtime := time.Now().UTC() + fmt.Println(UTCtime) +} + +// sra7a ai generated, I wouldn't be able to do that by myself +func handleWaitArg(wait string) (int, int, int, int, error) { + // Define the regular expression patterns for each unit + patterns := map[string]*regexp.Regexp{ + "d": regexp.MustCompile(`(\d*)d`), + "h": regexp.MustCompile(`(\d*)h`), + "m": regexp.MustCompile(`(\d*)m`), + "s": regexp.MustCompile(`(\d*)s`), + } + + // Initialize the time units + var days, hours, minutes, seconds int + + // Extract each unit from the argument + for unit, pattern := range patterns { + match := pattern.FindStringSubmatch(wait) + if match != nil { + value, err := strconv.Atoi(match[1]) + if err != nil { + return 0, 0, 0, 0, fmt.Errorf("invalid waiting time format: %s", wait) + } + + switch unit { + case "d": + days = value + case "h": + hours = value + case "m": + minutes = value + case "s": + seconds = value + } + } + } + + return days, hours, minutes, seconds, nil +} + +