Help fetching dynamic HTML title in iOS Shortcut

(I’m cross-posting this from r/Scriptable, since I didn’t get any replies.)

I’m trying to create an iOS Shortcut that accepts a URL and retrieves the contents of the <title> tag from that webpage. This would be simple, except I want it to work with Twitter permalinks – Twitter updates the page contents, including the title, using Javascript after the initial load. After a fair bit of trial and error, this is as close as I’ve been able to get: Shortcuts

This works fine for most websites, but not for Twitter. I get the following error:

Could Not Run Run Inline Script
Script completed without presenting UI, triggering a text to speak or outputting a value. If this is intentional, you can manually call Script.complete() to gracefully complete the script.

Curiously, if I copy the in-line code from that Shortcut (below for reference), and paste it into a new script in Scritpable, it works fine for Twitter only in that case (fetching the title for the hard-coded test URL).

Any suggestions? Perhaps it’s an issue with memory, or how I’m passing the data back? I’m new to Shortcuts and Scriptable, and have only limited Javascript experience, so I might be missing something small…

Thanks!

const url = args.plainTexts && args.plainTexts[0] ? args.plainTexts[0] : "https://twitter.com/lehrblogger/status/391287397457330177";
const interval = 300;
const webView = new WebView();
await webView.loadURL(url);

async function getTitle() { 
  const title = await webView.evaluateJavaScript("document.title");
  // Twitter URL pages start with no title, and then are progressively updated
  log(title);
  if (title && title != "Twitter" && title != "Tweet / Twitter") {
    Script.setShortcutOutput(title);
    Script.complete();
  } else {
    Timer.schedule(interval, false, getTitle);
  }
};

Timer.schedule(interval, false, getTitle);

Someone on Reddit recommended keeping Shortcuts and Scriptable separate. After some trial and error, I was able to do everything I wanted to do with only Scriptable. In case anyone has a similar issue in the future and finds this post, here is the full script which fetches the titles sufficiently reliably:

// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-blue; icon-glyph: map-pin;
// share-sheet-inputs: url;

const urlTest = "https://twitter.com/jack/status/20?s=21"

function normalizeURL(url) {
  // Remove unnecessary query parameters to prevent duplicate bookmarks
  let replacements = [
    [/\?s=21/, ""],
    [/\?s=12/, ""],
    [/\?igshid=[a-z0-9]{12}/, ""],
    [/\?referringSource=articleShares/, ""],
    [/\?referringSource=articleShare/, ""],
    [/en\.m\.wikipedia/, "en.wikipedia"],
    [/#:~:text=\S+/, ""]
  ], r;
  while ((r = replacements.shift()) && (url = String.prototype.replace.apply(url, r))) {}
  return url;
}

async function getTitleForURL(url) {
  const webView = new WebView();
  await webView.loadURL(url);
  let title = url;
  let currentTime = new Date().getTime();
  const endTime = currentTime + 5000;
  while (currentTime < endTime) {
    title = await webView.evaluateJavaScript("document.title");
    if (title && title != "Twitter" && title != "Tweet / Twitter") {
      break;
    }
    currentTime = new Date().getTime();
  }
  return title;
}

async function showNotificationForTitle(title) {
  let notification = new Notification();
  notification.title = "Bookmark added! Title:";
  notification.body = title;
  await notification.schedule();
}

async function showNotificationForError(error) {
  let notification = new Notification();
  notification.title = "Bookmark not added! Error:";
  notification.body = error;
  await notification.schedule();
}

const keychainKey = "pinboard-api-token";
if (!Keychain.contains(keychainKey)) {
  Script.complete();
}
const authToken = Keychain.get(keychainKey)

const urlOriginal = args.urls && args.urls[0] ? args.urls[0] : urlTest;
const urlNormal = normalizeURL(urlOriginal);

let input = new Alert();
input.title = "URL and Tags";
input.addTextField(urlNormal, urlNormal);
input.addTextField("tags");
input.addTextField("description");
input.addCancelAction("Cancel");
input.addAction("Add Bookmark");
if (await input.present() == 0) {
  let urlFinal = input.textFieldValue(0);
  let tags = input.textFieldValue(1);
  let extended = input.textFieldValue(2);
  let title = await getTitleForURL(urlFinal);
  pinboard = "https://api.pinboard.in/v1/posts/add?auth_token=" + authToken;
  pinboard += ("&format=json");
  pinboard += ("&url=" + encodeURIComponent(urlFinal));
  pinboard += ("&description=" + encodeURIComponent(title.slice(0, 255)));
  if (tags) {
    pinboard += ("&tags=" + encodeURIComponent(tags. slice(0,255)));
  }
  if (extended) {
    pinboard += ("&extended=" + encodeURIComponent(extended));
  }
  let request = new Request(pinboard);
  let json = await request.loadJSON();
  if (request.response.statusCode == 200) {
    log(JSON.stringify(request.response));
    await showNotificationForTitle(title);
  } else {
    await showNotificationForError(request.response.statusCode);
  }
}

Script.complete();
2 Likes