WikiEducator Notes: OERu's course feed aggregation and messaging system

View recent blog entries

Here at the OERu, we provide a service, attached to all of our online courses (and available to all of our partners - or anyone else for that matter) which allows anyone involved in those courses to communicate with one another from any one of a dazzling array of online "places" with WikiEducator Notes (aka WENotes). The entire system is free and open source software (FOSS). 

The magic glue is "tags" - most social media and online systems support "tagging" in one way or another. Tools like Twitter, Mastodon, and G+ support "hashtags" like "#OERu" as shorthand. Other tools like blog engines (Medium, WordPress, and others), forums (like Discourse), content annotation services (like Hypothesis), social bookmarking services (like Semantic Scuttle), and instant messaging services (like Rocket.Chat, Riot + Synapse, Zulip, and Mattermost, among others) support adding  "tags", "labels" or "categories" to any given post. WENotes is sensitive to those tags, and thanks to the modern trend towards services providing feeds, it can happily "harvest" relevant content from all over the web, in real time (or nearly, depending on the technology).

How WENotes components fit together

WENotes is a collection of a significant number of separate FOSS components. See the graphical diagram of WENotes alongside this article for an overview of the WENotes component architecture. To provide further explanation, WENotes is made up of several functional components:

  1. A set of feed harvesting scripts that are part of the WENotes-tools project (written in Javascript and Python, for those interested in that sort of detail, the former ideal for those sites producing JSON feeds and real time updates, the latter our choice for periodic scans - every 10 minutes by default at the moment - of RSS and ATOM feeds)
  2. A central CouchDB instance, which stores all posts harvested, along with metadata like their source, a unique ID (to ensure we don't re-harvest it) and some useful info to ensure we can push the messages to subscribers effectively.
  3. The WENotes client for MediaWiki or WordPress, part of the WENotes project, which provides a real-time feed of posts, either a full feed (for example this one) or specific to a tag (for example a specific OERu Course). The client also offers a posting interface, allowing logged in users to post new posts (up to 300 characters) automatically tagged in the relevant context. See two screenshots attached.
  4. A "publish-subscribe" or "Pub-Sub" service, WENotes-server project, a Node.JS based Faye implementation of the Bayeux protocol. It uses websockets to push out real-time updates to subscribed WENotes clients.
  5. The "couchwatch.js" script, also part of the WENotes-tools project, watches the CouchDB and alerts Faye whenever a new post is received, which in turn pushes it out to subscribed WENote clients.

Whence WENotes posts come

Here are three scenarios which outline the process by which a WENote post is created.

Handy WENotes post form

The most obvious way to create a WENotes post is to go to a WENote feed page - for instance - and log into the system (assuming you have authentication credentials or can create some). If you're logged in, you should see a simple form as illustrated in the two attached screen shots. The system then knows who you are so that your post can be properly attributed, and it knows the "context" for the feed you're looking at (namely the associated tag). You can enter a message of up to 300 characters, and hit the "Post a WENote" button, and this will send - direct from your browser - a suitably formatted post (a simple JSON object - example below) into the WENotes CouchDB where it is then available for inclusion in relevant feed displays.

Personal blog post, appropriately tagged

If you register for our Course site, or any course on that site, you have the option of adding a "Blog URL" - that's the web address of your blog (the address of this blog is https://tech.oeru.org, for instance). Because most widely used blogging platforms, like the open source WordPress platform (which is the most widely use web platform in the world, the basis for 25% of all websites!! For the record, this website uses Drupal, which is the 3rd biggest web platform, and is also open source), and proprietary cloud blogging platforms like Blogger and Medium, provide both "tagging" of posts and a built-in RSS feed. When you know what to look for, you'll start to see them all over the place (this blog has one - the logo is the little grey icon on the bottom left corner of this page).

When you create a blog post on your registered blog and tag it with a course-specific tag (each course we run has one, it's usually the short code immediately after the main course website URL: https://course.oeru.org/[course code here]/...) the WENotes feed harvesters will periodically visit your blog's RSS feed and look for new posts tagged with a course tag. If it finds one, it will create a new post in CouchDB with all the necessary info to provide an informative post in the WENotes feed, proper author attribution (and an "avatar" picture of you where available!), and a way for interested readers to get to your original post.

Tagged/Hashtagged social media reference

Because the attention of many people is heavily focused on social media - dominated by "network effect" companies like Twitter and Facebook, for better or worse, but also by an array of more open and egalitarian services (which we, on principle, prefer, but recognise that they're far less well marketed due mostly to not taking anyone's money) - we also provide a variety of social media specific "feed harvesters".  Some of these periodically scan specific social media technologies for new relevant posts, others perpetually monitor their sources providing instant updates.

If you

  • Toot including a relevant hashtag (e.g. #oeru or #lida101 for the "Learning in a Digital Age 101" course),
  • use Hypothesis to annotate a web page, tagging it appropriately (e.g. oeru or lida101),
  • add a Semantic Scuttle "Bookmark" to a web reference of interest, tagging it appropriately,
  • add a topic or reply on our Community or Forums Discourse instances also suitably tagged, or
  • create a Moodle post on a Moodle site running a suitable "student bot",

the WENotes feed harvesters will find it and create a suitable post in the CouchDB.

How WENotes feeds are made

Once you have a bunch of WENotes posts stored in CouchDB, how do we create a feed? At present, we have "WENotes client" scripts that run either on MediaWiki instances, like Wikieducator, or on WordPress sites, like our Course multisite, for which content is usually provisioned per-course using our "Snapshot" toolchain. In both cases, the client is invoked via the inclusion of a "Widget" in the MediaWiki content that will either be viewed on the MediaWiki instance itself, or on a target WordPress course subsite.

After you log into the Wikieducator site, and create or find a page on which you want a feed, you "Edit source" and you can add both a WENotes Post template and WENotes Feed template two simple one line recipes like this:

{{#widget:WEnotesPost|tag=lida101}}
{{#widget:WEnotes|tag=lida101|count=20}}

This invocation will create a page showing a WENotes Post Widget, where any submitted post will automatically be tagged by "lida101", and directly below that, a feed showing up to 20 posts from the CouchDB tagged with "lida101". Note, any new suitably tagged posts will pop into the feed in realtime as they're added to CouchDB thanks to couchwatch.js and Faye - invoking a WENotes Feed Widget automatically subscribes you to realtime updates for that tag.

You can also create a cross-tag feed using the magic "_" designator. WENotes Posts require a tag (the default, if none is specified, is "wikieducator"):

{{#widget:WEnotes|tag=_|count=20}}

WENotes technical details

This is a tech-blog, so I'm not going to shy away from the technical details. Here're some useful bits and pieces for folks wanting to get a better understanding of this system. If you're scared of technical details, you can stop reading here.

WENotes technology infrastructure

We implement the services making up the WENotes stack using a set of three Docker containers as well as our main MediaWiki implementation, WikiEducator, and our main WordPress multisite implementation, the OERu Course multisite (we also maintain development instance of each of these). The Docker containers are managed with Docker Compose and the roles are divided  We've created a separate code repository on GitHub for our evolving WENotes stack to facilitate others making use of some or all of it! The three containers currently being deployed do the following:

  1. run CouchDB version 2.0 or later, as well as its "Fauxton" interactive web front-end.
  2. run the Faye Pub-Sub websocket destination for realtime feed updates, implemented as a Node.JS service running under pm2.
  3. run the Javascript (using pm2) and Python-based (using cron to run periodically) feed harvesters and the Javascript "couchwatch.js" script which alerts Faye to the addition of new posts.

Once configured (we're working on making that a straight forward "out-of-the-box" experience, although it's not quite there yet), the three containers can be deployed together with a single invocation of Docker Compose - at the moment, though, this system is quite dependent on a properly configured "options.json" file, the format of which is still in a state of flux.

WENotes JSON object

A WENotes post is stored in our CouchDB as a JSON object (JSON = JavaScript Object Notation). This is an example of a Post as it's stored in our CouchDB:

{
  "_id": "3e1c16eb1c99ae59899761a680006f4a",
  "_rev": "1-7462f14a0515d98b5985b9f4b8c39dd5",
  "media_attachments": [
    {
      "url": "https://mastodon.oeru.org/system/media_attachments/files/000/000/017/or…",
      "text_url": "https://mastodon.oeru.org/media/jvUoIpj6OAt-e5t-H8k",
      "preview_url": "https://mastodon.oeru.org/system/media_attachments/files/000/000/017/sm…",
      "meta": {
        "small": {
          "width": 400,
          "size": "400x321",
          "aspect": 1.2461059190031152,
          "height": 321
        },
        "original": {
          "width": 1148,
          "size": "1148x921",
          "aspect": 1.246471226927253,
          "height": 921
        }
      },
      "type": "image",
      "id": 17,
      "remote_url": ""
    }
  ],
  "truncated": true,
  "reblog": null,
  "id": 21,
  "in_reply_to_id": null,
  "content": "<p>Productive weekend fixing hashtags and instructions for <a href=\"https://mastodon.oeru.org/tags/lida101\" class=\"mention hashtag\" rel=\"tag\">#<span>LiDA101</span></a> photo challenges . Super cool to see mastodon.oeru,org toots in the course feed. <a href=\"https://mastodon.oeru.org/tags/lida101photo\" class=\"mention hashtag\" rel=\"tag\">#<span>lida101photo</span></a> <a href=\"https://mastodon.oeru.org/media/jvUoIpj6OAt-e5t-H8k\" rel=\"nofollow noopener\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">mastodon.oeru.org/media/jvUoIp</span><span class=\"invisible\">j6OAt-e5t-H8k</span></a></p>",
  "application": null,
  "text": "Productive weekend fixing hashtags and instructions for #LiDA101 photo challenges . Super cool to see mastodon.oeru,org toots in the course feed. #lida101photo...",
  "profile_url": "https://mastodon.oeru.org/@mackiwg",
  "tags": [
    {
      "url": "https://mastodon.oeru.org/tags/lida101",
      "name": "lida101"
    },
    {
      "url": "https://mastodon.oeru.org/tags/lida101photo",
      "name": "lida101photo"
    }
  ],
  "visibility": "public",
  "we_tags": [
    "lida101"
  ],
  "user": {
    "screen_name": "mackiwg",
    "profile_image_url": "https://mastodon.oeru.org/system/accounts/avatars/000/000/002/original/…",
    "name": "Wayne Mackintosh"
  },
  "spoiler_text": "",
  "we_timestamp": "2017-09-10T04:09:05.000Z",
  "account": {
    "username": "mackiwg",
    "display_name": "Wayne Mackintosh",
    "statuses_count": 10,
    "following_count": 1,
    "url": "https://mastodon.oeru.org/@mackiwg",
    "locked": false,
    "created_at": "2017-04-30T01:51:59.778Z",
    "avatar_static": "https://mastodon.oeru.org/system/accounts/avatars/000/000/002/original/…",
    "note": "<p>Open sourcing education at OERu</p>",
    "header": "https://mastodon.oeru.org/headers/original/missing.png",
    "followers_count": 1,
    "avatar": "https://mastodon.oeru.org/system/accounts/avatars/000/000/002/original/…",
    "header_static": "https://mastodon.oeru.org/headers/original/missing.png",
    "acct": "mackiwg",
    "id": 2
  },
  "favourites_count": 0,
  "language": "en",
  "url": "https://mastodon.oeru.org/@mackiwg/21",
  "we_source": "mastodon",
  "in_reply_to_account_id": null,
  "uri": "tag:mastodon.oeru.org,2017-09-10:objectId=21:objectType=Status",
  "reblogs_count": 0,
  "sensitive": false,
  "mentions": [],
  "created_at": "2017-09-10T04:09:58.197Z"
}

The most crucial things for WENotes are the value that start with we_ They are used by the system which filters and displays WENotes posts. 

The OERu Tag List

The list of tags which OERu's WENotes looks for changes from time-to-time, usually growing as we add new course tags or other tags of business. Currently, the list is being updated manually, but it is available as a web feed in JSON format. We intend to provide both a simple editing interface for this list and to provide an API to automate adding new tags. Eventually, when we create a new course on our Course multisite, we expect to be able to automatically registered the new course "tag" with the tag list.

Credits

The entire underlying mechanics and most of the cleverness of WikiEducator Notes are thanks to the ingenuity and hard work of Jim Tittsler, my predecessor as OER Foundation Technical Lead.