How to build custom Zapier integration for Xero - json

I am attempting to build my own integration in zapier that will allow me to create quotes in Xero (a feature not currently supported natively). I've been using this this post and this reference to help me.
I've gotten to the point where I'm creating the action and testing it with test data. Unfortunately, the response I get is "Got 400 calling POST https://identity.xero.com/connect/token, expected 2xx." Perhaps I'm sending the json data incorrectly. I've tried using the 'pretty' and 'raw' ways of sending data:
Could a zapier "expert" help me with this? Perhaps by creating their own xero integration?
EDIT
Not sure if necessary, but blocked out the IDs. Although I now see that I didn't do that for the contactID in the first post lol...

Here is how to get it done, but remember that you will need to have a search action to find information for the required ID's. Given your error I think the problem is that you did not have the tenantId that should be defined in your header like so: 'xero-tenant-id': 'YOURNUMBERHERE'. See step 8 below to compare it to yours.
In case you can't find it, these are the steps I took:
XERO
Create Account
Create Xero App and add the Zapier OAuth Redirect URL to the Xero redirect section(from your 'Zapier Dev' app on 'step 2').
ZAPIER
In your dev app, add the CLient ID & Secret from xero to the appropriate sections in 'Zapier Dev step 3'
Add the POST endpoint (requested in 'Zapier Dev step 4') https://login.xero.com/identity/connect/authorize
with the HTTP headers:
response_type: code
client_id: {{process.env.CLIENT_ID}}
redirect_uri: {{bundle.inputData.redirect_uri}}
state: {{bundle.inputData.state}}
Add the scope: openid profile email accounting.transactions
Refresh token ('Zapier Dev step 4: Access Token') can be obtained using this:
REFRESH TOKEN: POST https://identity.xero.com/connect/token
TEST CALL: GET https://api.xero.com/connections
-Keep the returned tenantId for later use
-Test the authentication. Does it work? If yes, move on to step 7.
-If test fails: review for typos, check for correct url's and make sure your Headers match what xero requires (see link at bottom).
Add Action called createQuote
-Add input contactID
-Add input lineitem with label of description
-Add input tenantId
Add POST to API Config at url https://api.xero.com/api.xro/2.0/Quotes
Example POST:
const options = {
url: 'https://api.xero.com/api.xro/2.0/Quotes/',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${bundle.authData.access_token}`,
'xero-tenant-id': bundle.inputData.tenantID
},
params: {
},
body: {
"Contact": {
"ContactID": bundle.inputData.ContactID
},
"Date": "2019-11-29",
"LineItems": [
{
"Description": bundle.inputData.LineItems
}
]
}
}
return z.request(options)
.then((response) => {
response.throwForStatus();
const results = z.JSON.parse(response.content);
return results;
});
Plug in the test contactID, tenantID, lineitems and test it out
After completing this you will need to create a search action to grab the contactID and tenantID if you want it all automated. If you have problems I found the start-up doc to be useful.

Related

Google Sheets API OAuth Refresh Token Only Issued Once Per Account [duplicate]

I want to get the access token from Google. The Google API says that to get the access token, send the code and other parameters to token generating page, and the response will be a JSON Object like :
{
"access_token" : "ya29.AHES6ZTtm7SuokEB-RGtbBty9IIlNiP9-eNMMQKtXdMP3sfjL1Fc",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/HKSmLFXzqP0leUihZp2xUt3-5wkU7Gmu2Os_eBnzw74"
}
However, I'm not receiving the refresh token. The response in my case is:
{
"access_token" : "ya29.sddsdsdsdsds_h9v_nF0IR7XcwDK8XFB2EbvtxmgvB-4oZ8oU",
"token_type" : "Bearer",
"expires_in" : 3600
}
The refresh_token is only provided on the first authorization from the user. Subsequent authorizations, such as the kind you make while testing an OAuth2 integration, will not return the refresh_token again. :)
Go to the page showing Apps with access to your account:
https://myaccount.google.com/u/0/permissions.
Under the Third-party apps menu, choose your app.
Click Remove access and then click Ok to confirm
The next OAuth2 request you make will return a refresh_token (providing that it also includes the 'access_type=offline' query parameter.
Alternatively, you can add the query parameters prompt=consent&access_type=offline to the OAuth redirect (see Google's OAuth 2.0 for Web Server Applications page).
This will prompt the user to authorize the application again and will always return a refresh_token.
In order to get the refresh token you have to add both approval_prompt=force and access_type="offline"
If you are using the java client provided by Google it will look like this:
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, getClientSecrets(), scopes)
.build();
AuthorizationCodeRequestUrl authorizationUrl =
flow.newAuthorizationUrl().setRedirectUri(callBackUrl)
.setApprovalPrompt("force")
.setAccessType("offline");
I'd like to add a bit more info on this subject for those frustrated souls who encounter this issue. The key to getting a refresh token for an offline app is to make sure you are presenting the consent screen. The refresh_token is only returned immediately after a user grants authorization by clicking "Allow".
The issue came up for me (and I suspect many others) after I'd been doing some testing in a development environment and therefore already authorized my application on a given account. I then moved to production and attempted to authenticate again using an account which was already authorized. In this case, the consent screen will not come up again and the api will not return a new refresh token. To make this work, you must force the consent screen to appear again by either:
prompt=consent
or
approval_prompt=force
Either one will work but you should not use both. As of 2021, I'd recommend using prompt=consent since it replaces the older parameter approval_prompt and in some api versions, the latter was actually broken (https://github.com/googleapis/oauth2client/issues/453). Also, prompt is a space delimited list so you can set it as prompt=select_account%20consent if you want both.
Of course you also need:
access_type=offline
Additional reading:
Docs: https://developers.google.com/identity/protocols/oauth2/web-server#request-parameter-prompt
Docs: https://developers.google.com/identity/protocols/oauth2/openid-connect#re-consent
Discussion about this issue: https://github.com/googleapis/google-api-python-client/issues/213
I searched a long night and this is doing the trick:
Modified user-example.php from admin-sdk
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$authUrl = $client->createAuthUrl();
echo "<a class='login' href='" . $authUrl . "'>Connect Me!</a>";
then you get the code at the redirect url
and the authenticating with the code and getting the refresh token
$client()->authenticate($_GET['code']);
echo $client()->getRefreshToken();
You should store it now ;)
When your accesskey times out just do
$client->refreshToken($theRefreshTokenYouHadStored);
This has caused me some confusion so I thought I'd share what I've come to learn the hard way:
When you request access using the access_type=offline and approval_prompt=force parameters you should receive both an access token and a refresh token. The access token expires soon after you receive it and you will need to refresh it.
You correctly made the request to get a new access token and received the response that has your new access token. I was also confused by the fact that I didn't get a new refresh token. However, this is how it is meant to be since you can use the same refresh token over and over again.
I think some of the other answers assume that you wanted to get yourself a new refresh token for some reason and sugggested that you re-authorize the user but in actual fact, you don't need to since the refresh token you have will work until revoked by the user.
Rich Sutton's answer finally worked for me, after I realized that adding access_type=offline is done on the front end client's request for an authorization code, not the back end request that exchanges that code for an access_token. I've added a comment to his answer and this link at Google for more info about refreshing tokens.
P.S. If you are using Satellizer, here is how to add that option to the $authProvider.google in AngularJS.
In order to get the refresh_token you need to include access_type=offline in the OAuth request URL. When a user authenticates for the first time you will get back a non-nil refresh_token as well as an access_token that expires.
If you have a situation where a user might re-authenticate an account you already have an authentication token for (like #SsjCosty mentions above), you need to get back information from Google on which account the token is for. To do that, add profile to your scopes. Using the OAuth2 Ruby gem, your final request might look something like this:
client = OAuth2::Client.new(
ENV["GOOGLE_CLIENT_ID"],
ENV["GOOGLE_CLIENT_SECRET"],
authorize_url: "https://accounts.google.com/o/oauth2/auth",
token_url: "https://accounts.google.com/o/oauth2/token"
)
# Configure authorization url
client.authorize_url(
scope: "https://www.googleapis.com/auth/analytics.readonly profile",
redirect_uri: callback_url,
access_type: "offline",
prompt: "select_account"
)
Note the scope has two space-delimited entries, one for read-only access to Google Analytics, and the other is just profile, which is an OpenID Connect standard.
This will result in Google providing an additional attribute called id_token in the get_token response. To get information out of the id_token, check out this page in the Google docs. There are a handful of Google-provided libraries that will validate and “decode” this for you (I used the Ruby google-id-token gem). Once you get it parsed, the sub parameter is effectively the unique Google account ID.
Worth noting, if you change the scope, you'll get back a refresh token again for users that have already authenticated with the original scope. This is useful if, say, you have a bunch of users already and don't want to make them all un-auth the app in Google.
Oh, and one final note: you don't need prompt=select_account, but it's useful if you have a situation where your users might want to authenticate with more than one Google account (i.e., you're not using this for sign-in / authentication).
1. How to get 'refresh_token' ?
Solution: access_type='offline' option should be used when generating authURL.
source : Using OAuth 2.0 for Web Server Applications
2. But even with 'access_type=offline', I am not getting the 'refresh_token' ?
Solution: Please note that you will get it only on the first request, so if you are storing it somewhere and there is a provision to overwrite this in your code when getting new access_token after previous expires, then make sure not to overwrite this value.
From Google Auth Doc : (this value = access_type)
This value instructs the Google authorization server to return a
refresh token and an access token the first time that your application
exchanges an authorization code for tokens.
If you need 'refresh_token' again, then you need to remove access for your app as by following the steps written in Rich Sutton's answer.
I'm using nodejs client for access to private data
The solution was add the promp property with value consent to the settings object in oAuth2Client.generateAuthUrl function.
Here is my code:
const getNewToken = (oAuth2Client, callback) => {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
prompt: 'consent',
scope: SCOPES,
})
console.log('Authorize this app by visiting this url:', authUrl)
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
rl.question('Enter the code from that page here: ', (code) => {
rl.close()
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error while trying to retrieve access token', err)
oAuth2Client.setCredentials(token)
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err)
console.log('Token stored to', TOKEN_PATH)
})
callback(oAuth2Client)
})
})
}
You can use the online parameters extractor to get the code for generate your token:
Online parameters extractor
Here is the complete code from google official docs:
https://developers.google.com/sheets/api/quickstart/nodejs
I hope the information is useful
Setting this will cause the refresh token to be sent every time:
$client->setApprovalPrompt('force');
an example is given below (php):
$client = new Google_Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setRedirectUri($redirect_uri);
$client->addScope("email");
$client->addScope("profile");
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
For me I was trying out CalendarSampleServlet provided by Google. After 1 hour the access_key times out and there is a redirect to a 401 page. I tried all the above options but they didn't work. Finally upon checking the source code for 'AbstractAuthorizationCodeServlet', I could see that redirection would be disabled if credentials are present, but ideally it should have checked for refresh token!=null. I added below code to CalendarSampleServlet and it worked after that. Great relief after so many hours of frustration . Thank God.
if (credential.getRefreshToken() == null) {
AuthorizationCodeRequestUrl authorizationUrl = authFlow.newAuthorizationUrl();
authorizationUrl.setRedirectUri(getRedirectUri(req));
onAuthorization(req, resp, authorizationUrl);
credential = null;
}
Using offline access and prompt:consent worked well to me:
auth2 = gapi.auth2.init({
client_id: '{cliend_id}'
});
auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback);
In order to get new refresh_token each time on authentication the type of OAuth 2.0 credentials created in the dashboard should be "Other". Also as mentioned above the access_type='offline' option should be used when generating the authURL.
When using credentials with type "Web application" no combination of prompt/approval_prompt variables will work - you will still get the refresh_token only on the first request.
To get a refresh token using postman, here is an example of the configurations
Expected Response
now google had refused those parameters in my request (access_type, prompt)... :( and there is no "Revoke Access" button at all. I'm frustrating because of getting back my refresh_token lol
UPDATE:
I found the answer in here :D you can get back the refresh token by a request
https://developers.google.com/identity/protocols/OAuth2WebServer
curl -H "Content-type:application/x-www-form-urlencoded" \
https://accounts.google.com/o/oauth2/revoke?token={token}
The token can be an access token or a refresh token. If the token is an access token and it has a corresponding refresh token, the refresh token will also be revoked.
If the revocation is successfully processed, then the status code of the response is 200. For error conditions, a status code 400 is returned along with an error code.
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010_000;
use utf8;
binmode STDOUT, ":encoding(utf8)";
use Text::CSV_XS;
use FindBin;
use lib $FindBin::Bin . '/../lib';
use Net::Google::Spreadsheets::V4;
use Net::Google::DataAPI::Auth::OAuth2;
use lib 'lib';
use Term::Prompt;
use Net::Google::DataAPI::Auth::OAuth2;
use Net::Google::Spreadsheets;
use Data::Printer ;
my $oauth2 = Net::Google::DataAPI::Auth::OAuth2->new(
client_id => $ENV{CLIENT_ID},
client_secret => $ENV{CLIENT_SECRET},
scope => ['https://www.googleapis.com/auth/spreadsheets'],
);
my $url = $oauth2->authorize_url();
# system("open '$url'");
print "go to the following url with your browser \n" ;
print "$url\n" ;
my $code = prompt('x', 'paste code: ', '', '');
my $objToken = $oauth2->get_access_token($code);
my $refresh_token = $objToken->refresh_token() ;
print "my refresh token is : \n" ;
# debug p($refresh_token ) ;
p ( $objToken ) ;
my $gs = Net::Google::Spreadsheets::V4->new(
client_id => $ENV{CLIENT_ID}
, client_secret => $ENV{CLIENT_SECRET}
, refresh_token => $refresh_token
, spreadsheet_id => '1hGNULaWpYwtnMDDPPkZT73zLGDUgv5blwJtK7hAiVIU'
);
my($content, $res);
my $title = 'My foobar sheet';
my $sheet = $gs->get_sheet(title => $title);
# create a sheet if does not exit
unless ($sheet) {
($content, $res) = $gs->request(
POST => ':batchUpdate',
{
requests => [
{
addSheet => {
properties => {
title => $title,
index => 0,
},
},
},
],
},
);
$sheet = $content->{replies}[0]{addSheet};
}
my $sheet_prop = $sheet->{properties};
# clear all cells
$gs->clear_sheet(sheet_id => $sheet_prop->{sheetId});
# import data
my #requests = ();
my $idx = 0;
my #rows = (
[qw(name age favorite)], # header
[qw(tarou 31 curry)],
[qw(jirou 18 gyoza)],
[qw(saburou 27 ramen)],
);
for my $row (#rows) {
push #requests, {
pasteData => {
coordinate => {
sheetId => $sheet_prop->{sheetId},
rowIndex => $idx++,
columnIndex => 0,
},
data => $gs->to_csv(#$row),
type => 'PASTE_NORMAL',
delimiter => ',',
},
};
}
# format a header row
push #requests, {
repeatCell => {
range => {
sheetId => $sheet_prop->{sheetId},
startRowIndex => 0,
endRowIndex => 1,
},
cell => {
userEnteredFormat => {
backgroundColor => {
red => 0.0,
green => 0.0,
blue => 0.0,
},
horizontalAlignment => 'CENTER',
textFormat => {
foregroundColor => {
red => 1.0,
green => 1.0,
blue => 1.0
},
bold => \1,
},
},
},
fields => 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)',
},
};
($content, $res) = $gs->request(
POST => ':batchUpdate',
{
requests => \#requests,
},
);
exit;
#Google Sheets API, v4
# Scopes
# https://www.googleapis.com/auth/drive View and manage the files in your Google D# # i# rive
# https://www.googleapis.com/auth/drive.file View and manage Google Drive files and folders that you have opened or created with this app
# https://www.googleapis.com/auth/drive.readonly View the files in your Google Drive
# https://www.googleapis.com/auth/spreadsheets View and manage your spreadsheets in Google Drive
# https://www.googleapis.com/auth/spreadsheets.readonly View your Google Spreadsheets
My solution was a bit weird..i tried every solution i found on internet and nothing. Surprisely this worked: delete the credentials.json, refresh, vinculate your app in your account again. The new credentials.json file will have the refresh token. Backup this file somewhere.
Then keep using your app until the refresh token error comes again. Delete the crendetials.json file that now is only with an error message (this hapenned in my case), then paste you old credentials file in the folder, its done!
Its been 1 week since ive done this and had no more problems.
Adding access_type=offline to the authorisation Google authorisation URL did the trick for me. I am using Java and Spring framework.
This is the code that creates the client registration:
return CommonOAuth2Provider.GOOGLE
.getBuilder(client)
.scope("openid", "profile", "email", "https://www.googleapis.com/auth/gmail.send")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth?access_type=offline")
.clientId(clientId)
.redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
.clientSecret(clientSecret)
.build();
The important part here is the authorization URI, to which ?access_type=offline is appended.

Understanding JSON and Dropbox API pulls

so I'm jumping in head first here and trying to learn something new... I have an Angular Function that will pull in all the contents of a folder using the Dropbox API and list it into a JSON file (and later display it on a webpage.)
what I want to do next is to get a share link for all those files using the create_shared_link_with_settings API and put them into a JSON file for all the files in the folder.
here's what I have for the first part. If you could help me out or point me in the right direction on the proper way to tackle this.
var app = angular.module("content-review", []);
app.controller("Content-folders-4K", function ($scope, $http) {
$http({
url: 'https://api.dropboxapi.com/2/files/list_folder',
method: 'POST',
processData: false,
contentType: 'application/json',
data: ({
"path": "/MIG/Projects/Hippo content Watermark/HD/011_Blue"
}),
headers: {
"Authorization": "Bearer APIKEYTHATIMNOTSHARING",
},
})
.then(function (response) {
$scope.UHD = response.data.entries;
$scope.json = angular.toJson(response.data, true);
console.log($scope.json)
});
});
To create a shared link for a file or folder in Dropbox, the /2/sharing/create_shared_link_with_settings endpoint you mentioned is the correct endpoint to use. You can find the documentation here. There's also an example of calling it in curl that you can translate for use with your own HTTPS client.
You specify which item you want the link for using the path parameter similar to how you did for the /2/files/list_folder call you showed. You can get the path value from the path_lower value for each "entry" in the /2/files/list_folder responses.
Note that if a shared link already exists for the item, you'll get a shared_link_already_exists error. The error will include the existing shared link only if the settings for the existing link match the settings you requested. Otherwise, you'll need to call /2/sharing/list_shared_links to get the existing link.
Either way, you can parse the result of the successful call to get the SharedLinkMetadata.url value for use in your app.

How to turn on email forwarding with Google Script

A brief description of the project: I am looking to toggle the email forwarding option in the settings of one of my gmail accounts through a google script. This will be a function I would like to call every night between certain hours forwarding my mail from main_email#gmail to secondary_email#gmail.
I am having a difficult time finding the easiest way to toggle this through a google script. The simplest solution seems to be described here where they use an HTTP request. However in all honesty I don't completely understand how it all works, much less if it is the simplest way.
https://developers.google.com/gmail/api/v1/reference/users/settings/updateAutoForwarding
The code that I try and run on the gmail account to enable/disable email forwarding is the following:
function updateForwarding() {
var userID = "main_email#gmail.com"
var response = UrlFetchApp.fetch("https://www.googleapis.com/gmail/v1/users/" + userID + "/settings/autoForwarding", {
method: 'put',
enabled: true,
emailAddress: "secondary_email#gmail.com",
disposition: "leaveInInbox"
});
Logger.log(response.getContentText());
}
However I get the following error:
Request failed for
https://www.googleapis.com/gmail/v1/users/main_email#gmail.com/settings/autoForwarding
returned code 401. Truncated server response: { "error": { "errors": [
{ "domain": "global", "reason": "required", "message": "Login
Required", "locationType": "header", ... (use muteHttpExceptions
option to examine full response) (line 4, file "Code")
I recognize this is shows I need to provide credentials for making the request, but I don't understand how I would do that. I read on the tutorial (https://developers.google.com/gmail/api/auth/about-auth) I need to authorize my app with gmail and get an API key, so I have gone to the google developers console to create that. However, I have no idea how to authenticate or make the call through a Google script after a few hours of google.
Here are the key and secret I was given:
Is this the easiest solution to toggle gmail forwarding? If so, how do I authenticate my call? If not, what is the easiest solution to being able to toggle my gmail forwarding off/on?
You need to pass oAuth token in header information
function updateForwarding() {
var userID = "main_email#gmail.com";
var header = {
Authorization: 'Bearer ' + ScriptApp.getOAuthToken(),
}
var response = UrlFetchApp.fetch("https://www.googleapis.com/gmail/v1/users/" + userID + "/settings/autoForwarding", {
method: 'put',
enabled: true,
headers: header,
emailAddress: "secondary_email#gmail.com",
disposition: "leaveInInbox"
});
Logger.log(response.getContentText());
}
As noted in the authorization section of https://developers.google.com/gmail/api/v1/reference/users/settings/updateAutoForwarding, you need to use OAuth with the given scopes to make that call, not just an API key. You appear to have a client id, but you need to plug this into a library to handle the OAuth process for you. The OAuth process will then give you a Bearer token to add to your request (although most OAuth libraries will handle this for you).
It looks like https://github.com/googlesamples/apps-script-oauth2 is the current recommened way to do this if you're using UrlFetchApp (based on https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app).

How to create microsoft/google translate button for each div separately?

How to create microsoft or google translate button for each div?
Each div has content in different language and I would like to add a translate button for each div and make it respond to only that div like the button in the following link.
http://www.bing.com/widget/translator
But when I use the code mentioned in the link above, it translates the whole webpage. I would like to translate each div separately by clicking on the respective translate button.
Can the same thing be done easily using google translate?
Any translator is fine with me. Kindly help. Thanks.
This how the users' post appear on my website.
I would like to have a translate button for each of the divs so that the users can translate each div into any language they want.
Each of my div has an id.
Below I'm explaining how to get started with Microsoft Translator API. The very same functionality can be implemented via Google Translate API however it was somehow easier for me with MS as they offer 2M characters/monthly translation for free whereas Google charges minimum of 1$ for testing.
Prerequisites:
Sign up for free subscription on Microsoft Translator. For that you will be asked to create new MS account or login with existing one.
Register your application on Azure Datamarket.
Registration example. Note: There are two important fields here Client ID and Client secret you will need them for access token requests.
Implementation:
First things first, every request to the API should include access token. Expiration time is 10 minutes so you will have to renew them before they expire. Ideally the process should be done on the back-end side to protect your Client secret and result (token + expiration time) send back to web application.
Node.js example:
var request = require("request");
var options = {
method: 'POST',
url: 'https://datamarket.accesscontrol.windows.net/v2/OAuth2-13/',
form: {
// Client ID & Client secret values (see screenshot)
client_id: 'translator_3000',
client_secret: 'ZP8LzjZkKuFAb2qa05+3nNq+uOcqzWK7e3J6qCN1mtg=',
scope: 'http://api.microsofttranslator.com',
grant_type: 'client_credentials'
}
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
Response contains few fields including access_token, use its value for further requests.
{
"token_type": "http://schemas.xmlsoap.org/ws/2009/11/swt-token-profile-1.0",
"access_token": "http%3a%2f%2fschemas.xmlsoap.org%2fws%2f2005%2f05%2fidentity%2fclaims%2fnameidentifier=translator_3000&http%3a%2f%2fschemas.microsoft.com%2faccesscontrolservice%2f2010%2f07%2fclaims%2fidentityprovider=https%3a%2f%2fdatamarket.accesscontrol.windows.net%2f&Audience=http%3a%2f%2fapi.microsofttranslator.com&ExpiresOn=1435405881&Issuer=https%3a%2f%2fdatamarket.accesscontrol.windows.net%2f&HMACSHA256=st9LJ0F8CKSa6Ls59gQN0EqMWLFed5ftkJiOCVXE4ns%3d",
"expires_in": "600",
"scope": "http://api.microsofttranslator.com"
}
Now when we have access token it's time for translation request. Note: These are JSONP requests and access token is supplied using query string parameter appId in the format Bearer <token> (separated by space). Query string also includes text parameter - text of your post and to parameter - language code selected by user, list of all supported codes and language friendly names you can get from API as well.
Here is example:
var settings = {
"url": "https://api.microsofttranslator.com/v2/Ajax.svc/Translate",
"method": "GET",
"dataType": "jsonp",
"jsonp" : "oncomplete",
"data" : {
"text" : "Good Morning StackOverflow",
"to" : "uk",
"appId" : "Bearer http%3a%2f%2fschemas.xmlsoap.org%2fws%2f2005%2f05%2fidentity%2fclaims%2fnameidentifier=translator_3000&http%3a%2f%2fschemas.microsoft.com%2faccesscontrolservice%2f2010%2f07%2fclaims%2fidentityprovider=https%3a%2f%2fdatamarket.accesscontrol.windows.net%2f&Audience=http%3a%2f%2fapi.microsofttranslator.com&ExpiresOn=1435405881&Issuer=https%3a%2f%2fdatamarket.accesscontrol.windows.net%2f&HMACSHA256=st9LJ0F8CKSa6Ls59gQN0EqMWLFed5ftkJiOCVXE4ns%3d"
}
}
$.ajax(settings).done(function (response) {
console.log(response);
});
Response is translated string to be replaced with post original text:
"Доброго ранку StackOverflow"
And lastly, all language codes:
http://api.microsofttranslator.com/V2/Ajax.svc/GetLanguagesForTranslate
and friendly names for selected codes:
http://api.microsofttranslator.com/V2/Ajax.svc/GetLanguageNames?locale=en&languageCodes=["en", "de", "es", "uk"]
Official documentation included.
Use the Class element <div class="micropost293"> shown below.
<div class="micropost293"><p>Тестирование</p>
<div class="micropost293_control" lang="en"></div>
<script>
function googleSectionalElementInit() {
new google.translate.SectionalElement({
sectionalNodeClassName: 'micropost293',
controlNodeClassName: 'micropost293_control',
background: '#f4fa58'
}, 'google_sectional_element');
}
</script>
</div>
//Place this Script at bottom of page.
<script src="//translate.google.com/translate_a/element.js?cb=googleSectionalElementInit&ug=section&hl=en"></script>

Playframework handling post request

In my routes:
POST /forms/FormValidator1/validateForm controllers.FormValidator1.validateForm(jsonForm:String)
There is a controller method defined for that route:
def validateForm(jsonForm:String) = Action { ...
Then I try to send POST request by chrome POSTMAN plugin (see pic above).
I use:
url: http://localhost:9000/forms/FormValidator1/validateForm
headers: Content Type: application/json
json data: {name: "me", surname: "my"}
So, sending this POST request I can not reach controller's method by mentioned route / url. Why?
UPDATE:
Interestly enough: after I got it working on my laptop (see my answer below) then push it on gitHub and pull it to another machine it starts working differently. Now it complains than Bad Request is [Invalid XML] nevertheless I use "application/json" header and did not change any line of code after commit. I wonder maybe it is a bug.
It seems I got it.
Here:
https://groups.google.com/forum/#!topic/play-framework/XH3ulCys_co
And here:
https://groups.google.com/forum/#!msg/play-framework/M97vBcvvL58/216pTqm22HcJ
There is wrong and correct way explained:
Doesn't work: curl -d "name=sam" http://localhost:9000/test
Works: curl -d "" http://localhost:9000/test?name=sam
This is the way how POST params are passing..in play. (second link is explanation WHY):
Sometimes you have to make compromises. In Play 1 you could bind your
action parameters from any parameter extracted from the URL path,
query string or even the request body. It was highly productive but
you had no way to control the way the form was uploaded. I mean, if a
user uploads a big file you needed to load the entire request in
memory to be able to handle it.
In Play 2 you can control the request body submission. You can reject
it early if something is wrong with the user, you can process big
files or streams without filling your memory with more than one HTTP
chunk… You gain a high control of what happens and it can help you to
scale you service. But, the other side of the coin is that when a
request is routed, Play 2 only uses the request header to make its
decision: the request body is not available yet, hence the inability
to directly bind an action parameter from a parameter extracted from
the request body.
UPDATE:
Interestly enough: after I got it working on my laptop then push it on gitHub and pull it to another machine it starts working differently. Now it complains than Bad Request is [Invalid XML] nevertheless I use "application/json" header and did not change any line of code after commit.
UPDATE 2
So I fixed it like this:
On angular side (we even can comment dataType and headers):
var data = $scope.fields
$http({
url: '/forms/FormValidator1/validateForm',
method: "POST",
//dataType: "json",
data: data,
//headers: {'Content-Type': 'application/json'}
}).success(function (data, status, headers, config) {
console.log("good")
}).error(function (data, status, headers, config) {
console.log("something wrong")
});
On playFramework side: (use BodyParser)
def validateForm = Action { request =>
val body: AnyContent = request.body
val jsonBody: Option[JsValue] = body.asJson
// Expecting text body
jsonBody.map { jsValue =>
val name = (jsValue \ "name")
val surname = (jsValue \ "surname")
....
}
Routes (don't define parameters at all !):
POST /forms/FormValidator1/validateForm controllers.FormValidator1.validateForm