Google add on : Show a notification with the first loaded card - google-apps-script

Google add on initialization works with a function being called, the function is defined in the appscript JSON :
"addOns": {
"common": {
"name": "name of add on",
"homepageTrigger": {
"runFunction": "onLoad",
"enabled": true
},
The onLoad function has to return a built Card (CardService.newCardBuilder().build()).
I would like to make it so that the initial card that is build also sends a notification (the temporary message that shows as a snackbar for a few seconds on the add-on).
This would be done by adding a notification to an action response and building it.
var card = buildCard();
var navigation = CardService.newNavigation().popCard().pushCard(card);
var actionresponse = CardService.newActionResponseBuilder()
.setNavigation(navigation)
.setNotification(CardService.newNotification()
.setText(notificationText)
.setType(CardService.NotificationType.INFO));
return actionresponse.build();
However, the onLoad function does not accept a built action response, it needs a built card.
Any way to overcome this ?
Thank you

Related

Show changes to Calendar Event in view after advanced Calendar update

I am programmatically updating a calendar event from a button click in the add-on sidebar. I want to add the conferenceData to the event and have the conference appear in the event when I do so.
I am able to update the event with the conference data fine, but I have to refresh the page to see the conference in the event. How do I get the conference to appear without refresh?
I know it's possible because the zoom add-on does exactly this.
function createConference(e, label, uri) {
var event = Calendar.Events.get(e.calendar.calendarId, event.id);
event.conferenceData = {
conferenceId: newWaitingRoom._id,
entryPoints: [
{
label: label,
entryPointType: 'video',
uri: uri
}
],
conferenceSolution: {
key: { type: 'addOn' },
name: 'Digideck Live',
}
}
try {
event = Calendar.Events.update(event, e.calendar.calendarId, event.id, { conferenceDataVersion: 1 }, { 'If-Match': event.etag });
Logger.log('Successfully updated event: ' + event.id);
} catch (err) {
Logger.log('Fetch threw an exception: ' + err);
throw Error(err);
}
var nav = CardService.newNavigation().updateCard(createdConferenceCard);
return CardService.newActionResponseBuilder()
.setNavigation(nav)
.build();
}
Zoom Add-on example
picture 1:
before clicking Add Meeting button
picture 2:
after clicking Add Meeting Button (no other interactions, no refresh)
any help is greatly appreciated!
I figured it out. Instead of using the advanced Calendar API, you have to return
return CardService.newCalendarEventActionResponseBuilder()
.setConferenceData(ConferenceData).build();
EDIT: also found out that setting "currentEventAccess": "READ_WRITE" in the appsscript.json file is important to be able to get conferenceData from selected events via "eventOpenTrigger" etc

Cannot draft a reply. Permission error

I followed official guides https://developers.google.com/gmail/add-ons/how-tos/compose and https://developers.google.com/gmail/add-ons/guides/quickstart
Here is my appsscript.json:
{
"oauthScopes": [
"https://www.googleapis.com/auth/gmail.addons.execute",
"https://www.googleapis.com/auth/gmail.addons.current.action.compose"
],
"gmail": {
"name": "Gmail Add-on Quickstart",
"logoUrl": "https://www.gstatic.com/images/icons/material/system/2x/bookmark_black_24dp.png",
"contextualTriggers": [{
"unconditional": {
},
"onTriggerFunction": "createReplyDraft"
}],
"openLinkUrlPrefixes": [
"https://mail.google.com/"
],
"primaryColor": "#4285F4",
"secondaryColor": "#4285F4"
}
}
and Code.gs as:
var composeAction = CardService.newAction()
.setFunctionName('createReplyDraft');
var composeButton = CardService.newTextButton()
.setText('Compose Reply')
.setComposeAction(composeAction, CardService.ComposedEmailType.REPLY_AS_DRAFT);
// ...
/**
* Creates a draft email (with an attachment and inline image)
* as a reply to an existing message.
* #param {Object} e data passed by the compose action.
* #return {ComposeActionResponse}
*/
function createReplyDraft(e) {
// Activate temporary Gmail add-on scopes, in this case to allow
// a reply to be drafted.
var accessToken = e.messageMetadata.accessToken;
GmailApp.setCurrentMessageAccessToken(accessToken);
// Creates a draft reply.
var messageId = e.messageMetadata.messageId;
var message = GmailApp.getMessageById(messageId);
var draft = message.createDraftReply('',
{
htmlBody: "Kitten!"
}
);
// Return a built draft response. This causes Gmail to present a
// compose window to the user, pre-filled with the content specified
// above.
return CardService.newComposeActionResponseBuilder()
.setGmailDraft(draft).build();
}
I want to open the "Reply" panel of my Gmail and paste some content into it.
It is giving Error following error: with the add-on.
Runtime error.
Access denied: : Cannot compose without user interaction.. [line: 27, function: createReplyDraft, file: Code]
I had already reinstalled module many times and also tried to give full scope permission of "https://mail.google.com/".
Looking at your code it looks like you are trying to call your CreateReplyDraft function as soon as the add-on is loaded. Google does not allow that, the user would have to click a button in the UI to track creating a draft.
Have you tried adding https://www.googleapis.com/auth/gmail.readonly to your scope?
You cannot trigger compose action directly like this. You have to create a button widget which has linked compose action(your createReplyDraft function) with it. So when the user clicks on that button, compose action will be triggered.

Recording/Catching push notifications with chrome extension

I'm trying to catch push notifications that get sent to my chrome browser with a chrome extension to then write it to a file or run code on receiving it.
I'm sure the issue I'm having is simple enough for some of you so I will share what I've found so far and my code.
notifhook.js
(function() {
// save the original function
var origCreateNotif = notifications.createNotification;
// overwrite createNotification with a new function
notifications.createNotification = function(img, title, body) {
// call the original notification function
var result = origCreateNotif.apply(this, arguments);
alert("Function was called");
// bind a listener for when the notification is displayed
result.addEventListener("display", function() {
alert("Triggered code");
// do something when the notification is displayed
});
return result;
}
})()
manifest.json
{
"name": "Push",
"description": "Relay push notifications",
"version": "3.0",
"permissions": ["notifications"],
"web_accessible_resources": ["notifhook.js"],
"content_scripts" : [{
"run_at" : "document_start",
"matches" : ["<all_urls>"],
"js" : ["inject.js"]
}],
"manifest_version": 2
}
inject.js
var s = document.createElement("script");
s.src = chrome.extension.getURL("notifhook.js");
document.documentElement.appendChild(s);
Most of the code used was taken here :
How can I listen to notifications?
I am using this webapp to test my extension :
http://ttsvetko.github.io/HTML5-Desktop-Notifications/
So, to my knowledge what is happening so far is that the block containing my main function is being added to the webpage just fine
since I can alert at the start of it and on every page load it will be triggered.
What is failing is when I receive a push notification the event listener isn't working or my function names are wrong. I read somewhere that webkitNotifications was replaced with just 'notifications'.
Any help is much appreciated.
If the code is in service worker, like when there are notifications when you close the browser tab/window for the app (like on facebook) then you need to overwirte registration of service worker.
// taken from https://googlechrome.github.io/samples/service-worker/post-message/index.html
function sendMessage(message) {
return new Promise(function(resolve, reject) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function(event) {
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
navigator.serviceWorker.controller.postMessage(message,
[messageChannel.port2]);
});
}
(function() {
if ('serviceWorker' in navigator) {
var register = navigator.serviceWorker.register;
navigator.serviceWorker.register = function() {
register.call(navigator.serviceWorker, "yourscript.js").then(function() {
sendMessage({args: [].slice.call(arguments)});
});
};
}
})();
and in service worker (yourscript.js) you need to call importScript with original script taken from post message
// put content of notifhook.js file here
self.addEventListener('message', function handler(event) {
if (event.data && event.data.args && event.data.args.length) {
importScripts(event.data.args[0]); // import original script
self.removeEventListener('message', handler);
}
});

Show video from chrome.desktopCapture.chooseDesktopMedia in a tab?

I am trying out chrome.desktopCapture.chooseDesktopMedia in a chrome extension and I can get a desktop stream just fine.
I am using the following to turn the stream into an blob:-URL in the background script as follows:
var objectUrl = URL.createObjectURL(stream);
What I can't seem to work out is how to set this as the src attribute of a video element on the injected page.
I have tried the following, each of which do not work:
In Background.js:
var video = document.getElementById("video");
var objectUrl = URL.createObjectURL(stream);
video.src = objectUrl;
In Content.js
//objectUrl is a string received in a message from the background page by the content page
var video = document.getElementById("video");
video.src = objectUrl;
I get the following in the javascript console:
Not allowed to load local resource: blob:chrome-extension://panahgiakgfjeioddhenaabbacfmkclm/48ff3e53-ff6a-4bee-a1dd-1b8844591a91
I also get the same if I post the URL in a message all the way to the injected page. Should this work? I'd really appreciate any advice here.
In my manifest I also have
"web_accessible_resources": [ "*" ] but that was only to see if it resolved this issue (it did not).
In a content script, the DOM is shared with the page, so any DOM operations (such as setting the video src) is subject to the same-origin policy of the page, not the extension.
If you want to show the content of a tab, then you MUST pass a tab.Tab object to chrome.desktopCapture.chooseDesktopMedia. This object can be obtained in many ways, including the message passing and tabs APIs. Here is an example using the extension button:
background.js
chrome.browserAction.onClicked.addListener(function(tab) {
// NOTE: If you want to use the media stream in an iframe on an origin
// different from the top-level frame (e.g. http://example.com), set
// tab.url = 'http://example.com'; before calling chooseDesktopMedia!
// (setting tab.url only works in Chrome 40+)
chrome.desktopCapture.chooseDesktopMedia([
'screen', 'window'//, 'tab'
], tab, function(streamId) {
if (chrome.runtime.lastError) {
alert('Failed to get desktop media: ' +
chrome.runtime.lastError.message);
return;
}
// I am using inline code just to have a self-contained example.
// You can put the following code in a separate file and pass
// the stream ID to the extension via message passing if wanted.
var code = '(' + function(streamId) {
navigator.webkitGetUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: streamId
}
}
}, function onSuccess(stream) {
var url = URL.createObjectURL(stream);
var vid = document.createElement('video');
vid.src = url;
document.body.appendChild(vid);
}, function onError() {
alert('Failed to get user media.');
});
} + ')(' + JSON.stringify(streamId) + ')';
chrome.tabs.executeScript(tab.id, {
code: code
}, function() {
if (chrome.runtime.lastError) {
alert('Failed to execute script: ' +
chrome.runtime.lastError.message);
}
});
});
});
manifest.json
{
"name": "desktopCapture.chooseDesktopMedia for a tab",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_title": "Show desktop capture request"
},
"permissions": [
"desktopCapture",
"activeTab"
]
}
ObjectURLs can't be shared cross-origin. A Data URL can be shared cross-origin if it works with your video stream (I'm not sure).

Chrome Extension: how to capture selected text and send to a web service

For the Google Chrome extension, I need to capture selected text in a web page and send to a web service. I'm stuck!
First I tried a bookmarklet, but Chrome on Mac seems to have some bookmarklet bugs so I decided to write an extension.
I use this code in my ext:
function getSelText(){
var txt = 'nothing';
if (window.getSelection){
txt = "1" + window.getSelection();
} else if (document.getSelection) {
txt = "2" + document.getSelection();
} else if (document.selection) {
txt = "3" + document.selection.createRange().text;
} else txt = "wtf";
return txt;
}
var selection = getSelText();
alert("selection = " + selection);
When I click on my extension icon, I get a "1". So I think the act of selecting outside the browser window is causing the text to not be seen by the browser as "selected" any more.
Just a theory....
thoughts?
You can do this by using Extensions Messaging. Basically, your "background page" will send the request to your service. For example, lets say you have a "popup" and once you click on it, it will do a "Google search" which is your service.
content_script.js
In your content script, we need to listen for a request coming from your extension, so that we send it the selected text:
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if (request.method == "getSelection")
sendResponse({data: window.getSelection().toString()});
else
sendResponse({}); // snub them.
});
background.html
Now in background page you can handle the popup onclick event so that we know we clicked on the popup. Once we clicked on it, the callback fires, and then we can send a request to the content script using "Messaging" to fetch the selected text.
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.sendRequest(tab.id, {method: "getSelection"}, function(response){
sendServiceRequest(response.data);
});
});
function sendServiceRequest(selectedText) {
var serviceCall = 'http://www.google.com/search?q=' + selectedText;
chrome.tabs.create({url: serviceCall});
}
As you have seen, I registered a listener in a content script to allow my extension to send and receive messages from it. Then once I received a message, I handle it by searching for Google.
Hopefully, you can use what I explained above and apply it to your scenario. I just have to warn you that the code written above is not tested, so their might be spelling, or syntax errors. But those can easily be found by looking at your Inspector :)
content script
document.addEventListener('mouseup',function(event)
{
var sel = window.getSelection().toString();
if(sel.length)
chrome.extension.sendRequest({'message':'setText','data': sel},function(response){})
})
Background Page
<script>
var seltext = null;
chrome.extension.onRequest.addListener(function(request, sender, sendResponse)
{
switch(request.message)
{
case 'setText':
window.seltext = request.data
break;
default:
sendResponse({data: 'Invalid arguments'});
break;
}
});
function savetext(info,tab)
{
var jax = new XMLHttpRequest();
jax.open("POST","http://localhost/text/");
jax.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
jax.send("text="+seltext);
jax.onreadystatechange = function() { if(jax.readyState==4) { alert(jax.responseText); }}
}
var contexts = ["selection"];
for (var i = 0; i < contexts.length; i++)
{
var context = contexts[i];
chrome.contextMenus.create({"title": "Send to Server", "contexts":[context], "onclick": savetext});
}
</script>
manifest.json
{
"name": "Word Reminder",
"version": "1.0",
"description": "Word Reminder.",
"browser_action": {
"default_icon": "images/stick-man1.gif",
"popup":"popup.html"
},
"background_page": "background.html",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/myscript.js"]
}
],
"permissions": [
"http://*/*",
"https://*/*",
"contextMenus",
"tabs"
]
}
and here is the link where i have all in one extension to download.
after reading this i tried of my own and have published.
and here is the complete source
http://vikku.info/programming/chrome-extension/get-selected-text-send-to-web-server-in-chrome-extension-communicate-between-content-script-and-background-page.htm
Enjoy
Using a content_scripts is not a great solution as it injection to all documents including iframe-ads etc. I get an empty text selection from other pages than the one I expect half the times on messy web sites.
A better solution is to inject code into the selected tab only, as this is where your selected text lives anyhow. Example of jquery doc ready section:
$(document).ready(function() {
// set up an event listener that triggers when chrome.extension.sendRequest is fired.
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
// text selection is stored in request.selection
$('#text').val( request.selection );
});
// inject javascript into DOM of selected window and tab.
// injected code send a message (with selected text) back to the plugin using chrome.extension.sendRequest
chrome.tabs.executeScript(null, {code: "chrome.extension.sendRequest({selection: window.getSelection().toString() });"});
});
It is not clear from your code where it is. What I mean, is that if this code is either in popup html or background html then the results you are seeing are correct, nothing in those windows will be selected.
You will need to place this code in a content script so that it has access to the DOM of the page, and then when you click your browser action, you will need to send a message to the content script to fetch the current document selection.
You don't need a Google API for something as simple as this...
I'll use the Bing online service as an example. Note that the URL is set up to accept a parameter:
var WebService='http://www.bing.com/translator/?text=';
frameID.contentWindow.document.body.addEventListener('contextmenu',function(e){
T=frameID.contentWindow.getSelection().toString();
if(T!==''){e.preventDefault(); Open_New_Tab(WebService+encodeURIComponent(T)); return false;}
},false);
NB: The function "Open_New_Tab()" used above is an imaginary one that accepts the webservice URL with the encoded selected text as a parameter.
That's the idea basically.