Skip to content

Commit

Permalink
1st commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tanaikech committed Jan 10, 2018
0 parents commit cab4627
Show file tree
Hide file tree
Showing 6 changed files with 353 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
language: go
install:
- go get github.com/tanaikech/goodls
8 changes: 8 additions & 0 deletions LICENCE
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
The MIT License (MIT)
Copyright (c) 2018 Kanshi TANAIKE

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
87 changes: 87 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
goodls
=====

<a name="TOP"></a>
[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENCE)

<a name="Overview"></a>
# Overview
This is a CLI tool to download shared files from Google Drive.

# Demo
![](images/demo.gif)

The image used for this demonstration was created by [k3-studio](https://k3-studio.deviantart.com/art/Chromatic-spiral-416032436)

<a name="Description"></a>
# Description
We have already known that the shared files on Google Drive can be downloaded without the authorization. But when the size of file becomes large (about 40MB), it requires a little ingenuity to download the file. It requires to access 2 times to Google Drive. At 1st access, it retrieves a cookie and a code for downloading. At 2nd access, the file is downloaded using the cookie and code. I created this process as a CLI tool. This tool has the following features.

- Use suitable process for size and type of file.
- Retrieve filename and mimetype from response header.
- Can download all shared files except for project files.

# How to Install
Download an executable file of goodls from [the release page](https://github.com/tanaikech/goodls/releases) and import to a directory with path.

or

Use go get.

~~~bash
$ go get -u github.com/tanaikech/goodls
~~~

# Usage
You can use this just after you download or install goodls. You are not required to do like OAuth2 process.

~~~bash
$ goodls -u [URL of shared file on Google Drive]
~~~

- **Help**
- ``$ goodls --help``
- **Options**
- ``-e``
- Extension of output file. This is for only Google Docs (Spreadsheet, Document, Presentation). Default is ``pdf``.
- Sample :
- ``$ goodls -u https://docs.google.com/document/d/#####/edit?usp=sharing -e txt``
- ``-f``
- Filename of file which is output. When this was not used, the original filename on Google Drive is used.
- Sample :
- ``$ goodls -u https://docs.google.com/document/d/#####/edit?usp=sharing -e txt -f sample.txt``
- **URL is like below.**
- In the case of Google Docs (Spreadsheet, Document, Slides)
- ``https://docs.google.com/spreadsheets/d/#####/edit?usp=sharing``
- ``https://docs.google.com/document/d/#####/edit?usp=sharing``
- ``https://docs.google.com/presentation/d/#####/edit?usp=sharing``
- In the case of except for Google Docs
- ``https://drive.google.com/file/d/#####/view?usp=sharing``

**When you download shared files from Google Drive, please confirm whether the files are shared.**

# Q&A
- I want to download **shared projects** from user's Google Drive.
- You can download **shared projects** using [ggsrun](https://github.com/tanaikech/ggsrun).
- ggsrun can also download **shared files** from other user's Google Drive using Drive API which needs the access token.

-----

<a name="Licence"></a>
# Licence
[MIT](LICENCE)

<a name="Author"></a>
# Author
[Tanaike](https://tanaikech.github.io/about/)

If you have any questions and commissions for me, feel free to tell me.

<a name="Update_History"></a>
# Update History
* v1.0.0 (January 10, 2018)

1. Initial release.


[TOP](#TOP)
29 changes: 29 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Package main (doc.go) :
This is a CLI tool to download shared files from Google Drive.
We have already known that the shared files on Google Drive can be downloaded without the authorization. But when the size of file becomes large (about 40MB), it requires a little ingenuity to download the file. It requires to access 2 times to Google Drive. At 1st access, it retrieves a cookie and a code for downloading. At 2nd access, the file is downloaded using the cookie and code. I created this process as a CLI tool. This tool has the following features.
- Use suitable process for size and type of file.
- Retrieve filename and mimetype from response header.
- Can download all shared files except for project files.
---------------------------------------------------------------
# How to Install
Download an executable file of goodls from https://github.com/tanaikech/goodls/releases
or
Use go get.
$ go get -u github.com/tanaikech/goodls
# Usage
You can use this just after you download or install goodls. You are not required to do like OAuth2 process.
$ goodls -u [URL of shared file on Google Drive]
---------------------------------------------------------------
*/
package main
226 changes: 226 additions & 0 deletions goodls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// Package main (goodls.go) :
package main

import (
"errors"
"fmt"
"io/ioutil"
"log"
"mime"
"net/http"
"net/http/cookiejar"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/urfave/cli"
)

const (
appname = "goodls"
anyurl = "https://drive.google.com/uc?export=download"
docutl = "https://docs.google.com/"
)

// para : Structure for each parameter
type para struct {
Client *http.Client
Code string
ContentType string
Ext string
Filename string
Id string
Kind string
Url string
WorkDir string
}

// saveFile : Save retrieved data as a file.
func (p *para) saveFile(res *http.Response) error {
var err error
p.ContentType = res.Header["Content-Type"][0]
err = p.getFilename(res)
if err = p.getFilename(res); err != nil {
return err
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
ioutil.WriteFile(filepath.Join(p.WorkDir, p.Filename), body, 0777)
fmt.Printf("{\"Filename\": \"%s\", \"Type\": \"%s\", \"MimeType\": \"%s\", \"FileSize\": %d}\n", p.Filename, p.Kind, p.ContentType, len(body))
defer res.Body.Close()
return nil
}

// getFilename : Retrieve filename from header.
func (p *para) getFilename(s *http.Response) error {
if len(s.Header["Content-Disposition"]) > 0 {
_, para, err := mime.ParseMediaType(s.Header["Content-Disposition"][0])
if err != nil {
return err
}
if p.Filename == "" {
p.Filename = para["filename"]
}
} else {
return errors.New(fmt.Sprintf("File ID [ %s ] is not shared, while the file is existing.\n", p.Id))
}
return nil
}

// downloadLargeFile : When a large size of file is downloaded, this method is used.
func (p *para) downloadLargeFile() error {
fmt.Println("Now downloading.")
res, err := p.fetch(p.Url + "&confirm=" + p.Code)
if err != nil {
return err
}
if res.StatusCode != 200 && p.Kind != "file" {
return errors.New(fmt.Sprintf("Error: This error occurs when it downloads a large file of Google Docs.\nMessage: %+v", res))
}
p.saveFile(res)
return nil
}

// checkCookie : When a large size of file is downloaded, a code for downloading is retrieved at here.
func (p *para) checkCookie(rawCookies string) {
header := http.Header{}
header.Add("Cookie", rawCookies)
request := http.Request{Header: header}
for _, e := range request.Cookies() {
if strings.Contains(e.Name, "download_warning_") {
cookie, _ := request.Cookie(e.Name)
p.Code = cookie.Value
break
}
}
}

// fetch : Fetch data from Google Drive
func (p *para) fetch(url string) (*http.Response, error) {
req, err := http.NewRequest("get", url, nil)
if err != nil {
return nil, err
}
res, err := p.Client.Do(req)
if err != nil {
return nil, err
}
return res, nil
}

// checkUrl : Parse inputted URL.
func (p *para) checkUrl(s string) error {
r := regexp.MustCompile(`google\.com\/(\w.+)\/d\/(\w.+)\/`)
if r.MatchString(s) {
res := r.FindAllStringSubmatch(s, -1)
p.Kind = res[0][1]
p.Id = res[0][2]
if p.Kind == "file" {
p.Url = anyurl + "&id=" + p.Id
} else {
if p.Kind == "presentation" {
p.Url = docutl + p.Kind + "/d/" + p.Id + "/export/" + p.Ext
} else {
p.Url = docutl + p.Kind + "/d/" + p.Id + "/export?format=" + p.Ext
}
}
} else {
return errors.New("Error: URL is wrong.")
}
return nil
}

// download : Main method of download.
func (p *para) download(url string) error {
var err error
err = p.checkUrl(url)
if err != nil {
return err
}
jar, err := cookiejar.New(nil)
if err != nil {
return err
}
p.Client = &http.Client{Jar: jar}
res, err := p.fetch(p.Url)
if err != nil {
return err
}
if res.StatusCode == 200 {
if len(res.Header["Set-Cookie"]) == 0 {
return p.saveFile(res)
} else {
p.checkCookie(res.Header["Set-Cookie"][0])
if len(p.Code) == 0 && p.Kind == "file" {
return errors.New(fmt.Sprintf("File ID [ %s ] is not shared, while the file is existing.\n", p.Id))
} else if len(p.Code) == 0 && p.Kind != "file" {
return p.saveFile(res)
} else {
return p.downloadLargeFile()
}
}
} else {
return errors.New(fmt.Sprintf("File ID [ %s ] cannot be downloaded as [ %s ].\n", p.Id, p.Ext))
}
return nil
}

// handler : Initialize of "para".
func handler(c *cli.Context) {
if c.String("url") == "" {
createHelp().Run(os.Args)
return
}
var err error
workdir, err := filepath.Abs(".")
if err != nil {
log.Fatal(err)
}
p := &para{
Ext: c.String("extension"),
Filename: c.String("filename"),
WorkDir: workdir,
}
err = p.download(c.String("url"))
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
return
}

// createHelp : Create help document.
func createHelp() *cli.App {
a := cli.NewApp()
a.Name = appname
a.Author = "tanaike [ https://github.com/tanaikech/" + appname + " ] "
a.Email = "[email protected]"
a.Usage = "Download shared files on Google Drive."
a.Version = "1.0.0"
a.Flags = []cli.Flag{
cli.StringFlag{
Name: "url, u",
Usage: "URL of shared file on Google Drive. This is a required parameter.",
},
cli.StringFlag{
Name: "extension, e",
Usage: "Extension of output file. This is for only Google Docs (Spreadsheet, Document, Presentation).",
Value: "pdf",
},
cli.StringFlag{
Name: "filename, f",
Usage: "Filename of file which is output. When this was not used, the original filename on Google Drive is used.",
},
}
return a
}

// main : Main of this script
func main() {
a := createHelp()
a.Action = handler
a.Run(os.Args)
}
Binary file added images/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit cab4627

Please sign in to comment.