1
0
Fork 0

Changes in api

- Api functions can define an HTTP method to use to call them. "405 Method Not Allowed" is returned on error
- Api function that modify data accepts only POST as method.
- A list of HTTP return code related exception is added
- Api functions throw HTTP exceptions instead of return false or die()
- api_call() catches HTTP exceptions and return error message with corret HTTP response code
- api_format_items() returns also item activities count (# of like/dislike etc)
- api/friendica/photos/list return more info about photos. xml format added.
- api/friendica/photo/detail return more info, links to all sizes, no data except if 'size' parameter is passed. xml format added.
- new api api/friendica/activity/<verb> and api/friendica/activity/un<verb> to add or remove like/dislike/attend status
This commit is contained in:
fabrixxm 2015-12-27 17:45:20 +01:00
commit 6b60560ea2
5 changed files with 771 additions and 446 deletions

105
include/HTTPExceptions.php Normal file
View file

@ -0,0 +1,105 @@
<?php
/**
* Throwable exceptions to return HTTP status code
*
* This list of Exception has be extracted from
* here http://racksburg.com/choosing-an-http-status-code/
*/
class HTTPException extends Exception {
var $httpcode = 200;
var $httpdesc = "";
public function __construct($message="", $code = 0, Exception $previous = null) {
if ($this->httpdesc=="") {
$this->httpdesc = preg_replace("|([a-z])([A-Z])|",'$1 $2', str_replace("Exception","",get_class($this)));
}
parent::__construct($message, $code, $previous);
}
}
// 4xx
class TooManyRequestsException extends HTTPException {
var $httpcode = 429;
}
class UnauthorizedException extends HTTPException {
var $httpcode = 401;
}
class ForbiddenException extends HTTPException {
var $httpcode = 403;
}
class NotFoundException extends HTTPException {
var $httpcode = 404;
}
class GoneException extends HTTPException {
var $httpcode = 410;
}
class MethodNotAllowedException extends HTTPException {
var $httpcode = 405;
}
class NonAcceptableException extends HTTPException {
var $httpcode = 406;
}
class LenghtRequiredException extends HTTPException {
var $httpcode = 411;
}
class PreconditionFailedException extends HTTPException {
var $httpcode = 412;
}
class UnsupportedMediaTypeException extends HTTPException {
var $httpcode = 415;
}
class ExpetationFailesException extends HTTPException {
var $httpcode = 417;
}
class ConflictException extends HTTPException {
var $httpcode = 409;
}
class UnprocessableEntityException extends HTTPException {
var $httpcode = 422;
}
class ImATeapotException extends HTTPException {
var $httpcode = 418;
var $httpdesc = "I'm A Teapot";
}
class BadRequestException extends HTTPException {
var $httpcode = 400;
}
// 5xx
class ServiceUnavaiableException extends HTTPException {
var $httpcode = 503;
}
class BadGatewayException extends HTTPException {
var $httpcode = 502;
}
class GatewayTimeoutException extends HTTPException {
var $httpcode = 504;
}
class NotImplementedException extends HTTPException {
var $httpcode = 501;
}
class InternalServerErrorException extends HTTPException {
var $httpcode = 500;
}

View file

@ -1,66 +1,65 @@
<?php
/**
* @file include/api.php
* Friendica implementation of statusnet/twitter API
*
* @todo Automatically detect if incoming data is HTML or BBCode
*/
require_once('include/HTTPExceptions.php');
/* Contact details:
Gerhard Seeber Mail: gerhard@seeber.at Friendica: http://mozartweg.dyndns.org/friendica/gerhard
*/
/*
* Change history:
Gerhard Seeber 2015-NOV-25 Add API call /friendica/group_show to return all or a single group
with the containing contacts (necessary for Windows 10 Universal app)
Gerhard Seeber 2015-NOV-27 Add API call /friendica/group_delete to delete the specified group id
(necessary for Windows 10 Universal app)
Gerhard Seeber 2015-DEC-01 Add API call /friendica/group_create to create a group with the specified
name and the given list of contacts (necessary for Windows 10 Universal
app)
Gerhard Seeber 2015-DEC-07 Add API call /friendica/group_update to update a group with the given
list of contacts (necessary for Windows 10 Universal app)
*
*/
require_once("include/bbcode.php");
require_once("include/datetime.php");
require_once("include/conversation.php");
require_once("include/oauth.php");
require_once("include/html2plain.php");
require_once("mod/share.php");
require_once("include/Photo.php");
require_once("mod/item.php");
require_once('include/bbcode.php');
require_once('include/datetime.php');
require_once('include/conversation.php');
require_once('include/oauth.php');
require_once('include/html2plain.php');
require_once('mod/share.php');
require_once('include/Photo.php');
require_once('mod/item.php');
require_once('include/security.php');
require_once('include/contact_selectors.php');
require_once('include/html2bbcode.php');
require_once('mod/wall_upload.php');
require_once("mod/proxy.php");
require_once("include/message.php");
require_once("include/group.php");
require_once('mod/proxy.php');
require_once('include/message.php');
require_once('include/group.php');
require_once('include/like.php');
define('API_METHOD_ANY','*');
define('API_METHOD_GET','GET');
define('API_METHOD_POST','POST,PUT');
define('API_METHOD_DELETE','POST,DELETE');
/*
* Twitter-Like API
*
*/
$API = Array();
$called_api = Null;
/**
* @brief Auth API user
*
* It is not sufficient to use local_user() to check whether someone is allowed to use the API,
* because this will open CSRF holes (just embed an image with src=friendicasite.com/api/statuses/update?status=CSRF
* into a page, and visitors will post something without noticing it).
*/
function api_user() {
// It is not sufficient to use local_user() to check whether someone is allowed to use the API,
// because this will open CSRF holes (just embed an image with src=friendicasite.com/api/statuses/update?status=CSRF
// into a page, and visitors will post something without noticing it).
// Instead, use this function.
if ($_SESSION["allow_api"])
if ($_SESSION['allow_api'])
return local_user();
return false;
}
/**
* @brief Get source name from API client
*
* Clients can send 'source' parameter to be show in post metadata
* as "sent via <source>".
* Some clients doesn't send a source param, we support ones we know
* (only Twidere, atm)
*
* @return string
* Client source name, default to "api" if unset/unknown
*/
function api_source() {
if (requestdata('source'))
return (requestdata('source'));
@ -74,25 +73,63 @@
return ("api");
}
/**
* @brief Format date for API
*
* @param string $str Source date, as UTC
* @return string Date in UTC formatted as "D M d H:i:s +0000 Y"
*/
function api_date($str){
//Wed May 23 06:01:13 +0000 2007
return datetime_convert('UTC', 'UTC', $str, "D M d H:i:s +0000 Y" );
}
function api_register_func($path, $func, $auth=false){
/**
* @brief Register API endpoint
*
* Register a function to be the endpont for defined API path.
*
* @param string $path API URL path, relative to $a->get_baseurl()
* @param string $func Function name to call on path request
* @param bool $auth API need logged user
* @param string $method
* HTTP method reqiured to call this endpoint.
* One of API_METHOD_ANY, API_METHOD_GET, API_METHOD_POST.
* Default to API_METHOD_ANY
*/
function api_register_func($path, $func, $auth=false, $method=API_METHOD_ANY){
global $API;
$API[$path] = array('func'=>$func, 'auth'=>$auth);
$API[$path] = array(
'func'=>$func,
'auth'=>$auth,
'method'=> $method
);
// Workaround for hotot
$path = str_replace("api/", "api/1.1/", $path);
$API[$path] = array('func'=>$func, 'auth'=>$auth);
$API[$path] = array(
'func'=>$func,
'auth'=>$auth,
'method'=> $method
);
}
/**
* Simple HTTP Login
* @brief Login API user
*
* Log in user via OAuth1 or Simple HTTP Auth.
* Simple Auth allow username in form of <pre>user@server</pre>, ignoring server part
*
* @param App $a
* @hook 'authenticate'
* array $addon_auth
* 'username' => username from login form
* 'password' => password from login form
* 'authenticated' => return status,
* 'user_record' => return authenticated user record
* @hook 'logged_in'
* array $user logged user record
*/
function api_login(&$a){
// login with oauth
try{
@ -105,8 +142,7 @@
}
echo __file__.__line__.__function__."<pre>"; var_dump($consumer, $token); die();
}catch(Exception $e){
logger(__file__.__line__.__function__."\n".$e);
//die(__file__.__line__.__function__."<pre>".$e); die();
logger($e);
}
@ -189,16 +225,45 @@
}
/**************************
* MAIN API ENTRY POINT *
**************************/
/**
* @brief Check HTTP method of called API
*
* API endpoints can define which HTTP method to accept when called.
* This function check the current HTTP method agains endpoint
* registered method.
*
* @param string $method Required methods, uppercase, separated by comma
* @return bool
*/
function api_check_method($method) {
if ($method=="*") return True;
return strpos($method, $_SERVER['REQUEST_METHOD']) !== false;
}
/**
* @brief Main API entry point
*
* Authenticate user, call registered API function, set HTTP headers
*
* @param App $a
* @return string API call result
*/
function api_call(&$a){
GLOBAL $API, $called_api;
// preset
$type="json";
if (strpos($a->query_string, ".xml")>0) $type="xml";
if (strpos($a->query_string, ".json")>0) $type="json";
if (strpos($a->query_string, ".rss")>0) $type="rss";
if (strpos($a->query_string, ".atom")>0) $type="atom";
if (strpos($a->query_string, ".as")>0) $type="as";
try {
foreach ($API as $p=>$info){
if (strpos($a->query_string, $p)===0){
if (!api_check_method($info['method'])){
throw new MethodNotAllowedException();
}
$called_api= explode("/",$p);
//unset($_SERVER['PHP_AUTH_USER']);
if ($info['auth']===true && api_user()===false) {
@ -209,19 +274,17 @@
logger('API call for ' . $a->user['username'] . ': ' . $a->query_string);
logger('API parameters: ' . print_r($_REQUEST,true));
$type="json";
if (strpos($a->query_string, ".xml")>0) $type="xml";
if (strpos($a->query_string, ".json")>0) $type="json";
if (strpos($a->query_string, ".rss")>0) $type="rss";
if (strpos($a->query_string, ".atom")>0) $type="atom";
if (strpos($a->query_string, ".as")>0) $type="as";
$stamp = microtime(true);
$r = call_user_func($info['func'], $a, $type);
$duration = (float)(microtime(true)-$stamp);
logger("API call duration: ".round($duration, 2)."\t".$a->query_string, LOGGER_DEBUG);
if ($r===false) return;
if ($r===false) {
// api function returned false withour throw an
// exception. This should not happend, throw a 500
throw new InternalServerErrorException();
}
switch($type){
case "xml":
@ -253,40 +316,57 @@
break;
}
//echo "<pre>"; var_dump($r); die();
}
}
header("HTTP/1.1 404 Not Found");
logger('API call not implemented: '.$a->query_string." - ".print_r($_REQUEST,true));
return(api_error($a, $type, "not implemented"));
throw new NotImplementedException();
} catch (HTTPException $e) {
header("HTTP/1.1 {$e->httpcode} {$e->httpdesc}");
return api_error($a, $type, $e);
}
}
function api_error(&$a, $type, $error) {
/// @TODO https://dev.twitter.com/overview/api/response-codes
$r = "<status><error>".$error."</error><request>".$a->query_string."</request></status>";
/**
* @brief Format API error string
*
* @param Api $a
* @param string $type Return type (xml, json, rss, as)
* @param string $error Error message
*/
function api_error(&$a, $type, $e) {
$error = ($e->getMessage()!==""?$e->getMessage():$e->httpdesc);
# TODO: https://dev.twitter.com/overview/api/response-codes
$xmlstr = "<status><error>{$error}</error><code>{$e->httpcode} {$e->httpdesc}</code><request>{$a->query_string}</request></status>";
switch($type){
case "xml":
header ("Content-Type: text/xml");
return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$xmlstr;
break;
case "json":
header ("Content-Type: application/json");
return json_encode(array('error' => $error, 'request' => $a->query_string));
return json_encode(array(
'error' => $error,
'request' => $a->query_string,
'code' => $e->httpcode." ".$e->httpdesc
));
break;
case "rss":
header ("Content-Type: application/rss+xml");
return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$xmlstr;
break;
case "atom":
header ("Content-Type: application/atom+xml");
return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$r;
return '<?xml version="1.0" encoding="UTF-8"?>'."\n".$xmlstr;
break;
}
}
/**
* RSS extra info
* @brief Set values for RSS template
*
* @param App $a
* @param array $arr Array to be passed to template
* @param array $user_info
* @return array
*/
function api_rss_extra(&$a, $arr, $user_info){
if (is_null($user_info)) $user_info = api_get_user($a);
@ -306,7 +386,11 @@
/**
* Unique contact to contact url.
* @brief Unique contact to contact url.
*
* @param int $id Contact id
* @return bool|string
* Contact url or False if contact id is unknown
*/
function api_unique_id_to_url($id){
$r = q("SELECT `url` FROM `unique_contacts` WHERE `id`=%d LIMIT 1",
@ -318,7 +402,11 @@
}
/**
* Returns user info array.
* @brief Get user info array.
*
* @param Api $a
* @param int|string $contact_id Contact ID or URL
* @param string $type Return type (for errors)
*/
function api_get_user(&$a, $contact_id = Null, $type = "json"){
global $called_api;
@ -342,7 +430,7 @@
$user = dbesc(api_unique_id_to_url($contact_id));
if ($user == "")
die(api_error($a, $type, t("User not found.")));
throw new BadRequestException("User not found.");
$url = $user;
$extra_query = "AND `contact`.`nurl` = '%s' ";
@ -353,7 +441,7 @@
$user = dbesc(api_unique_id_to_url($_GET['user_id']));
if ($user == "")
die(api_error($a, $type, t("User not found.")));
throw new BadRequestException("User not found.");
$url = $user;
$extra_query = "AND `contact`.`nurl` = '%s' ";
@ -390,7 +478,8 @@
if (!$user) {
if (api_user()===false) {
api_login($a); return False;
api_login($a);
return False;
} else {
$user = $_SESSION['uid'];
$extra_query = "AND `contact`.`uid` = %d AND `contact`.`self` = 1 ";
@ -461,9 +550,9 @@
);
return $ret;
} else
die(api_error($a, $type, t("User not found.")));
} else {
throw new BadRequestException("User not found.");
}
}
if($uinfo[0]['self']) {
@ -672,7 +761,7 @@
* http://developer.twitter.com/doc/get/account/verify_credentials
*/
function api_account_verify_credentials(&$a, $type){
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
unset($_REQUEST["user_id"]);
unset($_GET["user_id"]);
@ -723,7 +812,7 @@
function api_statuses_mediap(&$a, $type) {
if (api_user()===false) {
logger('api_statuses_update: no user');
return false;
throw new ForbiddenException();
}
$user_info = api_get_user($a);
@ -757,14 +846,14 @@
// this should output the last post (the one we just posted).
return api_status_show($a,$type);
}
api_register_func('api/statuses/mediap','api_statuses_mediap', true);
api_register_func('api/statuses/mediap','api_statuses_mediap', true, API_METHOD_POST);
/*Waitman Gobble Mod*/
function api_statuses_update(&$a, $type) {
if (api_user()===false) {
logger('api_statuses_update: no user');
return false;
throw new ForbiddenException();
}
$user_info = api_get_user($a);
@ -882,7 +971,7 @@
$_REQUEST['body'] .= "\n\n".$media;
}
/// @TODO Multiple IDs
// To-Do: Multiple IDs
if (requestdata('media_ids')) {
$r = q("SELECT `resource-id`, `scale`, `nickname`, `type` FROM `photo` INNER JOIN `user` ON `user`.`uid` = `photo`.`uid` WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = %d) AND `scale` > 0 AND `photo`.`uid` = %d ORDER BY `photo`.`width` DESC LIMIT 1",
intval(requestdata('media_ids')), api_user());
@ -908,27 +997,27 @@
// this should output the last post (the one we just posted).
return api_status_show($a,$type);
}
api_register_func('api/statuses/update','api_statuses_update', true);
api_register_func('api/statuses/update_with_media','api_statuses_update', true);
api_register_func('api/statuses/update','api_statuses_update', true, API_METHOD_POST);
api_register_func('api/statuses/update_with_media','api_statuses_update', true, API_METHOD_POST);
function api_media_upload(&$a, $type) {
if (api_user()===false) {
logger('no user');
return false;
throw new ForbiddenException();
}
$user_info = api_get_user($a);
if(!x($_FILES,'media')) {
// Output error
return false;
throw new BadRequestException("No media.");
}
$media = wall_upload_post($a, false);
if(!$media) {
// Output error
return false;
throw new InternalServerErrorException();
}
$returndata = array();
@ -943,8 +1032,7 @@
return array("media" => $returndata);
}
api_register_func('api/media/upload','api_media_upload', true);
api_register_func('api/media/upload','api_media_upload', true, API_METHOD_POST);
function api_status_show(&$a, $type){
$user_info = api_get_user($a);
@ -1180,11 +1268,12 @@
$userlist[] = $userdata["user"];
}
$userlist = array("users" => $userlist);
} else
die(api_error($a, $type, t("User not found.")));
} else
die(api_error($a, $type, t("User not found.")));
} else {
throw new BadRequestException("User not found.");
}
} else {
throw new BadRequestException("User not found.");
}
return ($userlist);
}
@ -1194,11 +1283,11 @@
*
* http://developer.twitter.com/doc/get/statuses/home_timeline
*
* @TODO Optional parameters
* @TODO Add reply info
* TODO: Optional parameters
* TODO: Add reply info
*/
function api_statuses_home_timeline(&$a, $type){
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
unset($_REQUEST["user_id"]);
unset($_GET["user_id"]);
@ -1281,7 +1370,7 @@
api_register_func('api/statuses/friends_timeline','api_statuses_home_timeline', true);
function api_statuses_public_timeline(&$a, $type){
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
$user_info = api_get_user($a);
// get last newtork messages
@ -1351,7 +1440,7 @@
*
*/
function api_statuses_show(&$a, $type){
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
$user_info = api_get_user($a);
@ -1389,8 +1478,9 @@
intval($id)
);
if (!$r)
die(api_error($a, $type, t("There is no status with this id.")));
if (!$r) {
throw new BadRequestException("There is no status with this id.");
}
$ret = api_format_items($r,$user_info);
@ -1414,7 +1504,7 @@
*
*/
function api_conversation_show(&$a, $type){
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
$user_info = api_get_user($a);
@ -1464,7 +1554,7 @@
);
if (!$r)
die(api_error($a, $type, t("There is no conversation with this id.")));
throw new BadRequestException("There is no conversation with this id.");
$ret = api_format_items($r,$user_info);
@ -1480,7 +1570,7 @@
function api_statuses_repeat(&$a, $type){
global $called_api;
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
$user_info = api_get_user($a);
@ -1538,13 +1628,13 @@
$called_api = null;
return(api_status_show($a,$type));
}
api_register_func('api/statuses/retweet','api_statuses_repeat', true);
api_register_func('api/statuses/retweet','api_statuses_repeat', true, API_METHOD_POST);
/**
*
*/
function api_statuses_destroy(&$a, $type){
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
$user_info = api_get_user($a);
@ -1566,7 +1656,7 @@
return($ret);
}
api_register_func('api/statuses/destroy','api_statuses_destroy', true);
api_register_func('api/statuses/destroy','api_statuses_destroy', true, API_METHOD_DELETE);
/**
*
@ -1574,7 +1664,7 @@
*
*/
function api_statuses_mentions(&$a, $type){
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
unset($_REQUEST["user_id"]);
unset($_GET["user_id"]);
@ -1653,7 +1743,7 @@
function api_statuses_user_timeline(&$a, $type){
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
$user_info = api_get_user($a);
// get last network messages
@ -1714,7 +1804,6 @@
return api_apply_template("timeline", $type, $data);
}
api_register_func('api/statuses/user_timeline','api_statuses_user_timeline', true);
@ -1725,7 +1814,7 @@
* api v1 : https://web.archive.org/web/20131019055350/https://dev.twitter.com/docs/api/1/post/favorites/create/%3Aid
*/
function api_favorites_create_destroy(&$a, $type){
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
// for versioned api.
/// @TODO We need a better global soluton
@ -1743,7 +1832,8 @@
$item = q("SELECT * FROM item WHERE id=%d AND uid=%d",
$itemid, api_user());
if ($item===false || count($item)==0) die(api_error($a, $type, t("Invalid item.")));
if ($item===false || count($item)==0)
throw new BadRequestException("Invalid item.");
switch($action){
case "create":
@ -1753,7 +1843,7 @@
$item[0]['starred']=0;
break;
default:
die(api_error($a, $type, t("Invalid action. ".$action)));
throw new BadRequestException("Invalid action ".$action);
}
$r = q("UPDATE item SET starred=%d WHERE id=%d AND uid=%d",
$item[0]['starred'], $itemid, api_user());
@ -1761,7 +1851,8 @@
q("UPDATE thread SET starred=%d WHERE iid=%d AND uid=%d",
$item[0]['starred'], $itemid, api_user());
if ($r===false) die(api_error($a, $type, t("DB error")));
if ($r===false)
throw InternalServerErrorException("DB error");
$user_info = api_get_user($a);
@ -1777,14 +1868,13 @@
return api_apply_template("status", $type, $data);
}
api_register_func('api/favorites/create', 'api_favorites_create_destroy', true);
api_register_func('api/favorites/destroy', 'api_favorites_create_destroy', true);
api_register_func('api/favorites/create', 'api_favorites_create_destroy', true, API_METHOD_POST);
api_register_func('api/favorites/destroy', 'api_favorites_create_destroy', true, API_METHOD_DELETE);
function api_favorites(&$a, $type){
global $called_api;
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
$called_api= array();
@ -1842,14 +1932,12 @@
return api_apply_template("timeline", $type, $data);
}
api_register_func('api/favorites','api_favorites', true);
function api_format_as($a, $ret, $user_info) {
$as = array();
$as['title'] = $a->config['sitename']." Public Timeline";
$items = array();
@ -2015,8 +2103,10 @@
}
function api_get_entitities(&$text, $bbcode) {
/// @todo
/// Links at the first character of the post
/*
To-Do:
* Links at the first character of the post
*/
$a = get_app();
@ -2166,7 +2256,7 @@
return($entities);
}
function api_format_items_embeded_images($item, $text){
function api_format_items_embeded_images(&$item, $text){
$a = get_app();
$text = preg_replace_callback(
"|data:image/([^;]+)[^=]+=*|m",
@ -2177,7 +2267,42 @@
return $text;
}
function api_format_items($r,$user_info, $filter_user = false) {
/**
* @brief return likes, dislikes and attend status for item
*
* @param array $item
* @return array
* likes => int count
* dislikes => int count
*/
function api_format_items_likes(&$item) {
$activities = array(
ACTIVITY_LIKE => 'like',
ACTIVITY_DISLIKE => 'dislike',
ACTIVITY_ATTEND => 'attendyes',
ACTIVITY_ATTENDNO => 'attendno',
ACTIVITY_ATTENDMAYBE => 'attendmaybe'
);
$r = q("SELECT verb, count(verb) as n FROM item WHERE parent=%d GROUP BY verb",
intval($item['id']));
$res = array();
foreach($r as $row) {
if (x($activities, $row['verb'])) {
$res[$activities[$row['verb']]] = $row['n'];
}
}
return $res;
}
/**
* @brief format items to be returned by api
*
* @param array $r array of items
* @param array $user_info
* @param bool $filter_user filter items by $user_info
*/
function api_format_items(&$r,$user_info, $filter_user = false) {
$a = get_app();
$ret = Array();
@ -2250,6 +2375,7 @@
//'entities' => NULL,
'statusnet_html' => $converted["html"],
'statusnet_conversation_id' => $item['parent'],
'friendica_activities' => api_format_items_likes($item),
);
if (count($converted["attachments"]) > 0)
@ -2297,7 +2423,6 @@
function api_account_rate_limit_status(&$a,$type) {
$hash = array(
'reset_time_in_seconds' => strtotime('now + 1 hour'),
'remaining_hits' => (string) 150,
@ -2308,31 +2433,26 @@
$hash['resettime_in_seconds'] = $hash['reset_time_in_seconds'];
return api_apply_template('ratelimit', $type, array('$hash' => $hash));
}
api_register_func('api/account/rate_limit_status','api_account_rate_limit_status',true);
function api_help_test(&$a,$type) {
if ($type == 'xml')
$ok = "true";
else
$ok = "ok";
return api_apply_template('test', $type, array("$ok" => $ok));
}
api_register_func('api/help/test','api_help_test',false);
function api_lists(&$a,$type) {
$ret = array();
return array($ret);
}
api_register_func('api/lists','api_lists',true);
function api_lists_list(&$a,$type) {
$ret = array();
return array($ret);
}
@ -2344,7 +2464,7 @@
* returns: json, xml
**/
function api_statuses_f(&$a, $type, $qtype) {
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
$user_info = api_get_user($a);
if (x($_GET,'cursor') && $_GET['cursor']=='undefined'){
@ -2437,26 +2557,27 @@
api_register_func('api/statusnet/config','api_statusnet_config',false);
function api_statusnet_version(&$a,$type) {
// liar
$fake_statusnet_version = "0.9.7";
if($type === 'xml') {
header("Content-type: application/xml");
echo '<?xml version="1.0" encoding="UTF-8"?>' . "\r\n" . '<version>0.9.7</version>' . "\r\n";
echo '<?xml version="1.0" encoding="UTF-8"?>' . "\r\n" . '<version>'.$fake_statusnet_version.'</version>' . "\r\n";
killme();
}
elseif($type === 'json') {
header("Content-type: application/json");
echo '"0.9.7"';
echo '"'.$fake_statusnet_version.'"';
killme();
}
}
api_register_func('api/statusnet/version','api_statusnet_version',false);
/**
* @todo use api_apply_template() to return data
*/
function api_ff_ids(&$a,$type,$qtype) {
if(! api_user())
return false;
if(! api_user()) throw new ForbiddenException();
$user_info = api_get_user($a);
@ -2510,7 +2631,7 @@
function api_direct_messages_new(&$a, $type) {
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
if (!x($_POST, "text") OR (!x($_POST,"screen_name") AND !x($_POST,"user_id"))) return;
@ -2567,11 +2688,10 @@
return api_apply_template("direct_messages", $type, $data);
}
api_register_func('api/direct_messages/new','api_direct_messages_new',true);
api_register_func('api/direct_messages/new','api_direct_messages_new',true, API_METHOD_POST);
function api_direct_messages_box(&$a, $type, $box) {
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
// params
$count = (x($_GET,'count')?$_GET['count']:20);
@ -2701,36 +2821,73 @@
function api_fr_photos_list(&$a,$type) {
if (api_user()===false) return false;
$r = q("select distinct `resource-id` from photo where uid = %d and album != 'Contact Photos' ",
if (api_user()===false) throw new ForbiddenException();
$r = q("select `resource-id`, max(scale) as scale, album, filename, type from photo
where uid = %d and album != 'Contact Photos' group by `resource-id`",
intval(local_user())
);
$typetoext = array(
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif'
);
$data = array('photos'=>array());
if($r) {
$ret = array();
foreach($r as $rr)
$ret[] = $rr['resource-id'];
header("Content-type: application/json");
echo json_encode($ret);
foreach($r as $rr) {
$photo = array();
$photo['id'] = $rr['resource-id'];
$photo['album'] = $rr['album'];
$photo['filename'] = $rr['filename'];
$photo['type'] = $rr['type'];
$photo['thumb'] = $a->get_baseurl()."/photo/".$rr['resource-id']."-".$rr['scale'].".".$typetoext[$rr['type']];
$data['photos'][] = $photo;
}
killme();
}
return api_apply_template("photos_list", $type, $data);
}
function api_fr_photo_detail(&$a,$type) {
if (api_user()===false) return false;
if(! $_REQUEST['photo_id']) return false;
$scale = ((array_key_exists('scale',$_REQUEST)) ? intval($_REQUEST['scale']) : 0);
$r = q("select * from photo where uid = %d and `resource-id` = '%s' and scale = %d limit 1",
if (api_user()===false) throw new ForbiddenException();
if(!x($_REQUEST,'photo_id')) throw new BadRequestException("No photo id.");
$scale = (x($_REQUEST, 'scale') ? intval($_REQUEST['scale']) : false);
$scale_sql = ($scale === false ? "" : sprintf("and scale=%d",intval($scale)));
$data_sql = ($scale === false ? "" : "data, ");
$r = q("select %s `resource-id`, `created`, `edited`, `title`, `desc`, `album`, `filename`,
`type`, `height`, `width`, `datasize`, `profile`, min(`scale`) as minscale, max(`scale`) as maxscale
from photo where `uid` = %d and `resource-id` = '%s' %s group by `resource-id`",
$data_sql,
intval(local_user()),
dbesc($_REQUEST['photo_id']),
intval($scale)
$scale_sql
);
$typetoext = array(
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif'
);
if ($r) {
header("Content-type: application/json");
$r[0]['data'] = base64_encode($r[0]['data']);
echo json_encode($r[0]);
$data = array('photo' => $r[0]);
if ($scale !== false) {
$data['photo']['data'] = base64_encode($data['photo']['data']);
}
$data['photo']['link'] = array();
for($k=intval($data['photo']['minscale']); $k<=intval($data['photo']['maxscale']); $k++) {
$data['photo']['link'][$k] = $a->get_baseurl()."/photo/".$data['photo']['resource-id']."-".$k.".".$typetoext[$data['photo']['type']];
}
$data['photo']['id'] = $data['photo']['resource-id'];
unset($data['photo']['resource-id']);
unset($data['photo']['minscale']);
unset($data['photo']['maxscale']);
} else {
throw new NotFoundException();
}
killme();
return api_apply_template("photo_detail", $type, $data);
}
api_register_func('api/friendica/photos/list', 'api_fr_photos_list', true);
@ -2754,7 +2911,7 @@
$c_url = ((x($_GET,'c_url')) ? $_GET['c_url'] : '');
if ($url === '' || $c_url === '')
die((api_error($a, 'json', "Wrong parameters")));
throw new BadRequestException("Wrong parameters.");
$c_url = normalise_link($c_url);
@ -2766,7 +2923,7 @@
);
if ((! count($r)) || ($r[0]['network'] !== NETWORK_DFRN))
die((api_error($a, 'json', "Unknown contact")));
throw new BadRequestException("Unknown contact");
$cid = $r[0]['id'];
@ -2801,7 +2958,6 @@
api_register_func('api/friendica/remoteauth', 'api_friendica_remoteauth', true);
function api_share_as_retweet(&$item) {
$body = trim($item["body"]);
@ -2871,8 +3027,10 @@ function api_share_as_retweet(&$item) {
}
function api_get_nick($profile) {
/// @TODO Remove trailing junk from profile url
/// @TODO pump.io check has to check the website
/* To-Do:
- remove trailing junk from profile url
- pump.io check has to check the website
*/
$nick = "";
@ -2920,7 +3078,7 @@ function api_get_nick($profile) {
}
}
/// @TODO Look at the page if its really a pumpio site
// To-Do: look at the page if its really a pumpio site
//if (!$nick == "") {
// $pumpio = preg_replace("=https?://(.*)/(.*)/=ism", "$2", $profile."/");
// if ($pumpio != $profile)
@ -3034,7 +3192,7 @@ function api_best_nickname(&$contacts) {
// return all or a specified group of the user with the containing contacts
function api_friendica_group_show(&$a, $type) {
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
// params
$user_info = api_get_user($a);
@ -3048,7 +3206,7 @@ function api_best_nickname(&$contacts) {
intval($gid));
// error message if specified gid is not in database
if (count($r) == 0)
die(api_error($a, $type, 'gid not available'));
throw new BadRequestException("gid not available");
}
else
$r = q("SELECT * FROM `group` WHERE `deleted` = 0 AND `uid` = %d",
@ -3071,7 +3229,7 @@ function api_best_nickname(&$contacts) {
// delete the specified group of the user
function api_friendica_group_delete(&$a, $type) {
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
// params
$user_info = api_get_user($a);
@ -3081,7 +3239,7 @@ function api_best_nickname(&$contacts) {
// error if no gid specified
if ($gid == 0 || $name == "")
die(api_error($a, $type, 'gid or name not specified'));
throw new BadRequestException('gid or name not specified');
// get data of the specified group id
$r = q("SELECT * FROM `group` WHERE `uid` = %d AND `id` = %d",
@ -3089,7 +3247,7 @@ function api_best_nickname(&$contacts) {
intval($gid));
// error message if specified gid is not in database
if (count($r) == 0)
die(api_error($a, $type, 'gid not available'));
throw new BadRequestException('gid not available');
// get data of the specified group id and group name
$rname = q("SELECT * FROM `group` WHERE `uid` = %d AND `id` = %d AND `name` = '%s'",
@ -3098,7 +3256,7 @@ function api_best_nickname(&$contacts) {
dbesc($name));
// error message if specified gid is not in database
if (count($rname) == 0)
die(api_error($a, $type, 'wrong group name'));
throw new BadRequestException('wrong group name');
// delete group
$ret = group_rmv($uid, $name);
@ -3108,14 +3266,14 @@ function api_best_nickname(&$contacts) {
return api_apply_template("group_delete", $type, array('$result' => $success));
}
else
die(api_error($a, $type, 'other API error'));
throw new BadRequestException('other API error');
}
api_register_func('api/friendica/group_delete', 'api_friendica_group_delete', true);
api_register_func('api/friendica/group_delete', 'api_friendica_group_delete', true, API_METHOD_DELETE);
// create the specified group with the posted array of contacts
function api_friendica_group_create(&$a, $type) {
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
// params
$user_info = api_get_user($a);
@ -3126,7 +3284,7 @@ function api_best_nickname(&$contacts) {
// error if no name specified
if ($name == "")
die(api_error($a, $type, 'group name not specified'));
throw new BadRequestException('group name not specified');
// get data of the specified group name
$rname = q("SELECT * FROM `group` WHERE `uid` = %d AND `name` = '%s' AND `deleted` = 0",
@ -3134,7 +3292,7 @@ function api_best_nickname(&$contacts) {
dbesc($name));
// error message if specified group name already exists
if (count($rname) != 0)
die(api_error($a, $type, 'group name already exists'));
throw new BadRequestException('group name already exists');
// check if specified group name is a deleted group
$rname = q("SELECT * FROM `group` WHERE `uid` = %d AND `name` = '%s' AND `deleted` = 1",
@ -3149,7 +3307,7 @@ function api_best_nickname(&$contacts) {
if ($ret)
$gid = group_byname($uid, $name);
else
die(api_error($a, $type, 'other API error'));
throw new BadRequestException('other API error');
// add members
$erroraddinguser = false;
@ -3173,12 +3331,12 @@ function api_best_nickname(&$contacts) {
$success = array('success' => true, 'gid' => $gid, 'name' => $name, 'status' => $status, 'wrong users' => $errorusers);
return api_apply_template("group_create", $type, array('result' => $success));
}
api_register_func('api/friendica/group_create', 'api_friendica_group_create', true);
api_register_func('api/friendica/group_create', 'api_friendica_group_create', true, API_METHOD_POST);
// update the specified group with the posted array of contacts
function api_friendica_group_update(&$a, $type) {
if (api_user()===false) return false;
if (api_user()===false) throw new ForbiddenException();
// params
$user_info = api_get_user($a);
@ -3190,11 +3348,11 @@ function api_best_nickname(&$contacts) {
// error if no name specified
if ($name == "")
die(api_error($a, $type, 'group name not specified'));
throw new BadRequestException('group name not specified');
// error if no gid specified
if ($gid == "")
die(api_error($a, $type, 'gid not specified'));
throw new BadRequestException('gid not specified');
// remove members
$members = group_get_members($gid);
@ -3230,7 +3388,38 @@ function api_best_nickname(&$contacts) {
$success = array('success' => true, 'gid' => $gid, 'name' => $name, 'status' => $status, 'wrong users' => $errorusers);
return api_apply_template("group_update", $type, array('result' => $success));
}
api_register_func('api/friendica/group_update', 'api_friendica_group_update', true);
api_register_func('api/friendica/group_update', 'api_friendica_group_update', true, API_METHOD_POST);
function api_friendica_activity(&$a, $type) {
if (api_user()===false) throw new ForbiddenException();
#$verb = (x($_REQUEST, 'verb') ? strtolower($_REQUEST['verb']) : '');
$verb = strtolower($a->argv[3]);
$id = (x($_REQUEST, 'id') ? $_REQUEST['id'] : 0);
$res = do_like($id, $verb);
if ($res) {
if ($type == 'xml')
$ok = "true";
else
$ok = "ok";
return api_apply_template('test', $type, array("$ok" => $ok));
} else {
throw new BadRequestException('Error adding activity');
}
}
api_register_func('api/friendica/activity/like', 'api_friendica_activity', true, API_METHOD_POST);
api_register_func('api/friendica/activity/dislike', 'api_friendica_activity', true, API_METHOD_POST);
api_register_func('api/friendica/activity/attendyes', 'api_friendica_activity', true, API_METHOD_POST);
api_register_func('api/friendica/activity/attendno', 'api_friendica_activity', true, API_METHOD_POST);
api_register_func('api/friendica/activity/attendmaybe', 'api_friendica_activity', true, API_METHOD_POST);
api_register_func('api/friendica/activity/unlike', 'api_friendica_activity', true, API_METHOD_POST);
api_register_func('api/friendica/activity/undislike', 'api_friendica_activity', true, API_METHOD_POST);
api_register_func('api/friendica/activity/unattendyes', 'api_friendica_activity', true, API_METHOD_POST);
api_register_func('api/friendica/activity/unattendno', 'api_friendica_activity', true, API_METHOD_POST);
api_register_func('api/friendica/activity/unattendmaybe', 'api_friendica_activity', true, API_METHOD_POST);
/*
To.Do:
@ -3243,7 +3432,7 @@ To.Do:
[include_rts] => 1
[include_reply_count] => true
[include_descendent_reply_count] => true
(?)
Not implemented by now:

View file

@ -0,0 +1,21 @@
<photo>
<id>{{$photo.id}}</id>
<created>{{$photo.created}}</created>
<edited>{{$photo.edited}}</edited>
<title>{{$photo.title}}</title>
<desc>{{$photo.desc}}</desc>
<album>{{$photo.album}}</album>
<filename>{{$photo.filename}}</filename>
<type>{{$photo.type}}</type>
<height>{{$photo.height}}</height>
<width>{{$photo.width}}</width>
<datasize>{{$photo.datasize}}</datasize>
<profile>1</profile>
<url>{{foreach $photo.link as $scale => $url}}
<link type="{{$photo.type}}" scale="{{$scale}}" href="{{$url}}" />
{{/foreach}}</url>
{{if $photo.data}}
<data encode="base64">{{$photo.data}}</data>
{{/if}}
</photo>

View file

@ -0,0 +1,5 @@
<photos type="array">
{{foreach $photos as $photo}}
<photo id="{{$photo.id}}" album="{{$photo.album}}" filename="{{$photo.filename}}" type="{{$photo.type}}">{{$photo.thumb}}</photo>
{{/foreach}}</photos>

View file

@ -1,5 +1,7 @@
<statuses type="array" xmlns:statusnet="http://status.net/schema/api/1/">
<statuses type="array"
xmlns:statusnet="http://status.net/schema/api/1/"
xmlns:friendica="http://friendi.ca/schema/api/1/">
{{foreach $statuses as $status}} <status>
<text>{{$status.text}}</text>
<truncated>{{$status.truncated}}</truncated>
@ -17,5 +19,8 @@
<coordinates>{{$status.coordinates}}</coordinates>
<place>{{$status.place}}</place>
<contributors>{{$status.contributors}}</contributors>
<friendica:activities>{{foreach $status.friendica_activities as $k=>$v}}
<friendica:{{$k}}>{{$v}}</friendica:{{$k}}>
{{/foreach}}</friendica:activities>
</status>
{{/foreach}}</statuses>