Skip to content

Instantly share code, notes, and snippets.

@tmplinshi
Last active August 25, 2021 05:23
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tmplinshi/b81e135a87c778a5ed16d9e83017c703 to your computer and use it in GitHub Desktop.
Save tmplinshi/b81e135a87c778a5ed16d9e83017c703 to your computer and use it in GitHub Desktop.
; Google Drive API test
#NoEnv
SetWorkingDir %A_ScriptDir%
SetBatchLines, -1
; http://www.howtosolvenow.com/how-to-create-your-google-drive-api-keys/
; http://www.howtosolvenow.com/how-to-get-refresh-token-for-google-drive-api/
client_id := "xxxxxxxxxxx"
client_secret := "xxxxxxxxxxx"
refresh_token := "xxxxxxxxxxx"
client := new gdrive(client_id, client_secret, refresh_token)
; client.proxy := "localhost:1080"
msgbox % client.getAccessToken()
; MsgBox % client.get("/about?fields=*")
; MsgBox % client.get("/about?fields=user")
MsgBox % client.get("/files")
; MsgBox % client.get("/files?q=name = 'temp.xlsx'")
; MsgBox % client.get("/files?q=name contains 'test'&pageSize=2")
; MsgBox % client.getFileId("test.png")
; MsgBox % client.downloadByName("test.png")
; MsgBox % client.upload("C:\Users\weich\Desktop\test.png")
; MsgBox % client.upload("C:\Users\weich\Desktop\test.png", {name: "new-name.png"})
; MsgBox % client.updateFile("1Jva1qXDEDIR8bTvSc_EkJOBY1660aoG5", "C:\Users\weich\Desktop\test.png")
ExitApp
; https://developers.google.com/drive/api/v3/reference
class gdrive
{
whr := ComObjCreate("WinHttp.WinHttpRequest.5.1")
baseUrl := "https://www.googleapis.com/drive/v3"
__New(client_id, client_secret, refresh_token)
{
this.client_id := client_id
this.client_secret := client_secret
this.refresh_token := refresh_token
}
getAccessToken()
{
body := { "client_id": this.client_id
, "client_secret": this.client_secret
, "refresh_token": this.refresh_token
, "grant_type": "refresh_token" }
result := this.http( "POST"
, "https://www.googleapis.com/oauth2/v4/token"
, Jxon_Dump(body)
, {"Content-Type": "application/json"} )
return this.access_token := Jxon_Load(result).access_token
/*
{
"access_token": "xxxxxxxxxxxxxxxxx",
"expires_in": 3599,
"scope": "https://www.googleapis.com/auth/drive",
"token_type": "Bearer"
}
*/
}
get(resource)
{
return this.http("GET", this.baseUrl . resource)
}
post(resource, data, oHeaders := "")
{
return this.http("POST", this.baseUrl . resource, data, oHeaders)
}
simpileUpload(fileName)
{
url := "https://www.googleapis.com/upload/drive/v3/files?uploadType=media"
return this.http("POST", url, this.FileToSafeArray(fileName))
}
updateFile(fileId, fileName)
{
url := "https://www.googleapis.com/upload/drive/v3/files/" fileId "?uploadType=media"
return this.http("PATCH", url, this.FileToSafeArray(fileName))
}
getFileId(name)
{
res := "/files?pageSize=1&q=" this.URIEncode("name='" name "' or name contains '" name "'")
out := Jxon_Load( this.get(res) )
return out.files.1.id
}
upload(fileName, obj_Metadata := "")
{
if !FileExist(fileName)
throw
if !IsObject(obj_Metadata)
{
fName := RegExReplace(fileName, ".*[\\/]")
obj_Metadata := {name: fName}
}
url := "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"
obj := { "whatever": [fileName]
, "i_am_json": {json: Jxon_Dump(obj_Metadata)} }
CreateFormData(retData, retHeader, obj)
return this.http("POST", url, retData, {"Content-Type": retHeader})
}
downloadByName(name)
{
res := "/files?pageSize=1&q=" this.URIEncode("name='" name "' or name contains '" name "'")
out := Jxon_Load( this.get(res) )
if (out.files.1.id)
return this.downloadById(out.files.1.id, out.files.1.name)
}
downloadById(fileId, fileName := "")
{
if (fileName = "")
fileName := Jxon_Load( this.get("/files/" fileId) ).files.1.name
this.get("/files/" fileId "?alt=media")
this.StreamToFile(this.whr.ResponseStream, fileName)
return fileName
}
http(method, url, postData="", obj_headers="")
{
whr := this.whr
whr.Open(method, url, true)
if (this.proxy)
whr.SetProxy(2, this.proxy)
for k, v in obj_headers {
whr.SetRequestHeader(k, v)
}
if (this.access_token) {
whr.SetRequestHeader("Authorization", "Bearer " . this.access_token)
}
if (method = "POST") && !obj_headers.HasKey("Content-Type") {
whr.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
}
whr.Send(postData)
whr.WaitForResponse()
if (whr.Status = 200)
return whr.ResponseText
else
throw whr.Status " " whr.StatusText "`n" whr.GetAllResponseHeaders() "`n`n" whr.ResponseText
}
FileToSafeArray(fileName) {
File := FileOpen(FileName, "r")
safeArr := ComObjArray(0x11, File.length) ; Create SAFEARRAY = VT_ARRAY|VT_UI1
File.RawRead(NumGet(ComObjValue(safeArr) + 8 + A_PtrSize), File.length) ; read raw data
File.Close()
return safeArr
}
; https://autohotkey.com/board/topic/65701-urldownloadtofile-saving-download-to-default-name/?p=650775
; StreamToFile(whr.ResponseStream, "fileName")
StreamToFile(Stream, fileName) {
If (ComObjType(Stream) != 0xD)
Return 0
pIStream := ComObjQuery(Stream, "{0000000c-0000-0000-C000-000000000046}")
oFile := FileOpen(fileName, "w")
While (cbRead != 0)
VarSetCapacity(Buffer, 8192)
, DllCall(NumGet(NumGet(pIStream + 0) + 3 * A_PtrSize)
, "ptr", pIStream, "ptr", &Buffer, "uint", 8192, "ptr*", cbRead)
, oFile.RawWrite(&Buffer, cbRead)
Return 1, ObjRelease(pIStream), oFile.Close()
}
; https://www.autohotkey.com/boards/viewtopic.php?t=63835
URIEncode(str, encoding := "UTF-8") {
VarSetCapacity(var, StrPut(str, encoding))
StrPut(str, &var, encoding)
While code := NumGet(Var, A_Index - 1, "UChar") {
bool := (code > 0x7F || code < 0x30 || code = 0x3D)
UrlStr .= bool ? "%" . Format("{:02X}", code) : Chr(code)
}
Return UrlStr
}
}
; ===============================================================================
; CreateFormData
; 2020-06-08 - Added a temporary approach to handling the json content-type
; example: objParam := { "key": {json: "{'k':'v'}"} }
;
/*
CreateFormData - Creates "multipart/form-data" for http post
https://www.autohotkey.com/boards/viewtopic.php?t=7647
Usage: CreateFormData(ByRef retData, ByRef retHeader, objParam)
retData - (out) Data used for HTTP POST.
retHeader - (out) Content-Type header used for HTTP POST.
objParam - (in) An object defines the form parameters.
To specify files, use array as the value. Example:
objParam := { "key1": "value1"
, "upload[]": ["1.png", "2.png"] }
Requirements: BinArr.ahk -- https://gist.github.com/tmplinshi/a97d9a99b9aa5a65fd20
Version : / 2020-06-08 - Added a temporary approach to handling the json content-type
example: objParam := { "key": {json: "{'k':'v'}"} }
1.30 / 2019-01-13 - The file parameters are now placed at the end of the retData
1.20 / 2016-06-17 - Added CreateFormData_WinInet(), which can be used for VxE's HTTPRequest().
1.10 / 2015-06-23 - Fixed a bug
1.00 / 2015-05-14
*/
; Used for WinHttp.WinHttpRequest.5.1, Msxml2.XMLHTTP ...
CreateFormData(ByRef retData, ByRef retHeader, objParam) {
New CreateFormData(retData, retHeader, objParam)
}
; Used for WinInet
CreateFormData_WinInet(ByRef retData, ByRef retHeader, objParam) {
New CreateFormData(safeArr, retHeader, objParam)
size := safeArr.MaxIndex() + 1
VarSetCapacity(retData, size, 1)
DllCall("oleaut32\SafeArrayAccessData", "ptr", ComObjValue(safeArr), "ptr*", pdata)
DllCall("RtlMoveMemory", "ptr", &retData, "ptr", pdata, "ptr", size)
DllCall("oleaut32\SafeArrayUnaccessData", "ptr", ComObjValue(safeArr))
}
Class CreateFormData {
__New(ByRef retData, ByRef retHeader, objParam) {
CRLF := "`r`n"
Boundary := this.RandomBoundary()
BoundaryLine := "------------------------------" . Boundary
; Loop input paramters
binArrs := []
fileArrs := []
For k, v in objParam
{
If IsObject(v) {
if v.HasKey("json")
{
str := BoundaryLine . CRLF
. "Content-Type: application/json" . CRLF . CRLF
. v.json . CRLF
binArrs.Push( BinArr_FromString(str) )
}
else
{
For i, FileName in v
{
str := BoundaryLine . CRLF
. "Content-Disposition: form-data; name=""" . k . """; filename=""" . FileName . """" . CRLF
. "Content-Type: " . this.MimeType(FileName) . CRLF . CRLF
fileArrs.Push( BinArr_FromString(str) )
fileArrs.Push( BinArr_FromFile(FileName) )
fileArrs.Push( BinArr_FromString(CRLF) )
}
}
} Else {
str := BoundaryLine . CRLF
. "Content-Disposition: form-data; name=""" . k """" . CRLF . CRLF
. v . CRLF
binArrs.Push( BinArr_FromString(str) )
}
}
binArrs.push( fileArrs* )
str := BoundaryLine . "--" . CRLF
binArrs.Push( BinArr_FromString(str) )
retData := BinArr_Join(binArrs*)
retHeader := "multipart/form-data; boundary=----------------------------" . Boundary
}
RandomBoundary() {
str := "0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z"
Sort, str, D| Random
str := StrReplace(str, "|")
Return SubStr(str, 1, 12)
}
MimeType(FileName) {
n := FileOpen(FileName, "r").ReadUInt()
Return (n = 0x474E5089) ? "image/png"
: (n = 0x38464947) ? "image/gif"
: (n&0xFFFF = 0x4D42 ) ? "image/bmp"
: (n&0xFFFF = 0xD8FF ) ? "image/jpeg"
: (n&0xFFFF = 0x4949 ) ? "image/tiff"
: (n&0xFFFF = 0x4D4D ) ? "image/tiff"
: "application/octet-stream"
}
}
; ===============================================================================
BinArr_FromString(str) {
oADO := ComObjCreate("ADODB.Stream")
oADO.Type := 2 ; adTypeText
oADO.Mode := 3 ; adModeReadWrite
oADO.Open
oADO.Charset := "UTF-8"
oADO.WriteText(str)
oADO.Position := 0
oADO.Type := 1 ; adTypeBinary
oADO.Position := 3 ; Skip UTF-8 BOM
return oADO.Read, oADO.Close
}
BinArr_FromFile(FileName) {
oADO := ComObjCreate("ADODB.Stream")
oADO.Type := 1 ; adTypeBinary
oADO.Open
oADO.LoadFromFile(FileName)
return oADO.Read, oADO.Close
}
BinArr_Join(Arrays*) {
oADO := ComObjCreate("ADODB.Stream")
oADO.Type := 1 ; adTypeBinary
oADO.Mode := 3 ; adModeReadWrite
oADO.Open
For i, arr in Arrays
oADO.Write(arr)
oADO.Position := 0
return oADO.Read, oADO.Close
}
BinArr_ToString(BinArr, Encoding := "UTF-8") {
oADO := ComObjCreate("ADODB.Stream")
oADO.Type := 1 ; adTypeBinary
oADO.Mode := 3 ; adModeReadWrite
oADO.Open
oADO.Write(BinArr)
oADO.Position := 0
oADO.Type := 2 ; adTypeText
oADO.Charset := Encoding
return oADO.ReadText, oADO.Close
}
BinArr_ToFile(BinArr, FileName) {
oADO := ComObjCreate("ADODB.Stream")
oADO.Type := 1 ; adTypeBinary
oADO.Open
oADO.Write(BinArr)
oADO.SaveToFile(FileName, 2)
oADO.Close
}
; ==================================================================================
; by Coco, https://www.autohotkey.com/boards/viewtopic.php?t=627
Jxon_Load(ByRef src, args*)
{
static q := Chr(34)
key := "", is_key := false
stack := [ tree := [] ]
is_arr := { (tree): 1 }
next := q . "{[01234567890-tfn"
pos := 0
while ( (ch := SubStr(src, ++pos, 1)) != "" )
{
if InStr(" `t`n`r", ch)
continue
if !InStr(next, ch, true)
{
ln := ObjLength(StrSplit(SubStr(src, 1, pos), "`n"))
col := pos - InStr(src, "`n",, -(StrLen(src)-pos+1))
msg := Format("{}: line {} col {} (char {})"
, (next == "") ? ["Extra data", ch := SubStr(src, pos)][1]
: (next == "'") ? "Unterminated string starting at"
: (next == "\") ? "Invalid \escape"
: (next == ":") ? "Expecting ':' delimiter"
: (next == q) ? "Expecting object key enclosed in double quotes"
: (next == q . "}") ? "Expecting object key enclosed in double quotes or object closing '}'"
: (next == ",}") ? "Expecting ',' delimiter or object closing '}'"
: (next == ",]") ? "Expecting ',' delimiter or array closing ']'"
: [ "Expecting JSON value(string, number, [true, false, null], object or array)"
, ch := SubStr(src, pos, (SubStr(src, pos)~="[\]\},\s]|$")-1) ][1]
, ln, col, pos)
throw Exception(msg, -1, ch)
}
is_array := is_arr[obj := stack[1]]
if i := InStr("{[", ch)
{
val := (proto := args[i]) ? new proto : {}
is_array? ObjPush(obj, val) : obj[key] := val
ObjInsertAt(stack, 1, val)
is_arr[val] := !(is_key := ch == "{")
next := q . (is_key ? "}" : "{[]0123456789-tfn")
}
else if InStr("}]", ch)
{
ObjRemoveAt(stack, 1)
next := stack[1]==tree ? "" : is_arr[stack[1]] ? ",]" : ",}"
}
else if InStr(",:", ch)
{
is_key := (!is_array && ch == ",")
next := is_key ? q : q . "{[0123456789-tfn"
}
else ; string | number | true | false | null
{
if (ch == q) ; string
{
i := pos
while i := InStr(src, q,, i+1)
{
val := StrReplace(SubStr(src, pos+1, i-pos-1), "\\", "\u005C")
static end := A_AhkVersion<"2" ? 0 : -1
if (SubStr(val, end) != "\")
break
}
if !i ? (pos--, next := "'") : 0
continue
pos := i ; update pos
val := StrReplace(val, "\/", "/")
, val := StrReplace(val, "\" . q, q)
, val := StrReplace(val, "\b", "`b")
, val := StrReplace(val, "\f", "`f")
, val := StrReplace(val, "\n", "`n")
, val := StrReplace(val, "\r", "`r")
, val := StrReplace(val, "\t", "`t")
i := 0
while i := InStr(val, "\",, i+1)
{
if (SubStr(val, i+1, 1) != "u") ? (pos -= StrLen(SubStr(val, i)), next := "\") : 0
continue 2
; \uXXXX - JSON unicode escape sequence
xxxx := Abs("0x" . SubStr(val, i+2, 4))
if (A_IsUnicode || xxxx < 0x100)
val := SubStr(val, 1, i-1) . Chr(xxxx) . SubStr(val, i+6)
}
if is_key
{
key := val, next := ":"
continue
}
}
else ; number | true | false | null
{
val := SubStr(src, pos, i := RegExMatch(src, "[\]\},\s]|$",, pos)-pos)
; For numerical values, numerify integers and keep floats as is.
; I'm not yet sure if I should numerify floats in v2.0-a ...
static number := "number", integer := "integer"
if val is %number%
{
if val is %integer%
val += 0
}
; in v1.1, true,false,A_PtrSize,A_IsUnicode,A_Index,A_EventInfo,
; SOMETIMES return strings due to certain optimizations. Since it
; is just 'SOMETIMES', numerify to be consistent w/ v2.0-a
else if (val == "true" || val == "false")
val := %val% + 0
; AHK_H has built-in null, can't do 'val := %value%' where value == "null"
; as it would raise an exception in AHK_H(overriding built-in var)
else if (val == "null")
val := ""
; any other values are invalid, continue to trigger error
else if (pos--, next := "#")
continue
pos += i-1
}
is_array? ObjPush(obj, val) : obj[key] := val
next := obj==tree ? "" : is_array ? ",]" : ",}"
}
}
return tree[1]
}
Jxon_Dump(obj, indent:="", lvl:=1)
{
static q := Chr(34)
if IsObject(obj)
{
static Type := Func("Type")
if Type ? (Type.Call(obj) != "Object") : (ObjGetCapacity(obj) == "")
throw Exception("Object type not supported.", -1, Format("<Object at 0x{:p}>", &obj))
is_array := 0
for k in obj
is_array := k == A_Index
until !is_array
static integer := "integer"
if indent is %integer%
{
if (indent < 0)
throw Exception("Indent parameter must be a postive integer.", -1, indent)
spaces := indent, indent := ""
Loop % spaces
indent .= " "
}
indt := ""
Loop, % indent ? lvl : 0
indt .= indent
lvl += 1, out := "" ; Make #Warn happy
for k, v in obj
{
if IsObject(k) || (k == "")
throw Exception("Invalid object key.", -1, k ? Format("<Object at 0x{:p}>", &obj) : "<blank>")
if !is_array
out .= ( ObjGetCapacity([k], 1) ? Jxon_Dump(k) : q . k . q ) ;// key
. ( indent ? ": " : ":" ) ; token + padding
out .= Jxon_Dump(v, indent, lvl) ; value
. ( indent ? ",`n" . indt : "," ) ; token + indent
}
if (out != "")
{
out := Trim(out, ",`n" . indent)
if (indent != "")
out := "`n" . indt . out . "`n" . SubStr(indt, StrLen(indent)+1)
}
return is_array ? "[" . out . "]" : "{" . out . "}"
}
; Number
else if (ObjGetCapacity([obj], 1) == "")
return obj
; String (null -> not supported by AHK)
if (obj != "")
{
obj := StrReplace(obj, "\", "\\")
, obj := StrReplace(obj, "/", "\/")
, obj := StrReplace(obj, q, "\" . q)
, obj := StrReplace(obj, "`b", "\b")
, obj := StrReplace(obj, "`f", "\f")
, obj := StrReplace(obj, "`n", "\n")
, obj := StrReplace(obj, "`r", "\r")
, obj := StrReplace(obj, "`t", "\t")
static needle := (A_AhkVersion<"2" ? "O)" : "") . "[^\x20-\x7e]"
while RegExMatch(obj, needle, m)
obj := StrReplace(obj, m[0], Format("\u{:04X}", Ord(m[0])))
}
return q . obj . q
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment