Script to read Reuters headlines

I’m really enjoying Scriptable. While I love Shortcuts, for more complex tasks I find writing text code to be a lot less tedious than dragging little boxes around. And, as a side benefit, Scriptable has given me a fun excuse to start learning JavaScript. As part of this learning process, I’ve been porting a few of my Shortcuts over to Scriptable. I thought I’d share one of my favorites here.

Reuters provides a fantastic array of RSS feeds on all sorts of news topics. You can find the whole list here. The description field for each RSS item includes a précis of a Reuters story which is perfect for generating summaries of the day’s news. I use these feeds to have my iPhone read me a personalized news brief of the top news items on the topics I’m interested in.

This code also illustrates an important benefit of Scriptable/JS over Shortcuts. Unlike Shortcuts, which is very linear, with JS you can run multiple asynchronous processes in parallel. As a result, I am able to download and parse several RSS feeds simultaneously, which makes the whole thing run much quicker.

Any comments or feedback would be greatly appreciated. I’m quite new to JavaScript, so I’m sure there are things I could do better.

"use strict"

// list of information needed to report Reuters RSS feeds.
// Each entry contains a URL to the feed, a simple title for the feed,
// and the desired number of stories to be reported.
const feedList = [
	feedInfo("http://feeds.reuters.com/Reuters/worldNews", "World", 5), 
	feedInfo("http://feeds.reuters.com/Reuters/domesticNews","National", 5),
	feedInfo("http://feeds.reuters.com/reuters/businessNews","Business", 5),
	feedInfo("http://feeds.reuters.com/reuters/scienceNews","Science", 3),
];

reportNews();

// This is the main function.  It retrieves each Reuters RSS feed, processes it, 
// and reports the list of stories.  Multiple RSS feeds are processed in
// parallel. When processing of all feeds is complete, the stories from all feeds a
// spoken.
function reportNews() {
	// load network requests for RSS feeds into an array of promises.
	let feedRequests = [];
	for (let feed of feedList) {
	  let request = new Request(feed.url).loadString();
	  feedRequests.push ( request.then( result => feedParser(result) ));
	}
	// When all request promises have been resolved, speak text of stories.
    Promise.all(feedRequests).then( results => {
		let i = 0;
		for (let feed of feedList) {
			let result = results[i];
			Speech.speak(feed.title + " Headlines");
  		let items = (feed.maxItems<result.length) ? result.slice(0,feed.maxItems) : result;
  		for (let item of items) Speech.speak(item);
			i++;
		}
	} );
}

// Generate a structure of RSS feed information.
function feedInfo(url,title,maxItems) {
	let info = new Object()
	info.url = url;
	info.title = title;
	info.maxItems = maxItems;
	return info;
}
  
// Return a promise containing stories from a parsed RSS feed string.
function feedParser(rssData) {
	return new Promise( (resolve, reject) => {
		let stories = [];
		let currentItem = "";
		let parser = new XMLParser(rssData);
		parser.foundCharacters = str => currentItem += str;
		parser.didStartElement = name => {
			if (name == "description") {
				currentItem = "";
			}
		}
		parser.didEndElement = name => {
			if (name == "description") {
				// Strip out HTML tags and endlines from story text.
				stories.push(currentItem.replace(/(<(.+)>)/g, "").replace(/\n/g,""));
			}
		}
		parser.didEndDocument = () => {
			// Remove the first story, which is a summary of the RSS feed.
			stories.shift();
			resolve(stories);
		}
		parser.parse();
	});
}
4 Likes

Thanks for sharing! :smiley: