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.

2619 lines
82 KiB

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
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
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
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
11 years ago
10 years ago
11 years ago
10 years ago
10 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
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
10 years ago
11 years ago
11 years ago
10 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
10 years ago
10 years ago
11 years ago
10 years ago
10 years ago
11 years ago
10 years ago
10 years ago
11 years ago
10 years ago
10 years ago
  1. <?php
  2. /* To-Do:
  3. - Automatically detect if incoming data is HTML or BBCode
  4. */
  5. require_once("include/bbcode.php");
  6. require_once("include/datetime.php");
  7. require_once("include/conversation.php");
  8. require_once("include/oauth.php");
  9. require_once("include/html2plain.php");
  10. /*
  11. * Twitter-Like API
  12. *
  13. */
  14. $API = Array();
  15. $called_api = Null;
  16. function api_user() {
  17. // It is not sufficient to use local_user() to check whether someone is allowed to use the API,
  18. // because this will open CSRF holes (just embed an image with src=friendicasite.com/api/statuses/update?status=CSRF
  19. // into a page, and visitors will post something without noticing it).
  20. // Instead, use this function.
  21. if ($_SESSION["allow_api"])
  22. return local_user();
  23. return false;
  24. }
  25. function api_date($str){
  26. //Wed May 23 06:01:13 +0000 2007
  27. return datetime_convert('UTC', 'UTC', $str, "D M d H:i:s +0000 Y" );
  28. }
  29. function api_register_func($path, $func, $auth=false){
  30. global $API;
  31. $API[$path] = array('func'=>$func, 'auth'=>$auth);
  32. // Workaround for hotot
  33. $path = str_replace("api/", "api/1.1/", $path);
  34. $API[$path] = array('func'=>$func, 'auth'=>$auth);
  35. }
  36. /**
  37. * Simple HTTP Login
  38. */
  39. function api_login(&$a){
  40. // login with oauth
  41. try{
  42. $oauth = new FKOAuth1();
  43. list($consumer,$token) = $oauth->verify_request(OAuthRequest::from_request());
  44. if (!is_null($token)){
  45. $oauth->loginUser($token->uid);
  46. call_hooks('logged_in', $a->user);
  47. return;
  48. }
  49. echo __file__.__line__.__function__."<pre>"; var_dump($consumer, $token); die();
  50. }catch(Exception $e){
  51. logger(__file__.__line__.__function__."\n".$e);
  52. //die(__file__.__line__.__function__."<pre>".$e); die();
  53. }
  54. // workaround for HTTP-auth in CGI mode
  55. if(x($_SERVER,'REDIRECT_REMOTE_USER')) {
  56. $userpass = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"],6)) ;
  57. if(strlen($userpass)) {
  58. list($name, $password) = explode(':', $userpass);
  59. $_SERVER['PHP_AUTH_USER'] = $name;
  60. $_SERVER['PHP_AUTH_PW'] = $password;
  61. }
  62. }
  63. if (!isset($_SERVER['PHP_AUTH_USER'])) {
  64. logger('API_login: ' . print_r($_SERVER,true), LOGGER_DEBUG);
  65. header('WWW-Authenticate: Basic realm="Friendica"');
  66. header('HTTP/1.0 401 Unauthorized');
  67. die((api_error($a, 'json', "This api requires login")));
  68. //die('This api requires login');
  69. }
  70. $user = $_SERVER['PHP_AUTH_USER'];
  71. $encrypted = hash('whirlpool',trim($_SERVER['PHP_AUTH_PW']));
  72. /**
  73. * next code from mod/auth.php. needs better solution
  74. */
  75. // process normal login request
  76. $r = q("SELECT * FROM `user` WHERE ( `email` = '%s' OR `nickname` = '%s' )
  77. AND `password` = '%s' AND `blocked` = 0 AND `account_expired` = 0 AND `account_removed` = 0 AND `verified` = 1 LIMIT 1",
  78. dbesc(trim($user)),
  79. dbesc(trim($user)),
  80. dbesc($encrypted)
  81. );
  82. if(count($r)){
  83. $record = $r[0];
  84. } else {
  85. logger('API_login failure: ' . print_r($_SERVER,true), LOGGER_DEBUG);
  86. header('WWW-Authenticate: Basic realm="Friendica"');
  87. header('HTTP/1.0 401 Unauthorized');
  88. die('This api requires login');
  89. }
  90. require_once('include/security.php');
  91. authenticate_success($record); $_SESSION["allow_api"] = true;
  92. call_hooks('logged_in', $a->user);
  93. }
  94. /**************************
  95. * MAIN API ENTRY POINT *
  96. **************************/
  97. function api_call(&$a){
  98. GLOBAL $API, $called_api;
  99. // preset
  100. $type="json";
  101. foreach ($API as $p=>$info){
  102. if (strpos($a->query_string, $p)===0){
  103. $called_api= explode("/",$p);
  104. //unset($_SERVER['PHP_AUTH_USER']);
  105. if ($info['auth']===true && api_user()===false) {
  106. api_login($a);
  107. }
  108. load_contact_links(api_user());
  109. logger('API call for ' . $a->user['username'] . ': ' . $a->query_string);
  110. logger('API parameters: ' . print_r($_REQUEST,true));
  111. $type="json";
  112. if (strpos($a->query_string, ".xml")>0) $type="xml";
  113. if (strpos($a->query_string, ".json")>0) $type="json";
  114. if (strpos($a->query_string, ".rss")>0) $type="rss";
  115. if (strpos($a->query_string, ".atom")>0) $type="atom";
  116. if (strpos($a->query_string, ".as")>0) $type="as";
  117. $r = call_user_func($info['func'], $a, $type);
  118. if ($r===false) return;
  119. switch($type){
  120. case "xml":
  121. $r = mb_convert_encoding($r, "UTF-8",mb_detect_encoding($r));
  122. header ("Content-Type: text/xml");
  123. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  124. break;
  125. case "json":
  126. header ("Content-Type: application/json");
  127. foreach($r as $rr)
  128. return json_encode($rr);
  129. break;
  130. case "rss":
  131. header ("Content-Type: application/rss+xml");
  132. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  133. break;
  134. case "atom":
  135. header ("Content-Type: application/atom+xml");
  136. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  137. break;
  138. case "as":
  139. //header ("Content-Type: application/json");
  140. //foreach($r as $rr)
  141. // return json_encode($rr);
  142. return json_encode($r);
  143. break;
  144. }
  145. //echo "<pre>"; var_dump($r); die();
  146. }
  147. }
  148. header("HTTP/1.1 404 Not Found");
  149. logger('API call not implemented: '.$a->query_string." - ".print_r($_REQUEST,true));
  150. return(api_error($a, $type, "not implemented"));
  151. }
  152. function api_error(&$a, $type, $error) {
  153. $r = "<status><error>".$error."</error><request>".$a->query_string."</request></status>";
  154. switch($type){
  155. case "xml":
  156. header ("Content-Type: text/xml");
  157. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  158. break;
  159. case "json":
  160. header ("Content-Type: application/json");
  161. return json_encode(array('error' => $error, 'request' => $a->query_string));
  162. break;
  163. case "rss":
  164. header ("Content-Type: application/rss+xml");
  165. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  166. break;
  167. case "atom":
  168. header ("Content-Type: application/atom+xml");
  169. return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
  170. break;
  171. }
  172. }
  173. /**
  174. * RSS extra info
  175. */
  176. function api_rss_extra(&$a, $arr, $user_info){
  177. if (is_null($user_info)) $user_info = api_get_user($a);
  178. $arr['$user'] = $user_info;
  179. $arr['$rss'] = array(
  180. 'alternate' => $user_info['url'],
  181. 'self' => $a->get_baseurl(). "/". $a->query_string,
  182. 'base' => $a->get_baseurl(),
  183. 'updated' => api_date(null),
  184. 'atom_updated' => datetime_convert('UTC','UTC','now',ATOM_TIME),
  185. 'language' => $user_info['language'],
  186. 'logo' => $a->get_baseurl()."/images/friendica-32.png",
  187. );
  188. return $arr;
  189. }
  190. /**
  191. * Unique contact to contact url.
  192. */
  193. function api_unique_id_to_url($id){
  194. $r = q("SELECT url FROM unique_contacts WHERE id=%d LIMIT 1",
  195. intval($id));
  196. if ($r)
  197. return ($r[0]["url"]);
  198. else
  199. return false;
  200. }
  201. /**
  202. * Returns user info array.
  203. */
  204. function api_get_user(&$a, $contact_id = Null, $type = "json"){
  205. global $called_api;
  206. $user = null;
  207. $extra_query = "";
  208. $url = "";
  209. $nick = "";
  210. logger("api_get_user: Fetching user data for user ".$contact_id, LOGGER_DEBUG);
  211. // Searching for contact URL
  212. if(!is_null($contact_id) AND (intval($contact_id) == 0)){
  213. $user = dbesc(normalise_link($contact_id));
  214. $url = $user;
  215. $extra_query = "AND `contact`.`nurl` = '%s' ";
  216. if (api_user()!==false) $extra_query .= "AND `contact`.`uid`=".intval(api_user());
  217. }
  218. // Searching for unique contact id
  219. if(!is_null($contact_id) AND (intval($contact_id) != 0)){
  220. $user = dbesc(api_unique_id_to_url($contact_id));
  221. if ($user == "")
  222. die(api_error($a, $type, t("User not found.")));
  223. $url = $user;
  224. $extra_query = "AND `contact`.`nurl` = '%s' ";
  225. if (api_user()!==false) $extra_query .= "AND `contact`.`uid`=".intval(api_user());
  226. }
  227. if(is_null($user) && x($_GET, 'user_id')) {
  228. $user = dbesc(api_unique_id_to_url($_GET['user_id']));
  229. if ($user == "")
  230. die(api_error($a, $type, t("User not found.")));
  231. $url = $user;
  232. $extra_query = "AND `contact`.`nurl` = '%s' ";
  233. if (api_user()!==false) $extra_query .= "AND `contact`.`uid`=".intval(api_user());
  234. }
  235. if(is_null($user) && x($_GET, 'screen_name')) {
  236. $user = dbesc($_GET['screen_name']);
  237. $nick = $user;
  238. $extra_query = "AND `contact`.`nick` = '%s' ";
  239. if (api_user()!==false) $extra_query .= "AND `contact`.`uid`=".intval(api_user());
  240. }
  241. if (is_null($user) AND ($a->argc > (count($called_api)-1)) AND (count($called_api) > 0)){
  242. $argid = count($called_api);
  243. list($user, $null) = explode(".",$a->argv[$argid]);
  244. if(is_numeric($user)){
  245. $user = dbesc(api_unique_id_to_url($user));
  246. if ($user == "")
  247. return false;
  248. $url = $user;
  249. $extra_query = "AND `contact`.`nurl` = '%s' ";
  250. if (api_user()!==false) $extra_query .= "AND `contact`.`uid`=".intval(api_user());
  251. } else {
  252. $user = dbesc($user);
  253. $nick = $user;
  254. $extra_query = "AND `contact`.`nick` = '%s' ";
  255. if (api_user()!==false) $extra_query .= "AND `contact`.`uid`=".intval(api_user());
  256. }
  257. }
  258. logger("api_get_user: user ".$user, LOGGER_DEBUG);
  259. if (!$user) {
  260. if (api_user()===false) {
  261. api_login($a); return False;
  262. } else {
  263. $user = $_SESSION['uid'];
  264. $extra_query = "AND `contact`.`uid` = %d AND `contact`.`self` = 1 ";
  265. }
  266. }
  267. logger('api_user: ' . $extra_query . ', user: ' . $user);
  268. // user info
  269. $uinfo = q("SELECT *, `contact`.`id` as `cid` FROM `contact`
  270. WHERE 1
  271. $extra_query",
  272. $user
  273. );
  274. // Selecting the id by priority, friendica first
  275. api_best_nickname($uinfo);
  276. // if the contact wasn't found, fetch it from the unique contacts
  277. if (count($uinfo)==0) {
  278. $r = array();
  279. if ($url != "")
  280. $r = q("SELECT * FROM unique_contacts WHERE url='%s' LIMIT 1", $url);
  281. elseif ($nick != "")
  282. $r = q("SELECT * FROM unique_contacts WHERE nick='%s' LIMIT 1", $nick);
  283. if ($r) {
  284. // If no nick where given, extract it from the address
  285. if (($r[0]['nick'] == "") OR ($r[0]['name'] == $r[0]['nick']))
  286. $r[0]['nick'] = api_get_nick($r[0]["url"]);
  287. $ret = array(
  288. 'id' => $r[0]["id"],
  289. 'id_str' => (string) $r[0]["id"],
  290. 'name' => $r[0]["name"],
  291. 'screen_name' => (($r[0]['nick']) ? $r[0]['nick'] : $r[0]['name']),
  292. 'location' => NULL,
  293. 'description' => NULL,
  294. 'profile_image_url' => $r[0]["avatar"],
  295. 'profile_image_url_https' => $r[0]["avatar"],
  296. 'url' => $r[0]["url"],
  297. 'protected' => false,
  298. 'followers_count' => 0,
  299. 'friends_count' => 0,
  300. 'created_at' => api_date(0),
  301. 'favourites_count' => 0,
  302. 'utc_offset' => 0,
  303. 'time_zone' => 'UTC',
  304. 'statuses_count' => 0,
  305. 'following' => false,
  306. 'verified' => false,
  307. 'statusnet_blocking' => false,
  308. 'notifications' => false,
  309. 'statusnet_profile_url' => $r[0]["url"],
  310. 'uid' => 0,
  311. 'cid' => 0,
  312. 'self' => 0,
  313. 'network' => '',
  314. );
  315. return $ret;
  316. } else
  317. die(api_error($a, $type, t("User not found.")));
  318. }
  319. if($uinfo[0]['self']) {
  320. $usr = q("select * from user where uid = %d limit 1",
  321. intval(api_user())
  322. );
  323. $profile = q("select * from profile where uid = %d and `is-default` = 1 limit 1",
  324. intval(api_user())
  325. );
  326. //AND `allow_cid`='' AND `allow_gid`='' AND `deny_cid`='' AND `deny_gid`=''",
  327. // count public wall messages
  328. $r = q("SELECT count(*) as `count` FROM `item`
  329. WHERE `uid` = %d
  330. AND `type`='wall'",
  331. intval($uinfo[0]['uid'])
  332. );
  333. $countitms = $r[0]['count'];
  334. }
  335. else {
  336. //AND `allow_cid`='' AND `allow_gid`='' AND `deny_cid`='' AND `deny_gid`=''",
  337. $r = q("SELECT count(*) as `count` FROM `item`
  338. WHERE `contact-id` = %d",
  339. intval($uinfo[0]['id'])
  340. );
  341. $countitms = $r[0]['count'];
  342. }
  343. // count friends
  344. $r = q("SELECT count(*) as `count` FROM `contact`
  345. WHERE `uid` = %d AND `rel` IN ( %d, %d )
  346. AND `self`=0 AND `blocked`=0 AND `pending`=0 AND `hidden`=0",
  347. intval($uinfo[0]['uid']),
  348. intval(CONTACT_IS_SHARING),
  349. intval(CONTACT_IS_FRIEND)
  350. );
  351. $countfriends = $r[0]['count'];
  352. $r = q("SELECT count(*) as `count` FROM `contact`
  353. WHERE `uid` = %d AND `rel` IN ( %d, %d )
  354. AND `self`=0 AND `blocked`=0 AND `pending`=0 AND `hidden`=0",
  355. intval($uinfo[0]['uid']),
  356. intval(CONTACT_IS_FOLLOWER),
  357. intval(CONTACT_IS_FRIEND)
  358. );
  359. $countfollowers = $r[0]['count'];
  360. $r = q("SELECT count(*) as `count` FROM item where starred = 1 and uid = %d and deleted = 0",
  361. intval($uinfo[0]['uid'])
  362. );
  363. $starred = $r[0]['count'];
  364. if(! $uinfo[0]['self']) {
  365. $countfriends = 0;
  366. $countfollowers = 0;
  367. $starred = 0;
  368. }
  369. // Add a nick if it isn't present there
  370. if (($uinfo[0]['nick'] == "") OR ($uinfo[0]['name'] == $uinfo[0]['nick'])) {
  371. $uinfo[0]['nick'] = api_get_nick($uinfo[0]["url"]);
  372. }
  373. // Fetching unique id
  374. $r = q("SELECT id FROM unique_contacts WHERE url='%s' LIMIT 1", dbesc(normalise_link($uinfo[0]['url'])));
  375. // If not there, then add it
  376. if (count($r) == 0) {
  377. q("INSERT INTO unique_contacts (url, name, nick, avatar) VALUES ('%s', '%s', '%s', '%s')",
  378. dbesc(normalise_link($uinfo[0]['url'])), dbesc($uinfo[0]['name']),dbesc($uinfo[0]['nick']), dbesc($uinfo[0]['micro']));
  379. $r = q("SELECT id FROM unique_contacts WHERE url='%s' LIMIT 1", dbesc(normalise_link($uinfo[0]['url'])));
  380. }
  381. require_once('include/contact_selectors.php');
  382. $network_name = network_to_name($uinfo[0]['network']);
  383. $ret = Array(
  384. 'id' => intval($r[0]['id']),
  385. 'id_str' => (string) intval($r[0]['id']),
  386. 'name' => (($uinfo[0]['name']) ? $uinfo[0]['name'] : $uinfo[0]['nick']),
  387. 'screen_name' => (($uinfo[0]['nick']) ? $uinfo[0]['nick'] : $uinfo[0]['name']),
  388. 'location' => ($usr) ? $usr[0]['default-location'] : $network_name,
  389. 'description' => (($profile) ? $profile[0]['pdesc'] : NULL),
  390. 'profile_image_url' => $uinfo[0]['micro'],
  391. 'profile_image_url_https' => $uinfo[0]['micro'],
  392. 'url' => $uinfo[0]['url'],
  393. 'protected' => false,
  394. 'followers_count' => intval($countfollowers),
  395. 'friends_count' => intval($countfriends),
  396. 'created_at' => api_date($uinfo[0]['created']),
  397. 'favourites_count' => intval($starred),
  398. 'utc_offset' => "0",
  399. 'time_zone' => 'UTC',
  400. 'statuses_count' => intval($countitms),
  401. 'following' => (($uinfo[0]['rel'] == CONTACT_IS_FOLLOWER) OR ($uinfo[0]['rel'] == CONTACT_IS_FRIEND)),
  402. 'verified' => true,
  403. 'statusnet_blocking' => false,
  404. 'notifications' => false,
  405. 'statusnet_profile_url' => $a->get_baseurl()."/contacts/".$uinfo[0]['cid'],
  406. 'uid' => intval($uinfo[0]['uid']),
  407. 'cid' => intval($uinfo[0]['cid']),
  408. 'self' => $uinfo[0]['self'],
  409. 'network' => $uinfo[0]['network'],
  410. );
  411. return $ret;
  412. }
  413. function api_item_get_user(&$a, $item) {
  414. $author = q("SELECT * FROM unique_contacts WHERE url='%s' LIMIT 1",
  415. dbesc(normalise_link($item['author-link'])));
  416. if (count($author) == 0) {
  417. q("INSERT INTO unique_contacts (url, name, avatar) VALUES ('%s', '%s', '%s')",
  418. dbesc(normalise_link($item["author-link"])), dbesc($item["author-name"]), dbesc($item["author-avatar"]));
  419. $author = q("SELECT id FROM unique_contacts WHERE url='%s' LIMIT 1",
  420. dbesc(normalise_link($item['author-link'])));
  421. } else if ($item["author-link"].$item["author-name"] != $author[0]["url"].$author[0]["name"]) {
  422. q("UPDATE unique_contacts SET name = '%s', avatar = '%s' WHERE url = '%s'",
  423. dbesc($item["author-name"]), dbesc($item["author-avatar"]), dbesc(normalise_link($item["author-link"])));
  424. }
  425. $owner = q("SELECT id FROM unique_contacts WHERE url='%s' LIMIT 1",
  426. dbesc(normalise_link($item['owner-link'])));
  427. if (count($owner) == 0) {
  428. q("INSERT INTO unique_contacts (url, name, avatar) VALUES ('%s', '%s', '%s')",
  429. dbesc(normalise_link($item["owner-link"])), dbesc($item["owner-name"]), dbesc($item["owner-avatar"]));
  430. $owner = q("SELECT id FROM unique_contacts WHERE url='%s' LIMIT 1",
  431. dbesc(normalise_link($item['owner-link'])));
  432. } else if ($item["owner-link"].$item["owner-name"] != $owner[0]["url"].$owner[0]["name"]) {
  433. q("UPDATE unique_contacts SET name = '%s', avatar = '%s' WHERE url = '%s'",
  434. dbesc($item["owner-name"]), dbesc($item["owner-avatar"]), dbesc(normalise_link($item["owner-link"])));
  435. }
  436. // Comments in threads may appear as wall-to-wall postings.
  437. // So only take the owner at the top posting.
  438. if ($item["id"] == $item["parent"])
  439. $status_user = api_get_user($a,$item["owner-link"]);
  440. else