twitter.lib.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. <?php
  2. /**
  3. * @file
  4. * Classes to implement the full Twitter API
  5. */
  6. /**
  7. * Class TwitterConfig
  8. *
  9. * Singleton which stores common configuration
  10. * @see http://php.net/manual/en/language.oop5.patterns.php
  11. */
  12. class TwitterConf {
  13. private static $instance;
  14. private $attributes = array(
  15. 'host' => 'twitter.com',
  16. 'api' => 'api.twitter.com',
  17. 'search' => 'search.twitter.com',
  18. 'tiny_url' => 'tinyurl.com',
  19. );
  20. private function __construct() {}
  21. public static function instance() {
  22. if (!isset(self::$instance)) {
  23. $className = __CLASS__;
  24. self::$instance = new $className;
  25. }
  26. return self::$instance;
  27. }
  28. /**
  29. * Generic getter
  30. *
  31. * @param $attribute
  32. * string attribute name to return
  33. * @return
  34. * mixed value or NULL
  35. */
  36. public function get($attribute) {
  37. if (array_key_exists($attribute, $this->attributes)) {
  38. return $this->attributes[$attribute];
  39. }
  40. }
  41. /**
  42. * Generic setter
  43. * @param $attribute
  44. * string attribute name to be set
  45. * @param $value
  46. * mixed value
  47. */
  48. public function set($attribute, $value) {
  49. if (array_key_exists($attribute, $this->attributes)) {
  50. $this->attributes[$attribute] = $value;
  51. }
  52. }
  53. }
  54. /**
  55. * Exception handling class.
  56. */
  57. class TwitterException extends Exception {}
  58. /**
  59. * Primary Twitter API implementation class
  60. * Supports the full REST API for twitter.
  61. */
  62. class Twitter {
  63. /**
  64. * @var $format API format to use: can be json or xml
  65. */
  66. protected $format = 'json';
  67. /**
  68. * @var $source the twitter api 'source'
  69. */
  70. protected $source = 'drupal';
  71. /**
  72. * @var $username Twitter username to use for authenticated requests
  73. */
  74. protected $username;
  75. /**
  76. * @var $password Twitter password to use for authenticated requests
  77. */
  78. protected $password;
  79. /**
  80. * Constructor for the Twitter class
  81. */
  82. public function __construct($username = NULL, $password = NULL) {
  83. if (!empty($username) && !empty($password)) {
  84. $this->set_auth($username, $password);
  85. }
  86. }
  87. /**
  88. * Set the username and password
  89. */
  90. public function set_auth($username, $password) {
  91. $this->username = $username;
  92. $this->password = $password;
  93. }
  94. /**
  95. * Get an array of TwitterStatus objects from an API endpoint
  96. */
  97. protected function get_statuses($path, $params = array(), $use_auth = FALSE) {
  98. $values = $this->call($path, $params, 'GET', $use_auth);
  99. // Check on successfull call
  100. if ($values) {
  101. $statuses = array();
  102. foreach ($values as $status) {
  103. $statuses[] = new TwitterStatus($status);
  104. }
  105. return $statuses;
  106. }
  107. // Call might return FALSE , e.g. on failed authentication
  108. else {
  109. // As call allready throws an exception, we can return an empty array to
  110. // break no code.
  111. return array();
  112. }
  113. }
  114. /**
  115. * Fetch the public timeline
  116. *
  117. * @see http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-statuses-public_timeline
  118. */
  119. public function public_timeline() {
  120. return $this->get_statuses('statuses/public_timeline');
  121. }
  122. /**
  123. * Fetch the authenticated user's friends timeline
  124. *
  125. * @see http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-statuses-friends_timeline
  126. */
  127. public function friends_timeline($params = array()) {
  128. return $this->get_statuses('statuses/friends_timeline', $params, TRUE);
  129. }
  130. /**
  131. * Fetch a user's timeline
  132. *
  133. * @see http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-statuses-user_timeline
  134. */
  135. public function user_timeline($id, $params = array(), $use_auth = FALSE) {
  136. if (is_numeric($id)) {
  137. $params['user_id'] = $id;
  138. }
  139. else {
  140. $params['screen_name'] = $id;
  141. }
  142. return $this->get_statuses('statuses/user_timeline', $params, $use_auth);
  143. }
  144. /**
  145. *
  146. * @see http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-statuses-mentions
  147. */
  148. public function mentions($params = array()) {
  149. return $this->get_statuses('statuses/mentions', $params, TRUE);
  150. }
  151. /**
  152. *
  153. * @see http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-statuses%C2%A0update
  154. */
  155. public function status_update($status, $params = array()) {
  156. $params['status'] = $status;
  157. if ($this->source) {
  158. $params['source'] = $this->source;
  159. }
  160. $values = $this->call('statuses/update', $params, 'POST', TRUE);
  161. return new TwitterStatus($values);
  162. }
  163. /**
  164. *
  165. * @see http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-users%C2%A0show
  166. */
  167. public function users_show($id, $use_auth = TRUE) {
  168. $params = array();
  169. if (is_numeric($id)) {
  170. $params['user_id'] = $id;
  171. }
  172. else {
  173. $params['screen_name'] = $id;
  174. }
  175. $values = $this->call('users/show', $params, 'GET', $use_auth);
  176. return new TwitterUser($values);
  177. }
  178. /**
  179. *
  180. * @see http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-account%C2%A0verify_credentials
  181. */
  182. public function verify_credentials() {
  183. $values = $this->call('account/verify_credentials', array(), 'GET', TRUE);
  184. if (!$values) {
  185. return FALSE;
  186. }
  187. return new TwitterUser($values);
  188. }
  189. /**
  190. * Method for calling any twitter api resource
  191. */
  192. public function call($path, $params = array(), $method = 'GET', $use_auth = FALSE) {
  193. $url = $this->create_url($path);
  194. try {
  195. if ($use_auth) {
  196. $response = $this->auth_request($url, $params, $method);
  197. }
  198. else {
  199. $response = $this->request($url, $params, $method);
  200. }
  201. }
  202. catch (TwitterException $e) {
  203. watchdog('twitter', '!message', array('!message' => $e->__toString()), WATCHDOG_ERROR);
  204. return FALSE;
  205. }
  206. if (!$response) {
  207. return FALSE;
  208. }
  209. return $this->parse_response($response);
  210. }
  211. /**
  212. * Perform an authentication required request.
  213. */
  214. protected function auth_request($path, $params = array(), $method = 'GET') {
  215. if (empty($this->username) || empty($this->password)) {
  216. return false;
  217. }
  218. return $this->request($path, $params, $method, TRUE);
  219. }
  220. /**
  221. * Perform a request
  222. */
  223. protected function request($url, $params = array(), $method = 'GET', $use_auth = FALSE) {
  224. $data = '';
  225. if (count($params) > 0) {
  226. if ($method == 'GET') {
  227. $url .= '?'. http_build_query($params, '', '&');
  228. }
  229. else {
  230. $data = http_build_query($params, '', '&');
  231. }
  232. }
  233. $headers = array();
  234. if ($use_auth) {
  235. $headers['Authorization'] = 'Basic '. base64_encode($this->username .':'. $this->password);
  236. $headers['Content-type'] = 'application/x-www-form-urlencoded';
  237. }
  238. $response = drupal_http_request($url, array('headers' => $headers, 'method' => $method, 'data' => $data));
  239. if (!isset($response->error)) {
  240. return $response->data;
  241. }
  242. else {
  243. $error = $response->error;
  244. $data = $this->parse_response($response->data);
  245. if (isset($data['error'])) {
  246. $error = $data['error'];
  247. }
  248. throw new TwitterException($error);
  249. }
  250. }
  251. protected function parse_response($response, $format = NULL) {
  252. if (empty($format)) {
  253. $format = $this->format;
  254. }
  255. switch ($format) {
  256. case 'json':
  257. // http://drupal.org/node/985544 - json_decode large integer issue
  258. $length = strlen(PHP_INT_MAX);
  259. $response = preg_replace('/"(id|in_reply_to_status_id)":(\d{' . $length . ',})/', '"\1":"\2"', $response);
  260. return json_decode($response, TRUE);
  261. }
  262. }
  263. protected function create_url($path, $format = NULL) {
  264. if (is_null($format)) {
  265. $format = $this->format;
  266. }
  267. $conf = TwitterConf::instance();
  268. $url = 'http://'. $conf->get('api') .'/'. $path;
  269. if (!empty($format)) {
  270. $url .= '.'. $this->format;
  271. }
  272. return $url;
  273. }
  274. }
  275. /**
  276. * A class to provide OAuth enabled access to the twitter API
  277. */
  278. class TwitterOAuth extends Twitter {
  279. protected $signature_method;
  280. protected $consumer;
  281. protected $token;
  282. public function __construct($consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL) {
  283. $this->signature_method = new OAuthSignatureMethod_HMAC_SHA1();
  284. $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret);
  285. if (!empty($oauth_token) && !empty($oauth_token_secret)) {
  286. $this->token = new OAuthConsumer($oauth_token, $oauth_token_secret);
  287. }
  288. }
  289. public function get_request_token() {
  290. $url = $this->create_url('oauth/request_token', '');
  291. try {
  292. $response = $this->auth_request($url);
  293. }
  294. catch (TwitterException $e) {
  295. }
  296. parse_str($response, $token);
  297. $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']);
  298. return $token;
  299. }
  300. public function get_authorize_url($token) {
  301. $url = $this->create_url('oauth/authorize', '');
  302. $url.= '?oauth_token=' . $token['oauth_token'];
  303. return $url;
  304. }
  305. public function get_authenticate_url($token) {
  306. $url = $this->create_url('oauth/authenticate', '');
  307. $url.= '?oauth_token=' . $token['oauth_token'];
  308. return $url;
  309. }
  310. public function get_access_token() {
  311. $url = $this->create_url('oauth/access_token', '');
  312. try {
  313. $response = $this->auth_request($url);
  314. }
  315. catch (TwitterException $e) {
  316. }
  317. parse_str($response, $token);
  318. $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']);
  319. return $token;
  320. }
  321. public function auth_request($url, $params = array(), $method = 'GET') {
  322. $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $params);
  323. $request->sign_request($this->signature_method, $this->consumer, $this->token);
  324. switch ($method) {
  325. case 'GET':
  326. return $this->request($request->to_url());
  327. case 'POST':
  328. return $this->request($request->get_normalized_http_url(), $request->get_parameters(), 'POST');
  329. }
  330. }
  331. }
  332. /**
  333. * Twitter search is not used in this module yet
  334. */
  335. class TwitterSearch extends Twitter {
  336. public function search($params = array()) {}
  337. }
  338. /**
  339. * Class for containing an individual twitter status.
  340. */
  341. class TwitterStatus {
  342. /**
  343. * @var created_at
  344. */
  345. public $created_at;
  346. public $id;
  347. public $text;
  348. public $source;
  349. public $truncated;
  350. public $favorited;
  351. public $in_reply_to_status_id;
  352. public $in_reply_to_user_id;
  353. public $in_reply_to_screen_name;
  354. public $user;
  355. /**
  356. * Constructor for TwitterStatus
  357. */
  358. public function __construct($values = array()) {
  359. $this->created_at = $values['created_at'];
  360. $this->id = $values['id'];
  361. $this->text = $values['text'];
  362. $this->source = $values['source'];
  363. $this->truncated = $values['truncated'];
  364. $this->favorited = $values['favorited'];
  365. $this->in_reply_to_status_id = $values['in_reply_to_status_id'];
  366. $this->in_reply_to_user_id = $values['in_reply_to_user_id'];
  367. $this->in_reply_to_screen_name = $values['in_reply_to_screen_name'];
  368. if (isset($values['user'])) {
  369. $this->user = new TwitterUser($values['user']);
  370. }
  371. }
  372. }
  373. class TwitterUser {
  374. public $id;
  375. public $screen_name;
  376. public $name;
  377. public $location;
  378. public $description;
  379. public $followers_count;
  380. public $friends_count;
  381. public $statuses_count;
  382. public $favourites_count;
  383. public $url;
  384. public $protected;
  385. public $profile_image_url;
  386. public $profile_background_color;
  387. public $profile_text_color;
  388. public $profile_link_color;
  389. public $profile_sidebar_fill_color;
  390. public $profile_sidebar_border_color;
  391. public $profile_background_image_url;
  392. public $profile_background_tile;
  393. public $verified;
  394. public $created_at;
  395. public $created_time;
  396. public $utc_offset;
  397. public $status;
  398. protected $password;
  399. protected $oauth_token;
  400. protected $oauth_token_secret;
  401. public function __construct($values = array()) {
  402. $this->id = $values['id'];
  403. $this->screen_name = $values['screen_name'];
  404. $this->name = $values['name'];
  405. $this->location = $values['location'];
  406. $this->description = $values['description'];
  407. $this->url = $values['url'];
  408. $this->followers_count = $values['followers_count'];
  409. $this->friends_count = $values['friends_count'];
  410. $this->statuses_count = $values['statuses_count'];
  411. $this->favourites_count = $values['favourites_count'];
  412. $this->protected = $values['protected'];
  413. $this->profile_image_url = $values['profile_image_url'];
  414. $this->profile_background_color = $values['profile_background_color'];
  415. $this->profile_text_color = $values['profile_text_color'];
  416. $this->profile_link_color = $values['profile_link_color'];
  417. $this->profile_sidebar_fill_color = $values['profile_sidebar_fill_color'];
  418. $this->profile_sidebar_border_color = $values['profile_sidebar_border_color'];
  419. $this->profile_background_image_url = $values['profile_background_image_url'];
  420. $this->profile_background_tile = $values['profile_background_tile'];
  421. $this->verified = $values['verified'];
  422. $this->created_at = $values['created_at'];
  423. if ($values['created_at'] && $created_time = strtotime($values['created_at'])) {
  424. $this->created_time = $created_time;
  425. }
  426. $this->utc_offset = $values['utc_offset']?$values['utc_offset']:0;
  427. if (isset($values['status'])) {
  428. $this->status = new TwitterStatus($values['status']);
  429. }
  430. }
  431. public function get_auth() {
  432. return array('password' => $this->password, 'oauth_token' => $this->oauth_token, 'oauth_token_secret' => $this->oauth_token_secret);
  433. }
  434. public function set_auth($values) {
  435. $this->oauth_token = isset($values['oauth_token'])?$values['oauth_token']:NULL;
  436. $this->oauth_token_secret = isset($values['oauth_token_secret'])?$values['oauth_token_secret']:NULL;
  437. }
  438. }