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.

544 lines
16 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
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
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
10 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
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
11 years ago
11 years ago
11 years ago
11 years ago
  1. <?php
  2. require_once("bbcode.php");
  3. require_once("datetime.php");
  4. /*
  5. * Twitter-Like API
  6. *
  7. */
  8. $API = Array();
  9. function api_date($str){
  10. //Wed May 23 06:01:13 +0000 2007
  11. return datetime_convert('UTC', 'UTC', $str, "D M d h:i:s +0000 Y" );
  12. }
  13. function api_register_func($path, $func, $auth=false){
  14. global $API;
  15. $API[$path] = array('func'=>$func,
  16. 'auth'=>$auth);
  17. }
  18. /**
  19. * Simple HTTP Login
  20. */
  21. function api_login(&$a){
  22. // workaround for HTTP-auth in CGI mode
  23. if(x($_SERVER,'REDIRECT_REMOTE_USER')) {
  24. $userpass = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"],6)) ;
  25. if(strlen($userpass)) {
  26. list($name, $password) = explode(':', $userpass);
  27. $_SERVER['PHP_AUTH_USER'] = $name;
  28. $_SERVER['PHP_AUTH_PW'] = $password;
  29. }
  30. }
  31. if (!isset($_SERVER['PHP_AUTH_USER'])) {
  32. logger('API_login: ' . print_r($_SERVER,true), LOGGER_DEBUG);
  33. header('WWW-Authenticate: Basic realm="Friendika"');
  34. header('HTTP/1.0 401 Unauthorized');
  35. die('This api requires login');
  36. }
  37. $user = $_SERVER['PHP_AUTH_USER'];
  38. $encrypted = hash('whirlpool',trim($_SERVER['PHP_AUTH_PW']));
  39. /**
  40. * next code from mod/auth.php. needs better solution
  41. */
  42. // process normal login request
  43. $r = q("SELECT * FROM `user` WHERE ( `email` = '%s' OR `nickname` = '%s' )
  44. AND `password` = '%s' AND `blocked` = 0 AND `verified` = 1 LIMIT 1",
  45. dbesc(trim($user)),
  46. dbesc(trim($user)),
  47. dbesc($encrypted)
  48. );
  49. if(count($r)){
  50. $record = $r[0];
  51. } else {
  52. logger('API_login failure: ' . print_r($_SERVER,true), LOGGER_DEBUG);
  53. header('WWW-Authenticate: Basic realm="Friendika"');
  54. header('HTTP/1.0 401 Unauthorized');
  55. die('This api requires login');
  56. }
  57. $_SESSION['uid'] = $record['uid'];
  58. $_SESSION['theme'] = $record['theme'];
  59. $_SESSION['authenticated'] = 1;
  60. $_SESSION['page_flags'] = $record['page-flags'];
  61. $_SESSION['my_url'] = $a->get_baseurl() . '/profile/' . $record['nickname'];
  62. $_SESSION['addr'] = $_SERVER['REMOTE_ADDR'];
  63. //notice( t("Welcome back ") . $record['username'] . EOL);
  64. $a->user = $record;
  65. if(strlen($a->user['timezone'])) {
  66. date_default_timezone_set($a->user['timezone']);
  67. $a->timezone = $a->user['timezone'];
  68. }
  69. $r = q("SELECT * FROM `contact` WHERE `uid` = %s AND `self` = 1 LIMIT 1",
  70. intval($_SESSION['uid']));
  71. if(count($r)) {
  72. $a->contact = $r[0];
  73. $a->cid = $r[0]['id'];
  74. $_SESSION['cid'] = $a->cid;
  75. }
  76. q("UPDATE `user` SET `login_date` = '%s' WHERE `uid` = %d LIMIT 1",
  77. dbesc(datetime_convert()),
  78. intval($_SESSION['uid'])
  79. );
  80. call_hooks('logged_in', $a->user);
  81. header('X-Account-Management-Status: active; name="' . $a->user['username'] . '"; id="' . $a->user['nickname'] .'"');
  82. }
  83. /**************************
  84. * MAIN API ENTRY POINT *
  85. **************************/
  86. function api_call(&$a){
  87. GLOBAL $API;
  88. foreach ($API as $p=>$info){
  89. if (strpos($a->query_string, $p)===0){
  90. #unset($_SERVER['PHP_AUTH_USER']);
  91. if ($info['auth']===true && local_user()===false) {
  92. api_login($a);
  93. }
  94. $type="json";
  95. if (strpos($a->query_string, ".xml")>0) $type="xml";
  96. if (strpos($a->query_string, ".json")>0) $type="json";
  97. if (strpos($a->query_string, ".rss")>0) $type="rss";
  98. if (strpos($a->query_string, ".atom")>0) $type="atom";
  99. $r = call_user_func($info['func'], $a, $type);
  100. if ($r===false) return;
  101. switch($type){
  102. case "xml":
  103. $r = mb_convert_encoding($r, "UTF-8",mb_detect_encoding($r));
  104. header ("Content-Type: text/xml");
  105. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  106. break;
  107. case "json":
  108. header ("Content-Type: application/json");
  109. foreach($r as $rr)
  110. return json_encode($rr);
  111. break;
  112. case "rss":
  113. header ("Content-Type: application/rss+xml");
  114. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  115. break;
  116. case "atom":
  117. #header ("Content-Type: application/atom+xml");
  118. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  119. break;
  120. }
  121. //echo "<pre>"; var_dump($r); die();
  122. }
  123. }
  124. return false;
  125. }
  126. /**
  127. * RSS extra info
  128. */
  129. function api_rss_extra(&$a, $arr, $user_info){
  130. if (is_null($user_info)) $user_info = api_get_user($a);
  131. $arr['$rss'] = array(
  132. 'alternate' => $user_info['url'],
  133. 'self' => $a->get_baseurl(). "/". $a->query_string,
  134. 'updated' => api_date(null),
  135. 'language' => $user_info['language'],
  136. 'logo' => $a->get_baseurl()."/images/friendika-32.png",
  137. );
  138. return $arr;
  139. }
  140. /**
  141. * Returns user info array.
  142. */
  143. function api_get_user(&$a){
  144. $user = null;
  145. $extra_query = "";
  146. if(x($_GET, 'user_id')) {
  147. $user = intval($_GET['user_id']);
  148. $extra_query = "AND `contact`.`id` = %d ";
  149. }
  150. if(x($_GET, 'screen_name')) {
  151. $user = dbesc($_GET['screen_name']);
  152. $extra_query = "AND `contact`.`nick` = '%s' ";
  153. }
  154. if ($user===null){
  155. list($user, $null) = explode(".",$a->argv[3]);
  156. if(is_numeric($user)){
  157. $user = intval($user);
  158. $extra_query = "AND `contact`.`id` = %d ";
  159. } else {
  160. $user = dbesc($user);
  161. $extra_query = "AND `contact`.`nick` = '%s' ";
  162. }
  163. }
  164. if ($user==='') {
  165. if (local_user()===false) {
  166. api_login($a); return False;
  167. } else {
  168. $user = $_SESSION['uid'];
  169. $extra_query = "AND `user`.`uid` = %d ";
  170. }
  171. }
  172. // user info
  173. $uinfo = q("SELECT *, `contact`.`id` as `cid` FROM `user`, `contact`
  174. WHERE `user`.`uid`=`contact`.`uid` AND `contact`.`self`=1
  175. $extra_query",
  176. $user
  177. );
  178. if (count($uinfo)==0) {
  179. return False;
  180. }
  181. // count public wall messages
  182. $r = q("SELECT COUNT(`id`) as `count` FROM `item`
  183. WHERE `uid` = %d
  184. AND `type`='wall'
  185. AND `allow_cid`='' AND `allow_gid`='' AND `deny_cid`='' AND `deny_gid`=''",
  186. intval($uinfo[0]['uid'])
  187. );
  188. $countitms = $r[0]['count'];
  189. // count friends
  190. $r = q("SELECT COUNT(`id`) as `count` FROM `contact`
  191. WHERE `uid` = %d
  192. AND `self`=0 AND `blocked`=0",
  193. intval($uinfo[0]['uid'])
  194. );
  195. $countfriends = $r[0]['count'];
  196. $ret = Array(
  197. 'id' => $uinfo[0]['cid'],
  198. 'name' => $uinfo[0]['username'],
  199. 'screen_name' => $uinfo[0]['nickname'],
  200. 'location' => $uinfo[0]['default-location'],
  201. 'profile_image_url' => $uinfo[0]['micro'],
  202. 'url' => $uinfo[0]['url'],
  203. 'protected' => false, #
  204. 'friends_count' => $countfriends,
  205. 'created_at' => api_date($uinfo[0]['created']),
  206. 'utc_offset' => 0, #XXX: fix me
  207. 'time_zone' => $uinfo[0]['timezone'],
  208. 'geo_enabled' => false,
  209. 'statuses_count' => $countitms, #XXX: fix me
  210. 'lang' => 'en', #XXX: fix me
  211. 'description' => '',
  212. 'followers_count' => $countfriends, #XXX: fix me
  213. 'favourites_count' => 0,
  214. 'contributors_enabled' => false,
  215. 'follow_request_sent' => false,
  216. 'profile_background_color' => 'cfe8f6',
  217. 'profile_text_color' => '000000',
  218. 'profile_link_color' => 'FF8500',
  219. 'profile_sidebar_fill_color' =>'AD0066',
  220. 'profile_sidebar_border_color' => 'AD0066',
  221. 'profile_background_image_url' => '',
  222. 'profile_background_tile' => false,
  223. 'profile_use_background_image' => false,
  224. 'notifications' => false,
  225. 'verified' => true, #XXX: fix me
  226. 'followers' => '', #XXX: fix me
  227. #'status' => null
  228. );
  229. return $ret;
  230. }
  231. /**
  232. * apply xmlify() to all values of array $val, recursively
  233. */
  234. function api_xmlify($val){
  235. if (is_bool($val)) return $val?"true":"false";
  236. if (is_array($val)) return array_map('api_xmlify', $val);
  237. return xmlify($val);
  238. }
  239. /**
  240. * load api $templatename for $type and replace $data array
  241. */
  242. function api_apply_template($templatename, $type, $data){
  243. switch($type){
  244. case "rss":
  245. case "atom":
  246. case "xml":
  247. $data = api_xmlify($data);
  248. $tpl = get_markup_template("api_".$templatename."_".$type.".tpl");
  249. $ret = replace_macros($tpl, $data);
  250. break;
  251. case "json":
  252. $ret = $data;
  253. break;
  254. }
  255. return $ret;
  256. }
  257. /**
  258. ** TWITTER API
  259. */
  260. /**
  261. * Returns an HTTP 200 OK response code and a representation of the requesting user if authentication was successful;
  262. * returns a 401 status code and an error message if not.
  263. * http://developer.twitter.com/doc/get/account/verify_credentials
  264. */
  265. function api_account_verify_credentials(&$a, $type){
  266. if (local_user()===false) return false;
  267. $user_info = api_get_user($a);
  268. return api_apply_template("user", $type, array('$user' => $user_info));
  269. }
  270. api_register_func('api/account/verify_credentials','api_account_verify_credentials', true);
  271. // TODO - media uploads
  272. function api_statuses_update(&$a, $type) {
  273. if (local_user()===false) return false;
  274. $user_info = api_get_user($a);
  275. // convert $_POST array items to the form we use for web posts.
  276. $_POST['body'] = urldecode($_POST['status']);
  277. $_POST['parent'] = $_POST['in_reply_to_status_id'];
  278. if($_POST['lat'] && $_POST['long'])
  279. $_POST['coord'] = sprintf("%s %s",$_POST['lat'],$_POST['long']);
  280. $_POST['profile_uid'] = local_user();
  281. if($_POST['parent'])
  282. $_POST['type'] = 'net-comment';
  283. else
  284. $_POST['type'] = 'wall';
  285. // set this so that the item_post() function is quiet and doesn't redirect or emit json
  286. $_POST['api_source'] = true;
  287. // call out normal post function
  288. require_once('mod/item.php');
  289. item_post($a);
  290. // this should output the last post (the one we just posted).
  291. return api_status_show($a,$type);
  292. }
  293. api_register_func('api/statuses/update','api_statuses_update', true);
  294. function api_status_show(&$a, $type){
  295. $user_info = api_get_user($a);
  296. // get last public wall message
  297. $lastwall = q("SELECT `item`.*, `i`.`contact-id` as `reply_uid`, `i`.`nick` as `reply_author`
  298. FROM `item`, `contact`,
  299. (SELECT `item`.`id`, `item`.`contact-id`, `contact`.`nick` FROM `item`,`contact` WHERE `contact`.`id`=`item`.`contact-id`) as `i`
  300. WHERE `item`.`contact-id` = %d
  301. AND `i`.`id` = `item`.`parent`
  302. AND `contact`.`id`=`item`.`contact-id` AND `contact`.`self`=1
  303. AND `type`!='activity'
  304. AND `item`.`allow_cid`='' AND `item`.`allow_gid`='' AND `item`.`deny_cid`='' AND `item`.`deny_gid`=''
  305. ORDER BY `created` DESC
  306. LIMIT 1",
  307. intval($user_info['id'])
  308. );
  309. if (count($lastwall)>0){
  310. $lastwall = $lastwall[0];
  311. $in_reply_to_status_id = '';
  312. $in_reply_to_user_id = '';
  313. $in_reply_to_screen_name = '';
  314. if ($lastwall['parent']!=$lastwall['id']) {
  315. $in_reply_to_status_id=$lastwall['parent'];
  316. $in_reply_to_user_id = $lastwall['reply_uid'];
  317. $in_reply_to_screen_name = $lastwall['reply_author'];
  318. }
  319. $status_info = array(
  320. 'created_at' => api_date($lastwall['created']),
  321. 'id' => $lastwall['contact-id'],
  322. 'text' => strip_tags(bbcode($lastwall['body'])),
  323. 'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'),
  324. 'truncated' => false,
  325. 'in_reply_to_status_id' => $in_reply_to_status_id,
  326. 'in_reply_to_user_id' => $in_reply_to_user_id,
  327. 'favorited' => false,
  328. 'in_reply_to_screen_name' => $in_reply_to_screen_name,
  329. 'geo' => '',
  330. 'coordinates' => $lastwall['coord'],
  331. 'place' => $lastwall['location'],
  332. 'contributors' => ''
  333. );
  334. $status_info['user'] = $user_info;
  335. }
  336. return api_apply_template("status", $type, array('$status' => $status_info));
  337. }
  338. /**
  339. * Returns extended information of a given user, specified by ID or screen name as per the required id parameter.
  340. * The author's most recent status will be returned inline.
  341. * http://developer.twitter.com/doc/get/users/show
  342. */
  343. function api_users_show(&$a, $type){
  344. $user_info = api_get_user($a);
  345. // get last public wall message
  346. $lastwall = q("SELECT `item`.*, `i`.`contact-id` as `reply_uid`, `i`.`nick` as `reply_author`
  347. FROM `item`, `contact`,
  348. (SELECT `item`.`id`, `item`.`contact-id`, `contact`.`nick` FROM `item`,`contact` WHERE `contact`.`id`=`item`.`contact-id`) as `i`
  349. WHERE `item`.`contact-id` = %d
  350. AND `i`.`id` = `item`.`parent`
  351. AND `contact`.`id`=`item`.`contact-id` AND `contact`.`self`=1
  352. AND `type`!='activity'
  353. AND `item`.`allow_cid`='' AND `item`.`allow_gid`='' AND `item`.`deny_cid`='' AND `item`.`deny_gid`=''
  354. ORDER BY `created` DESC
  355. LIMIT 1",
  356. intval($user_info['id'])
  357. );
  358. if (count($lastwall)>0){
  359. $lastwall = $lastwall[0];
  360. $in_reply_to_status_id = '';
  361. $in_reply_to_user_id = '';
  362. $in_reply_to_screen_name = '';
  363. if ($lastwall['parent']!=$lastwall['id']) {
  364. $in_reply_to_status_id=$lastwall['parent'];
  365. $in_reply_to_user_id = $lastwall['reply_uid'];
  366. $in_reply_to_screen_name = $lastwall['reply_author'];
  367. }
  368. $user_info['status'] = array(
  369. 'created_at' => api_date($lastwall['created']),
  370. 'id' => $lastwall['contact-id'],
  371. 'text' => strip_tags(bbcode($lastwall['body'])),
  372. 'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'),
  373. 'truncated' => false,
  374. 'in_reply_to_status_id' => $in_reply_to_status_id,
  375. 'in_reply_to_user_id' => $in_reply_to_user_id,
  376. 'favorited' => false,
  377. 'in_reply_to_screen_name' => $in_reply_to_screen_name,
  378. 'geo' => '',
  379. 'coordinates' => $lastwall['coord'],
  380. 'place' => $lastwall['location'],
  381. 'contributors' => ''
  382. );
  383. }
  384. return api_apply_template("user", $type, array('$user' => $user_info));
  385. }
  386. api_register_func('api/users/show','api_users_show');
  387. /**
  388. *
  389. * http://developer.twitter.com/doc/get/statuses/home_timeline
  390. *
  391. * TODO: Optional parameters
  392. * TODO: Add reply info
  393. */
  394. function api_statuses_home_timeline(&$a, $type){
  395. if (local_user()===false) return false;
  396. $user_info = api_get_user($a);
  397. // get last newtork messages
  398. $sql_extra = " AND `item`.`parent` IN ( SELECT `parent` FROM `item` WHERE `id` = `parent` ) ";
  399. $r = q("SELECT `item`.*, `item`.`id` AS `item_id`,
  400. `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
  401. `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`,
  402. `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid`
  403. FROM `item`, `contact`, `user`
  404. WHERE `item`.`contact-id` = %d AND `user`.`uid` = `item`.`uid`
  405. AND `item`.`visible` = 1 AND `item`.`deleted` = 0
  406. AND `contact`.`id` = `item`.`contact-id`
  407. AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0
  408. $sql_extra
  409. ORDER BY `item`.`created` DESC LIMIT %d ,%d ",
  410. intval($user_info['id']),
  411. 0,20
  412. );
  413. $ret = Array();
  414. foreach($r as $item) {
  415. $status = array(
  416. 'created_at'=> api_date($item['created']),
  417. 'published' => datetime_convert('UTC','UTC',$item['created'],ATOM_TIME),
  418. 'updated' => datetime_convert('UTC','UTC',$item['edited'],ATOM_TIME),
  419. 'id' => $item['id'],
  420. 'text' => strip_tags(bbcode($item['body'])),
  421. 'html' => bbcode($item['body']),
  422. 'source' => (($item['app']) ? $item['app'] : 'web'),
  423. 'url' => ($item['plink']!=''?$item['plink']:$item['author-link']),
  424. 'truncated' => False,
  425. 'in_reply_to_status_id' => ($item['parent']!=$item['id']?$item['parent']:''),
  426. 'in_reply_to_user_id' => '',
  427. 'favorited' => false,
  428. 'in_reply_to_screen_name' => '',
  429. 'geo' => '',
  430. 'coordinates' => $item['coord'],
  431. 'place' => $item['location'],
  432. 'contributors' => '',
  433. 'annotations' => '',
  434. 'entities' => '',
  435. 'user' => $user_info,
  436. 'objecttype' => $item['object-type'],
  437. 'verb' => $item['verb'],
  438. 'self' => $a->get_baseurl()."/api/statuses/show/".$ite['id'].".".$type,
  439. 'edit' => $a->get_baseurl()."/api/statuses/show/".$ite['id'].".".$type,
  440. );
  441. $ret[]=$status;
  442. };
  443. $data = array('$statuses' => $ret);
  444. switch($type){
  445. case "atom":
  446. case "rss":
  447. $data = api_rss_extra($a, $data, $user_info);
  448. }
  449. return api_apply_template("timeline", $type, $data);
  450. }
  451. api_register_func('api/statuses/home_timeline','api_statuses_home_timeline', true);
  452. api_register_func('api/statuses/friends_timeline','api_statuses_home_timeline', true);
  453. api_register_func('api/statuses/user_timeline','api_statuses_home_timeline', true);
  454. # TODO: user_timeline should be profile view
  455. function api_account_rate_limit_status(&$a,$type) {
  456. $hash = array(
  457. 'remaining_hits' => (string) 150,
  458. 'hourly_limit' => (string) 150,
  459. 'reset_time' => datetime_convert('UTC','UTC','now + 1 hour',ATOM_TIME),
  460. 'reset_time_in_seconds' => strtotime('now + 1 hour')
  461. );
  462. return api_apply_template('ratelimit', $type, array('$hash' => $hash));
  463. }
  464. api_register_func('api/account/rate_limit_status','api_account_rate_limit_status',true);