|
package main |
|
|
|
import ( |
|
"context" |
|
"encoding/base64" |
|
"encoding/json" |
|
"fmt" |
|
"log" |
|
"os" |
|
"time" |
|
|
|
"golang.org/x/oauth2" |
|
"golang.org/x/oauth2/google" |
|
"google.golang.org/api/gmail/v1" |
|
"google.golang.org/api/option" |
|
) |
|
|
|
// userID is the user's email address. The special value "me" can be used to indicate the authenticated user. |
|
// https://developers.google.com/gmail/api/reference/rest/v1/users.messages/get |
|
const userID = "me" |
|
|
|
var ( |
|
// destinationDir is the path to the directory where the attachments will be saved. |
|
destinationDir = os.Getenv("DESTINATION_DIR") |
|
|
|
// tokenFile is the path to the file containing the token. |
|
tokenFile = os.Getenv("CLIENT_TOKEN_FILE") |
|
) |
|
|
|
// getAttachments retrieves the attachments from the messages matching the query |
|
// and saves them to the destination directory. |
|
func getAttachments(ctx context.Context, svc *gmail.Service, query string) { |
|
pageToken := "" |
|
for { |
|
// Listing messages. |
|
listResp, err := svc.Users.Messages.List(userID).Q(query).PageToken(pageToken).Context(ctx).Do() |
|
if err != nil { |
|
log.Fatalf("Unable to retrieve messages: %v", err) |
|
} |
|
|
|
log.Printf("Processing %d message(s)...\n", len(listResp.Messages)) |
|
for _, msgInfo := range listResp.Messages { |
|
// Getting a message. |
|
log.Printf("Processing msg %s...\n", msgInfo.Id) |
|
msg, err := svc.Users.Messages.Get(userID, msgInfo.Id).Context(ctx).Do() |
|
if err != nil { |
|
log.Fatalf("Unable to retrieve message: %v", err) |
|
} |
|
|
|
for _, part := range msg.Payload.Parts { |
|
if part.Filename == "" { // Only present for attachments |
|
continue |
|
} |
|
// Getting an attachment. |
|
att, err := svc.Users.Messages.Attachments.Get(userID, msg.Id, part.Body.AttachmentId).Context(ctx).Do() |
|
if err != nil { |
|
log.Fatalf("Unable to retrieve attachment: %v", err) |
|
} |
|
|
|
// Saving the attachment. |
|
saveAttachment(att.Data, part.Filename) |
|
} |
|
} |
|
|
|
if pageToken = listResp.NextPageToken; pageToken == "" { |
|
break |
|
} |
|
} |
|
} |
|
|
|
// getTokenFromWeb requests a token from the web, then returns the retrieved token. |
|
func getTokenFromWeb(ctx context.Context, config *oauth2.Config) *oauth2.Token { |
|
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) |
|
fmt.Printf("Go to the following link in your browser then type the authorization code: \n%v\n", authURL) |
|
|
|
var authCode string |
|
if _, err := fmt.Scan(&authCode); err != nil { |
|
log.Fatalf("Unable to read authorization code: %v", err) |
|
} |
|
|
|
tok, err := config.Exchange(ctx, authCode) |
|
if err != nil { |
|
log.Fatalf("Unable to retrieve token from web: %v", err) |
|
} |
|
return tok |
|
} |
|
|
|
// saveAttachment saves the attachment to the destination directory. |
|
func saveAttachment(data, filename string) { |
|
// Decoding the base64url encoded string into bytes. |
|
b, err := base64.URLEncoding.DecodeString(data) |
|
if err != nil { |
|
log.Fatalf("Unable to decode data: %v", err) |
|
} |
|
|
|
// Creating a file for saving the attachment. |
|
log.Printf("Saving %s...\n", filename) |
|
out, err := os.Create(destinationDir + filename) |
|
if err != nil { |
|
log.Fatalf("Unable to create file: %v", err) |
|
} |
|
defer func() { |
|
if err = out.Close(); err != nil { |
|
log.Fatalf("Unable to close the file: %v", err) |
|
} |
|
}() |
|
|
|
if _, err = out.Write(b); err != nil { |
|
log.Fatalf("Unable to write file: %v", err) |
|
} |
|
if err = out.Sync(); err != nil { |
|
log.Fatalf("Unable to sync commit: %v", err) |
|
} |
|
} |
|
|
|
// saveToken saves a token to a file path. |
|
func saveToken(token *oauth2.Token) { |
|
fmt.Printf("Saving credential file to: %s\n", tokenFile) |
|
f, err := os.OpenFile(tokenFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) |
|
if err != nil { |
|
log.Fatalf("Unable to cache oauth token: %v", err) |
|
} |
|
defer func() { |
|
if err = f.Close(); err != nil { |
|
log.Fatalf("Unable to close the token file: %v", err) |
|
} |
|
}() |
|
|
|
if err = json.NewEncoder(f).Encode(token); err != nil { |
|
log.Fatalf("Unable to encode token: %v", err) |
|
} |
|
} |
|
|
|
// tokenFromFile returns the token, which was previously saved into a file. |
|
func tokenFromFile() *oauth2.Token { |
|
f, err := os.Open(tokenFile) |
|
if err != nil { |
|
log.Fatalf("Unable to open token file: %v", err) |
|
} |
|
defer func() { |
|
if err = f.Close(); err != nil { |
|
log.Fatalf("Unable to close the token file: %v", err) |
|
} |
|
}() |
|
|
|
tok := &oauth2.Token{} |
|
if err = json.NewDecoder(f).Decode(tok); err != nil { |
|
log.Fatalf("Unable to decode token: %v", err) |
|
} |
|
return tok |
|
} |
|
|
|
func main() { |
|
// Setting up the config manually. You can also retrieve a config from a file, e.g. |
|
// config, err := google.ConfigFromJSON(b, gmail.GmailReadonlyScope) |
|
// where b corresponds to the contents of the client credentials file. |
|
config := &oauth2.Config{ |
|
ClientID: os.Getenv("CLIENT_ID"), |
|
ClientSecret: os.Getenv("CLIENT_SECRET"), |
|
Endpoint: google.Endpoint, |
|
RedirectURL: "http://localhost", // Not required once you have a token |
|
Scopes: []string{gmail.GmailReadonlyScope}, |
|
} |
|
|
|
// Retrieving a token for first time and saving it. |
|
// Required if you don't have a token, or it's expired. |
|
// saveToken(getTokenFromWeb(ctx, config)) |
|
|
|
// Setting up the token manually. You can also retrieve a token from a file, e.g. |
|
// token := tokenFromFile() |
|
token := &oauth2.Token{ |
|
AccessToken: os.Getenv("ACCESS_TOKEN"), |
|
TokenType: "Bearer", |
|
RefreshToken: os.Getenv("REFRESH_TOKEN"), |
|
Expiry: time.Date(2023, time.March, 27, 15, 5, 14, 0, time.UTC), |
|
} |
|
|
|
// Creating the gmail service. |
|
ctx := context.Background() |
|
svc, err := gmail.NewService(ctx, option.WithHTTPClient(config.Client(ctx, token))) |
|
if err != nil { |
|
log.Fatalf("Unable to create gmail service: %v", err) |
|
} |
|
|
|
// Getting the attachments. |
|
query := "from:[email protected] has:attachment subject:(My Subject)" |
|
getAttachments(ctx, svc, query) |
|
} |