Google Workspace Add-ons Quickstart Cats stopped working on iOS gmail - google-workspace-add-ons

I've started to receive reports that the add-on we have been using for a while stopped working on iOS mobile. I was able to confirm this and started to dig into the issues. I've been able to reproduce the error with the "Quickstart: Cats Google Workspace Add-on" using multiple google accounts (gmail and workspace). It appears that cards, navigation, and notifications being returned from a function called by a button in card do not render on iOS Gmail. The code appears to work on desktop. I was able to demo this with the Quickstart tutorial. I removed the need for whitelist urls on mobile using the following createCatCard that shows the current time instead of a cat picture. This project works on desktop but not on iOS Gmail.
I've reported this to google via their issue tracker (https://issuetracker.google.com/issues/210484310) but they state that "The Issue Tracker is intended for reporting issues and asking for features related to development with/on Google products, therefore it is not the best place to ask for support on this matter."
Does anyone know if iOS Gmail add-ons are still supported and if there is a workaround for this behavior?
/**
* Creates a card with an image of a cat, overlayed with the text.
* #param {String} text The text to overlay on the image.
* #param {Boolean} isHomepage True if the card created here is a homepage;
* false otherwise. Defaults to false.
* #return {CardService.Card} The assembled card.
*/
function createCatCard(text, isHomepage) {
// Explicitly set the value of isHomepage as false if null or undefined.
if (!isHomepage) {
isHomepage = false;
}
// Use the "Cat as a service" API to get the cat image. Add a "time" URL
// parameter to act as a cache buster.
var now = new Date();
// Replace formward slashes in the text, as they break the CataaS API.
var caption = text.replace(/\//g, ' ');
var d = new Date();
var textLabel = CardService.newTextParagraph().setText("Current Time." + d.toLocaleTimeString());
// Create a button that changes the cat image when pressed.
// Note: Action parameter keys and values must be strings.
var action = CardService.newAction()
.setFunctionName('onChangeCat')
.setParameters({text: text, isHomepage: isHomepage.toString()});
var button = CardService.newTextButton()
.setText('Change cat')
.setOnClickAction(action)
.setTextButtonStyle(CardService.TextButtonStyle.FILLED);
var buttonSet = CardService.newButtonSet()
.addButton(button);
// Assemble the widgets and return the card.
var section = CardService.newCardSection()
.addWidget(textLabel)
.addWidget(buttonSet);
var card = CardService.newCardBuilder()
.addSection(section);
if (!isHomepage) {
// Create the header shown when the card is minimized,
// but only when this card is a contextual card. Peek headers
// are never used by non-contexual cards like homepages.
var peekHeader = CardService.newCardHeader()
.setTitle('Contextual Cat')
.setImageUrl('https://www.gstatic.com/images/icons/material/system/1x/pets_black_48dp.png')
.setSubtitle(text);
card.setPeekCardHeader(peekHeader)
}
return card.build();
}

Related

YouTube API - retrieve more than 5k items

I just want to fetch all my liked videos ~25k items. as far as my research goes this is not possible via the YouTube v3 API.
I have already found multiple issues (issue, issue) on the same problem, though some claim to have fixed it, but it only works for them as they don't have < 5000 items in their liked video list.
playlistItems list API endpoint with playlist id set to "liked videos" (LL) has a limit of 5000.
videos list API endpoint has a limit of 1000.
Unfortunately those endpoints don't provide me with parameters that I could use to paginate the requests myself (e.g. give me all the liked videos between date x and y), so I'm forced to take the provided order (which I can't get past 5k entries).
Is there any possibility I can fetch all my likes via the API?
more thoughts to the reply from #Yarin_007
if there are deleted videos in the timeline they appear as "Liked https://...url" , the script doesnt like that format and fails as the underlying elements dont have the same structure as existing videos
can be easily fixed with a try catch
function collector(all_cards) {
var liked_videos = {};
all_cards.forEach(card => {
try {
// ignore Dislikes
if (card.innerText.split("\n")[1].startsWith("Liked")) {
....
}
}
catch {
console.log("error, prolly deleted video")
}
})
return liked_videos;
}
to scroll down to the bottom of the page ive used this simple script, no need to spin up something big
var millisecondsToWait = 1000;
setInterval(function() {
window.scrollTo(0, document.body.scrollHeight);
console.log("scrolling")
}, millisecondsToWait);
when more ppl want to retrive this kind of data, one could think about building a proper script that is more convenient to use. If you check the network requests you can find the desired data in the response of requests called batchexecute. One could copy the authentification of one of them provide them to a script that queries those endpoints and prepares the data like the other script i currently manually inject.
Hmm. perhaps Google Takeout?
I have verified the youtube data contains a csv called "liked videos.csv". The header is Video Id,Time Added, and the rows are
dQw4w9WgXcQ,2022-12-18 23:42:19 UTC
prvXCuEA1lw,2022-12-24 13:22:13 UTC
for example.
So you would need to retrieve video metadata per video ID. Not too bad though.
Note: the export could take a while, especially with 25k videos. (select only YouTube data)
I also had an idea that involves scraping the actual liked videos page (which would save you 25k HTTP Requests). But I'm unsure if it breaks with more than 5000 songs. (also, emulating the POST requests on that page may prove quite difficult, albeit not impossible. (they fetch /browse?key=..., and have some kind of obfuscated / encrypted base64 strings in the request-body, among other parameters)
EDIT:
Look. There's probably a normal way to get a complete dump of all you google data. (i mean, other than takeout. Email them? idk.)
anyway, the following is the other idea...
Follow this deep link to your liked videos history.
Scroll to the bottom... maybe with selenium, maybe with autoit, maybe put something on the "end" key of your keyboard until you reach your first liked video.
Hit f12 and run this in the developer console
// https://www.youtube.com/watch?v=eZPXmCIQW5M
// https://myactivity.google.com/page?utm_source=my-activity&hl=en&page=youtube_likes
// go over all "cards" in the activity webpage. (after scrolling down to the absolute bottom of it)
// create a dictionary - the key is the Video ID, the value is a list of the video's properties
function collector(all_cards) {
var liked_videos = {};
all_cards.forEach(card => {
// ignore Dislikes
if (card.innerText.split("\n")[1].startsWith("Liked")) {
// horrible parsing. your mileage may vary. I Tried to avoid using any gibberish class names.
let a_links = card.querySelectorAll("a")
let details = a_links[0];
let url = details.href.split("?v=")[1]
let video_length = a_links[3].innerText;
let time = a_links[2].parentElement.innerText.split(" • ")[0];
let title = details.innerText;
let date = card.closest("[data-date]").getAttribute("data-date")
liked_videos[url] = [title,video_length, date, time];
// console.log(title, video_length, date, time, url);
}
})
return liked_videos;
}
// https://stackoverflow.com/questions/57709550/how-to-download-text-from-javascript-variable-on-all-browsers
function download(filename, text, type = "text/plain") {
// Create an invisible A element
const a = document.createElement("a");
a.style.display = "none";
document.body.appendChild(a);
// Set the HREF to a Blob representation of the data to be downloaded
a.href = window.URL.createObjectURL(
new Blob([text], { type })
);
// Use download attribute to set set desired file name
a.setAttribute("download", filename);
// Trigger the download by simulating click
a.click();
// Cleanup
window.URL.revokeObjectURL(a.href);
document.body.removeChild(a);
}
function main() {
// gather relevant elements
var all_cards = document.querySelectorAll("div[aria-label='Card showing an activity from YouTube']")
var liked_videos = collector(all_cards)
// download json
download("liked_videos.json", JSON.stringify(liked_videos))
}
main()
Basically it gathers all the liked videos' details and creates a key: video_ID - Value: [title,video_length, date, time] object for each liked video.
It then automatically downloads the json as a file.

Can you write a Google Sheets function that draws something?

Is it possible to write your own custom function in google sheets script that returns a drawn image, similar to how the SPARKLINE function works, except I want to make one that draws a pie chart instead.
I do not want to use Insert > Chart... > Pie Chart because that creates a floating chart on top of the spreadsheet. I would like to be able to write my own function that would return a pie chart that is embedded within the cell that the function is entered in, just like you can do with columns, bars, and line charts using sparkline.
How about following idea? This sample script embeds a chart to a cell using custom function on Spreadsheet. I think that this method is one of various ideas.
Problems :
When you want to create a chart and embed it to a cell using custom functions, you notice that insertChart() cannot be used. There are some limitations for using custom functions. But insertChart() creates floating charts. So in order to embed a chart to a cell, the function =IMAGE() is suitable for this situation. Here, setFormula() for setting =IMAGE() and DriveApp.createFile() for creating images from charts also cannot be used for custom functions.
Solution :
In order to avoid these limitations, I used Web Apps.
To use this sample script, please deploy Web Apps as follows.
On the Script Editor,
File
-> Manage Versions
-> Save New Version
Publish
-> Deploy as Web App
-> At Execute the app as, select "your account"
-> At Who has access to the app, select "Anyone, even anonymous"
-> Click "Deploy"
-> Copy "Current web app URL"
-> Click "OK"
When it deploys Web Apps, the approval required authorization can be done, simultaneously.
Sample Script :
Please copy and paste this script to a bound script of spreadsheet.
var folderId = "### Folder ID ###"; // This is a folder to save images.
var webappsurl = "https://script.google.com/macros/s/######/exec"; // Here, please put "Current web app URL".
function embedChart(range) {
var ac = SpreadsheetApp.getActiveSheet().getActiveCell();
var q1 = "?datarange=" + range;
var q2 = "&row=" + ac.getRow();
var q3 = "&col=" + ac.getColumn();
var url = webappsurl + q1 + q2 + q3;
UrlFetchApp.fetch(url);
}
function doGet(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var chart = sheet.newChart()
.setChartType(Charts.ChartType.PIE)
.addRange(sheet.getRange(e.parameters.datarange))
.setOption('height', 280)
.setOption('width', 480)
.setOption('title', 'Sample chart')
.build();
var file = DriveApp.getFolderById(folderId).createFile(
chart.getAs('image/png').setName("chart_image.png")
);
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
sheet.getRange(e.parameters.row, e.parameters.col).setFormula(
'=IMAGE("' + "http://drive.google.com/uc?id=" + file.getId() + '")'
);
}
Flow of Script :
embedChart()
Input =embedChart("a2:a6") in cell B7.
Using fetch(), sends data of a2:a6 and the inputted coordinate to doGet().
doGet()
Using doGet(), get the data.
Creates a chart using inputted range a2:a6. (in this case, creates a pie chart)
Saves a chart as an image. (in this case, saves as PNG)
Updates a permission of the image file to use for =IMAGE().
Embeds the image using =IMAGE() which was imported by setFormula().
Result :
By inputting =embedChart("a2:a6") in cell B7 as a custom function, following result can be obtained.
Note :
When the custom function embedChart() is used, loading time is about 40 seconds. (I don't know whether this occurs at only my environment.)
Permissions of the created image are ANYONE_WITH_LINK, VIEW.
embedChart() is overwritten by =IMAGE(). So when the spreadsheet is reopened, the response of =IMAGE() is much faster than that of embedChart().
If I misunderstand your question, I'm sorry.

How to implement IndexDB in IOS

I am developing a mobile application using phonegap, Initially I have developed using WEBSQL but now I m planning to move it on INDEXDB. The problem is it does not have direct support on IOS , so on doing much R&D I came to know using IndexedDB Polyfil we can implement it on IOS too
http://blog.nparashuram.com/2012/10/indexeddb-example-on-cordova-phonegap.html
http://nparashuram.com/IndexedDBShim/
Can some please help me how to implement this as there are not enough documentation for this and I cannot figure out a any other solution / api except this
I have tested this on safari 5.1.7
Below is my code and Error Image
var request1 = indexedDB.open(dbName, 5);
request1.onsuccess = function (evt) {
db = request1.result;
var transaction = db.transaction(["AcceptedOrders"], "readwrite");
var objectStore = transaction.objectStore("AcceptedOrders");
for (var i in data) {
var request = objectStore.add(data[i]);
request.onsuccess = function (event) {
// alert("am again inserted")
// event.target.result == customerData[i].ssn;
};
}
};
request1.onerror = function (evt) {
alert("IndexedDB error: " + evt.target.errorCode);
};
Error Image
One blind guess
Maybe your dbName contains illegal characters for WebSQL database names. The polyfill doesn't translate your database names in any kind. So if you create a database called my-test, it would try to create a WebSQL database with the name my-test. This name is acceptable for an IndexedDB database, but in WebSQL you'll get in trouble because of the - character. So your database name has to match both, the IndexedDB and the WebSQL name conventions.
... otherwise use the debugger
You could set a break point onto your alert(...); line and use the debugger to look inside the evt object. This way you may get either more information about the error itself or more information to share with us.
To do so, enable the development menu in the Safari advanced settings, hit F10 and go to Developer > Start debugging JavaScript (something like that, my Safari is in a different language). Now open then "Scripts" tab in the developer window, select your script and set the break point by clicking on the line number. Reload the page and it should stop right in your error callback, where you can inspect the evt object.
If this doesn't help, you could get the non-minified version of the polyfill and try set some breakpoints around their open function to find the origin of this error.
You could try my open source library https://bitbucket.org/ytkyaw/ydn-db/wiki/Home. It works on iOS and Android.

Hiding custom ItemProperties from print. Interop.Outlook

I have written an Outlook plugin that basically allows emails being received through Outlook to be linked with a website so that the email can also be view in the communications feature of the website. I store additional details within the ItemProperties of a MailItem, these details are basically things like the id of the user the email relates to within a website.
The problem I'm having is any ItemProperties I add to a MailItem are being printed when the email is printed. Does anyone know how to exclude custom ItemProperties when printing an email?
Here is the code that is creating the custom ItemProperty:
// Try and access the required property.
Microsoft.Office.Interop.Outlook.ItemProperty property = mailItem.ItemProperties[name];
// Required property doesnt exist so we'll create it on the fly.
if (property == null) property = mailItem.ItemProperties.Add(name, Microsoft.Office.Interop.Outlook.OlUserPropertyType.olText);
// Set the value.
property.Value = value;
I'm working on Outlook extension and sometimes ago we had the same issue.
One of our team members found a solution. You can create some method which is responsible for disable printing. You can see peace of our code below:
public void DisablePrint()
{
long printablePropertyFlag = 0x4; // PDO_PRINT_SAVEAS
string printablePropertyCode = "[DispID=107]";
Type customPropertyType = _customProperty.GetType();
// Get current flags.
object rawFlags = customPropertyType.InvokeMember(printablePropertyCode , BindingFlags.GetProperty, null, _customProperty, null);
long flags = long.Parse(rawFlags.ToString());
// Remove printable flag.
flags &= ~printablePropertyFlag;
object[] newParameters = new object[] { flags };
// Set current flags.
customPropertyType.InvokeMember(printablePropertyCode, BindingFlags.SetProperty, null, _customProperty, newParameters);
}
Make sure that _customProperty it is your property which you created by the following code: mailItem.ItemProperties.Add(name,Microsoft.Office.Interop.Outlook.OlUserPropertyType.olText);
On the low (Extended MAPI) level, each user property definition has a flag that determines whether it is printable (namely, PDO_PRINT_SAVEAS). That flag however is not exposed through the Outlook Object Model.
You can either parse the user properties blob and manually set that flag (user properties blob format is documented, and you can see it in OutlookSpy (I am its author) if you click the IMessage button) or you can use Redemption (I am also its author) and its RDOUserProperty.Printable property.
The following script (VB) will reset the printable property for all user propeties of the currently selected message:
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set Msg = Session.GetMessageFromID(Application.ActiveExplorer.Selection(1).EntryID)
for each prop in Msg.UserProperties
Debug.Print prop.Name
prop.Printable = false
next
Msg.Save

Google App Script ListBox - Reading items

I can not find a way to read the items in a UI ListBox object. I have search and found some methods like getItemCount() and getItemText() in the Google Web Toolkit but they are not available when using the object in a javascript.....
Any ideas?
You will have to pass the list-box widget trough a Server Side call to get the selected data out of it:
function doGet(){
var app = UiApp.createApplication();
var lb = app.createListBox();
lb.setName("calName") //name used to fetch selected result
.addItem("Text Option One", "1")//options for selection
.addItem("Text Option Two","");//first peram display text and second peram value
app.add(lb); // adds listbox to display
app.add(app.createButton("Click Me")//create button
.addClickHandler(app.createServerHandler("showResult") // Create server side execution to handel click
.addCallbackElement(lb))); // add list-boxas element to be passed to server as parameter
return app;
}
function showResult(e){
var app = UiApp.getActiveApplication();
var res = e.parameter.calName;//parameter name same as list-box name given
app.add(app.createLabel("Selected option is " + res));//displays selected option
return app;
}
just create test app and save a version, Use this code for application and view latest code after deploying it.

Resources