Bing Ads Script to change shared campaign budget on multiple accounts using Google Sheets - google-play-services

I have a Google Ads script running to change campaign budgets, but implementation of the same script into Bing Ads is more difficult for me. I'm having problems with the code to connect Google Sheets with Bing Ads Script. I got clientId, clientSecret and refresh token to authorize Google service in Bing, but am struggling with the code to allow the script read my Google Sheets file.
I attached some code responsible for connecting Google Sheets file to Bing Script. It should allow it to read it's content and later change it to whatever values I provided in that file.
const credentials = {
accessToken: '', // not sure if i needed it if I got refresh token
clientId: 'HIDDEN',
clientSecret: 'HIDDEN',
refreshToken: 'HIDDEN'
};
function main() {
var SPREADSHEET_URL = 'HIDDEN';
var GoogleApis;
(function (GoogleApis) {
GoogleApis.readSheetsService = credentials => readService("https://sheets.googleapis.com/$discovery/rest?version=v4", credentials);
 
// Creation logic based on https://developers.google.com/discovery/v1/using#usage-simple
function readService(SPREADSHEET_URL, credentials) {
const content = UrlFetchApp.fetch(SPREADSHEET_URL).getContentText();
const discovery = JSON.parse(content);
const accessToken = getAccessToken(credentials);
const standardParameters = discovery.parameters;
}
function getAccessToken(credentials) {
if (credentials.accessToken) {
return credentials.accessToken;
}
const tokenResponse = UrlFetchApp.fetch('https://www.googleapis.com/oauth2/v4/token', { method: 'post', contentType: 'application/x-www-form-urlencoded', muteHttpExceptions: true, payload: { client_id: credentials.clientId, client_secret: credentials.clientSecret, refresh_token: credentials.refreshToken, grant_type: 'refresh_token' } });
const responseCode = tokenResponse.getResponseCode();
const responseText = tokenResponse.getContentText();
if (responseCode >= 200 && responseCode <= 299) {
const accessToken = JSON.parse(responseText)['access_token'];
return accessToken;
}
throw new Error(responseText);
})(GoogleApis || (GoogleApis = {}));
it throws syntax error on the last line of the code:
})(GoogleApis || (GoogleApis = {}));
but i think there is more than that.

Please try the var GoogleApis declaration outside main() as this example shows: https://learn.microsoft.com/en-us/advertising/scripts/examples/calling-google-services
I hope this helps.

Related

How to use Nuxt 3 server as a passthrough API with FormData to hide external endpoints

I'm trying to get my head around the Nuxt /server API and can't seem to figure out how to send a POST request with form-data (ie files) to Nuxt server to forward on to an external service:
In my pages.vue file I have this method:
async function onSubmit() {
const formData = new FormData();
for (let file of form.files) {
await formData.append("image", file);
}
await $fetch("/api/send", {
method: "POST",
body: formData
});
}
and then in /server/api/send.js I have:
export default defineEventHandler(async (event) => {
const { method } = event.node.req;
// I THINK THE ISSUE IS HERE
const body =
method !== "GET" && method !== "HEAD"
? await readMultipartFormData(event)
: undefined;
const response = await $fetch.raw(https://*******, {
method,
baseURL: *********,
headers: {
},
body: body
});
return response._data;
}
I'm effectively creating a passthrough API using Nuxt so that the external endpoint isn't exposed to the end user. Just can't figure out how to access the formData in the correct format to pass through on the server side. I don't think I am supposed to use readMultipartFormData() because that seems to be parsing the data somehow whereas I just want to pass the formData straight through to the external API. Any tips?
I've tried using both readMultipartFormData() and readBody() and neither seem to work. I don't actually need to read the body but rather get it and pass it through without any formatting...
If you want to pass the data with formdata to the endpoint try this library:
https://www.npmjs.com/package/object-to-formdata
code:
import { serialize } from 'object-to-formdata';
const formData = serialize(body);
const response = await $fetch.raw(https://*******, {
method,
baseURL: *********,
headers: {
},
body: formData
});
I managed to make it work with ugly solution, first you have to update nuxt to version 3.2.0 min then here my front side
let jobApplicationDTO = {
firstName: values.firstName,
lastName: values.lastName,
email: values.email,
phoneNumber: values.phoneNumber,
company: values.company,
shortDescription: values.shortDescription
};
const formData = new FormData();
formData.append("application", new Blob([JSON.stringify(jobApplicationDTO)], {type: "application/json"}));
formData.append("file", values.file) ;
//formData.append("file", values.file );
await useFetch("/api/application", {
method: "POST",
body: formData,
onResponse({request, response, options}) {
// Process the response data
if (response.status === 200) {
errorMessage.value = "";
successMessage.value = "Your application wa sent successfully, you will be contacted soon !";
}
},
onResponseError({request, response, options}) {
console.debug(response);
if (response.status === 400) {
successMessage.value = "";
errorMessage.value = "There may be an issue with our server. Please try again later, or send an email to support#mantiq.com";
} else {
successMessage.value = "";
errorMessage.value = "Sorry we couldn’t send the message, there may be an issue with our server. Please try again later, or send an email to support#mantiq.com";
}
},
});
}
and server side
import {FormData} from "node-fetch-native";
export default defineEventHandler(async (event) => {
const {BACKEND_REST_API, ENQUIRY_TOKEN} = useRuntimeConfig();
//retrieve frontend post formData
const form = await readMultipartFormData(event);
const applicationUrl = BACKEND_REST_API + '/job/apply'
console.log("url used for enquiry rest call :" + applicationUrl);
console.log("Job application token :" + ENQUIRY_TOKEN);
const formData = new FormData();
console.log(form);
if (form) {
formData.append(form[0].name, new Blob([JSON.stringify(JSON.parse(form[0].data))], {type: form[0].type}));
formData.append(form[1].name, new Blob([form[1].data], {type: form[1].type}), form[1].filename);
}
console.log(formData.values);
return await $fetch(applicationUrl, {
method: "POST",
body: formData,
headers: {
Authorization: ENQUIRY_TOKEN,
},
});
})
What is funny is on frontend you have to create a formData , then to get content and to recreate a formData from your previous formData converted in MultiFormPart[], i created a ticket on nuxt to see how to do it properly

Cannot Access themes.json using shopify api and nodejs

I am not able to access the themes.json of my development store using shopify api and nodejs.
Here is what I am doing:
app.get('/shopify/examplePage', (req, res) => {
const { shop, hmac, code, state } = req.query;
const stateCookie = cookie.parse(req.headers.cookie).state;
// Verifying Cookie
if (state !== stateCookie) {
return res.status(403).send('Request origin cannot be verified');
}
// Verifying Hmac
if (shop && hmac && code) {
const map = Object.assign({}, req.query);
delete map['hmac'];
const message = querystring.stringify(map);
const generatedHash = crypto
.createHmac('sha256', apiSecret)
.update(message)
.digest('hex');
if(generatedHash !== hmac){
return res.status(400).send('HMAC verification failed');
}
// Appending Access Token to the shop Url
const accessTokenRequestUrl = 'https://' + shop + '/admin/oauth/access_token';
const accessTokenPayload = {
client_id: apiKey,
client_secret: apiSecret,
code
};
// Making an API Request And getting an API response
request.post(accessTokenRequestUrl, {json: accessTokenPayload })
// Promise for Access Token Response
.then((accessTokenResponse) => {
const accessToken = accessTokenResponse.access_token;
// Request URL for Products
const apiRequestUrl = 'https://' + shop + '/admin/api/2019-04/themes.json'
console.log(apiRequestUrl);
const apiRequestHeader = {
'X-Shopify-Access-Token': accessToken
};
request.get(apiRequestUrl, { headers: apiRequestHeader })
.then((apiResponse) => {
let example = JSON.parse(apiResponse);
res.send(example);
// End API Response
res.end(apiResponse)
}).catch((error) => {
res.status(error.statusCode).send(error.error.error_descripton)
});
}).catch((error) => {
res.status(error.statusCode).send(error.error.error_descripton)
})
} else {
res.status(400).send('Required parameters missing');
}
});
There is this error showing that the access to {ngrok}.ngrok.io was denied while I can access product.json & shop.json with the help of same code
Denied means your API key doesn’t have access. If this is a public app you need to add read_themes to your scopes. If it is a private app you need to go to the app setup and add theme access.

Get AWS Cognito user from ID Token retrieved from Token Endpoint

I am building a React Native app using Expo and AWS Cognito with AWS Amplify, and I am trying to enable signing in with Facebook, Google, etc. using AWS
I can create a user and sign in using Cognito APIs without any issue.
Using third-parties, though, requires using the Expo AuthSession functionality.
The functionality itself works fine, and I am able to get all the way to retrieving the proper tokens from my /oauth2/token endpoint.
However, as far as Amplify is concerned (and I am aware), the user is not signed in, so when I try to get Auth.currentAuthenticatedUser(), null is returned.
// Open URL in a browser
openURL = async (url) => {
let result = await AuthSession.startAsync({ authUrl: url })
this.getTokenbyCode(result.params.code)
};
getTokenbyCode = async (code) => {
const details = {
grant_type: 'authorization_code',
code,
client_id: '10eavoe3ufj2d70m5m3m2hl4pl',
redirect_uri: AuthSession.getRedirectUrl()
}
const formBody = Object.keys(details)
.map(
key => `${encodeURIComponent(key)}=${encodeURIComponent(details[key])}`
)
.join("&");
await fetch(
'https://presentor.auth.us-west-2.amazoncognito.com/oauth2/token',
{
method: "POST",
headers: {
'Content-type': 'application/x-www-form-urlencoded;charset=UTF-8'
},
body: formBody
}
)
.then(async (res) => {
console.log('res: ', res);
let resJSON = await res.json();
let idToken = await resJSON.id_token;
let decodedToken = jwt(idToken);
let userData = {
Username : decodedToken["cognito:username"],
Pool : Auth.userPool
}
})
.catch(error => {
console.log('error: ', error);
});
}
When I decode the token, I see the payload as I expect, but if I want to, for example, utilize the APIs to refresh the token if it expires, I have to workaround manually (check for expiration and retrieve a new token if it's expired).
Am I missing something basic?
Ok, I figured it out. Not sure if this is the right path, but it's pretty clean and it works, so I'm good with it.
Create CognitoIdToken, CognitoAccessToken, and CognitoRefreshToken objects using amazon-cognito-identity-js
Create a user session from those tokens
Create a user from that user session
await fetch(
'TOKEN ENDPOINT',
{
method: "POST",
headers: {
'Content-type': 'application/x-www-form-urlencoded;charset=UTF-8'
},
body: formBody
}
)
.then(async (res) => {
const IdToken = new CognitoIdToken({ IdToken: tokenRequestJson.id_token });
const AccessToken = new CognitoAccessToken({ AccessToken: tokenRequestJson.access_token });
const RefreshToken = new CognitoRefreshToken({ RefreshToken: tokenRequestJson.refresh_token })
try {
let userSession = new CognitoUserSession({ IdToken, AccessToken, RefreshToken });
console.log('userSession: ', userSession);
const userData = {
Username: userSession.idToken.payload.email,
Pool: userPool
};
console.log('userData: ', userData);
cognitoUser = new CognitoUser(userData);
cognitoUser.setSignInUserSession(userSession);
cognitoUser.getSession((err, session) => { // You must run this to verify that session (internally)
if (session.isValid()) {
console.log('session is valid');
this.setState({user: cognitoUser})
this.props.navigation.navigate('AuthLoading')
} else {
console.log('session is not valid: ', session);
}
})
}
catch (FBSignInError) {
console.log('FBSignInError: ', FBSignInError)
}
})
.catch(fetchError => console.log('fetchError: ', fetchError))

API Request in Dialogflow Fulfillment (Javascript)

So I'm trying to make a google action using Dialogflow that requires an external API. I've always used jQuery .getJSON() to make API calls, so I had no idea how to do this. After searching this up online, I found a way to do this using vanilla javascript (I also tested the way on my website and it worked fine). The code for that is below:
function loadXMLDoc() {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
console.log(xmlhttp.responseText);
}
};
xmlhttp.open("GET", "https://translate.yandex.net/api/v1.5/tr.json/translate?lang=en-es&key=trnsl.1.1.20190105T052356Z.7f8f950adbfaa46e.9bb53211cb35a84da9ce6ef4b30649c6119514a4&text=eat", true);
xmlhttp.send();
}
The code worked fine on my website, but as soon as I added it to the Dialogflow, it would give me the error
XMLHttpRequest is not defined
Obviously that happened because I never defined it (using var), except it worked without me doing anything. So then, I tried adding this line
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
to the code, and it stopped giving me the error (because I defined XMLHttpRequest). But then, my code wouldn't work.
TL;DR: How can I make an external API call using Dialogflow fulfillment?
You can use https. But make sure that you upgrade to Blaze Pay(or any other plans) to make external API calls, else you will receive an error such as
Error:
Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove these restrictions.
Code to make external api call,
// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
"use strict";
const functions = require("firebase-functions");
const { WebhookClient } = require("dialogflow-fulfillment");
const { Card, Suggestion } = require("dialogflow-fulfillment");
const https = require("https");
process.env.DEBUG = "dialogflow:debug"; // enables lib debugging statements
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(
(request, response) => {
const agent = new WebhookClient({ request, response });
console.log(
"Dialogflow Request headers: " + JSON.stringify(request.headers)
);
console.log("Dialogflow Request body: " + JSON.stringify(request.body));
function getWeather() {
return weatherAPI()
.then(chat => {
agent.add(chat);
})
.catch(() => {
agent.add(`I'm sorry.`);
});
}
function weatherAPI() {
const url =
"https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22";
return new Promise((resolve, reject) => {
https.get(url, function(resp) {
var json = "";
resp.on("data", function(chunk) {
console.log("received JSON response: " + chunk);
json += chunk;
});
resp.on("end", function() {
let jsonData = JSON.parse(json);
let chat = "The weather is " + jsonData.weather[0].description;
resolve(chat);
});
});
});
}
function welcome(agent) {
agent.add(`Welcome to my agent!`);
}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
let intentMap = new Map();
intentMap.set("Default Welcome Intent", welcome);
intentMap.set("Default Fallback Intent", fallback);
intentMap.set("Weather Intent", getWeather);
agent.handleRequest(intentMap);
}
);
This article is a diamond! It really helped to clarify what's going on and what's required in Dialogflow fullfilments.
A small suggestion is to gracefully catch the error in the connection to the webservice:
function weatherAPI() {
const url = "https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b6907d289e10d714a6e88b30761fae22";
return new Promise((resolve, reject) => {
https.get(url, function(resp) {
var json = "";
resp.on("data", function(chunk) {
console.log("received JSON response: " + chunk);
json += chunk;
});
resp.on("end", function() {
let jsonData = JSON.parse(json);
let chat = "The weather is " + jsonData.weather[0].description;
resolve(chat);
});
}).on("error", (err) => {
reject("Error: " + err.message);
});
});
}

Direct Upload to S3 from the browser with Authorization Signature Ver 4

I need to upload a file to S3 directly from the browser. In the beginning I created a script that is working but to authorize I need to put my credentials accessKeyId and secretAccessKey, what it is not secure.
I figured out that I can use for authorization the "Authorization Signature"
It seems great but I can't find where I can put this authorization header to the request in the upload() method.
An example of my authorization header:
Authorization: AWS4-HMAC-SHA256
Credential=/20151016//s3/aws4_request,
SignedHeaders=content-type;host;x-amz-date,
Signature=4eee344a71a58623febc4079024a27cb62f3d26546695422244fcefe50d0168d
Thanks for your advice.
I have found solution for this issue. My solution is based on example from this site.
In final solution I don't use javascript SDK, it is using post form with authorization inputs what is sending with post parameters.
You can enclose a signed policy document with your POST request in order to authenticate securely, with AWS Signature Version 4.
If you're on Node, you can use the aws-s3-form package on the server to generate the necessary form data your client requires in order to send a successful request to S3.
You might want to read my blog post on the subject for full insight.
Example Server Side Code (Node)
let AwsS3Form = require('aws-s3-form')
[...]
// A hapi.js server route
server.route({
method: ['GET',],
path: '/api/s3Settings',
config: {
auth: 'session',
handler: (request, reply) => {
let {key,} = request.query
let keyPrefix = `u/${request.auth.credentials.username}/`
let region = process.env.S3_REGION
let s3Form = new AwsS3Form({
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region,
bucket,
keyPrefix,
successActionStatus: 200,
})
let url = `https://s3.${region}.amazonaws.com/${bucket}/${keyPrefix}${key}`
let formData = s3Form.create(key)
reply({
bucket,
region,
url,
fields: formData.fields,
})
},
},
})
Example Client Side Code
let R = require('ramda')
let ajax = require('./ajax')
class S3Uploader {
constructor({folder,}) {
this.folder = folder
}
send(file) {
let key = `${this.folder}/${file.name}`
return ajax.getJson(`s3Settings`, {key,})
.then((s3Settings) => {
let formData = new FormData()
R.forEach(([key, value,]) => {
formData.append(key, value)
}, R.toPairs(s3Settings.fields))
formData.append('file', file)
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest()
request.onreadystatechange = () => {
if (request.readyState === XMLHttpRequest.DONE) {
if (request.status === 200) {
resolve(s3Settings.url)
} else {
reject(request.responseText)
}
}
}
let url = `https://s3.${s3Settings.region}.amazonaws.com/${s3Settings.bucket}`
request.open('POST', url, true)
request.send(formData)
})
}, (error) => {
throw new Error(`Failed to receive S3 settings from server`)
})
}
}