PageRenderTime 56ms CodeModel.GetById 2ms app.highlight 47ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/TwitterApiClient.class.php

http://github.com/n1k0/phptwitterbot
PHP | 815 lines | 376 code | 93 blank | 346 comment | 113 complexity | f8c0f58ef6de3f2c6ba22901ada9daff MD5 | raw file
  1<?php
  2require_once dirname(__FILE__).'/TwitterApiServer.class.php';
  3require_once dirname(__FILE__).'/Tweet.class.php';
  4require_once dirname(__FILE__).'/TweetCollection.class.php';
  5require_once dirname(__FILE__).'/TwitterDirectMessage.class.php';
  6require_once dirname(__FILE__).'/TwitterDirectMessageCollection.class.php';
  7require_once dirname(__FILE__).'/TwitterUser.class.php';
  8require_once dirname(__FILE__).'/TwitterUserCollection.class.php';
  9
 10/**
 11 * Twitter api client
 12 * 
 13 * Credits:
 14 *  - Some parts of the code are based on the work of Tijs Verkoyen on the PHPTwitter project (BSD licensed)
 15 *
 16 * @author  Nicolas Perriault <nperriault@gmail.com>
 17 * @license MIT License
 18 */
 19class TwitterApiClient
 20{
 21  protected 
 22    $debug     = false,
 23    $server    = null,
 24    $userAgent = 'PHPTwitterBot (http://code.google.com/p/phptwitterbot/)';
 25
 26  /**
 27   * Default constructor
 28   *
 29   * @param  TwitterApiServer\null  $server
 30   * @param  Boolean                $debug
 31   */
 32  public function __construct(TwitterApiServer $server = null, $debug = false)
 33  {
 34    if (is_null($server))
 35    {
 36      // Default server configuration
 37      $server = new TwitterApiServer('http://twitter.com', array(
 38        'userAgent' => $this->getUserAgent(),
 39        'httpPort'  => 80,
 40        'timeOut'   => 30,
 41      ));
 42    }
 43    
 44    $this->debug = $debug;
 45    $this->server = $server;
 46  }
 47
 48  /**
 49   * Make the call
 50   *
 51   * @param  string $url           API url to call
 52   * @param  array  $parameters    Parameters for the request
 53   * @param  bool   $authenticate  Shall we use authentication?
 54   * @param  bool   $usePost       Uses POST method instead of GET
 55   *
 56   * @return mixed
 57   *
 58   * @throws InvalidArgumentException   if the type provided is not supported
 59   * @throws TwitterApiServerException  if the request fails for any reason
 60   * @throws RuntimeException           if the xml response is invalid
 61   */
 62  protected function doCall($url, $parameters = array(), $authenticate = false, $usePost = true, $type = 'entity')
 63  {
 64    $response = $this->server->request(sprintf('%s.xml', $url), $parameters, $usePost ? 'POST' : 'GET');
 65 
 66    switch ($type)
 67    {
 68      case 'entity':
 69        $dom = new DOMDocument();
 70        $dom->loadXML($response);
 71        return TwitterEntity::createFromXml($dom);
 72      break;
 73      
 74      case 'boolean':
 75        if (!$xml = @simplexml_load_string($response))
 76        {
 77          throw new RuntimeException('XML error');
 78        }
 79        return (bool) ((string) $xml === 'true');  
 80      break;
 81        
 82      case 'hash':
 83        if (!$xml = @simplexml_load_string($response))
 84        {
 85          throw new RuntimeException('XML error');
 86        }
 87        return (array) $xml;  
 88      break;
 89      
 90      case 'search_results':
 91        return TweetCollection::createFromJSON($response);
 92      break;
 93      
 94      default:
 95        throw new InvalidArgumentException(sprintf('Type "%s" is not supported', $type));  
 96      break;
 97    }
 98  }
 99  
100  /**
101   * Search in public or friends timeline for tweets. We cannot use the doCall method
102   * because twitter doesn't provide XML format for searches, and will therefore force us to use
103   * JSON.
104   *
105   * Available options:
106   *
107   *  - source: can be 'public' or 'friends'
108   *  - max:    Number of items to retrieve
109   *  - page:   Page number to query
110   *
111   * @param  string  $terms    Search terms string
112   * @param  array   $options  Options
113   *
114   * @return TweetCollection
115   *
116   * @throws InvalidArgumentException if unsupported source name
117   */
118  public function search($terms, array $options = array())
119  {
120    $options = array_merge(array('source' => 'public', 'max' => 15, 'page' => 1), $options);
121    
122    if (!in_array($options['source'], array('public', 'friends')))
123    {
124      throw new InvalidArgumentException(sprintf('Source "%s" is not supported', $source));
125    }
126    
127    if ('public' === $options['source'])
128    {
129      $parameters = array('q' => $terms, 'page' => $options['page'], 'rpp' => $options['max']);
130      
131      return $this->doCall('search', $parameters, false, false, 'search_results');
132    }
133    else
134    {
135      $results = array();
136      
137      foreach ($this->getFriendsTimeline(null, null, 200) as $tweet)
138      {
139        if (preg_match(sprintf('/%s/i', $terms), $tweet->text))
140        {
141          $results[] = $tweet;
142        }
143      }
144      
145      return new TweetCollection($results);
146    }
147  }
148  
149  /**
150   * Returns the 20 most recent statuses from non-protected users who have set a custom user icon.
151   *
152   * Note that the public timeline is cached for 60 seconds so requesting it more often than that 
153   * is a waste of resources.
154   *
155   * @return TweetCollection
156   */
157  public function getPublicTimeline()
158  {
159    return $this->doCall('statuses/public_timeline');
160  }
161
162  /**
163   * Returns the 20 most recent statuses posted by the authenticating user and that user's friends.
164   * This is the equivalent of /home on the Web.
165   *
166   * @param  int  $since    Narrows the returned results to just those statuses created after the specified UNIX-timestamp, up to 24 hours old.
167   * @param  int  $sinceId  Returns only statuses with an id greater than (that is, more recent than) the specified $sinceId.
168   * @param  int  $count    Specifies the number of statuses to retrieve. May not be greater than 200.
169   * @param  int  $page
170   *
171   * @return TweetCollection
172   */
173  public function getFriendsTimeline($since = null, $sinceId = null, $count = null, $page = null)
174  {
175    // validate parameters
176    if (!is_null($since) && (int) $since <= 0) throw new TwitterApiClientException('Invalid timestamp for since.');
177    if (!is_null($sinceId) && (int) $sinceId <= 0) throw new TwitterApiClientException('Invalid value for sinceId.');
178    if (!is_null($count) && (int) $count > 200) throw new TwitterApiClientException('Count can\'t be larger then 200.');
179
180    // build url
181    $parameters = array();
182    if (!is_null($since)) $parameters['since'] = date('r', (int) $since);
183    if (!is_null($sinceId)) $parameters['since_id'] = (int) $sinceId;
184    if (!is_null($count)) $parameters['count'] = (int) $count;
185    if (!is_null($page)) $parameters['page'] = (int) $page;
186
187    return $this->doCall('statuses/friends_timeline', $parameters, true, false);
188  }
189
190  /**
191   * Returns the 20 most recent statuses posted from the authenticating user. 
192   *
193   * It's also possible to request another user's timeline via the id parameter below.
194   * This is the equivalent of the Web /archive page for your own user, or the profile page for a third party.
195   *
196   * @param  string  $id       Specifies the id or screen name of the user for whom to return the friends_timeline.
197   * @param  int     $since    Narrows the returned results to just those statuses created after the specified UNIX-timestamp, up to 24 hours old
198   * @param  int     $sinceId  Returns only statuses with an id greater than (that is, more recent than) the specified $sinceId.
199   * @param  int     $count    Specifies the number of statuses to retrieve. May not be greater than 200.
200   * @param  int     $page     Page number
201   *
202   * @return TweetCollection
203   */
204  public function getUserTimeline($id = null, $since = null, $sinceId = null, $count = null, $page = null)
205  {
206    // validate parameters
207    if (!is_null($since) && (int) $since <= 0) throw new TwitterApiClientException('Invalid timestamp for since.');
208    if (!is_null($sinceId) && (int) $sinceId <= 0) throw new TwitterApiClientException('Invalid value for sinceId.');
209    if (!is_null($count) && (int) $count > 200) throw new TwitterApiClientException('Count can\'t be larger then 200.');
210
211    // build parameters
212    $parameters = array();
213    if (!is_null($since)) $parameters['since'] = date('r', (int) $since);
214    if (!is_null($sinceId)) $parameters['since_id'] = (int) $sinceId;
215    if (!is_null($count)) $parameters['count'] = (int) $count;
216    if (!is_null($page)) $parameters['page'] = (int) $page;
217
218    // build url
219    $url = 'statuses/user_timeline';
220
221    if (!is_null($id))
222    {
223      $url = 'statuses/user_timeline/'.urlencode($id);
224    }
225
226    return $this->doCall($url, $parameters, true, false);
227  }
228
229  /**
230   * Returns a single status, specified by the id parameter below.
231   *
232   * @param  int $id  The numerical id of the status you're trying to retrieve.
233   *
234   * @return Tweet
235   */
236  public function getStatus($id)
237  {
238    return $this->doCall('statuses/show/'.urlencode($id));
239  }
240  
241  /**
242   * Checks if a given status has already been published recently
243   *
244   * @param  string  $status  Status text
245   * @param  int     $max     Number of existing statuses to check
246   *
247   * @return Booleanean
248   */
249  public function isDuplicateStatus($status, $max = 1)
250  {
251    foreach ($this->getUserTimeline() as $tweet)
252    {
253      if (trim(strtolower($tweet->text)) == trim(strtolower($status)))
254      {
255        return true;
256      }
257    }
258    
259    return false;
260  }
261
262  /**
263   * Updates the authenticating user's status.
264   * A status update with text identical to the authenticating user's current status will be ignored.
265   *
266   * @param  string  $status       The text of your status update. Should not be more than 140 characters.
267   * @param  int     $inReplyToId  The id of an existing status that the status to be posted is in reply to.
268   *
269   * @return Tweet
270   */
271  public function updateStatus($status, $inReplyToId = null)
272  {
273    if (mb_strlen($status) > 140)
274    {
275      throw new TwitterApiClientException('Maximum 140 characters allowed for status.');
276    }
277
278    $parameters = array('status' => $status);
279    
280    if (!is_null($inReplyToId))
281    {
282      $parameters['in_reply_to_status_id'] = $inReplyToId;
283    }
284
285    return $this->doCall('statuses/update', $parameters, true);
286  }
287
288  /**
289   * Returns the 20 most recent @replies (status updates prefixed with @username) for the authenticating user.
290   *
291   * @param  int  $since    Narrows the returned results to just those replies created after the specified UNIX-timestamp, up to 24 hours old.
292   * @param  int  $sinceId  Returns only statuses with an id greater than (that is, more recent than) the specified $sinceId.
293   * @param  int  $page
294   *
295   * @return TweetCollection
296   */
297  public function getReplies($since = null, $sinceId = null, $page = null)
298  {
299    if (!is_null($since) && (int) $since <= 0) throw new TwitterApiClientException('Invalid timestamp for since.');
300    if (!is_null($sinceId) && (int) $sinceId <= 0) throw new TwitterApiClientException('Invalid value for sinceId.');
301
302    $parameters = array();
303    if (!is_null($since)) $parameters['since'] = date('r', (int) $since);
304    if (!is_null($sinceId)) $parameters['since_id'] = (int) $sinceId;
305    if (!is_null($page)) $parameters['page'] = (int) $page;
306
307    return $this->doCall('statuses/replies', $parameters, true, false);
308  }
309
310  /**
311   * Destroys the status specified by the required $id parameter.
312   * The authenticating user must be the author of the specified status.
313   *
314   * @param  int $id
315   *
316   * @return Tweet
317   */
318  public function deleteStatus($id)
319  {
320    return $this->doCall('statuses/destroy/'.urlencode($id), array('id' => $id), true);
321  }
322
323  /**
324   * Returns up to 100 of the authenticating user's friends who have most recently updated.
325   * It's also possible to request another user's recent friends list via the $id parameter.
326   *
327   * @param  string $id  The id or screen name of the user for whom to request a list of friends.
328   * @param  int $page
329   *
330   * @return TwitterUserCollection
331   */
332  public function getFriends($id = null, $page = null)
333  {
334    $parameters = array();
335    
336    if (!is_null($page))
337    {
338      $parameters['page'] = (int) $page;
339    }
340
341    $url = 'statuses/friends';
342    
343    if (!is_null($id))
344    {
345      $url = 'statuses/friends/'.urlencode($id);
346    }
347
348    return $this->doCall($url, $parameters, true, false);
349  }
350
351  /**
352   * Returns the authenticating user's followers.
353   *
354   * @param  string  $id    The id or screen name of the user for whom to request a list of followers.
355   * @param  int     $page
356   *
357   * @return TwitterUserCollection
358   */
359  public function getFollowers($id = null, $page = null)
360  {
361    $parameters = array();
362    
363    if (!is_null($page))
364    {
365      $parameters['page'] = (int) $page;
366    }
367
368    $url = 'statuses/followers';
369    
370    if (!is_null($id))
371    {
372      $url = 'statuses/followers/'.urlencode($id);
373    }
374
375    return $this->doCall($url, $parameters, true, false);
376  }
377
378  /**
379   * Returns extended information of a given user, specified by id or screen name.
380   * This information includes design settings, so third party developers can theme their widgets according to a given user's preferences.
381   * You must be properly authenticated to request the page of a protected user.
382   *
383   * @param  string  $id     The id or screen name of a user.
384   * @param  string  $email  May be used in place of $id.
385   *
386   * @return TwitterUser
387   */
388  public function getUser($id)
389  {
390    return $this->doCall('users/show/'.urlencode($id), array('id' => $id), true, false);
391  }
392
393  /**
394   * Returns a direct message. This method of the twitter API is not documented but exists though.
395   *
396   * @param  int  $id  Direct message id
397   *
398   * @return TwitterDirectMessage
399   */
400  public function getDirectMessage($id)
401  {
402    return $this->doCall('direct_messages/show/'.urlencode($id), array('id' => $id), true, false);
403  }
404
405
406  /**
407   * Returns a list of the 20 most recent direct messages sent to the authenticating user.
408   *
409   * @param  int  $since    Narrows the resulting list of direct messages to just those sent after the specified UNIX-timestamp, up to 24 hours old.
410   * @param  int  $sinceId  Returns only direct messages with an id greater than (that is, more recent than) the specified $sinceId.
411   * @param  int  $page
412   *
413   * @return TwitterDirectMessageCollection
414   */
415  public function getDirectMessages($since = null, $sinceId = null, $page = null)
416  {
417    if (!is_null($since) && (int) $since <= 0) throw new TwitterApiClientException('Invalid timestamp for since.');
418    if (!is_null($sinceId) && (int) $sinceId <= 0) throw new TwitterApiClientException('Invalid value for sinceId.');
419
420    $parameters = array();
421    if (!is_null($since)) $parameters['since'] = date('r', (int) $since);
422    if (!is_null($sinceId)) $parameters['since_id'] = (int) $sinceId;
423    if (!is_null($page)) $parameters['page'] = (int) $page;
424
425    return $this->doCall('direct_messages', $parameters, true, false);
426  }
427
428  /**
429   * Returns a list of the 20 most recent direct messages sent by the authenticating user.
430   *
431   * @param  int  $since    Narrows the resulting list of direct messages to just those sent after the specified UNIX-timestamp, up to 24 hours old.
432   * @param  int  $sinceId  Returns only sent direct messages with an id greater than (that is, more recent than) the specified $sinceId.
433   * @param  int  $page
434   *
435   * @return TwitterDirectMessageCollection
436   */
437  public function getSentDirectMessages($since = null, $sinceId = null, $page = null)
438  {
439    if (!is_null($since) && (int) $since <= 0) throw new TwitterApiClientException('Invalid timestamp for since.');
440    if (!is_null($sinceId) && (int) $sinceId <= 0) throw new TwitterApiClientException('Invalid value for sinceId.');
441
442    $parameters = array();
443    if (!is_null($since)) $parameters['since'] = date('r', (int) $since);
444    if (!is_null($sinceId)) $parameters['since_id'] = (int) $sinceId;
445    if (!is_null($page)) $parameters['page'] = (int) $page;
446
447    return $this->doCall('direct_messages/sent', $parameters, true, false);
448  }
449
450  /**
451   * Sends a new direct message to the specified user from the authenticating user.
452   *
453   * @param  string  $id    The id or screen name of the recipient user.
454   * @param  string  $text  The text of your direct message. Keep it under 140 characters.
455   *
456   * @return TwitterDirectMessage
457   */
458  public function sendDirectMessage($id, $text)
459  {
460    if (mb_strlen($text) > 140)
461    {
462      throw new TwitterApiClientException('Maximum 140 characters allowed for status.');
463    }
464
465    return $this->doCall('direct_messages/new', array('user' => $id, 'text' => $text), true);
466  }
467
468  /**
469   * Destroys the direct message.
470   * The authenticating user must be the recipient of the specified direct message.
471   *
472   * @param  string $id
473   *
474   * @return TwitterDirectMessage
475   */
476  public function deleteDirectMessage($id)
477  {
478    return $this->doCall('direct_messages/destroy/'.urlencode($id), array('id' => $id), true);
479  }
480
481  /**
482   * Befriends the user specified in the id parameter as the authenticating user.
483   *
484   * @param  string $id      The id or screen name of the user to befriend.
485   * @param  bool   $follow  Enable notifications for the target user in addition to becoming friends.
486   *
487   * @return TwitterUser
488   */
489  public function createFriendship($id, $follow = true)
490  {
491    $parameters = array('id' => $id);
492    
493    if ($follow)
494    {
495      $parameters['follow'] = $follow;
496    }
497
498    return $this->doCall('friendships/create/'.urlencode($id), $parameters, true);
499  }
500
501  /**
502   * Discontinues friendship with the user.
503   *
504   * @param  string $id
505   */
506  public function deleteFriendship($id)
507  {
508    return $this->doCall('friendships/destroy/'.urlencode($id), array('id' => $id), true);
509  }
510
511  /**
512   * Tests if a friendship exists between two users.
513   *
514   * @param  string  $id        The id or screen_name of the first user to test friendship for.
515   * @param  string  $friendId  The id or screen_name of the second user to test friendship for.
516   */
517  public function existsFriendship($id, $friendId)
518  {
519    return $this->doCall('friendships/exists', array('user_a' => $id, 'user_b' => $friendId), true, false, 'boolean');
520  }
521
522  /**
523   * Verifies your credentials
524   * Use this method to test if supplied user credentials are valid.
525   *
526   * @return Boolean
527   */
528  public function verifyCredentials()
529  {
530    try
531    {
532      return $this->doCall('account/verify_credentials', array(), true) instanceof TwitterUser;
533    }
534    catch (Exception $e)
535    {
536      if ($e->getCode() == 401 || $e->getMessage() == 'Could not authenticate you.')
537      {
538        return false;
539      }
540      else
541      {
542        throw $e;
543      }
544    }
545  }
546
547  /**
548   * Sets values that users are able to set under the "Account" tab of their settings page.
549   * Only the parameters specified will be updated.
550   *
551   * @param  string  $name
552   * @param  string  $email
553   * @param  string  $url
554   * @param  string  $location
555   * @param  string  $description
556   *
557   * @return TwitterUser
558   */
559  public function updateProfile($name = null, $email = null, $url = null, $location = null, $description = null)
560  {
561    if ($name === null && $email === null && $url === null && $location === null && $description === null) throw new TwitterApiClientException('Specify at least one parameter.');
562    if (!is_null($name) && mb_strlen($name) > 40) throw new TwitterApiClientException('Maximum 40 characters allowed for name.');
563    if (!is_null($email) && mb_strlen($email) > 40) throw new TwitterApiClientException('Maximum 40 characters allowed for email.');
564    if (!is_null($url) && mb_strlen($url) > 100) throw new TwitterApiClientException('Maximum 100 characters allowed for url.');
565    if (!is_null($location) && mb_strlen($location) > 30) throw new TwitterApiClientException('Maximum 30 characters allowed for location.');
566    if (!is_null($description) && mb_strlen($description) > 160) throw new TwitterApiClientException('Maximum 160 characters allowed for description.');
567
568    $parameters = array();
569    if (!is_null($name)) $parameters['name'] = (string) $name;
570    if (!is_null($email)) $parameters['email'] = (string) $email;
571    if (!is_null($url)) $parameters['url'] = (string) $url;
572    if (!is_null($location)) $parameters['location'] = (string) $location;
573    if (!is_null($description)) $parameters['description'] = (string) $description;
574
575    return $this->doCall('account/update_profile', $parameters, true);
576  }
577
578  /**
579   * Sets one or more hex values that control the color scheme of the authenticating user's profile page on twitter.com.
580   * Only the parameters specified will be updated.
581   *
582   * @param  string  $backgroundColor
583   * @param  string  $textColor
584   * @param  string  $linkColor
585   * @param  string  $sidebarBackgroundColor
586   * @param  string  $sidebarBorderColor
587   *
588   * @return TwitterUser
589   */
590  public function updateProfileColors($backgroundColor = null, $textColor = null, $linkColor = null, $sidebarBackgroundColor = null, $sidebarBorderColor = null)
591  {
592    if ($backgroundColor === null && $textColor === null && $linkColor === null && $sidebarBackgroundColor === null && $sidebarBorderColor === null) throw new TwitterApiClientException('Specify at least one parameter.');
593    if (!is_null($backgroundColor) && (mb_strlen($backgroundColor) < 3 || mb_strlen($backgroundColor) > 6)) throw new TwitterApiClientException('Invalid color for background color.');
594    if (!is_null($textColor) && (mb_strlen($textColor) < 3 || mb_strlen($textColor) > 6)) throw new TwitterApiClientException('Invalid color for text color.');
595    if (!is_null($linkColor) && (mb_strlen($linkColor) < 3 || mb_strlen($linkColor) > 6)) throw new TwitterApiClientException('Invalid color for link color.');
596    if (!is_null($sidebarBackgroundColor) && (mb_strlen($sidebarBackgroundColor) < 3 || mb_strlen($sidebarBackgroundColor) > 6)) throw new TwitterApiClientException('Invalid color for sidebar background color.');
597    if (!is_null($sidebarBorderColor) && (mb_strlen($sidebarBorderColor) < 3 || mb_strlen($sidebarBorderColor) > 6)) throw new TwitterApiClientException('Invalid color for sidebar border color.');
598
599    $parameters = array();
600    if (!is_null($backgroundColor)) $parameters['profile_background_color'] = (string) $backgroundColor;
601    if (!is_null($textColor)) $parameters['profile_text_color'] = (string) $textColor;
602    if (!is_null($linkColor)) $parameters['profile_link_color'] = (string) $linkColor;
603    if (!is_null($sidebarBackgroundColor)) $parameters['profile_sidebar_fill_color'] = (string) $sidebarBackgroundColor;
604    if (!is_null($sidebarBorderColor)) $parameters['profile_sidebar_border_color'] = (string) $sidebarBorderColor;
605
606    return $this->doCall('account/update_profile_colors', $parameters, true);
607  }
608
609  /**
610   * Returns the remaining number of API requests available to the requesting user before 
611   * the API limit is reached for the current hour.
612   *
613   * @return array
614   */
615  public function getRateLimitStatus()
616  {
617    return $this->doCall('account/rate_limit_status', array(), true, false, 'hash');
618  }
619
620  /**
621   * Returns the 20 most recent favorite statuses for the authenticating user or user specified 
622   * by the $id parameter
623   *
624   * @param  string  $id    The id or screen name of the user for whom to request a list of favorite statuses.
625   * @param  int     $page
626   *
627   * @return TweetCollection
628   */
629  public function getFavorites($id = null, $page = null)
630  {
631    $parameters = array();
632    
633    if (!is_null($page))
634    {
635      $parameters['page'] = (int) $page;
636    }
637
638    $url = 'favorites';
639    
640    if (!is_null($id))
641    {
642      $url = 'favorites/'.urlencode($id);
643    }
644
645    return $this->doCall($url, $parameters, true, false);
646  }
647
648  /**
649   * Favorites the status specified in the id parameter as the authenticating user.
650   *
651   * @param  string $id
652   *
653   * @return Tweet
654   */
655  public function createFavorite($id)
656  {
657    return $this->doCall('favorites/create/'.urlencode($id), array('id' => $id), true);
658  }
659
660  /**
661   * Un-favorites the status specified in the id parameter as the authenticating user.
662   *
663   * @param  string $id
664   *
665   * @return Tweet
666   */
667  public function deleteFavorite($id)
668  {
669    return $this->doCall('favorites/destroy/'.urlencode($id), array('id' => $id), true);
670  }
671
672  /**
673   * Enables notifications for updates from the specified user to the authenticating user.
674   * This method requires the authenticated user to already be friends with the specified 
675   * user otherwise the error "there was a problem following the specified user" will be 
676   * returned.
677   *
678   * @param  string $id
679   *
680   * @return TwitterUser
681   */
682  public function followUser($id)
683  {
684    return $this->doCall('notifications/follow/'.urlencode($id), array('id' => $id), true);
685  }
686
687  /**
688   * Disables notifications for updates from the specified user to the authenticating user.
689   * This method requires the authenticated user to already be friends with the specified 
690   * user otherwise the error "there was a problem following the specified user" will be 
691   * returned.
692   *
693   * @param  string $id
694   */
695  public function unfollowUser($id)
696  {
697    return $this->doCall('notifications/leave/'.urlencode($id), array('id' => $id), true);
698  }
699
700  /**
701   * Blocks the user specified in the id parameter as the authenticating user.
702   *
703   * @param  string $id
704   */
705  public function blockUser($id)
706  {
707    return $this->doCall('blocks/create/'.urlencode($id), array('id' => $id), true);
708  }
709
710  /**
711   * Un-blocks the user specified in the id parameter as the authenticating user.
712   *
713   * @param  string $id
714   */
715  public function unblockUser($id)
716  {
717    return $this->doCall('blocks/destroy/'.urlencode($id), array('id' => $id), true);
718  }
719
720  /**
721   * Test the connection to Twitter
722   *
723   * @return Boolean
724   */
725  public function test()
726  {
727    return '<ok>true</ok>' === $this->doCall('help/test');
728  }
729
730  /**
731   * Returns the same text displayed on http://twitter.com/home when a maintenance window is scheduled.
732   *
733   * @return string
734   */
735  public function getDowntimeSchedule()
736  {
737    // make the call
738    $response = $this->doCall('help/downtime_schedule');
739
740    // convert into xml-object
741    $xml = simplexml_load_string($response);
742
743    // validate
744    if ($xml == false) throw new TwitterApiClientException('invalid body');
745    if (!isset($xml->error)) throw new TwitterApiClientException('invalid body');
746
747    // return
748    return (string) utf8_decode($xml->error);
749  }
750  
751  /**
752   * Get the useragent
753   *
754   * @return string
755   */
756  public function getUserAgent()
757  {
758    return $this->userAgent;
759  }
760  
761  /**
762   * Set the user-agent
763   * It will be appended to ours
764   *
765   * @param  string $userAgent
766   */
767  public function setUserAgent($userAgent)
768  {
769    $this->userAgent = $userAgent;
770  }
771
772  /**
773   * Get the username
774   *
775   * @return string
776   */
777  public function getUsername()
778  {
779    return $this->server->getUsername();
780  }
781  
782  /**
783   * Set username
784   *
785   * @param  string $username
786   */
787  public function setUsername($username)
788  {
789    $this->server->setUsername($username);
790  }
791  
792  /**
793   * Get the password
794   *
795   * @return string
796   */
797  public function getPassword()
798  {
799    return $this->server->getPassword();
800  }
801  
802  /**
803   * Set the password
804   *
805   * @param  string $password
806   */
807  public function setPassword($password)
808  {
809    $this->server->setPassword($password);
810  }
811}
812
813class TwitterApiClientException extends Exception
814{
815}