Not a developer? Go to MovableType.com

Documentation

Action Streams Recipe Developer Guide

The Action Streams plugin collects authors’ actions performed in remote services. Plugin developers can add support to Action Streams for new web services, often with no Perl code required at all.

Making a plugin with the registry

In Movable Type, plugins are defined using a file called config.yaml instead of Perl code. When Movable Type loads the plugin, the settings the plugin defines in config.yaml are integrated into what’s called the registry. The registry is a tree of settings that informs Movable Type what settings, pages, and objects are available.

To define your plugin, make a new directory in the plugins directory. Inside it, create a file called config.yaml, and enter all the basic plugin settings. For example:

id:   ExampleService
key:  ExampleService
name: Example Service
description: An example Action Streams service.
version: 1.0
author_name: Mark Paschal
author_link: http://www.example.com/mark/
plugin_link: http://www.example.com/plugins/example-service/

These settings define a plugin called “Example Service” with all the specified settings. Consult the Movable Type developer documentation for further help on creating plugins.

Defining a profile service

Every action and stream belongs to a service, so unless you’re adding a stream for a service that already exists in Action Streams, you need to define one of those first. Usually a service is a web site at which an author has an account. Authors can use the Action Streams plugin to make a sidebar list of their accounts on all the services for which they enter their accounts.

Profile services are defined in the profile_services section of the registry. Because Movable Type merges your config.yaml into the registry, you specify additional services by adding your own profile_services section to your plugin’s config.yaml. For example:

profile_services:
    example:
        name: Simple Example Service
        url: http://www.example.com/people/{{ident}}
    complex:
        name: Complex Example Service
        url: http://big.example.com/{{ident}}/profile
        ident_label: User ID
        ident_example: 12345
        ident_suffix: /profile
        service_type: example
        icon: example.png

This defines two services. Each service has its own key by which it’s known in the registry. These services’ keys are example and complex. The data you can set for a service are:

name (required)

The human-readable name of the service. This name is used for display and for ordering the list of services.

url (required)

The URL to a user’s profile on the service. This is the URL used in the author’s list of services.

The string {{ident}} will be replaced by the author’s identifier for this service. Only one identifier per service is currently supported.

ident_label

A label to describe what the author’s identifier on the service is. If not given, the label Username is used. For example, if the user identifier on the service is a number, you might specify User ID here to indicate the author should enter a number. For the AIM service, the label is Screen name.

ident_example

A label to show what the identifiers for the service generally look like. For example, if the user identifier is a number, you might specify 12345. For the Flickr service, the example identifier is 36381329@N00.

ident_suffix

A label to show after the field in which the identifier is entered. Typically this is used to further suggest that the one’s subdomain on a service is one’s identifier. For example, the Vox service’s suffix is .vox.com.

service_type

The kind of web service this profile is about. Authors can list their accounts for a certain type of services using the type attribute of the OtherProfiles tag. Any value is allowed here, but the established service types are:

  • contact (AIM, Yahoo! Messenger, etc)
  • blog (Vox, Tumblr, the Website service, etc)
  • photos (Flickr, Smugmug, etc)
  • video (Vimeo, YouTube, etc)
  • links (Delicious, Digg, etc)
  • status (Pownce, Twitter, etc)
  • network (Facebook, MySpace, etc)

icon

The location of the service’s icon. This can be a complete URL to the image. If not a URL, icon should be a path to the image relative to your plugin’s mt-static directory. For example, if you install your example.png at mt-static/plugins/ExampleService/example.png, your icon setting should be example.png.

Defining stream recipes for a service

Once a service is defined in profile_services, you can make recipes for collecting its action streams. These recipes go in the action_streams section of the registry. For example:

action_streams:
    example:
        posts:
            name: Posts
            description: Posts you posted on the example service
            html_form: '[_1] posted <a href="[_2]">[_3]</a>'
            html_params:
                - url
                - title
            url: http://www.example.com/people/{{ident}}/posts
            atom:
                thumbnail: media:thumbnail/child::text()

Each service has its own section, labelled with the same key as in the profile_services section. Inside that, you can define one or more recipes for how to collect actions. As you can see, recipes have three parts:

  • URLs that indicate where to find action content.
  • Collectors that specify how to turn the resource at the stream’s URL into action data. Action data is primarily a title, a link, and a unique identifier for each action, but you can specify additional data fields in your recipe.
  • Forms that describe how to turn the action data back into HTML. Authors can display however they like with template code, but in the application and by default, actions in this stream will display as you specify in the recipe.

Together, these parts define the recipe.

Picking a stream ID

The word you use as the registry key when defining a stream is its ID. While you can use any term here, if the stream you’re defining is analogous to a stream available from another service, use the same ID as the other service’s stream does.

Template authors can list similar actions across services by using the stream attribute on the ActionStreams tag in absence of the service attribute. The value template authors will use with stream will be this ID, so in order for your (say) photos stream to be included when a user lists all posted photos across all services, use photos for your stream ID.

Some of the standard stream IDs used in the standard provided streams are:

  • links, web pages or resources that have been bookmarked (as in Delicious, Digg, Magnolia)
  • photos, photos posted by the author (as in Flickr, Picasa on the Web, Smugmug)
  • favorites, assets and objects of others’ that the author saved as a favorite (as in Vox, Twitter, YouTube)
  • achievements, awards won by the author (as in Steam or Kongregate)
  • videos, videos posted by the author (as in Viddler, Vimeo, YouTube)

Picking the stream resource

Typically, modern web services will provide a web feed or XML resource for their members that rolls up their recent activity on the site. Sometimes a service only provides an HTML page, but Action Streams can work with them too. This is the resource you will specify as your stream’s url.

The URL for the resource needs to include the user’s identifier in the URL. In some extreme cases, you may even have to change what identifier your profile service uses in order to get the stream resource. Normally the same user name or ID is in the URL, though.

Three schools of collection

Most services don’t yet publish their members’ actions as such, so even if you can collect from a nice standard web feed, the feed content will need some massaging. There are three main kinds of collectors at your disposal: XML feeds, HTML scrapers, and custom collectors.

XML feeds

It’s easy to collect data from XML feeds. Action Streams accepts recipes using XPath syntax. For example, you might collect data from Delicious’ RSS feeds with an xpath recipe:

delicious:
    links:
        name: Links
        description: Your public links
        # ...
        url: 'http://delicious.com/rss/{{ident}}'
        identifier: url
        xpath:
            foreach: //item
            get:
                created_on: dc:date
                title:      title
                url:        link

This specifies that each item in the feed is an XPath //item, meaning an item tag anywhere in the document. Within each item, you can find values for the created_on, title, and url fields using the given XPath selectors. The identifier: url statement indicates to use the url value as the action’s unique identifier.

As many services provide content as standard web feeds, it’s actually even easier to collect from those. Instead of xpath, specify your recipe as rss or atom, with any additional or special fields as additional XPath selectors:

delicious:
    links:
        name: Links
        description: Your public links
        # ...
        url: 'http://delicious.com/rss/{{ident}}'
        identifier: url
        rss:
            created_on: dc:date

If there are no special fields to collect, you can also specify the recipe as simply 1, as in this recipe for Jaiku:

jaiku:
    jaikus:
        name: Jaikus
        description: Jaikus you posted
        # ...
        url: 'http://{{ident}}.jaiku.com/feed/atom'
        atom: 1

The values of each entry’s ID field (guid in RSS and id in Atom) are then used for actions’ identifiers.

HTML pages

When feeds aren’t available, HTML will do. Action Streams reads HTML pages using Web::Scraper, an HTML scraping library inspired by the Ruby scrapi library. For example, the Iwtst plugin reads your iwanttoseethat.com profile page with this definition:

iwtst:
    want:
        name: Movies
        description: The movies you want to see
        # ...
        url: 'http://iwanttoseethat.com/people/{{ident}}'
        identifier: url
        scraper:
            foreach: 'div.seethat div.seethat_yes'
            get:
                url:
                  - a
                  - @href
                title:
                  - a
                  - TEXT

Instead of XPath, here we can use CSS selector syntax. (For more complex constructions, you can use XPath selectors as well.) This recipe says each action is a div with class seethat_yes inside a seethat div. The action’s url field is then the value of the href attribute of the a tag inside, while the title field is the text linked inside that a tag.

Custom collectors

If you aren’t pulling web data, or you can’t quite articulate the set of selectors to pull out the data you want, you can implement your own action collector as a Perl class. Specify what class implements your stream as the class option:

complex:
    posts:
        name: Posts
        description: Posts you posted on the complex example service
        # ...
        class: Example::Event::ComplexPosts

Your class should subclass the plugin’s ActionStreams::Event class, and implement your particular content-finding behavior. As the XML and HTML recipes are really the default behavior of an ActionStreams::Event object, the above techniques are easily accessible from your class too. See some of the ActionStreams::Event subclasses included with the Action Streams plugin for examples.

Formatting actions for display

While bloggers can use the provided template tags to customize how actions are published to the blog, recipes define how actions are displayed by default: when using the mt:StreamAction tag and when displayed in the application. This display is governed by the html_form and html_params options, as in the Delicious recipe:

delicious:
    links:
        name: Links
        description: Your public links
        html_form: '[_1] saved the link <a href="[_2]">[_3]</a>'
        html_params:
            - url
            - title
        # ...

The html_form setting is a pattern in maketext format, making it easy to . Each replacement token is numbered. The first replacement token, [_1], is always the name of the MT user whose action is being displayed. The next tokens represent the fields you specify in the html_params list (in this case, url and title).

Note that the result of formatting an action is, in fact, HTML. This markup will be returned verbatim when used with the mt:StreamAction tag. For display in the Movable Type application, the HTML is removed; Delicious links are the text “Author name saved the link title” without the hyperlink, for example.

Cleaning up after your stream

The provided collectors described above may not leave the action data in the correct displayable format. For example, the regular Twitter tweet feeds include the authors’ usernames with the tweets. In order to use the feeds, the Twitter stream needs to remove the names, so the tweet text is only the tweet text. How can it remove the usernames from the collected actions?

Movable Type provides a system for defining callbacks, or code hooks that are run at certain points of a process. The Action Streams plugin provides several callbacks at various stages of stream event collection. You will have to use Perl code to write these callbacks, but as you’re doing very small things, the code is usually simple.

For example, the Twitter stream uses the pre_build_action_streams_event callback to modify the actions after they’re collected by the normal feed collector, but before they are saved. The callback is defined in the registry thus:

callbacks:
    pre_build_action_streams_event.twitter_tweets: \
        $ActionStreams::ActionStreams::Plugin::fix_twitter_tweet_name

The stream callback code is then defined in ActionStreams::Plugin as:

sub fix_twitter_tweet_name {
    my ($cb, $app, $item, $event, $author, $profile) = @_;
    # Remove the Twitter username from the front of the tweet.
    my $ident = $profile->{ident};
    $item->{tweet} =~ s{
        \A          # start of the string
        \s*         # any whitespace
        \Q$ident\E  # the Twitter username
        : \s*       # followed by a colon and whitespace
    }{}xmsi;
}

The Action Streams callbacks available to you are documented later. See the Movable Type developer documentation for more about defining callbacks.

Stream recipe option reference

Recipes describe how to collect and display users’ actions on profile services. They are found in the action_streams section of the registry, in sections named the same as the related service in the profile_services section.

The options you can set for action streams are:

name (required)

The name of the action stream. This is displayed to authors when they enter their profile service identifier, where they are given the option of collecting the stream.

description (required)

The description of the action stream. This is displayed to authors with the stream’s name when they enter profile identifiers.

fields

The list of additional fields supported by actions of this stream.

Every stream already has a set of standard fields. You need only specify additional fields beyond these. The standard fields are:

title

The name of the page/asset/resource the action is about.

url

The web address of the page/asset/resource the action is about.

thumbnail

The web address (URL) of a thumbnail image of the page/asset/resource the action is about.

identifier

A string that uniquely identifies this action in its particular stream.

created_on

When the action was taken. This is not necessarily when the page/asset/resource the action is about was created; when a link is saved or a video is favorited, for example.

modified_on

When the action was last altered. Many actions occur only once and will never change, but some may (such as if the author changes the rating or tags for a saved link). Action Streams will update this timestamp automatically when the action object is changed.

html_form

The formatting string for formatting an action for display.

Numbered replacement tokens, beginning with [_ and ending with ], are replaced with the author’s name and the values of the fields named in html_params. For example, [_2] in the html_form will be replaced with the value of the second field specified in html_params.

html_params

The list of additional fields to replace into html_form when displaying an action. The values of the event fields named here are replaced into the numbered replacement tokens of html_form to produce the HTML version of an action.

If html_form has more tokens than there are fields specified in html_params, the nickname of the author whose action is being rendered is provided for your convenience. That is, if you omit the first field, [_1] in your html_form will automatically be replaced with the action’s author’s name.

If your event class is implemented as a Perl class, you can also use the names of any additional methods that class implements in your html_params.

url

The URL from which to collect action data. The token {{ident}} will be replaced with the author’s identifier on that service.

identifier

The name of the field to use as the unique identifier. Multiple fields can be specified by separating their names with commas (for example: title,created_on).

If not given, the collection recipe should collect an identifier field from the action source. If an identifier field is not collected, each action will be considered unique, regardless of the content of the other fields.

xpath

The XPath recipe with which to collect action data. The recipe options for an XPath recipe are:

foreach (required)

The XPath selector that selects the nodes of the resource document that represent actions. All the get selectors are then applied to each node to find the action data.

get (required)

A set of XPath selectors, keyed on the names of the action fields they describe. To find the value of each field, the indicated selector is applied to each node as determined by the foreach selector.

rss

The RSS recipe with which to collect action data. RSS recipes collect data from the standard RSS entry content. If child options are specified, each is used as an XPath selector (as in an xpath recipe’s get option) to collect a field from the RSS item nodes.

atom

The Atom recipe with which to collect action data. Atom recipes collect data from the standard Atom feed content. If child options are specified, each is used as an XPath selector (as in an xpath recipe’s get option) to collect a field from the Atom entry nodes.

scraper

The Web::Scraper recipe with which to collect action data. The options for a Web::Scraper recipe are:

foreach (required)

The CSS or XPath selector that selects the nodes of the resource document that represent the actions. All the get selectors are then applied to each node to find the action data.

get (required)

A set of Web::Scraper selectors, keyed on the names of the action fields they describe. Each selector is applied to each action node, as determined by the foreach selector, to find the action data.

Each Web::Scraper selector is a two-element list:

  • The CSS or XPath selector to use to find a child node.
  • @attribute or TEXT to select an attribute value or the text content of the node.

class

The name of the Perl package to use to represent actions in this stream. The Perl package should be a subclass of ActionStreams::Event. See the ActionStreams::Event perldoc for information on defining an action stream in Perl code.

Stream recipe callback reference

Action Streams specifies several callbacks for you to customize the creation of your action streams and the collection of their events.

Note that Movable Type’s standard object callbacks are also available for the ActionStreams::Event objects representing actions.

pre_add_profile.type

Fires before an author’s new profile is added. type is the ID (registry key) of the service being added. The parameters passed to your callback routine are:

  • $app, the current MT::App through which the profile is being added
  • $user, the MT::Author whose profile is being added
  • $profile, a hashref containing the information about the profile being added:
    • type, the ID of the profile service
    • ident, the user ID on the remote service
    • label, a user-facing description of the profile
    • uri, the URL of the profile page
    • streams, a hashref with keys that are the stream IDs of the streams the author has selected to collect; all values are set to 1

post_add_profile.type

Fires after an author’s new profile is added. The same parameters are passed to your callback routine as to a pre_add_profile routine.

pre_remove_profile.type

Fires before a profile is removed. The parameters passed to your callback routine are:

  • $app, the current MT::App through which the profile is being removed
  • $user, the MT::Author whose profile is being removed
  • $type, the ID of service corresponding to the profile being removed
  • $ident, the user ID on the remote service for the profile being removed

post_remove_profile.type

Fires after a profile is removed. The parameters passed to your callback are the same as to a pre_remove_profile callback.

pre_action_streams_task

Fires before the scheduled task to collect stream events is performed. The parameters to your callback routine are:

  • $mt, the current MT instance through which the stream events will be collected

post_action_streams_task

Fires after the scheduled task to collect stream events is completed. The parameters to your callback are the same as for pre_action_streams_task.

pre_update_action_streams_profile.type

Fires before the streams for a particular author’s profile are processed. For example, this callback fires once before an author’s Twitter streams (both the tweets and favorites streams) are collected. The parameters passed to your callback routine are:

  • $mt, the current MT instance through which the stream events will be collected
  • $author, the MT::Author whose streams are being collected
  • $profile, a hashref containing all the information about the profile; this is the same as the $profile argument to the pre_add_profile callback

post_update_action_streams_profile.type

Fires after the streams for a particular author’s profile are processed. The parameters passed to your callback routine are the same as to pre_update_action_streams_profile.

Note that in the current implementation, action streams are collected asynchronously, whereas this callback is fired synchronously after the collection jobs are queued. The new actions from the author’s profile will not have been collected when this callback occurs.

pre_build_action_streams_event.type

Fires after a stream event is collected, but before it’s put into the ActionStreams::Event object. The parameters given to your callback routine are:

  • $mt, the current MT instance through which the stream event was collected
  • $item, a hashref containing the data collected for this event
  • $event, the ActionStreams::Event into which the event data will put; this is either a new empty ActionStreams::Event, or the previous version of this event as determined by the stream recipe’s identifier
  • $author, the MT::Author whose stream is being collected
  • $profile, a hashref containing all the information about the profile; this is the same as the $profile argument to the pre_add_profile callback

post_build_action_streams_event.type

Fires after a stream event is composed, but before it is saved. The parameters passed to your callback routine are the same as to pre_build_action_streams_event.

Back