This is part two of our Twitter List API tutorial within our broader Twitter API series. In this tutorial, we'll walk through building List features with the API. Code samples are provided in the Github repository. Installation instructions are described in more detail here (be sure to use the repository provided in this tutorial and not the initial Birdcage repository listed on that page).
Overview of the Twitter List API
Again, there are roughly 19 APIs for Twitter Lists, divided into three major areas:
Let's begin with integrating some of the basic features of List API development.
Creating a List
When you load Birdcage, you'll see the Manage Lists page. Click the Create a List option in the right sidebar menu.
The Create a List form will be displayed. I'm going to create a list for tracking Seattle journalists on Twitter:
Here's the Create code in TwitterlistController.php
. It handles code for displaying the form and, after the form is posted, code for processing:
public function actionCreate() { $model=new TwitterList; $this->performAjaxValidation($model); if(!isset($_POST['TwitterList'])) { // initial load // load the current selected account as a default $model->account_id = Yii::app()->session['account_id']; // display form $this->render('create',array( 'model'=>$model, )); } else { // POSTed - process form $model->attributes=$_POST['TwitterList']; // load the account selected $account = Account::model()->findByPK($model->account_id); // connect to twitter $twitter = Yii::app()->twitter->getTwitterTokened($account['oauth_token'], $account['oauth_token_secret']); // create remotely at Twitter $new_list= $twitter->post("lists/create",array('name'=>$model->name,'description'=>$model->description,'mode'=>$model->getModeString($model->mode))); if (TwitterList::model()->isError($new_list)) { // to do - set flash error var_dump($new_list); yexit(); } else { $model->owner_id =$account->twitter_id; $model->list_id =$new_list->id_str; $model->slug=$new_list->slug; $model->full_name=$new_list->full_name; $model->created_at = date( 'Y-m-d H:i:s', strtotime($new_list->created_at) ); $model->modified_at =new CDbExpression('NOW()'); if($model->save()) $this->redirect(array('admin')); } } }
You can read documentation for the Twitter Lists/Create API here.
Once the form is submitted, you'll see something like this:
Importing Members to a List
One of the most frustrating limitations of the Twitter List user interface is how difficult it is to add members. You have to visit each member and add them individually to your lists—the process is very convoluted and slow.
We're going to implement a feature to subscribe a comma-separated list of Twitter accounts.
In the list view, if you click on the Manage List icon to the right of Seattle Journalists, you'll see the View List page:
Click on Import members in the right sidebar menu. Then, type in the list of Twitter accounts you wish to add to this list:
Let's review the code that uploads the members to Twitter. The code for displaying the form above begins in ListMemberController.php
:
/** * Import members to a list */ public function actionImport($id) { $model = new Import(); $model->list_id = $id; // Uncomment the following line if AJAX validation is needed $this->performAjaxValidation($model); if(isset($_POST['Import'])) { if($model->save()) { $result = ListMember::model()->import($id,$_POST['Import']['member_list']); Yii::app()->user->setFlash('import_success','Thank you! Your members have been added.'); $this->redirect(array('/twitterlist/view','id'=>$id)); } // end if save // end if post } else { $this->render('import',array( 'model'=>$model,'list_id'=>$id, )); } }
In the ListMember.php
model, the import code looks as follows. We use preg_split
to convert the list of accounts to an array. Then, for each account, we fetch information about the account at Users/Show and post the Twitter ID to Lists Member Create.
public function import($id,$import_list) { // retrieve account $tl = TwitterList::model()->findByAttributes(array('id'=>$id)); $list_id = $tl['list_id']; $account = Account::model()->findByPk($tl->account_id); // retrieve members and add to list $twitter = Yii::app()->twitter->getTwitterTokened($account['oauth_token'], $account['oauth_token_secret']); // convert post rows to array $add_list = preg_split ("(\r|\n|,)", $import_list, -1, PREG_SPLIT_NO_EMPTY); $max_count = 0; foreach ($add_list as $item) { $item = trim($item); $user_info= $twitter->get("users/show",array('screen_name'=>$item)); if (ErrorLog::model()->isError('getUserInfo', $account['id'], $user_info)) { continue; } if (ErrorLog::model()->isRateLimited($user_info)) { continue; } // add remotely to list $people= $twitter->post("lists/members/create",array('list_id'=>$list_id,'screen_name'=>$item)); // add locally to db $this->remote_add($list_id,$user_info->id_str,$item); $max_count+=1; if ($max_count>=99) break; } } public function remote_add($list_id,$member_id,$screen_name = 'tbd') { TwitterUser::model()->setPlaceholder($member_id,$screen_name); $lm = ListMember::model()->findByAttributes(array('list_id'=>$list_id,'member_id'=>$member_id)); if (empty($lm)) { $lm = new ListMember; $lm->list_id=$list_id; $lm->member_id=$member_id; $lm->save(); } }
After submitting the members, you'll see something like this:
The tbd
represents a user that isn't already in our database; the profile information will be fetched (hydrated) in the background later.
On Twitter, you'll see something like this:
Synchronizing Lists
Now that we've created a list, let's fetch the lists that already exist in our account so that we can manage them from our console. Again, from the Manage Lists menu, click Synchronize Lists.
The sync action is coded in TwitterlistController.php
:
public function actionSync() { TwitterList::model()->sync(); $this->redirect(array('admin')); }
The TwitterList.php
model sync operation is relatively sophisticated, as shown below. First, the sync model loops through all of the accounts you've configured and calls the syncOne
account for lists.
SyncOne calls fetch Lists/Ownerships in the API to find all the lists owned by each account.
public function sync() { $users = User::model()->findAll(); foreach ($users as $user) { $user_id = $user['id']; $accounts = Account::model()->findAllByAttributes(array('user_id'=>$user_id)); // loop through Twitter accounts (may be multiple) foreach ($accounts as $account) { $this->syncOne($account['id']); } // end account loop } // end user loop } public function syncOne($account_id) { $account=Account::model()->findByPk($account_id); $twitter = Yii::app()->twitter->getTwitterTokened($account['oauth_token'], $account['oauth_token_secret']); // fetch lists owned by this account $twitter_lists= $twitter->get("lists/ownerships",array('count'=>100,'cursor'=>-1)); //print_r($twitter_lists); if (count($twitter_lists->lists)==0) return; foreach ($twitter_lists->lists as $tl) { //echo $tl->id_str.' '.$tl->slug.' '.$tl->member_count;lb(); $this->remote_add($account_id,$tl); // spawn action to get list members $this->addMembershipAction($account_id,$tl->id_str); } // end loop of lists }
However, rather than try to fetch the members for the list in real time, which would likely time out, it creates a background action to fetch the members for a given list.
public function addMembershipAction($account_id,$item_id) { // adds a background task action to retrieve memberships for a list id $check_dup = Action::model()->findByAttributes(array('account_id'=>$account_id,'action'=>Action::ACTION_MEMBERSHIPS,'status'=>Action::STATUS_ACTIVE,'item_id'=>$item_id)); if (empty($check_dup)) { $a = new Action(); $a->account_id = $account_id; $a->action = Action::ACTION_MEMBERSHIPS; $a->item_id = $item_id; $a->last_tweet_id = 0; // set cursor $a->status = Action::STATUS_ACTIVE; $a->created_at =new CDbExpression('NOW()'); $a->modified_at =new CDbExpression('NOW()'); $a->save(); } }
The background cron task invokes the Action.php
model which calls getListMembership
for one list at a time.
public function getListMembership($action,$limit = 50) { // collect next $limit members of list $account = Account::model()->findByPk($action->account_id); // last_tweet_id is the cursor $cursor = $action->last_tweet_id; if ($cursor ==0 ) $cursor =-1; // since last_tweet_id is unsigned, can't store -1 start $result = TwitterList::model()->getMemberships($account, $action->item_id, $cursor , $limit); $a = Action::model()->findByPk($action->id); if ($result->rateLimit) { return false; } else if ($result->complete) { $a->status=self::STATUS_COMPLETE; $a->save(); } else { // set lowest cursor $a->last_tweet_id = $result->cursor; $a->save(); } }
When the task runs, it invokes the TwitterList.php
model's getMemberships
method. This method uses cursoring to page through member records without timing out or hitting rate limits. The last cursor is stored in the Action table so that the next background invocation can continue adding members where it left off previously.
public function getMemberships($account,$list_id, $cursor =-1,$limit = 200) { echo 'entering getMemberships: account_id:'.$account['id'].' list_id:'.$list_id.' cursor: '.$cursor;lb(); $result = new StdClass; $result->cursor = -1; $result->count =0; $result->error =false; $result->complete = false; $result->rateLimit = false; $count_people = 0; // retrieve members and add to list $twitter = Yii::app()->twitter->getTwitterTokened($account['oauth_token'], $account['oauth_token_secret']); echo 'here'.$cursor;lb(); while ($cursor <>0 ) { echo 'inside';lb(); $people= $twitter->get("lists/members",array('list_id'=>$list_id,'cursor'=>$cursor,'skip_status'=>true,'include_entities'=>false)); if (ErrorLog::model()->isError('getMemberships', $account['id'], $people)) { $result->error =false; return $result; } if (ErrorLog::model()->isRateLimited($people)) { $result->rateLimit = true; return $result; } if (isset($people->next_cursor)) $cursor = $people->next_cursor; else $cursor = 0; $result->cursor = $cursor; $count_people+=count($people->users); echo 'Count people: '.count($people->users);lb(); foreach ($people->users as $u) { //var_dump($u);lb(); echo 'Member:'.$u->screen_name;lb(); if (isset($u->screen_name)) $screen_name = $u->screen_name; else $screen_name = 'tbd'; ListMember::model()->remote_add($list_id,$u->id_str,$screen_name); } if (count($people->users)==0 or $cursor==0) { $result->complete = true; return $result; } } // end while loop echo 'exiting getMemberships';lb(); return $result; }
You should see something like this when the operation completes:
Once the background tasks complete, you'll see members for each list like this:
What's Next?
I hope you've enjoyed this Twitter List API series thus far. I'd like to hear your suggestions of favorite API scenarios and feature requests; please post them in the comments.
If you have any questions or suggestions, please post them in the comments. If you'd like to keep up on my future Tuts+ tutorials and other series, please visit my instructor page or follow @reifman.
Comments