diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2422c1d --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +*.txt +*.json \ No newline at end of file diff --git a/cmd/gdown/cli.go b/cmd/gdown/cli.go new file mode 100644 index 0000000..5e26cf7 --- /dev/null +++ b/cmd/gdown/cli.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "github.com/Juvenal-Yescas/gdown" + "github.com/Juvenal-Yescas/gdown/internal/helpers/auth" + "github.com/Juvenal-Yescas/gdown/internal/process" + "github.com/Juvenal-Yescas/gdown/internal/utils" + "os" +) + +func cliDownload(url string) { + output, err := gdown.Download(url) + if err != nil { + if utils.CaseInsensitiveContains(err.Error(), "exceeded") { + fmt.Println("The file has exceeded the download limit, try download with your account") + output, err := downloadWithAccount(url) + if err != nil { + fmt.Println("Error : ", err) + os.Exit(1) + } + fmt.Println("File downloaded : ", output) + os.Exit(0) + } + fmt.Println("Error : ", err) + os.Exit(1) + + } + fmt.Println("File downloaded: ", output) + os.Exit(0) +} + +func downloadWithAccount(url string) (string, error) { + idFileUrl, err := process.GetIdFromUrl(url) + if err != nil { + return "", err + } + + clientHttp := utils.CreateClientHttp() + outputName, err := process.GetNameOutput(clientHttp, idFileUrl) + if err != nil { + return "", err + } + + clientGDrive, err := auth.CreateClientApiDrive() + if err != nil { + return "", err + } + infoFolderDrive, infoFileDrive, err := process.CreateCopyInDrive(clientGDrive, idFileUrl, outputName) + if err != nil { + return "", err + } + fmt.Println("Downloading ... ", outputName) + output, err := gdown.DownloadOutput("https://drive.google.com/uc?id="+infoFileDrive.Id, outputName) + if err != nil { + return "", err + } + + err = process.CleanCopyInDrive(clientGDrive, infoFolderDrive.Id) + if err != nil { + return "", err + } + + return output, nil +} diff --git a/cmd/gdown/main.go b/cmd/gdown/main.go new file mode 100644 index 0000000..4d10e99 --- /dev/null +++ b/cmd/gdown/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + log "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" + "io/ioutil" + "os" +) + +func init() { + log.SetOutput(ioutil.Discard) + // log.SetLevel(log.DebugLevel) +} + +const Version = "0.1.0" + +func main() { + + cli.VersionFlag = &cli.BoolFlag{ + Name: "print-version", Aliases: []string{"V"}, + Usage: "print only the version", + } + + app := &cli.App{ + Name: "gdown", + Version: Version, + Usage: "Download files directly from googledrive", + + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "url", + Aliases: []string{"u"}, + Usage: "`\"FILEURL\"` to download from google drive ", + }, + }, + Action: func(c *cli.Context) error { + if c.String("url") != "" { + cliDownload(c.String("url")) + } + if c.NArg() > 0 { + link := c.Args().Get(0) + cliDownload(link) + } + return nil + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } + fmt.Println("gdown --help") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..89df08b --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/Juvenal-Yescas/gdown + +go 1.13 + +require ( + github.com/sirupsen/logrus v1.4.2 + github.com/urfave/cli/v2 v2.1.1 + golang.org/x/net v0.0.0-20200226121028-0de0cce0169b + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + google.golang.org/api v0.19.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5610be1 --- /dev/null +++ b/go.sum @@ -0,0 +1,108 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= +github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.19.0 h1:GwFK8+l5/gdsOYKz5p6M4UK+QT8OvmHWZPJCnf+5DjA= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/helpers/auth/client.go b/internal/helpers/auth/client.go new file mode 100644 index 0000000..3abf29e --- /dev/null +++ b/internal/helpers/auth/client.go @@ -0,0 +1,52 @@ +// refs https://developers.google.com/drive/v3/web/quickstart/go +package auth + +import ( + "github.com/Juvenal-Yescas/gdown/internal/utils" + "golang.org/x/net/context" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "google.golang.org/api/drive/v3" + "io/ioutil" + "log" + "net/http" +) + +func CreateClientApiDrive() (*drive.Service, error) { + b, err := ioutil.ReadFile("credentials.json") + if err != nil { + log.Fatalf("Unable to read client secret file: %v", err) + return nil, err + } + + // If modifying these scopes, delete your previously saved token.json. + config, err := google.ConfigFromJSON(b, drive.DriveScope) + if err != nil { + log.Fatalf("Unable to parse client secret file to config: %v", err) + return nil, err + } + + client := getClient(config) + + srv, err := drive.New(client) + if err != nil { + log.Fatalf("Unable to retrieve Drive client: %v", err) + return nil, err + } + return srv, nil +} + +// Retrieve a token, saves the token, then returns the generated client. +func getClient(config *oauth2.Config) *http.Client { + // The file token.json stores the user's access and refresh tokens, and is + // created automatically when the authorization flow completes for the first + // time. + + tokFile := utils.GetDefaultConfigDir() // tokFile := "token.json" + tok, err := tokenFromFile(tokFile) + if err != nil { + tok = getTokenFromWeb(config) + saveToken(tokFile, tok) + } + return config.Client(context.Background(), tok) +} diff --git a/internal/helpers/auth/token.go b/internal/helpers/auth/token.go new file mode 100644 index 0000000..6a00680 --- /dev/null +++ b/internal/helpers/auth/token.go @@ -0,0 +1,52 @@ +// refs https://developers.google.com/drive/v3/web/quickstart/go +package auth + +import ( + "encoding/json" + "fmt" + log "github.com/sirupsen/logrus" + "golang.org/x/net/context" + "golang.org/x/oauth2" + "os" +) + +// Request a token from the web, then returns the retrieved token. +func getTokenFromWeb(config *oauth2.Config) *oauth2.Token { + authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) + log.Debug("Go to the following link in your browser then type the "+ + "authorization code: \n", authURL) + + var authCode string + if _, err := fmt.Scan(&authCode); err != nil { + log.Error("Unable to read authorization code %v", err) + } + + tok, err := config.Exchange(context.TODO(), authCode) + if err != nil { + log.Error("Unable to retrieve token from web %v", err) + } + return tok +} + +// Retrieves a token from a local file. +func tokenFromFile(file string) (*oauth2.Token, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + defer f.Close() + tok := &oauth2.Token{} + err = json.NewDecoder(f).Decode(tok) + return tok, err +} + +// Saves a token to a file path. +func saveToken(path string, token *oauth2.Token) { + log.Debug("Saving credential file to: ", path) + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Fatalf("Unable to cache oauth token: %v", err) + } + defer f.Close() + json.NewEncoder(f).Encode(token) +} diff --git a/internal/helpers/webscraping/webscraping.go b/internal/helpers/webscraping/webscraping.go new file mode 100644 index 0000000..56d2ac4 --- /dev/null +++ b/internal/helpers/webscraping/webscraping.go @@ -0,0 +1,54 @@ +package webscraping + +import ( + log "github.com/sirupsen/logrus" + "golang.org/x/net/html" + "io" + "net/http" +) + +func GetTittle(client *http.Client, link string) string { + resp, err := client.Get(link) + if err != nil { + panic(err) + } + + defer resp.Body.Close() + + if title, ok := getHtmlTitle(resp.Body); ok { + log.Debug("Tittle website: ", title) + return title + } else { + println("Fail to get HTML title") + } + + return "" +} + +func getHtmlTitle(r io.Reader) (string, bool) { + doc, err := html.Parse(r) + if err != nil { + panic("Fail to parse html") + } + + return traverse(doc) +} + +func traverse(n *html.Node) (string, bool) { + if isTitleElement(n) { + return n.FirstChild.Data, true + } + + for c := n.FirstChild; c != nil; c = c.NextSibling { + result, ok := traverse(c) + if ok { + return result, ok + } + } + + return "", false +} + +func isTitleElement(n *html.Node) bool { + return n.Type == html.ElementNode && n.Data == "title" +} diff --git a/internal/process/account.go b/internal/process/account.go new file mode 100644 index 0000000..6ebffd4 --- /dev/null +++ b/internal/process/account.go @@ -0,0 +1,51 @@ +package process + +import ( + // "github.com/Juvenal-Yescas/gdown/internal/helpers/auth" + "github.com/Juvenal-Yescas/gdown/pkg/gdriveapi" + "google.golang.org/api/drive/v3" +) + +func CreateCopyInDrive(clientGDrive *drive.Service, idFile string, outputName string) (*drive.File, *drive.File, error) { + idFolder, err := gdriveapi.CreateFolder(clientGDrive, "gdown") + if err != nil { + return nil, nil, err + } + + _, err = gdriveapi.MakeSharedFolder(clientGDrive, idFolder.Id) + if err != nil { + return idFolder, nil, err + } + arrayFolder := []string{idFolder.Id} + + idFileCopied, err := gdriveapi.CreateACopy(clientGDrive, idFile, outputName, arrayFolder) + if err != nil { + return idFolder, nil, err + } + + return idFolder, idFileCopied, nil +} + +func CleanCopyInDrive(clientGDrive *drive.Service, idFolder string) error { + err := gdriveapi.Delete(clientGDrive, idFolder) + return err +} + +// func SkipQuotaExceeded(clientWeb *http.Client, idFile string, outputName string) (string, error) { +// client, err := auth.CreateClient() +// if err != nil { +// return "", err +// } + +// log.Debug("Try download again") +// output, err := StartDownload(clientWeb, idFileCopied.Id, outputName) +// if err != nil { +// return "", err +// } + +// err = gdriveapi.Delete(client, idFolder.Id) +// if err != nil { +// return "", err +// } +// return output, err +// } diff --git a/internal/process/functions.go b/internal/process/functions.go new file mode 100644 index 0000000..07e9b8f --- /dev/null +++ b/internal/process/functions.go @@ -0,0 +1,105 @@ +package process + +import ( + "errors" + "github.com/Juvenal-Yescas/gdown/internal/helpers/webscraping" + "github.com/Juvenal-Yescas/gdown/internal/utils" + log "github.com/sirupsen/logrus" + "io/ioutil" + "net/http" + "regexp" + "strings" +) + +func GetUrlConfirmation(clientHttp *http.Client, idFile string) (string, error) { + + urlGoogleDrive := "https://drive.google.com/uc?id=" + idFile + + // Generate cookie + resp, err := clientHttp.Get(urlGoogleDrive) + if err != nil { + log.Error("Client get : %v", err) + return "", err + } + + defer resp.Body.Close() + + var urlDirect string + + if len(resp.Cookies()) == 0 { + + data, _ := ioutil.ReadAll(resp.Body) + bodyString := string(data) + + if utils.CaseInsensitiveContains(bodyString, "exceeded") { + log.Info("Google Drive - Quota exceeded : Try download using your account...") + log.Debug(urlGoogleDrive) + return "", errors.New("This file is limited: Quota exceeded") + } + // small file + return urlGoogleDrive, nil + } + + // big file + codeConfirmation := resp.Cookies()[0].Value + urlDirect = urlGoogleDrive + "&confirm=" + codeConfirmation + return urlDirect, nil +} + +func StartDownload(clientHttp *http.Client, urlDirect string, outputName string) (string, error) { + log.Info("Downloading ... ", outputName) + + fileOutput, err := utils.CreateFile(outputName) + if err != nil { + return "", err + } + + resp, err := clientHttp.Get(urlDirect) + if err != nil { + log.Error("Client get : %v", err) + return "", err + } + + defer resp.Body.Close() + _, err = utils.WriteToOutput(fileOutput, resp.Body) + if err != nil { + log.Error("Write data error : %v", err) + return "", err + } + return outputName, nil +} + +func GetIdFromUrl(url string) (string, error) { + if utils.CaseInsensitiveContains(url, "google.com") { + if utils.CaseInsensitiveContains(url, "id") { + arrayUrl := strings.Split(url, "=") + if utils.CaseInsensitiveContains(arrayUrl[1], "export") { + arrayUrl := strings.Split(arrayUrl[1], "&") + return arrayUrl[0], nil + } + return arrayUrl[1], nil + } else if utils.CaseInsensitiveContains(url, "docs") { + log.Error("Documents not implemented") + return "", errors.New("Documents not implemented") + } else { + arrayUrl := strings.Split(url, "/") + return arrayUrl[5], nil + } + } + return url, nil +} + +func GetNameOutput(client *http.Client, idFile string) (string, error) { + urlOpen := "https://drive.google.com/open?id=" + idFile + + tittleWebsite := webscraping.GetTittle(client, urlOpen) + + extraTittle := regexp.MustCompile(" - Google Drive") + outputName := extraTittle.ReplaceAllString(tittleWebsite, "") + log.Debug("Correct name output: ", outputName) + + if outputName == "" { + return "", errors.New("Tittle dont found") + } + return outputName, nil +} diff --git a/internal/utils/clienthttp.go b/internal/utils/clienthttp.go new file mode 100644 index 0000000..8dc6342 --- /dev/null +++ b/internal/utils/clienthttp.go @@ -0,0 +1,18 @@ +package utils + +import ( + "net/http" + "net/http/cookiejar" +) + +func CreateClientHttp() *http.Client { + cookieJar, _ := cookiejar.New(nil) + client := &http.Client{ + Jar: cookieJar, + CheckRedirect: func(r *http.Request, via []*http.Request) error { + r.URL.Opaque = r.URL.Path + return nil + }, + } + return client +} diff --git a/internal/utils/output.go b/internal/utils/output.go new file mode 100644 index 0000000..a621d9d --- /dev/null +++ b/internal/utils/output.go @@ -0,0 +1,22 @@ +package utils + +import ( + log "github.com/sirupsen/logrus" + "io" + "os" +) + +func CreateFile(output string) (*os.File, error) { + fileOuput, err := os.Create(output) + if err != nil { + log.Debug("create file: %v", err) + return nil, err + } + return fileOuput, nil +} + +func WriteToOutput(fileOuput *os.File, resp io.Reader) (int64, error) { + n, err := io.Copy(fileOuput, resp) + defer fileOuput.Close() + return n, err +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 0000000..0fc371d --- /dev/null +++ b/internal/utils/utils.go @@ -0,0 +1,24 @@ +package utils + +import ( + "os" + "path/filepath" + "runtime" + "strings" +) + +func CaseInsensitiveContains(s, substr string) bool { + s, substr = strings.ToUpper(s), strings.ToUpper(substr) + return strings.Contains(s, substr) +} + +func GetDefaultConfigDir() string { + return filepath.Join(Homedir(), ".gdown") +} + +func Homedir() string { + if runtime.GOOS == "windows" { + return os.Getenv("APPDATA") + } + return os.Getenv("HOME") +} diff --git a/module.go b/module.go new file mode 100644 index 0000000..fed6abf --- /dev/null +++ b/module.go @@ -0,0 +1,49 @@ +package gdown + +import ( + "github.com/Juvenal-Yescas/gdown/internal/process" + "github.com/Juvenal-Yescas/gdown/internal/utils" +) + +func Download(url string) (string, error) { + idFile, err := process.GetIdFromUrl(url) + if err != nil { + return "", err + } + + clientHttp := utils.CreateClientHttp() + outputName, err := process.GetNameOutput(clientHttp, idFile) + if err != nil { + return "", err + } + + urlDirect, err := process.GetUrlConfirmation(clientHttp, idFile) + if err != nil { + return "", err + } + + downloaded, err := process.StartDownload(clientHttp, urlDirect, outputName) + if err != nil { + return "", err + } + return downloaded, nil +} + +func DownloadOutput(url string, outputName string) (string, error) { + idFile, err := process.GetIdFromUrl(url) + if err != nil { + return "", err + } + + clientHttp := utils.CreateClientHttp() + urlDirect, err := process.GetUrlConfirmation(clientHttp, idFile) + if err != nil { + return "", err + } + + downloaded, err := process.StartDownload(clientHttp, urlDirect, outputName) + if err != nil { + return "", err + } + return downloaded, nil +} diff --git a/pkg/gdriveapi/files.go b/pkg/gdriveapi/files.go new file mode 100644 index 0000000..c93d490 --- /dev/null +++ b/pkg/gdriveapi/files.go @@ -0,0 +1,42 @@ +package gdriveapi + +import ( + log "github.com/sirupsen/logrus" + "google.golang.org/api/drive/v3" +) + +func CreateFolder(client *drive.Service, nameFolder string) (*drive.File, error) { + + file := &drive.File{Name: nameFolder, MimeType: "application/vnd.google-apps.folder"} + + resp, err := client.Files.Create(file).Do() + if err != nil { + log.Error("An error occurred: %v\n", err) + return nil, err + } + log.Debug("Id folder: %+v", resp.Id) + return resp, nil +} + +func CreateACopy(client *drive.Service, fileId string, nameOutput string, folderId []string) (*drive.File, error) { + + file := &drive.File{Name: nameOutput, Parents: folderId} + + resp, err := client.Files.Copy(fileId, file).Do() + if err != nil { + log.Debug("An error occurred: %v\n", err) + log.Error(err) + return nil, err + } + + log.Debug("Id file copied: %+v", resp.Id) + return resp, err +} + +func Delete(client *drive.Service, fileIde string) error { + error := client.Files.Delete(fileIde).Do() + if error != nil { + log.Error("An error occurred: %v\n", error) + } + return error +} diff --git a/pkg/gdriveapi/permissions.go b/pkg/gdriveapi/permissions.go new file mode 100644 index 0000000..6dea42e --- /dev/null +++ b/pkg/gdriveapi/permissions.go @@ -0,0 +1,17 @@ +package gdriveapi + +import ( + log "github.com/sirupsen/logrus" + "google.golang.org/api/drive/v3" +) + +func MakeSharedFolder(client *drive.Service, idFolder string) (*drive.Permission, error) { + file := &drive.Permission{Role: "reader", Type: "anyone"} + resp, err := client.Permissions.Create(idFolder, file).Do() + if err != nil { + log.Debug("An error occurred: %v\n", err) + return nil, err + } + log.Debug("Type shared folder: %+v", resp.Id) + return resp, nil +}