I have a VBA function which retrieves data from the deepl.com API (translation).
Private Function Translate_Text(INPUT_TEXT As String) As String
Dim apiKey As String
Dim textToTranslate As String
Dim fromLanguage As String
Dim toLanguage As String
Dim request As New MSXML2.XMLHTTP60
Dim response As New MSHTML.HTMLDocument
' Set my API key here
apiKey = myAPIKey
' Get the text to translate from a text box
textToTranslate = INPUT_TEXT
' Set the languages to translate from and to
fromLanguage = "en"
toLanguage = "de"
' Send the request to the DeepL API
request.Open "POST", "https://api-free.deepl.com/v2/translate?", False
request.SetRequestHeader "Content-Type", "application/x-www-form-urlencoded"
request.Send "auth_key=" & apiKey & "&text=" & textToTranslate & "&source_lang=" & fromLanguage & "&target_lang=" & toLanguage
' Parse the response from the API
response.body.innerHTML = request.responseText
Debug.Print request.responseText
This print shows that the request itself was successful:
{"translations":[{"detected_source_language":"EN","text":"Steigerung der Benutzereffizienz"}]}
I then try to access the "text" element:
translatedText = response.getElementsByTagName("text")(0).innerText
However this throws an error ("Object variable or with block variable not defined")
So my question is how I can correctly access the text in the response object.
The request you get from the API is JSON formatted, not HTML.
You can not use getElementsByTagName on JSON that's why it always says its empty.
You would need to use something like VBA-JSON (as suggested by Tim Williams above). https://github.com/VBA-tools/VBA-JSON
When using VBA-JSON you can do something like this:
Dim Json As Object
Set Json = JsonConverter.ParseJson(request.responseText)
Debug.Print Json.text
Related
I am using Postman to send in an authorization request to google to retrieve an access token. When I enter all the required information on the Authorization tab and click on the Get New Access Token button, I get a response back from google with an access token. This proves that the data I am sending in is valid and my project is set up properly on google. Now, I need to convert this request to a VBA request.
When I view the Postman log I can see the data that Postman sends to Google. This is what I see:
And this is the response that google sends back:
This is the VBA code that I am using to try to replicate the Postman call (I have the redirect url set to my local machine which is in the list of approved redirect url's on my google project):
Dim sClient_ID As String
Dim sClient_Secret As String
Dim sGrant_Type As String
Dim sCode As String
Dim sRedirect_URI As String
sClient_ID = "{the client id that google provided to me}"
sClient_Secret = "{the client secret that google provided to me}"
sGrant_Type = "authorization_code"
sCode = "4/0AWtgzh75ZFps55t9vPx-gm_rm8W_uyWQbZwBF1qTLthpUuPjaAXBr9iywT-RweVvagcGPg"
sRedirect_URI = "https://localhost:8080"
' build the json string which will be sent to get the google access token
Dim a As New Scripting.Dictionary
a.Add "grant_type", sGrant_Type
a.Add "code", sCode
a.Add "redirect_uri", sRedirect_URI
a.Add "client_id", sClient_ID
a.Add "client_secret", sClient_Secret
Dim Json_Get_Access_Token As String
Json_Get_Access_Token = JsonConverter.ConvertToJson(a, Whitespace:=" ")
' send the json string via POST
Set httpCall = CreateObject("MSXML2.ServerXMLHTTP")
Dim sTokenURL As String
sTokenURL = "https://oauth2.googleapis.com/token"
httpCall.Open "POST", sTokenURL, False
httpCall.setRequestHeader "Content-Type", "application/json;charset=UTF-8"
httpCall.Send Json_Get_Access_Token
Dim sReturnToken As String
sReturnToken = httpCall.responseText
When I run the code and look at the value is sReturnToken, I see this:
{
"error": "invalid_grant",
"error_description": "Bad Request"
}
Any idea what I'm not setting up properly?
Thank you.
EDIT:
You can actually send the requests as json or encoded URL
Send request as JSON
Set Content-Type to application/json and convert the a dictinary to json
Dim Json_Get_Access_Token As String
Json_Get_Access_Token = JsonConverter.ConvertToJson(a, Whitespace:=" ")
'...
httpCall.Send Json_Get_Access_Token
'...
Send request as Encoded URL
Set Content-Type to application/x-www-form-urlencoded and concatenate the values of the a dictionary like this: data=value&data2=value2, each value must be encoded with WorksheetFunction.EncodeURL function that only works in Excel 2013+ if you have an older version or non-Excel app then use this function
Dim strQueryString as String
For Each vKey In a.Keys()
strQueryString = strQueryString & vKey & "=" & WorksheetFunction.EncodeURL(a(vKey)) & "&"
Next
' Remove last &
strQueryString = Left(strQueryString, Len(strQueryString) - 1)
'...
httpCall.Send strQueryString
'...
Here more info
Tested output:
{
"access_token": "ya29.xxx",
"expires_in": 3599,
"refresh_token": "1//xxx",
"scope": "https://www.googleapis.com/auth/calendar.events.readonly",
"token_type": "Bearer"
}
I'm trying to catch a Response to a POST Request using XMLHTTP using the code below
Dim XMLPage As New MSXML2.XMLHTTP60
Dim HTMLDoc As New MSHTML.HTMLDocument
Dim htmlEle1 As MSHTML.IHTMLElement
Dim htmlEle2 As MSHTML.IHTMLElement
Dim URL As String
Dim elemValue As String
URL = "https://www.informadb.pt/pt/pesquisa/?search=500004064"
XMLPage.Open "GET", URL, False
XMLPage.send
HTMLDoc.body.innerHTML = XMLPage.responseText
For Each htmlEle1 In HTMLDoc.getElementsByTagName("div")
Debug.Print htmlEle1.className
If htmlEle1.className = "styles__SCFileModuleFooter-e6rbca-1 kUUNkj" Then
elemValue = Trim(htmlEle1.innerText)
If InStr(UCase$(elemValue), "CONSTITU") > 0 Then
'Found Value
Exit For
End If
End If
Next htmlEle1
The problem is that I can't find the ClassName "styles__SCFileModuleFooter-e6rbca-1 kUUNkj", because I notice that when I manually insert the value (500004064) in the search box of the URL : https://www.informadb.pt/pt/pesquisa/, the Web Page generates addicinal traffic and turns up at an end point URL : https://www.informadb.pt/pt/pesquisa/empresa/?Duns=453060832, where that className can be found in the Request ResponseText.
My goal is to use the First URL to retrieve the Duns number '453060832', to be able to access the information in the ResponseText of the EndPoint URL. And to catch Duns Number, I need to find a way to get the Endpoint URL, or try to get The POST request response below, and get that value using JSON parser:
{'TotalResults': 1,
'NumberOfPages': 1,
'Results': [{'Duns': '453060832',
'Vat': '500004064',
'Name': 'A PANIFICADORA CENTRAL EBORENSE, S.A.',
'Address': 'BAIRRO DE NOSSA SENHORA DO CARMO,',
'Locality': 'ÉVORA',
'OfficeType': 'HeadOffice',
'FoundIn': None,
'Score': 231.72766,
'PageUrl': '/pt/pesquisa/empresa/?Duns=453060832'}]}
I'm not being able to capture what is really happening using the XMLHTTP Browser request, that seems to be the below steps:
navigate to https://www.informadb.pt/pt/pesquisa/?search=500004064
Webpage generates additional traffic
Amongst that additional traffic is an API POST XHR request which
returns search results as JSON. That request goes to
https://www.informadb.pt/Umbraco/Api/Search/Companies and includes
the 500004064 identifier amongst the arguments within the post body
Based on the API results the browser ends up at the following URI
https://www.informadb.pt/pt/pesquisa/empresa/?Duns=453060832
Can someone help me please, I have to do it using VBA.
Thanks in advance.
A small example how to POST data to your website using VBA, and how to use bare-bones string processing to extract data from the result, as outlined in my comments above.
Function GetVatId(dunsNumber As String) As String
With New MSXML2.XMLHTTP60
.open "POST", "https://www.informadb.pt/Umbraco/Api/Search/Companies", False
.setRequestHeader "Content-Type", "application/json"
.send "{""Page"":0,""PageSize"":5,""SearchTerm"":""" & dunsNumber & """,""Filters"":[{""Key"":""districtFilter"",""Name"":""Distrito"",""Values"":[]},{""Key"":""legalFormFilter"",""Name"":""Forma Jurídica"",""Values"":[]}],""Culture"":""pt""}"
If .status = 200 Then
MsgBox "Response: " & .responseText, vbInformation
GetVatId = Mid(.responseText, InStr(.responseText, """Vat"":""") + 7, 9)
Else
MsgBox "Repsonse status " & .status, vbExclamation
End If
End With
End Function
Usage:
Dim vatId As String
vatId = GetVatId("453060832") ' => "500004064"
For a more robust solution, you should use a JSON parser and -serializer, something like https://github.com/VBA-tools/VBA-JSON.
Need help in solving error in HTTP GET request [closed]
I have the following code in VB.NET framework which should pull the data in json format and add it to a list view control using GET request. However I am getting following error:
System.Threading.Tasks.Task.FromResult(System.Net.Http.HttpResponseMessage)
Dim client = New HttpClient()
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "{Subscription-Key}")
Dim uri = "https://example.com/" & SearchKey
Dim response = client.GetAsync(uri)
lstMer.Items.Add(response.ToString)
Initially I wrote the following code in VBA and it was working fine:
Dim jsonText as String
Set req = New MSXML2.ServerXMLHTTP60
key_id = "{Subscription-Key}"
key_header_name = "Subscription-Key-Id"
liveURL = "https://example.com/" & SearchKey
req.Open "GET", liveURL, False
req.setRequestHeader {Subscription-Key-Id}, {Subscription-Key}
req.setRequestHeader "Host", "site Address"
req.send
jsonText = req.responseText
The requirement is to get the headers from URL and fill a list view.
I think I understand. You don't get an error
System.Threading.Tasks.Task.FromResult(System.Net.Http.HttpResponseMessage)
rather you get that as a result of response.ToString(). This is because you are calling client.GetAsync(uri) directly, which returns a Task.FromResult<TResult>. However, it is meant to be run async, and there are some convenient keywords for achieving this, namely Async / Await. If you await the task, instead of returning a Task.FromResult<TResult>, it will return a TResult. So change your code to do just that
Private Async Sub Foo()
Dim client = New HttpClient()
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "{Subscription-Key}")
Dim uri = "https://example.com/" & SearchKey
Dim response = Await client.GetAsync(uri)
lstMer.Items.Add(response.ToString)
End Sub
See HttpClient.GetAsync and about what it returns Task.FromResult
Question
Is it possible to call Microsoft Graph API using VBA code?
If yes, how to handle O365 authorization? I have seen plenty of topics saying to create an application in Microsoft Azure to get a token but I am surprised I must do that for a simple local use.
What I tried
After discovering Microsoft Graph, I tried this API in Graph Explorer
https://graph.microsoft.com/v1.0/planner/tasks
I was able to create a task in planner!
Consequently, in my mind, it was possible to call this API from VBA code executed directly in Outlook.
I created this macro in Outlook:
Sub TaskPlannerCreation()
Dim PlannerService As New MSXML2.XMLHTTP60
Dim sData As Variant
sData = " { "" ""planId"": ""K9Zv2QHm1U-GSAhd-PTGZfdFeOn"",""bucketId"": ""b6NVNiEIQkGZeBBzn7kWqJvAGvvs"",""title"": ""Outlook task"" } "
With PlannerService
.Open "POST", "https://graph.microsoft.com/v1.0/planner/tasks", False
.setRequestHeader "Content-Type", "application/json"
.setRequestHeader "Accept", "application/json"
.setRequestHeader "User-Agent", "xx"
.Send (sData)
I have an Authorization error with
error code 401
UPDATE on 12-03-2020 :
Solution found to get a Graph Api token analysing URL when calling Graph Explorer (works perfectly for me) :
Function GetToken()
Dim xml As New MSXML2.XMLHTTP60
Dim doc As MSHTML.HTMLDocument
Dim urltoken As String
'copy paste the URL you see when calling Microsoft Graph Explorer and add prompt + domain_hint parameters
urltoken = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_mode=form_post&nonce=graph_explorer&mkt=fr-FR&client_id={clientid}&response_type=token&scope=openid profile User.ReadWrite User.ReadBasic.All Sites.ReadWrite.All Contacts.ReadWrite People.Read Notes.ReadWrite.All Tasks.ReadWrite Mail.ReadWrite Files.ReadWrite.All Calendars.ReadWrite&prompt=none&domain_hint=organizations"
xml.Open "GET", urltoken, False
xml.Send
If xml.readyState = 4 And xml.Status = 200 Then
Set doc = New MSHTML.HTMLDocument
doc.Body.innerHTML = xml.responseText
GetToken = doc.getElementsByName("access_token")(0).Value
sSuccess = True
Else
MsgBox "Error" & vbNewLine & "Ready state: " & xml.readyState & _
vbNewLine & "HTTP request status: " & xml.Status
sSuccess = False
End If
Set xml = Nothing
End Function
So using VBA for calling Graph API is possible :)
So the code you show is only partially correct. Here is what I found to actually work. (This is with what you provided as I actually found a Json parser to work with the data better than the innerHTML methods, I also had to use a different version of MSXML since the one you reference wasnt working for me.)
Function GetToken()
Dim xml As New MSXML2.XMLHTTP60
Dim doc As MSHTML.HTMLDocument
Dim urltoken As String
'copy paste the URL you see when calling Microsoft Graph Explorer and add prompt + domain_hint parameters
urltoken = "https://login.microsoftonline.com/{tenent id}/oauth2/v2.0/token"
xml.Open "POST", urltoken, False
xml.Send("client_id={clientid}&scope=https://graph.microsoft.com/.default&grant_type=client_credentials&client_secret=(cleint secret}")
If xml.readyState = 4 And xml.Status = 200 Then
Set doc = New MSHTML.HTMLDocument
doc.Body.innerHTML = xml.responseText
GetToken = doc.getElementsByName("access_token")(0).Value
sSuccess = True
Else
MsgBox "Error" & vbNewLine & "Ready state: " & xml.readyState & _
vbNewLine & "HTTP request status: " & xml.Status
sSuccess = False
End If
Set xml = Nothing
End Function
I'm trying to scrap data from a website using MSXML2.XMLHTTP object on VBA environment (Excel) and I cannot figure out how to solve this problem! The website is the following:
http://www.detran.ms.gov.br/consulta-de-debitos/
You guys can use the following test data to fill the form:
Placa: oon5868
Renavam: 1021783231
I want to retrieve data like "chassi", with the data above that would be " 9BD374121F5068077".
I do not have problems parsing the html document, the difficult is actually getting the information as response! Code below:
Sub SearchVehicle()
Dim strPlaca As String
Dim strRenavam As String
strPlaca = "oon5868"
strRenavam = "01021783231"
Dim oXmlPage As MSXML2.XMLHTTP60
Dim strUrl As String
Dim strPostData As String
Set oXmlPage = New MSXML2.XMLHTTP60
strUrl = "http://www2.detran.ms.gov.br/detranet/nsite/veiculo/veiculos/retornooooveiculos.asp"
strPostData = "placa=" & strPlaca & "&renavam=" & strRenavam
oXmlPage.Open "POST", strUrl, False
oXmlPage.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
oXmlPage.send strPostData
Debug.Print oXmlPage.responseText
End Sub
The strURL used in the POST method ".../retornooooveiculos.asp" is the one google developer tools and fiddler showed me that was the correct address the website was posting the payload.
When manually accessed, the website retrieve the correct information, but running my code I always get the following response on the .responseText:
<html>Acesse: <b><a href='http://www.detran.ms.gov.br target='_parent'>www.detran.ms.gov.br</a></b></html>
HELP PLEASE, I'm getting crazy trying to solve this puzzle! Why do I get redirected like this?
I need the "CHASSI" information and can't find the correct http Request to do this!
Try the below approach. It should fetch you the content you are after. The thing is you need to supply the Cookie copied from your Request Headers fields in order for your script to work which you can find using devtools.
Sub SearchVehicle()
Const URL As String = "http://www2.detran.ms.gov.br/detranet/nsite/veiculo/veiculos/retornooooveiculos.asp"
Dim HTTP As New ServerXMLHTTP60, HTML As New HTMLDocument
Dim elem As Object, splaca$, srenavam$, qsp$
splaca = "oon5868"
srenavam = "01021783231"
qsp = "placa=" & splaca & "&renavam=" & srenavam
With HTTP
.Open "POST", URL, False
.setRequestHeader "User-Agent", "Mozilla/5.0"
.setRequestHeader "Cookie", "ISAWPLB{07D08995-E67C-4F44-91A1-F6A16337ECD6}={286E0BB1-C5F9-4439-A2CE-A7BE8C3955E0}; ASPSESSIONIDSCSDSCTB=AGDPOBEAAPJLLMKKIGPLBGMJ; 69137927=967930978"
.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
.send qsp
HTML.body.innerHTML = .responseText
End With
For Each elem In HTML.getElementsByTagName("b")
If InStr(elem.innerText, "Chassi:") > 0 Then MsgBox elem.ParentNode.NextSibling.innerText: Exit For
Next elem
End Sub
Once again: fill in the Cookie field by collecting it using your devtools (from Request Headers section), if for some reason my provided Cookie doesn't work for you. Thanks.
Output I'm getting:
9BD374121F5068077