Fetch Spreadsheet ID using Google Golang Sheets API V4 / Drive API V3? - go

I'm using Golang for a small project of mine and am currently trying to pull a spreadsheet ID given the exactly filesystem path (in Drive) and spreadsheet/worksheet name. However, looking through the API library in Golang, I don't see a function that allows me to do this.
I'm pretty new to this kind of programming in general so sorry in advance if this has a trivial solution.
Thanks!

You can use drive.files.list in Drive API at Google. drive.files.list can search files with folder information from your Google Drive.
From your question, I thought following 2 steps.
Search file using drive.files.list. File ID and parent folder id can be retrieved, simultaneously. In this case, the fields are id and parents.
Retrieve folder name from folder id using drive.files.get. The field is name.
You can make file tree using folder information got from each file.
About sample script, it used Go Quickstart for Drive API (https://developers.google.com/drive/v3/web/quickstart/go) Please change main() for a script of "Step 3: Set up the sample" to following script.
Script :
func main() {
ctx := context.Background()
b, err := ioutil.ReadFile("client_secret.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/drive-go-quickstart.json
config, err := google.ConfigFromJSON(b, drive.DriveMetadataReadonlyScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(ctx, config)
srv, err := drive.New(client)
if err != nil {
log.Fatalf("Unable to retrieve drive Client %v", err)
}
r, err := srv.Files.List().PageSize(10).
Fields("nextPageToken, files(id, name)").Do()
if err != nil {
log.Fatalf("Unable to retrieve files: %v", err)
}
// From here, it's sample script.
searchfile := "filename"
r, err := srv.Files.List().
Q("name=\"" + searchfile + "\" and trashed=false").Fields("files(id,parents)").Do() // "trashed=false" doesn't search in the trash box.
if err != nil {
log.Fatalf("Error: %v", err)
}
for _, i := range r.Files {
r, err := srv.Files.Get(i.Parents[0]).Fields("name").Do()
if err != nil {
log.Fatalf("Error: %v", err)
}
fmt.Printf("FileID=%s, FolderID=%s, FolderName=%s\n", i.Id, i.Parents[0], r.Name)
}
}
Result :
FileID=#####, FolderID=#####, FolderName=#####
Files on Google Drive can have several parent folders. At this script, it assumes that each file has one parent folder. If your files have several parent folders, please retrieve their folders from parent array.
References :
drive.files.list
https://developers.google.com/drive/v3/reference/files/list
drive.files.get
https://developers.google.com/drive/v3/reference/files/get
Go Quickstart for Drive API
https://developers.google.com/drive/v3/web/quickstart/go

Related

File has no content after downloading xlsx file using http/net

I tried downloading an Excel file from a URL using http/net by calling the GET method. I don't know if this is releveant, but as for my case, I use dropbox to store the file on the cloud (it's open for public, not restricted, it can be accessed on incognito).
But when I open the file that's saved on the local, it has no content at all. It has just an empty sheet. The file is supposed to have filled with lots of data in cell.
What's happening here? Anyone knows how to solve it? There's no error at all when I print it.
func main() {
filePath := "./file/filename.xlsx"
url := "http://www.dropbox.com/somethingsomething.xlsx"
out, err := os.Create(filePath)
if err != nil {
fmt.Println(err)
}
defer out.Close()
resp, err := http.Get(url)
if err != nil {
fmt.Println(err)
}
defer resp.Body.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
fmt.Println(err)
}
return
}
Does the dropbox URL have dl=0 query param?
If so, try changing it to dl=1 to force download the file.
I tried the same with one of my files and it worked.
Thanks!

Share Google Doc via Golang SDK

I am using service account JSON to create google Doc via Golang SDK but as this doc is only accessible to Service account I am not able to access it with my Personal Google Account. In the Google Doc SDK documentation I couldn't find any function to share the Doc.
This is my sample Code:
package googledoc
import (
"context"
"fmt"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2/google"
"google.golang.org/api/docs/v1"
"google.golang.org/api/option"
func CreateDoc(title string) error {
ctx := context.Background()
cred, err := GetSecrets("ap-south-1", "GOOGLE_SVC_ACC_JSON")
if err != nil {
return fmt.Errorf("unable to get the SSM %v", err)
}
config, err := google.JWTConfigFromJSON(cred, docs.DocumentsScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := config.Client(ctx)
srv, err := docs.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
log.Fatalf("Unable to retrieve Docs client: %v", err)
}
docObj := docs.Document{
Title: title,
}
doc, err := srv.Documents.Create(&docObj).Do()
if err != nil {
log.Fatalf("Unable to retrieve data from document: %v", err)
return err
}
fmt.Printf("The title of the doc is: %s %s\n", doc.Title, doc.DocumentId)
return nil
}
Any help with this would be really appreciated Thanks.
I believe your goal is as follows.
You want to share the created Google Document by the service account with your Google account.
You want to achieve this using googleapis for golang.
Unfortunately, Google Docs API cannot be used for sharing the Document with users. In this case, Drive API is used. When your script is modified using Drive API, how about the following modification?
Sample script:
Please add this script just after the line of fmt.Printf("The title of the doc is: %s %s\n", doc.Title, doc.DocumentId). By this, the created Google Document is shared with the user.
driveSrv, err := drive.NewService(ctx, option.WithHTTPClient(client)) // Please use your client and service for using Drive API.
if err != nil {
log.Fatal(err)
}
permission := &drive.Permission{
EmailAddress: "###", // Please set the email address you want to share.
Role: "writer",
Type: "user",
}
res, err := driveSrv.Permissions.Create(doc.DocumentId, permission).Do()
if err != nil {
log.Fatal(err)
return err
}
fmt.Println(res)
When this script is added to your script, the created Document is shared with the user as the writer.
Reference:
Permissions: create

How to create a NamgedRange using google sheets API when doing a batch update with Go

I recently started writing a program to export CSV and SQL to google sheets. And in some scenarios I need to create a NamedRange while creating the sheet and/or updating it. The google official documentation is kinda confusion and not very helpful for me. Can anyone please show me an example code or point me in the right direction?
Right now I have something along these lines. This is just sample code to show one of the scenarios.
func writeSS(ssid string, content [][]interface{}) {
ctx := context.Background()
b, err := ioutil.ReadFile("./credentials/client_secret.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
config, err := google.ConfigFromJSON(b, "https://www.googleapis.com/auth/spreadsheets.readonly")
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(config)
srv, err := sheets.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
log.Fatalf("Unable to retrieve Sheets client: %v", err)
}
spreadsheetId := ssid
rangeData := "Sheet name!A1:A6"
rb := &sheets.BatchUpdateValuesRequest{
ValueInputOption: "USER_ENTERED",
}
rb.Data = append(rb.Data, &sheets.ValueRange{
Range: rangeData,
Values: content,
})
_, err = srv.Spreadsheets.Values.BatchUpdate(spreadsheetId, rb).Context(ctx).Do() //Check this again
// _, err = srv.Spreadsheets.Values.Update(spreadsheetId, writeRange, &vr).ValueInputOption("USER_ENTERED").Do()
if err != nil {
log.Fatal(err)
}
fmt.Println("Done.")
}
I believe your goal is as follows.
You want to create a named range using googleapis with golang.
You have already been able to get and put values for Google Spreadsheet using Sheets API.
Modification points:
When I saw your script, the method of spreadsheets.values.batchUpdate of Sheets API is used. When you want to create the named range in the existing Google Spreadsheet, please use the method of spreadsheets.batchUpdate.
In your script, you are trying to put the values to the cells using the scope of https://www.googleapis.com/auth/spreadsheets.readonly. I think that an error related to the scopes occurs. In this case, please use the scope of https://www.googleapis.com/auth/spreadsheets.
When these points are reflected in your script, it becomes as follows.
Modified script:
config, err := google.ConfigFromJSON(b, "https://www.googleapis.com/auth/spreadsheets")
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(config)
srv, err := sheets.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
log.Fatalf("Unable to retrieve Sheets client: %v", err)
}
spreadsheetId := "###" // Please set your Spreadsheet ID.
sheetId := 1234567890 // Please set your sheet ID.
nameOfNamedRange := "sampleNamedRange1" // Please set the name of the named range.
req := sheets.Request{
AddNamedRange: &sheets.AddNamedRangeRequest{
NamedRange: &sheets.NamedRange{
Range: &sheets.GridRange{
SheetId: int64(sheetId),
StartRowIndex: 1,
EndRowIndex: 3,
StartColumnIndex: 1,
EndColumnIndex: 3,
},
Name: nameOfNamedRange,
},
},
}
requestBody := &sheets.BatchUpdateSpreadsheetRequest{
Requests: []*sheets.Request{&req},
}
resp, err := srv.Spreadsheets.BatchUpdate(spreadsheetId, requestBody).Do()
if err != nil {
log.Fatal(err)
}
fmt.Print(resp)
In this sample script, the gridrange of StartRowIndex: 1, EndRowIndex: 3, StartColumnIndex: 1, EndColumnIndex: 3, means the cells "B2:C3".
When this script is run, the named range of nameOfNamedRange is created with the range of "B2:C3" of sheetId in the Google Spreadsheet of spreadsheetId.
Note:
From your showing script, unfortunately, I cannot know the filename of the file including the access token and refresh token. If the filename is token.json used in the Quickstart, before you run the modified script, please delete the file. And, please reauthorize the scopes. Please be careful about this.
References:
Method: spreadsheets.batchUpdate
AddNamedRangeRequest

How to add images into specific folder in Go? Getting error like: `%!(EXTRA *fs.PathError=open /photos: read-only file system)`

I am trying to put images into a specific folder in Golang. Here is the code below.
This is the function where I create a folder called photos in the root directory.
func createPhotoFolder(folderName string) {
err := os.Mkdir(folderName, 777)
if err != nil {
fmt.Println("Error creating folder: ", err)
return
}
fmt.Println(folderName, " created successfully in the root directory")
}
This is the function where I make get request to fetch image and try to put them into a photos folder I created earlier.
func downloadImages(urls []string) {
for i, url := range urls {
resp, err := http.Get(url)
fmt.Printf("%d inside for loop\n", i)
if err != nil {
log.Fatal("error fetching image: ", err)
}
defer resp.Body.Close()
out, err := os.Create("photos")
if err != nil {
log.Printf("Can't put image into folder: ", err)
}
defer out.Close()
}
}
This is the error I get when I run the program.
1- If the folder name is written in this way os.Create("photos") without forwardslash I get the error message as below.
Can't put image into folder: %!(EXTRA *fs.PathError=open photos: is a directory)
2- If I write it like os.Create("/photos"). I get the error as below.
Can't put image into folder: %!(EXTRA *fs.PathError=open /photos: read-only file system)
I gave all the permission while creating the photos folder in the way of chmod.
I did try using io.Copy() but it requires a file parameter which I don't get while creating one using os.Create()
How should I create the folder and put the images inside it properly?
Here, in your code, in os.Create, it should have the complete address of the file to be created along with the name of the file to be created. Like:
gopath := "C:/Users/<username>/go/src/photos/" //where photos is the folder you created
filename := "photo1.jpg"
out, err := os.Create(gopath + filename)
Also, as #steven-penny gave in his answer, create a filename from the image name directly from the url. So that you don't have to give the filename for each image you download.
out, err := os.create(gopath + filepath.Base(link))
And save the image to your system with,
out.Readfrom(resp.Body)
Here is a small program that does what I think you are trying to do:
package main
import (
"net/http"
"os"
"path/filepath"
)
func downloadImages(links []string) error {
tmp := os.TempDir()
for _, link := range links {
println(link)
res, err := http.Get(link)
if err != nil { return err }
defer res.Body.Close()
file, err := os.Create(filepath.Join(tmp, filepath.Base(link)))
if err != nil { return err }
defer file.Close()
file.ReadFrom(res.Body)
}
return nil
}
func main() {
links := []string{
"http://cdn.sstatic.net/Sites/stackoverflow/Img/apple-touch-icon.png",
"http://cdn.sstatic.net/Sites/stackoverflow/Img/favicon.ico",
}
err := downloadImages(links)
if err != nil {
panic(err)
}
}
You'll want to modify it, as you were using a different directory, but it should get you started.
https://golang.org/pkg/os#File.ReadFrom

SFTP In Go Error: User does not have appropriate read permission

I am trying to upload a product feed to a Google Merchant SFTP account. I am able to upload a file manually through the command prompt but encounter the following error when trying to do it through Go.
Error: sftp: "User does not have appropriate read permission." (SSH_FX_PERMISSION_DENIED)
I am using the github.com/pkg/sftp package, following the example in https://godoc.org/github.com/pkg/sftp#Client.Open. I suspect that the Create/Write pattern here ends up being different from a simple put from command line.
Code
func (g *GoogleExporter) ExportToSFTP(file []byte) error {
// Creating an SSH connection
sshConfig := &ssh.ClientConfig{
User: g.Creds.AccessData.SFTPUser,
Auth: []ssh.AuthMethod{
ssh.Password(g.Creds.AccessData.SFTPPassword),
},
}
hostPort := fmt.Sprintf("%s:%d", SFTPHostName, SFTPHostPort)
connection, err := ssh.Dial("tcp", hostPort, sshConfig)
if err != nil {
return err
}
fmt.Println(">> SSH Connection Created!")
// Creating an SFPT connection over SSH
sftp, err := sftp.NewClient(connection)
if err != nil {
return err
}
defer sftp.Close()
fmt.Println(">> SFTP Client Created!")
// Uploading the file
remoteFileName := "products.xml" // TODO: Make this name configurable
remoteFile, err := sftp.Create(remoteFileName)
if err != nil {
return err
}
fmt.Println(">> SFTP File Created!")
if _, err := remoteFile.Write(file); err != nil {
return err
}
fmt.Println("Successfully uploaded product feed to SFTP, file:%s user:%s", remoteFileName, g.Creds.AccessData.SFTPUser)
util.Log("Successfully uploaded product feed to SFTP, file:%s user:%s", remoteFileName, g.Creds.AccessData.SFTPUser)
// Confirming if the file is there
if _, err := sftp.Lstat(remoteFileName); err != nil {
return err
}
return nil
}
The error is cause by this line:
remoteFile, err := sftp.Create(remoteFileName)
I am answering my own question to help anyone else that is having this problem. I was able to find a solution.
The Google Merchant SFTP account only gives you write only access. However, according to the docs, when using the sftp.Create(..) function, it creates a file with the flags as 0666, which does not agree with the permissions set on your user.
To mimic the behavior of sftp.Create(..) with write only permissions, you can use the more general sftp.OpenFile(..) function.
remoteFile, err := sftp.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
The flags os.O_WRONLY|os.O_CREATE|os.O_TRUNC will mimic the behavior of Create() i.e. create a file it doesn't exist and truncate the file if it does.

Resources