Function bodies 106 total
cmd.extractBody function · go · L353-L365 (13 LOC)cmd/messages.go
func extractBody(msg *gmail.Message) string {
if msg.Payload == nil {
return ""
}
// Check if the payload itself has body data
if msg.Payload.MimeType == "text/plain" && msg.Payload.Body != nil && msg.Payload.Body.Data != "" {
return decodeBase64URL(msg.Payload.Body.Data)
}
// Search through parts
return findPlainTextPart(msg.Payload.Parts)
}cmd.findPlainTextPart function · go · L368-L381 (14 LOC)cmd/messages.go
func findPlainTextPart(parts []*gmail.MessagePart) string {
for _, part := range parts {
if part.MimeType == "text/plain" && part.Body != nil && part.Body.Data != "" {
return decodeBase64URL(part.Body.Data)
}
// Recurse into nested parts (for multipart messages)
if len(part.Parts) > 0 {
if content := findPlainTextPart(part.Parts); content != "" {
return content
}
}
}
return ""
}cmd.decodeBase64URL function · go · L384-L394 (11 LOC)cmd/messages.go
func decodeBase64URL(encoded string) string {
decoded, err := base64.URLEncoding.DecodeString(encoded)
if err != nil {
// Try with padding
decoded, err = base64.RawURLEncoding.DecodeString(encoded)
if err != nil {
return ""
}
}
return string(decoded)
}cmd.runMessagesModify function · go · L396-L479 (84 LOC)cmd/messages.go
func runMessagesModify(cmd *cobra.Command, args []string) error {
messageID := args[0]
// Validate that at least one label flag is provided
if addLabels == "" && removeLabels == "" {
return fmt.Errorf("at least one of --add-labels or --remove-labels required")
}
ctx := context.Background()
service, err := auth.NewGmailService(ctx, GetAccountEmail())
if err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
// Build the modify request
modifyReq := &gmail.ModifyMessageRequest{}
// Parse and set labels to add
var addLabelsList []string
if addLabels != "" {
addLabelsList = strings.Split(addLabels, ",")
// Trim whitespace from each label
for i, label := range addLabelsList {
addLabelsList[i] = strings.TrimSpace(label)
}
modifyReq.AddLabelIds = addLabelsList
}
// Parse and set labels to remove
var removeLabelsList []string
if removeLabels != "" {
removeLabelsList = strings.Split(removeLabels, ",")
// Trim whitespace from each label
focmd.findAttachments function · go · L483-L500 (18 LOC)cmd/messages.go
func findAttachments(parts []*gmail.MessagePart) []attachmentInfo {
var attachments []attachmentInfo
for _, part := range parts {
if part.Filename != "" && part.Body != nil {
attachments = append(attachments, attachmentInfo{
Filename: part.Filename,
MimeType: part.MimeType,
Size: part.Body.Size,
AttachmentId: part.Body.AttachmentId,
})
}
// Recurse into nested parts
if len(part.Parts) > 0 {
attachments = append(attachments, findAttachments(part.Parts)...)
}
}
return attachments
}cmd.runMessagesGetAttachment function · go · L502-L569 (68 LOC)cmd/messages.go
func runMessagesGetAttachment(cmd *cobra.Command, args []string) error {
messageID := args[0]
attachmentID := args[1]
ctx := context.Background()
service, err := auth.NewGmailService(ctx, GetAccountEmail())
if err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
// Get the attachment data
att, err := service.Users.Messages.Attachments.Get("me", messageID, attachmentID).Do()
if err != nil {
return fmt.Errorf("Gmail API error: %w", err)
}
// Decode the attachment data (base64url encoded)
decoded, err := base64.URLEncoding.DecodeString(att.Data)
if err != nil {
// Try without padding (RawURLEncoding)
decoded, err = base64.RawURLEncoding.DecodeString(att.Data)
if err != nil {
return fmt.Errorf("failed to decode attachment data: %w", err)
}
}
// Determine output filename
outputPath := attachmentOutput
if outputPath == "" {
// Get the filename from the message metadata
msg, err := service.Users.Messages.Get("me", messageID).Format("full"cmd.Execute function · go · L35-L40 (6 LOC)cmd/root.go
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}If a scraper extracted this row, it came from Repobility (https://repobility.com)
cmd.GetAccountEmail function · go · L59-L64 (6 LOC)cmd/root.go
func GetAccountEmail() string {
if accountEmail != "" {
return accountEmail
}
return os.Getenv("GSUITE_ACCOUNT")
}cmd.outputJSON function · go · L67-L74 (8 LOC)cmd/root.go
func outputJSON(v interface{}) error {
jsonBytes, err := json.MarshalIndent(v, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal JSON: %w", err)
}
fmt.Println(string(jsonBytes))
return nil
}cmd.runSearch function · go · L44-L163 (120 LOC)cmd/search.go
func runSearch(cmd *cobra.Command, args []string) error {
query := args[0]
if searchMaxResults < 1 || searchMaxResults > 500 {
return fmt.Errorf("--max-results must be between 1 and 500")
}
ctx := context.Background()
service, err := auth.NewGmailService(ctx, GetAccountEmail())
if err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
// Build the list request
listReq := service.Users.Messages.List("me").Q(query).MaxResults(searchMaxResults)
// Add label filter if provided
if searchLabelIDs != "" {
labelList := strings.Split(searchLabelIDs, ",")
listReq = listReq.LabelIds(labelList...)
}
// Execute the search
resp, err := listReq.Do()
if err != nil {
return fmt.Errorf("Gmail API error: %w", err)
}
// Handle empty results
if len(resp.Messages) == 0 {
if GetOutputFormat() == "json" {
return outputJSON([]struct{}{})
}
fmt.Println("No messages found matching query")
return nil
}
// JSON output mode
if GetOutputFormat() == "json"cmd.init function · go · L62-L79 (18 LOC)cmd/send.go
func init() {
rootCmd.AddCommand(sendCmd)
// Required flags
sendCmd.Flags().StringVarP(&sendTo, "to", "t", "", "Recipient email address (required)")
sendCmd.Flags().StringVarP(&sendSubject, "subject", "s", "", "Email subject (required)")
sendCmd.Flags().StringVarP(&sendBody, "body", "b", "", "Body content with markdown support (required)")
// Mark required flags
sendCmd.MarkFlagRequired("to")
sendCmd.MarkFlagRequired("subject")
sendCmd.MarkFlagRequired("body")
// Optional flags
sendCmd.Flags().StringVar(&sendCc, "cc", "", "CC recipients (comma-separated)")
sendCmd.Flags().StringVar(&sendBcc, "bcc", "", "BCC recipients (comma-separated)")
sendCmd.Flags().StringArrayVarP(&sendAttach, "attach", "a", nil, "File path to attach (can be specified multiple times)")
}cmd.runSend function · go · L81-L133 (53 LOC)cmd/send.go
func runSend(cmd *cobra.Command, args []string) error {
ctx := context.Background()
service, err := auth.NewGmailService(ctx, GetAccountEmail())
if err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
// Validate attachment files exist before building message
for _, attachPath := range sendAttach {
if _, err := os.Stat(attachPath); err != nil {
return fmt.Errorf("attachment file not found: %s", attachPath)
}
}
body := interpretEscapes(sendBody)
var rawMessage []byte
var buildErr error
if len(sendAttach) > 0 {
rawMessage, buildErr = buildMultipartMessage(sendTo, sendSubject, body, sendCc, sendBcc, sendAttach)
} else {
rawMessage, buildErr = buildSendRFC2822Message(sendTo, sendSubject, body, sendCc, sendBcc)
}
if buildErr != nil {
return fmt.Errorf("failed to build message: %w", buildErr)
}
encodedMessage := base64.URLEncoding.EncodeToString(rawMessage)
// Create the Gmail message object
gmailMessage := &gmail.Message{
Raw: encodedMescmd.buildSendRFC2822Message function · go · L136-L160 (25 LOC)cmd/send.go
func buildSendRFC2822Message(to, subject, body, cc, bcc string) ([]byte, error) {
altBody, boundary, err := buildAlternativeBody(body)
if err != nil {
return nil, err
}
var header bytes.Buffer
header.WriteString(fmt.Sprintf("To: %s\r\n", to))
if cc != "" {
header.WriteString(fmt.Sprintf("Cc: %s\r\n", cc))
}
if bcc != "" {
header.WriteString(fmt.Sprintf("Bcc: %s\r\n", bcc))
}
header.WriteString(fmt.Sprintf("Subject: %s\r\n", subject))
header.WriteString("MIME-Version: 1.0\r\n")
header.WriteString(fmt.Sprintf("Content-Type: multipart/alternative; boundary=%s\r\n", boundary))
header.WriteString("\r\n")
var result bytes.Buffer
result.Write(header.Bytes())
result.Write(altBody)
return result.Bytes(), nil
}cmd.buildMultipartMessage function · go · L164-L243 (80 LOC)cmd/send.go
func buildMultipartMessage(to, subject, body, cc, bcc string, attachPaths []string) ([]byte, error) {
var buf bytes.Buffer
mixedWriter := multipart.NewWriter(&buf)
// Write top-level headers
var headerBuf bytes.Buffer
headerBuf.WriteString(fmt.Sprintf("To: %s\r\n", to))
if cc != "" {
headerBuf.WriteString(fmt.Sprintf("Cc: %s\r\n", cc))
}
if bcc != "" {
headerBuf.WriteString(fmt.Sprintf("Bcc: %s\r\n", bcc))
}
headerBuf.WriteString(fmt.Sprintf("Subject: %s\r\n", subject))
headerBuf.WriteString("MIME-Version: 1.0\r\n")
headerBuf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\r\n", mixedWriter.Boundary()))
headerBuf.WriteString("\r\n")
// Nest multipart/alternative as the first part of multipart/mixed
altBody, altBoundary, err := buildAlternativeBody(body)
if err != nil {
return nil, err
}
altHeader := make(textproto.MIMEHeader)
altHeader.Set("Content-Type", fmt.Sprintf("multipart/alternative; boundary=%s", altBoundary))
altPart, err := mixcmd.interpretEscapes function · go · L248-L273 (26 LOC)cmd/send.go
func interpretEscapes(s string) string {
var b strings.Builder
b.Grow(len(s))
for i := 0; i < len(s); i++ {
if s[i] == '\\' && i+1 < len(s) {
switch s[i+1] {
case 'n':
b.WriteByte('\n')
i++
case 't':
b.WriteByte('\t')
i++
case '\\':
b.WriteByte('\\')
i++
default:
b.WriteByte(s[i])
}
} else {
b.WriteByte(s[i])
}
}
return b.String()
}Repobility · code-quality intelligence · https://repobility.com
cmd.plainTextToHTML function · go · L277-L288 (12 LOC)cmd/send.go
func plainTextToHTML(text string) string {
md := goldmark.New(
goldmark.WithExtensions(extension.GFM),
goldmark.WithRendererOptions(gmhtml.WithHardWraps()),
)
var buf bytes.Buffer
if err := md.Convert([]byte(text), &buf); err != nil {
escaped := html.EscapeString(text)
return "<!DOCTYPE html><html><body>" + strings.ReplaceAll(escaped, "\n", "<br>\n") + "</body></html>"
}
return "<!DOCTYPE html><html><body>" + buf.String() + "</body></html>"
}cmd.buildAlternativeBody function · go · L292-L322 (31 LOC)cmd/send.go
func buildAlternativeBody(body string) ([]byte, string, error) {
var buf bytes.Buffer
altWriter := multipart.NewWriter(&buf)
plainHeader := make(textproto.MIMEHeader)
plainHeader.Set("Content-Type", "text/plain; charset=UTF-8")
plainPart, err := altWriter.CreatePart(plainHeader)
if err != nil {
return nil, "", fmt.Errorf("failed to create text/plain part: %w", err)
}
if _, err := plainPart.Write([]byte(body)); err != nil {
return nil, "", fmt.Errorf("failed to write text/plain body: %w", err)
}
htmlHeader := make(textproto.MIMEHeader)
htmlHeader.Set("Content-Type", "text/html; charset=UTF-8")
htmlPart, err := altWriter.CreatePart(htmlHeader)
if err != nil {
return nil, "", fmt.Errorf("failed to create text/html part: %w", err)
}
if _, err := htmlPart.Write([]byte(plainTextToHTML(body))); err != nil {
return nil, "", fmt.Errorf("failed to write text/html body: %w", err)
}
boundary := altWriter.Boundary()
if err := altWriter.Close(); err != nil {
return nilcmd.init function · go · L62-L71 (10 LOC)cmd/threads.go
func init() {
rootCmd.AddCommand(threadsCmd)
threadsCmd.AddCommand(threadsListCmd)
threadsCmd.AddCommand(threadsGetCmd)
// threads list flags
threadsListCmd.Flags().Int64VarP(&threadsMaxResults, "max-results", "n", 10, "Maximum number of threads to return (max 500)")
threadsListCmd.Flags().StringVar(&threadsLabelIDs, "label-ids", "", "Comma-separated list of label IDs to filter by")
threadsListCmd.Flags().StringVarP(&threadsQuery, "query", "q", "", "Gmail search query (same syntax as web interface)")
}cmd.runThreadsList function · go · L73-L166 (94 LOC)cmd/threads.go
func runThreadsList(cmd *cobra.Command, args []string) error {
ctx := context.Background()
service, err := auth.NewGmailService(ctx, GetAccountEmail())
if err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
// Build threads list request
listCall := service.Users.Threads.List("me")
// Apply max results (cap at 500)
if threadsMaxResults > 500 {
threadsMaxResults = 500
}
listCall = listCall.MaxResults(threadsMaxResults)
// Apply label IDs filter
if threadsLabelIDs != "" {
labels := strings.Split(threadsLabelIDs, ",")
for i := range labels {
labels[i] = strings.TrimSpace(labels[i])
}
listCall = listCall.LabelIds(labels...)
}
// Apply search query
if threadsQuery != "" {
listCall = listCall.Q(threadsQuery)
}
// Execute request
result, err := listCall.Do()
if err != nil {
return fmt.Errorf("Gmail API error: %w", err)
}
// Handle empty results
if len(result.Threads) == 0 {
if GetOutputFormat() == "json" {
return outputJSON([]cmd.runThreadsGet function · go · L168-L266 (99 LOC)cmd/threads.go
func runThreadsGet(cmd *cobra.Command, args []string) error {
threadID := args[0]
ctx := context.Background()
service, err := auth.NewGmailService(ctx, GetAccountEmail())
if err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
// Get thread with full message details
thread, err := service.Users.Threads.Get("me", threadID).Format("full").Do()
if err != nil {
return fmt.Errorf("Gmail API error: %w", err)
}
// JSON output mode
if GetOutputFormat() == "json" {
type threadMessage struct {
From string `json:"from"`
To string `json:"to"`
Subject string `json:"subject"`
Date string `json:"date"`
Body string `json:"body"`
}
type threadGetResult struct {
ThreadID string `json:"thread_id"`
Messages []threadMessage `json:"messages"`
}
result := threadGetResult{
ThreadID: thread.Id,
}
for _, msg := range thread.Messages {
headers := make(map[string]string)
if msg.Payload != nil {
for _, h := rangcmd.extractMessageBody function · go · L269-L304 (36 LOC)cmd/threads.go
func extractMessageBody(payload *gmail.MessagePart) string {
if payload == nil {
return ""
}
// If this part has text/plain body, decode and return it
if payload.MimeType == "text/plain" && payload.Body != nil && payload.Body.Data != "" {
decoded, err := base64.URLEncoding.DecodeString(payload.Body.Data)
if err != nil {
return ""
}
return string(decoded)
}
// Check for multipart messages
if strings.HasPrefix(payload.MimeType, "multipart/") && len(payload.Parts) > 0 {
// First try to find text/plain
for _, part := range payload.Parts {
if part.MimeType == "text/plain" {
body := extractMessageBody(part)
if body != "" {
return body
}
}
}
// Recurse into nested multipart
for _, part := range payload.Parts {
body := extractMessageBody(part)
if body != "" {
return body
}
}
}
return ""
}cmd.truncateSnippet function · go · L307-L315 (9 LOC)cmd/threads.go
func truncateSnippet(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
if maxLen < 3 {
return s[:maxLen]
}
return s[:maxLen-3] + "..."
}cmd.runWhoami function · go · L26-L60 (35 LOC)cmd/whoami.go
func runWhoami(cmd *cobra.Command, args []string) error {
ctx := context.Background()
service, err := auth.NewGmailService(ctx, GetAccountEmail())
if err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
// Get user profile
profile, err := service.Users.GetProfile("me").Do()
if err != nil {
return fmt.Errorf("Gmail API error: %w", err)
}
// JSON output mode
if GetOutputFormat() == "json" {
type whoamiResult struct {
Email string `json:"email"`
MessagesTotal int64 `json:"messages_total"`
ThreadsTotal int64 `json:"threads_total"`
}
return outputJSON(whoamiResult{
Email: profile.EmailAddress,
MessagesTotal: profile.MessagesTotal,
ThreadsTotal: profile.ThreadsTotal,
})
}
// Print profile information (text mode)
fmt.Printf("Email: %s\n", profile.EmailAddress)
fmt.Printf("Messages Total: %d\n", profile.MessagesTotal)
fmt.Printf("Threads Total: %d\n", profile.ThreadsTotal)
return nil
}Generated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
auth.AccountStorePath function · go · L26-L42 (17 LOC)internal/auth/accounts.go
func AccountStorePath() (string, error) {
configDir, err := os.UserConfigDir()
if err != nil {
home, homeErr := os.UserHomeDir()
if homeErr != nil {
return "", fmt.Errorf("failed to determine config directory: %w", err)
}
configDir = filepath.Join(home, ".config")
}
dir := filepath.Join(configDir, tokenDir)
if err := os.MkdirAll(dir, 0700); err != nil {
return "", fmt.Errorf("failed to create config directory %s: %w", dir, err)
}
return filepath.Join(dir, accountsFile), nil
}auth.LoadAccountStore function · go · L46-L66 (21 LOC)internal/auth/accounts.go
func LoadAccountStore() (*AccountStore, error) {
path, err := AccountStorePath()
if err != nil {
return nil, err
}
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return &AccountStore{}, nil
}
return nil, fmt.Errorf("failed to read accounts file %s: %w", path, err)
}
var store AccountStore
if err := json.Unmarshal(data, &store); err != nil {
return nil, fmt.Errorf("failed to parse accounts file %s: %w", path, err)
}
return &store, nil
}auth.AccountStore.Save method · go · L69-L85 (17 LOC)internal/auth/accounts.go
func (s *AccountStore) Save() error {
path, err := AccountStorePath()
if err != nil {
return err
}
data, err := json.MarshalIndent(s, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal account store: %w", err)
}
if err := os.WriteFile(path, data, 0600); err != nil {
return fmt.Errorf("failed to write accounts file %s: %w", path, err)
}
return nil
}auth.AccountStore.AddAccount method · go · L89-L103 (15 LOC)internal/auth/accounts.go
func (s *AccountStore) AddAccount(email string) error {
if email == "" {
return fmt.Errorf("email cannot be empty")
}
if !s.HasAccount(email) {
s.Accounts = append(s.Accounts, AccountEntry{
Email: email,
AddedAt: time.Now().UTC(),
})
}
s.Active = email
return nil
}auth.AccountStore.RemoveAccount method · go · L107-L135 (29 LOC)internal/auth/accounts.go
func (s *AccountStore) RemoveAccount(email string) error {
if email == "" {
return fmt.Errorf("email cannot be empty")
}
idx := -1
for i, a := range s.Accounts {
if strings.EqualFold(a.Email, email) {
idx = i
break
}
}
if idx == -1 {
return fmt.Errorf("account %s not found", email)
}
s.Accounts = append(s.Accounts[:idx], s.Accounts[idx+1:]...)
if strings.EqualFold(s.Active, email) {
if len(s.Accounts) > 0 {
s.Active = s.Accounts[0].Email
} else {
s.Active = ""
}
}
return nil
}auth.AccountStore.SetActive method · go · L138-L149 (12 LOC)internal/auth/accounts.go
func (s *AccountStore) SetActive(email string) error {
if email == "" {
return fmt.Errorf("email cannot be empty")
}
if !s.HasAccount(email) {
return fmt.Errorf("account %s not found", email)
}
s.Active = email
return nil
}auth.AccountStore.GetActive method · go · L153-L166 (14 LOC)internal/auth/accounts.go
func (s *AccountStore) GetActive() (string, error) {
if s.Active != "" {
return s.Active, nil
}
switch len(s.Accounts) {
case 0:
return "", fmt.Errorf("no accounts configured. Run 'gsuite login' first")
case 1:
return s.Accounts[0].Email, nil
default:
return "", fmt.Errorf("multiple accounts found but none is active. Use 'gsuite accounts switch' to set one")
}
}auth.AccountStore.HasAccount method · go · L169-L176 (8 LOC)internal/auth/accounts.go
func (s *AccountStore) HasAccount(email string) bool {
for _, a := range s.Accounts {
if strings.EqualFold(a.Email, email) {
return true
}
}
return false
}Want this analysis on your repo? https://repobility.com/scan/
auth.LoadCredentials function · go · L19-L33 (15 LOC)internal/auth/auth.go
func LoadCredentials() ([]byte, error) {
if jsonContent := os.Getenv("GOOGLE_CREDENTIALS"); jsonContent != "" {
return []byte(jsonContent), nil
}
if filePath := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"); filePath != "" {
data, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("failed to read GOOGLE_APPLICATION_CREDENTIALS file %s: %w", filePath, err)
}
return data, nil
}
return nil, fmt.Errorf("no OAuth2 client credentials found: set GOOGLE_CREDENTIALS env var (JSON) or GOOGLE_APPLICATION_CREDENTIALS env var (file path)")
}auth.extractOAuth2ClientCreds function · go · L37-L65 (29 LOC)internal/auth/auth.go
func extractOAuth2ClientCreds(jsonData []byte) (clientID, clientSecret string, err error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(jsonData, &raw); err != nil {
return "", "", fmt.Errorf("failed to parse credentials JSON: %w", err)
}
var clientJSON json.RawMessage
if data, ok := raw["installed"]; ok {
clientJSON = data
} else if data, ok := raw["web"]; ok {
clientJSON = data
} else {
return "", "", fmt.Errorf("credentials JSON has neither \"installed\" nor \"web\" key")
}
var creds struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
}
if err := json.Unmarshal(clientJSON, &creds); err != nil {
return "", "", fmt.Errorf("failed to parse client credentials: %w", err)
}
if creds.ClientID == "" {
return "", "", fmt.Errorf("client_id is empty in credentials JSON")
}
return creds.ClientID, creds.ClientSecret, nil
}auth.Login function · go · L69-L108 (40 LOC)internal/auth/auth.go
func Login(ctx context.Context, credJSON []byte) (string, error) {
clientID, clientSecret, err := extractOAuth2ClientCreds(credJSON)
if err != nil {
return "", fmt.Errorf("failed to extract OAuth2 client credentials: %w", err)
}
oauthCfg := NewOAuth2Config(clientID, clientSecret)
token, err := oauthCfg.Authenticate(ctx)
if err != nil {
return "", fmt.Errorf("authentication failed: %w", err)
}
service, err := oauthCfg.NewGmailService(ctx, token)
if err != nil {
return "", fmt.Errorf("failed to create Gmail service: %w", err)
}
profile, err := service.Users.GetProfile("me").Do()
if err != nil {
return "", fmt.Errorf("failed to get user profile: %w", err)
}
email := profile.EmailAddress
if err := SaveTokenFor(email, token); err != nil {
return "", fmt.Errorf("failed to save token for %s: %w", email, err)
}
store, err := LoadAccountStore()
if err != nil {
return "", fmt.Errorf("failed to load account store: %w", err)
}
if err := store.AddAccount(email);auth.newAuthenticatedClient function · go · L112-L149 (38 LOC)internal/auth/auth.go
func newAuthenticatedClient(ctx context.Context, account string) (*OAuth2Config, *oauth2.Token, error) {
credJSON, err := LoadCredentials()
if err != nil {
return nil, nil, fmt.Errorf("failed to load credentials: %w", err)
}
clientID, clientSecret, err := extractOAuth2ClientCreds(credJSON)
if err != nil {
return nil, nil, err
}
if err := EnsureMigrated(ctx); err != nil {
return nil, nil, fmt.Errorf("failed to run migration: %w", err)
}
resolvedEmail := account
if resolvedEmail == "" {
store, err := LoadAccountStore()
if err != nil {
return nil, nil, fmt.Errorf("failed to load account store: %w", err)
}
resolvedEmail, err = store.GetActive()
if err != nil {
return nil, nil, fmt.Errorf("no authenticated accounts. Run 'gsuite login' first")
}
}
token, err := LoadTokenFor(resolvedEmail)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, nil, fmt.Errorf("no token for account %s. Run 'gsuite login' to authenticate", resolvedEmail)
auth.NewGmailService function · go · L154-L160 (7 LOC)internal/auth/auth.go
func NewGmailService(ctx context.Context, account string) (*gmail.Service, error) {
oauthCfg, token, err := newAuthenticatedClient(ctx, account)
if err != nil {
return nil, err
}
return oauthCfg.NewGmailService(ctx, token)
}auth.NewCalendarService function · go · L164-L170 (7 LOC)internal/auth/auth.go
func NewCalendarService(ctx context.Context, account string) (*calendar.Service, error) {
oauthCfg, token, err := newAuthenticatedClient(ctx, account)
if err != nil {
return nil, err
}
return oauthCfg.NewCalendarService(ctx, token)
}auth.isInsufficientScopeError function · go · L173-L183 (11 LOC)internal/auth/auth.go
func isInsufficientScopeError(err error) bool {
var gErr *googleapi.Error
if errors.As(err, &gErr) && gErr.Code == 403 {
for _, item := range gErr.Errors {
if item.Reason == "insufficientPermissions" {
return true
}
}
}
return false
}auth.HandleCalendarError function · go · L186-L205 (20 LOC)internal/auth/auth.go
func HandleCalendarError(err error, context string) error {
if err == nil {
return nil
}
var gErr *googleapi.Error
if errors.As(err, &gErr) {
switch gErr.Code {
case 401:
return fmt.Errorf("%s: authentication expired. Run 'gsuite login' to re-authenticate", context)
case 403:
if isInsufficientScopeError(err) {
return fmt.Errorf("%s: calendar permission not granted. Run 'gsuite login' to re-authenticate with calendar access", context)
}
return fmt.Errorf("%s: access denied: %w", context, err)
case 404:
return fmt.Errorf("%s: not found", context)
}
}
return fmt.Errorf("%s: %w", context, err)
}If a scraper extracted this row, it came from Repobility (https://repobility.com)
auth.MigrateIfNeeded function · go · L12-L68 (57 LOC)internal/auth/migrate.go
func MigrateIfNeeded(ctx context.Context, credJSON []byte) error {
store, err := LoadAccountStore()
if err != nil {
return fmt.Errorf("migration: failed to load account store: %w", err)
}
if len(store.Accounts) > 0 {
return nil
}
legacyPath, err := LegacyTokenPath()
if err != nil {
return fmt.Errorf("migration: failed to resolve legacy token path: %w", err)
}
if _, err := os.Stat(legacyPath); os.IsNotExist(err) {
return nil
}
token, err := LoadLegacyToken()
if err != nil {
return fmt.Errorf("migration: failed to load legacy token: %w", err)
}
clientID, clientSecret, err := extractOAuth2ClientCreds(credJSON)
if err != nil {
return fmt.Errorf("migration: failed to extract OAuth2 client credentials: %w", err)
}
oauthCfg := NewOAuth2Config(clientID, clientSecret)
service, err := oauthCfg.NewGmailService(ctx, token)
if err != nil {
return fmt.Errorf("migration: failed to create Gmail service from legacy token: %w", err)
}
profile, err := service.Usersauth.EnsureMigrated function · go · L73-L79 (7 LOC)internal/auth/migrate.go
func EnsureMigrated(ctx context.Context) error {
credJSON, err := LoadCredentials()
if err != nil {
return nil
}
return MigrateIfNeeded(ctx, credJSON)
}auth.NewOAuth2Config function · go · L40-L54 (15 LOC)internal/auth/oauth2.go
func NewOAuth2Config(clientID, clientSecret string) *OAuth2Config {
return &OAuth2Config{
config: &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: google.Endpoint,
RedirectURL: redirectURL,
Scopes: []string{
gmail.GmailModifyScope,
calendar.CalendarEventsScope,
calendar.CalendarReadonlyScope,
},
},
}
}auth.OAuth2Config.Authenticate method · go · L59-L157 (99 LOC)internal/auth/oauth2.go
func (c *OAuth2Config) Authenticate(ctx context.Context) (*oauth2.Token, error) {
// Generate PKCE code verifier (32 random bytes, base64url no padding)
verifier, err := generateCodeVerifier()
if err != nil {
return nil, fmt.Errorf("failed to generate PKCE code verifier: %w", err)
}
// Generate code challenge (SHA256 of verifier, base64url no padding)
challenge := generateCodeChallenge(verifier)
// Generate random state parameter (16 bytes, hex-encoded)
state, err := generateState()
if err != nil {
return nil, fmt.Errorf("failed to generate state parameter: %w", err)
}
// Build authorization URL with PKCE parameters
authURL := c.config.AuthCodeURL(state,
oauth2.AccessTypeOffline,
oauth2.SetAuthURLParam("code_challenge", challenge),
oauth2.SetAuthURLParam("code_challenge_method", "S256"),
oauth2.SetAuthURLParam("prompt", "consent"),
)
// Channel to receive the authorization code from the callback
codeCh := make(chan string, 1)
errCh := make(chan error, 1)auth.OAuth2Config.NewGmailService method · go · L160-L170 (11 LOC)internal/auth/oauth2.go
func (c *OAuth2Config) NewGmailService(ctx context.Context, token *oauth2.Token) (*gmail.Service, error) {
tokenSource := c.config.TokenSource(ctx, token)
client := oauth2.NewClient(ctx, tokenSource)
service, err := gmail.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
return nil, fmt.Errorf("failed to create Gmail service: %w", err)
}
return service, nil
}auth.OAuth2Config.NewCalendarService method · go · L173-L183 (11 LOC)internal/auth/oauth2.go
func (c *OAuth2Config) NewCalendarService(ctx context.Context, token *oauth2.Token) (*calendar.Service, error) {
tokenSource := c.config.TokenSource(ctx, token)
client := oauth2.NewClient(ctx, tokenSource)
service, err := calendar.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
return nil, fmt.Errorf("failed to create Calendar service: %w", err)
}
return service, nil
}auth.generateCodeVerifier function · go · L187-L193 (7 LOC)internal/auth/oauth2.go
func generateCodeVerifier() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("failed to generate random bytes: %w", err)
}
return base64.RawURLEncoding.EncodeToString(b), nil
}auth.generateState function · go · L203-L209 (7 LOC)internal/auth/oauth2.go
func generateState() (string, error) {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("failed to generate random bytes: %w", err)
}
return hex.EncodeToString(b), nil
}Repobility · code-quality intelligence · https://repobility.com
auth.openBrowser function · go · L214-L230 (17 LOC)internal/auth/oauth2.go
func openBrowser(url string) {
var cmd *exec.Cmd
switch runtime.GOOS {
case "linux":
cmd = exec.Command("xdg-open", url)
case "darwin":
cmd = exec.Command("open", url)
case "windows":
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url)
default:
return
}
// Fire and forget — if it fails, user already has the URL printed
cmd.Start() //nolint:errcheck
}auth.LegacyTokenPath function · go · L20-L36 (17 LOC)internal/auth/token.go
func LegacyTokenPath() (string, error) {
configDir, err := os.UserConfigDir()
if err != nil {
home, homeErr := os.UserHomeDir()
if homeErr != nil {
return "", fmt.Errorf("failed to determine config directory: %w", err)
}
configDir = filepath.Join(home, ".config")
}
dir := filepath.Join(configDir, tokenDir)
if err := os.MkdirAll(dir, 0700); err != nil {
return "", fmt.Errorf("failed to create token directory %s: %w", dir, err)
}
return filepath.Join(dir, tokenFile), nil
}auth.saveLegacyToken function · go · L40-L56 (17 LOC)internal/auth/token.go
func saveLegacyToken(token *oauth2.Token) error {
path, err := LegacyTokenPath()
if err != nil {
return fmt.Errorf("failed to resolve token path: %w", err)
}
data, err := json.MarshalIndent(token, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal token: %w", err)
}
if err := os.WriteFile(path, data, 0600); err != nil {
return fmt.Errorf("failed to write token file %s: %w", path, err)
}
return nil
}