NAME

BusyBird::Manual::Tutorial - Tutorial to use BusyBird

Installation

First you need C compiler tool-chains and cpanm command. In Ubuntu Linux, for example, you can install them by

$ sudo apt-get install build-essential cpanminus

To install BusyBird, type

$ cpanm BusyBird

This may take time because it also installs many modules BusyBird depends on.

If you don't have much time and don't want it to run tests, set -n option to skip tests.

$ cpanm -n BusyBird

If you are new to cpanm, set the following environment variables.

$ export PERL5LIB="$HOME/perl5/lib/perl5:$PERL5LIB"
$ export PATH="$HOME/perl5/bin:$PATH"

These are necessary for perl to find the modules installed in ~/perl5 directory. Be sure to write them in ~/.profile, too.

Start BusyBird

After installing BusyBird successfully, type

$ busybird
Twiggy: Accepting connections at http://127.0.0.1:5000/

Then, access the URL ( http://127.0.0.1:5000/ ) by your Web browser. If you can see the top page, congraturations! BusyBird has successfully started.

Note that if you already use the TCP port 5000, busybird command fails with "Address already in use" error. In that case, try another port by -p option.

$ busybird -p 4444

You can see complete list of options by -h option.

$ busybird -h

Input Statuses

By default, your BusyBird instance has a timeline called "home", but the timeline has no status yet. It won't magically import statuses out of nowhere. It is you who input statuses to it.

To input statuses, you can use BusyBird's Web API.

$ curl -d '{"text":"hello, world!"}' -H 'Content-Type: application/json' \
  http://127.0.0.1:5000/timelines/home/statuses.json

POST /timelines/home/statuses.json endpoint inputs statuses to the "home" timeline. Statuses are in the HTTP request body, encoded in JSON.

You can input more than one statuses by posting an array of statuses. Here is a bit more complicated example.

$ curl \
  -d '[{"text":"Hello, Bob!", "user":{"screen_name":"Alice"}}, {"text":"Hello, Alice!", "user":{"screen_name":"Bob"}}]' \
  -H 'Content-Type: application/json' http://127.0.0.1:5000/timelines/home/statuses.json

This time, the statuses have user.screen_name fields. BusyBird renders this field as the person or object that created the status.

Import Statuses from Twitter

You now understand how to input statuses to BusyBird, but it is boring to create statuses by hand. So let's import statuses (tweets) from Twitter and view them on BusyBird.

To import tweets from Twitter via its API, you have to get the tokens first. Here is how to get the tokens.

  1. Access https://apps.twitter.com/.

  2. "Sign in" with your Twitter account.

  3. Click "Create New App" button.

  4. Fill the form and click "Create your Twitter application".

  5. Click "API Keys" tab.

  6. Click "Create my access token" button at the bottom of the page. You may have to refresh the page to get the created token.

  7. Now in "API Keys" page, you see four mysterious tokens: API key, API secret, Access token and Access token secret.

The procedure above may be out of date. The point is to get the four OAuth tokens.

Make sure to keep the four tokens (especially "secret" ones) secret. Those are like the username-password pair of your account.

Now that you have access to Twitter API, let's write a script called "import.pl" to import tweets.

## File: import.pl

use strict;
use warnings;
use Net::Twitter::Lite::WithAPIv1_1;
use JSON;

my $nt = Net::Twitter::Lite::WithAPIv1_1->new(
    consumer_key        => "API_KEY",
    consumer_secret     => "API_SECRET",
    access_token        => "ACCESS_TOKEN",
    access_token_secret => "ACCESS_TOKEN_SECRET",
    ssl                 => 1,
);
print encode_json $nt->home_timeline;

Replace the four tokens above with yours. This simple script imports statuses from your home timeline, encodes them in JSON and prints them to STDOUT.

To run the script, you need Net::Twitter::Lite and JSON modules, so install them.

$ cpanm Net::Twitter::Lite JSON

OK, now you can run it.

You can directly input statuses from Twitter to BusyBird. No conversion is necessary.

$ perl import.pl | curl -d @- -H 'Content-Type: application/json' http://127.0.0.1:5000/timelines/home/statuses.json

After that, you can see the Twitter statuses on BusyBird.

Try repeating the command above. You will see that BusyBird only accepts the new statuses that are not yet in BusyBird's timeline. In a BusyBird timeline, all statuses must have unique id field. If you input a status that is already in the timeline, that status is ignored.

This means you can repeat the above command without worrying about duplicate statuses. Register the command with cron, then the BusyBird timeline is automatically synchronized to your Twitter timeline. If you are wondering how frequently you should run the command, Net::Twitter::Loader may help.

Acked/Unacked States of Statuses

BusyBird maintains read/unread states of individual statuses.

If you read statuses in BusyBird via a Web browser, those statuses are marked as "read". When new statuses are input to BusyBird, it shows the number of those "unread" statuses.

In the rest of the document and throughout BusyBird API, we use the terms "acked/unacked" instead of "read/unread". This is because we want to distinguish verbs from adjectives. The verb "ack" or "acknowledge" means "mark as read".

Status Level

BusyBird renders statuses based on their status "levels".

The status level is an integer value to indicate the importance of the status. The higher the level is, the more important the status is. By default, all statuses are on level 0.

To demonstrate status levels, let's improve the "import.pl" above. As a friendly person, you want to reply to strangers on Twitter, right? To do that, you have to watch "mentions timeline", too.

## File: import2.pl

use strict;
use warnings;
use Net::Twitter::Lite::WithAPIv1_1;
use JSON;

my $nt = Net::Twitter::Lite::WithAPIv1_1->new(
    consumer_key        => "API_KEY",
    consumer_secret     => "API_SECRET",
    access_token        => "ACCESS_TOKEN",
    access_token_secret => "ACCESS_TOKEN_SECRET",
    ssl                 => 1,
);
my $home = $nt->home_timeline;
my $mentions = $nt->mentions;

foreach my $s (@$mentions) {
    $s->{busybird}{level} = 1;
}

print encode_json [@$mentions, @$home];

The above script imports "home timeline" and "mentions timeline". The busybird.level field of "mentions" statuses is set to 1. Then, it outputs both kinds of statuses to STDOUT.

Like we did above, mixing multiple Twitter timelines into a single BusyBird timeline is OK. BusyBird sorts those statuses and renders them in chronological order.

Let's save the above script as "import2.pl" and run it.

$ perl import2.pl | curl -d @- -H 'Content-Type: application/json' http://127.0.0.1:5000/timelines/home/statuses.json

If you have ever been mentioned by someone, you'll see the statuses metioning you with the status level 1.

Change the level threshold by the buttons at the top-right corner. If you set to the threshold to "Lv. 1", it shows the "mention" statuses only, and hides everything else. That way, you can quickly review the mentions and reply to them.

You can use arbitrary integer values for the status level (busybird.level field), including negative values.

Configuration

So far, we use BusyBird with its default configuration, but you can customize its behavior by writing a configuration file.

BusyBird configuration file is ~/.busybird/config.psgi. By default, it looks like:

use BusyBird;
timeline("home");
end;

config.psgi is a Perl script, so you can write arbitrary Perl codes into it. However, here is the basic rule: you must write your config between "use BusyBird;" and "end;" statements. Follow this rule unless you know what you are doing.

Configure More Than One Timelines

You can have more than one timelines in a single BusyBird instance.

To create a timeline, just add a line

timeline("foobar");

which creates a timeline named "foobar".

To input statuses to the "foobar" timeline, try

$ perl import2.pl | curl -d @- http://127.0.0.1:5000/timelines/foobar/statuses.json

Timeline's names must be unique. So if you repeat calling timeline() function with the same name,

timeline("foobar");
timeline("foobar");

it creates only one timeline named "foobar".

Configuration Parameters

You can set various config parameters by set_config() method.

To set a global config parameter, use busybird->set_config(...).

busybird->set_config(time_zone => "UTC");

A global config parameter affects all timelines and the overall behavior of the BusyBird instance. In the above example, status timestamps in all timelines are rendered in UTC time zone.

set_config() method accepts more than one key-value pairs.

busybird->set_config(
    time_zone   => "+0900",
    time_locale => "ja_JP"
);

Some parameters are per-timeline config parameters, which can be set to individual timelines.

To set a per-timeline parameter, use timeline(...)->set_config(...).

busybird->set_config(time_zone => "UTC");

timeline("foobar")->set_config(time_zone => "+0900");

Per-timeline config parameters always take precedence over global ones. So, in the above example, statuses are renderred in "+0900" time zone only in the timeline "foobar". In other timelines, the time zone is "UTC".

See BusyBird::Manual::Config for the complete list of config parameters.

Filters

You can set status filters to timelines.

A status filter is a function that is executed when you input statuses into a timeline. You can modify the input statuses with the filter before they are stored into the timeline.

[ Statuses ] --HTTP POST--> [ Filter 1 ] --> [ Filter 2 ] --> ... --> [ Timeline ]

By default, timelines have no filters. Statuses are directly input to them.

To add a status filter to a timeline, use add_filter() method.

timeline("home")->add_filter(sub {
    my ($statuses) = @_;
    foreach my $status (@$statuses) {
        if($status->{text} =~ /\@my_name/) {
            $status->{busybird}{level}++;
        }
    }
    return $statuses;
});

A status filter is just a subroutine reference in Perl. It is called like

$result_arrayref = $filter->($arrayref_of_statuses)

where $arrayref_of_statuses is an array-ref of input statuses.

In the above example, the filter inspects each status's text field. If it finds your screen name ("@my_name"), it increments the status's level, meaning that it's important. Then the filter returns the array of modified statuses.

A timeline can have more than one filters. Those filters are executed in the order they are added, and the output of one filter becomes the input of the next filter.

timeline("home")->add_filter(sub {
    my ($statuses) = @_;
    foreach my $status (@$statuses) {
        if($status->{text} =~ /\@my_name/) {
            $status->{busybird}{level}++;
        }
    }
    return $statuses;
});
timeline("home")->add_filter(sub {
    my ($statuses) = @_;
    return [grep { $_->{busybird}{level} > 4 } @$statuses];
});

In the above example, the second filter extracts statuses whose level is higher than 4.

Inspecting statuses one by one is a typical pattern in status filters. BusyBird::Filter defines some functions useful for that purpose.

See BusyBird::Timeline and BusyBird::Filter for more about status filters.

Pre-defined Filter for Twitter

We have a pre-defined status filter named BusyBird::Filter::Twitter for statuses imported from Twitter.

timeline("home");

use BusyBird::Filter::Twitter qw(:filter);
timeline("home")->add_filter(filter_twitter_all);

It's not mandatory to use this filter, but we recommend it. The filter fixes a bug where the status text is HTML-escaped twice.

For detail, see BusyBird::Filter::Twitter.

What's Next?

For advanced and more detailed topics, you may find the following documents interesting.

BusyBird::Manual::WebAPI

Reference manual of BusyBird Web API, including endpoints that get, post or ack statuses.

BusyBird::Manual::Status

Object structure of BusyBird statuses.

BusyBird::Manual::Config

Full list of configuration items.

BusyBird::Filter

Functions useful when writing status filters.

BusyBird::Main

The class of the object returned by busybird keyword in config.psgi.

BusyBird::Timeline

The class of the object returned by timeline(...) keyword in config.psgi. It has various methods to manipuate statuses in it.

AUTHOR

Toshio Ito <toshioito [at] cpan.org>