Retrieve all my subscriptions in Google Sheets - google-apps-script

I'm using YouTube Data API v3 and Google Apps Script for retrieve all my subscriptions.
The problem I'm facing is that - using the following code, the response brings duplicated channels:
do {
const mySubsResponse = YouTube.Subscriptions.list('snippet', {
mine: true,
//channelId: "<MY_CHANNEL_ID>",
maxResults: 50,
fields: "pageInfo(totalResults),nextPageToken,items(snippet(title,resourceId(channelId)))"
});
if (!mySubsResponse || mySubsResponse == undefined) {
Logger.log('No subscriptions found.');
SpreadsheetApp.getUi().alert("No subscriptions found.");
break;
}
// Loop all my subscriptions found in the response:
for (let j = 0; j < mySubsResponse.items.length; j++) {
const mySubItem = mySubsResponse.items[j];
sheet.getRange("H" + incrSub).setValue(mySubItem.snippet.title);
sheet.getRange("I" + incrSub).setValue(mySubItem.snippet.resourceId.channelId);
incrSub++;
}
nextPageToken = mySubsResponse.nextPageToken;
} while (nextPageToken);
I believe this is due each item in the response is actually the video uploaded by the channel I'm subscribed to - I don't think it's a problem with the page token.
In the code above, I've commented the channelId parameter and I've testted with both: mine:true and channelId:<MY_CHANNEL_ID> and, the totalResults shows me I have 479 subscriptions, but, when I'm looping the results,
For example, I'm subscribed to the channel called "Channel_1"; this
channel had uploaded three videos today. The response of the code
above brings me "Channel_1" three times, when it should be only 1 -
because I'm subscribed to "Channel_1" once.
What I want to get is a list of all channels I'm subscribed to.
I've checked the subscriptions:list documentation, but, it's not clear how I can get my subscriptions only.
If the subscriptions:list endopint is not the correct one for this task, which endpoint enables me to bring the desired results?1
1a list of all channels I'm subscribed to.

After checking more closely (and, I admit, after a little break I have), I finally found the problem and the solution:
The problem is: I wasn't using the nextPageToken in every loop, so, basically, I was requesting the same page without actually making any pagination.
In this section:
const mySubsResponse = YouTube.Subscriptions.list('snippet', {
mine: true,
//channelId: "<MY_CHANNEL_ID>",
maxResults: 50,
fields: "pageInfo(totalResults),nextPageToken,items(snippet(title,resourceId(channelId)))"
});
Can be seen that the pageToken: nextPageToken is not defined.
Then, the solution is:
Modify the code for sending the nextPageToken obtained.
This is the modified code:
// Call my subscriptions:
/** Token pagination. */
var nextPageToken = "";
/** Row position where to start writing the results. */
var incrSub = 6;
/**
* Get all my subscriptions.
*/
do {
const mySubsResponse = YouTube.Subscriptions.list('snippet', {
channelId: "<MY_CHANNEL_ID>", // also works with "mine: true".
maxResults: 50,
// Here, the first time the call is made, the "nextPageToken" value
// is empty. In every iteration (if "nextPageToken" is retrieved),
// the "nextPageToken" is used - in order to get the next page.
pageToken: nextPageToken,
fields: "nextPageToken,items(snippet(title,resourceId(channelId)))"
});
if (!mySubsResponse || mySubsResponse == undefined) {
Logger.log('No subscriptions found.');
SpreadsheetApp.getUi().alert("No subscriptions found.");
break;
}
// Write the subscriptions returned in the response:
for (let j = 0; j < mySubsResponse.items.length; j++) {
const mySubItem = mySubsResponse.items[j];
sheet.getRange("H" + incrSub).setValue(mySubItem.snippet.title);
sheet.getRange("I" + incrSub).setValue(mySubItem.snippet.resourceId.channelId);
incrSub++;
}
// Check the token:
try {
if (mySubsResponse.nextPageToken != null || mySubsResponse.nextPageToken != undefined) {
nextPageToken = mySubsResponse.nextPageToken;
} else {
nextPageToken = undefined;
break;
}
} catch (ex_page) {
// An error occurred. Check closely the code.
}
} while (nextPageToken != undefined);
With this modified code, all of my subscriptions are returned successfully.

Related

Workaround for 403 error when using URLFETCH with Google Apps Script (external website)

I've used sof for many years (I almost always found all my answers!) but I'm quite stuck for the current project so this is the first time I post here. :)
I want to get the product price from www.hermes.com using either the URL or the product ref.
ex: https://www.hermes.com/fr/fr/product/portefeuille-dogon-duo-H050896CK5E/
ref = H050896CK5E
The URLs and Refs are stored in a Spreadsheet.
As I called UrlFetchApp.fetch function in my script, I got 403 error.
If my understanding is correct, that means the hermes.com server is blocking me out.
I also tried =IMPORTXML and it says that the spreadsheet cannot access the URL.
Here are the workaround I found: use Google Custom Search API to search the URL and iterate until the result URL matches the query.
[Current issues]
If the object is out of stock or if the URL is not found, I am unable to get the price.
ex:
when I search https://www.hermes.com/it/it/product/cappello-alla-pescatora-eden-H221007NvA259/
it returns me nothing.
I know it can return
https://www.hermes.com/it/it/product/cappello-alla-pescatora-eden-H221007Nv0156/
but not the same colour (and sometimes the price does change between colours)
So my question was:
How would you do to bypass the 403 error ? (not bypass security of course but if you have any ideas how to retrieve the hermes.com prices, please let me know!)
I will paste the scripts below.
Thank you in advance.
→ What I used for hermes.com.
With the muteHttpExceptions = true, I get the captcha html
var response = UrlFetchApp.fetch("http://www.hermes.com/",
{
method: "get",
contentType: "application/json",
muteHttpExceptions: true,
});
→ Result of above (a captcha html, I think hermes.com knows I'm a bot)
<html><head><title>hermes.com</title><style>#cmsg{animation: A 1.5s;}#keyframes A{0%{opacity:0;}99%{opacity:0;}100%{opacity:1;}}</style></head><body style="margin:0"><p id="cmsg">Please enable JS and disable any ad blocker</p><script>var dd={'cid':'AHrlqAAAAAMAs2XwactPh88AInQWTw==','hsh':'2211F522B61E269B869FA6EAFFB5E1','t':'fe','s':13461,'host':'geo.captcha-delivery.com'}</script><script src="https://ct.captcha-delivery.com/c.js"></script></body></html>
→ What I'm using now (Google Custom Search)
for (var i = 0; i < 5; i++) {
var start = (i * 10) + 1;
var apiUrl = "https://www.googleapis.com/customsearch/v1?key=" + apiKey + "&cx=" + searchId + "&q=search " + query + "&start=" + start;
var apiOptions = {
method: 'get'
};
var responseApi = UrlFetchApp.fetch(apiUrl, apiOptions);
var responseJson = JSON.parse(responseApi.getContentText());
var checkDomain = "";
for (var v = 0; v < 10; v++) {
if (responseJson["items"] != null && responseJson["items"][v] != null) {
checkDomain = responseJson["items"][v]["link"];
if (checkDomain != null && checkDomain == query) {
productPrice = responseJson["items"][v]["pagemap"]["metatags"][0]["product:price:amount"];
currency = responseJson["items"][v]["pagemap"]["metatags"][0]["product:price:currency"];
break;
}
}
}
if (productPrice > 0) { break; }
}

How to get all pages of a Google site with many pages

I have a Google site and I use Google Apps Script to get all the pages of the site and export their data to JSON format.
I use the getAllDescendants function with a code similar to this:
function getAllSitePages(site) {
var result = [], i = 0;
while(true) {
var pages = site.getAllDescendants({start: i});
if(!pages || pages.length == 0) break;
result = result.concat(pages);
i += pages.length;
};
return result;
}
But this only gets me the first 891 (?!) pages. If my sites has around 1000 pages, is there a way to get all of them with the Sites Service?
For now, I was able to bypass the problem by using the getChildren function instead (as I currently don't have any page (including root), that has more than 800 direct children):
function getAllSitePages(root, result) {
result = result || []
var start = 0;
while (true) {
var pages = root.getChildren({ start });
if (!pages || pages.length == 0) break;
result.push(...pages);
pages.forEach(page => getAllSitePages(page, result));
start += pages.length;
};
return result;
}

Getting past permissions for a file through the API/Apps Script

I'm trying to create a list of files stored in my Google Drive and also a list of their current and previous permissions. Specifically, I want to create a list of files in my Google Drive which at any point in the past have had the 'Anyone with a link can view/edit (etc)' permission set.
I have created a Google Apps Script to do this and I can iterate through all the files OK and I can get files which currently have that permission set, but I can't see a way to get the history of the file's permissions.
I have found and activated the revisions list API: https://developers.google.com/drive/api/v2/reference/revisions/list
This gets revisions but I can't see anywhere that it lists the sharing history of a revision.
Is what I'm attempting to do possible?
It's definitely possible using the Drive Activity API. You can use the Quickstart for Google Apps Script to view all the activity of an item (file or folder) or done by a User. In this case I modified the Quickstart to show the Permissions changes of a given Drive Id.
function listDriveActivity() {
var request = {
itemName: "items/1bFQvSJ8pMdss4jInrrg7bxdae3dKgu-tJqC1A2TktMs", //Id of the file
pageSize: 10};
var response = DriveActivity.Activity.query(request);
var activities = response.activities;
if (activities && activities.length > 0) {
Logger.log('Recent activity:');
for (var i = 0; i < activities.length; i++) {
var activity = activities[i];
var time = getTimeInfo(activity);
var action = getActionInfo(activity.primaryActionDetail);
var actors = activity.actors.map(getActorInfo);
var targets = activity.targets.map(getTargetInfo);
if (action == "permissionChange"){ //Only show permissionChange activity
Logger.log(
'%s: %s, %s, %s', time, truncated(actors), action,
truncated(targets));
}
}
} else {
Logger.log('No activity.');
}
}
/** Returns a string representation of the first elements in a list. */
function truncated(array, opt_limit) {
var limit = opt_limit || 2;
var contents = array.slice(0, limit).join(', ');
var more = array.length > limit ? ', ...' : '';
return '[' + contents + more + ']';
}
/** Returns the name of a set property in an object, or else "unknown". */
function getOneOf(object) {
for (var key in object) {
return key;
}
return 'unknown';
}
/** Returns a time associated with an activity. */
function getTimeInfo(activity) {
if ('timestamp' in activity) {
return activity.timestamp;
}
if ('timeRange' in activity) {
return activity.timeRange.endTime;
}
return 'unknown';
}
/** Returns the type of action. */
function getActionInfo(actionDetail) {
return getOneOf(actionDetail);
}
/** Returns user information, or the type of user if not a known user. */
function getUserInfo(user) {
if ('knownUser' in user) {
var knownUser = user.knownUser;
var isMe = knownUser.isCurrentUser || false;
return isMe ? 'people/me' : knownUser.personName;
}
return getOneOf(user);
}
/** Returns actor information, or the type of actor if not a user. */
function getActorInfo(actor) {
if ('user' in actor) {
return getUserInfo(actor.user)
}
return getOneOf(actor);
}
/** Returns the type of a target and an associated title. */
function getTargetInfo(target) {
if ('driveItem' in target) {
var title = target.driveItem.title || 'unknown';
return 'driveItem:"' + title + '"';
}
if ('drive' in target) {
var title = target.drive.title || 'unknown';
return 'drive:"' + title + '"';
}
if ('fileComment' in target) {
var parent = target.fileComment.parent || {};
var title = parent.title || 'unknown';
return 'fileComment:"' + title + '"';
}
return getOneOf(target) + ':unknown';
}
Remember to enable the Drive Activity API in Resources > Advanced Google Services
In my example this returns the logs:
You can also look deeper into the Permissions by using the permissionChange Parameters in the query.
If you have a business/enterprise/edu account the admin audit logs will tell you this for 6 months of data. Or it will at least tell you when a permission was changed from x to y.
Can't think of a method for personal.

Labeling Gmail message (not the whole thread) with Google Apps Script

Is it possible to search to messages with the label 'Apps script queue' and give just these specific messages (not the whole thread) a new label?
When I use GmailApp.search('label:Apps script queue') I get the requested messages but when I assign a new label to these messages, all the other messages of the thread (on other places in the mailbox) will get the same label. And that is not what I want.
This code does not return an error while adding a label to a specific message in a thread and if you use thread list method you'll see that it is only placed in the specific messageID(treated separately). But once your UI(Gmail site) is in conversation mode, it will be viewable in both labels.
function searchMail(){
var threads = GmailApp.search("SOME SEARCH");
Logger.log(threads.length);
listLabel('me');
for (var i = 0; i < threads.length; i++) {
var messages = threads[i].getMessages();
Logger.log(messages.length);
for (var j = 0; j < messages.length; j++){
if (messages[j].isInInbox()){
Logger.log('me' + 'id msg: ' + messages[j].getId());
//Add label to the first reply
addLabel('me',messages[1].getId());
}
else{
Logger.log('me' + 'id msg: ' + messages[j].getId() +" not in inbox");
}
}
}
}
function addLabel(userId, messageId){
var resource = {addLabelIds: ["Label_6"]}
Gmail.Users.Messages.modify(resource, userId, messageId);
}
In Gmail, labels are applied to a thread and cannot be applied to a single email message of a thread.
You can however apply stars / colors to individual messages.
This is an old thread, but for anybody who might be reading it like me, maybe this will save you some time:
function getLabelMap() {
var allLabels = Gmail.Users.Labels.list('me');
var labelMap = [];
for (var label of allLabels.labels) {
labelMap[label.name] = label.id;
}
return labelMap;
}
var labelMap = getLabelMap();
function getLabel(labelName) {
return labelMap[labelName];
}
function labelMessage(messageID, labelName) {
var labelID = getLabel(labelName);
var labelRequest = {addLabelIds: [labelID]};
var subject = GmailApp.getMessageById(messageID).getSubject();
if (labelID != null) {
Logger.log("Labelling as %s: %s", labelName, subject);
Gmail.Users.Messages.modify(labelRequest, 'me', messageID);
} else {
Logger.log("Label not found: %s", labelName);
}
}
function unlabelMessage(messageID, labelName) {
var labelID = getLabel(labelName);
var labelRequest = {removeLabelIds: [labelID]};
var subject = GmailApp.getMessageById(messageID).getSubject();
if (labelID != null) {
Logger.log("Removing label %s: %s", labelName, subject);
Gmail.Users.Messages.modify(labelRequest, 'me', messageID);
} else {
Logger.log("Label not found: %s", labelName);
}
}
function reLabel () {
var messagesToRelabel = Gmail.Users.Messages.list('me', {'q':'label:Apps-script-queue'}).messages || [];
// Loop through each message (not by thread), using the Advanced Gmail Service (full GMail API in a Google Script).
messagesToRelabel.forEach(function (messageToRelabel){
unlabelMessage(messageToRelabel.id, "Apps script queue");
labelMessage(messageToRelabel.id, "New label");
});
}
Not asked for by the OP, but may be helpful for others who are trying to do "advanced filtering / labeling" using the GMail API:
function getMessageHeader(messageID, headerField) {
var messageInfo = Gmail.Users.Messages.get('me', messageID, {'format':'METADATA', 'metadataHeaders':[headerField]});
if (messageInfo.payload.headers) {
return messageInfo.payload.headers[0].value;
} else {
return null;
}
}
The above lets you filter on header info, e.g. I use it to check whether X-Uniform-Type-Identifier is equal to com.apple.mail-note to automatically flag old Apple Notes for deletion.

Getting list of child OU in Apps Script

Google Directory provide API to get all organizational units from domain.
I'm able to get OU inside "/" but I want to get all child OU.
There is function with some arguments:
AdminDirectory.Orgunits.list(String costumerId, Object optionalArg)
This function accept "type" argument as mentioned in official documentation, but when I put argument "orgUnitPath" it will respond with "Bad request"
Here is my full code
var page = AdminDirectory.Orgunits.list('my_costumer',{
orgUnithPath: '/Kiosks',
type: 'all'});
var units = page.organizationUnits;
if (units) {
for (var i = 0; i < units.length; i++) {
var unit = units[i];
Logger.log(unit);
}
} else {
Logger.log('No units found.');
}
Thank you very much
You mistyped 'my_customer' and orgUnitPath. Also, organizationalUnits is going to be an array of objects that you will want to get specific properties of such as orgUnit.name, not log directly.
function listSubOUs() {
var page = AdminDirectory.Orgunits.list('my_customer', {
orgUnitPath: '/',
type: 'all'
});
var orgUnits = page.organizationUnits;
if (orgUnits) {
for (var i = 0; i < orgUnits.length; i++) {
var orgUnit = orgUnits[i];
Logger.log('%s (%s)', orgUnit.name, orgUnit.orgUnitPath, orgUnit.description);
}
} else {
Logger.log('No OUs found.');
}
}