diff --git a/commits.go b/gh-recon/commits.go similarity index 74% rename from commits.go rename to gh-recon/commits.go index 8993a10..27d9d68 100644 --- a/commits.go +++ b/gh-recon/commits.go @@ -1,19 +1,13 @@ -package main +package ghrecon import ( - "context" "fmt" - "github.com/charmbracelet/log" "github.com/google/go-github/v72/github" ) -func commits(client *github.Client, ctx context.Context, username string) { - // Commits - fmt.Println( - GreyStyle.Render("[")+GreenStyle.Render("+")+GreyStyle.Render("]"), - GreyStyle.Render("Commits:\n"), - ) +func (r Recon) Commits(username string) { + PrintTitle("šŸ™ Commits") seenNames := make(map[string]struct{}) seenEmails := make(map[string]struct{}) @@ -22,13 +16,13 @@ func commits(client *github.Client, ctx context.Context, username string) { collect := func(order string) error { opts := &github.SearchOptions{ Sort: "author-date", - Order: order, // "desc" ou "asc" + Order: order, ListOptions: github.ListOptions{PerPage: 100}, } for page := 1; page <= 10; page++ { opts.ListOptions.Page = page - result, resp, err := client.Search.Commits( - ctx, + result, resp, err := r.client.Search.Commits( + r.ctx, fmt.Sprintf("author:%s", username), opts, ) @@ -69,11 +63,9 @@ func commits(client *github.Client, ctx context.Context, username string) { } if err := collect("desc"); err != nil { - log.Error("Failed to fetch commits", "err", err) - return + r.logger.Error("Failed to fetch commits", "err", err, "order", "desc") } if err := collect("asc"); err != nil { - log.Error("Failed to fetch commits", "err", err) - return + r.logger.Error("Failed to fetch commits", "err", err, "order", "asc") } } diff --git a/gh-recon/keys.go b/gh-recon/keys.go new file mode 100644 index 0000000..a6d7447 --- /dev/null +++ b/gh-recon/keys.go @@ -0,0 +1,111 @@ +package ghrecon + +import ( + "fmt" +) + +func (r Recon) SshKeys(username string) { + sshKeys, resp, err := r.client.Users.ListKeys(r.ctx, username, nil) + if err != nil { + r.logger.Error("Failed to fetch ssh keys", "err", err) + } else if len(sshKeys) == 0 { + PrintTitle("šŸ”‘ SSH Keys") + r.logger.Info("No SSH Keys found\n") + } else { + PrintTitle("šŸ”‘ SSH Keys") + for i, key := range sshKeys { + PrintInfo("Key n°", fmt.Sprintf("%d", i)) + PrintInfo("ID", fmt.Sprintf("%d", key.GetID())) + PrintInfo("URL", key.GetURL()) + PrintInfo("Title", key.GetTitle()) + PrintInfo("Created At", key.GetCreatedAt().String()) + PrintInfo("Key", key.GetKey()) + PrintInfo("Read Only", fmt.Sprintf("%t", key.GetReadOnly())) + PrintInfo("Verified", fmt.Sprintf("%t", key.GetVerified())) + PrintInfo("Last Used", key.GetLastUsed().String()) + PrintInfo("Added By", key.GetAddedBy()) + fmt.Println() + } + } + WaitForRateLimit(resp) +} + +func (r Recon) GpgKeys(username string) { + gpgKeys, resp, err := r.client.Users.ListGPGKeys(r.ctx, username, nil) + if err != nil { + r.logger.Error("Failed to fetch user's gpg keys", "err", err) + } else if len(gpgKeys) == 0 { + PrintTitle("šŸ—ļø GPG Keys") + r.logger.Info("No GPG Keys found\n") + } else { + PrintTitle("šŸ—ļø GPG Keys") + for i, key := range gpgKeys { + PrintInfo("Key n°", fmt.Sprintf("%d", i)) + PrintInfo("ID", fmt.Sprintf("%d", key.GetID())) + PrintInfo("Key ID", key.GetKeyID()) + PrintInfo("Public Key", key.GetPublicKey()) + PrintInfo("Created At", key.GetCreatedAt().String()) + PrintInfo("Expires At", key.GetExpiresAt().String()) + PrintInfo("Can Sign", fmt.Sprintf("%t", key.GetCanSign())) + PrintInfo("Can Encrypt Comms", fmt.Sprintf("%t", key.GetCanEncryptComms())) + PrintInfo("Can Encrypt Storage", fmt.Sprintf("%t", key.GetCanEncryptStorage())) + PrintInfo("Can Certify", fmt.Sprintf("%t", key.GetCanCertify())) + PrintInfo("Primary Key ID", fmt.Sprintf("%d", key.GetPrimaryKeyID())) + PrintInfo("Raw Key", key.GetRawKey()) + PrintInfo("Emails", fmt.Sprintf("%d", len(key.Emails))) + for j, email := range key.Emails { + PrintInfo(" Email n°", fmt.Sprintf("%d", j)) + PrintInfo(" Email", email.GetEmail()) + PrintInfo(" Verified", fmt.Sprintf("%t", email.GetVerified())) + } + PrintInfo("Subkeys", fmt.Sprintf("%d", len(key.Subkeys))) + for j, subkey := range key.Subkeys { + PrintInfo(" Subkey n°", fmt.Sprintf("%d", j)) + PrintInfo(" Subkey ID", fmt.Sprintf("%d", subkey.GetID())) + PrintInfo(" Subkey Key ID", subkey.GetKeyID()) + PrintInfo(" Subkey Created At", subkey.GetCreatedAt().String()) + PrintInfo(" Subkey Expires At", subkey.GetExpiresAt().String()) + PrintInfo(" Subkey Can Sign", fmt.Sprintf("%t", subkey.GetCanSign())) + PrintInfo( + " Subkey Can Encrypt Comms", + fmt.Sprintf("%t", subkey.GetCanEncryptComms()), + ) + PrintInfo( + " Subkey Can Encrypt Storage", + fmt.Sprintf("%t", subkey.GetCanEncryptStorage()), + ) + PrintInfo(" Subkey Can Certify", fmt.Sprintf("%t", subkey.GetCanCertify())) + PrintInfo(" Subkey Primary Key ID", fmt.Sprintf("%d", subkey.GetPrimaryKeyID())) + PrintInfo(" Subkey Raw Key", subkey.GetRawKey()) + PrintInfo(" Subkey Public Key", subkey.GetPublicKey()) + } + fmt.Println() + } + } + WaitForRateLimit(resp) +} + +func (r Recon) SshSigningKeys(username string) { + signingKeys, resp, err := r.client.Users.ListSSHSigningKeys( + r.ctx, + username, + nil, + ) + if err != nil { + r.logger.Error("Failed to fetch user's ssh signing keys", "err", err) + } else if len(signingKeys) == 0 { + PrintTitle("šŸ“ SSH Signing Keys") + r.logger.Info("No SSH Signing Keys found\n") + } else { + PrintTitle("šŸ“ SSH Signing Keys") + for i, key := range signingKeys { + PrintInfo("Key n°", fmt.Sprintf("%d", i)) + PrintInfo("ID", fmt.Sprintf("%d", key.GetID())) + PrintInfo("Key", key.GetKey()) + PrintInfo("Title", key.GetTitle()) + PrintInfo("Created At", key.GetCreatedAt().String()) + fmt.Println() + } + } + WaitForRateLimit(resp) +} diff --git a/gh-recon/main.go b/gh-recon/main.go new file mode 100644 index 0000000..08ae099 --- /dev/null +++ b/gh-recon/main.go @@ -0,0 +1,22 @@ +package ghrecon + +import ( + "context" + + "github.com/charmbracelet/log" + "github.com/google/go-github/v72/github" +) + +type Recon struct { + client *github.Client + logger *log.Logger + ctx context.Context +} + +func NewRecon(client *github.Client, logger *log.Logger, ctx context.Context) *Recon { + return &Recon{ + client: client, + logger: logger, + ctx: ctx, + } +} diff --git a/gh-recon/orgs.go b/gh-recon/orgs.go new file mode 100644 index 0000000..e4d1e70 --- /dev/null +++ b/gh-recon/orgs.go @@ -0,0 +1,34 @@ +package ghrecon + +import ( + "fmt" +) + +func (r Recon) Orgs(username string) { + orgs, resp, err := r.client.Organizations.List(r.ctx, username, nil) + if err != nil { + r.logger.Error("Failed to fetch organizations", "err", err) + } else if len(orgs) == 0 { + PrintTitle("šŸ¢ Organizations") + r.logger.Info("No Organizations found\n") + } else { + PrintTitle("šŸ¢ Organizations") + for i, org := range orgs { + PrintInfo("Orgs n°", fmt.Sprintf("%d", i)) + PrintInfo("Login", org.GetLogin()) + PrintInfo("ID", fmt.Sprintf("%d", org.GetID())) + PrintInfo("Node ID", org.GetNodeID()) + PrintInfo("URL", org.GetURL()) + PrintInfo("Repos URL", org.GetReposURL()) + PrintInfo("Events URL", org.GetEventsURL()) + PrintInfo("Hooks URL", org.GetHooksURL()) + PrintInfo("Issues URL", org.GetIssuesURL()) + PrintInfo("Members URL", org.GetMembersURL()) + PrintInfo("Public Members URL", org.GetPublicMembersURL()) + PrintInfo("Avatar URL", org.GetAvatarURL()) + PrintInfo("Description", org.GetDescription()) + fmt.Println() + } + } + WaitForRateLimit(resp) +} diff --git a/gh-recon/socials.go b/gh-recon/socials.go new file mode 100644 index 0000000..2638cbc --- /dev/null +++ b/gh-recon/socials.go @@ -0,0 +1,40 @@ +package ghrecon + +import ( + "encoding/json" + "fmt" +) + +func (r Recon) Socials(username string) { + resp, err := FetchGitHubAPI(r.client, "", "/users/"+username+"/social_accounts") + if err != nil { + r.logger.Error("Failed to fetch socials", "err", err) + return + } + + type SocialAccount struct { + Provider string `json:"provider"` + URL string `json:"url"` + } + type SocialAccounts []SocialAccount + + var socialAccounts SocialAccounts + err = json.Unmarshal(resp, &socialAccounts) + if err != nil { + r.logger.Error("Failed to unmarshal socials", "err", err) + return + } + + if len(socialAccounts) == 0 { + PrintTitle("🐄 Socials") + PrintTitle("No Socials found\n") + } else { + PrintTitle("🐄 Socials") + for i, account := range socialAccounts { + PrintInfo("Social n°", fmt.Sprintf("%d", i)) + PrintInfo("Provider", account.Provider) + PrintInfo("URL", account.URL) + fmt.Println() + } + } +} diff --git a/user.go b/gh-recon/user.go similarity index 70% rename from user.go rename to gh-recon/user.go index 8f4e228..688bb2a 100644 --- a/user.go +++ b/gh-recon/user.go @@ -1,38 +1,19 @@ -package main +package ghrecon import ( - "context" "fmt" - "os" - - "github.com/charmbracelet/log" - "github.com/google/go-github/v72/github" ) -func userInfo(client *github.Client, ctx context.Context, username string) { - fmt.Println( - GreyStyle.Render("[")+ - GreenStyle.Render("-")+ - GreyStyle.Render("]"), - GreyStyle.Render("Fetching user info...\n"), - ) - - user, resp, err := client.Users.Get(ctx, username) +func (r Recon) User(username string) { + user, resp, err := r.client.Users.Get(r.ctx, username) if resp.StatusCode == 404 { - fmt.Println( - GreyStyle.Render("[")+ - RedStyle.Render("x")+ - GreyStyle.Render("]"), - GreyStyle.Render("Error:"), - RedStyle.Render("User not found"), - ) - os.Exit(1) + r.logger.Fatal("User not found") } if err != nil { - log.Error("Failed to fetch user's information", "err", err) - os.Exit(1) + r.logger.Fatal("Failed to fetch user's information", "err", err) } + PrintTitle("šŸ‘¤ User informations") PrintInfo("Username", user.GetLogin()) PrintInfo("ID", fmt.Sprintf("%d", user.GetID())) PrintInfo("Avatar URL", user.GetAvatarURL()) diff --git a/utils.go b/gh-recon/utils.go similarity index 84% rename from utils.go rename to gh-recon/utils.go index 0e5690c..5f25532 100644 --- a/utils.go +++ b/gh-recon/utils.go @@ -1,4 +1,4 @@ -package main +package ghrecon import ( "fmt" @@ -22,12 +22,12 @@ var ( RedStyle = lipgloss.NewStyle().Foreground(Red) ) -func header() { +func Header() { asciiArt := " __ \n ___ _/ / _______ _______ ___ \n / _ `/ _ \\/ __/ -_) __/ _ \\/ _ \\\n \\_, /_//_/_/ \\__/\\__/\\___/_//_/\n/___/ " fmt.Println(GreyStyle.Render(lipgloss.JoinVertical(lipgloss.Right, asciiArt, "@anotherhadi\n"))) } -func parseUsername(username string) error { +func ParseUsername(username string) error { if username == "" { return fmt.Errorf("username is required") } @@ -83,11 +83,20 @@ func FetchGitHubAPI(github *github.Client, token, path string) ([]byte, error) { return bodyBytes, nil } +func PrintTitle(title string) { + style := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#7287fd")) + fmt.Println(style.Render(title) + "\n") +} + func PrintInfo(key, value string, more ...string) { - if value == "" || value == "0" || value == "0001-01-01 00:00:00 +0000 UTC" { + if value == "" || value == "0001-01-01 00:00:00 +0000 UTC" { return } - fmt.Printf("%s %s", GreyStyle.Render(key+":"), GreenStyle.Render(value)) + if strings.HasSuffix(key, "n°") { + fmt.Printf(" %s %s", GreyStyle.Render(key), value) + } else { + fmt.Printf(" %s %s", GreyStyle.Render(key+":"), value) + } if len(more) > 0 { fmt.Printf(" %s", GreyStyle.Render("("+strings.Join(more, ", ")+")")) } diff --git a/keys.go b/keys.go deleted file mode 100644 index a75c367..0000000 --- a/keys.go +++ /dev/null @@ -1,140 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/charmbracelet/log" - "github.com/google/go-github/v72/github" -) - -func keys(client *github.Client, ctx context.Context, username string) { - sshKeys, resp, err := client.Users.ListKeys(ctx, username, nil) - if err != nil { - log.Error("Failed to fetch user's keys", "err", err) - os.Exit(1) - } - if len(sshKeys) == 0 { - fmt.Println( - GreyStyle.Render("[")+ - RedStyle.Render("x")+ - GreyStyle.Render("]"), - GreyStyle.Render("No keys found for user "+username), - ) - } else { - fmt.Println( - GreyStyle.Render("[")+ - GreenStyle.Render("+")+ - GreyStyle.Render("]"), - GreyStyle.Render("Keys:\n"), - ) - } - for _, key := range sshKeys { - PrintInfo("ID", fmt.Sprintf("%d", key.GetID())) - PrintInfo("URL", key.GetURL()) - PrintInfo("Title", key.GetTitle()) - PrintInfo("Created At", key.GetCreatedAt().String()) - PrintInfo("Key", key.GetKey()) - PrintInfo("Read Only", fmt.Sprintf("%t", key.GetReadOnly())) - PrintInfo("Verified", fmt.Sprintf("%t", key.GetVerified())) - PrintInfo("Last Used", key.GetLastUsed().String()) - PrintInfo("Added By", key.GetAddedBy()) - fmt.Println() - } - WaitForRateLimit(resp) - - gpgKeys, resp, err := client.Users.ListGPGKeys(ctx, username, nil) - if err != nil { - log.Error("Failed to fetch user's GPG keys", "err", err) - } - - if len(gpgKeys) == 0 { - fmt.Println( - GreyStyle.Render("[")+ - RedStyle.Render("x")+ - GreyStyle.Render("]"), - GreyStyle.Render("No GPG keys found\n"), - ) - } else { - fmt.Println( - GreyStyle.Render("[")+ - GreenStyle.Render("+")+ - GreyStyle.Render("]"), - GreyStyle.Render("GPG Keys:\n"), - ) - } - for _, key := range gpgKeys { - PrintInfo("ID", fmt.Sprintf("%d", key.GetID())) - PrintInfo("Key ID", key.GetKeyID()) - PrintInfo("Public Key", key.GetPublicKey()) - PrintInfo("Created At", key.GetCreatedAt().String()) - PrintInfo("Expires At", key.GetExpiresAt().String()) - PrintInfo("Can Sign", fmt.Sprintf("%t", key.GetCanSign())) - PrintInfo("Can Encrypt Comms", fmt.Sprintf("%t", key.GetCanEncryptComms())) - PrintInfo("Can Encrypt Storage", fmt.Sprintf("%t", key.GetCanEncryptStorage())) - PrintInfo("Can Certify", fmt.Sprintf("%t", key.GetCanCertify())) - PrintInfo("Primary Key ID", fmt.Sprintf("%d", key.GetPrimaryKeyID())) - PrintInfo("Raw Key", key.GetRawKey()) - PrintInfo("Emails", fmt.Sprintf("%d", len(key.Emails))) - for _, email := range key.Emails { - PrintInfo("\tEmail", email.GetEmail()) - PrintInfo("\tVerified", fmt.Sprintf("%t", email.GetVerified())) - } - PrintInfo("Subkeys", fmt.Sprintf("%d", len(key.Subkeys))) - for _, subkey := range key.Subkeys { - PrintInfo("\tSubkey ID", fmt.Sprintf("%d", subkey.GetID())) - PrintInfo("\tSubkey Key ID", subkey.GetKeyID()) - PrintInfo("\tSubkey Created At", subkey.GetCreatedAt().String()) - PrintInfo("\tSubkey Expires At", subkey.GetExpiresAt().String()) - PrintInfo("\tSubkey Can Sign", fmt.Sprintf("%t", subkey.GetCanSign())) - PrintInfo( - "\tSubkey Can Encrypt Comms", - fmt.Sprintf("%t", subkey.GetCanEncryptComms()), - ) - PrintInfo( - "\tSubkey Can Encrypt Storage", - fmt.Sprintf("%t", subkey.GetCanEncryptStorage()), - ) - PrintInfo("\tSubkey Can Certify", fmt.Sprintf("%t", subkey.GetCanCertify())) - PrintInfo("\tSubkey Primary Key ID", fmt.Sprintf("%d", subkey.GetPrimaryKeyID())) - PrintInfo("\tSubkey Raw Key", subkey.GetRawKey()) - PrintInfo("\tSubkey Public Key", subkey.GetPublicKey()) - } - fmt.Println() - } - WaitForRateLimit(resp) - - signingKeys, resp, err := client.Users.ListSSHSigningKeys( - ctx, - username, - nil, - ) - if err != nil { - log.Error("Failed to fetch user's signing keys", "err", err) - } - if len(signingKeys) == 0 { - fmt.Println( - GreyStyle.Render("[")+ - RedStyle.Render("x")+ - GreyStyle.Render("]"), - GreyStyle.Render("No signing keys found\n"), - ) - } else { - fmt.Println( - GreyStyle.Render("[")+ - GreenStyle.Render("+")+ - GreyStyle.Render("]"), - GreyStyle.Render("Signing Keys:\n"), - ) - } - for _, key := range signingKeys { - PrintInfo("ID", fmt.Sprintf("%d", key.GetID())) - PrintInfo("Key", key.GetKey()) - PrintInfo("Title", key.GetTitle()) - PrintInfo("Created At", key.GetCreatedAt().String()) - fmt.Println() - } - - WaitForRateLimit(resp) -} diff --git a/main.go b/main.go index 3534a3a..5b56193 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "fmt" "os" + ghrecon "github.com/anotherhadi/gh-recon/gh-recon" "github.com/charmbracelet/log" "github.com/google/go-github/v72/github" ) @@ -21,7 +22,7 @@ func main() { fmt.Println("Please provide a username with the --username flag") os.Exit(1) } - err := parseUsername(username) + err := ghrecon.ParseUsername(username) if err != nil { log.Error("Invalid username", "err", err) os.Exit(1) @@ -38,10 +39,26 @@ func main() { ctx := context.Background() - header() - userInfo(client, ctx, username) - orgs(client, ctx, username) - keys(client, ctx, username) - socials(client, username) - commits(client, ctx, username) + styles := log.DefaultStyles() + styles.Levels[log.InfoLevel] = styles.Levels[log.InfoLevel].Foreground(ghrecon.Grey) + logger := log.NewWithOptions(os.Stderr, log.Options{ + ReportCaller: false, + ReportTimestamp: false, + }) + logger.SetStyles(styles) + + r := ghrecon.NewRecon( + client, + logger, + ctx, + ) + + ghrecon.Header() + r.User(username) + r.Orgs(username) + r.SshKeys(username) + r.GpgKeys(username) + r.SshSigningKeys(username) + r.Socials(username) + r.Commits(username) } diff --git a/orgs.go b/orgs.go deleted file mode 100644 index 5f91e9a..0000000 --- a/orgs.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "context" - "fmt" - - "github.com/charmbracelet/log" - "github.com/google/go-github/v72/github" -) - -func orgs(client *github.Client, ctx context.Context, username string) { - orgs, resp, err := client.Organizations.List(ctx, username, nil) - if err != nil { - log.Error("Failed to fetch user's organizations", "err", err) - } - if len(orgs) == 0 { - fmt.Println( - GreyStyle.Render("[")+ - RedStyle.Render("x")+ - GreyStyle.Render("]"), - GreyStyle.Render("No organizations found\n"), - ) - } else { - fmt.Println( - GreyStyle.Render("[")+ - GreenStyle.Render("+")+ - GreyStyle.Render("]"), - GreyStyle.Render("Organizations:\n"), - ) - } - for _, org := range orgs { - PrintInfo("Login", org.GetLogin()) - PrintInfo("ID", fmt.Sprintf("%d", org.GetID())) - PrintInfo("Node ID", org.GetNodeID()) - PrintInfo("URL", org.GetURL()) - PrintInfo("Repos URL", org.GetReposURL()) - PrintInfo("Events URL", org.GetEventsURL()) - PrintInfo("Hooks URL", org.GetHooksURL()) - PrintInfo("Issues URL", org.GetIssuesURL()) - PrintInfo("Members URL", org.GetMembersURL()) - PrintInfo("Public Members URL", org.GetPublicMembersURL()) - PrintInfo("Avatar URL", org.GetAvatarURL()) - PrintInfo("Description", org.GetDescription()) - fmt.Println() - } - WaitForRateLimit(resp) -} diff --git a/socials.go b/socials.go deleted file mode 100644 index 902f835..0000000 --- a/socials.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - - "github.com/charmbracelet/log" - "github.com/google/go-github/v72/github" -) - -func socials(client *github.Client, username string) { - resp, err := FetchGitHubAPI(client, "", "/users/"+username+"/social_accounts") - if err != nil { - log.Error("Failed to fetch user's social media accounts", "err", err) - } - - type SocialAccount struct { - Provider string `json:"provider"` - URL string `json:"url"` - } - type SocialAccounts []SocialAccount - - var socialAccounts SocialAccounts - err = json.Unmarshal(resp, &socialAccounts) - if err != nil { - log.Error("Failed to unmarshal social media accounts", "err", err) - } - if len(socialAccounts) == 0 { - fmt.Println( - GreyStyle.Render("[")+ - RedStyle.Render("x")+ - GreyStyle.Render("]"), - GreyStyle.Render("No social media accounts found\n"), - ) - } else { - fmt.Println( - GreyStyle.Render("[")+ - GreenStyle.Render("+")+ - GreyStyle.Render("]"), - GreyStyle.Render("Social Media Accounts:\n"), - ) - } - for _, account := range socialAccounts { - PrintInfo("Provider", account.Provider) - PrintInfo("URL", account.URL) - fmt.Println() - } -}