Skip to content

Instantly share code, notes, and snippets.

@joeyespo
Last active May 15, 2019 13:18
Show Gist options
  • Save joeyespo/e826833ce56f6028373c78a0df03303f to your computer and use it in GitHub Desktop.
Save joeyespo/e826833ce56f6028373c78a0df03303f to your computer and use it in GitHub Desktop.
Webtask.io cron job for monitoring Chrome Extension reviews and support tickets (notifications sent through IFTTT)

Chrome Extension Comment Monitor

Jump to the code

A small Webtask Cron script to monitor a Chrome Web Store Extension for new review and support comments.

Dependencies

  1. webtask.io
  2. ifttt.com

Instructions

  1. Create an IFTTT account to send notifications with

  2. Create a new recipe with the Webhooks service to receive the web request with

    Screenshot 01

  3. Set the Event Name to new_chrome_extension_comment

    Screenshot 02

  4. Use the Email action service to send yourself an email

    Screenshot 03

  5. Customize the Subject and Body (optional)

    Screenshot 04

    Note that the arguments sent from the script are:

    • {{Value1}} -- either Review or Support
    • {{Value2}} -- the Chrome Extension ID (without the title)
    • {{Value3}} -- the URL subpath (either /reviews or /support)
  6. Optionally customize the title, review, and click Finish

  7. Clone this Gist locally

  8. Create a new webtask.io Cron job

    $ wt cron schedule \
      --secret EXTENSION_ID=<YOUR-EXTENSION-ID> \
      --secret IFTTT_KEY=<YOUR-IFTTT-KEY> \
      1m check-chrome-extension-comments.js

    Your Extension ID can be found in the Web Store URL, e.g. mpjmeeikbbgccbjkbfabocnjcaejdpmj for this extension.

    Your IFTTT Key can be found in your Maker Webhooks settings.

    Screenshot 05

If wired up correctly, you should receive an email within the hour. (...yep, IFTTT email can be slow. You may want to use the "Send yourself an email" action from the Gmail service instead if you need to minimize the delay.)

Here's what the resulting email will look like:

Screenshot 06

Clicking the link will take you to the latest comment.

You can also run wt logs to confirm it's running.

Known issues

  • Uncaught error: Error starting local server: Unexpected token import when running wt serve

    Until this issue is resolved, you'll have to run babel locally and serve the manually compiled output.

  • ReferenceError: regeneratorRuntime is not defined after deploying

    See this issue.

import Bluebird from 'bluebird';
import request from 'request-promise';
const COMPONENT_ID = 1; // Can use any integer as long as it's used consistently
const COMPONENTS_RESPONSE_PREFIX = `window.google.annotations2.component.load({'${COMPONENT_ID}':{'results':`;
const COMPONENTS_RESPONSE_PARTIAL_POSTFIX = "}},{'rpTime':";
const IFTTT_EVENT_NAME = 'new_chrome_extension_comment';
function parseComments(body) {
// Skip over fixed function call
const index = body.indexOf(COMPONENTS_RESPONSE_PREFIX);
const start = index + COMPONENTS_RESPONSE_PREFIX.length;
if (index !== 0) {
throw new Error(`Comments response contained an unexpected prefix, body = "${body.substr(0, 100)}..."`);
}
// Reverse search so we don't naively search over any user-entered string
const end = body.lastIndexOf(COMPONENTS_RESPONSE_PARTIAL_POSTFIX);
if (end === -1) {
throw new Error(`Comments response contained an postfix, body = "${body.substr(0, 100)}..."`);
}
// Strip away the surrounding JavaScript
const json = body.substring(start, end);
// Parse and return the comments (annotations)
return JSON.parse(json).annotations;
}
async function getComments(extensionId, group, count = 10) {
// Required JSON parameters passed to the Google Web Store API
const req = {
appId: 94,
hl: 'en',
specs: [
{
type: 'CommentThread',
url: `http://chrome.google.com/extensions/permalink?id=${encodeURIComponent(extensionId)}`,
groups: group,
sortby: 'date',
startindex: '0',
numresults: count.toString(),
id: COMPONENT_ID.toString(),
}
],
internedKeys: [],
internedValues: [],
};
// Request the comments from the Google Web Store API
const body = await request.post({
url: `https://chrome.google.com/reviews/components`,
headers: { 'User-Agent': 'Request-Promise', 'Content-Type': 'text/plain;charset=UTF-8' },
body: `req=${JSON.stringify(req)}`,
});
// Parse the API response and return the resulting object
return parseComments(body);
}
async function triggerIFTTT(ifttt_key, event_name, value1, value2, value3) {
return request.post({
url: `https://maker.ifttt.com/trigger/${event_name}/with/key/${ifttt_key}`,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ value1, value2, value3 })
});
}
async function runAsync(ctx) {
const { IFTTT_KEY, EXTENSION_ID } = ctx.secrets;
// Get latest regular and support comments
const reviews = await getComments(EXTENSION_ID, 'chrome_webstore', 1);
const tickets = await getComments(EXTENSION_ID, 'chrome_webstore_support', 1);
// Get latest timestamps (or `undefined` if there are no comments)
const reviewTimestamp = reviews.map(c => c.timestamp)[0];
const ticketTimestamp = tickets.map(c => c.timestamp)[0];
// Get latest stored timestamps and store the latest (last write wins)
const storage = Bluebird.promisifyAll(ctx.storage);
const data = await storage.getAsync();
await storage.setAsync({ reviewTimestamp, ticketTimestamp }, { force: 1 });
// Send requests on new comments
// Note: Send a request on initialization to verify that its all working
if (!data || reviewTimestamp !== data.reviewTimestamp) {
console.log(`Change detected! (reviewTimestamp = ${reviewTimestamp}, data.reviewTimestamp = ${data.reviewTimestamp})`);
await triggerIFTTT(IFTTT_KEY, IFTTT_EVENT_NAME, 'Review', EXTENSION_ID, '/reviews');
} else {
console.log(`No change detected (reviewTimestamp = ${reviewTimestamp}, data.reviewTimestamp = ${data.reviewTimestamp}), latest review = ${JSON.stringify(reviews[0])}`);
}
if (!data || ticketTimestamp !== data.ticketTimestamp) {
console.log(`Change detected! (ticketTimestamp = ${ticketTimestamp}, data.ticketTimestamp = ${data.ticketTimestamp})`);
await triggerIFTTT(IFTTT_KEY, IFTTT_EVENT_NAME, 'Support', EXTENSION_ID, '/support');
} else {
console.log(`No change detected (ticketTimestamp = ${ticketTimestamp}, data.ticketTimestamp = ${data.ticketTimestamp}), latest ticket = ${JSON.stringify(tickets[0])}`);
}
// Return the reviews and tickets for debugging purposes
return { reviews, tickets };
}
module.exports = function (ctx, cb) {
// TODO: Email errors?
runAsync(ctx).then((result) => cb(null, result)).catch((err) => cb(err));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment