Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1791 lines
56 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
10 years ago
11 years ago
11 years ago
10 years ago
10 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
  1. <?php
  2. require_once("include/bbcode.php");
  3. require_once("include/datetime.php");
  4. require_once("include/conversation.php");
  5. require_once("include/oauth.php");
  6. require_once("include/html2plain.php");
  7. /*
  8. * Twitter-Like API
  9. *
  10. */
  11. $API = Array();
  12. $called_api = Null;
  13. function api_user() {
  14. // It is not sufficient to use local_user() to check whether someone is allowed to use the API,
  15. // because this will open CSRF holes (just embed an image with src=friendicasite.com/api/statuses/update?status=CSRF
  16. // into a page, and visitors will post something without noticing it).
  17. // Instead, use this function.
  18. if ($_SESSION["allow_api"])
  19. return local_user();
  20. return false;
  21. }
  22. function api_date($str){
  23. //Wed May 23 06:01:13 +0000 2007
  24. return datetime_convert('UTC', 'UTC', $str, "D M d H:i:s +0000 Y" );
  25. }
  26. function api_register_func($path, $func, $auth=false){
  27. global $API;
  28. $API[$path] = array('func'=>$func,
  29. 'auth'=>$auth);
  30. }
  31. /**
  32. * Simple HTTP Login
  33. */
  34. function api_login(&$a){
  35. // login with oauth
  36. try{
  37. $oauth = new FKOAuth1();
  38. list($consumer,$token) = $oauth->verify_request(OAuthRequest::from_request());
  39. if (!is_null($token)){
  40. $oauth->loginUser($token->uid);
  41. call_hooks('logged_in', $a->user);
  42. return;
  43. }
  44. echo __file__.__line__.__function__."<pre>"; var_dump($consumer, $token); die();
  45. }catch(Exception $e){
  46. logger(__file__.__line__.__function__."\n".$e);
  47. //die(__file__.__line__.__function__."<pre>".$e); die();
  48. }
  49. // workaround for HTTP-auth in CGI mode
  50. if(x($_SERVER,'REDIRECT_REMOTE_USER')) {
  51. $userpass = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"],6)) ;
  52. if(strlen($userpass)) {
  53. list($name, $password) = explode(':', $userpass);
  54. $_SERVER['PHP_AUTH_USER'] = $name;
  55. $_SERVER['PHP_AUTH_PW'] = $password;
  56. }
  57. }
  58. if (!isset($_SERVER['PHP_AUTH_USER'])) {
  59. logger('API_login: ' . print_r($_SERVER,true), LOGGER_DEBUG);
  60. header('WWW-Authenticate: Basic realm="Friendica"');
  61. header('HTTP/1.0 401 Unauthorized');
  62. die('This api requires login');
  63. }
  64. $user = $_SERVER['PHP_AUTH_USER'];
  65. $encrypted = hash('whirlpool',trim($_SERVER['PHP_AUTH_PW']));
  66. /**
  67. * next code from mod/auth.php. needs better solution
  68. */
  69. // process normal login request
  70. $r = q("SELECT * FROM `user` WHERE ( `email` = '%s' OR `nickname` = '%s' )
  71. AND `password` = '%s' AND `blocked` = 0 AND `account_expired` = 0 AND `account_removed` = 0 AND `verified` = 1 LIMIT 1",
  72. dbesc(trim($user)),
  73. dbesc(trim($user)),
  74. dbesc($encrypted)
  75. );
  76. if(count($r)){
  77. $record = $r[0];
  78. } else {
  79. logger('API_login failure: ' . print_r($_SERVER,true), LOGGER_DEBUG);
  80. header('WWW-Authenticate: Basic realm="Friendica"');
  81. header('HTTP/1.0 401 Unauthorized');
  82. die('This api requires login');
  83. }
  84. require_once('include/security.php');
  85. authenticate_success($record); $_SESSION["allow_api"] = true;
  86. call_hooks('logged_in', $a->user);
  87. }
  88. /**************************
  89. * MAIN API ENTRY POINT *
  90. **************************/
  91. function api_call(&$a){
  92. GLOBAL $API, $called_api;
  93. // preset
  94. $type="json";
  95. foreach ($API as $p=>$info){
  96. if (strpos($a->query_string, $p)===0){
  97. $called_api= explode("/",$p);
  98. //unset($_SERVER['PHP_AUTH_USER']);
  99. if ($info['auth']===true && api_user()===false) {
  100. api_login($a);
  101. }
  102. load_contact_links(api_user());
  103. logger('API call for ' . $a->user['username'] . ': ' . $a->query_string);
  104. logger('API parameters: ' . print_r($_REQUEST,true));
  105. $type="json";
  106. if (strpos($a->query_string, ".xml")>0) $type="xml";
  107. if (strpos($a->query_string, ".json")>0) $type="json";
  108. if (strpos($a->query_string, ".rss")>0) $type="rss";
  109. if (strpos($a->query_string, ".atom")>0) $type="atom";
  110. if (strpos($a->query_string, ".as")>0) $type="as";
  111. $r = call_user_func($info['func'], $a, $type);
  112. if ($r===false) return;
  113. switch($type){
  114. case "xml":
  115. $r = mb_convert_encoding($r, "UTF-8",mb_detect_encoding($r));
  116. header ("Content-Type: text/xml");
  117. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  118. break;
  119. case "json":
  120. //header ("Content-Type: application/json");
  121. foreach($r as $rr)
  122. return json_encode($rr);
  123. break;
  124. case "rss":
  125. header ("Content-Type: application/rss+xml");
  126. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  127. break;
  128. case "atom":
  129. header ("Content-Type: application/atom+xml");
  130. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  131. break;
  132. case "as":
  133. //header ("Content-Type: application/json");
  134. //foreach($r as $rr)
  135. // return json_encode($rr);
  136. return json_encode($r);
  137. break;
  138. }
  139. //echo "<pre>"; var_dump($r); die();
  140. }
  141. }
  142. header("HTTP/1.1 404 Not Found");
  143. logger('API call not implemented: '.$a->query_string." - ".print_r($_REQUEST,true));
  144. $r = '<status><error>not implemented</error></status>';
  145. switch($type){
  146. case "xml":
  147. header ("Content-Type: text/xml");
  148. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  149. break;
  150. case "json":
  151. header ("Content-Type: application/json");
  152. return json_encode(array('error' => 'not implemented'));
  153. break;
  154. case "rss":
  155. header ("Content-Type: application/rss+xml");
  156. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  157. break;
  158. case "atom":
  159. header ("Content-Type: application/atom+xml");
  160. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  161. break;
  162. }
  163. }
  164. /**
  165. * RSS extra info
  166. */
  167. function api_rss_extra(&$a, $arr, $user_info){
  168. if (is_null($user_info)) $user_info = api_get_user($a);
  169. $arr['$user'] = $user_info;
  170. $arr['$rss'] = array(
  171. 'alternate' => $user_info['url'],
  172. 'self' => $a->get_baseurl(). "/". $a->query_string,
  173. 'base' => $a->get_baseurl(),
  174. 'updated' => api_date(null),
  175. 'atom_updated' => datetime_convert('UTC','UTC','now',ATOM_TIME),
  176. 'language' => $user_info['language'],
  177. 'logo' => $a->get_baseurl()."/images/friendica-32.png",
  178. );
  179. return $arr;
  180. }
  181. /**
  182. * Returns user info array.
  183. */
  184. function api_get_user(&$a, $contact_id = Null){
  185. global $called_api;
  186. $user = null;
  187. $extra_query = "";
  188. if(!is_null($contact_id)){
  189. $user=$contact_id;
  190. $extra_query = "AND `contact`.`id` = %d ";
  191. }
  192. if(is_null($user) && x($_GET, 'user_id')) {
  193. $user = intval($_GET['user_id']);
  194. $extra_query = "AND `contact`.`id` = %d ";
  195. }
  196. if(is_null($user) && x($_GET, 'screen_name')) {
  197. $user = dbesc($_GET['screen_name']);
  198. $extra_query = "AND `contact`.`nick` = '%s' ";
  199. if (api_user()!==false) $extra_query .= "AND `contact`.`uid`=".intval(api_user());
  200. }
  201. if (is_null($user) && $a->argc > (count($called_api)-1)){
  202. $argid = count($called_api);
  203. list($user, $null) = explode(".",$a->argv[$argid]);
  204. if(is_numeric($user)){
  205. $user = intval($user);
  206. $extra_query = "AND `contact`.`id` = %d ";
  207. } else {
  208. $user = dbesc($user);
  209. $extra_query = "AND `contact`.`nick` = '%s' ";
  210. if (api_user()!==false) $extra_query .= "AND `contact`.`uid`=".intval(api_user());
  211. }
  212. }
  213. if (! $user) {
  214. if (api_user()===false) {
  215. api_login($a); return False;
  216. } else {
  217. $user = $_SESSION['uid'];
  218. $extra_query = "AND `contact`.`uid` = %d AND `contact`.`self` = 1 ";
  219. }
  220. }
  221. logger('api_user: ' . $extra_query . ', user: ' . $user);
  222. // user info
  223. $uinfo = q("SELECT *, `contact`.`id` as `cid` FROM `contact`
  224. WHERE 1
  225. $extra_query",
  226. $user
  227. );
  228. if (count($uinfo)==0) {
  229. return False;
  230. }
  231. if($uinfo[0]['self']) {
  232. $usr = q("select * from user where uid = %d limit 1",
  233. intval(api_user())
  234. );
  235. $profile = q("select * from profile where uid = %d and `is-default` = 1 limit 1",
  236. intval(api_user())
  237. );
  238. // count public wall messages
  239. $r = q("SELECT COUNT(`id`) as `count` FROM `item`
  240. WHERE `uid` = %d
  241. AND `type`='wall'
  242. AND `allow_cid`='' AND `allow_gid`='' AND `deny_cid`='' AND `deny_gid`=''",
  243. intval($uinfo[0]['uid'])
  244. );
  245. $countitms = $r[0]['count'];
  246. }
  247. else {
  248. $r = q("SELECT COUNT(`id`) as `count` FROM `item`
  249. WHERE `contact-id` = %d
  250. AND `allow_cid`='' AND `allow_gid`='' AND `deny_cid`='' AND `deny_gid`=''",
  251. intval($uinfo[0]['id'])
  252. );
  253. $countitms = $r[0]['count'];
  254. }
  255. // count friends
  256. $r = q("SELECT COUNT(`id`) as `count` FROM `contact`
  257. WHERE `uid` = %d AND `rel` IN ( %d, %d )
  258. AND `self`=0 AND `blocked`=0 AND `pending`=0 AND `hidden`=0",
  259. intval($uinfo[0]['uid']),
  260. intval(CONTACT_IS_SHARING),
  261. intval(CONTACT_IS_FRIEND)
  262. );
  263. $countfriends = $r[0]['count'];
  264. $r = q("SELECT COUNT(`id`) as `count` FROM `contact`
  265. WHERE `uid` = %d AND `rel` IN ( %d, %d )
  266. AND `self`=0 AND `blocked`=0 AND `pending`=0 AND `hidden`=0",
  267. intval($uinfo[0]['uid']),
  268. intval(CONTACT_IS_FOLLOWER),
  269. intval(CONTACT_IS_FRIEND)
  270. );
  271. $countfollowers = $r[0]['count'];
  272. $r = q("SELECT count(`id`) as `count` FROM item where starred = 1 and uid = %d and deleted = 0",
  273. intval($uinfo[0]['uid'])
  274. );
  275. $starred = $r[0]['count'];
  276. if(! $uinfo[0]['self']) {
  277. $countfriends = 0;
  278. $countfollowers = 0;
  279. $starred = 0;
  280. }
  281. $ret = Array(
  282. 'id' => intval($uinfo[0]['cid']),
  283. 'self' => intval($uinfo[0]['self']),
  284. 'uid' => intval($uinfo[0]['uid']),
  285. 'name' => (($uinfo[0]['name']) ? $uinfo[0]['name'] : $uinfo[0]['nick']),
  286. 'screen_name' => (($uinfo[0]['nick']) ? $uinfo[0]['nick'] : $uinfo[0]['name']),
  287. 'location' => ($usr) ? $usr[0]['default-location'] : '',
  288. 'profile_image_url' => $uinfo[0]['micro'],
  289. 'url' => $uinfo[0]['url'],
  290. 'contact_url' => $a->get_baseurl()."/contacts/".$uinfo[0]['cid'],
  291. 'protected' => false,
  292. 'friends_count' => intval($countfriends),
  293. 'created_at' => api_date($uinfo[0]['name-date']),
  294. 'utc_offset' => "+00:00",
  295. 'time_zone' => 'UTC', //$uinfo[0]['timezone'],
  296. 'geo_enabled' => false,
  297. 'statuses_count' => intval($countitms), #XXX: fix me
  298. 'lang' => 'en', #XXX: fix me
  299. 'description' => (($profile) ? $profile[0]['pdesc'] : ''),
  300. 'followers_count' => intval($countfollowers),
  301. 'favourites_count' => intval($starred),
  302. 'contributors_enabled' => false,
  303. 'follow_request_sent' => true,
  304. 'profile_background_color' => 'cfe8f6',
  305. 'profile_text_color' => '000000',
  306. 'profile_link_color' => 'FF8500',
  307. 'profile_sidebar_fill_color' =>'AD0066',
  308. 'profile_sidebar_border_color' => 'AD0066',
  309. 'profile_background_image_url' => '',
  310. 'profile_background_tile' => false,
  311. 'profile_use_background_image' => false,
  312. 'notifications' => false,
  313. 'following' => '', #XXX: fix me
  314. 'verified' => true, #XXX: fix me
  315. 'status' => array()
  316. );
  317. return $ret;
  318. }
  319. function api_item_get_user(&$a, $item) {
  320. global $usercache;
  321. // The author is our direct contact, in a conversation with us.
  322. if(link_compare($item['url'],$item['author-link'])) {
  323. return api_get_user($a,$item['cid']);
  324. }
  325. else {
  326. // The author may be a contact of ours, but is replying to somebody else.
  327. // Figure out if we know him/her.
  328. $normalised = normalise_link((strlen($item['author-link'])) ? $item['author-link'] : $item['url']);
  329. if(($normalised != 'mailbox') && (x($a->contacts[$normalised])))
  330. return api_get_user($a,$a->contacts[$normalised]['id']);
  331. }
  332. // We don't know this person directly.
  333. list($nick, $name) = array_map("trim",explode("(",$item['author-name']));
  334. $name=str_replace(")","",$name);
  335. if ($name == '')
  336. $name = $nick;
  337. if ($nick == '')
  338. $nick = $name;
  339. // Generating a random ID
  340. if (is_null($usercache[$nick]) or !array_key_exists($nick, $usercache))
  341. $usercache[$nick] = mt_rand(2000000, 2100000);
  342. $ret = array(
  343. 'id' => $usercache[$nick],
  344. 'name' => $name,
  345. 'screen_name' => $nick,
  346. 'location' => '', //$uinfo[0]['default-location'],
  347. 'description' => '',
  348. 'profile_image_url' => $item['author-avatar'],
  349. 'url' => $item['author-link'],
  350. 'protected' => false, #
  351. 'followers_count' => 0,
  352. 'friends_count' => 0,
  353. 'created_at' => '',
  354. 'favourites_count' => 0,
  355. 'utc_offset' => 0, #XXX: fix me
  356. 'time_zone' => '', //$uinfo[0]['timezone'],
  357. 'statuses_count' => 0,
  358. 'following' => 1,
  359. 'statusnet_blocking' => false,
  360. 'notifications' => false,
  361. 'uid' => 0,
  362. 'contact_url' => 0,
  363. 'geo_enabled' => false,
  364. 'lang' => 'en', #XXX: fix me
  365. 'contributors_enabled' => false,
  366. 'follow_request_sent' => false,
  367. 'profile_background_color' => 'cfe8f6',
  368. 'profile_text_color' => '000000',
  369. 'profile_link_color' => 'FF8500',
  370. 'profile_sidebar_fill_color' =>'AD0066',
  371. 'profile_sidebar_border_color' => 'AD0066',
  372. 'profile_background_image_url' => '',
  373. 'profile_background_tile' => false,
  374. 'profile_use_background_image' => false,
  375. 'verified' => true, #XXX: fix me
  376. 'followers' => '', #XXX: fix me
  377. 'status' => array()
  378. );
  379. return $ret;
  380. }
  381. /**
  382. * load api $templatename for $type and replace $data array
  383. */
  384. function api_apply_template($templatename, $type, $data){
  385. $a = get_app();
  386. switch($type){
  387. case "atom":
  388. case "rss":
  389. case "xml":
  390. $data = array_xmlify($data);
  391. $tpl = get_markup_template("api_".$templatename."_".$type.".tpl");
  392. if(! $tpl) {
  393. header ("Content-Type: text/xml");
  394. echo '<?xml version="1.0" encoding="UTF-8"?>'."\n".'<status><error>not implemented</error></status>';
  395. killme();
  396. }
  397. $ret = replace_macros($tpl, $data);
  398. break;
  399. case "json":
  400. $ret = $data;
  401. break;
  402. }
  403. return $ret;
  404. }
  405. /**
  406. ** TWITTER API
  407. */
  408. /**
  409. * Returns an HTTP 200 OK response code and a representation of the requesting user if authentication was successful;
  410. * returns a 401 status code and an error message if not.
  411. * http://developer.twitter.com/doc/get/account/verify_credentials
  412. */
  413. function api_account_verify_credentials(&$a, $type){
  414. if (api_user()===false) return false;
  415. $user_info = api_get_user($a);
  416. return api_apply_template("user", $type, array('$user' => $user_info));
  417. }
  418. api_register_func('api/account/verify_credentials','api_account_verify_credentials', true);
  419. /**
  420. * get data from $_POST or $_GET
  421. */
  422. function requestdata($k){
  423. if (isset($_POST[$k])){
  424. return $_POST[$k];
  425. }
  426. if (isset($_GET[$k])){
  427. return $_GET[$k];
  428. }
  429. return null;
  430. }
  431. /*Waitman Gobble Mod*/
  432. function api_statuses_mediap(&$a, $type) {
  433. if (api_user()===false) {
  434. logger('api_statuses_update: no user');
  435. return false;
  436. }
  437. $user_info = api_get_user($a);
  438. $_REQUEST['type'] = 'wall';
  439. $_REQUEST['profile_uid'] = api_user();
  440. $_REQUEST['api_source'] = true;
  441. $txt = requestdata('status');
  442. //$txt = urldecode(requestdata('status'));
  443. require_once('library/HTMLPurifier.auto.php');
  444. require_once('include/html2bbcode.php');
  445. if((strpos($txt,'<') !== false) || (strpos($txt,'>') !== false)) {
  446. $txt = html2bb_video($txt);
  447. $config = HTMLPurifier_Config::createDefault();
  448. $config->set('Cache.DefinitionImpl', null);
  449. $purifier = new HTMLPurifier($config);
  450. $txt = $purifier->purify($txt);
  451. }
  452. $txt = html2bbcode($txt);
  453. $a->argv[1]=$user_info['screen_name']; //should be set to username?
  454. $_REQUEST['hush']='yeah'; //tell wall_upload function to return img info instead of echo
  455. require_once('mod/wall_upload.php');
  456. $bebop = wall_upload_post($a);
  457. //now that we have the img url in bbcode we can add it to the status and insert the wall item.
  458. $_REQUEST['body']=$txt."\n\n".$bebop;
  459. require_once('mod/item.php');
  460. item_post($a);
  461. // this should output the last post (the one we just posted).
  462. return api_status_show($a,$type);
  463. }
  464. api_register_func('api/statuses/mediap','api_statuses_mediap', true);
  465. /*Waitman Gobble Mod*/
  466. function api_statuses_update(&$a, $type) {
  467. if (api_user()===false) {
  468. logger('api_statuses_update: no user');
  469. return false;
  470. }
  471. $user_info = api_get_user($a);
  472. // convert $_POST array items to the form we use for web posts.
  473. // logger('api_post: ' . print_r($_POST,true));
  474. if(requestdata('htmlstatus')) {
  475. require_once('library/HTMLPurifier.auto.php');
  476. require_once('include/html2bbcode.php');
  477. $txt = requestdata('htmlstatus');
  478. if((strpos($txt,'<') !== false) || (strpos($txt,'>') !== false)) {
  479. $txt = html2bb_video($txt);
  480. $config = HTMLPurifier_Config::createDefault();
  481. $config->set('Cache.DefinitionImpl', null);
  482. $purifier = new HTMLPurifier($config);
  483. $txt = $purifier->purify($txt);
  484. $_REQUEST['body'] = html2bbcode($txt);
  485. }
  486. }
  487. else
  488. $_REQUEST['body'] = requestdata('status');
  489. //$_REQUEST['body'] = urldecode(requestdata('status'));
  490. $_REQUEST['title'] = requestdata('title');
  491. $parent = requestdata('in_reply_to_status_id');
  492. if(ctype_digit($parent))
  493. $_REQUEST['parent'] = $parent;
  494. else
  495. $_REQUEST['parent_uri'] = $parent;
  496. if(requestdata('lat') && requestdata('long'))
  497. $_REQUEST['coord'] = sprintf("%s %s",requestdata('lat'),requestdata('long'));
  498. $_REQUEST['profile_uid'] = api_user();
  499. if($parent)
  500. $_REQUEST['type'] = 'net-comment';
  501. else {
  502. $_REQUEST['type'] = 'wall';
  503. if(x($_FILES,'media')) {
  504. // upload the image if we have one
  505. $_REQUEST['hush']='yeah'; //tell wall_upload function to return img info instead of echo
  506. require_once('mod/wall_upload.php');
  507. $media = wall_upload_post($a);
  508. if(strlen($media)>0)
  509. $_REQUEST['body'] .= "\n\n".$media;
  510. }
  511. }
  512. // set this so that the item_post() function is quiet and doesn't redirect or emit json
  513. $_REQUEST['api_source'] = true;
  514. // call out normal post function
  515. require_once('mod/item.php');
  516. item_post($a);
  517. // this should output the last post (the one we just posted).
  518. return api_status_show($a,$type);
  519. }
  520. api_register_func('api/statuses/update','api_statuses_update', true);
  521. function api_status_show(&$a, $type){
  522. $user_info = api_get_user($a);
  523. // get last public wall message
  524. $lastwall = q("SELECT `item`.*, `i`.`contact-id` as `reply_uid`, `c`.`nick` as `reply_author`
  525. FROM `item`, `contact`, `item` as `i`, `contact` as `c`
  526. WHERE `item`.`contact-id` = %d
  527. AND `i`.`id` = `item`.`parent`
  528. AND `contact`.`id`=`item`.`contact-id` AND `c`.`id`=`i`.`contact-id` AND `contact`.`self`=1
  529. AND `item`.`type`!='activity'
  530. AND `item`.`allow_cid`='' AND `item`.`allow_gid`='' AND `item`.`deny_cid`='' AND `item`.`deny_gid`=''
  531. ORDER BY `item`.`created` DESC
  532. LIMIT 1",
  533. intval($user_info['id'])
  534. );
  535. // $lastwall = q("SELECT `item`.*, `i`.`contact-id` as `reply_uid`, `i`.`nick` as `reply_author`
  536. // FROM `item`, `contact`,
  537. // (SELECT `item`.`id`, `item`.`contact-id`, `contact`.`nick` FROM `item`,`contact` WHERE `contact`.`id`=`item`.`contact-id`) as `i`
  538. // WHERE `item`.`contact-id` = %d
  539. // AND `i`.`id` = `item`.`parent`
  540. // AND `contact`.`id`=`item`.`contact-id` AND `contact`.`self`=1
  541. // AND `type`!='activity'
  542. // AND `item`.`allow_cid`='' AND `item`.`allow_gid`='' AND `item`.`deny_cid`='' AND `item`.`deny_gid`=''
  543. // ORDER BY `created` DESC
  544. // LIMIT 1",
  545. // intval($user_info['id'])
  546. // );
  547. if (count($lastwall)>0){
  548. $lastwall = $lastwall[0];
  549. $in_reply_to_status_id = '';
  550. $in_reply_to_user_id = '';
  551. $in_reply_to_screen_name = '';
  552. if ($lastwall['parent']!=$lastwall['id']) {
  553. $in_reply_to_status_id=$lastwall['parent'];
  554. $in_reply_to_user_id = $lastwall['reply_uid'];
  555. $in_reply_to_screen_name = $lastwall['reply_author'];
  556. }
  557. $status_info = array(
  558. 'text' => html2plain(bbcode($lastwall['body'], false, false, true), 0),
  559. 'truncated' => false,
  560. 'created_at' => api_date($lastwall['created']),
  561. 'in_reply_to_status_id' => $in_reply_to_status_id,
  562. 'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'),
  563. 'id' => $lastwall['id'],
  564. 'in_reply_to_user_id' => $in_reply_to_user_id,
  565. 'in_reply_to_screen_name' => $in_reply_to_screen_name,
  566. 'geo' => '',
  567. 'favorited' => false,
  568. 'coordinates' => $lastwall['coord'],
  569. 'place' => $lastwall['location'],
  570. 'contributors' => ''
  571. );
  572. $status_info['user'] = $user_info;
  573. }
  574. return api_apply_template("status", $type, array('$status' => $status_info));
  575. }
  576. /**
  577. * Returns extended information of a given user, specified by ID or screen name as per the required id parameter.
  578. * The author's most recent status will be returned inline.
  579. * http://developer.twitter.com/doc/get/users/show
  580. */
  581. function api_users_show(&$a, $type){
  582. $user_info = api_get_user($a);
  583. // get last public wall message
  584. $lastwall = q("SELECT `item`.*, `i`.`contact-id` as `reply_uid`, `i`.`nick` as `reply_author`
  585. FROM `item`, `contact`,
  586. (SELECT `item`.`id`, `item`.`contact-id`, `contact`.`nick` FROM `item`,`contact` WHERE `contact`.`id`=`item`.`contact-id`) as `i`
  587. WHERE `item`.`contact-id` = %d
  588. AND `i`.`id` = `item`.`parent`
  589. AND `contact`.`id`=`item`.`contact-id` AND `contact`.`self`=1
  590. AND `type`!='activity'
  591. AND `item`.`allow_cid`='' AND `item`.`allow_gid`='' AND `item`.`deny_cid`='' AND `item`.`deny_gid`=''
  592. ORDER BY `created` DESC
  593. LIMIT 1",
  594. intval($user_info['id'])
  595. );
  596. if (count($lastwall)>0){
  597. $lastwall = $lastwall[0];
  598. $in_reply_to_status_id = '';
  599. $in_reply_to_user_id = '';
  600. $in_reply_to_screen_name = '';
  601. if ($lastwall['parent']!=$lastwall['id']) {
  602. $in_reply_to_status_id=$lastwall['parent'];
  603. $in_reply_to_user_id = $lastwall['reply_uid'];
  604. $in_reply_to_screen_name = $lastwall['reply_author'];
  605. }
  606. $user_info['status'] = array(
  607. 'created_at' => api_date($lastwall['created']),
  608. 'id' => $lastwall['contact-id'],
  609. 'text' => html2plain(bbcode($lastwall['body'], false, false, true), 0),
  610. 'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'),
  611. 'truncated' => false,
  612. 'in_reply_to_status_id' => $in_reply_to_status_id,
  613. 'in_reply_to_user_id' => $in_reply_to_user_id,
  614. 'favorited' => false,
  615. 'in_reply_to_screen_name' => $in_reply_to_screen_name,
  616. 'geo' => '',
  617. 'coordinates' => $lastwall['coord'],
  618. 'place' => $lastwall['location'],
  619. 'contributors' => ''
  620. );
  621. }
  622. return api_apply_template("user", $type, array('$user' => $user_info));
  623. }
  624. api_register_func('api/users/show','api_users_show');
  625. /**
  626. *
  627. * http://developer.twitter.com/doc/get/statuses/home_timeline
  628. *
  629. * TODO: Optional parameters
  630. * TODO: Add reply info
  631. */
  632. function api_statuses_home_timeline(&$a, $type){
  633. if (api_user()===false) return false;
  634. $user_info = api_get_user($a);
  635. // get last newtork messages
  636. // params
  637. $count = (x($_REQUEST,'count')?$_REQUEST['count']:20);
  638. $page = (x($_REQUEST,'page')?$_REQUEST['page']-1:0);
  639. if ($page<0) $page=0;
  640. $since_id = (x($_REQUEST,'since_id')?$_REQUEST['since_id']:0);
  641. $max_id = (x($_REQUEST,'max_id')?$_REQUEST['max_id']:0);
  642. //$since_id = 0;//$since_id = (x($_REQUEST,'since_id')?$_REQUEST['since_id']:0);
  643. $exclude_replies = (x($_REQUEST,'exclude_replies')?1:0);
  644. $conversation_id = (x($_REQUEST,'conversation_id')?$_REQUEST['conversation_id']:0);
  645. $start = $page*$count;
  646. //$include_entities = (x($_REQUEST,'include_entities')?$_REQUEST['include_entities']:false);
  647. $sql_extra = '';
  648. if ($max_id > 0)
  649. $sql_extra .= ' AND `item`.`id` <= '.intval($max_id);
  650. if ($exclude_replies > 0)
  651. $sql_extra .= ' AND `item`.`parent` = `item`.`id`';
  652. if ($conversation_id > 0)
  653. $sql_extra .= ' AND `item`.`parent` = '.intval($conversation_id);
  654. $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
  655. `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
  656. `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
  657. `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid`
  658. FROM `item`, `contact`
  659. WHERE `item`.`uid` = %d
  660. AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0
  661. AND `contact`.`id` = `item`.`contact-id`
  662. AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  663. $sql_extra
  664. AND `item`.`id`>%d
  665. ORDER BY `item`.`received` DESC LIMIT %d ,%d ",
  666. intval($user_info['uid']),
  667. intval($since_id),
  668. intval($start), intval($count)
  669. );
  670. $ret = api_format_items($r,$user_info);
  671. // We aren't going to try to figure out at the item, group, and page
  672. // level which items you've seen and which you haven't. If you're looking
  673. // at the network timeline just mark everything seen.
  674. $r = q("UPDATE `item` SET `unseen` = 0
  675. WHERE `unseen` = 1 AND `uid` = %d",
  676. intval($user_info['uid'])
  677. );
  678. $data = array('$statuses' => $ret);
  679. switch($type){
  680. case "atom":
  681. case "rss":
  682. $data = api_rss_extra($a, $data, $user_info);
  683. break;
  684. case "as":
  685. $as = api_format_as($a, $ret, $user_info);
  686. $as['title'] = $a->config['sitename']." Home Timeline";
  687. $as['link']['url'] = $a->get_baseurl()."/".$user_info["screen_name"]."/all";
  688. return($as);
  689. break;
  690. }
  691. return api_apply_template("timeline", $type, $data);
  692. }
  693. api_register_func('api/statuses/home_timeline','api_statuses_home_timeline', true);
  694. api_register_func('api/statuses/friends_timeline','api_statuses_home_timeline', true);
  695. function api_statuses_public_timeline(&$a, $type){
  696. if (api_user()===false) return false;
  697. $user_info = api_get_user($a);
  698. // get last newtork messages
  699. // params
  700. $count = (x($_REQUEST,'count')?$_REQUEST['count']:20);
  701. $page = (x($_REQUEST,'page')?$_REQUEST['page']-1:0);
  702. if ($page<0) $page=0;
  703. $since_id = (x($_REQUEST,'since_id')?$_REQUEST['since_id']:0);
  704. $max_id = (x($_REQUEST,'max_id')?$_REQUEST['max_id']:0);
  705. //$since_id = 0;//$since_id = (x($_REQUEST,'since_id')?$_REQUEST['since_id']:0);
  706. $exclude_replies = (x($_REQUEST,'exclude_replies')?1:0);
  707. $conversation_id = (x($_REQUEST,'conversation_id')?$_REQUEST['conversation_id']:0);
  708. $start = $page*$count;
  709. //$include_entities = (x($_REQUEST,'include_entities')?$_REQUEST['include_entities']:false);
  710. if ($max_id > 0)
  711. $sql_extra = 'AND `item`.`id` <= '.intval($max_id);
  712. if ($exclude_replies > 0)
  713. $sql_extra .= ' AND `item`.`parent` = `item`.`id`';
  714. if ($conversation_id > 0)
  715. $sql_extra .= ' AND `item`.`parent` = '.intval($conversation_id);
  716. /*$r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
  717. `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
  718. `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
  719. `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid`
  720. FROM `item`, `contact`
  721. WHERE `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0
  722. AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
  723. AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
  724. AND `item`.`private` = 0 AND `item`.`wall` = 1 AND `user`.`hidewall` = 0
  725. AND `contact`.`id` = `item`.`contact-id`
  726. AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  727. $sql_extra
  728. AND `item`.`id`>%d
  729. ORDER BY `item`.`received` DESC LIMIT %d ,%d ",
  730. intval($since_id),
  731. intval($start), intval($count)
  732. );*/
  733. $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
  734. `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
  735. `contact`.`network`, `contact`.`thumb`, `contact`.`self`, `contact`.`writable`,
  736. `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid`,
  737. `user`.`nickname`, `user`.`hidewall`
  738. FROM `item` LEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
  739. LEFT JOIN `user` ON `user`.`uid` = `item`.`uid`
  740. WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0
  741. AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
  742. AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
  743. AND `item`.`private` = 0 AND `item`.`wall` = 1 AND `user`.`hidewall` = 0
  744. AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  745. $sql_extra
  746. AND `item`.`id`>%d
  747. ORDER BY `received` DESC LIMIT %d, %d ",
  748. intval($since_id),
  749. intval($start),
  750. intval($count));
  751. $ret = api_format_items($r,$user_info);
  752. $data = array('$statuses' => $ret);
  753. switch($type){
  754. case "atom":
  755. case "rss":
  756. $data = api_rss_extra($a, $data, $user_info);
  757. break;
  758. case "as":
  759. $as = api_format_as($a, $ret, $user_info);
  760. $as['title'] = $a->config['sitename']." Public Timeline";
  761. $as['link']['url'] = $a->get_baseurl()."/";
  762. return($as);
  763. break;
  764. }
  765. return api_apply_template("timeline", $type, $data);
  766. }
  767. api_register_func('api/statuses/public_timeline','api_statuses_public_timeline', true);
  768. /**
  769. *
  770. */
  771. function api_statuses_show(&$a, $type){
  772. if (api_user()===false) return false;
  773. $user_info = api_get_user($a);
  774. // params
  775. $id = intval($a->argv[3]);
  776. logger('API: api_statuses_show: '.$id);
  777. //$include_entities = (x($_REQUEST,'include_entities')?$_REQUEST['include_entities']:false);
  778. $conversation = (x($_REQUEST,'conversation')?1:0);
  779. $sql_extra = '';
  780. if ($conversation)
  781. $sql_extra .= " AND `item`.`parent` = %d ORDER BY `received` ASC ";
  782. else
  783. $sql_extra .= " AND `item`.`id` = %d";
  784. $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
  785. `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
  786. `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
  787. `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid`
  788. FROM `item`, `contact`
  789. WHERE `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0
  790. AND `contact`.`id` = `item`.`contact-id`
  791. AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  792. $sql_extra",
  793. intval($id)
  794. );
  795. $ret = api_format_items($r,$user_info);
  796. if ($conversation) {
  797. $data = array('$statuses' => $ret);
  798. return api_apply_template("timeline", $type, $data);
  799. } else {
  800. $data = array('$status' => $ret[0]);
  801. /*switch($type){
  802. case "atom":
  803. case "rss":
  804. $data = api_rss_extra($a, $data, $user_info);
  805. }*/
  806. return api_apply_template("status", $type, $data);
  807. }
  808. }
  809. api_register_func('api/statuses/show','api_statuses_show', true);
  810. /**
  811. *
  812. */
  813. function api_statuses_repeat(&$a, $type){
  814. if (api_user()===false) return false;
  815. $user_info = api_get_user($a);
  816. // params
  817. $id = intval($a->argv[3]);
  818. logger('API: api_statuses_repeat: '.$id);
  819. //$include_entities = (x($_REQUEST,'include_entities')?$_REQUEST['include_entities']:false);
  820. $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `contact`.`nick` as `reply_author`,
  821. `contact`.`name`, `contact`.`photo` as `reply_photo`, `contact`.`url` as `reply_url`, `contact`.`rel`,
  822. `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
  823. `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid`
  824. FROM `item`, `contact`
  825. WHERE `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0
  826. AND `contact`.`id` = `item`.`contact-id`
  827. AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  828. $sql_extra
  829. AND `item`.`id`=%d",
  830. intval($id)
  831. );
  832. if ($r[0]['body'] != "") {
  833. if (!intval(get_config('system','old_share'))) {
  834. $post = "[share author='".str_replace("'", "&#039;", $r[0]['reply_author']).
  835. "' profile='".$r[0]['reply_url'].
  836. "' avatar='".$r[0]['reply_photo'].
  837. "' link='".$r[0]['plink']."']";
  838. $post .= $r[0]['body'];
  839. $post .= "[/share]";
  840. $_REQUEST['body'] = $post;
  841. } else
  842. $_REQUEST['body'] = html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8')."[url=".$r[0]['reply_url']."]".$r[0]['reply_author']."[/url] \n".$r[0]['body'];
  843. $_REQUEST['profile_uid'] = api_user();
  844. $_REQUEST['type'] = 'wall';
  845. $_REQUEST['api_source'] = true;
  846. require_once('mod/item.php');
  847. item_post($a);
  848. }
  849. if ($type == 'xml')
  850. $ok = "true";
  851. else
  852. $ok = "ok";
  853. return api_apply_template('test', $type, array('$ok' => $ok));
  854. }
  855. api_register_func('api/statuses/retweet','api_statuses_repeat', true);
  856. /**
  857. *
  858. */
  859. function api_statuses_destroy(&$a, $type){
  860. if (api_user()===false) return false;
  861. $user_info = api_get_user($a);
  862. // params
  863. $id = intval($a->argv[3]);
  864. logger('API: api_statuses_destroy: '.$id);
  865. require_once('include/items.php');
  866. drop_item($id, false);
  867. if ($type == 'xml')
  868. $ok = "true";
  869. else
  870. $ok = "ok";
  871. return api_apply_template('test', $type, array('$ok' => $ok));
  872. }
  873. api_register_func('api/statuses/destroy','api_statuses_destroy', true);
  874. /**
  875. *
  876. * http://developer.twitter.com/doc/get/statuses/mentions
  877. *
  878. */
  879. function api_statuses_mentions(&$a, $type){
  880. if (api_user()===false) return false;
  881. $user_info = api_get_user($a);
  882. // get last newtork messages
  883. // params
  884. $count = (x($_REQUEST,'count')?$_REQUEST['count']:20);
  885. $page = (x($_REQUEST,'page')?$_REQUEST['page']-1:0);
  886. if ($page<0) $page=0;
  887. $since_id = (x($_REQUEST,'since_id')?$_REQUEST['since_id']:0);
  888. $max_id = (x($_REQUEST,'max_id')?$_REQUEST['max_id']:0);
  889. //$since_id = 0;//$since_id = (x($_REQUEST,'since_id')?$_REQUEST['since_id']:0);
  890. $start = $page*$count;
  891. //$include_entities = (x($_REQUEST,'include_entities')?$_REQUEST['include_entities']:false);
  892. $myurl = $a->get_baseurl() . '/profile/'. $a->user['nickname'];
  893. $myurl = substr($myurl,strpos($myurl,'://')+3);
  894. //$myurl = str_replace(array('www.','.'),array('','\\.'),$myurl);
  895. $myurl = str_replace('www.','',$myurl);
  896. $diasp_url = str_replace('/profile/','/u/',$myurl);
  897. $sql_extra .= sprintf(" AND `item`.`parent` IN (SELECT distinct(`parent`) from item where `author-link` IN ('https://%s', 'http://%s') OR `mention`)",
  898. dbesc(protect_sprintf($myurl)),
  899. dbesc(protect_sprintf($myurl))
  900. );
  901. if ($max_id > 0)
  902. $sql_extra .= ' AND `item`.`id` <= '.intval($max_id);
  903. $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
  904. `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
  905. `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
  906. `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid`
  907. FROM `item`, `contact`
  908. WHERE `item`.`uid` = %d
  909. AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0
  910. AND `contact`.`id` = `item`.`contact-id`
  911. AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  912. $sql_extra
  913. AND `item`.`id`>%d
  914. ORDER BY `item`.`received` DESC LIMIT %d ,%d ",
  915. intval($user_info['uid']),
  916. intval($since_id),
  917. intval($start), intval($count)
  918. );
  919. $ret = api_format_items($r,$user_info);
  920. $data = array('$statuses' => $ret);
  921. switch($type){
  922. case "atom":
  923. case "rss":
  924. $data = api_rss_extra($a, $data, $user_info);
  925. break;
  926. case "as":
  927. $as = api_format_as($a, $ret, $user_info);
  928. $as["title"] = $a->config['sitename']." Mentions";
  929. $as['link']['url'] = $a->get_baseurl()."/";
  930. return($as);
  931. break;
  932. }
  933. return api_apply_template("timeline", $type, $data);
  934. }
  935. api_register_func('api/statuses/mentions','api_statuses_mentions', true);
  936. api_register_func('api/statuses/replies','api_statuses_mentions', true);
  937. function api_statuses_user_timeline(&$a, $type){
  938. if (api_user()===false) return false;
  939. $user_info = api_get_user($a);
  940. // get last newtork messages
  941. logger("api_statuses_user_timeline: api_user: ". api_user() .
  942. "\nuser_info: ".print_r($user_info, true) .
  943. "\n_REQUEST: ".print_r($_REQUEST, true),
  944. LOGGER_DEBUG);
  945. // params
  946. $count = (x($_REQUEST,'count')?$_REQUEST['count']:20);
  947. $page = (x($_REQUEST,'page')?$_REQUEST['page']-1:0);
  948. if ($page<0) $page=0;
  949. $since_id = (x($_REQUEST,'since_id')?$_REQUEST['since_id']:0);
  950. //$since_id = 0;//$since_id = (x($_REQUEST,'since_id')?$_REQUEST['since_id']:0);
  951. $exclude_replies = (x($_REQUEST,'exclude_replies')?1:0);
  952. $conversation_id = (x($_REQUEST,'conversation_id')?$_REQUEST['conversation_id']:0);
  953. $start = $page*$count;
  954. $sql_extra = '';
  955. if ($user_info['self']==1) $sql_extra .= " AND `item`.`wall` = 1 ";
  956. if ($exclude_replies > 0)
  957. $sql_extra .= ' AND `item`.`parent` = `item`.`id`';
  958. if ($conversation_id > 0)
  959. $sql_extra .= ' AND `item`.`parent` = '.intval($conversation_id);
  960. $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
  961. `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
  962. `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
  963. `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid`
  964. FROM `item`, `contact`
  965. WHERE `item`.`uid` = %d
  966. AND `item`.`contact-id` = %d
  967. AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0
  968. AND `contact`.`id` = `item`.`contact-id`
  969. AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  970. $sql_extra
  971. AND `item`.`id`>%d
  972. ORDER BY `item`.`received` DESC LIMIT %d ,%d ",
  973. intval(api_user()),
  974. intval($user_info['id']),
  975. intval($since_id),
  976. intval($start), intval($count)
  977. );
  978. $ret = api_format_items($r,$user_info);
  979. $data = array('$statuses' => $ret);
  980. switch($type){
  981. case "atom":
  982. case "rss":
  983. $data = api_rss_extra($a, $data, $user_info);
  984. }
  985. return api_apply_template("timeline", $type, $data);
  986. }
  987. api_register_func('api/statuses/user_timeline','api_statuses_user_timeline', true);
  988. function api_favorites(&$a, $type){
  989. if (api_user()===false) return false;
  990. $user_info = api_get_user($a);
  991. // in friendica starred item are private
  992. // return favorites only for self
  993. logger('api_favorites: self:' . $user_info['self']);
  994. if ($user_info['self']==0) {
  995. $ret = array();
  996. } else {
  997. // params
  998. $count = (x($_GET,'count')?$_GET['count']:20);
  999. $page = (x($_REQUEST,'page')?$_REQUEST['page']-1:0);
  1000. if ($page<0) $page=0;
  1001. $start = $page*$count;
  1002. $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
  1003. `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
  1004. `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
  1005. `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid`
  1006. FROM `item`, `contact`
  1007. WHERE `item`.`uid` = %d
  1008. AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0
  1009. AND `item`.`starred` = 1
  1010. AND `contact`.`id` = `item`.`contact-id`
  1011. AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  1012. $sql_extra
  1013. ORDER BY `item`.`received` DESC LIMIT %d ,%d ",
  1014. intval($user_info['uid']),
  1015. intval($start), intval($count)
  1016. );
  1017. $ret = api_format_items($r,$user_info);
  1018. }
  1019. $data = array('$statuses' => $ret);
  1020. switch($type){
  1021. case "atom":
  1022. case "rss":
  1023. $data = api_rss_extra($a, $data, $user_info);
  1024. }
  1025. return api_apply_template("timeline", $type, $data);
  1026. }
  1027. api_register_func('api/favorites','api_favorites', true);
  1028. function api_format_as($a, $ret, $user_info) {
  1029. $as = array();
  1030. $as['title'] = $a->config['sitename']." Public Timeline";
  1031. $items = array();
  1032. foreach ($ret as $item) {
  1033. $singleitem["actor"]["displayName"] = $item["user"]["name"];
  1034. $singleitem["actor"]["id"] = $item["user"]["contact_url"];
  1035. $avatar[0]["url"] = $item["user"]["profile_image_url"];
  1036. $avatar[0]["rel"] = "avatar";
  1037. $avatar[0]["type"] = "";
  1038. $avatar[0]["width"] = 96;
  1039. $avatar[0]["height"] = 96;
  1040. $avatar[1]["url"] = $item["user"]["profile_image_url"];
  1041. $avatar[1]["rel"] = "avatar";
  1042. $avatar[1]["type"] = "";
  1043. $avatar[1]["width"] = 48;
  1044. $avatar[1]["height"] = 48;
  1045. $avatar[2]["url"] = $item["user"]["profile_image_url"];
  1046. $avatar[2]["rel"] = "avatar";
  1047. $avatar[2]["type"] = "";
  1048. $avatar[2]["width"] = 24;
  1049. $avatar[2]["height"] = 24;
  1050. $singleitem["actor"]["avatarLinks"] = $avatar;
  1051. $singleitem["actor"]["image"]["url"] = $item["user"]["profile_image_url"];
  1052. $singleitem["actor"]["image"]["rel"] = "avatar";
  1053. $singleitem["actor"]["image"]["type"] = "";
  1054. $singleitem["actor"]["image"]["width"] = 96;
  1055. $singleitem["actor"]["image"]["height"] = 96;
  1056. $singleitem["actor"]["type"] = "person";
  1057. $singleitem["actor"]["url"] = $item["person"]["contact_url"];
  1058. $singleitem["actor"]["statusnet:profile_info"]["local_id"] = $item["user"]["id"];
  1059. $singleitem["actor"]["statusnet:profile_info"]["following"] = $item["user"]["following"] ? "true" : "false";
  1060. $singleitem["actor"]["statusnet:profile_info"]["blocking"] = "false";
  1061. $singleitem["actor"]["contact"]["preferredUsername"] = $item["user"]["screen_name"];
  1062. $singleitem["actor"]["contact"]["displayName"] = $item["user"]["name"];
  1063. $singleitem["actor"]["contact"]["addresses"] = "";
  1064. $singleitem["body"] = $item["text"];
  1065. $singleitem["object"]["displayName"] = $item["text"];
  1066. $singleitem["object"]["id"] = $item["url"];
  1067. $singleitem["object"]["type"] = "note";
  1068. $singleitem["object"]["url"] = $item["url"];
  1069. //$singleitem["context"] =;
  1070. $singleitem["postedTime"] = date("c", strtotime($item["published"]));
  1071. $singleitem["provider"]["objectType"] = "service";
  1072. $singleitem["provider"]["displayName"] = "Test";
  1073. $singleitem["provider"]["url"] = "http://test.tld";
  1074. $singleitem["title"] = $item["text"];
  1075. $singleitem["verb"] = "post";
  1076. $singleitem["statusnet:notice_info"]["local_id"] = $item["id"];
  1077. $singleitem["statusnet:notice_info"]["source"] = $item["source"];
  1078. $singleitem["statusnet:notice_info"]["favorite"] = "false";
  1079. $singleitem["statusnet:notice_info"]["repeated"] = "false";
  1080. //$singleitem["original"] = $item;
  1081. $items[] = $singleitem;
  1082. }
  1083. $as['items'] = $items;
  1084. $as['link']['url'] = $a->get_baseurl()."/".$user_info["screen_name"]."/all";
  1085. $as['link']['rel'] = "alternate";
  1086. $as['link']['type'] = "text/html";
  1087. return($as);
  1088. }
  1089. function api_format_messages($item, $recipient, $sender) {
  1090. // standard meta information
  1091. $ret=Array(
  1092. 'id' => $item['id'],
  1093. 'created_at' => api_date($item['created']),
  1094. 'sender_id' => $sender['id'] ,
  1095. 'sender_screen_name' => $sender['screen_name'],
  1096. 'sender' => $sender,
  1097. 'recipient_id' => $recipient['id'],
  1098. 'recipient_screen_name' => $recipient['screen_name'],
  1099. 'recipient' => $recipient,
  1100. );
  1101. //don't send title to regular StatusNET requests to avoid confusing these apps
  1102. if (x($_GET, 'getText')) {
  1103. $ret['title'] = $item['title'] ;
  1104. if ($_GET["getText"] == "html") {
  1105. $ret['text'] = bbcode($item['body']);
  1106. }
  1107. elseif ($_GET["getText"] == "plain") {
  1108. $ret['text'] = html2plain(bbcode($item['body'], false, false, true), 0);
  1109. }
  1110. }
  1111. else {
  1112. $ret['text'] = $item['title']."\n".html2plain(bbcode($item['body'], false, false, true), 0);
  1113. }
  1114. if (isset($_GET["getUserObjects"]) && $_GET["getUserObjects"] == "false") {
  1115. unset($ret['sender']);
  1116. unset($ret['recipient']);
  1117. }
  1118. return $ret;
  1119. }
  1120. function api_format_items($r,$user_info) {
  1121. //logger('api_format_items: ' . print_r($r,true));
  1122. //logger('api_format_items: ' . print_r($user_info,true));
  1123. $a = get_app();
  1124. $ret = Array();
  1125. foreach($r as $item) {
  1126. localize_item($item);
  1127. $status_user = (($item['cid']==$user_info['id'])?$user_info: api_item_get_user($a,$item));
  1128. if ($item['parent']!=$item['id']) {
  1129. $r = q("select id from item where parent=%s and id<%s order by id desc limit 1",
  1130. intval($item['parent']), intval($item['id']));
  1131. if ($r)
  1132. $in_reply_to_status_id = $r[0]['id'];
  1133. else
  1134. $in_reply_to_status_id = $item['parent'];
  1135. $r = q("select `item`.`contact-id`, `contact`.nick, `item`.`author-name` from item, contact
  1136. where `contact`.`id` = `item`.`contact-id` and `item`.id=%d", intval($in_reply_to_status_id));
  1137. $in_reply_to_screen_name = $r[0]['author-name'];
  1138. $in_reply_to_user_id = $r[0]['contact-id'];
  1139. } else {
  1140. $in_reply_to_screen_name = '';
  1141. $in_reply_to_user_id = 0;
  1142. $in_reply_to_status_id = 0;
  1143. }
  1144. // Workaround for ostatus messages where the title is identically to the body
  1145. $statusbody = trim(html2plain(bbcode($item['body'], false, false, true), 0));
  1146. $statustitle = trim($item['title']);
  1147. if (($statustitle != '') and (strpos($statusbody, $statustitle) !== false))
  1148. $statustext = trim($statusbody);
  1149. else
  1150. $statustext = trim($statustitle."\n\n".$statusbody);
  1151. if (($item["network"] == NETWORK_FEED) and (strlen($statustext)> 1000))
  1152. $statustext = substr($statustext, 0, 1000)."... \n".$item["plink"];
  1153. $status = array(
  1154. 'text' => $statustext,
  1155. 'truncated' => False,
  1156. 'created_at'=> api_date($item['created']),
  1157. 'in_reply_to_status_id' => $in_reply_to_status_id,
  1158. 'source' => (($item['app']) ? $item['app'] : 'web'),
  1159. 'id' => intval($item['id']),
  1160. 'in_reply_to_user_id' => $in_reply_to_user_id,
  1161. 'in_reply_to_screen_name' => $in_reply_to_screen_name,
  1162. 'geo' => '',
  1163. 'favorited' => $item['starred'] ? true : false,
  1164. 'user' => $status_user ,
  1165. 'statusnet_html' => trim(bbcode($item['body'])),
  1166. 'statusnet_conversation_id' => $item['parent'],
  1167. );
  1168. // Seesmic doesn't like the following content
  1169. if ($_SERVER['HTTP_USER_AGENT'] != 'Seesmic') {
  1170. $status2 = array(
  1171. 'updated' => api_date($item['edited']),
  1172. 'published' => api_date($item['created']),
  1173. 'message_id' => $item['uri'],
  1174. 'url' => ($item['plink']!=''?$item['plink']:$item['author-link']),
  1175. 'coordinates' => $item['coord'],
  1176. 'place' => $item['location'],
  1177. 'contributors' => '',
  1178. 'annotations' => '',
  1179. 'entities' => '',
  1180. 'objecttype' => (($item['object-type']) ? $item['object-type'] : ACTIVITY_OBJ_NOTE),
  1181. 'verb' => (($item['verb']) ? $item['verb'] : ACTIVITY_POST),
  1182. 'self' => $a->get_baseurl()."/api/statuses/show/".$item['id'].".".$type,
  1183. 'edit' => $a->get_baseurl()."/api/statuses/show/".$item['id'].".".$type,
  1184. );
  1185. $status = array_merge($status, $status2);
  1186. }
  1187. $ret[]=$status;
  1188. };
  1189. return $ret;
  1190. }
  1191. function api_account_rate_limit_status(&$a,$type) {
  1192. $hash = array(
  1193. 'reset_time_in_seconds' => strtotime('now + 1 hour'),
  1194. 'remaining_hits' => (string) 150,
  1195. 'hourly_limit' => (string) 150,
  1196. 'reset_time' => datetime_convert('UTC','UTC','now + 1 hour',ATOM_TIME),
  1197. );
  1198. if ($type == "xml")
  1199. $hash['resettime_in_seconds'] = $hash['reset_time_in_seconds'];
  1200. return api_apply_template('ratelimit', $type, array('$hash' => $hash));
  1201. }
  1202. api_register_func('api/account/rate_limit_status','api_account_rate_limit_status',true);
  1203. function api_help_test(&$a,$type) {
  1204. if ($type == 'xml')
  1205. $ok = "true";
  1206. else
  1207. $ok = "ok";
  1208. return api_apply_template('test', $type, array('$ok' => $ok));
  1209. }
  1210. api_register_func('api/help/test','api_help_test',false);
  1211. /**
  1212. * https://dev.twitter.com/docs/api/1/get/statuses/friends
  1213. * This function is deprecated by Twitter
  1214. * returns: json, xml
  1215. **/
  1216. function api_statuses_f(&$a, $type, $qtype) {
  1217. if (api_user()===false) return false;
  1218. $user_info = api_get_user($a);
  1219. // friends and followers only for self
  1220. if ($user_info['self']==0){
  1221. return false;
  1222. }
  1223. if (x($_GET,'cursor') && $_GET['cursor']=='undefined'){
  1224. /* this is to stop Hotot to load friends multiple times
  1225. * I'm not sure if I'm missing return something or
  1226. * is a bug in hotot. Workaround, meantime
  1227. */
  1228. /*$ret=Array();
  1229. return array('$users' => $ret);*/
  1230. return false;
  1231. }
  1232. if($qtype == 'friends')
  1233. $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(CONTACT_IS_SHARING), intval(CONTACT_IS_FRIEND));
  1234. if($qtype == 'followers')
  1235. $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(CONTACT_IS_FOLLOWER), intval(CONTACT_IS_FRIEND));
  1236. $r = q("SELECT id FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 $sql_extra",
  1237. intval(api_user())
  1238. );
  1239. $ret = array();
  1240. foreach($r as $cid){
  1241. $ret[] = api_get_user($a, $cid['id']);
  1242. }
  1243. return array('$users' => $ret);
  1244. }
  1245. function api_statuses_friends(&$a, $type){
  1246. $data = api_statuses_f($a,$type,"friends");
  1247. if ($data===false) return false;
  1248. return api_apply_template("friends", $type, $data);
  1249. }
  1250. function api_statuses_followers(&$a, $type){
  1251. $data = api_statuses_f($a,$type,"followers");
  1252. if ($data===false) return false;
  1253. return api_apply_template("friends", $type, $data);
  1254. }
  1255. api_register_func('api/statuses/friends','api_statuses_friends',true);
  1256. api_register_func('api/statuses/followers','api_statuses_followers',true);
  1257. function api_statusnet_config(&$a,$type) {
  1258. $name = $a->config['sitename'];
  1259. $server = $a->get_hostname();
  1260. $logo = $a->get_baseurl() . '/images/friendica-64.png';
  1261. $email = $a->config['admin_email'];
  1262. $closed = (($a->config['register_policy'] == REGISTER_CLOSED) ? 'true' : 'false');
  1263. $private = (($a->config['system']['block_public']) ? 'true' : 'false');
  1264. $textlimit = (string) (($a->config['max_import_size']) ? $a->config['max_import_size'] : 200000);
  1265. if($a->config['api_import_size'])
  1266. $texlimit = string($a->config['api_import_size']);
  1267. $ssl = (($a->config['system']['have_ssl']) ? 'true' : 'false');
  1268. $sslserver = (($ssl === 'true') ? str_replace('http:','https:',$a->get_baseurl()) : '');
  1269. $config = array(
  1270. 'site' => array('name' => $name,'server' => $server, 'theme' => 'default', 'path' => '',
  1271. 'logo' => $logo, 'fancy' => 'true', 'language' => 'en', 'email' => $email, 'broughtby' => '',
  1272. 'broughtbyurl' => '', 'timezone' => 'UTC', 'closed' => $closed, 'inviteonly' => 'false',
  1273. 'private' => $private, 'textlimit' => $textlimit, 'sslserver' => $sslserver, 'ssl' => $ssl,
  1274. 'shorturllength' => '30',
  1275. 'friendica' => array(
  1276. 'FRIENDICA_PLATFORM' => FRIENDICA_PLATFORM,
  1277. 'FRIENDICA_VERSION' => FRIENDICA_VERSION,
  1278. 'DFRN_PROTOCOL_VERSION' => DFRN_PROTOCOL_VERSION,
  1279. 'DB_UPDATE_VERSION' => DB_UPDATE_VERSION
  1280. )
  1281. ),
  1282. );
  1283. return api_apply_template('config', $type, array('$config' => $config));
  1284. }
  1285. api_register_func('api/statusnet/config','api_statusnet_config',false);
  1286. function api_statusnet_version(&$a,$type) {
  1287. // liar
  1288. if($type === 'xml') {
  1289. header("Content-type: application/xml");
  1290. echo '<?xml version="1.0" encoding="UTF-8"?>' . "\r\n" . '<version>0.9.7</version>' . "\r\n";
  1291. killme();
  1292. }
  1293. elseif($type === 'json') {
  1294. header("Content-type: application/json");
  1295. echo '"0.9.7"';
  1296. killme();
  1297. }
  1298. }
  1299. api_register_func('api/statusnet/version','api_statusnet_version',false);
  1300. function api_ff_ids(&$a,$type,$qtype) {
  1301. if(! api_user())
  1302. return false;
  1303. if($qtype == 'friends')
  1304. $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(CONTACT_IS_SHARING), intval(CONTACT_IS_FRIEND));
  1305. if($qtype == 'followers')
  1306. $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(CONTACT_IS_FOLLOWER), intval(CONTACT_IS_FRIEND));
  1307. $r = q("SELECT id FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 $sql_extra",
  1308. intval(api_user())
  1309. );
  1310. if(is_array($r)) {
  1311. if($type === 'xml') {
  1312. header("Content-type: application/xml");
  1313. echo '<?xml version="1.0" encoding="UTF-8"?>' . "\r\n" . '<ids>' . "\r\n";
  1314. foreach($r as $rr)
  1315. echo '<id>' . $rr['id'] . '</id>' . "\r\n";
  1316. echo '</ids>' . "\r\n";
  1317. killme();
  1318. }
  1319. elseif($type === 'json') {
  1320. $ret = array();
  1321. header("Content-type: application/json");
  1322. foreach($r as $rr) $ret[] = $rr['id'];
  1323. echo json_encode($ret);
  1324. killme();
  1325. }
  1326. }
  1327. }
  1328. function api_friends_ids(&$a,$type) {
  1329. api_ff_ids($a,$type,'friends');
  1330. }
  1331. function api_followers_ids(&$a,$type) {
  1332. api_ff_ids($a,$type,'followers');
  1333. }
  1334. api_register_func('api/friends/ids','api_friends_ids',true);
  1335. api_register_func('api/followers/ids','api_followers_ids',true);
  1336. function api_direct_messages_new(&$a, $type) {
  1337. if (api_user()===false) return false;
  1338. if (!x($_POST, "text") || !x($_POST,"screen_name")) return;
  1339. $sender = api_get_user($a);
  1340. require_once("include/message.php");
  1341. $r = q("SELECT `id` FROM `contact` WHERE `uid`=%d AND `nick`='%s'",
  1342. intval(api_user()),
  1343. dbesc($_POST['screen_name']));
  1344. $recipient = api_get_user($a, $r[0]['id']);
  1345. $replyto = '';
  1346. $sub = '';
  1347. if (x($_REQUEST,'replyto')) {
  1348. $r = q('SELECT `parent-uri`, `title` FROM `mail` WHERE `uid`=%d AND `id`=%d',
  1349. intval(api_user()),
  1350. intval($_REQUEST['replyto']));
  1351. $replyto = $r[0]['parent-uri'];
  1352. $sub = $r[0]['title'];
  1353. }
  1354. else {
  1355. if (x($_REQUEST,'title')) {
  1356. $sub = $_REQUEST['title'];
  1357. }
  1358. else {
  1359. $sub = ((strlen($_POST['text'])>10)?substr($_POST['text'],0,10)."...":$_POST['text']);
  1360. }
  1361. }
  1362. $id = send_message($recipient['id'], $_POST['text'], $sub, $replyto);
  1363. if ($id>-1) {
  1364. $r = q("SELECT * FROM `mail` WHERE id=%d", intval($id));
  1365. $ret = api_format_messages($r[0], $recipient, $sender);
  1366. } else {
  1367. $ret = array("error"=>$id);
  1368. }
  1369. $data = Array('$messages'=>$ret);
  1370. switch($type){
  1371. case "atom":
  1372. case "rss":
  1373. $data = api_rss_extra($a, $data, $user_info);
  1374. }
  1375. return api_apply_template("direct_messages", $type, $data);
  1376. }
  1377. api_register_func('api/direct_messages/new','api_direct_messages_new',true);
  1378. function api_direct_messages_box(&$a, $type, $box) {
  1379. if (api_user()===false) return false;
  1380. $user_info = api_get_user($a);
  1381. // params
  1382. $count = (x($_GET,'count')?$_GET['count']:20);
  1383. $page = (x($_REQUEST,'page')?$_REQUEST['page']-1:0);
  1384. if ($page<0) $page=0;
  1385. $start = $page*$count;
  1386. $profile_url = $a->get_baseurl() . '/profile/' . $a->user['nickname'];
  1387. if ($box=="sentbox") {
  1388. $sql_extra = "`from-url`='".dbesc( $profile_url )."'";
  1389. }
  1390. elseif ($box=="conversation") {
  1391. $sql_extra = "`parent-uri`='".dbesc( $_GET["uri"] ) ."'";
  1392. }
  1393. elseif ($box=="all") {
  1394. $sql_extra = "true";
  1395. }
  1396. elseif ($box=="inbox") {
  1397. $sql_extra = "`from-url`!='".dbesc( $profile_url )."'";
  1398. }
  1399. $r = q("SELECT * FROM `mail` WHERE uid=%d AND $sql_extra ORDER BY created DESC LIMIT %d,%d",
  1400. intval(api_user()),
  1401. intval($start), intval($count)
  1402. );
  1403. $ret = Array();
  1404. foreach($r as $item) {
  1405. if ($box == "inbox" || $item['from-url'] != $profile_url){
  1406. $recipient = $user_info;
  1407. $sender = api_get_user($a,$item['contact-id']);
  1408. }
  1409. elseif ($box == "sentbox" || $item['from-url'] != $profile_url){
  1410. $recipient = api_get_user($a,$item['contact-id']);
  1411. $sender = $user_info;
  1412. }
  1413. $ret[]=api_format_messages($item, $recipient, $sender);
  1414. }
  1415. $data = array('$messages' => $ret);
  1416. switch($type){
  1417. case "atom":
  1418. case "rss":
  1419. $data = api_rss_extra($a, $data, $user_info);
  1420. }
  1421. return api_apply_template("direct_messages", $type, $data);
  1422. }
  1423. function api_direct_messages_sentbox(&$a, $type){
  1424. return api_direct_messages_box($a, $type, "sentbox");
  1425. }
  1426. function api_direct_messages_inbox(&$a, $type){
  1427. return api_direct_messages_box($a, $type, "inbox");
  1428. }
  1429. function api_direct_messages_all(&$a, $type){
  1430. return api_direct_messages_box($a, $type, "all");
  1431. }
  1432. function api_direct_messages_conversation(&$a, $type){
  1433. return api_direct_messages_box($a, $type, "conversation");
  1434. }
  1435. api_register_func('api/direct_messages/conversation','api_direct_messages_conversation',true);
  1436. api_register_func('api/direct_messages/all','api_direct_messages_all',true);
  1437. api_register_func('api/direct_messages/sent','api_direct_messages_sentbox',true);
  1438. api_register_func('api/direct_messages','api_direct_messages_inbox',true);
  1439. function api_oauth_request_token(&$a, $type){
  1440. try{
  1441. $oauth = new FKOAuth1();
  1442. $r = $oauth->fetch_request_token(OAuthRequest::from_request());
  1443. }catch(Exception $e){
  1444. echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage()); killme();
  1445. }
  1446. echo $r;
  1447. killme();
  1448. }
  1449. function api_oauth_access_token(&$a, $type){
  1450. try{
  1451. $oauth = new FKOAuth1();
  1452. $r = $oauth->fetch_access_token(OAuthRequest::from_request());
  1453. }catch(Exception $e){
  1454. echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage()); killme();
  1455. }
  1456. echo $r;
  1457. killme();
  1458. }
  1459. api_register_func('api/oauth/request_token', 'api_oauth_request_token', false);
  1460. api_register_func('api/oauth/access_token', 'api_oauth_access_token', false);
  1461. /*
  1462. Not implemented by now:
  1463. favorites
  1464. favorites/create
  1465. favorites/destroy
  1466. statuses/retweets_of_me
  1467. friendships/create
  1468. friendships/destroy
  1469. friendships/exists
  1470. friendships/show
  1471. account/update_location
  1472. account/update_profile_background_image
  1473. account/update_profile_image
  1474. blocks/create
  1475. blocks/destroy
  1476. Not implemented in status.net:
  1477. statuses/retweeted_to_me
  1478. statuses/retweeted_by_me
  1479. direct_messages/destroy
  1480. account/end_session
  1481. account/update_delivery_device
  1482. notifications/follow
  1483. notifications/leave
  1484. blocks/exists
  1485. blocks/blocking
  1486. lists
  1487. */