Using Callbacks in River5

The River5 RSS aggregator by Dave Winer supports the ability to run a user script when a new item is added to a river. This post will give an overview of how this works and provide some example scripts. (Note: this is an extension of a previous wiki page on callbacks for River4. This page is also cross-posted on the River5 wiki).

The callbacks folder

There is a sub-folder in the River5 folder, the same folder as the river5.js app: callbacks. Each sub-folder of callbacks stores scripts that are called on a specific event. Only files with the extension .js are run. When River5 starts, it creates the callbacks folder and also creates a subfolder called addToRiver.

The addToRiver sub-folder of callbacks contains scripts that are called when a new item has been added to the river.

Each script in the addToRiver sub-folder is called once for each new item.

If there is more than one script in the folder, there is no guarantee of the order in which they run.

User scripts have three variables that are in scope when the script is called: urlfeed, itemFromParser and itemFromRiver. urlfeed is the HTTP address of the feed. itemFromParser is an object containing all the data that River5 received from the FeedParser module. itemFromRiver is the actual object in the river for today. You can add data or modify data in this record, and it will be saved to the river. The items you add can be accessed in other callbacks.

To be clear, you can modify any of the three in-scope objects, but only itemFromRiver will be saved.

Your first callback

Here is a link to a sample script. The script saves information from the new items added to the river (titles of the items).

To run the script, launch River5. It should create a callbacks folder and an addToRiver sub-folder when the first new item comes in.

Save a copy of this script in the addToRiver sub-folder, then wait until some new items come in.

Look in the file localstorage.json in the data folder within the River5 folder (this is described more in the next section). You should see some data. Here’s a link to example data from running the script for a few hours.

localStorage.json

There’s a new file maintained in your data folder, localStorage.json.

The file localStorage.json in the data folder within the River5 folder contains the data that callback scripts save in localStorage.

It provides a place to store persistent data that will be around next time River5 runs. Unlike browser-based localStorage, any type of data can be stored, not just strings. You can use this test script as a starting point for new scripts saving data.

Another script example

One use for a script can be to modify data that is in the item being added to the river. I got interested in doing this after I had added a feed from the Flickr photo sharing service for the latest photo uploads. When I looked at the feed as it was rendered, there was a link to the photo, but you had to click on the link to see the photo.

River5Pic1

I then decided to see if I could get the photo to appear in the item.

Now, for developing this script, I was using River5 with just one feed (the Flickr photo feed). I also added an element to the config.json file to force River5 to generate a new river every minute, so that I would not have to wait as long to see updates. My config.json file then looked like this:

{
"maxRiverItems": 300,
"ctMinutesBetwBuilds": 1
}

First, I modified the first example script to write data to localStorage to write the contents of the item:

if (thisFeed.stories === undefined) {

thisFeed.stories = new Array ();

}

thisFeed.stories [thisFeed.stories.length] = itemFromRiver; // New line

This is a typical result captured in localStorage.json:

"feeds": {
"https://api.flickr.com/services/feeds/photos_public.gne": {
"ctAdds": 40,
"whenFirstAdd": "2016-03-19T15:04:26.042Z",
"whenLastAdd": "2016-03-19T15:05:26.221Z",
"stories": [
{
"title": "Thank God my dad used to be a tailor",
"link": "https://www.flickr.com/photos/75485933@N03/25274558054/",
"description": "ColCronin132 (484-568-3918) posted a photo:",
"permalink": "",
"enclosure": {
"url": "https://farm2.staticflickr.com/1677/25274558054_98e37a46ca_b.jpg",
"type": "image/jpeg",
"length": null
},
"pubdate": "2016-03-19T15:04:18.000Z",
"comments": "",
"feedUrl": "https://api.flickr.com/services/feeds/photos_public.gne",
"when": "2016-03-19T15:04:25.952Z",
"aggregator": "River5 v0.45a",
"id": 440,
"whenLastSayHello": "2016-03-19T15:04:26.042Z",
"ctHellos": 1
},

You can see that the description element within the item has the text displayed when a user looks at the photo on Flickr. Further down, in the enclosure element, there is a URL to the photo itself:

"enclosure": {
"url": "https://farm2.staticflickr.com/1677/25274558054_98e37a46ca_b.jpg",
"type": "image/jpeg",
"length": null
},

I decided that I wanted to modify the description element to include an <img> link with the above URL to display the photo. I then added the following code:

var testString = "";

var testResult = "";

testString = '&lt;img src="' + itemFromRiver.enclosure.url + '"&gt;';

testResult = testString.concat(itemFromRiver.description);

itemFromRiver.description = testResult;

This code extracts the link and builds a text string with the link and the former description, then saves it in the description element of itemFromRiver. When we look at the data saved to localStorage.json, we now see the updated description:

"feeds": {
"https://api.flickr.com/services/feeds/photos_public.gne": {
"ctAdds": 40,
"whenFirstAdd": "2016-03-19T15:19:01.595Z",
"whenLastAdd": "2016-03-19T15:20:01.727Z",
"stories": [
{
"title": "CAM04520",
"link": "https://www.flickr.com/photos/gohomekiki/25274874964/",
"description": "&lt;img src=\"https://farm2.staticflickr.com/1669/25274874964_f9aa92cdf2_b.jpg\"&gt;gohomekiki posted a photo:",
"permalink": "",
"enclosure": {
"url": "https://farm2.staticflickr.com/1669/25274874964_f9aa92cdf2_b.jpg",
"type": "image/jpeg",
"length": null
},
"pubdate": "2016-03-19T15:18:51.000Z",
"comments": "",
"feedUrl": "https://api.flickr.com/services/feeds/photos_public.gne",
"when": "2016-03-19T15:19:01.455Z",
"aggregator": "River5 v0.45a",
"id": 480,
"whenLastSayHello": "2016-03-19T15:19:01.595Z",
"ctHellos": 1
},

The result in the River5 display now looks like this:

River5Pic2

However, most users will have multiple feeds (and multiple rivers) within River5, and this logic should only run when the item being processed is from a Flickr feed. I then added code to update the item description only when the item was from a Flickr feed. Here is the final version of the updated user script:

var testString = "";

var testResult = "";

if (itemFromRiver.link.search("flickr") != -1)

{

testString = '&lt;img src="' + itemFromRiver.enclosure.url + '"&gt;';

testResult = testString.concat(itemFromRiver.description);

itemFromRiver.description = testResult;

}

The script checks to see if the text string “flickr” appears in the link URL. The search method returns a -1 if the string is not present, so this logic will not be executed if itemFromRiver contains a link from a site other than Flickr.

Now that the script is working, make sure to remove the first line of code added at the beginning of this tutorial, so that locaStorage.json does not get too large:

thisFeed.stories [thisFeed.stories.length] = itemFromRiver; // New line

Notes

If the user script modifies itemFromRiver within the script, a call to the function todaysRiverChanged () needs to be added to the user script. This tells River5 to save the structure at the top of the minute.

The contents of localStorage is saved once a minute, if there were changes.