Skip to content

Commit

Permalink
Merge pull request #18 from chriskyfung/fix-google-drive-security-update
Browse files Browse the repository at this point in the history
## Fix google drive security update

🏗 CHANGES:

- Rebuilt badge update functions with Class DriveApp
- Removed the functions that were dependent on Drive API v3
- Removed unused script files
- Split testing codes to a separated script file
  • Loading branch information
chriskyfung authored Sep 12, 2021
2 parents 4f327de + c7f5e1f commit 8baae92
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 160 deletions.
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

0 comments on commit 8baae92

Please sign in to comment.