1
0
Fork 0

Merge pull request #2216 from fabrixxm/feature-api-like

Works on API and a small refractor, updated docs
This commit is contained in:
Tobias Diekershoff 2015-12-28 18:39:21 +01:00
commit 99861b9fa6
10 changed files with 1436 additions and 850 deletions

2
.gitignore vendored
View file

@ -13,7 +13,7 @@ addon
robots.txt robots.txt
#ignore documentation, it should be newly built #ignore documentation, it should be newly built
doc/api doc/html
#ignore reports, should be generted with every build #ignore reports, should be generted with every build
report/ report/

View file

@ -1,4 +1,4 @@
Implemented API calls Friendica API
=== ===
The Friendica API aims to be compatible to the [GNU Social API](http://skilledtests.com/wiki/Twitter-compatible_API) and the [Twitter API](https://dev.twitter.com/rest/public). The Friendica API aims to be compatible to the [GNU Social API](http://skilledtests.com/wiki/Twitter-compatible_API) and the [Twitter API](https://dev.twitter.com/rest/public).
@ -24,13 +24,45 @@ Please refer to the linked documentation for further information.
* cid: Contact id of the user (important for "contact_allow" and "contact_deny") * cid: Contact id of the user (important for "contact_allow" and "contact_deny")
* network: network of the user * network: network of the user
#### Errors
When an error occour in API call, an HTTP error code is returned, with an error message
Usually:
- 400 Bad Request: if parameter are missing or items can't be found
- 403 Forbidden: if authenticated user is missing
- 405 Method Not Allowed: if API was called with invalid method, eg. GET when API require POST
- 501 Not Implemented: if requested API doesn't exists
- 500 Internal Server Error: on other error contitions
Error body is
json:
```
{
"error": "Specific error message",
"request": "API path requested",
"code": "HTTP error code"
}
```
xml:
```
<status>
<error>Specific error message</error>
<request>API path requested</request>
<code>HTTP error code</code>
</status>
```
---
### account/rate_limit_status ### account/rate_limit_status
---
### account/verify_credentials ### account/verify_credentials
#### Parameters #### Parameters
* skip_status: Don't show the "status" field. (Default: false) * skip_status: Don't show the "status" field. (Default: false)
* include_entities: "true" shows entities for pictures and links (Default: false) * include_entities: "true" shows entities for pictures and links (Default: false)
---
### conversation/show ### conversation/show
Unofficial Twitter command. It shows all direct answers (excluding the original post) to a given id. Unofficial Twitter command. It shows all direct answers (excluding the original post) to a given id.
@ -47,6 +79,7 @@ Unofficial Twitter command. It shows all direct answers (excluding the original
* trim_user * trim_user
* contributor_details * contributor_details
---
### direct_messages ### direct_messages
#### Parameters #### Parameters
* count: Items per page (default: 20) * count: Items per page (default: 20)
@ -59,6 +92,7 @@ Unofficial Twitter command. It shows all direct answers (excluding the original
#### Unsupported parameters #### Unsupported parameters
* skip_status * skip_status
---
### direct_messages/all ### direct_messages/all
#### Parameters #### Parameters
* count: Items per page (default: 20) * count: Items per page (default: 20)
@ -67,6 +101,7 @@ Unofficial Twitter command. It shows all direct answers (excluding the original
* max_id: maximum id * max_id: maximum id
* getText: Defines the format of the status field. Can be "html" or "plain" * getText: Defines the format of the status field. Can be "html" or "plain"
---
### direct_messages/conversation ### direct_messages/conversation
Shows all direct messages of a conversation Shows all direct messages of a conversation
#### Parameters #### Parameters
@ -77,6 +112,7 @@ Shows all direct messages of a conversation
* getText: Defines the format of the status field. Can be "html" or "plain" * getText: Defines the format of the status field. Can be "html" or "plain"
* uri: URI of the conversation * uri: URI of the conversation
---
### direct_messages/new ### direct_messages/new
#### Parameters #### Parameters
* user_id: id of the user * user_id: id of the user
@ -85,6 +121,7 @@ Shows all direct messages of a conversation
* replyto: ID of the replied direct message * replyto: ID of the replied direct message
* title: Title of the direct message * title: Title of the direct message
---
### direct_messages/sent ### direct_messages/sent
#### Parameters #### Parameters
* count: Items per page (default: 20) * count: Items per page (default: 20)
@ -94,6 +131,7 @@ Shows all direct messages of a conversation
* getText: Defines the format of the status field. Can be "html" or "plain" * getText: Defines the format of the status field. Can be "html" or "plain"
* include_entities: "true" shows entities for pictures and links (Default: false) * include_entities: "true" shows entities for pictures and links (Default: false)
---
### favorites ### favorites
#### Parameters #### Parameters
* count: Items per page (default: 20) * count: Items per page (default: 20)
@ -108,16 +146,19 @@ Shows all direct messages of a conversation
Favorites aren't displayed to other users, so "user_id" and "screen_name". So setting this value will result in an empty array. Favorites aren't displayed to other users, so "user_id" and "screen_name". So setting this value will result in an empty array.
---
### favorites/create ### favorites/create
#### Parameters #### Parameters
* id * id
* include_entities: "true" shows entities for pictures and links (Default: false) * include_entities: "true" shows entities for pictures and links (Default: false)
---
### favorites/destroy ### favorites/destroy
#### Parameters #### Parameters
* id * id
* include_entities: "true" shows entities for pictures and links (Default: false) * include_entities: "true" shows entities for pictures and links (Default: false)
---
### followers/ids ### followers/ids
#### Parameters #### Parameters
* stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false) * stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false)
@ -129,16 +170,139 @@ Favorites aren't displayed to other users, so "user_id" and "screen_name". So se
Friendica doesn't allow showing followers of other users. Friendica doesn't allow showing followers of other users.
---
### friendica/activity/<verb>
#### parameters
* id: item id
Add or remove an activity from an item.
'verb' can be one of:
- like
- dislike
- attendyes
- attendno
- attendmaybe
To remove an activity, prepend the verb with "un", eg. "unlike" or "undislike"
Attend verbs disable eachother: that means that if "attendyes" was added to an item, adding "attendno" remove previous "attendyes".
Attend verbs should be used only with event-related items (there is no check at the moment)
#### Return values
On success:
json
```"ok"```
xml
```<ok>true</ok>```
On error:
HTTP 400 BadRequest
---
### friendica/photo ### friendica/photo
#### Parameters #### Parameters
* photo_id: Resource id of a photo. * photo_id: Resource id of a photo.
* scale: (optional) scale value of the photo
Returns data of a picture with the given resource. Returns data of a picture with the given resource.
If 'scale' isn't provided, returned data include full url to each scale of the photo.
If 'scale' is set, returned data include image data base64 encoded.
possibile scale value are:
0: original or max size by server settings
1: image with or height at <= 640
2: image with or height at <= 320
3: thumbnail 160x160
4: Profile image at 175x175
5: Profile image at 80x80
6: Profile image at 48x48
An image used as profile image has only scale 4-6, other images only 0-3
#### Return values
json
```
{
"id": "photo id"
"created": "date(YYYY-MM-GG HH:MM:SS)",
"edited": "date(YYYY-MM-GG HH:MM:SS)",
"title": "photo title",
"desc": "photo description",
"album": "album name",
"filename": "original file name",
"type": "mime type",
"height": "number",
"width": "number",
"profile": "1 if is profile photo",
"link": {
"<scale>": "url to image"
...
},
// if 'scale' is set
"datasize": "size in byte",
"data": "base64 encoded image data"
}
```
xml
```
<photo>
<id>photo id</id>
<created>date(YYYY-MM-GG HH:MM:SS)</created>
<edited>date(YYYY-MM-GG HH:MM:SS)</edited>
<title>photo title</title>
<desc>photo description</desc>
<album>album name</album>
<filename>original file name</filename>
<type>mime type</type>
<height>number</height>
<width>number</width>
<profile>1 if is profile photo</profile>
<links type="array">
<link type="mime type" scale="scale number" href="image url"/>
...
</links>
</photo>
```
---
### friendica/photos/list ### friendica/photos/list
Returns a list of all photo resources of the logged in user. Returns a list of all photo resources of the logged in user.
#### Return values
json
```
[
{
id: "resource_id",
album: "album name",
filename: "original file name",
type: "image mime type",
thumb: "url to thumb sized image"
},
...
]
```
xml
```
<photos type="array">
<photo id="resource_id"
album="album name"
filename="original file name"
type="image mime type">
"url to thumb sized image"
</photo>
...
</photos>
```
---
### friends/ids ### friends/ids
#### Parameters #### Parameters
* stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false) * stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false)
@ -150,12 +314,15 @@ Returns a list of all photo resources of the logged in user.
Friendica doesn't allow showing friends of other users. Friendica doesn't allow showing friends of other users.
---
### help/test ### help/test
---
### media/upload ### media/upload
#### Parameters #### Parameters
* media: image data * media: image data
---
### oauth/request_token ### oauth/request_token
#### Parameters #### Parameters
* oauth_callback * oauth_callback
@ -163,6 +330,7 @@ Friendica doesn't allow showing friends of other users.
#### Unsupported parameters #### Unsupported parameters
* x_auth_access_type * x_auth_access_type
---
### oauth/access_token ### oauth/access_token
#### Parameters #### Parameters
* oauth_verifier * oauth_verifier
@ -172,6 +340,7 @@ Friendica doesn't allow showing friends of other users.
* x_auth_username * x_auth_username
* x_auth_mode * x_auth_mode
---
### statuses/destroy ### statuses/destroy
#### Parameters #### Parameters
* id: message number * id: message number
@ -180,12 +349,15 @@ Friendica doesn't allow showing friends of other users.
#### Unsupported parameters #### Unsupported parameters
* trim_user * trim_user
---
### statuses/followers ### statuses/followers
* include_entities: "true" shows entities for pictures and links (Default: false) * include_entities: "true" shows entities for pictures and links (Default: false)
---
### statuses/friends ### statuses/friends
* include_entities: "true" shows entities for pictures and links (Default: false) * include_entities: "true" shows entities for pictures and links (Default: false)
---
### statuses/friends_timeline ### statuses/friends_timeline
#### Parameters #### Parameters
* count: Items per page (default: 20) * count: Items per page (default: 20)
@ -201,6 +373,7 @@ Friendica doesn't allow showing friends of other users.
* trim_user * trim_user
* contributor_details * contributor_details
---
### statuses/home_timeline ### statuses/home_timeline
#### Parameters #### Parameters
* count: Items per page (default: 20) * count: Items per page (default: 20)
@ -216,6 +389,7 @@ Friendica doesn't allow showing friends of other users.
* trim_user * trim_user
* contributor_details * contributor_details
---
### statuses/mentions ### statuses/mentions
#### Parameters #### Parameters
* count: Items per page (default: 20) * count: Items per page (default: 20)
@ -229,6 +403,7 @@ Friendica doesn't allow showing friends of other users.
* trim_user * trim_user
* contributor_details * contributor_details
---
### statuses/public_timeline ### statuses/public_timeline
#### Parameters #### Parameters
* count: Items per page (default: 20) * count: Items per page (default: 20)
@ -242,6 +417,7 @@ Friendica doesn't allow showing friends of other users.
#### Unsupported parameters #### Unsupported parameters
* trim_user * trim_user
---
### statuses/replies ### statuses/replies
#### Parameters #### Parameters
* count: Items per page (default: 20) * count: Items per page (default: 20)
@ -255,6 +431,7 @@ Friendica doesn't allow showing friends of other users.
* trim_user * trim_user
* contributor_details * contributor_details
---
### statuses/retweet ### statuses/retweet
#### Parameters #### Parameters
* id: message number * id: message number
@ -263,6 +440,7 @@ Friendica doesn't allow showing friends of other users.
#### Unsupported parameters #### Unsupported parameters
* trim_user * trim_user
---
### statuses/show ### statuses/show
#### Parameters #### Parameters
* id: message number * id: message number
@ -273,6 +451,7 @@ Friendica doesn't allow showing friends of other users.
* include_my_retweet * include_my_retweet
* trim_user * trim_user
---
### statuses/update, statuses/update_with_media ### statuses/update, statuses/update_with_media
#### Parameters #### Parameters
* title: Title of the status * title: Title of the status
@ -296,6 +475,7 @@ Friendica doesn't allow showing friends of other users.
* place_id * place_id
* display_coordinates * display_coordinates
---
### statuses/user_timeline ### statuses/user_timeline
#### Parameters #### Parameters
* user_id: id of the user * user_id: id of the user
@ -313,8 +493,10 @@ Friendica doesn't allow showing friends of other users.
* trim_user * trim_user
* contributor_details * contributor_details
---
### statusnet/config ### statusnet/config
---
### statusnet/version ### statusnet/version
#### Unsupported parameters #### Unsupported parameters
@ -324,6 +506,7 @@ Friendica doesn't allow showing friends of other users.
Friendica doesn't allow showing followers of other users. Friendica doesn't allow showing followers of other users.
---
### users/search ### users/search
#### Parameters #### Parameters
* q: name of the user * q: name of the user
@ -333,6 +516,7 @@ Friendica doesn't allow showing followers of other users.
* count * count
* include_entities * include_entities
---
### users/show ### users/show
#### Parameters #### Parameters
* user_id: id of the user * user_id: id of the user
@ -349,6 +533,7 @@ Friendica doesn't allow showing friends of other users.
## Implemented API calls (not compatible with other APIs) ## Implemented API calls (not compatible with other APIs)
---
### friendica/group_show ### friendica/group_show
Return all or a specified group of the user with the containing contacts as array. Return all or a specified group of the user with the containing contacts as array.
@ -362,9 +547,11 @@ Array of:
* user: array of group members (return from api_get_user() function for each member) * user: array of group members (return from api_get_user() function for each member)
---
### friendica/group_delete ### friendica/group_delete
delete the specified group of contacts; API call need to include the correct gid AND name of the group to be deleted. delete the specified group of contacts; API call need to include the correct gid AND name of the group to be deleted.
---
### Parameters ### Parameters
* gid: id of the group to be deleted * gid: id of the group to be deleted
* name: name of the group to be deleted * name: name of the group to be deleted
@ -378,6 +565,7 @@ Array of:
* wrong users: empty array * wrong users: empty array
---
### friendica/group_create ### friendica/group_create
Create the group with the posted array of contacts as members. Create the group with the posted array of contacts as members.
#### Parameters #### Parameters
@ -398,6 +586,7 @@ Array of:
* wrong users: array of users, which were not available in the contact table * wrong users: array of users, which were not available in the contact table
---
### friendica/group_update ### friendica/group_update
Update the group with the posted array of contacts as members (post all members of the group to the call; function will remove members not posted). Update the group with the posted array of contacts as members (post all members of the group to the call; function will remove members not posted).
#### Parameters #### Parameters
@ -418,7 +607,7 @@ Array of:
* status: „missing user“ | „ok“ * status: „missing user“ | „ok“
* wrong users: array of users, which were not available in the contact table * wrong users: array of users, which were not available in the contact table
---
## Not Implemented API calls ## Not Implemented API calls
The following API calls are implemented in GNU Social but not in Friendica: (incomplete) The following API calls are implemented in GNU Social but not in Friendica: (incomplete)
@ -505,6 +694,10 @@ The following API calls from the Twitter API aren't implemented neither in Frien
* trends/closest * trends/closest
* users/report_spam * users/report_spam
---
---
## Usage Examples ## Usage Examples
### BASH / cURL ### BASH / cURL
Betamax has documentated some example API usage from a [bash script](https://en.wikipedia.org/wiki/Bash_(Unix_shell) employing [curl](https://en.wikipedia.org/wiki/CURL) (see [his posting](https://betamax65.de/display/betamax65/43539)). Betamax has documentated some example API usage from a [bash script](https://en.wikipedia.org/wiki/Bash_(Unix_shell) employing [curl](https://en.wikipedia.org/wiki/CURL) (see [his posting](https://betamax65.de/display/betamax65/43539)).

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 <?php
/** /**
* @file include/api.php * @file include/api.php
* Friendica implementation of statusnet/twitter API
* *
* @todo Automatically detect if incoming data is HTML or BBCode * @todo Automatically detect if incoming data is HTML or BBCode
*/ */
require_once('include/HTTPExceptions.php');
/* Contact details: require_once('include/bbcode.php');
Gerhard Seeber Mail: gerhard@seeber.at Friendica: http://mozartweg.dyndns.org/friendica/gerhard 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');
* Change history: require_once('mod/item.php');
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/security.php'); require_once('include/security.php');
require_once('include/contact_selectors.php'); require_once('include/contact_selectors.php');
require_once('include/html2bbcode.php'); require_once('include/html2bbcode.php');
require_once('mod/wall_upload.php'); require_once('mod/wall_upload.php');
require_once("mod/proxy.php"); require_once('mod/proxy.php');
require_once("include/message.php"); require_once('include/message.php');
require_once("include/group.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(); $API = Array();
$called_api = Null; $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() { function api_user() {
// It is not sufficient to use local_user() to check whether someone is allowed to use the API, if ($_SESSION['allow_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"])
return local_user(); return local_user();
return false; 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() { function api_source() {
if (requestdata('source')) if (requestdata('source'))
return (requestdata('source')); return (requestdata('source'));
@ -74,25 +73,63 @@
return ("api"); 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){ function api_date($str){
//Wed May 23 06:01:13 +0000 2007 //Wed May 23 06:01:13 +0000 2007
return datetime_convert('UTC', 'UTC', $str, "D M d H:i:s +0000 Y" ); 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; global $API;
$API[$path] = array('func'=>$func, 'auth'=>$auth); $API[$path] = array(
'func'=>$func,
'auth'=>$auth,
'method'=> $method
);
// Workaround for hotot // Workaround for hotot
$path = str_replace("api/", "api/1.1/", $path); $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){ function api_login(&$a){
// login with oauth // login with oauth
try{ try{
@ -105,8 +142,7 @@
} }
echo __file__.__line__.__function__."<pre>"; var_dump($consumer, $token); die(); echo __file__.__line__.__function__."<pre>"; var_dump($consumer, $token); die();
}catch(Exception $e){ }catch(Exception $e){
logger(__file__.__line__.__function__."\n".$e); logger($e);
//die(__file__.__line__.__function__."<pre>".$e); die();
} }
@ -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){ function api_call(&$a){
GLOBAL $API, $called_api; GLOBAL $API, $called_api;
// preset
$type="json"; $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){ foreach ($API as $p=>$info){
if (strpos($a->query_string, $p)===0){ if (strpos($a->query_string, $p)===0){
if (!api_check_method($info['method'])){
throw new MethodNotAllowedException();
}
$called_api= explode("/",$p); $called_api= explode("/",$p);
//unset($_SERVER['PHP_AUTH_USER']); //unset($_SERVER['PHP_AUTH_USER']);
if ($info['auth']===true && api_user()===false) { if ($info['auth']===true && api_user()===false) {
@ -209,19 +274,17 @@
logger('API call for ' . $a->user['username'] . ': ' . $a->query_string); logger('API call for ' . $a->user['username'] . ': ' . $a->query_string);
logger('API parameters: ' . print_r($_REQUEST,true)); 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); $stamp = microtime(true);
$r = call_user_func($info['func'], $a, $type); $r = call_user_func($info['func'], $a, $type);
$duration = (float)(microtime(true)-$stamp); $duration = (float)(microtime(true)-$stamp);
logger("API call duration: ".round($duration, 2)."\t".$a->query_string, LOGGER_DEBUG); 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){ switch($type){
case "xml": case "xml":
@ -253,40 +316,57 @@
break; break;
} }
//echo "<pre>"; var_dump($r); die();
} }
} }
header("HTTP/1.1 404 Not Found"); throw new NotImplementedException();
logger('API call not implemented: '.$a->query_string." - ".print_r($_REQUEST,true)); } catch (HTTPException $e) {
return(api_error($a, $type, "not implemented")); 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 * @brief Format API error string
$r = "<status><error>".$error."</error><request>".$a->query_string."</request></status>"; *
* @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){ switch($type){
case "xml": case "xml":
header ("Content-Type: text/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; break;
case "json": case "json":
header ("Content-Type: application/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; break;
case "rss": case "rss":
header ("Content-Type: application/rss+xml"); 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; break;
case "atom": case "atom":
header ("Content-Type: application/atom+xml"); 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; 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){ function api_rss_extra(&$a, $arr, $user_info){
if (is_null($user_info)) $user_info = api_get_user($a); 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){ function api_unique_id_to_url($id){
$r = q("SELECT `url` FROM `unique_contacts` WHERE `id`=%d LIMIT 1", $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"){ function api_get_user(&$a, $contact_id = Null, $type = "json"){
global $called_api; global $called_api;
@ -342,7 +430,7 @@
$user = dbesc(api_unique_id_to_url($contact_id)); $user = dbesc(api_unique_id_to_url($contact_id));
if ($user == "") if ($user == "")
die(api_error($a, $type, t("User not found."))); throw new BadRequestException("User not found.");
$url = $user; $url = $user;
$extra_query = "AND `contact`.`nurl` = '%s' "; $extra_query = "AND `contact`.`nurl` = '%s' ";
@ -353,7 +441,7 @@
$user = dbesc(api_unique_id_to_url($_GET['user_id'])); $user = dbesc(api_unique_id_to_url($_GET['user_id']));
if ($user == "") if ($user == "")
die(api_error($a, $type, t("User not found."))); throw new BadRequestException("User not found.");
$url = $user; $url = $user;
$extra_query = "AND `contact`.`nurl` = '%s' "; $extra_query = "AND `contact`.`nurl` = '%s' ";
@ -390,7 +478,8 @@
if (!$user) { if (!$user) {
if (api_user()===false) { if (api_user()===false) {
api_login($a); return False; api_login($a);
return False;
} else { } else {
$user = $_SESSION['uid']; $user = $_SESSION['uid'];
$extra_query = "AND `contact`.`uid` = %d AND `contact`.`self` = 1 "; $extra_query = "AND `contact`.`uid` = %d AND `contact`.`self` = 1 ";
@ -461,9 +550,9 @@
); );
return $ret; return $ret;
} else } else {
die(api_error($a, $type, t("User not found."))); throw new BadRequestException("User not found.");
}
} }
if($uinfo[0]['self']) { if($uinfo[0]['self']) {
@ -672,7 +761,7 @@
* http://developer.twitter.com/doc/get/account/verify_credentials * http://developer.twitter.com/doc/get/account/verify_credentials
*/ */
function api_account_verify_credentials(&$a, $type){ 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($_REQUEST["user_id"]);
unset($_GET["user_id"]); unset($_GET["user_id"]);
@ -723,7 +812,7 @@
function api_statuses_mediap(&$a, $type) { function api_statuses_mediap(&$a, $type) {
if (api_user()===false) { if (api_user()===false) {
logger('api_statuses_update: no user'); logger('api_statuses_update: no user');
return false; throw new ForbiddenException();
} }
$user_info = api_get_user($a); $user_info = api_get_user($a);
@ -757,14 +846,14 @@
// this should output the last post (the one we just posted). // this should output the last post (the one we just posted).
return api_status_show($a,$type); 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*/ /*Waitman Gobble Mod*/
function api_statuses_update(&$a, $type) { function api_statuses_update(&$a, $type) {
if (api_user()===false) { if (api_user()===false) {
logger('api_statuses_update: no user'); logger('api_statuses_update: no user');
return false; throw new ForbiddenException();
} }
$user_info = api_get_user($a); $user_info = api_get_user($a);
@ -882,7 +971,7 @@
$_REQUEST['body'] .= "\n\n".$media; $_REQUEST['body'] .= "\n\n".$media;
} }
/// @TODO Multiple IDs // To-Do: Multiple IDs
if (requestdata('media_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", $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()); intval(requestdata('media_ids')), api_user());
@ -908,27 +997,27 @@
// this should output the last post (the one we just posted). // this should output the last post (the one we just posted).
return api_status_show($a,$type); return api_status_show($a,$type);
} }
api_register_func('api/statuses/update','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_register_func('api/statuses/update_with_media','api_statuses_update', true, API_METHOD_POST);
function api_media_upload(&$a, $type) { function api_media_upload(&$a, $type) {
if (api_user()===false) { if (api_user()===false) {
logger('no user'); logger('no user');
return false; throw new ForbiddenException();
} }
$user_info = api_get_user($a); $user_info = api_get_user($a);
if(!x($_FILES,'media')) { if(!x($_FILES,'media')) {
// Output error // Output error
return false; throw new BadRequestException("No media.");
} }
$media = wall_upload_post($a, false); $media = wall_upload_post($a, false);
if(!$media) { if(!$media) {
// Output error // Output error
return false; throw new InternalServerErrorException();
} }
$returndata = array(); $returndata = array();
@ -943,8 +1032,7 @@
return array("media" => $returndata); return array("media" => $returndata);
} }
api_register_func('api/media/upload','api_media_upload', true, API_METHOD_POST);
api_register_func('api/media/upload','api_media_upload', true);
function api_status_show(&$a, $type){ function api_status_show(&$a, $type){
$user_info = api_get_user($a); $user_info = api_get_user($a);
@ -1180,11 +1268,12 @@
$userlist[] = $userdata["user"]; $userlist[] = $userdata["user"];
} }
$userlist = array("users" => $userlist); $userlist = array("users" => $userlist);
} else } else {
die(api_error($a, $type, t("User not found."))); throw new BadRequestException("User not found.");
} else }
die(api_error($a, $type, t("User not found."))); } else {
throw new BadRequestException("User not found.");
}
return ($userlist); return ($userlist);
} }
@ -1194,11 +1283,11 @@
* *
* http://developer.twitter.com/doc/get/statuses/home_timeline * http://developer.twitter.com/doc/get/statuses/home_timeline
* *
* @TODO Optional parameters * TODO: Optional parameters
* @TODO Add reply info * TODO: Add reply info
*/ */
function api_statuses_home_timeline(&$a, $type){ 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($_REQUEST["user_id"]);
unset($_GET["user_id"]); unset($_GET["user_id"]);
@ -1281,7 +1370,7 @@
api_register_func('api/statuses/friends_timeline','api_statuses_home_timeline', true); api_register_func('api/statuses/friends_timeline','api_statuses_home_timeline', true);
function api_statuses_public_timeline(&$a, $type){ 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); $user_info = api_get_user($a);
// get last newtork messages // get last newtork messages
@ -1351,7 +1440,7 @@
* *
*/ */
function api_statuses_show(&$a, $type){ 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); $user_info = api_get_user($a);
@ -1389,8 +1478,9 @@
intval($id) intval($id)
); );
if (!$r) if (!$r) {
die(api_error($a, $type, t("There is no status with this id."))); throw new BadRequestException("There is no status with this id.");
}
$ret = api_format_items($r,$user_info); $ret = api_format_items($r,$user_info);
@ -1414,7 +1504,7 @@
* *
*/ */
function api_conversation_show(&$a, $type){ 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); $user_info = api_get_user($a);
@ -1464,7 +1554,7 @@
); );
if (!$r) 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); $ret = api_format_items($r,$user_info);
@ -1480,7 +1570,7 @@
function api_statuses_repeat(&$a, $type){ function api_statuses_repeat(&$a, $type){
global $called_api; global $called_api;
if (api_user()===false) return false; if (api_user()===false) throw new ForbiddenException();
$user_info = api_get_user($a); $user_info = api_get_user($a);
@ -1538,13 +1628,13 @@
$called_api = null; $called_api = null;
return(api_status_show($a,$type)); 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){ 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); $user_info = api_get_user($a);
@ -1566,7 +1656,7 @@
return($ret); 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){ function api_statuses_mentions(&$a, $type){
if (api_user()===false) return false; if (api_user()===false) throw new ForbiddenException();
unset($_REQUEST["user_id"]); unset($_REQUEST["user_id"]);
unset($_GET["user_id"]); unset($_GET["user_id"]);
@ -1653,7 +1743,7 @@
function api_statuses_user_timeline(&$a, $type){ 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); $user_info = api_get_user($a);
// get last network messages // get last network messages
@ -1714,7 +1804,6 @@
return api_apply_template("timeline", $type, $data); return api_apply_template("timeline", $type, $data);
} }
api_register_func('api/statuses/user_timeline','api_statuses_user_timeline', true); 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 * 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){ function api_favorites_create_destroy(&$a, $type){
if (api_user()===false) return false; if (api_user()===false) throw new ForbiddenException();
// for versioned api. // for versioned api.
/// @TODO We need a better global soluton /// @TODO We need a better global soluton
@ -1743,7 +1832,8 @@
$item = q("SELECT * FROM item WHERE id=%d AND uid=%d", $item = q("SELECT * FROM item WHERE id=%d AND uid=%d",
$itemid, api_user()); $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){ switch($action){
case "create": case "create":
@ -1753,7 +1843,7 @@
$item[0]['starred']=0; $item[0]['starred']=0;
break; break;
default: 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", $r = q("UPDATE item SET starred=%d WHERE id=%d AND uid=%d",
$item[0]['starred'], $itemid, api_user()); $item[0]['starred'], $itemid, api_user());
@ -1761,7 +1851,8 @@
q("UPDATE thread SET starred=%d WHERE iid=%d AND uid=%d", q("UPDATE thread SET starred=%d WHERE iid=%d AND uid=%d",
$item[0]['starred'], $itemid, api_user()); $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); $user_info = api_get_user($a);
@ -1777,14 +1868,13 @@
return api_apply_template("status", $type, $data); return api_apply_template("status", $type, $data);
} }
api_register_func('api/favorites/create', 'api_favorites_create_destroy', true, API_METHOD_POST);
api_register_func('api/favorites/create', 'api_favorites_create_destroy', true); api_register_func('api/favorites/destroy', 'api_favorites_create_destroy', true, API_METHOD_DELETE);
api_register_func('api/favorites/destroy', 'api_favorites_create_destroy', true);
function api_favorites(&$a, $type){ function api_favorites(&$a, $type){
global $called_api; global $called_api;
if (api_user()===false) return false; if (api_user()===false) throw new ForbiddenException();
$called_api= array(); $called_api= array();
@ -1842,14 +1932,12 @@
return api_apply_template("timeline", $type, $data); return api_apply_template("timeline", $type, $data);
} }
api_register_func('api/favorites','api_favorites', true); api_register_func('api/favorites','api_favorites', true);
function api_format_as($a, $ret, $user_info) { function api_format_as($a, $ret, $user_info) {
$as = array(); $as = array();
$as['title'] = $a->config['sitename']." Public Timeline"; $as['title'] = $a->config['sitename']." Public Timeline";
$items = array(); $items = array();
@ -2015,8 +2103,10 @@
} }
function api_get_entitities(&$text, $bbcode) { 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(); $a = get_app();
@ -2166,7 +2256,7 @@
return($entities); return($entities);
} }
function api_format_items_embeded_images($item, $text){ function api_format_items_embeded_images(&$item, $text){
$a = get_app(); $a = get_app();
$text = preg_replace_callback( $text = preg_replace_callback(
"|data:image/([^;]+)[^=]+=*|m", "|data:image/([^;]+)[^=]+=*|m",
@ -2177,7 +2267,42 @@
return $text; 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(); $a = get_app();
$ret = Array(); $ret = Array();
@ -2250,6 +2375,7 @@
//'entities' => NULL, //'entities' => NULL,
'statusnet_html' => $converted["html"], 'statusnet_html' => $converted["html"],
'statusnet_conversation_id' => $item['parent'], 'statusnet_conversation_id' => $item['parent'],
'friendica_activities' => api_format_items_likes($item),
); );
if (count($converted["attachments"]) > 0) if (count($converted["attachments"]) > 0)
@ -2297,7 +2423,6 @@
function api_account_rate_limit_status(&$a,$type) { function api_account_rate_limit_status(&$a,$type) {
$hash = array( $hash = array(
'reset_time_in_seconds' => strtotime('now + 1 hour'), 'reset_time_in_seconds' => strtotime('now + 1 hour'),
'remaining_hits' => (string) 150, 'remaining_hits' => (string) 150,
@ -2308,31 +2433,26 @@
$hash['resettime_in_seconds'] = $hash['reset_time_in_seconds']; $hash['resettime_in_seconds'] = $hash['reset_time_in_seconds'];
return api_apply_template('ratelimit', $type, array('$hash' => $hash)); return api_apply_template('ratelimit', $type, array('$hash' => $hash));
} }
api_register_func('api/account/rate_limit_status','api_account_rate_limit_status',true); api_register_func('api/account/rate_limit_status','api_account_rate_limit_status',true);
function api_help_test(&$a,$type) { function api_help_test(&$a,$type) {
if ($type == 'xml') if ($type == 'xml')
$ok = "true"; $ok = "true";
else else
$ok = "ok"; $ok = "ok";
return api_apply_template('test', $type, array("$ok" => $ok)); return api_apply_template('test', $type, array("$ok" => $ok));
} }
api_register_func('api/help/test','api_help_test',false); api_register_func('api/help/test','api_help_test',false);
function api_lists(&$a,$type) { function api_lists(&$a,$type) {
$ret = array(); $ret = array();
return array($ret); return array($ret);
} }
api_register_func('api/lists','api_lists',true); api_register_func('api/lists','api_lists',true);
function api_lists_list(&$a,$type) { function api_lists_list(&$a,$type) {
$ret = array(); $ret = array();
return array($ret); return array($ret);
} }
@ -2344,7 +2464,7 @@
* returns: json, xml * returns: json, xml
**/ **/
function api_statuses_f(&$a, $type, $qtype) { 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); $user_info = api_get_user($a);
if (x($_GET,'cursor') && $_GET['cursor']=='undefined'){ if (x($_GET,'cursor') && $_GET['cursor']=='undefined'){
@ -2437,26 +2557,27 @@
api_register_func('api/statusnet/config','api_statusnet_config',false); api_register_func('api/statusnet/config','api_statusnet_config',false);
function api_statusnet_version(&$a,$type) { function api_statusnet_version(&$a,$type) {
// liar // liar
$fake_statusnet_version = "0.9.7";
if($type === 'xml') { if($type === 'xml') {
header("Content-type: application/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(); killme();
} }
elseif($type === 'json') { elseif($type === 'json') {
header("Content-type: application/json"); header("Content-type: application/json");
echo '"0.9.7"'; echo '"'.$fake_statusnet_version.'"';
killme(); killme();
} }
} }
api_register_func('api/statusnet/version','api_statusnet_version',false); 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) { function api_ff_ids(&$a,$type,$qtype) {
if(! api_user()) if(! api_user()) throw new ForbiddenException();
return false;
$user_info = api_get_user($a); $user_info = api_get_user($a);
@ -2510,7 +2631,7 @@
function api_direct_messages_new(&$a, $type) { 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; 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); 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) { function api_direct_messages_box(&$a, $type, $box) {
if (api_user()===false) return false; if (api_user()===false) throw new ForbiddenException();
// params // params
$count = (x($_GET,'count')?$_GET['count']:20); $count = (x($_GET,'count')?$_GET['count']:20);
@ -2701,36 +2821,75 @@
function api_fr_photos_list(&$a,$type) { function api_fr_photos_list(&$a,$type) {
if (api_user()===false) return false; if (api_user()===false) throw new ForbiddenException();
$r = q("select distinct `resource-id` from photo where uid = %d and album != 'Contact Photos' ", $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()) intval(local_user())
); );
$typetoext = array(
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif'
);
$data = array('photos'=>array());
if($r) { if($r) {
$ret = array(); foreach($r as $rr) {
foreach($r as $rr) $photo = array();
$ret[] = $rr['resource-id']; $photo['id'] = $rr['resource-id'];
header("Content-type: application/json"); $photo['album'] = $rr['album'];
echo json_encode($ret); $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) { function api_fr_photo_detail(&$a,$type) {
if (api_user()===false) return false; if (api_user()===false) throw new ForbiddenException();
if(! $_REQUEST['photo_id']) return false; if(!x($_REQUEST,'photo_id')) throw new BadRequestException("No photo id.");
$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", $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()), intval(local_user()),
dbesc($_REQUEST['photo_id']), dbesc($_REQUEST['photo_id']),
intval($scale) $scale_sql
); );
$typetoext = array(
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif'
);
if ($r) { if ($r) {
header("Content-type: application/json"); $data = array('photo' => $r[0]);
$r[0]['data'] = base64_encode($r[0]['data']); if ($scale !== false) {
echo json_encode($r[0]); $data['photo']['data'] = base64_encode($data['photo']['data']);
} else {
unset($data['photo']['datasize']); //needed only with scale param
}
$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); api_register_func('api/friendica/photos/list', 'api_fr_photos_list', true);
@ -2754,7 +2913,7 @@
$c_url = ((x($_GET,'c_url')) ? $_GET['c_url'] : ''); $c_url = ((x($_GET,'c_url')) ? $_GET['c_url'] : '');
if ($url === '' || $c_url === '') if ($url === '' || $c_url === '')
die((api_error($a, 'json', "Wrong parameters"))); throw new BadRequestException("Wrong parameters.");
$c_url = normalise_link($c_url); $c_url = normalise_link($c_url);
@ -2766,7 +2925,7 @@
); );
if ((! count($r)) || ($r[0]['network'] !== NETWORK_DFRN)) if ((! count($r)) || ($r[0]['network'] !== NETWORK_DFRN))
die((api_error($a, 'json', "Unknown contact"))); throw new BadRequestException("Unknown contact");
$cid = $r[0]['id']; $cid = $r[0]['id'];
@ -2801,7 +2960,6 @@
api_register_func('api/friendica/remoteauth', 'api_friendica_remoteauth', true); api_register_func('api/friendica/remoteauth', 'api_friendica_remoteauth', true);
function api_share_as_retweet(&$item) { function api_share_as_retweet(&$item) {
$body = trim($item["body"]); $body = trim($item["body"]);
@ -2871,8 +3029,10 @@ function api_share_as_retweet(&$item) {
} }
function api_get_nick($profile) { function api_get_nick($profile) {
/// @TODO Remove trailing junk from profile url /* To-Do:
/// @TODO pump.io check has to check the website - remove trailing junk from profile url
- pump.io check has to check the website
*/
$nick = ""; $nick = "";
@ -2920,7 +3080,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 == "") { //if (!$nick == "") {
// $pumpio = preg_replace("=https?://(.*)/(.*)/=ism", "$2", $profile."/"); // $pumpio = preg_replace("=https?://(.*)/(.*)/=ism", "$2", $profile."/");
// if ($pumpio != $profile) // if ($pumpio != $profile)
@ -3034,7 +3194,7 @@ function api_best_nickname(&$contacts) {
// return all or a specified group of the user with the containing contacts // return all or a specified group of the user with the containing contacts
function api_friendica_group_show(&$a, $type) { function api_friendica_group_show(&$a, $type) {
if (api_user()===false) return false; if (api_user()===false) throw new ForbiddenException();
// params // params
$user_info = api_get_user($a); $user_info = api_get_user($a);
@ -3048,7 +3208,7 @@ function api_best_nickname(&$contacts) {
intval($gid)); intval($gid));
// error message if specified gid is not in database // error message if specified gid is not in database
if (count($r) == 0) if (count($r) == 0)
die(api_error($a, $type, 'gid not available')); throw new BadRequestException("gid not available");
} }
else else
$r = q("SELECT * FROM `group` WHERE `deleted` = 0 AND `uid` = %d", $r = q("SELECT * FROM `group` WHERE `deleted` = 0 AND `uid` = %d",
@ -3071,7 +3231,7 @@ function api_best_nickname(&$contacts) {
// delete the specified group of the user // delete the specified group of the user
function api_friendica_group_delete(&$a, $type) { function api_friendica_group_delete(&$a, $type) {
if (api_user()===false) return false; if (api_user()===false) throw new ForbiddenException();
// params // params
$user_info = api_get_user($a); $user_info = api_get_user($a);
@ -3081,7 +3241,7 @@ function api_best_nickname(&$contacts) {
// error if no gid specified // error if no gid specified
if ($gid == 0 || $name == "") 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 // get data of the specified group id
$r = q("SELECT * FROM `group` WHERE `uid` = %d AND `id` = %d", $r = q("SELECT * FROM `group` WHERE `uid` = %d AND `id` = %d",
@ -3089,7 +3249,7 @@ function api_best_nickname(&$contacts) {
intval($gid)); intval($gid));
// error message if specified gid is not in database // error message if specified gid is not in database
if (count($r) == 0) 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 // get data of the specified group id and group name
$rname = q("SELECT * FROM `group` WHERE `uid` = %d AND `id` = %d AND `name` = '%s'", $rname = q("SELECT * FROM `group` WHERE `uid` = %d AND `id` = %d AND `name` = '%s'",
@ -3098,7 +3258,7 @@ function api_best_nickname(&$contacts) {
dbesc($name)); dbesc($name));
// error message if specified gid is not in database // error message if specified gid is not in database
if (count($rname) == 0) if (count($rname) == 0)
die(api_error($a, $type, 'wrong group name')); throw new BadRequestException('wrong group name');
// delete group // delete group
$ret = group_rmv($uid, $name); $ret = group_rmv($uid, $name);
@ -3108,14 +3268,14 @@ function api_best_nickname(&$contacts) {
return api_apply_template("group_delete", $type, array('$result' => $success)); return api_apply_template("group_delete", $type, array('$result' => $success));
} }
else 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 // create the specified group with the posted array of contacts
function api_friendica_group_create(&$a, $type) { function api_friendica_group_create(&$a, $type) {
if (api_user()===false) return false; if (api_user()===false) throw new ForbiddenException();
// params // params
$user_info = api_get_user($a); $user_info = api_get_user($a);
@ -3126,7 +3286,7 @@ function api_best_nickname(&$contacts) {
// error if no name specified // error if no name specified
if ($name == "") 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 // get data of the specified group name
$rname = q("SELECT * FROM `group` WHERE `uid` = %d AND `name` = '%s' AND `deleted` = 0", $rname = q("SELECT * FROM `group` WHERE `uid` = %d AND `name` = '%s' AND `deleted` = 0",
@ -3134,7 +3294,7 @@ function api_best_nickname(&$contacts) {
dbesc($name)); dbesc($name));
// error message if specified group name already exists // error message if specified group name already exists
if (count($rname) != 0) 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 // check if specified group name is a deleted group
$rname = q("SELECT * FROM `group` WHERE `uid` = %d AND `name` = '%s' AND `deleted` = 1", $rname = q("SELECT * FROM `group` WHERE `uid` = %d AND `name` = '%s' AND `deleted` = 1",
@ -3149,7 +3309,7 @@ function api_best_nickname(&$contacts) {
if ($ret) if ($ret)
$gid = group_byname($uid, $name); $gid = group_byname($uid, $name);
else else
die(api_error($a, $type, 'other API error')); throw new BadRequestException('other API error');
// add members // add members
$erroraddinguser = false; $erroraddinguser = false;
@ -3173,12 +3333,12 @@ function api_best_nickname(&$contacts) {
$success = array('success' => true, 'gid' => $gid, 'name' => $name, 'status' => $status, 'wrong users' => $errorusers); $success = array('success' => true, 'gid' => $gid, 'name' => $name, 'status' => $status, 'wrong users' => $errorusers);
return api_apply_template("group_create", $type, array('result' => $success)); 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 // update the specified group with the posted array of contacts
function api_friendica_group_update(&$a, $type) { function api_friendica_group_update(&$a, $type) {
if (api_user()===false) return false; if (api_user()===false) throw new ForbiddenException();
// params // params
$user_info = api_get_user($a); $user_info = api_get_user($a);
@ -3190,11 +3350,11 @@ function api_best_nickname(&$contacts) {
// error if no name specified // error if no name specified
if ($name == "") if ($name == "")
die(api_error($a, $type, 'group name not specified')); throw new BadRequestException('group name not specified');
// error if no gid specified // error if no gid specified
if ($gid == "") if ($gid == "")
die(api_error($a, $type, 'gid not specified')); throw new BadRequestException('gid not specified');
// remove members // remove members
$members = group_get_members($gid); $members = group_get_members($gid);
@ -3230,7 +3390,38 @@ function api_best_nickname(&$contacts) {
$success = array('success' => true, 'gid' => $gid, 'name' => $name, 'status' => $status, 'wrong users' => $errorusers); $success = array('success' => true, 'gid' => $gid, 'name' => $name, 'status' => $status, 'wrong users' => $errorusers);
return api_apply_template("group_update", $type, array('result' => $success)); 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: To.Do:
@ -3243,7 +3434,7 @@ To.Do:
[include_rts] => 1 [include_rts] => 1
[include_reply_count] => true [include_reply_count] => true
[include_descendent_reply_count] => true [include_descendent_reply_count] => true
(?)
Not implemented by now: Not implemented by now:

374
include/like.php Normal file
View file

@ -0,0 +1,374 @@
<?php
/**
* @brief add/remove activity to an item
*
* Toggle activities as like,dislike,attend of an item
*
* @param string $item_id
* @param string $verb
* Activity verb. One of
* like, unlike, dislike, undislike, attendyes, unattendyes,
* attendno, unattendno, attendmaybe, unattendmaybe
* @hook 'post_local_end'
* array $arr
* 'post_id' => ID of posted item
*/
function do_like($item_id, $verb) {
$a = get_app();
if(! local_user() && ! remote_user()) {
return false;
}
switch($verb) {
case 'like':
case 'unlike':
$activity = ACTIVITY_LIKE;
break;
case 'dislike':
case 'undislike':
$activity = ACTIVITY_DISLIKE;
break;
case 'attendyes':
case 'unattendyes':
$activity = ACTIVITY_ATTEND;
break;
case 'attendno':
case 'unattendno':
$activity = ACTIVITY_ATTENDNO;
break;
case 'attendmaybe':
case 'unattendmaybe':
$activity = ACTIVITY_ATTENDMAYBE;
break;
default:
return false;
break;
}
logger('like: verb ' . $verb . ' item ' . $item_id);
$r = q("SELECT * FROM `item` WHERE `id` = '%s' OR `uri` = '%s' LIMIT 1",
dbesc($item_id),
dbesc($item_id)
);
if(! $item_id || (! count($r))) {
logger('like: no item ' . $item_id);
return false;
}
$item = $r[0];
$owner_uid = $item['uid'];
if(! can_write_wall($a,$owner_uid)) {
return false;
}
$remote_owner = null;
if(! $item['wall']) {
// The top level post may have been written by somebody on another system
$r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
intval($item['contact-id']),
intval($item['uid'])
);
if(! count($r))
return false;
if(! $r[0]['self'])
$remote_owner = $r[0];
}
// this represents the post owner on this system.
$r = q("SELECT `contact`.*, `user`.`nickname` FROM `contact` LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
WHERE `contact`.`self` = 1 AND `contact`.`uid` = %d LIMIT 1",
intval($owner_uid)
);
if(count($r))
$owner = $r[0];
if(! $owner) {
logger('like: no owner');
return false;
}
if(! $remote_owner)
$remote_owner = $owner;
// This represents the person posting
if((local_user()) && (local_user() == $owner_uid)) {
$contact = $owner;
}
else {
$r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
intval($_SESSION['visitor_id']),
intval($owner_uid)
);
if(count($r))
$contact = $r[0];
}
if(! $contact) {
return false;
}
$verbs = " '".dbesc($activity)."' ";
// event participation are essentially radio toggles. If you make a subsequent choice,
// we need to eradicate your first choice.
if($activity === ACTIVITY_ATTEND || $activity === ACTIVITY_ATTENDNO || $activity === ACTIVITY_ATTENDMAYBE) {
$verbs = " '" . dbesc(ACTIVITY_ATTEND) . "','" . dbesc(ACTIVITY_ATTENDNO) . "','" . dbesc(ACTIVITY_ATTENDMAYBE) . "' ";
}
$r = q("SELECT `id`, `guid` FROM `item` WHERE `verb` IN ( $verbs ) AND `deleted` = 0
AND `contact-id` = %d AND `uid` = %d
AND (`parent` = '%s' OR `parent-uri` = '%s' OR `thr-parent` = '%s') LIMIT 1",
intval($contact['id']), intval($owner_uid),
dbesc($item_id), dbesc($item_id), dbesc($item['uri'])
);
if(count($r)) {
$like_item = $r[0];
// Already voted, undo it
$r = q("UPDATE `item` SET `deleted` = 1, `unseen` = 1, `changed` = '%s' WHERE `id` = %d",
dbesc(datetime_convert()),
intval($like_item['id'])
);
// Clean up the Diaspora signatures for this like
// Go ahead and do it even if Diaspora support is disabled. We still want to clean up
// if it had been enabled in the past
$r = q("DELETE FROM `sign` WHERE `iid` = %d",
intval($like_item['id'])
);
// Save the author information for the unlike in case we need to relay to Diaspora
store_diaspora_like_retract_sig($activity, $item, $like_item, $contact);
$like_item_id = $like_item['id'];
proc_run('php',"include/notifier.php","like","$like_item_id");
return true;
}
$uri = item_new_uri($a->get_hostname(),$owner_uid);
$post_type = (($item['resource-id']) ? t('photo') : t('status'));
if($item['obj_type'] === ACTIVITY_OBJ_EVENT)
$post_type = t('event');
$objtype = (($item['resource-id']) ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE );
$link = xmlify('<link rel="alternate" type="text/html" href="' . $a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id'] . '" />' . "\n") ;
$body = $item['body'];
$obj = <<< EOT
<object>
<type>$objtype</type>
<local>1</local>
<id>{$item['uri']}</id>
<link>$link</link>
<title></title>
<content>$body</content>
</object>
EOT;
if($verb === 'like')
$bodyverb = t('%1$s likes %2$s\'s %3$s');
if($verb === 'dislike')
$bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s');
if($verb === 'attendyes')
$bodyverb = t('%1$s is attending %2$s\'s %3$s');
if($verb === 'attendno')
$bodyverb = t('%1$s is not attending %2$s\'s %3$s');
if($verb === 'attendmaybe')
$bodyverb = t('%1$s may attend %2$s\'s %3$s');
if(! isset($bodyverb))
return false;
$arr = array();
$arr['uri'] = $uri;
$arr['uid'] = $owner_uid;
$arr['contact-id'] = $contact['id'];
$arr['type'] = 'activity';
$arr['wall'] = $item['wall'];
$arr['origin'] = 1;
$arr['gravity'] = GRAVITY_LIKE;
$arr['parent'] = $item['id'];
$arr['parent-uri'] = $item['uri'];
$arr['thr-parent'] = $item['uri'];
$arr['owner-name'] = $remote_owner['name'];
$arr['owner-link'] = $remote_owner['url'];
$arr['owner-avatar'] = $remote_owner['thumb'];
$arr['author-name'] = $contact['name'];
$arr['author-link'] = $contact['url'];
$arr['author-avatar'] = $contact['thumb'];
$ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]';
$alink = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]';
$plink = '[url=' . $a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id'] . ']' . $post_type . '[/url]';
$arr['body'] = sprintf( $bodyverb, $ulink, $alink, $plink );
$arr['verb'] = $activity;
$arr['object-type'] = $objtype;
$arr['object'] = $obj;
$arr['allow_cid'] = $item['allow_cid'];
$arr['allow_gid'] = $item['allow_gid'];
$arr['deny_cid'] = $item['deny_cid'];
$arr['deny_gid'] = $item['deny_gid'];
$arr['visible'] = 1;
$arr['unseen'] = 1;
$arr['last-child'] = 0;
$post_id = item_store($arr);
if(! $item['visible']) {
$r = q("UPDATE `item` SET `visible` = 1 WHERE `id` = %d AND `uid` = %d",
intval($item['id']),
intval($owner_uid)
);
}
// Save the author information for the like in case we need to relay to Diaspora
store_diaspora_like_sig($activity, $post_type, $contact, $post_id);
$arr['id'] = $post_id;
call_hooks('post_local_end', $arr);
proc_run('php',"include/notifier.php","like","$post_id");
return true;
}
function store_diaspora_like_retract_sig($activity, $item, $like_item, $contact) {
// Note that we can only create a signature for a user of the local server. We don't have
// a key for remote users. That is ok, because if a remote user is "unlike"ing a post, it
// means we are the relay, and for relayable_retractions, Diaspora
// only checks the parent_author_signature if it doesn't have to relay further
//
// If $item['resource-id'] exists, it means the item is a photo. Diaspora doesn't support
// likes on photos, so don't bother.
$enabled = intval(get_config('system','diaspora_enabled'));
if(! $enabled) {
logger('mod_like: diaspora support disabled, not storing like retraction signature', LOGGER_DEBUG);
return;
}
logger('mod_like: storing diaspora like retraction signature');
if(($activity === ACTIVITY_LIKE) && (! $item['resource-id'])) {
$signed_text = $like_item['guid'] . ';' . 'Like';
// Only works for NETWORK_DFRN
$contact_baseurl_start = strpos($contact['url'],'://') + 3;
$contact_baseurl_length = strpos($contact['url'],'/profile') - $contact_baseurl_start;
$contact_baseurl = substr($contact['url'], $contact_baseurl_start, $contact_baseurl_length);
$diaspora_handle = $contact['nick'] . '@' . $contact_baseurl;
// Get contact's private key if he's a user of the local Friendica server
$r = q("SELECT `contact`.`uid` FROM `contact` WHERE `url` = '%s' AND `self` = 1 LIMIT 1",
dbesc($contact['url'])
);
if( $r) {
$contact_uid = $r['uid'];
$r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1",
intval($contact_uid)
);
if( $r)
$authorsig = base64_encode(rsa_sign($signed_text,$r['prvkey'],'sha256'));
}
if(! isset($authorsig))
$authorsig = '';
q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
intval($like_item['id']),
dbesc($signed_text),
dbesc($authorsig),
dbesc($diaspora_handle)
);
}
return;
}
function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) {
// Note that we can only create a signature for a user of the local server. We don't have
// a key for remote users. That is ok, because if a remote user is "unlike"ing a post, it
// means we are the relay, and for relayable_retractions, Diaspora
// only checks the parent_author_signature if it doesn't have to relay further
$enabled = intval(get_config('system','diaspora_enabled'));
if(! $enabled) {
logger('mod_like: diaspora support disabled, not storing like signature', LOGGER_DEBUG);
return;
}
logger('mod_like: storing diaspora like signature');
if(($activity === ACTIVITY_LIKE) && ($post_type === t('status'))) {
// Only works for NETWORK_DFRN
$contact_baseurl_start = strpos($contact['url'],'://') + 3;
$contact_baseurl_length = strpos($contact['url'],'/profile') - $contact_baseurl_start;
$contact_baseurl = substr($contact['url'], $contact_baseurl_start, $contact_baseurl_length);
$diaspora_handle = $contact['nick'] . '@' . $contact_baseurl;
// Get contact's private key if he's a user of the local Friendica server
$r = q("SELECT `contact`.`uid` FROM `contact` WHERE `url` = '%s' AND `self` = 1 LIMIT 1",
dbesc($contact['url'])
);
if( $r) {
$contact_uid = $r['uid'];
$r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1",
intval($contact_uid)
);
if( $r)
$contact_uprvkey = $r['prvkey'];
}
$r = q("SELECT guid, parent FROM `item` WHERE id = %d LIMIT 1",
intval($post_id)
);
if( $r) {
$p = q("SELECT guid FROM `item` WHERE id = %d AND parent = %d LIMIT 1",
intval($r[0]['parent']),
intval($r[0]['parent'])
);
if( $p) {
$signed_text = $r[0]['guid'] . ';Post;' . $p[0]['guid'] . ';true;' . $diaspora_handle;
if(isset($contact_uprvkey))
$authorsig = base64_encode(rsa_sign($signed_text,$contact_uprvkey,'sha256'));
else
$authorsig = '';
q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
intval($post_id),
dbesc($signed_text),
dbesc($authorsig),
dbesc($diaspora_handle)
);
}
}
}
return;
}

View file

@ -36,13 +36,14 @@ function help_content(&$a) {
$path .= argv($x); $path .= argv($x);
} }
$title = basename($path); $title = basename($path);
$filename = $path;
$text = load_doc_file('doc/' . $path . '.md'); $text = load_doc_file('doc/' . $path . '.md');
$a->page['title'] = t('Help:') . ' ' . str_replace('-', ' ', notags($title)); $a->page['title'] = t('Help:') . ' ' . str_replace('-', ' ', notags($title));
} }
$home = load_doc_file('doc/Home.md'); $home = load_doc_file('doc/Home.md');
if (!$text) { if (!$text) {
$text = $home; $text = $home;
$filename = "Home";
$a->page['title'] = t('Help'); $a->page['title'] = t('Help');
} else { } else {
$a->page['aside'] = Markdown($home); $a->page['aside'] = Markdown($home);
@ -57,7 +58,45 @@ function help_content(&$a) {
} }
$html = Markdown($text); $html = Markdown($text);
$html = "<style>.md_warning { padding: 1em; border: #ff0000 solid 2px; background-color: #f9a3a3; color: #ffffff;</style>".$html;
if ($filename !== "Home") {
// create TOC but not for home
$lines = explode("\n", $html);
$toc="<style>aside ul {padding-left: 1em;}</style><h2>TOC</h2><ul id='toc'>";
$lastlevel=1;
$idnum = array(0,0,0,0,0,0,0);
foreach($lines as &$line){
if (substr($line,0,2)=="<h") {
$level = substr($line,2,1);
if ($level!="r") {
$level = intval($level);
if ($level<$lastlevel) {
for($k=$level;$k<$lastlevel; $k++) $toc.="</ul>";
for($k=$level+1;$k<count($idnum);$k++) $idnum[$k]=0;
}
if ($level>$lastlevel) $toc.="<ul>";
$idnum[$level]++;
$id = implode("_", array_slice($idnum,1,$level));
$href = $a->get_baseurl()."/help/{$filename}#{$id}";
$toc .= "<li><a href='{$href}'>".strip_tags($line)."</a></li>";
$line = "<a name='{$id}'></a>".$line;
$lastlevel = $level;
}
}
}
for($k=1;$k<$lastlevel; $k++) $toc.="</ul>";
$html = implode("\n",$lines);
$a->page['aside'] = $toc.$a->page['aside'];
}
$html = "
<style>
.md_warning {
padding: 1em; border: #ff0000 solid 2px;
background-color: #f9a3a3; color: #ffffff;
}
</style>".$html;
return $html; return $html;
} }

View file

@ -3,254 +3,27 @@
require_once('include/security.php'); require_once('include/security.php');
require_once('include/bbcode.php'); require_once('include/bbcode.php');
require_once('include/items.php'); require_once('include/items.php');
require_once('include/like.php');
function like_content(&$a) { function like_content(&$a) {
if(! local_user() && ! remote_user()) { if(! local_user() && ! remote_user()) {
return; return false;
} }
$verb = notags(trim($_GET['verb'])); $verb = notags(trim($_GET['verb']));
if(! $verb) if(! $verb)
$verb = 'like'; $verb = 'like';
switch($verb) {
case 'like':
case 'unlike':
$activity = ACTIVITY_LIKE;
break;
case 'dislike':
case 'undislike':
$activity = ACTIVITY_DISLIKE;
break;
case 'attendyes':
case 'unattendyes':
$activity = ACTIVITY_ATTEND;
break;
case 'attendno':
case 'unattendno':
$activity = ACTIVITY_ATTENDNO;
break;
case 'attendmaybe':
case 'unattendmaybe':
$activity = ACTIVITY_ATTENDMAYBE;
break;
default:
return;
break;
}
$item_id = (($a->argc > 1) ? notags(trim($a->argv[1])) : 0); $item_id = (($a->argc > 1) ? notags(trim($a->argv[1])) : 0);
logger('like: verb ' . $verb . ' item ' . $item_id); $r = do_like($item_id, $verb);
if (!$r) return;
$r = q("SELECT * FROM `item` WHERE `id` = '%s' OR `uri` = '%s' LIMIT 1",
dbesc($item_id),
dbesc($item_id)
);
if(! $item_id || (! count($r))) {
logger('like: no item ' . $item_id);
return;
}
$item = $r[0];
$owner_uid = $item['uid'];
if(! can_write_wall($a,$owner_uid)) {
return;
}
$remote_owner = null;
if(! $item['wall']) {
// The top level post may have been written by somebody on another system
$r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
intval($item['contact-id']),
intval($item['uid'])
);
if(! count($r))
return;
if(! $r[0]['self'])
$remote_owner = $r[0];
}
// this represents the post owner on this system.
$r = q("SELECT `contact`.*, `user`.`nickname` FROM `contact` LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid`
WHERE `contact`.`self` = 1 AND `contact`.`uid` = %d LIMIT 1",
intval($owner_uid)
);
if(count($r))
$owner = $r[0];
if(! $owner) {
logger('like: no owner');
return;
}
if(! $remote_owner)
$remote_owner = $owner;
// This represents the person posting
if((local_user()) && (local_user() == $owner_uid)) {
$contact = $owner;
}
else {
$r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
intval($_SESSION['visitor_id']),
intval($owner_uid)
);
if(count($r))
$contact = $r[0];
}
if(! $contact) {
return;
}
// See if we've been passed a return path to redirect to // See if we've been passed a return path to redirect to
$return_path = ((x($_REQUEST,'return')) ? $_REQUEST['return'] : ''); $return_path = ((x($_REQUEST,'return')) ? $_REQUEST['return'] : '');
$verbs = " '".dbesc($activity)."' ";
// event participation are essentially radio toggles. If you make a subsequent choice,
// we need to eradicate your first choice.
if($activity === ACTIVITY_ATTEND || $activity === ACTIVITY_ATTENDNO || $activity === ACTIVITY_ATTENDMAYBE) {
$verbs = " '" . dbesc(ACTIVITY_ATTEND) . "','" . dbesc(ACTIVITY_ATTENDNO) . "','" . dbesc(ACTIVITY_ATTENDMAYBE) . "' ";
}
$r = q("SELECT `id`, `guid` FROM `item` WHERE `verb` IN ( $verbs ) AND `deleted` = 0
AND `contact-id` = %d AND `uid` = %d
AND (`parent` = '%s' OR `parent-uri` = '%s' OR `thr-parent` = '%s') LIMIT 1",
intval($contact['id']), intval($owner_uid),
dbesc($item_id), dbesc($item_id), dbesc($item['uri'])
);
if(count($r)) {
$like_item = $r[0];
// Already voted, undo it
$r = q("UPDATE `item` SET `deleted` = 1, `unseen` = 1, `changed` = '%s' WHERE `id` = %d",
dbesc(datetime_convert()),
intval($like_item['id'])
);
// Clean up the Diaspora signatures for this like
// Go ahead and do it even if Diaspora support is disabled. We still want to clean up
// if it had been enabled in the past
$r = q("DELETE FROM `sign` WHERE `iid` = %d",
intval($like_item['id'])
);
// Save the author information for the unlike in case we need to relay to Diaspora
store_diaspora_like_retract_sig($activity, $item, $like_item, $contact);
// proc_run('php',"include/notifier.php","like","$post_id"); // $post_id isn't defined here!
$like_item_id = $like_item['id'];
proc_run('php',"include/notifier.php","like","$like_item_id");
like_content_return($a->get_baseurl(), $return_path);
return; // NOTREACHED
}
$uri = item_new_uri($a->get_hostname(),$owner_uid);
$post_type = (($item['resource-id']) ? t('photo') : t('status'));
if($item['obj_type'] === ACTIVITY_OBJ_EVENT)
$post_type = t('event');
$objtype = (($item['resource-id']) ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE );
$link = xmlify('<link rel="alternate" type="text/html" href="' . $a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id'] . '" />' . "\n") ;
$body = $item['body'];
$obj = <<< EOT
<object>
<type>$objtype</type>
<local>1</local>
<id>{$item['uri']}</id>
<link>$link</link>
<title></title>
<content>$body</content>
</object>
EOT;
if($verb === 'like')
$bodyverb = t('%1$s likes %2$s\'s %3$s');
if($verb === 'dislike')
$bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s');
if($verb === 'attendyes')
$bodyverb = t('%1$s is attending %2$s\'s %3$s');
if($verb === 'attendno')
$bodyverb = t('%1$s is not attending %2$s\'s %3$s');
if($verb === 'attendmaybe')
$bodyverb = t('%1$s may attend %2$s\'s %3$s');
if(! isset($bodyverb))
return;
$arr = array();
$arr['uri'] = $uri;
$arr['uid'] = $owner_uid;
$arr['contact-id'] = $contact['id'];
$arr['type'] = 'activity';
$arr['wall'] = $item['wall'];
$arr['origin'] = 1;
$arr['gravity'] = GRAVITY_LIKE;
$arr['parent'] = $item['id'];
$arr['parent-uri'] = $item['uri'];
$arr['thr-parent'] = $item['uri'];
$arr['owner-name'] = $remote_owner['name'];
$arr['owner-link'] = $remote_owner['url'];
$arr['owner-avatar'] = $remote_owner['thumb'];
$arr['author-name'] = $contact['name'];
$arr['author-link'] = $contact['url'];
$arr['author-avatar'] = $contact['thumb'];
$ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]';
$alink = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]';
$plink = '[url=' . $a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id'] . ']' . $post_type . '[/url]';
$arr['body'] = sprintf( $bodyverb, $ulink, $alink, $plink );
$arr['verb'] = $activity;
$arr['object-type'] = $objtype;
$arr['object'] = $obj;
$arr['allow_cid'] = $item['allow_cid'];
$arr['allow_gid'] = $item['allow_gid'];
$arr['deny_cid'] = $item['deny_cid'];
$arr['deny_gid'] = $item['deny_gid'];
$arr['visible'] = 1;
$arr['unseen'] = 1;
$arr['last-child'] = 0;
$post_id = item_store($arr);
if(! $item['visible']) {
$r = q("UPDATE `item` SET `visible` = 1 WHERE `id` = %d AND `uid` = %d",
intval($item['id']),
intval($owner_uid)
);
}
// Save the author information for the like in case we need to relay to Diaspora
store_diaspora_like_sig($activity, $post_type, $contact, $post_id);
$arr['id'] = $post_id;
call_hooks('post_local_end', $arr);
proc_run('php',"include/notifier.php","like","$post_id");
like_content_return($a->get_baseurl(), $return_path); like_content_return($a->get_baseurl(), $return_path);
killme(); // NOTREACHED killme(); // NOTREACHED
// return; // NOTREACHED // return; // NOTREACHED
@ -273,123 +46,3 @@ function like_content_return($baseurl, $return_path) {
killme(); killme();
} }
function store_diaspora_like_retract_sig($activity, $item, $like_item, $contact) {
// Note that we can only create a signature for a user of the local server. We don't have
// a key for remote users. That is ok, because if a remote user is "unlike"ing a post, it
// means we are the relay, and for relayable_retractions, Diaspora
// only checks the parent_author_signature if it doesn't have to relay further
//
// If $item['resource-id'] exists, it means the item is a photo. Diaspora doesn't support
// likes on photos, so don't bother.
$enabled = intval(get_config('system','diaspora_enabled'));
if(! $enabled) {
logger('mod_like: diaspora support disabled, not storing like retraction signature', LOGGER_DEBUG);
return;
}
logger('mod_like: storing diaspora like retraction signature');
if(($activity === ACTIVITY_LIKE) && (! $item['resource-id'])) {
$signed_text = $like_item['guid'] . ';' . 'Like';
// Only works for NETWORK_DFRN
$contact_baseurl_start = strpos($contact['url'],'://') + 3;
$contact_baseurl_length = strpos($contact['url'],'/profile') - $contact_baseurl_start;
$contact_baseurl = substr($contact['url'], $contact_baseurl_start, $contact_baseurl_length);
$diaspora_handle = $contact['nick'] . '@' . $contact_baseurl;
// Get contact's private key if he's a user of the local Friendica server
$r = q("SELECT `contact`.`uid` FROM `contact` WHERE `url` = '%s' AND `self` = 1 LIMIT 1",
dbesc($contact['url'])
);
if( $r) {
$contact_uid = $r['uid'];
$r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1",
intval($contact_uid)
);
if( $r)
$authorsig = base64_encode(rsa_sign($signed_text,$r['prvkey'],'sha256'));
}
if(! isset($authorsig))
$authorsig = '';
q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
intval($like_item['id']),
dbesc($signed_text),
dbesc($authorsig),
dbesc($diaspora_handle)
);
}
return;
}
function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) {
// Note that we can only create a signature for a user of the local server. We don't have
// a key for remote users. That is ok, because if a remote user is "unlike"ing a post, it
// means we are the relay, and for relayable_retractions, Diaspora
// only checks the parent_author_signature if it doesn't have to relay further
$enabled = intval(get_config('system','diaspora_enabled'));
if(! $enabled) {
logger('mod_like: diaspora support disabled, not storing like signature', LOGGER_DEBUG);
return;
}
logger('mod_like: storing diaspora like signature');
if(($activity === ACTIVITY_LIKE) && ($post_type === t('status'))) {
// Only works for NETWORK_DFRN
$contact_baseurl_start = strpos($contact['url'],'://') + 3;
$contact_baseurl_length = strpos($contact['url'],'/profile') - $contact_baseurl_start;
$contact_baseurl = substr($contact['url'], $contact_baseurl_start, $contact_baseurl_length);
$diaspora_handle = $contact['nick'] . '@' . $contact_baseurl;
// Get contact's private key if he's a user of the local Friendica server
$r = q("SELECT `contact`.`uid` FROM `contact` WHERE `url` = '%s' AND `self` = 1 LIMIT 1",
dbesc($contact['url'])
);
if( $r) {
$contact_uid = $r['uid'];
$r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1",
intval($contact_uid)
);
if( $r)
$contact_uprvkey = $r['prvkey'];
}
$r = q("SELECT guid, parent FROM `item` WHERE id = %d LIMIT 1",
intval($post_id)
);
if( $r) {
$p = q("SELECT guid FROM `item` WHERE id = %d AND parent = %d LIMIT 1",
intval($r[0]['parent']),
intval($r[0]['parent'])
);
if( $p) {
$signed_text = $r[0]['guid'] . ';Post;' . $p[0]['guid'] . ';true;' . $diaspora_handle;
if(isset($contact_uprvkey))
$authorsig = base64_encode(rsa_sign($signed_text,$contact_uprvkey,'sha256'));
else
$authorsig = '';
q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
intval($post_id),
dbesc($signed_text),
dbesc($authorsig),
dbesc($diaspora_handle)
);
}
}
}
return;
}

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>
<links type="array">{{foreach $photo.link as $scale => $url}}
<link type="{{$photo.type}}" scale="{{$scale}}" href="{{$url}}" />
{{/foreach}}</links>
{{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> {{foreach $statuses as $status}} <status>
<text>{{$status.text}}</text> <text>{{$status.text}}</text>
<truncated>{{$status.truncated}}</truncated> <truncated>{{$status.truncated}}</truncated>
@ -17,5 +19,8 @@
<coordinates>{{$status.coordinates}}</coordinates> <coordinates>{{$status.coordinates}}</coordinates>
<place>{{$status.place}}</place> <place>{{$status.place}}</place>
<contributors>{{$status.contributors}}</contributors> <contributors>{{$status.contributors}}</contributors>
<friendica:activities>{{foreach $status.friendica_activities as $k=>$v}}
<friendica:{{$k}}>{{$v}}</friendica:{{$k}}>
{{/foreach}}</friendica:activities>
</status> </status>
{{/foreach}}</statuses> {{/foreach}}</statuses>