GitHub Rest Api SHA of file that contains special characters - google-apps-script

Main issue
This code works, but the areReallyEqual should not be necessary. In fact it is not necessary for files that don't contain special characters ("ñ", "é", "à", etc), so: all of my files except for 2 of them.
if (internalFileTextSha !== repositoryFile.sha) {
// The sha might not be equal due to special characters in the text ("ñ", "é", "à", etc)... doing a second check...
const remoteFileText = this._gitHubApi.fetchGitHubGetUrl(repositoryFile.download_url).getContentText();
const remoteFileTextSha = GitHubCrypto.getSha(remoteFileText);
const areReallyEqual = remoteFileTextSha === internalFileTextSha;
if(!areReallyEqual) {
// The file has been modified, creating a new commit with the updated file...
this._gitHubApi.commitUpdatedFile(repositoryName, repositoryFile, internalFile.source);
} else {
// The second check determined that both files were equal
}
}
More details
internalFile comes from here:
/**
* #typedef {Object} InternalFile
* #property {string} id - The ID of the internal file.
* #property {string} name - The name of the internal file.
* #property {string} type - The type of the internal file.
* #property {string} source - The source code of the internal file.
*/
/**
* Gets all the internal files of a Google Apps Script file.
*
* #returns {InternalFile[]} An array of objects.
*/
static getScriptInternalFiles(file) {
// Check that the file is a Google Apps Script file
if (file.getMimeType() == 'application/vnd.google-apps.script') {
// Get the script content as a string
const fileId = file.getId();
const params = {
headers: {
Authorization: 'Bearer ' + ScriptApp.getOAuthToken(),
'Accept-Charset': 'utf-8'
},
followRedirects: true,
muteHttpExceptions: true,
};
const url =
'https://script.google.com/feeds/download/export?id='
+ fileId
+ '&format=json';
const response = UrlFetchApp.fetch(url, params);
const json = JSON.parse(response);
return json.files;
} else {
throw new Error("The file is not a Google Apps Script file.");
}
}
...while remoteFile comes from here:
/**
* #typedef {Object} RepositoryFile
* #property {string} name - The name of the file.
* #property {string} path - The file path in the repository.
* #property {string} sha - The SHA hash of the file.
* #property {Number} size - The size of the file in bytes.
* #property {string} url - The URL of the file's contents.
* #property {string} html_url - The URL to the file's page on the repository website.
* #property {string} git_url - The URL to the file's git contents.
* #property {string} download_url - The URL to download the file.
* #property {string} type - The type of the file, usually "file".
*/
/**
* Gets all the internal files of a Google Apps Script file.
*
* #returns {RepositoryFile[]} An array of objects.
*/
listFilesInRepository(repositoryName) {
let repositoryFiles = [];
try {
const options = {
headers: {
...this._authorizationHeader,
},
};
const response = UrlFetchApp.fetch(`${this._buildRepositoryUrl(repositoryName)}/contents`, options);
repositoryFiles = JSON.parse(response);
} catch(e) {
const errorMessage = GitHubApi._getErrorMessage(e);
if(errorMessage === 'This repository is empty.') {
// do nothing
} else {
// unknown error
throw e;
}
}
return repositoryFiles;
}
...and the SHA calculation:
class GitHubCrypto {
/**
* #param {string} fileContent
* #returns {string} SHA1 hash string
*/
static getSha(fileContent) {
// GitHub is computing the sum of `blob <length>\x00<contents>`, where `length` is the length in bytes of the content string and `\x00` is a single null byte.
// For the Sha1 implementation, see: www.movable-type.co.uk/scripts/sha1.html
const sha = Sha1.hash('blob ' + fileContent.length + '\x00' + fileContent);
return sha;
}
}

I believe your goal is as follows.
You want to retrieve the has value of 430f370909821443112064cd149a4bebd271bfc4 from a value of ñèàü using Google Apps Script.
In this case, how about the following sample script?
Sample script:
function myFunction() {
const fileContent = 'ñèàü'; // This is from your comment.
const value = 'blob ' + fileContent.length + '\x00' + fileContent;
const bytes = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_1, value, Utilities.Charset.UTF_8);
const res = bytes.map(byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
console.log(res); // 430f370909821443112064cd149a4bebd271bfc4
}
When this script is run, the has value of 430f370909821443112064cd149a4bebd271bfc4 is obtained.
When aaaa is directly used like const res = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_1, "aaaa", Utilities.Charset.UTF_8).map(byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join(''), 70c881d4a26984ddce795f6f71817c9cf4480e79 is obtained.
Reference:
computeDigest(algorithm, value)

I have finally found the problem... the problem was in the "bytes length":
GitHub is computing the sum of blob <length>\x00<contents>, where length is the length in bytes of the content string and \x00 is a single null byte.
When there are no special characters, lengthInBytes == inputStr.length, but when there are special characters this is no longer true:
// For the Sha1 implementation, see: www.movable-type.co.uk/scripts/sha1.html
function simpleShaTest1() {
const fileContent = 'ñèàü';
const expectedSha = '56c9357fcf2589619880e1978deb8365454ece11';
const sha = Sha1.hash('blob ' + byteLength(fileContent) + '\x00' + fileContent);
const areEqual = (sha === expectedSha); // true
}
function simpleShaTest2() {
const fileContent = 'aaa';
const expectedSha = '7c4a013e52c76442ab80ee5572399a30373600a2';
const sha = Sha1.hash('blob ' + lengthInUtf8Bytes(fileContent) + '\x00' + fileContent);
const areEqual = (sha === expectedSha); // true
}
function lengthInUtf8Bytes(str) {
// Matches only the 10.. bytes that are non-initial characters in a multi-byte sequence.
var m = encodeURIComponent(str).match(/%[89ABab]/g);
// `m` is `null` when there are no special characters, thus returning `m.length`
return str.length + (m ? m.length : 0);
}

Related

list activities on google drive

I would like to be able to upload my file activities with API from google drive activity or other ideas. Each of my files are stored in a tree structure that contains the name of my client. And in the following put it in an excel file in order to be able to sort it correctly for example if a file has been modified more than 2 times in one day, it is validated
Currently, I used the code provided by google but I can't find the directory of these modified files
I thank you in advance, I really block, other ideas or API are welcome.
Sincerly
/**
* Lists 10 activity for a Drive user.
* #see https://developers.google.com/drive/activity/v2/reference/rest/v2/activity/query
*/
function listDriveActivity() {
const request = {
"ancestor_name": "items/root",
"filter": "time >= \"2022-06-01T00:00:00Z\" time < \"2022-06-30T00:00:00Z\" detail.action_detail_case:EDIT",
"consolidation_strategy": { "legacy": {} },
"page_size": 10,
};
try {
// Activity.query method is used Query past activity in Google Drive.
const response = DriveActivity.Activity.query(request);
const activities = response.activities;
if (!activities || activities.length === 0) {
console.log('No activity.');
return;
}
console.log('Recent activity:');
for (const activity of activities) {
// get time information of activity.
const time = getTimeInfo(activity);
// get the action details/information
const action = getActionInfo(activity.primaryActionDetail);
// get the actor's details of activity
const actors = activity.actors.map(getActorInfo);
// get target information of activity.
const targets = activity.targets.map(getTargetInfo);
// print the time,actor,action and targets of drive activity.
console.log('%s: %s, %s, %s', time, actors, action, targets);
}
} catch (err) {
// TODO (developer) - Handle error from drive activity API
console.log('Failed with an error %s', err.message);
}
}
/**
* #param {object} object
* #return {string} Returns the name of a set property in an object, or else "unknown".
*/
function getOneOf(object) {
for (const key in object) {
return key;
}
return 'unknown';
}
/**
* #param {object} activity Activity object.
* #return {string} 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';
}
/**
* #param {object} actionDetail The primary action details of the activity.
* #return {string} Returns the type of action.
*/
function getActionInfo(actionDetail) {
return getOneOf(actionDetail);
}
/**
* #param {object} user The User object.
* #return {string} Returns user information, or the type of user if not a known user.
*/
function getUserInfo(user) {
if ('knownUser' in user) {
const knownUser = user.knownUser;
const isMe = knownUser.isCurrentUser || false;
return isMe ? 'people/me' : knownUser.personName;
}
return getOneOf(user);
}
/**
* #param {object} actor The Actor object.
* #return {string} 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);
}
/**
* #param {object} target The Target object.
* #return {string} Returns the type of a target and an associated title.
*/
function getTargetInfo(target) {
if ('driveItem' in target) {
const title = target.driveItem.title || 'unknown';
return 'driveItem:"' + title + '"';
}
if ('drive' in target) {
const title = target.drive.title || 'unknown';
return 'drive:"' + title + '"';
}
if ('fileComment' in target) {
const parent = target.fileComment.parent || {};
const title = parent.title || 'unknown';
return 'fileComment:"' + title + '"';
}
return getOneOf(target) + ':unknown';
}
Final goal: to get the amount of days I work for all my clients.
That a idea
After some search and code (some help with GTP so sorry for begin code)
function listDriveActivity(sheet,timezone) {
let pageToken = null;
do {
try {
// Activity.query method is used Query past activity in Google Drive.
const response = DriveActivity.Activity.query({ "ancestor_name": "items/13oHhdSDQqnM4ppO48FPJq7HAmVjR27H5", //"items/root",
"filter": "time >= \"2022-01-01T00:00:00Z\" time < \"2022-01-31T00:00:00Z\" detail.action_detail_case:EDIT",
"consolidation_strategy": { "legacy": {} },"pageSize": 10,pageToken: pageToken});
//Logger.log(response);
const activities = response.activities;
if (!activities || activities.length === 0) {
console.log('No activity.');
return;
}
//console.log('Recent activity:');
for (const activity of activities) {
// get time information of activity.
const time = getTimeInfo(activity);
// get the action details/information
const action = getActionInfo(activity.primaryActionDetail);
// get the actor's details of activity
//const actors = activity.actors.map(getActorInfo);
// get target information of activity.
const targets = activity.targets.map(getTargetInfo);
// print the time,actor,action and targets of drive activity.
// const folderName = activity.targets.map(getFileArborescenceByName);
const folderName = activity.targets.map(getFileArborescenceByID);
var NomDossier = JSON.stringify(folderName).replace(/\[\"|\"\]/g,'');
const ClientName = getClient(NomDossier);
//console.log('%s: %s, %s, %s, %s', time, action, targets, folderName, ClientName);
const timeAsDate = new Date(time);
const lastModified = Utilities.formatDate(timeAsDate, "UTC", "dd-MM-yyyy HH:mm");
var Nomfichier = JSON.stringify(targets).replace(/\[\"|\"\]/g,'');
sheet.appendRow([lastModified, action, Nomfichier, NomDossier, ClientName]);
}
pageToken = response.nextPageToken;
}
catch (err) {
// TODO (developer) - Handle error from drive activity API
console.log('Failed with an error %s', err.message);
}
} while (pageToken);
return sheet;
}
function getFileArborescenceByID(activity) {
if ('driveItem' in activity) {
try {
const fileName = activity.driveItem.name;
var modif = getLastPart (fileName);
var file = DriveApp.getFileById(modif);
var folders = [];
var parent = file.getParents();
while (parent.hasNext()) {
parent = parent.next();
folders.push(parent.getName());
parent = parent.getParents();
}
if (folders.length) {
// Display the full folder path
var folderName = (folders.reverse().join("/"));
}
//var ClientName = getClient(folderName);
}
catch (err) {
// TODO (developer) - Handle error from drive activity API
console.log('Failed with an error %s', err.message);
}
}
//return 'folderName:"' + folderName + '", Client :"' + ClientName + '"';
return folderName;

Exception: Cannot retrieve the next object: iterator has reached the end

im trying to get files in different folders and get some cell values from them and put it in a sheet.
but i get below error
Exception: Cannot retrieve the next object: iterator has reached the end
im using below code and when i run it. it returns error on line 14
it seems code returns some values from first and second folders but for third one it returns error
after running macro it gives log below:
5:24:43 PM Notice Execution started
5:24:45 PM Info [[]]
5:24:45 PM Info [[]]
5:24:46 PM Error
Exception: Cannot retrieve the next object: iterator has reached the end.
list_files_in_folders # macros.gs:14
my code : line 14 error
function list_files_in_folders(){
var sh = SpreadsheetApp.getActiveSheet();
var mainfolder = DriveApp.getFolderById('id-here'); // I change the folder ID here
var mfolders = mainfolder.getFolders();
var data = [];
data.push(['Person Name','File Name','Value 1','Value 2']);
while (mfolders.hasNext){
var mfolder = mfolders.next();
var personfolder = DriveApp.getFolderById(mfolder.getId());
var pfiles = personfolder.getFiles();
data.push([personfolder.getName,,,]);
while(pfiles.hasNext){
var pfile = pfiles.next(); //error here
var personfile = SpreadsheetApp.openById(pfile.getId());
var value1 = personfile.getSheetValues(2,9,1,1);
//var value2 = personfile.getSheetValues();
Logger.log(value1);
data.push(["",pfile.getName,value1,""]);
}
}
sh.getRange(1,1,data.length,data[0].length).setValues(data);
}
Folder or File hasNext() is a method, not a property.
Replace both
mfolders.hasNext
pfiles.hasNext
With
mfolders.hasNext()
pfiles.hasNext()
Reference
FolderIterator.hasNext()
FileIterator.hasNext()
You can make your list_files_in_folders() function easier to adapt by doing report building in one function and the recursive subfolder iteration in another, using closures to process the files.
More lines of code are required, but making changes becomes much easier, and you can apply these functions in other use cases more smoothly — just modify the _fileAction and _folderAction closures as required.
This pattern also lets you handle errors and timeouts more gracefully.
/** #NotOnlyCurrentDoc */
'use strict';
function list_files_in_folders() {
const mainFolder = DriveApp.getFolderById('...put folder ID here...');
const data = generateReport_(mainFolder);
SpreadsheetApp.getActiveSheet().getRange('A1')
.offset(0, 0, data.length, data[0].length)
.setValues(data);
}
/**
* Generates a report based on spreadsheets organized by subfolder.
*
* #param {DriveApp.Folder} folder A folder with files and subfolders.
* #return {String[][]} A 2D array that contains a report of files organized by subfolder.
*/
function generateReport_(folder) {
const data = [];
const _errorHandler = (error) => console.log(error.message);
const _prefix = (folderName, nestingLevel) => ' '.repeat(nestingLevel) + folderName;
const _folderAction = (folder, nestingLevel) => {
data.push([_prefix(folder.getName(), nestingLevel), '', '', '']);
};
const _fileAction = (file) => {
const ss = getSpreadsheetFromFile_(file, _errorHandler);
if (ss) {
data.push(['', ss.getName(), ss.getSheetValues(2, 9, 1, 1), '']);
} else {
data.push(['', file.getName(), '(not a Google Sheet)', '']);
}
};
const timelimit = new Date().getTime() + 4 * 60 * 1000; // stop when 4 minutes have passed
const error = processFilesInFolderRecursively_(folder, _folderAction, _fileAction, _errorHandler, 1, timelimit);
if (error) {
data.push([error.message, '', '', '']);
_errorHandler(error);
}
return [['Person Name', 'File Name', 'Value 1', 'Value 2']].concat(
data.length ? data : [['(Could not find any files. Check folder ID.)', '', '', '']]
);
}
/**
* Iterates files in a folder and its subfolders, executing
* _folderAction with each folder and _fileAction with each file.
*
* #param {DriveApp.Folder} folder The folder to process.
* #param {Function} _folderAction The function to run with each folder.
* #param {Function} _fileAction The function to run with each file.
* #param {Function} _errorHandler The function to call when an error occurs.
* #param {String} nestingLevel Optional. A number that indicates the current folder nesting nevel.
* #param {Number} timelimit Optional. The moment in milliseconds that indicates when to stop processing.
* #return {Error} An error when the function ran out of time, otherwise undefined.
* #license https://www.gnu.org/licenses/gpl-3.0.html
*/
function processFilesInFolderRecursively_(folder, _folderAction, _fileAction, _errorHandler, nestingLevel, timelimit) {
// version 1.2, written by --Hyde, 1 December 2022
nestingLevel = nestingLevel || 0;
const outOfTime = new Error('Ran out of time.');
if (new Date().getTime() > timelimit) {
return outOfTime;
}
const files = folder.getFiles();
while (files.hasNext()) {
if (new Date().getTime() > timelimit) {
return outOfTime;
}
try {
_fileAction(files.next());
} catch (error) {
_errorHandler(error);
}
}
const subfolders = folder.getFolders();
while (subfolders.hasNext()) {
const folder = subfolders.next();
_folderAction(folder, nestingLevel);
const error = processFilesInFolderRecursively_(folder, _folderAction, _fileAction, _errorHandler, nestingLevel + 1, timelimit);
if (error) {
return error; // outOfTime
};
}
}
/**
* Gets a spreadsheet object from a file object.
*
* #param {DriveApp.File} file The file that contains a spreadsheet.
* #param {Function} _errorHandler The function to call when an error occurs.
* #return {SpreadsheetApp.Spreadsheet} The spreadsheet object, or null if file is not a spreadsheet or cannot be accessed.
* #license https://www.gnu.org/licenses/gpl-3.0.html
*/
function getSpreadsheetFromFile_(file, _errorHandler) {
// version 1.0, written by --Hyde, 9 October 2022
if (file.getMimeType() !== 'application/vnd.google-apps.spreadsheet') {
return null;
}
let ss;
try {
ss = SpreadsheetApp.open(file);
} catch (error) {
_errorHandler(error);
return null;
}
return ss;
}

Import value from JSON in google sheets

I'm new to JSON and I found this custom script to import a JSON array in Google Sheets, but now I'm interested to filter out a certain value. How do I do this? I have no idea how to query this.
Example: I'm interested in the Pairs Priceusd value of this URL: https://api.dexscreener.io/latest/dex/tokens/0x333fd139caef6aa31056cc905987b77b1044d259.
Credits for the script go to: https://gist.github.com/paulgambill/cacd19da95a1421d3164
/**
* Retrieves all the rows in the active spreadsheet that contain data and logs the
* values for each row.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function readRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
Logger.log(row);
}
};
/**
* Adds a custom menu to the active spreadsheet, containing a single menu item
* for invoking the readRows() function specified above.
* The onOpen() function, when defined, is automatically invoked whenever the
* spreadsheet is opened.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Read Data",
functionName : "readRows"
}];
sheet.addMenu("Script Center Menu", entries);
};
/*====================================================================================================================================*
ImportJSON by Trevor Lohrbeer (#FastFedora)
====================================================================================================================================
Version: 1.1
Project Page: http://blog.fastfedora.com/projects/import-json
Copyright: (c) 2012 by Trevor Lohrbeer
License: GNU General Public License, version 3 (GPL-3.0)
http://www.opensource.org/licenses/gpl-3.0.html
------------------------------------------------------------------------------------------------------------------------------------
A library for importing JSON feeds into Google spreadsheets. Functions include:
ImportJSON For use by end users to import a JSON feed from a URL
ImportJSONAdvanced For use by script developers to easily extend the functionality of this library
Future enhancements may include:
- Support for a real XPath like syntax similar to ImportXML for the query parameter
- Support for OAuth authenticated APIs
Or feel free to write these and add on to the library yourself!
------------------------------------------------------------------------------------------------------------------------------------
Changelog:
1.1 Added support for the noHeaders option
1.0 Initial release
*====================================================================================================================================*/
/**
* Imports a JSON feed and returns the results to be inserted into a Google Spreadsheet. The JSON feed is flattened to create
* a two-dimensional array. The first row contains the headers, with each column header indicating the path to that data in
* the JSON feed. The remaining rows contain the data.
*
* By default, data gets transformed so it looks more like a normal data import. Specifically:
*
* - Data from parent JSON elements gets inherited to their child elements, so rows representing child elements contain the values
* of the rows representing their parent elements.
* - Values longer than 256 characters get truncated.
* - Headers have slashes converted to spaces, common prefixes removed and the resulting text converted to title case.
*
* To change this behavior, pass in one of these values in the options parameter:
*
* noInherit: Don't inherit values from parent elements
* noTruncate: Don't truncate values
* rawHeaders: Don't prettify headers
* noHeaders: Don't include headers, only the data
* debugLocation: Prepend each value with the row & column it belongs in
*
* For example:
*
* =ImportJSON("http://gdata.youtube.com/feeds/api/standardfeeds/most_popular?v=2&alt=json", "/feed/entry/title,/feed/entry/content",
* "noInherit,noTruncate,rawHeaders")
*
* #param {url} the URL to a public JSON feed
* #param {query} a comma-separated lists of paths to import. Any path starting with one of these paths gets imported.
* #param {options} a comma-separated list of options that alter processing of the data
*
* #return a two-dimensional array containing the data, with the first row containing headers
* #customfunction
**/
function ImportJSON(url, query, options) {
return ImportJSONAdvanced(url, query, options, includeXPath_, defaultTransform_);
}
/**
* An advanced version of ImportJSON designed to be easily extended by a script. This version cannot be called from within a
* spreadsheet.
*
* Imports a JSON feed and returns the results to be inserted into a Google Spreadsheet. The JSON feed is flattened to create
* a two-dimensional array. The first row contains the headers, with each column header indicating the path to that data in
* the JSON feed. The remaining rows contain the data.
*
* Use the include and transformation functions to determine what to include in the import and how to transform the data after it is
* imported.
*
* For example:
*
* =ImportJSON("http://gdata.youtube.com/feeds/api/standardfeeds/most_popular?v=2&alt=json",
* "/feed/entry",
* function (query, path) { return path.indexOf(query) == 0; },
* function (data, row, column) { data[row][column] = data[row][column].toString().substr(0, 100); } )
*
* In this example, the import function checks to see if the path to the data being imported starts with the query. The transform
* function takes the data and truncates it. For more robust versions of these functions, see the internal code of this library.
*
* #param {url} the URL to a public JSON feed
* #param {query} the query passed to the include function
* #param {options} a comma-separated list of options that may alter processing of the data
* #param {includeFunc} a function with the signature func(query, path, options) that returns true if the data element at the given path
* should be included or false otherwise.
* #param {transformFunc} a function with the signature func(data, row, column, options) where data is a 2-dimensional array of the data
* and row & column are the current row and column being processed. Any return value is ignored. Note that row 0
* contains the headers for the data, so test for row==0 to process headers only.
*
* #return a two-dimensional array containing the data, with the first row containing headers
**/
function ImportJSONAdvanced(url, query, options, includeFunc, transformFunc) {
var jsondata = UrlFetchApp.fetch(url);
var object = JSON.parse(jsondata.getContentText());
return parseJSONObject_(object, query, options, includeFunc, transformFunc);
}
/**
* Encodes the given value to use within a URL.
*
* #param {value} the value to be encoded
*
* #return the value encoded using URL percent-encoding
*/
function URLEncode(value) {
return encodeURIComponent(value.toString());
}
/**
* Parses a JSON object and returns a two-dimensional array containing the data of that object.
*/
function parseJSONObject_(object, query, options, includeFunc, transformFunc) {
var headers = new Array();
var data = new Array();
if (query && !Array.isArray(query) && query.toString().indexOf(",") != -1) {
query = query.toString().split(",");
}
if (options) {
options = options.toString().split(",");
}
parseData_(headers, data, "", 1, object, query, options, includeFunc);
parseHeaders_(headers, data);
transformData_(data, options, transformFunc);
return hasOption_(options, "noHeaders") ? (data.length > 1 ? data.slice(1) : new Array()) : data;
}
/**
* Parses the data contained within the given value and inserts it into the data two-dimensional array starting at the rowIndex.
* If the data is to be inserted into a new column, a new header is added to the headers array. The value can be an object,
* array or scalar value.
*
* If the value is an object, it's properties are iterated through and passed back into this function with the name of each
* property extending the path. For instance, if the object contains the property "entry" and the path passed in was "/feed",
* this function is called with the value of the entry property and the path "/feed/entry".
*
* If the value is an array containing other arrays or objects, each element in the array is passed into this function with
* the rowIndex incremeneted for each element.
*
* If the value is an array containing only scalar values, those values are joined together and inserted into the data array as
* a single value.
*
* If the value is a scalar, the value is inserted directly into the data array.
*/
function parseData_(headers, data, path, rowIndex, value, query, options, includeFunc) {
var dataInserted = false;
if (isObject_(value)) {
for (key in value) {
if (parseData_(headers, data, path + "/" + key, rowIndex, value[key], query, options, includeFunc)) {
dataInserted = true;
}
}
} else if (Array.isArray(value) && isObjectArray_(value)) {
for (var i = 0; i < value.length; i++) {
if (parseData_(headers, data, path, rowIndex, value[i], query, options, includeFunc)) {
dataInserted = true;
rowIndex++;
}
}
} else if (!includeFunc || includeFunc(query, path, options)) {
// Handle arrays containing only scalar values
if (Array.isArray(value)) {
value = value.join();
}
// Insert new row if one doesn't already exist
if (!data[rowIndex]) {
data[rowIndex] = new Array();
}
// Add a new header if one doesn't exist
if (!headers[path] && headers[path] != 0) {
headers[path] = Object.keys(headers).length;
}
// Insert the data
data[rowIndex][headers[path]] = value;
dataInserted = true;
}
return dataInserted;
}
/**
* Parses the headers array and inserts it into the first row of the data array.
*/
function parseHeaders_(headers, data) {
data[0] = new Array();
for (key in headers) {
data[0][headers[key]] = key;
}
}
/**
* Applies the transform function for each element in the data array, going through each column of each row.
*/
function transformData_(data, options, transformFunc) {
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
transformFunc(data, i, j, options);
}
}
}
/**
* Returns true if the given test value is an object; false otherwise.
*/
function isObject_(test) {
return Object.prototype.toString.call(test) === '[object Object]';
}
/**
* Returns true if the given test value is an array containing at least one object; false otherwise.
*/
function isObjectArray_(test) {
for (var i = 0; i < test.length; i++) {
if (isObject_(test[i])) {
return true;
}
}
return false;
}
/**
* Returns true if the given query applies to the given path.
*/
function includeXPath_(query, path, options) {
if (!query) {
return true;
} else if (Array.isArray(query)) {
for (var i = 0; i < query.length; i++) {
if (applyXPathRule_(query[i], path, options)) {
return true;
}
}
} else {
return applyXPathRule_(query, path, options);
}
return false;
};
/**
* Returns true if the rule applies to the given path.
*/
function applyXPathRule_(rule, path, options) {
return path.indexOf(rule) == 0;
}
/**
* By default, this function transforms the value at the given row & column so it looks more like a normal data import. Specifically:
*
* - Data from parent JSON elements gets inherited to their child elements, so rows representing child elements contain the values
* of the rows representing their parent elements.
* - Values longer than 256 characters get truncated.
* - Values in row 0 (headers) have slashes converted to spaces, common prefixes removed and the resulting text converted to title
* case.
*
* To change this behavior, pass in one of these values in the options parameter:
*
* noInherit: Don't inherit values from parent elements
* noTruncate: Don't truncate values
* rawHeaders: Don't prettify headers
* debugLocation: Prepend each value with the row & column it belongs in
*/
function defaultTransform_(data, row, column, options) {
if (!data[row][column]) {
if (row < 2 || hasOption_(options, "noInherit")) {
data[row][column] = "";
} else {
data[row][column] = data[row-1][column];
}
}
if (!hasOption_(options, "rawHeaders") && row == 0) {
if (column == 0 && data[row].length > 1) {
removeCommonPrefixes_(data, row);
}
data[row][column] = toTitleCase_(data[row][column].toString().replace(/[\/\_]/g, " "));
}
if (!hasOption_(options, "noTruncate") && data[row][column]) {
data[row][column] = data[row][column].toString().substr(0, 256);
}
if (hasOption_(options, "debugLocation")) {
data[row][column] = "[" + row + "," + column + "]" + data[row][column];
}
}
/**
* If all the values in the given row share the same prefix, remove that prefix.
*/
function removeCommonPrefixes_(data, row) {
var matchIndex = data[row][0].length;
for (var i = 1; i < data[row].length; i++) {
matchIndex = findEqualityEndpoint_(data[row][i-1], data[row][i], matchIndex);
if (matchIndex == 0) {
return;
}
}
for (var i = 0; i < data[row].length; i++) {
data[row][i] = data[row][i].substring(matchIndex, data[row][i].length);
}
}
/**
* Locates the index where the two strings values stop being equal, stopping automatically at the stopAt index.
*/
function findEqualityEndpoint_(string1, string2, stopAt) {
if (!string1 || !string2) {
return -1;
}
var maxEndpoint = Math.min(stopAt, string1.length, string2.length);
for (var i = 0; i < maxEndpoint; i++) {
if (string1.charAt(i) != string2.charAt(i)) {
return i;
}
}
return maxEndpoint;
}
/**
* Converts the text to title case.
*/
function toTitleCase_(text) {
if (text == null) {
return null;
}
return text.replace(/\w\S*/g, function(word) { return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase(); });
}
/**
* Returns true if the given set of options contains the given option.
*/
function hasOption_(options, option) {
return options && options.indexOf(option) >= 0;
}```
You can retrieve the value by
function dexscreener(token) {
var url = 'https://api.dexscreener.io/latest/dex/tokens/' + token
var obj = JSON.parse(UrlFetchApp.fetch(url).getContentText())
return obj.pairs[0].priceUsd
}
put token in A1, and formula in B1 as follows
=dexscreener(A1)
Was able to make it work with this:
function DEXPRICE3 (url) {
var obj = JSON.parse(UrlFetchApp.fetch(url).getContentText())
return (obj.pairs[0].priceUsd)
}
Thanks for the help!

How to find out the correct link of the web table to copy with Urlfetch in GAS

I'm trying to get data from a table of a website. I was reading about the URL Fetch Service of Google App Scripts, I understand it, but cannot find what is the correct link to evaluate.
I tried this:
https://www.codelco.com/prontus_codelco/site/edic/base/port/licitaciones_enproceso.html
This is the basic code: (when I find the correct link I will develop more)
function getCodelcoTable() {
var html = UrlFetchApp.fetch('https://www.codelco.com/prontus_codelco/site/edic/base/port/licitaciones_enproceso.html');
Logger.log(html.getContentText());
}
Code
It seems there is what you need
/**
* Parses the first html-table from the content. max 7 columns
*
* #param {string} content A html string
* #returns {Array.<Array.<object>>}
*/
function parseHtmlTableToArray_(content) {
var inline = content.replace(/[\n\r]/g, ' ');
var table = inline.split(/<table.*?>/)[1].split(/<\/table>/)[0];
return table.split(/<tr.*?>/).map(function(row) {
return (
row
.split(/(<td.*?>|<th.*?>)/)
.slice(2)
.reduce(function(p, cell, i) {
if (~i % 2) {
p.push(
cell
.replace(/<.*?>/g, ' ')
.replace(/\s+/g, ' ')
.replace(/^\s+/g, '')
.replace(/\s+$/g, '')
);
}
return p;
}, [])
.join(PLACEHOLDER) + PLACEHOLDER.repeat(7)
)
.split(PLACEHOLDER)
.slice(0, 7);
});
}
You need to define PLACEHOLDER and pass your data to parseHtmlTableToArray_.
Run
For an example you can exec this like:
var PLACEHOLDER = '----YADA-YADA----';
function userActionRun() {
var content = UrlFetchApp.fetch(
'https://www.codelco.com/prontus_codelco/site/edic/base/port/licitaciones_enproceso.html'
).getContentText();
var data = parseHtmlTableToArray_(content);
SpreadsheetApp.openById('XXX')
.getSheets()[0]
.clearContents()
.getRange(1, 1, data.length, data[0].length)
.setValues(data);
}
Result
Notes
Don't miss add the String.repeat polyfill
Links
The full snippet
String.repeat
The full project example

pull json api to google sheets no oauth

I'm attempting to pull data from an API we are using and put it into Google Sheets. I've gotten this code from FastFedora (see code below), but that stuff currently isn't working or passing the credentials through unfortunately.
The way that i'm calling this currently in Google Sheets is by utilizing this command:
=ImportJSONBasicAuthentication("https://WEBSITE.com/builds/ids?include_shares=true&app_id=*","noInherit,noTruncate,rawHeaders")
Within the script.google, but it's returning an error of "Code 401". Any help is appreciated. I'm happy to answer any questions as well.
/**
* Retrieves all the rows in the active spreadsheet that contain data and logs the
* values for each row.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function readRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
Logger.log(row);
}
};
/**
* Adds a custom menu to the active spreadsheet, containing a single menu item
* for invoking the readRows() function specified above.
* The onOpen() function, when defined, is automatically invoked whenever the
* spreadsheet is opened.
* For more information on using the Spreadsheet API, see
* https://developers.google.com/apps-script/service_spreadsheet
*/
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{
name : "Read Data",
functionName : "readRows"
}];
sheet.addMenu("Script Center Menu", entries);
};
/*====================================================================================================================================*
ImportJSON by Trevor Lohrbeer (#FastFedora)
====================================================================================================================================
Version: 1.1
Project Page: http://blog.fastfedora.com/projects/import-json
Copyright: (c) 2012 by Trevor Lohrbeer
License: GNU General Public License, version 3 (GPL-3.0)
http://www.opensource.org/licenses/gpl-3.0.html
------------------------------------------------------------------------------------------------------------------------------------
A library for importing JSON feeds into Google spreadsheets. Functions include:
ImportJSON For use by end users to import a JSON feed from a URL
ImportJSONAdvanced For use by script developers to easily extend the functionality of this library
Future enhancements may include:
- Support for a real XPath like syntax similar to ImportXML for the query parameter
- Support for OAuth authenticated APIs
Or feel free to write these and add on to the library yourself!
------------------------------------------------------------------------------------------------------------------------------------
Changelog:
1.1 Added support for the noHeaders option
1.0 Initial release
*====================================================================================================================================*/
/**
* Imports a JSON feed and returns the results to be inserted into a Google Spreadsheet. The JSON feed is flattened to create
* a two-dimensional array. The first row contains the headers, with each column header indicating the path to that data in
* the JSON feed. The remaining rows contain the data.
*
* By default, data gets transformed so it looks more like a normal data import. Specifically:
*
* - Data from parent JSON elements gets inherited to their child elements, so rows representing child elements contain the values
* of the rows representing their parent elements.
* - Values longer than 256 characters get truncated.
* - Headers have slashes converted to spaces, common prefixes removed and the resulting text converted to title case.
*
* To change this behavior, pass in one of these values in the options parameter:
*
* noInherit: Don't inherit values from parent elements
* noTruncate: Don't truncate values
* rawHeaders: Don't prettify headers
* noHeaders: Don't include headers, only the data
* debugLocation: Prepend each value with the row & column it belongs in
*
* For example:
*
* =ImportJSON("http://gdata.youtube.com/feeds/api/standardfeeds/most_popular?v=2&alt=json", "/feed/entry/title,/feed/entry/content",
* "noInherit,noTruncate,rawHeaders")
*
* #param {url} the URL to a public JSON feed
* #param {query} a comma-separated lists of paths to import. Any path starting with one of these paths gets imported.
* #param {options} a comma-separated list of options that alter processing of the data
*
* #return a two-dimensional array containing the data, with the first row containing headers
* #customfunction
**/
function ImportJSON(url, query, options) {
return ImportJSONAdvanced(url, query, options, includeXPath_, defaultTransform_);
}
/**
* An advanced version of ImportJSON designed to be easily extended by a script. This version cannot be called from within a
* spreadsheet.
*
* Imports a JSON feed and returns the results to be inserted into a Google Spreadsheet. The JSON feed is flattened to create
* a two-dimensional array. The first row contains the headers, with each column header indicating the path to that data in
* the JSON feed. The remaining rows contain the data.
*
* Use the include and transformation functions to determine what to include in the import and how to transform the data after it is
* imported.
*
* For example:
*
* =ImportJSON("http://gdata.youtube.com/feeds/api/standardfeeds/most_popular?v=2&alt=json",
* "/feed/entry",
* function (query, path) { return path.indexOf(query) == 0; },
* function (data, row, column) { data[row][column] = data[row][column].toString().substr(0, 100); } )
*
* In this example, the import function checks to see if the path to the data being imported starts with the query. The transform
* function takes the data and truncates it. For more robust versions of these functions, see the internal code of this library.
*
* #param {url} the URL to a public JSON feed
* #param {query} the query passed to the include function
* #param {options} a comma-separated list of options that may alter processing of the data
* #param {includeFunc} a function with the signature func(query, path, options) that returns true if the data element at the given path
* should be included or false otherwise.
* #param {transformFunc} a function with the signature func(data, row, column, options) where data is a 2-dimensional array of the data
* and row & column are the current row and column being processed. Any return value is ignored. Note that row 0
* contains the headers for the data, so test for row==0 to process headers only.
*
* #return a two-dimensional array containing the data, with the first row containing headers
**/
function ImportJSONAdvanced(url, query, options, includeFunc, transformFunc) {
var jsondata = UrlFetchApp.fetch(url);
var object = JSON.parse(jsondata.getContentText());
return parseJSONObject_(object, query, options, includeFunc, transformFunc);
}
/**
* Encodes the given value to use within a URL.
*
* #param {value} the value to be encoded
*
* #return the value encoded using URL percent-encoding
*/
function URLEncode(value) {
return encodeURIComponent(value.toString());
}
/**
* Parses a JSON object and returns a two-dimensional array containing the data of that object.
*/
function parseJSONObject_(object, query, options, includeFunc, transformFunc) {
var headers = new Array();
var data = new Array();
if (query && !Array.isArray(query) && query.toString().indexOf(",") != -1) {
query = query.toString().split(",");
}
if (options) {
options = options.toString().split(",");
}
parseData_(headers, data, "", 1, object, query, options, includeFunc);
parseHeaders_(headers, data);
transformData_(data, options, transformFunc);
return hasOption_(options, "noHeaders") ? (data.length > 1 ? data.slice(1) : new Array()) : data;
}
/**
* Parses the data contained within the given value and inserts it into the data two-dimensional array starting at the rowIndex.
* If the data is to be inserted into a new column, a new header is added to the headers array. The value can be an object,
* array or scalar value.
*
* If the value is an object, it's properties are iterated through and passed back into this function with the name of each
* property extending the path. For instance, if the object contains the property "entry" and the path passed in was "/feed",
* this function is called with the value of the entry property and the path "/feed/entry".
*
* If the value is an array containing other arrays or objects, each element in the array is passed into this function with
* the rowIndex incremeneted for each element.
*
* If the value is an array containing only scalar values, those values are joined together and inserted into the data array as
* a single value.
*
* If the value is a scalar, the value is inserted directly into the data array.
*/
function parseData_(headers, data, path, rowIndex, value, query, options, includeFunc) {
var dataInserted = false;
if (isObject_(value)) {
for (key in value) {
if (parseData_(headers, data, path + "/" + key, rowIndex, value[key], query, options, includeFunc)) {
dataInserted = true;
}
}
} else if (Array.isArray(value) && isObjectArray_(value)) {
for (var i = 0; i < value.length; i++) {
if (parseData_(headers, data, path, rowIndex, value[i], query, options, includeFunc)) {
dataInserted = true;
rowIndex++;
}
}
} else if (!includeFunc || includeFunc(query, path, options)) {
// Handle arrays containing only scalar values
if (Array.isArray(value)) {
value = value.join();
}
// Insert new row if one doesn't already exist
if (!data[rowIndex]) {
data[rowIndex] = new Array();
}
// Add a new header if one doesn't exist
if (!headers[path] && headers[path] != 0) {
headers[path] = Object.keys(headers).length;
}
// Insert the data
data[rowIndex][headers[path]] = value;
dataInserted = true;
}
return dataInserted;
}
/**
* Parses the headers array and inserts it into the first row of the data array.
*/
function parseHeaders_(headers, data) {
data[0] = new Array();
for (key in headers) {
data[0][headers[key]] = key;
}
}
/**
* Applies the transform function for each element in the data array, going through each column of each row.
*/
function transformData_(data, options, transformFunc) {
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
transformFunc(data, i, j, options);
}
}
}
/**
* Returns true if the given test value is an object; false otherwise.
*/
function isObject_(test) {
return Object.prototype.toString.call(test) === '[object Object]';
}
/**
* Returns true if the given test value is an array containing at least one object; false otherwise.
*/
function isObjectArray_(test) {
for (var i = 0; i < test.length; i++) {
if (isObject_(test[i])) {
return true;
}
}
return false;
}
/**
* Returns true if the given query applies to the given path.
*/
function includeXPath_(query, path, options) {
if (!query) {
return true;
} else if (Array.isArray(query)) {
for (var i = 0; i < query.length; i++) {
if (applyXPathRule_(query[i], path, options)) {
return true;
}
}
} else {
return applyXPathRule_(query, path, options);
}
return false;
};
/**
* Returns true if the rule applies to the given path.
*/
function applyXPathRule_(rule, path, options) {
return path.indexOf(rule) == 0;
}
/**
* By default, this function transforms the value at the given row & column so it looks more like a normal data import. Specifically:
*
* - Data from parent JSON elements gets inherited to their child elements, so rows representing child elements contain the values
* of the rows representing their parent elements.
* - Values longer than 256 characters get truncated.
* - Values in row 0 (headers) have slashes converted to spaces, common prefixes removed and the resulting text converted to title
* case.
*
* To change this behavior, pass in one of these values in the options parameter:
*
* noInherit: Don't inherit values from parent elements
* noTruncate: Don't truncate values
* rawHeaders: Don't prettify headers
* debugLocation: Prepend each value with the row & column it belongs in
*/
function defaultTransform_(data, row, column, options) {
if (!data[row][column]) {
if (row < 2 || hasOption_(options, "noInherit")) {
data[row][column] = "";
} else {
data[row][column] = data[row-1][column];
}
}
if (!hasOption_(options, "rawHeaders") && row == 0) {
if (column == 0 && data[row].length > 1) {
removeCommonPrefixes_(data, row);
}
data[row][column] = toTitleCase_(data[row][column].toString().replace(/[\/\_]/g, " "));
}
if (!hasOption_(options, "noTruncate") && data[row][column]) {
data[row][column] = data[row][column].toString().substr(0, 256);
}
if (hasOption_(options, "debugLocation")) {
data[row][column] = "[" + row + "," + column + "]" + data[row][column];
}
}
/**
* If all the values in the given row share the same prefix, remove that prefix.
*/
function removeCommonPrefixes_(data, row) {
var matchIndex = data[row][0].length;
for (var i = 1; i < data[row].length; i++) {
matchIndex = findEqualityEndpoint_(data[row][i-1], data[row][i], matchIndex);
if (matchIndex == 0) {
return;
}
}
for (var i = 0; i < data[row].length; i++) {
data[row][i] = data[row][i].substring(matchIndex, data[row][i].length);
}
}
/**
* Locates the index where the two strings values stop being equal, stopping automatically at the stopAt index.
*/
function findEqualityEndpoint_(string1, string2, stopAt) {
if (!string1 || !string2) {
return -1;
}
var maxEndpoint = Math.min(stopAt, string1.length, string2.length);
for (var i = 0; i < maxEndpoint; i++) {
if (string1.charAt(i) != string2.charAt(i)) {
return i;
}
}
return maxEndpoint;
}
/**
* Converts the text to title case.
*/
function toTitleCase_(text) {
if (text == null) {
return null;
}
return text.replace(/\w\S*/g, function(word) { return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase(); });
}
/**
* Returns true if the given set of options contains the given option.
*/
function hasOption_(options, option) {
return options && options.indexOf(option) >= 0;
}
function ImportJSONBasicAuthentication(url, query, parseOptions, username, password) {
var fetchOptions = {
headers : {
'authorization': "Bearer THIS_IS_MY_TOKEN_NUMBER",
'accept': "application/json"
},
muteHttpExceptions: true
}
return ImportJSONAdvanced(url, fetchOptions, query, parseOptions, includeXPath_, defaultTransform_);
}
I know its a little bit late, but maybe I can help some one else.
You have to add a parameter here:
function ImportJSONAdvanced(url, header, query, options, includeFunc, transformFunc) //line 126
Here I add the parameter "header" to get "fetchOptions" from your function ImportJSONBasicAuthentication.
After that you have to add this value(header) to this:
var jsondata = UrlFetchApp.fetch(url,header) //line 127
after that the code should be work fine and no 401 should be come there