Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Google Drive security update #18

Merged
merged 4 commits into from
Sep 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ For Build 2020.06.05, a new function called `test_pipeline()` has been added to

## History

🚧 **AN IMPORTANT UPDATE ON 2021-09-12** 🚧

([#17](https://github.com/chriskyfung/AutoFetcher-IG-Stories-to-GDrive/issues/17)) Google Drive Drive will apply a security update on September 13, 2021. Please update your Apps Script code to avoid failing access to Google Drive files.

🚧 **AN IMPORTANT UPDATE ON 2020-12-09** 🚧

([#11](https://github.com/chriskyfung/AutoFetcher-IG-Stories-to-GDrive/issues/11)) Instagram changed code around noon, 7 Dec, UTC. Please update to Build 2020.12.09.
Expand Down
178 changes: 84 additions & 94 deletions gascript/code.gs
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/**
* Copyright (c) 2018-2021
*
* A Google Apps Script for deploying a web application that automatically fetches the latest available IG Stories of a target Instagram user to your Google Drive.
*
* @summary short description for the file
* @author Chris K.Y. Fung <github.com/chriskyfung>
*
* Created at : 2018-01-29
* Last modified : 2021-09-12
*/

// User-defined Variables
var g_username = 'your username for this app';
var g_password = 'your password for this app';
Expand All @@ -16,14 +28,23 @@ var X_IG_WWW_CLAIM = '<your x-ig-www-claim in the request header>';

var isDebug = false;

/**
* Compose the URL and the query string to the Instagram's API endpoint
*/
function getQuery(ig_user_id){
return "https://i.instagram.com/api/v1/feed/reels_media/?reel_ids=" + ig_user_id;
}

/**
* Fetch data from the Instagram's API with a custom header.
* @param {string} query - The URL and the query string to the API endpoint.
* @returns {Object} The content of an HTTP response encoded as a string.
*/
function fetch_ig_stories(query){
var opt3 = {
"headers": {
"accept": "*/*",
"accept-language": "zh-HK,zh;q=0.9,en-HK;q=0.8,en;q=0.7,ja-JP;q=0.6,ja;q=0.5,en-US;q=0.4,zh-TW;q=0.3",
"cache-control": "no-cache",
"pragma": "no-cache",
"sec-fetch-dest": "empty",
Expand All @@ -39,21 +60,27 @@ function fetch_ig_stories(query){
"method": "GET",
"mode": "cors"
};
var data = UrlFetchApp.fetch(query, opt3).getContentText();
if (isDebug) { Logger.log(data); }
return data;
var response = UrlFetchApp.fetch(query, opt3).getContentText();
if (isDebug) { Logger.log(response); }
return response;
}

function parseDownloadUrl(html,isTest){
if (html == '{"reels": {}, "reels_media": [], "status": "ok"}') {
/**
* Resolve the URLs of downloadable media files from a textual HTTP response.
* @param {string} rawdata - The JSON string contains the data retrieved from Instagram's API.
* @param {boolen} isTest - Set true to run in Test mode without changing the log file.
* @returns {string[]} The URLs of downloadable media files.
*/
function parseDownloadUrl(response,isTest){
if (response == '{"reels":{},"reels_media":[],"status":"ok"}') {
return [];
};
if (isTest) {
var data = JSON.parse(html);
var data = JSON.parse(response);
} else {
var fetchContentlog = DocumentApp.openById(fetchContentLog_id);
fetchContentlog.clear();
fetchContentlog.appendParagraph(html);
fetchContentlog.appendParagraph(response);
var body = fetchContentlog.getBody().getText();
var data = JSON.parse(body);
}
Expand All @@ -69,10 +96,19 @@ function parseDownloadUrl(html,isTest){
urls[i] = item.image_versions2.candidates[0].url;
}
}
if (isDebug) { Logger.log(urls); }
//Logger.log(urls);
return urls;
}

/**
* Main function of this Apps Script.
* Append logs to the specified Goolge Doc file.
* Fetch data from Instagram's API using fetch_ig_stories().
* Resolve the URLs of downloadable media files from the retrieved data using parseDownloadUrl().
* Upload the media files to Google Drive if any URLs do not exist in the log file.
* @param {Object} targetIgUser - A JSON object contains the name and id of an Instagram account, e.g. { "name": "john", "id": "1234567890" }.
* @returns {string} The log messages.
*/
function __main__(targetIgUser) {

// Access the body of the log google doc file
Expand All @@ -84,12 +120,12 @@ function __main__(targetIgUser) {

// Fetch URL
var query_url = getQuery(targetIgUser.id);
var html = fetch_ig_stories(query_url);
var urls = parseDownloadUrl(html,false);
var response = fetch_ig_stories(query_url);
var urls = parseDownloadUrl(response,false);

var msg = '';

// For each article element found in the HTML
// For each article element found in the HTML response
if (urls != null && urls.length > 0) {
for (i in urls) {
var url = urls[i];
Expand All @@ -108,7 +144,7 @@ function __main__(targetIgUser) {
msg = 'File has already been fetched once.'
}
lastlogfile.getBody().appendParagraph('\n\t' + msg); // log the upload msg to the Doc file
Logger.log('msg');
Logger.log(msg);
}
}
var logtxt = lastlogfile.getBody().getText();
Expand All @@ -118,98 +154,45 @@ function __main__(targetIgUser) {
return logtxt;
}

function test_fucntion (targetIgUser) {
var query_url = getQuery(targetIgUser.id);
var html = fetch_ig_stories(query_url);

var urls = parseDownloadUrl(html,true);
Logger.log("Number of URL(s) from @" + targetIgUser.id + ": " + urls.length);
return urls.length;
}

function test_nasa_ig(){
return test_fucntion({ "name": "nasa", "id":"528817151"});
}

function test_medium_ig(){
return test_fucntion({ "name": "medium", "id":"1112881921"});
}

function test_bbcnews_ig(){
return test_fucntion({ "name": "bbcnews", "id":"16278726"});
}

function test_pipeline(){
updateLastTestedBadge();
if (test_bbcnews_ig() == 0) {
if (test_nasa_ig() == 0) {
if (test_medium_ig() == 0) {
MailApp.sendEmail(crashReportEmail,
"Google Apps Script [AutoFetcher-IG-Stories-to-GDrive] Crash reporter",
"Get none urls from the test accounts.\nPlease verify the service websites and check update on https://bit.ly/2zQLd6p.");
Logger.log('Failed to fetch data from the test accounts!!!')
updateBadgeToFailStatus();
return false;
};
};
};
Logger.log('Successfully fetch data from the test accounts!')
updateBadgeToSucessStatus();
return true;
}

function updateBadgeToFailStatus() {
/**
* Update the SVG file of "Test Status" badge to "Failed" using Class DriveApp.
* @param {string} status - The arguement to determine the badge color and the text to display in the badge.
* @returns {string|null} The URL that can be used to download the file. Otherwise, returns null.
*/
function setTestStatusBadge(status='failed') {
if (statusBadge_id != '') {
var fileDescr = Drive.Files.get(statusBadge_id).getDescription();
if (fileDescr != 'test-failed') {
var newBadge = UrlFetchApp.fetch("https://img.shields.io/badge/ig%20fetch%20test-failed-red");
var badgeBlob = newBadge.getBlob();
var file_meta = {
mimeType: 'image/svg+xml',
description: 'test-failed'
};
var updatedFile = Drive.Files.update(file_meta, statusBadge_id,badgeBlob);
};
};
}

function updateBadgeToSucessStatus() {
if (statusBadge_id != '') {
var fileDescr = Drive.Files.get(statusBadge_id).getDescription();
if (fileDescr != 'test-passed') {
var newBadge = UrlFetchApp.fetch("https://img.shields.io/badge/ig%20fetch%20test-passed-brightgreen");
var badgeBlob = newBadge.getBlob();
var file_meta = {
mimeType: 'image/svg+xml',
description: 'test-passed'
};
var updatedFile = Drive.Files.update(file_meta, statusBadge_id,badgeBlob);
};
};
const color = status == 'passed' ? '#4c1' : '#f00';
const file = DriveApp.getFileById(statusBadge_id);
return DriveApp.getFileByIdAndResourceKey(statusBadge_id, file.getResourceKey()).setContent(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="124" height="20" role="img" aria-label="ig fetch test: ${status}}"><title>ig fetch test: ${status}</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="124" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="75" height="20" fill="#555"/><rect x="75" width="49" height="20" fill="${color}"/><rect width="124" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="385" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="650">ig fetch test</text><text x="385" y="140" transform="scale(.1)" fill="#fff" textLength="650">ig fetch test</text><text aria-hidden="true" x="985" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="390">${status}</text><text x="985" y="140" transform="scale(.1)" fill="#fff" textLength="390">${status}</text></g></svg>`).setDescription(`test-${status}`).getDownloadUrl();
}
return null;
}

function updateLastTestedBadge() {
/**
* Update the SVG file of "last tested" badge using Class DriveApp
* @returns {string|null} The URL that can be used to download the file. Otherwise, returns null.
*/
function setTestDateBadge() {
if (lastTestedBadge_id != '') {
var formattedDate = Utilities.formatDate(new Date(), "GMT", "MMM dd, YYYY");
var webSafeFormattedDate = formattedDate.replace(/\s/g, '%20').replace(/,/g, '%2C');
Logger.log(formattedDate) ;
var newBadge = UrlFetchApp.fetch("https://img.shields.io/badge/last%20tested-" + webSafeFormattedDate + "-orange");
var badgeBlob = newBadge.getBlob();
var file_meta = {
mimeType: 'image/svg+xml',
description: 'last test badge'
};
var updatedFile = Drive.Files.update(file_meta, lastTestedBadge_id,badgeBlob);
};
const formattedDate = Utilities.formatDate(new Date(), "GMT", "MMM dd, YYYY");
const file = DriveApp.getFileById(lastTestedBadge_id);
return DriveApp.getFileByIdAndResourceKey(lastTestedBadge_id, file.getResourceKey()).setContent(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="152" height="20" role="img" aria-label="last tested: ${formattedDate}"><title>last tested: ${formattedDate}</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="152" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="67" height="20" fill="#555"/><rect x="67" width="85" height="20" fill="#fe7d37"/><rect width="152" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="345" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="570">last tested</text><text x="345" y="140" transform="scale(.1)" fill="#fff" textLength="570">last tested</text><text aria-hidden="true" x="1085" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="750">${formattedDate}</text><text x="1085" y="140" transform="scale(.1)" fill="#fff" textLength="750">${formattedDate}</text></g></svg>`).getDownloadUrl();
}
return null;
}

/**
* Handle all HTTP GET requests made to the web app URL.
* @param {Object} e - An event object containing request parameters, including the username and password for simple security check and the id and name of an Instagram account.
* @returns {string} The log messages.
*/
function doGet(e) {

var usr = '';
var pwd = '';
var target = '';

// parse query
// parse the username, password, and targeted Instagrm account information
try {
usr = e.parameter.usr.trim();
pwd = e.parameter.pwd.trim();
Expand All @@ -221,17 +204,24 @@ function doGet(e) {

var msg = '';

// execute if correct username and password in the query
/**
* Run __main__() if the request made with valid username, password, target parameters.
* Send the textual log or error message as the HTML response.
*/
if (usr == g_username && pwd == g_password && target != '') {
msg = __main__(target);
msg = ContentService.createTextOutput(msg);
}
else { msg = ContentService.createTextOutput('Invalid username and password or targetID!');
Logger.log(msg);
};
};

return msg;
}

/**
* Test doGet() with targeting NASA instagram stories
*/
function try_get() {
var e = {
"parameter": {
Expand Down
53 changes: 0 additions & 53 deletions gascript/parseHtml.gs

This file was deleted.

Loading