├── CSE2Sheets.gs ├── Sheet Formatting ├── sheetsManager.gs └── sortDataInTargetSheet.gs ├── Sheet Styling └── setStandardSheetLayout.js ├── ebay_monitor.js ├── file management └── demo_scripts.gs ├── getAverageTenureAtTargetCompany.gs ├── image to text ├── code.gs └── view.html ├── parseCandidateResumes_fromEmail.js ├── parseCandidateResumes_fromEmail_v2.gs ├── peopledatalabs_fetch.js └── sheets └── openLinksInSheets.gs /CSE2Sheets.gs: -------------------------------------------------------------------------------- 1 | const target_column_name = 'CSE URL'; 2 | 3 | const getTableValuesBy = (sheet) => sheet.getRange(1,1,1,1).isBlank() ? [] : Array.from(sheet.getRange(1,1,sheet.getLastRow(),sheet.getLastColumn()).getValues()); 4 | /* gets full sheet as Table, by the sheetObject. This only retrieves the max rows and columns containing data. Returns an empty array if the first cell is blank */ 5 | const getColIndexBy = (table,header_name) => table[0].indexOf(header_name); 6 | /* gets the index number (not sheet col number) of the specified header within a given sheet */ 7 | 8 | function getDataFromCSEURLs() { 9 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); 10 | 11 | var table = getTableValuesBy(sheet); 12 | 13 | var target_col_index = getColIndexBy(table,target_column_name); 14 | 15 | var updated_table = [ 16 | [ 17 | ...table[0], 18 | ...[ 19 | 'Result Link', 20 | 'Result Snippet', 21 | 'Result Title' 22 | ] 23 | ] 24 | ]; 25 | 26 | for(let i=1; i table.map(col=> col[i]); 2 | /* utility sheets 3 | returns column as an array. arguments are the column index and the table. 4 | */ 5 | 6 | const getTableValuesBy = (sheet) => sheet.getRange(1,1,1,1).isBlank() ? [] : Array.from(sheet.getRange(1,1,sheet.getLastRow(),sheet.getLastColumn()).getValues()); 7 | /* utility sheets 8 | getTableValuesBy => gets full sheet as Table, by the sheetObject. This only retrieves the max rows and columns containing data. Returns an empty array if the first cell is blank 9 | */ 10 | 11 | const getColIndexBy = (table,header_name) => table[0].indexOf(header_name); 12 | /* utility sheets 13 | getColIndexBy => gets the index number (not sheet col number) of the specified header within a given sheet 14 | */ 15 | 16 | const getRowIndexBy = (table,header_name,query) => table.findIndex(r=> r[getColIndexBy(table,header_name)] == query); 17 | /* utility sheets 18 | getRowIndexBy => gets the index number of the first string matching row value within a specified headername within a given sheet. 19 | */ 20 | 21 | const getRowIndexRegX = (table,header_name,x) => table.findIndex(r=> x.test(r[getColIndexBy(table,header_name)])); 22 | /* utility sheets 23 | getRowIndexByX => gets the index number of the first matching regular expression on a row value within a specified headername within a given sheet. 24 | */ 25 | 26 | function insertRowByColumnLooper(sheet,arr,row){ 27 | var table = getTableValuesBy(sheet); 28 | for(var i=0; i -1){//insures we found a matching headername 31 | var val = typeof arr[i][1] == 'object' ? JSON.stringify(arr[i][1]) : arr[i][1]; // this converts objects to strings so we can insert them into cells 32 | sheet.getRange(row, (colIndex+1)).setValue(val); 33 | } 34 | } 35 | } 36 | /* utility sheets 37 | insertRowByColumnLooper => takes a sheetTable, 2D array with the [[header_name,value_to_set]], and the designated row to set, and sets the data in a loop. The headername is passed through another function to gain the appropriate column index to set. 38 | */ 39 | 40 | function insertAdditionalHeaders(sheet,headers){ 41 | if(sheet.getRange(1,1,3,3).isBlank()){ //assumes that we need to add headers if the first three rows and columns are blank 42 | sheet.getRange(1,1,1,headers.length).setValues([headers]); 43 | sheet.setFrozenRows(1); 44 | }else{ 45 | var head = Array.from(getTableValuesBy(sheet))[0]; 46 | var addThese = headers.filter(itm=> head.every(cell=> cell != itm ) ); 47 | if(addThese.length > 0){ 48 | sheet.getRange(1,(sheet.getLastColumn()+1),1,addThese.length).setValues([addThese]); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sheet Formatting/sortDataInTargetSheet.gs: -------------------------------------------------------------------------------- 1 | const transpose = (a)=> a[0].map((_, c)=> a.map(r=> r[c])); //https://gist.github.com/femto113/1784503 2 | const nameCase = (s) => s && typeof s == 'string' ? s.split(/(?=[^ğᴀғʀńŃŌŌŚŠśšŪūÿłžźżŁŽŹŻçćčÇĆČáāàâäãåÁÀÂÄÃĀĀÅĀÆæéèêëęēėÉÈÊËíìîïīįñÑóòôöõøœÓÒÔÖÕØŒßÚÙÛÜúùûüřa-zA-Z])\b/).map(el=> el.replace(/\w\S*/g, txt=> txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase())).join('').replace(/(?<=\bMc)\w/ig, t=> t.charAt(0).toUpperCase()) : s; 3 | const millisecondsToYears = (m) => m && /^\d[\d.]*$/.test(m) ? Math.round((m/3.154e+10)*1000)/1000 : m; 4 | 5 | function table2JSON(table){ 6 | const arr = []; 7 | for(let i=1; i { 34 | let target_index = getColIndexBy(target_table,obj.key); 35 | let col = getColumn(target_index,target_table); 36 | return obj.action && obj.action == 'nameCase' ? col.map(cell=> nameCase(cell)) : obj.action && obj.action == 'millisecondsToYears' ? col.map(cell=> millisecondsToYears(cell)) : col; 37 | })); 38 | let rename_index = getColIndexBy(key_table,'display'); 39 | let renamed_header = key_table.map(row=> row[rename_index]); 40 | renamed_header.shift(); 41 | mapped_table.shift(); 42 | let new_table = [...[renamed_header],...mapped_table]; 43 | 44 | ss_parsed.insertSheet(target_sheet.getSheetName()+' parsed_sheet '+now_date).getRange(1,1,new_table.length,new_table[0].length).setValues(new_table); 45 | 46 | let parsed_sheet = ss_parsed.getSheetByName(target_sheet.getSheetName()+' parsed_sheet '+now_date); 47 | 48 | let styling_indexes = getColumn(getColIndexBy(key_table,'background color'),key_table); 49 | styling_indexes.shift(); 50 | 51 | styling_indexes.forEach((color,i,r)=> { 52 | let rows = parsed_sheet.getLastRow(); 53 | if(color){ 54 | let colors = Array(rows).fill([color]); 55 | parsed_sheet.getRange(1,(i+1),parsed_sheet.getLastRow(),1).setBackgrounds(colors); 56 | } 57 | }) 58 | parsed_sheet.setFrozenRows(1); 59 | parsed_sheet.setFrozenColumns(4); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sheet Styling/setStandardSheetLayout.js: -------------------------------------------------------------------------------- 1 | /* 2 | view a review of the code and demo @ https://youtu.be/C6OgbYen1_s 3 | */ 4 | function setStandardSheetLayout() { 5 | const header_background_color = '#009bad'; 6 | const header_text_color = '#ffffff'; 7 | const header_border_color = '#ffffff'; 8 | const header_rows_to_freeze = 1; 9 | const column_border_color = '#7c7c7c'; 10 | const number_of_columns_to_freeze = 3; 11 | 12 | const ss = SpreadsheetApp.getActiveSpreadsheet(); 13 | const sheets = ss.getSheets(); 14 | for(let i=0; i { 30 | let jobs_before_match = []; 31 | let jobs_after_match = []; 32 | record.jobs.forEach((j,i,r)=> { 33 | if(xarr.every(x=> x.test(j[key]))) { 34 | if(r[(i-1)]) jobs_before_match.push(r[(i-1)]) 35 | if(r[(i+1)]) jobs_after_match.push(r[(i+1)]) 36 | } 37 | }) 38 | return cleanObject({...record,...{jobs_after_match:jobs_after_match},...{jobs_before_match:jobs_before_match}}); 39 | }); 40 | } 41 | function countKeys(records,key,matchkey){ 42 | const mapped = records.filter(r=> r[matchkey]).map(r=> r[matchkey] ? r[matchkey].map(j=> j[key]) : []).flat(); 43 | let counted = unqHsh(mapped,{}).map(j=> { 44 | return { 45 | ...{match:j}, 46 | ...{ count: mapped.filter(m=> m == j).length}, 47 | } 48 | }); 49 | counted.sort((a,b)=> a.count - b.count); 50 | counted.reverse(); 51 | return counted; 52 | } 53 | function getAverageTimeWithKeySearch(booleanString,key){ 54 | const ss = SpreadsheetApp.openById(your_spreadsheet_id); 55 | const sheet = ss.getSheetByName(your_sheet_name); 56 | const table = getTableValuesBy(sheet); 57 | const renested = renestJobs(table); 58 | const xarr = buildSearchSet(booleanString); 59 | const work = renested.filter( record=> record.jobs.filter( job=> xarr.every( x=> x.test(job[key]) ) ).length ); 60 | const times = renested.map(record=> { 61 | let matching = record.jobs.filter( job=> xarr.every( x=> x.test(job[key]) ) ).map( job=> job.years_in_job ? parseFloat(job.years_in_job) : 0.02); 62 | return matching.length ? matching.reduce((a,b)=> a+b) : 0; 63 | }); 64 | return times.reduce((a,b)=> a+b) / work.length; 65 | } 66 | 67 | /*This translates the jobs back into a nested array so we can filter down on jobs by candidate record */ 68 | function renestJobs(table){ 69 | const reg = (o, n) => o ? o[n] : ''; 70 | return table2JSON(table).map(record=> { 71 | let jobs = []; 72 | Object.entries(record).filter(kv=> /job_/.test(kv[0])).forEach(keyvalpair=> { 73 | let key = keyvalpair[0]; 74 | let val = keyvalpair[1]; 75 | let is_job_record = /^job_\d+_/.test(key); 76 | let placement_index = reg(/^job_(\d+)_/.exec(key),1) ? (parseInt(reg(/^job_(\d+)_/.exec(key),1)) -1) : 0; 77 | if(is_job_record && reg(/^job_(\d+)_/.exec(key),1)){ 78 | if(jobs[placement_index]){ 79 | jobs[placement_index][key.replace(/^job_\d+_/,'')] = val; 80 | }else{ 81 | jobs[placement_index] = {}; 82 | jobs[placement_index][key.replace(/^job_\d+_/,'')] = val; 83 | } 84 | } 85 | }); 86 | return {...record,...{jobs: jobs}}; 87 | }).filter(r=> r.job_1_job_company_name); 88 | } 89 | 90 | const parseStringAsXset = (s) => s 91 | .split(/\s+\band\b\s+|(? 93 | el.split(/\s+\bor\b\s+/i).map(ii=> 94 | ii.replace(/\s*\)\s*/g,'') 95 | .replace(/\s*\(\s*/g,'') 96 | .replace(/\s+/g,'.{0,3}') 97 | .replace(/"/g,'\\b') 98 | .replace(/\*/g,'\\w*') 99 | .replace(/\*\*\*/g,'.{0,60}')) 100 | .reduce((a,b)=> a+'|'+b)).filter(el=> el); 101 | 102 | function permutateNear(input,joiner){ 103 | var nearx = /(?<=\||^)\S+?(?=\||$)/g; 104 | var base = input.replace(nearx, '').replace(/[\|]+/g, '|'); 105 | var near_or = input.match(nearx) ? input.match(nearx).map(str=> { 106 | var arr = str.split(/~/); 107 | if(arr.length > 5){ 108 | return str.replace(/[~]+/,'.'); 109 | }else{ 110 | var cont = []; 111 | var containArr = []; 112 | function comboLoop(arr, cont){ 113 | if (arr.length == 0) { 114 | var row = cont.join(joiner); 115 | containArr.push(row) 116 | } 117 | for (var i = 0; i < arr.length; i++) { 118 | var x = arr.splice(i, 1); 119 | cont.push(x); 120 | comboLoop(arr, cont); 121 | cont.pop(); 122 | arr.splice(i, 0, x); 123 | } 124 | } 125 | comboLoop(arr, cont); 126 | return containArr.reduce((a,b)=> a+'|'+b); 127 | } 128 | }).flat().reduce((a,b)=> a+'|'+b) : ''; 129 | return base + near_or; 130 | } 131 | function buildSearchSet(str){ 132 | if(str){ 133 | var set = parseStringAsXset(str); 134 | var xset = set.map(r=> permutateNear(r,'.{0,39}')).map(r=> tryRegExp(r.replace(/^\||\|$/g,''),'i')); 135 | return xset; 136 | }else{return null} 137 | } 138 | function tryRegExp(s,f){ 139 | try{return new RegExp(s,f)} 140 | catch(err){return err} 141 | } 142 | 143 | function table2JSON(table){ 144 | const arr = []; 145 | for(let i=1; i a.filter(i=> o.hasOwnProperty(i) ? false : (o[i] = true)); 155 | 156 | const cleanObject = (ob) => 157 | Object.entries(ob).reduce((r, [k, v]) => { 158 | if(v != null && v != undefined && v != "" && ( typeof v == 'boolean' || typeof v == 'string' || typeof v == 'symbol' || typeof v == 'number' || typeof v == 'function' || (typeof v == 'object' && ((Array.isArray(v) && v.length) || (Array.isArray(v) != true)) ) ) ) { 159 | r[k] = v; 160 | return r; 161 | } else { 162 | return r; 163 | } 164 | }, {}); 165 | 166 | const getColumn = (i,table) => table.map(col=> col[i]); 167 | /* utility sheets 168 | returns column as an array. arguments are the column index and the table. 169 | */ 170 | 171 | const getTableValuesBy = (sheet) => sheet.getRange(1,1,1,1).isBlank() ? [] : Array.from(sheet.getRange(1,1,sheet.getLastRow(),sheet.getLastColumn()).getValues()); 172 | /* utility sheets 173 | getTableValuesBy => gets full sheet as Table, by the sheetObject. This only retrieves the max rows and columns containing data. Returns an empty array if the first cell is blank 174 | */ 175 | 176 | const getColIndexBy = (table,header_name) => table[0].indexOf(header_name); 177 | /* utility sheets 178 | getColIndexBy => gets the index number (not sheet col number) of the specified header within a given sheet 179 | */ 180 | 181 | const getRowIndexBy = (table,header_name,query) => table.findIndex(r=> r[getColIndexBy(table,header_name)] == query); 182 | /* utility sheets 183 | getRowIndexBy => gets the index number of the first string matching row value within a specified headername within a given sheet. 184 | */ 185 | 186 | const getRowIndexRegX = (table,header_name,x) => table.findIndex(r=> x.test(r[getColIndexBy(table,header_name)])); 187 | /* utility sheets 188 | getRowIndexByX => gets the index number of the first matching regular expression on a row value within a specified headername within a given sheet. 189 | */ 190 | -------------------------------------------------------------------------------- /image to text/code.gs: -------------------------------------------------------------------------------- 1 | function doGet(e) { //this function serves up the view HTML file 2 | return HtmlService.createTemplateFromFile('view') 3 | .evaluate() 4 | .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL); 5 | } 6 | 7 | function processResumeFile(filename,dataURI){ //this function is called within the view.html file using the google.script.run.withSuccessHandler method 8 | var blob = dataURItoBlob(dataURI, filename); 9 | return fileImageBlob(blob); 10 | } 11 | 12 | function dataURItoBlob(dataURI, filename) { // code swiped from https://stackoverflow.com/a/36949118/1027723 13 | var byteString; 14 | if (dataURI.split(',')[0].indexOf('base64') >= 0){ 15 | byteString = Utilities.base64Decode(dataURI.split(',')[1]); 16 | } else { 17 | byteString = decodeURI(dataURI.split(',')[1]); 18 | } 19 | var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; 20 | return Utilities.newBlob(byteString, mimeString, filename); 21 | } 22 | 23 | function fileImageBlob(blob){ //TODO: we are overwriteing the file variable. Fix this later and test to ensure it doesnt break anything. 24 | var filename = blob.getName(); 25 | var type = /docx$/i.test(filename) ? 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : /pdf$/i.test(filename) ? 'application/pdf' : /png$/i.test(filename) ? 'image/png' : /jpeg$|jpg$/i.test(filename) ? 'image/jpeg' : /bmp$/i.test(filename) ? 'image/bmp' : 'text/plain'; 26 | if(type){ 27 | var file = { 28 | title: 'OCR File', 29 | mimeType: type 30 | }; 31 | file = Drive.Files.insert(file, blob, {ocr: true}); 32 | var doc = DocumentApp.openByUrl(file.embedLink); 33 | var body = doc.getBody().getText().replace(/\n/g,'
'); 34 | deleteFileById(file.getId()); 35 | return body; 36 | }else{ 37 | return 'file type not supported'; 38 | } 39 | } 40 | 41 | function deleteFileById(id){ 42 | var file = DriveApp.getFileById(id); 43 | file.setTrashed(true); 44 | } 45 | -------------------------------------------------------------------------------- /image to text/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |

Image and PDF to Text converter

10 |

Upload a file, wait for the processing, then download the text file. Text content is editable prior to downloading.

11 |
12 |
13 |
14 |
Upload file
15 |
16 |
17 |
18 |
19 |
20 | 21 | 22 | 23 | 177 | -------------------------------------------------------------------------------- /parseCandidateResumes_fromEmail.js: -------------------------------------------------------------------------------- 1 | /* 2 | GOOGLE APPS SCRIPT 3 | This script will monitor inbound emails for a given subject name, then will parse those resumes as a Google Doc, and add a record to a spreadsheet 4 | 5 | Full build video: 6 | https://youtu.be/r05EwELmymE 7 | 8 | Set Time-Based Triggers: 9 | https://youtu.be/RvUyyDpXxuE 10 | */ 11 | 12 | var sheetId = 'YOUR_SPREADSHEET_ID_GOES_HERE'; 13 | var ss = SpreadsheetApp.openById(sheetId); //https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#openById(String) 14 | var mainSheet = ss.getSheetByName('Main'); //https://developers.google.com/apps-script/reference/spreadsheet/sheet#getsheetbyname 15 | 16 | function reg(o,n){if(o){return o[n].trim()}else{return '';}} 17 | function unq(arr){ return arr.filter(function(e, p, a) { return a.indexOf(e) == p }) } 18 | function fixCase(s){ return s.replace(/\w\S*/g, function(txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()} );} 19 | 20 | function parseAsRegexArr(bool) { 21 | function rxReady(s){ return s ? s.replace(/"/g, '\\b').trim().replace(/\)/g, '').replace(/\(/g, '').replace(/\s+/g, '.{0,2}').replace(/\//g, '\\/').replace(/\+/g, '\\+').replace(/\s*\*\s*/g, '\\s*\\w*\\s+') : s;} 22 | function checkSimpleOR(s) { return /\bor\b/i.test(s) && /\(/.test(s) === false;} 23 | if (checkSimpleOR(bool)) { 24 | var x = new RegExp(bool.replace(/\s+OR\s+|\s*\|\s*/gi, '|').replace(/\//g, '\\/').replace(/"/g, '\\b').replace(/\s+/g, '.{0,2}').replace(/\s*\*\s*/g, '\\s*\\w*\\s+'), 'i'); 25 | var xArr = [x]; 26 | return xArr; 27 | } else { 28 | var orx = "\\(.+?\\)|(\\(\\w+\\s{0,1}OR\\s|\\w+\\s{0,1}OR\\s)+((\\w+\s)+?|(\\w+)\\)+)+?"; 29 | var orMatch = bool ? bool.match(new RegExp(orx, 'g')) : []; 30 | var orArr = orMatch ? orMatch.map(function(b) {return rxReady(b.replace(/\s+OR\s+|\s*\|\s*/gi, '|'))}) : []; 31 | var noOrs = bool ? bool.replace(new RegExp(orx, 'g'), '').split(/\s+[AND\s+]+/i) : bool; 32 | var ands = noOrs ? noOrs.map(function(a) { return rxReady(a)}) : []; 33 | var xArr = ands.concat(orArr).filter(function(i){ return i != ''}).map(function(x){return new RegExp(x, 'i')}); 34 | return xArr; 35 | } 36 | } 37 | function booleanSearch(bool,target){ 38 | var arr = parseAsRegexArr(bool); 39 | return arr.every(function(x){ 40 | return x.test(target); 41 | }); 42 | } 43 | 44 | function getFolderByName(x){ 45 | var folders = DriveApp.getFolders(); //https://developers.google.com/apps-script/reference/drive/folder-iterator 46 | while(folders.hasNext()){ 47 | var folder = folders.next(); 48 | if(x.test(folder.getName())) return folder.getId(); 49 | } 50 | } 51 | 52 | function getEmailThreadsBySubject(searchString){ 53 | var matches = []; 54 | var threads = GmailApp.getInboxThreads(0, 50); //https://developers.google.com/apps-script/reference/gmail/gmail-app#getInboxThreads(Integer,Integer) 55 | for(var i=0; i 0) { //https://developers.google.com/apps-script/reference/gmail/gmail-thread#isUnread() 60 | matches.push(msgs[0]); 61 | } // end if msgs unread && match params 62 | } //end forloop 63 | return matches; 64 | } 65 | 66 | function parseCandidateSubmissions(){ 67 | var targetEmails = getEmailThreadsBySubject('Candidate Submission OR candidate submittal'); 68 | for(var i=0; i=0; i--){ if (data[i][0] != null && data[i][0] != ''){ rowArr.push(i+1); } } 23 | if(rowArr.length <1) { return 0; }else{ return Math.max.apply(null, rowArr); } 24 | } 25 | 26 | function peopleDataMapper() { 27 | var lr = s1.getLastRow(); 28 | var lc = s1.getLastColumn(); 29 | var lrByCol = lastRowNum_bySpecifiedCol(lc); 30 | var table = s1.getRange(1,1,lr,s1.getLastColumn()).getValues(); 31 | 32 | if(arr(table[0]).some(function(el){ return el == 'Likelihood_score'}) === false){ 33 | 34 | var header = [['Likelihood_score','birthdate','personal_phones','personal_emails','work_phones','work_emails','other_phones','other_emails']]; 35 | s1.getRange(1,(lc+1),1,header[0].length).setValues(header); 36 | 37 | } else { 38 | 39 | var t = getIndexOfPubUrl(table[1]); 40 | var stop = table.length - lrByCol < numberToQuery ? table.length : lrByCol + numberToQuery; 41 | 42 | for(var i=lrByCol; i 0){ 64 | emails.forEach(function(el){ 65 | if(el.type != 'personal' || el.type != 'professional'){n_email.push(el.address);} 66 | if(el.type == 'professional'){ work_email.push(el.address); } 67 | if(el.type == 'personal'){ personal_email.push(el.address); } 68 | }); 69 | } 70 | if(phones.length > 0){ 71 | phones.forEach(function(el){ 72 | if(/personal/i.test(el.type) === false || /professional/i.test(el.type) === false){n_phone.push(el.national_number);} 73 | if(/professional/i.test(el.type)){ work_phone.push(el.national_number); } 74 | if(/personal/i.test(el.type)){ personal_phone.push(el.national_number); } 75 | }); 76 | } 77 | 78 | var emailstr_person = personal_email.length > 0 ? '[' + personal_email.toString() + ']' : '[]'; 79 | var phonestr_person = personal_phone.length > 0 ? '[' + personal_phone.toString() + ']' : '[]'; 80 | 81 | var emailstr_work = work_email.length > 0 ? '[' + work_email.toString() + ']' : '[]'; 82 | var phonestr_work = work_phone.length > 0 ? '[' + work_phone.toString() + ']' : '[]'; 83 | 84 | var emailstr_n = n_email.length > 0 ? '[' + n_email.toString() + ']' : '[]'; 85 | var phonestr_n = n_phone.length > 0 ? '[' + n_phone.toString() + ']' : '[]'; 86 | 87 | 88 | var birthdate = dat.data.birth_date ? dat.data.birth_date : ''; 89 | var likelihood = dat.likelihood ? dat.likelihood : ''; 90 | var output = [[likelihood,birthdate,phonestr_person,emailstr_person,phonestr_work,emailstr_work,phonestr_n,emailstr_n]]; 91 | 92 | s1.getRange((i+1),(apiLC+1),output.length, output[0].length).setValues(output); 93 | Logger.log(dat); 94 | Logger.log(output); 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /sheets/openLinksInSheets.gs: -------------------------------------------------------------------------------- 1 | /* 2 | https://www.youtube.com/watch?v=ZrM2agJqyn8 3 | */ 4 | var your_sheet_id = 'YOUR_SHEET_ID_NG51Lm7YUMCG3coJFYx9fgU'; 5 | var your_sheet_name = 'YOUR_SHEET_NAME'; 6 | var ss = SpreadsheetApp.openById(your_sheet_id); 7 | var report = ss.getSheetByName(your_sheet_name); 8 | 9 | function onOpen(e) { 10 | var ui = SpreadsheetApp.getUi(); 11 | ui.createMenu('Quickli Tools') 12 | .addItem('Links Viewer', 'pagePanel') 13 | .addToUi(); 14 | } 15 | 16 | function pagePanel() { 17 | const link_table = getLinksInCols(0); 18 | const jdat= JSON.stringify(link_table).replace(/"/g,"""); 19 | var header = ` 20 | 33 |
34 |
next
35 |
36 | `; 37 | var page = UrlFetchApp.fetch(link_table.link_table[0].cell).toString(); 38 | var html = HtmlService.createHtmlOutput(page.replace(/\n|\r/g,'').replace(//gi,'').replace(//i,''+header)) 39 | .setWidth(720) 40 | .setHeight(500) 41 | SpreadsheetApp.getUi() // Or DocumentApp or SlidesApp or FormApp. 42 | .showModalDialog(html,link_table.link_table[0].cell.slice(0,52)); 43 | } 44 | function pagerPanel(data,page){ 45 | const link_table = getLinksInCols(data.current_index+page); 46 | const jdat= JSON.stringify(link_table).replace(/"/g,"""); 47 | var header = ` 48 | 64 |
65 |
${link_table.link_table[data.current_index+1].cell ? '' : 'next'}
66 |
67 | `; 68 | var page = UrlFetchApp.fetch(link_table.link_table[data.current_index+1].cell).toString(); 69 | var html = HtmlService.createHtmlOutput(page.replace(/\n|\r/g,'').replace(//gi,'').replace(//i,''+header)) 70 | .setWidth(720) 71 | .setHeight(500) 72 | SpreadsheetApp.getUi() 73 | .showModalDialog(html,link_table.link_table[data.current_index+1].cell.slice(0,52)); 74 | } 75 | 76 | 77 | function getLinksInCols(cindex) { 78 | var header = Array.from(report.getRange(1, 1, 1, report.getLastColumn()).getValues())[0]; 79 | var rows = report.getLastRow(); 80 | var cols_with_links = header.map((i,n,r) => { //i == the current value on the iteration. n == the index, r == the full array/header 81 | return Array.from(report.getRange(1,(n+1),rows,1).getRichTextValues()).map((row,ix,rr)=> {return {col_head:i,row_index:ix,col_index:n,cell:row[0].getLinkUrl()}}); 82 | }).flat().filter(r=> r.cell) 83 | return {current_index: cindex, link_table: cols_with_links}; 84 | } 85 | --------------------------------------------------------------------------------