In a Google Apps Script I need to query the Google user profile picture URL of many coworkers.
Here is a working example for a single user:
searchDirectoryPeople('jimmy.neutron#example.com');
function searchDirectoryPeople(query) {
const options = {
query: query,
readMask: 'photos,emailAddresses',
sources: ['DIRECTORY_SOURCE_TYPE_DOMAIN_PROFILE']
}
const people = People.People.searchDirectoryPeople(options);
if(people && people.people) {
Logger.log('size: '+people.people.length);
people.people.forEach(person => {
let url = '';
let email = '';
if(person) {
if(person.photos && person.photos[0]) {
url = person.photos[0].url;
}
if(person.emailAddresses && person.emailAddresses.length) {
person.emailAddresses.forEach(item => {
if(item.metadata && item.metadata.sourcePrimary) {
email = item.value;
}
});
}
}
Logger.log('email: '+email+': '+url);
//Logger.log('person: %s', JSON.stringify(person, null, 2));
});
} else {
Logger.log('no people.people');
}
}
I found out that I can query all jimmy people:
searchDirectoryPeople('jimmy');
I have the email address of all employees. I could loop through a big list of 1000+ employees one by one, but this is not practical. I am looking for a way to query multiple email addresses. The docs at https://developers.google.com/people/api/rest/v1/people/searchDirectoryPeople are cryptic for the query. I tried many things like these but nothing works:
'jimmy.neutron#example.com, carl.wheezer#example.com, cindy.vortex#example.com'
'jimmy.neutron#example.com OR carl.wheezer#example.com OR cindy.vortex#example.com'
I am looking for a query by list of email addresses as input, such as:
[ 'jimmy.neutron#example.com', 'carl.wheezer#example.com', 'cindy.vortex#example.com' ]
Is it possible to have an OR query in People.People.searchDirectoryPeople()?
UPDATE 2022-05-31
I tried looping through all emails and ran either into a quota limit or a script runtime limit.
#Lorena Gomez's answer is correct: First use the People.People.listDirectoryPeople() to get the resource names of all email address, followed by People.People.getBatchGet() to get the profile picture URL by resource names. The former limits to 1000 employees per call, the latter limits to 200. This works in our case where we have 1k+ email addresses as input, and 20k+ employees returned by listDirectoryPeople().
Working code:
const emails = [
'jimmy.neutron#example.com',
'carl.wheezer#example.com',
'cindy.vortex#example.com'
];
let emailToUrl = getGoogleProfilePictureUrls(emails);
Logger.log('emailToUrl: %s', JSON.stringify(emailToUrl, null, 2));
// expected output:
// emailToUrl: {
// "jimmy.neutron#example.com": "https://lh3.googleusercontent.com/a-/xxxx=s100",
// "carl.wheezer#example.com": "https://lh3.googleusercontent.com/a-/xxxx=s100",
// "cindy.vortex#example.com": "https://lh3.googleusercontent.com/a-/xxxx=s100"
// }
function getGoogleProfilePictureUrls(emails) {
let options = {
readMask: 'emailAddresses',
sources: ['DIRECTORY_SOURCE_TYPE_DOMAIN_PROFILE'],
pageSize: 1000
}
let run = 1;
let resourceNameToEmails = {};
let result = {};
while(run === 1 || result.nextPageToken) {
if(result.nextPageToken) {
options.pageToken = result.nextPageToken;
}
result = People.People.listDirectoryPeople(options);
Logger.log('request #' + (run++) + ', got '+result.people.length+' resource names');
result.people.forEach(person => {
if(person.emailAddresses) {
person.emailAddresses.forEach(obj => {
if(obj.metadata && obj.metadata.sourcePrimary) {
let email = obj.value
if(emails.indexOf(email) >= 0) {
resourceNameToEmails[person.resourceName] = email;
}
}
});
}
});
Utilities.sleep(200);
}
run = 1;
let emailToUrl = {};
let resourceNames = Object.keys(resourceNameToEmails);
let resourceNameBatch = resourceNames.splice(0, 200);
while(resourceNameBatch.length) {
options = {
personFields: 'photos',
resourceNames: resourceNameBatch,
sources: [ 'READ_SOURCE_TYPE_PROFILE' ]
};
result = People.People.getBatchGet(options);
if(result && result.responses) {
Logger.log('request #' + (run++) + ', got '+result.responses.length+' urls');
result.responses.forEach(person => {
let primaryUrl = '';
let url = '';
if(person.person && person.person.photos) {
person.person.photos.forEach(photo => {
if(photo.metadata && photo.metadata.source && photo.metadata) {
url = photo.url;
if(photo.metadata.source.type === 'PROFILE' && photo.metadata.primary) {
primaryUrl = url;
}
}
});
}
let email = resourceNameToEmails[person.person.resourceName];
emailToUrl[email] = primaryUrl || url;
});
}
Utilities.sleep(200);
resourceNameBatch = resourceNames.splice(0, 200);
}
return emailToUrl;
}
It looks like with Method: people.searchDirectoryPeople you can only specify one person at a time.
Another option could be People.People.getBatchGet() which will require an extra step but provides you information about a list of the people you specify. The request would look something like this:
const options = {
personFields: 'photos,emailAddresses',
resourceNames: [
'people/account_id',
'people/account_id',
'people/account_id'
],
sources: [
'READ_SOURCE_TYPE_PROFILE'
]
}
const people = People.People.getBatchGet(options);
You can get the user's account_id with Method: people.listDirectoryPeople
How about this?
function searchDirectoryPeople(query) {
const options = {
query: query,
readMask: 'photos,emailAddresses',
sources: ['DIRECTORY_SOURCE_TYPE_DOMAIN_PROFILE']
}
const people = People.People.searchDirectoryPeople(options);
if(people && people.people) {
Logger.log('size: '+people.people.length);
people.people.forEach(person => {
let url = '';
let email = '';
if(person) {
if(person.photos && person.photos[0]) {
url = person.photos[0].url;
}
if(person.emailAddresses && person.emailAddresses.length) {
person.emailAddresses.forEach(item => {
if(item.metadata && item.metadata.sourcePrimary) {
email = item.value;
}
});
}
}
return {"imgurl":url,"email":email}
});
}
}
function searchPlus(emailArray) {
let oA = [];
emailArray.forEach(e => {
oA.push(searchDirectoryPeople(e))
});
if(oA && oA.length) {
return oA;
}
}
Related
I wrote a code to fetch the data present and store it in Array format but I thing I have wrote code multiple times can It be possible to minimize the code as its too long
let topicsValue = ["requiredType.*", "Entry.*", "token.*", "RestAPI.*"];
let Topic = [],
rest = ["required", "unrequired"],
final = ["createInput", "mustPossible", "finalOutput"];
topicsValue.map((data) => {
let requiredType, entries, token, restAPI;
if (data.split(".")[1].includes("*")) {
if (data.split(".")[0].includes("requiredType")) {
for (const value of final) {
requiredType = data
.split(".")[0]
.replace("requiredType", "required_type")
.concat(`.${value}`);
Topic.push(requiredType);
}
}
if (data.split(".")[0].includes("Entry")) {
for (const value of final) {
entries = data
.split(".")[0]
.replace("Entry", "entries")
.concat(`.${value}`);
Topic.push(entries);
}
for (const value of rest) {
entries = data
.split(".")[0]
.replace("Entry", "entries")
.concat(`.${value}`);
Topic.push(entries);
}
}
if (data.split(".")[0].includes("token")) {
for (const value of final) {
token = data
.split(".")[0]
.replace("token", "tokens")
.concat(`.${value}`);
Topic.push(token);
}
for (const value of rest) {
token = data
.split(".")[0]
.replace("token", "tokens")
.concat(`.${value}`);
Topic.push(token);
}
}
if (
data.split(".")[0].includes("RestAPI") &&
!data.split(".")[0].includes("RestAPIAction")
) {
restAPI = data
.split(".")[0]
.replace("RestAPI", "restAPI")
.concat(`.deploy`);
Topic.push(restAPI);
}
} else {
if (data.split(".")[0].includes("requiredType")) {
if (!rest.includes(data.split(".")[1])) {
requiredType = data
.split(".")[0]
.replace("requiredType", "required_type")
.concat(`.${data.split(".")[1]}`);
Topic.push(requiredType);
}
}
if (data.split(".")[0].includes("Entry")) {
if (rest.includes(data.split(".")[1])) {
entries = data
.split(".")[0]
.replace("Entry", "entries")
.concat(`.${data.split(".")[1]}.demo`);
Topic.push(entries);
} else {
entries = data
.split(".")[0]
.replace("Entry", "entries")
.concat(`.${data.split(".")[1]}.demo`);
Topic.push(entries);
}
}
if (data.split(".")[0].includes("token")) {
if (rest.includes(data.split(".")[1])) {
token = data
.split(".")[0]
.replace("token", "tokens")
.concat(`.${data.split(".")[1]}`);
Topic.push(token);
} else {
token = data
.split(".")[0]
.replace("token", "tokens")
.concat(`.${data.split(".")[1]}`);
Topic.push(token);
}
}
if (
data.split(".")[0].includes("RestAPI") &&
!data.split(".")[0].includes("RestAPIAction")
) {
restAPI = data
.split(".")[0]
.replace("RestAPI", "restAPI")
.concat(`.deploy`);
Topic.push(restAPI);
}
}
});
console.log(Topic);
Is there any possible way I can reduce the code without effecting the output
As the requirement of the code is like if the topicValue contain * or the other value so I wrote this long code and now I am trying to minimize the code so its look short and effective.
I made refactoring only if(true) part of your code. You can implement the else part by yourself. I didn't run the code, spend any time for proper naming, etc. So it was quick refactoring to give you an idea. I think there is a mistake in else block because both if and else parts of tokens and Entry are doing the same thing.
let replace = {
requiredType: 'required_type',
Entry: 'entries',
token: 'tokens',
RestAPI: 'restAPI'
}
const run = () => {
topicsValue.map((data) => task(data));
}
const task = (data) => {
data.split(".")[1].includes("*") ? funcIf(data) : funcElse(data);
}
const funcIf = (data) => {
for (const key in replace) {
if (data.split(".")[0].includes(key)) commonTask(key, data);
}
}
const commonTask = (key, data) => {
if (key == 'RestAPI' && data.split(".")[0].includes("RestAPIAction")) return;
final.forEach(value => Topic.push(makeOutput(key, value, data)));
}
const makeOutput = (key, value, data) => {
return data.split(".")[0]
.replace(key, replace[key])
.concat(key == 'restAPI' ? '.deploy' : `.${value}`);
}
Try using the && operator.
if(y==1 && x == 1) {
do somthing
}
it only runs if both statements are true.
I am curently working on a small project where I want to check via the google calendar API if a room is busy or not.
For that I use this function:
//checking for change of all values. Then console.log values on change and executing request if busy.
function avalabilityCheck() {
[...inputs].forEach(input => {
input.addEventListener('change', function () {
if (date.value !== "" && startTime.value !== "" && endTime.value !== ""
) {let isBusy = true;
//looping through all rooms in compartment
for (let key in comp_1) {
if (comp_1.hasOwnProperty(key)) {
let calendarID = comp_1[key];
let roomName = key;
//console.log(value);
//user input that goes into the freebusy query
let requestBody = {
timeMin: date.value + "T" + startTime.value + ":00.000Z",
timeMax: date.value + "T" + endTime.value + ":00.000Z",
items: [
{
id: calendarID
}
],
timeZone: "GMT+01:00"
};
//make request to gcalendar if rooms are free. Giving back array on what times room is busy.
var freeRequest = gapi.client.calendar.freebusy.query(requestBody);
//executing request.
freeRequest.execute(function (resp) {
var responseObject = JSON.stringify(resp);
console.log(responseObject);
if (resp.calendars[calendarID].busy.length < 1) {
console.log(`${roomName} is free`);
} else { isBusy = false;
console.log("room is Busy");}
})
}
}
console.log("finito");
if (isBusy === false) {
console.log("working?");
}
else{console.log("not working");}
} else {
console.log("change date pls");
}
}
)
}
)
}
now let isBusy = true; and when a room is busy I want isBusy to be false:
if (resp.calendars[calendarID].busy.length < 1) {
console.log(`${roomName} is free`);
} else { isBusy = false;
console.log("room is Busy");}
So When I run the app the console should either give :
console.log("finito");
if (isBusy === false) {
console.log("working?");
}
else{console.log("not working");}
But it only gives "finito" to the console and apparently is not checking the state of isBusy.
Does anyone see what is the Problem here?
Thank you!
I am trying to get a user's input from a search bar, and use it to get the drink information from a cocktail API, but I am not getting the results back.
If I use a real value for example, Margarita, instead of the placeholder ${drinkName} in my query URL I do get the data I'm looking for in the object form I'm looking for, but something about how I'm getting/using the user input is wrong. I do get the user's input from the search in the console when I console log drinkName, but it doesn't seem to pass that into the searchByName function, and console logging cocktail after the searchByName function returns my empty cocktail object.
let cocktail = {};
const searchByName = (drinkName) => {
fetch(`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${drinkName}`)
.then(
function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' +
response.status);
return data;
}
response.json().then(function(data) {
console.log(data);
getDrinkName(data);
getIngredients(data);
getDirections(data);
getImage(data);
buildCocktail(data);
console.log(cocktail);
});
}
)
.catch(function(err) {
console.log('Fetch Error :-S', err);
});
}
const getImage = (data) => {
const imageUrl = (data.drinks[0].strDrinkThumb);
return imageUrl;
}
const getDrinkName = (data) => {
const name = (data.drinks[0].strDrink);
return name;
}
const getIngredients = (data) => {
let ingredientList = [];
for (let i = 1; i < 16; i++) {
if (data.drinks[0][`strIngredient${i}`] == null){
break;
} else {
const ingredients = ((data.drinks[0][`strMeasure${i}`]) + ': ' + data.drinks[0][`strIngredient${i}`]);
ingredientList.push(ingredients);
}
}
return ingredientList;
}
const getDirections = (data) => {
const directions = (data.drinks[0].strInstructions);
return directions;
}
const buildCocktail = (data) => {
cocktail.image = getImage(data);
cocktail.name = getDrinkName(data);
cocktail.ingredients = getIngredients(data);
cocktail.directions = getDirections(data);
console.log(cocktail);
}
$("#search").keypress(function(event) {
if (event.which === 13) {
const drinkName = $('#search').val();
console.log(drinkName);
searchByName(drinkName);
console.log(cocktail);
}
});
I'm building a small web scraper and I have stumbled into the following problem: my applications needs to scrape different parts of a website and put the information into the database. Sometimes it gives crazy results such as duplicated entries, or it returns undefined from a function getPhoto(). However, if I only call that function (and don't run the rest of the script), it returns a correct result!
I have a for loop, that loops through different URL. It goes to each URL and scrapes the following information: 1. title, 2.description, 3. internal link, 4. calls a function that generates an image according to the title (getPhoto(...) ), 5. saves the results to the DB. Everything happens on the server (I'm using Cron jobs, no client interaction)
for (i = 0; i < AllLinks.length; i++) {
if (AllLinks[i] != undefined && AllLinks[i] != null && sepLink[2] == "www.fly4free.pl") {
var t2 = {
travelTitle: null,
travelTitle2: null,
travelTitle3: null,
travelDescription: null,
travelDescription2: null,
travelDescription3: null,
travelBuy: null,
travelBuy2: null,
travelImage: null
};
var TravelLink1 = AllLinks[i];
result = HTTP.get(AllLinks[i], {});
$ = cheerio.load(result.content);
t2.travelTitle = $('.article__title').text();
t2.travelDescription = $('.article__content').find('p').first().text();
if ($("img[src$='//www.fly4free.pl/wp-content/uploads/2016/09/lotJm.png']").parent().attr('href') != null) {
t2.travelBuy = $("img[src$='//www.fly4free.pl/wp-content/uploads/2016/09/lotJm.png']").parent().attr('href'); // Link to buy
}
if (t2.travelBuy) {
if (t2.travelBuy.split('https://').pop().split('http://').pop() != null) {
t2.travelBuy2 = t2.travelBuy.split('https://').pop().split('http://').pop(); // link ready for DB
} else {
t2.travelBuy2 = t2.travelBuy;
}
}
t2.travelTitle3 = convertCurrencyInText(t2.travelTitle, 'PLN');
t2.travelDescription3 = convertCurrencyInText(t2.travelDescription, 'PLN');
translate(t2.travelTitle3, {from: 'pl', to: 'en'}).then(res => {
t2.travelTitle2 = res.text; // title for DB
if (t2.travelTitle2) { t2.travelImage = getPhoto(t2.travelTitle2); }
translate(t2.travelDescription3, {from: 'pl', to: 'en'}).then(response => {
t2.travelDescription2 = response.text; // description for DB
if (t2.travelDescription2 != null && t2.travelTitle2 != null && t2.travelBuy2 != null && TravelLink1 != null && t2.travelImage != null) {
Links.insert({ title: t2.travelTitle2, description:t2.travelDescription2, image: t2.travelImage, buyLink:t2.travelBuy2, link: TravelLink1, datetime: new Date() });
}
}).catch(err => {
console.error(err);
});
}).catch(err => {
console.error(err);
});
}
}
"AllLinks" contains different URLs. I have problems scraping this URL: http://www.fly4free.pl/na-wakacje-do-toskanii-tanie-loty-do-pizy-z-gdanska-za-170-pln/
getPhoto() function
function getPhoto(title) {
var travelPlace = nlp(title).match('to *').out('text').replace('to','').trim();
if (travelPlace) {var travelPlace2 = travelPlace.split(' '); }
if (travelPlace2) {var travelPlace3 = travelPlace2[0] + "+" + travelPlace2[1]; }
if (travelPlace3) {
var URL = "https://pixabay.com/api/?key="+API_KEY+"&q="+travelPlace3+"&category=travel&orientation=horizontal";
var images = (HTTP.get(URL, {}));
if (images.data.totalHits > 0) {
var imageLink = images.data.hits[0].webformatURL;
return imageLink;
} else if (images.data.totalHits == 0) {
var URL = "https://pixabay.com/api/?key="+API_KEY+"&q="+travelPlace2[0]+"&category=travel&orientation=horizontal";
var images = (HTTP.get(URL, {}));
if (images.data.totalHits > 0) {
var imageLink = images.data.hits[0].webformatURL;
return imageLink;
}
}
} else if (nlp(title).places().data().length > 0) {
var result = nlp(title).places().data()[0].text.replace(/[^a-zA-Z ]/g, "").trim();
var URL = "https://pixabay.com/api/?key="+API_KEY+"&q="+result+"&category=travel&orientation=horizontal";
var images = (HTTP.get(URL, {}));
if (images.data.totalHits > 0) {
var imageLink = images.data.hits[0].webformatURL;
return imageLink;
}
} else {
var title2 = title.replace(/[^a-zA-Z ]/g, "").split(" ");
if (title2) {
for(i = 0; i < title2.length; i++) {
if (cities[title2[i]] == 1) {
var URL = "https://pixabay.com/api/?key="+API_KEY+"&q="+title2[i]+"&category=travel&orientation=horizontal";
var images = (HTTP.get(URL, {}));
if (images.data.totalHits > 0) {
var imageLink = images.data.hits[0].webformatURL;
return imageLink;
}
} else {
var URL = "https://pixabay.com/api/?key="+API_KEY+"&q=travel&category=travel&orientation=horizontal";
var images = (HTTP.get(URL, {}));
if (images.data.totalHits > 0) {
var imageLink = images.data.hits[0].webformatURL;
return imageLink;
}
}
}
}
}
}
I try to console log the results - sometimes I get a correct image from getPhoto(), but an undefined link from t2.travelBuy, sometimes vice versa. Can you tell me what I'm doing wrong? I saw some people are using Promises or async/await functions on that kind of problems. Do you think that would help me? How should I change my code in order to scrape the website without getting "undefined"?
"translate" comes from "google-translate-api" package
you can try var new_func = Meteor.wrapAsync(YOUR FUNCTION THAT HAVE CALLBACK) and the when you use new_func() it will return the result as you would expect from normal function instead of waiting for callback
Given that I have:
var permissions = {
'blog': {
'article' : ['add_article', 'view_articles']
},
'gallery': {
'images' : ['upload_image', 'view_other_user_images']
}
}
var permissionsChecker = function(permissions) {
this.permissions = permissions;
this.hasPermissions = function (permissionsString) {
// permissionsString is in this.permissions
// return true
}
}
How can I search in the array within that object to check that I have a certain permission? I want to be able to search by giving a string like so:
'blog.article.add_article' or 'gallery.images.view_other_user_images'
I may not be understanding correctly, but I think this is what you're looking for:
var permissionsChecker = function(permissions) {
this.permissions = permissions;
this.hasPermissions = function (permissionsString) {
var [section, subsection, permission] = permissionsString.split('.');
if(this.permissions[section]){
if(this.permissions[section][subsection]){
return this.permissions[section][subsection].indexOf(permission) > -1;
}
}
return false;
};
}
var checker = new permissionsChecker(permissions);
checker.hasPermission('add_article');//true
checker.hasPermission('destroy_world');//false