Golang Script to extract file direct download link for Google Drive files with any size without encountering the dump Google Drive can't scan this file for viruses
webpage.
Last active
October 29, 2023 08:38
-
-
Save AhmedAbouelkher/c9594848523ad88abe9998bca4777839 to your computer and use it in GitHub Desktop.
Golang Script to extract file direct download link for Google Drive files with any size.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"encoding/base64" | |
"flag" | |
"fmt" | |
"net/http" | |
"net/url" | |
"os" | |
"regexp" | |
"strings" | |
"golang.org/x/net/html" | |
"google.golang.org/api/drive/v2" | |
"google.golang.org/api/option" | |
) | |
var ( | |
ctx = context.Background() | |
) | |
func main() { | |
var shareUri string | |
flag.StringVar(&shareUri, "u", "", "share uri") | |
flag.Parse() | |
if shareUri == "" { | |
panic("share uri is required") | |
} | |
fmt.Print(` | |
----------------------------------------------- | |
You are using a beta version of this tool. | |
The tool is not meant to be distributed or used in production. | |
The tool is provided as is, without any warranty. | |
Version: 0.0.2 | |
By: Ahmed Mahmoud | |
----------------------------------------------- | |
`) | |
apiKey := os.Getenv("GOOGLE_API_KEY") | |
if apiKey == "" { | |
panic(` | |
GOOGLE_API_KEY is required, You can get an API key from https://console.developers.google.com/apis/credentials | |
After creating the API key, make sure to enable the following APIs: | |
- Google Drive API | |
Then export the API key as an environment variable: | |
Example: export GOOGLE_API_KEY=your-api-key | |
`) | |
} | |
valid := ValidateShareUrl(shareUri) | |
if !valid { | |
panic("invalid share uri: " + shareUri) | |
} | |
fileId, err := ExtractFileId(shareUri) | |
if err != nil { | |
panic(err) | |
} | |
srv, err := drive.NewService(ctx, option.WithAPIKey(apiKey)) | |
if err != nil { | |
panic("Unable to retrieve Drive client: %v" + err.Error()) | |
} | |
file, err := srv.Files.Get(fileId).Do() | |
if err != nil { | |
panic(err) | |
} | |
client := http.DefaultClient | |
downloadUrl, err := GenerateDownloadUrl(client, file) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Println("Download URL: " + downloadUrl) | |
} | |
func ValidateShareUrl(shareUri string) bool { | |
regexPattern := `https:\/\/drive\.google\.com\/file\/d\/[a-zA-Z0-9_-]+\/view` | |
match, _ := regexp.MatchString(regexPattern, shareUri) | |
return match | |
} | |
func ExtractFileId(shareUri string) (string, error) { | |
u, err := url.Parse(shareUri) | |
if err != nil { | |
return "", err | |
} | |
if u.Host != "drive.google.com" { | |
return "", fmt.Errorf("invalid share uri: %s", shareUri) | |
} | |
s := strings.Split(shareUri, "d/") | |
if len(s) != 2 { | |
return "", fmt.Errorf("invalid share uri: %s", shareUri) | |
} | |
ss := strings.Split(s[1], "/") | |
if len(ss) < 1 { | |
return "", fmt.Errorf("invalid share uri: %s", shareUri) | |
} | |
return ss[0], nil | |
} | |
func DecodeBase64(s string) string { | |
s = strings.Trim(s, " ") | |
s = strings.Trim(s, "\"") | |
d, err := base64.StdEncoding.DecodeString(s) | |
if err != nil { | |
return "" | |
} | |
return string(d) | |
} | |
func GenerateDownloadUrl(client *http.Client, file *drive.File) (string, error) { | |
fileSize := file.FileSize | |
fileId := file.Id | |
// check if file size is larger than 100MB | |
if fileSize < 100*(1<<20) { | |
// simple download | |
// https://drive.google.com/uc?export=download&id=file-id | |
downloadUrl := fmt.Sprintf("https://drive.google.com/uc?export=download&id=%s", fileId) | |
return downloadUrl, nil | |
} | |
// large file download, more complicated download | |
// Example: https://drive.google.com/u/0/uc?id=XXXXXXXXBKPx3G5_UZWA79g79ncqEfQ&export=download | |
scanFailedUrl := fmt.Sprintf("https://drive.google.com/uc?export=download&id=%s", fileId) | |
res, err := client.Get(scanFailedUrl) | |
if err != nil { | |
return "", err | |
} | |
defer res.Body.Close() | |
if res.StatusCode != http.StatusOK { | |
return "", fmt.Errorf("failed to open download url: %s", scanFailedUrl) | |
} | |
// check content type, must be html | |
contentType := res.Header.Get("Content-Type") | |
if contentType != "text/html; charset=utf-8" { | |
return "", fmt.Errorf("download page content type is not html: %s", contentType) | |
} | |
downloadPage, err := html.Parse(res.Body) | |
if err != nil { | |
return "", err | |
} | |
// TODO: Check if the page is "Google Drive Quota Exceeded or Limit Reached" | |
formElem := GetElementByID(downloadPage, "download-form") | |
if formElem == nil { | |
return "", fmt.Errorf("form element was not found in the download page") | |
} | |
formAction := GetAttribute(formElem, "action") | |
if formAction == "" { | |
return "", fmt.Errorf("form action was not found in the download page") | |
} | |
return formAction, nil | |
} | |
func GetElementByID(n *html.Node, id string) *html.Node { | |
if n.Type == html.ElementNode { | |
if attr := GetAttribute(n, "id"); attr == id { | |
return n | |
} | |
} | |
for c := n.FirstChild; c != nil; c = c.NextSibling { | |
if n := GetElementByID(c, id); n != nil { | |
return n | |
} | |
} | |
return nil | |
} | |
func GetAttribute(n *html.Node, key string) string { | |
for _, attr := range n.Attr { | |
if attr.Key == key { | |
return attr.Val | |
} | |
} | |
return "" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment