Friendica Communications Platform
(please note that this is a clone of the repository at github, issues are handled there)
https://friendi.ca
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5022 lines
154 KiB
5022 lines
154 KiB
<?php |
|
/** |
|
* @file include/api.php |
|
* Friendica implementation of statusnet/twitter API |
|
* |
|
* @todo Automatically detect if incoming data is HTML or BBCode |
|
*/ |
|
|
|
use Friendica\App; |
|
use Friendica\Core\Config; |
|
|
|
require_once 'include/HTTPExceptions.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 'include/like.php'; |
|
require_once 'include/NotificationsManager.php'; |
|
require_once 'include/plaintext.php'; |
|
require_once 'include/xml.php'; |
|
|
|
define('API_METHOD_ANY', '*'); |
|
define('API_METHOD_GET', 'GET'); |
|
define('API_METHOD_POST', 'POST,PUT'); |
|
define('API_METHOD_DELETE', 'POST,DELETE'); |
|
|
|
$API = array(); |
|
$called_api = null; |
|
|
|
/// @TODO Fix intending |
|
/** |
|
* @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() { |
|
if (x($_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'); |
|
} |
|
|
|
// Support for known clients that doesn't send a source name |
|
if (strpos($_SERVER['HTTP_USER_AGENT'], "Twidere") !== false) { |
|
return "Twidere"; |
|
} |
|
|
|
logger("Unrecognized user-agent ".$_SERVER['HTTP_USER_AGENT'], LOGGER_DEBUG); |
|
|
|
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" ); |
|
} |
|
|
|
/** |
|
* @brief Register API endpoint |
|
* |
|
* Register a function to be the endpont for defined API path. |
|
* |
|
* @param string $path API URL path, relative to App::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, |
|
'method' => $method, |
|
); |
|
|
|
// Workaround for hotot |
|
$path = str_replace("api/", "api/1.1/", $path); |
|
|
|
$API[$path] = array( |
|
'func' => $func, |
|
'auth' => $auth, |
|
'method' => $method, |
|
); |
|
} |
|
|
|
/** |
|
* @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(App $a) { |
|
// login with oauth |
|
try { |
|
$oauth = new FKOAuth1(); |
|
list($consumer,$token) = $oauth->verify_request(OAuthRequest::from_request()); |
|
if (!is_null($token)) { |
|
$oauth->loginUser($token->uid); |
|
call_hooks('logged_in', $a->user); |
|
return; |
|
} |
|
echo __FILE__.__LINE__.__FUNCTION__ . "<pre>"; |
|
var_dump($consumer, $token); |
|
die(); |
|
} catch (Exception $e) { |
|
logger($e); |
|
} |
|
|
|
// workaround for HTTP-auth in CGI mode |
|
if (x($_SERVER, 'REDIRECT_REMOTE_USER')) { |
|
$userpass = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"], 6)) ; |
|
if (strlen($userpass)) { |
|
list($name, $password) = explode(':', $userpass); |
|
$_SERVER['PHP_AUTH_USER'] = $name; |
|
$_SERVER['PHP_AUTH_PW'] = $password; |
|
} |
|
} |
|
|
|
if (!x($_SERVER, 'PHP_AUTH_USER')) { |
|
logger('API_login: ' . print_r($_SERVER,true), LOGGER_DEBUG); |
|
header('WWW-Authenticate: Basic realm="Friendica"'); |
|
throw new UnauthorizedException("This API requires login"); |
|
} |
|
|
|
$user = $_SERVER['PHP_AUTH_USER']; |
|
$password = $_SERVER['PHP_AUTH_PW']; |
|
$encrypted = hash('whirlpool', trim($password)); |
|
|
|
// allow "user@server" login (but ignore 'server' part) |
|
$at = strstr($user, "@", true); |
|
if ($at) { |
|
$user = $at; |
|
} |
|
|
|
// next code from mod/auth.php. needs better solution |
|
$record = null; |
|
|
|
$addon_auth = array( |
|
'username' => trim($user), |
|
'password' => trim($password), |
|
'authenticated' => 0, |
|
'user_record' => null, |
|
); |
|
|
|
/* |
|
* A plugin indicates successful login by setting 'authenticated' to non-zero value and returning a user record |
|
* Plugins should never set 'authenticated' except to indicate success - as hooks may be chained |
|
* and later plugins should not interfere with an earlier one that succeeded. |
|
*/ |
|
call_hooks('authenticate', $addon_auth); |
|
|
|
if (($addon_auth['authenticated']) && (count($addon_auth['user_record']))) { |
|
$record = $addon_auth['user_record']; |
|
} else { |
|
// process normal login request |
|
$r = q("SELECT * FROM `user` WHERE (`email` = '%s' OR `nickname` = '%s') |
|
AND `password` = '%s' AND NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `verified` LIMIT 1", |
|
dbesc(trim($user)), |
|
dbesc(trim($user)), |
|
dbesc($encrypted) |
|
); |
|
if (dbm::is_result($r)) { |
|
$record = $r[0]; |
|
} |
|
} |
|
|
|
if ((! $record) || (! count($record))) { |
|
logger('API_login failure: ' . print_r($_SERVER, true), LOGGER_DEBUG); |
|
header('WWW-Authenticate: Basic realm="Friendica"'); |
|
//header('HTTP/1.0 401 Unauthorized'); |
|
//die('This api requires login'); |
|
throw new UnauthorizedException("This API requires login"); |
|
} |
|
|
|
authenticate_success($record); |
|
|
|
$_SESSION["allow_api"] = true; |
|
|
|
call_hooks('logged_in', $a->user); |
|
|
|
} |
|
|
|
/** |
|
* @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(App $a) { |
|
global $API, $called_api; |
|
|
|
$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"; |
|
} |
|
|
|
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']); |
|
|
|
/// @TODO should be "true ==[=] $info['auth']", if you miss only one = character, you assign a variable (only with ==). Let's make all this even. |
|
if ($info['auth'] === true && api_user() === false) { |
|
api_login($a); |
|
} |
|
|
|
logger('API call for ' . $a->user['username'] . ': ' . $a->query_string); |
|
logger('API parameters: ' . print_r($_REQUEST, true)); |
|
|
|
$stamp = microtime(true); |
|
$r = call_user_func($info['func'], $type); |
|
$duration = (float) (microtime(true) - $stamp); |
|
logger("API call duration: " . round($duration, 2) . "\t" . $a->query_string, LOGGER_DEBUG); |
|
|
|
if (get_config("system", "profiler")) { |
|
$duration = microtime(true)-$a->performance["start"]; |
|
|
|
/// @TODO round() really everywhere? |
|
logger(parse_url($a->query_string, PHP_URL_PATH) . ": " . sprintf("Database: %s/%s, Network: %s, I/O: %s, Other: %s, Total: %s", |
|
round($a->performance["database"] - $a->performance["database_write"], 3), |
|
round($a->performance["database_write"], 3), |
|
round($a->performance["network"], 2), |
|
round($a->performance["file"], 2), |
|
round($duration - ($a->performance["database"] + $a->performance["network"] |
|
+ $a->performance["file"]), 2), |
|
round($duration, 2)), |
|
LOGGER_DEBUG |
|
); |
|
|
|
if (get_config("rendertime", "callstack")) { |
|
$o = "Database Read:\n"; |
|
foreach ($a->callstack["database"] AS $func => $time) { |
|
$time = round($time, 3); |
|
if ($time > 0) { |
|
$o .= $func . ": " . $time . "\n"; |
|
} |
|
} |
|
$o .= "\nDatabase Write:\n"; |
|
foreach ($a->callstack["database_write"] AS $func => $time) { |
|
$time = round($time, 3); |
|
if ($time > 0) { |
|
$o .= $func . ": " . $time . "\n"; |
|
} |
|
} |
|
|
|
$o .= "\nNetwork:\n"; |
|
foreach ($a->callstack["network"] AS $func => $time) { |
|
$time = round($time, 3); |
|
if ($time > 0) { |
|
$o .= $func . ": " . $time . "\n"; |
|
} |
|
} |
|
logger($o, LOGGER_DEBUG); |
|
} |
|
} |
|
|
|
if (false === $r) { |
|
/* |
|
* api function returned false withour throw an |
|
* exception. This should not happend, throw a 500 |
|
*/ |
|
throw new InternalServerErrorException(); |
|
} |
|
|
|
switch ($type) { |
|
case "xml": |
|
header ("Content-Type: text/xml"); |
|
return $r; |
|
break; |
|
case "json": |
|
header ("Content-Type: application/json"); |
|
foreach ($r as $rr) |
|
$json = json_encode($rr); |
|
if (x($_GET, 'callback')) { |
|
$json = $_GET['callback'] . "(" . $json . ")"; |
|
} |
|
return $json; |
|
break; |
|
case "rss": |
|
header ("Content-Type: application/rss+xml"); |
|
return '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $r; |
|
break; |
|
case "atom": |
|
header ("Content-Type: application/atom+xml"); |
|
return '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $r; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
logger('API call not implemented: ' . $a->query_string); |
|
throw new NotImplementedException(); |
|
} catch (HTTPException $e) { |
|
header("HTTP/1.1 {$e->httpcode} {$e->httpdesc}"); |
|
return api_error($type, $e); |
|
} |
|
} |
|
|
|
/** |
|
* @brief Format API error string |
|
* |
|
* @param string $type Return type (xml, json, rss, as) |
|
* @param HTTPException $error Error object |
|
* @return strin error message formatted as $type |
|
*/ |
|
function api_error($type, $e) { |
|
|
|
$a = get_app(); |
|
|
|
$error = ($e->getMessage() !== "" ? $e->getMessage() : $e->httpdesc); |
|
/// @TODO: https://dev.twitter.com/overview/api/response-codes |
|
|
|
$error = array("error" => $error, |
|
"code" => $e->httpcode . " " . $e->httpdesc, |
|
"request" => $a->query_string); |
|
|
|
$ret = api_format_data('status', $type, array('status' => $error)); |
|
|
|
switch ($type) { |
|
case "xml": |
|
header ("Content-Type: text/xml"); |
|
return $ret; |
|
break; |
|
case "json": |
|
header ("Content-Type: application/json"); |
|
return json_encode($ret); |
|
break; |
|
case "rss": |
|
header ("Content-Type: application/rss+xml"); |
|
return $ret; |
|
break; |
|
case "atom": |
|
header ("Content-Type: application/atom+xml"); |
|
return $ret; |
|
break; |
|
} |
|
} |
|
|
|
/** |
|
* @brief Set values for RSS template |
|
* |
|
* @param App $a |
|
* @param array $arr Array to be passed to template |
|
* @param array $user_info |
|
* @return array |
|
* @todo find proper type-hints |
|
*/ |
|
function api_rss_extra(App $a, $arr, $user_info) { |
|
if (is_null($user_info)) { |
|
$user_info = api_get_user($a); |
|
} |
|
|
|
$arr['$user'] = $user_info; |
|
$arr['$rss'] = array( |
|
'alternate' => $user_info['url'], |
|
'self' => App::get_baseurl() . "/" . $a->query_string, |
|
'base' => App::get_baseurl(), |
|
'updated' => api_date(null), |
|
'atom_updated' => datetime_convert('UTC', 'UTC', 'now', ATOM_TIME), |
|
'language' => $user_info['language'], |
|
'logo' => App::get_baseurl() . "/images/friendica-32.png", |
|
); |
|
|
|
return $arr; |
|
} |
|
|
|
|
|
/** |
|
* @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 = dba::select('contact', array('url'), array('uid' => 0, 'id' => $id), array('limit' => 1)); |
|
|
|
if (dbm::is_result($r)) { |
|
return $r["url"]; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* @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(App $a, $contact_id = null, $type = "json") { |
|
global $called_api; |
|
|
|
$user = null; |
|
$extra_query = ""; |
|
$url = ""; |
|
$nick = ""; |
|
|
|
logger("api_get_user: Fetching user data for user ".$contact_id, LOGGER_DEBUG); |
|
|
|
// Searching for contact URL |
|
if (!is_null($contact_id) AND (intval($contact_id) == 0)) { |
|
$user = dbesc(normalise_link($contact_id)); |
|
$url = $user; |
|
$extra_query = "AND `contact`.`nurl` = '%s' "; |
|
if (api_user() !== false) { |
|
$extra_query .= "AND `contact`.`uid`=" . intval(api_user()); |
|
} |
|
} |
|
|
|
// Searching for contact id with uid = 0 |
|
if (!is_null($contact_id) AND (intval($contact_id) != 0)) { |
|
$user = dbesc(api_unique_id_to_url($contact_id)); |
|
|
|
if ($user == "") { |
|
throw new BadRequestException("User not found."); |
|
} |
|
|
|
$url = $user; |
|
$extra_query = "AND `contact`.`nurl` = '%s' "; |
|
if (api_user() !== false) { |
|
$extra_query .= "AND `contact`.`uid`=" . intval(api_user()); |
|
} |
|
} |
|
|
|
if (is_null($user) && x($_GET, 'user_id')) { |
|
$user = dbesc(api_unique_id_to_url($_GET['user_id'])); |
|
|
|
if ($user == "") { |
|
throw new BadRequestException("User not found."); |
|
} |
|
|
|
$url = $user; |
|
$extra_query = "AND `contact`.`nurl` = '%s' "; |
|
if (api_user() !== false) { |
|
$extra_query .= "AND `contact`.`uid`=" . intval(api_user()); |
|
} |
|
} |
|
if (is_null($user) && x($_GET, 'screen_name')) { |
|
$user = dbesc($_GET['screen_name']); |
|
$nick = $user; |
|
$extra_query = "AND `contact`.`nick` = '%s' "; |
|
if (api_user() !== false) { |
|
$extra_query .= "AND `contact`.`uid`=".intval(api_user()); |
|
} |
|
} |
|
|
|
if (is_null($user) && x($_GET, 'profileurl')) { |
|
$user = dbesc(normalise_link($_GET['profileurl'])); |
|
$nick = $user; |
|
$extra_query = "AND `contact`.`nurl` = '%s' "; |
|
if (api_user() !== false) { |
|
$extra_query .= "AND `contact`.`uid`=".intval(api_user()); |
|
} |
|
} |
|
|
|
if (is_null($user) AND ($a->argc > (count($called_api) - 1)) AND (count($called_api) > 0)) { |
|
$argid = count($called_api); |
|
list($user, $null) = explode(".", $a->argv[$argid]); |
|
if (is_numeric($user)) { |
|
$user = dbesc(api_unique_id_to_url($user)); |
|
|
|
if ($user == "") { |
|
return false; |
|
} |
|
|
|
$url = $user; |
|
$extra_query = "AND `contact`.`nurl` = '%s' "; |
|
if (api_user() !== false) { |
|
$extra_query .= "AND `contact`.`uid`=" . intval(api_user()); |
|
} |
|
} else { |
|
$user = dbesc($user); |
|
$nick = $user; |
|
$extra_query = "AND `contact`.`nick` = '%s' "; |
|
if (api_user() !== false) { |
|
$extra_query .= "AND `contact`.`uid`=" . intval(api_user()); |
|
} |
|
} |
|
} |
|
|
|
logger("api_get_user: user ".$user, LOGGER_DEBUG); |
|
|
|
if (!$user) { |
|
if (api_user() === false) { |
|
api_login($a); |
|
return false; |
|
} else { |
|
$user = $_SESSION['uid']; |
|
$extra_query = "AND `contact`.`uid` = %d AND `contact`.`self` "; |
|
} |
|
|
|
} |
|
|
|
logger('api_user: ' . $extra_query . ', user: ' . $user); |
|
|
|
// user info |
|
$uinfo = q("SELECT *, `contact`.`id` AS `cid` FROM `contact` |
|
WHERE 1 |
|
$extra_query", |
|
$user |
|
); |
|
|
|
// Selecting the id by priority, friendica first |
|
api_best_nickname($uinfo); |
|
|
|
// if the contact wasn't found, fetch it from the contacts with uid = 0 |
|
if (!dbm::is_result($uinfo)) { |
|
$r = array(); |
|
|
|
if ($url != "") { |
|
$r = q("SELECT * FROM `contact` WHERE `uid` = 0 AND `nurl` = '%s' LIMIT 1", dbesc(normalise_link($url))); |
|
} |
|
|
|
if (dbm::is_result($r)) { |
|
$network_name = network_to_name($r[0]['network'], $r[0]['url']); |
|
|
|
// If no nick where given, extract it from the address |
|
if (($r[0]['nick'] == "") OR ($r[0]['name'] == $r[0]['nick'])) { |
|
$r[0]['nick'] = api_get_nick($r[0]["url"]); |
|
} |
|
|
|
$ret = array( |
|
'id' => $r[0]["id"], |
|
'id_str' => (string) $r[0]["id"], |
|
'name' => $r[0]["name"], |
|
'screen_name' => (($r[0]['nick']) ? $r[0]['nick'] : $r[0]['name']), |
|
'location' => ($r[0]["location"] != "") ? $r[0]["location"] : $network_name, |
|
'description' => $r[0]["about"], |
|
'profile_image_url' => $r[0]["micro"], |
|
'profile_image_url_https' => $r[0]["micro"], |
|
'url' => $r[0]["url"], |
|
'protected' => false, |
|
'followers_count' => 0, |
|
'friends_count' => 0, |
|
'listed_count' => 0, |
|
'created_at' => api_date($r[0]["created"]), |
|
'favourites_count' => 0, |
|
'utc_offset' => 0, |
|
'time_zone' => 'UTC', |
|
'geo_enabled' => false, |
|
'verified' => false, |
|
'statuses_count' => 0, |
|
'lang' => '', |
|
'contributors_enabled' => false, |
|
'is_translator' => false, |
|
'is_translation_enabled' => false, |
|
'following' => false, |
|
'follow_request_sent' => false, |
|
'statusnet_blocking' => false, |
|
'notifications' => false, |
|
'statusnet_profile_url' => $r[0]["url"], |
|
'uid' => 0, |
|
'cid' => get_contact($r[0]["url"], api_user(), true), |
|
'self' => 0, |
|
'network' => $r[0]["network"], |
|
); |
|
|
|
return $ret; |
|
} else { |
|
throw new BadRequestException("User not found."); |
|
} |
|
} |
|
|
|
if ($uinfo[0]['self']) { |
|
|
|
if ($uinfo[0]['network'] == "") { |
|
$uinfo[0]['network'] = NETWORK_DFRN; |
|
} |
|
|
|
$usr = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", |
|
intval(api_user()) |
|
); |
|
$profile = q("SELECT * FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1", |
|
intval(api_user()) |
|
); |
|
|
|
/// @TODO old-lost code? (twice) |
|
// Counting is deactivated by now, due to performance issues |
|
// count public wall messages |
|
//$r = q("SELECT COUNT(*) as `count` FROM `item` WHERE `uid` = %d AND `wall`", |
|
// intval($uinfo[0]['uid']) |
|
//); |
|
//$countitms = $r[0]['count']; |
|
$countitms = 0; |
|
} else { |
|
// Counting is deactivated by now, due to performance issues |
|
//$r = q("SELECT count(*) as `count` FROM `item` |
|
// WHERE `contact-id` = %d", |
|
// intval($uinfo[0]['id']) |
|
//); |
|
//$countitms = $r[0]['count']; |
|
$countitms = 0; |
|
} |
|
|
|
/// @TODO old-lost code? (twice) |
|
/* |
|
// Counting is deactivated by now, due to performance issues |
|
// count friends |
|
$r = q("SELECT count(*) as `count` FROM `contact` |
|
WHERE `uid` = %d AND `rel` IN ( %d, %d ) |
|
AND `self`=0 AND NOT `blocked` AND NOT `pending` AND `hidden`=0", |
|
intval($uinfo[0]['uid']), |
|
intval(CONTACT_IS_SHARING), |
|
intval(CONTACT_IS_FRIEND) |
|
); |
|
$countfriends = $r[0]['count']; |
|
|
|
$r = q("SELECT count(*) as `count` FROM `contact` |
|
WHERE `uid` = %d AND `rel` IN ( %d, %d ) |
|
AND `self`=0 AND NOT `blocked` AND NOT `pending` AND `hidden`=0", |
|
intval($uinfo[0]['uid']), |
|
intval(CONTACT_IS_FOLLOWER), |
|
intval(CONTACT_IS_FRIEND) |
|
); |
|
$countfollowers = $r[0]['count']; |
|
|
|
$r = q("SELECT count(*) as `count` FROM item where starred = 1 and uid = %d and deleted = 0", |
|
intval($uinfo[0]['uid']) |
|
); |
|
$starred = $r[0]['count']; |
|
|
|
|
|
if (! $uinfo[0]['self']) { |
|
$countfriends = 0; |
|
$countfollowers = 0; |
|
$starred = 0; |
|
} |
|
*/ |
|
$countfriends = 0; |
|
$countfollowers = 0; |
|
$starred = 0; |
|
|
|
// Add a nick if it isn't present there |
|
if (($uinfo[0]['nick'] == "") OR ($uinfo[0]['name'] == $uinfo[0]['nick'])) { |
|
$uinfo[0]['nick'] = api_get_nick($uinfo[0]["url"]); |
|
} |
|
|
|
$network_name = network_to_name($uinfo[0]['network'], $uinfo[0]['url']); |
|
|
|
$pcontact_id = get_contact($uinfo[0]['url'], 0, true); |
|
|
|
$ret = array( |
|
'id' => intval($pcontact_id), |
|
'id_str' => (string) intval($pcontact_id), |
|
'name' => (($uinfo[0]['name']) ? $uinfo[0]['name'] : $uinfo[0]['nick']), |
|
'screen_name' => (($uinfo[0]['nick']) ? $uinfo[0]['nick'] : $uinfo[0]['name']), |
|
'location' => ($usr) ? $usr[0]['default-location'] : $network_name, |
|
'description' => (($profile) ? $profile[0]['pdesc'] : NULL), |
|
'profile_image_url' => $uinfo[0]['micro'], |
|
'profile_image_url_https' => $uinfo[0]['micro'], |
|
'url' => $uinfo[0]['url'], |
|
'protected' => false, |
|
'followers_count' => intval($countfollowers), |
|
'friends_count' => intval($countfriends), |
|
'listed_count' => 0, |
|
'created_at' => api_date($uinfo[0]['created']), |
|
'favourites_count' => intval($starred), |
|
'utc_offset' => "0", |
|
'time_zone' => 'UTC', |
|
'geo_enabled' => false, |
|
'verified' => true, |
|
'statuses_count' => intval($countitms), |
|
'lang' => '', |
|
'contributors_enabled' => false, |
|
'is_translator' => false, |
|
'is_translation_enabled' => false, |
|
'following' => (($uinfo[0]['rel'] == CONTACT_IS_FOLLOWER) OR ($uinfo[0]['rel'] == CONTACT_IS_FRIEND)), |
|
'follow_request_sent' => false, |
|
'statusnet_blocking' => false, |
|
'notifications' => false, |
|
/// @TODO old way? |
|
//'statusnet_profile_url' => App::get_baseurl()."/contacts/".$uinfo[0]['cid'], |
|
'statusnet_profile_url' => $uinfo[0]['url'], |
|
'uid' => intval($uinfo[0]['uid']), |
|
'cid' => intval($uinfo[0]['cid']), |
|
'self' => $uinfo[0]['self'], |
|
'network' => $uinfo[0]['network'], |
|
); |
|
|
|
return $ret; |
|
|
|
} |
|
|
|
/** |
|
* @brief return api-formatted array for item's author and owner |
|
* |
|
* @param App $a |
|
* @param array $item : item from db |
|
* @return array(array:author, array:owner) |
|
*/ |
|
function api_item_get_user(App $a, $item) { |
|
|
|
$status_user = api_get_user($a, $item["author-link"]); |
|
|
|
$status_user["protected"] = (($item["allow_cid"] != "") OR |
|
($item["allow_gid"] != "") OR |
|
($item["deny_cid"] != "") OR |
|
($item["deny_gid"] != "") OR |
|
$item["private"]); |
|
|
|
if ($item['thr-parent'] == $item['uri']) { |
|
$owner_user = api_get_user($a, $item["owner-link"]); |
|
} else { |
|
$owner_user = $status_user; |
|
} |
|
|
|
return (array($status_user, $owner_user)); |
|
} |
|
|
|
/** |
|
* @brief walks recursively through an array with the possibility to change value and key |
|
* |
|
* @param array $array The array to walk through |
|
* @param string $callback The callback function |
|
* |
|
* @return array the transformed array |
|
*/ |
|
function api_walk_recursive(array &$array, callable $callback) { |
|
|
|
$new_array = array(); |
|
|
|
foreach ($array as $k => $v) { |
|
if (is_array($v)) { |
|
if ($callback($v, $k)) { |
|
$new_array[$k] = api_walk_recursive($v, $callback); |
|
} |
|
} else { |
|
if ($callback($v, $k)) { |
|
$new_array[$k] = $v; |
|
} |
|
} |
|
} |
|
$array = $new_array; |
|
|
|
return $array; |
|
} |
|
|
|
/** |
|
* @brief Callback function to transform the array in an array that can be transformed in a XML file |
|
* |
|
* @param variant $item Array item value |
|
* @param string $key Array key |
|
* |
|
* @return boolean Should the array item be deleted? |
|
*/ |
|
function api_reformat_xml(&$item, &$key) { |
|
if (is_bool($item)) { |
|
$item = ($item ? "true" : "false"); |
|
} |
|
|
|
if (substr($key, 0, 10) == "statusnet_") { |
|
$key = "statusnet:".substr($key, 10); |
|
} elseif (substr($key, 0, 10) == "friendica_") { |
|
$key = "friendica:".substr($key, 10); |
|
} |
|
/// @TODO old-lost code? |
|
//else |
|
// $key = "default:".$key; |
|
|
|
return true; |
|
} |
|
|
|
/** |
|
* @brief Creates the XML from a JSON style array |
|
* |
|
* @param array $data JSON style array |
|
* @param string $root_element Name of the root element |
|
* |
|
* @return string The XML data |
|
*/ |
|
function api_create_xml($data, $root_element) { |
|
$childname = key($data); |
|
$data2 = array_pop($data); |
|
$key = key($data2); |
|
|
|
$namespaces = array("" => "http://api.twitter.com", |
|
"statusnet" => "http://status.net/schema/api/1/", |
|
"friendica" => "http://friendi.ca/schema/api/1/", |
|
"georss" => "http://www.georss.org/georss"); |
|
|
|
/// @todo Auto detection of needed namespaces |
|
if (in_array($root_element, array("ok", "hash", "config", "version", "ids", "notes", "photos"))) { |
|
$namespaces = array(); |
|
} |
|
|
|
if (is_array($data2)) { |
|
api_walk_recursive($data2, "api_reformat_xml"); |
|
} |
|
|
|
if ($key == "0") { |
|
$data4 = array(); |
|
$i = 1; |
|
|
|
foreach ($data2 AS $item) { |
|
$data4[$i++.":".$childname] = $item; |
|
} |
|
|
|
$data2 = $data4; |
|
} |
|
|
|
$data3 = array($root_element => $data2); |
|
|
|
$ret = xml::from_array($data3, $xml, false, $namespaces); |
|
return $ret; |
|
} |
|
|
|
/** |
|
* @brief Formats the data according to the data type |
|
* |
|
* @param string $root_element Name of the root element |
|
* @param string $type Return type (atom, rss, xml, json) |
|
* @param array $data JSON style array |
|
* |
|
* @return (string|object) XML data or JSON data |
|
*/ |
|
function api_format_data($root_element, $type, $data) { |
|
|
|
$a = get_app(); |
|
|
|
switch ($type) { |
|
case "atom": |
|
case "rss": |
|
case "xml": |
|
$ret = api_create_xml($data, $root_element); |
|
break; |
|
case "json": |
|
$ret = $data; |
|
break; |
|
} |
|
|
|
return $ret; |
|
} |
|
|
|
/** |
|
** TWITTER API |
|
*/ |
|
|
|
/** |
|
* Returns an HTTP 200 OK response code and a representation of the requesting user if authentication was successful; |
|
* returns a 401 status code and an error message if not. |
|
* http://developer.twitter.com/doc/get/account/verify_credentials |
|
*/ |
|
function api_account_verify_credentials($type) { |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
throw new ForbiddenException(); |
|
} |
|
|
|
unset($_REQUEST["user_id"]); |
|
unset($_GET["user_id"]); |
|
|
|
unset($_REQUEST["screen_name"]); |
|
unset($_GET["screen_name"]); |
|
|
|
$skip_status = (x($_REQUEST, 'skip_status')?$_REQUEST['skip_status'] : false); |
|
|
|
$user_info = api_get_user($a); |
|
|
|
// "verified" isn't used here in the standard |
|
unset($user_info["verified"]); |
|
|
|
// - Adding last status |
|
if (!$skip_status) { |
|
$user_info["status"] = api_status_show("raw"); |
|
if (!count($user_info["status"])) { |
|
unset($user_info["status"]); |
|
} else { |
|
unset($user_info["status"]["user"]); |
|
} |
|
} |
|
|
|
// "uid" and "self" are only needed for some internal stuff, so remove it from here |
|
unset($user_info["uid"]); |
|
unset($user_info["self"]); |
|
|
|
return api_format_data("user", $type, array('user' => $user_info)); |
|
|
|
} |
|
|
|
/// @TODO move to top of file or somwhere better |
|
api_register_func('api/account/verify_credentials','api_account_verify_credentials', true); |
|
|
|
/** |
|
* get data from $_POST or $_GET |
|
*/ |
|
function requestdata($k) { |
|
if (x($_POST, $k)) { |
|
return $_POST[$k]; |
|
} |
|
if (x($_GET, $k)) { |
|
return $_GET[$k]; |
|
} |
|
return null; |
|
} |
|
|
|
/*Waitman Gobble Mod*/ |
|
function api_statuses_mediap($type) { |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
logger('api_statuses_update: no user'); |
|
throw new ForbiddenException(); |
|
} |
|
$user_info = api_get_user($a); |
|
|
|
$_REQUEST['type'] = 'wall'; |
|
$_REQUEST['profile_uid'] = api_user(); |
|
$_REQUEST['api_source'] = true; |
|
$txt = requestdata('status'); |
|
/// @TODO old-lost code? |
|
//$txt = urldecode(requestdata('status')); |
|
|
|
if ((strpos($txt,'<') !== false) || (strpos($txt,'>') !== false)) { |
|
|
|
$txt = html2bb_video($txt); |
|
$config = HTMLPurifier_Config::createDefault(); |
|
$config->set('Cache.DefinitionImpl', null); |
|
$purifier = new HTMLPurifier($config); |
|
$txt = $purifier->purify($txt); |
|
} |
|
$txt = html2bbcode($txt); |
|
|
|
$a->argv[1]=$user_info['screen_name']; //should be set to username? |
|
|
|
// tell wall_upload function to return img info instead of echo |
|
$_REQUEST['hush'] = 'yeah'; |
|
$bebop = wall_upload_post($a); |
|
|
|
// now that we have the img url in bbcode we can add it to the status and insert the wall item. |
|
$_REQUEST['body'] = $txt . "\n\n" . $bebop; |
|
item_post($a); |
|
|
|
// this should output the last post (the one we just posted). |
|
return api_status_show($type); |
|
} |
|
|
|
/// @TODO move this to top of file or somewhere better! |
|
api_register_func('api/statuses/mediap','api_statuses_mediap', true, API_METHOD_POST); |
|
|
|
function api_statuses_update($type) { |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
logger('api_statuses_update: no user'); |
|
throw new ForbiddenException(); |
|
} |
|
|
|
$user_info = api_get_user($a); |
|
|
|
// convert $_POST array items to the form we use for web posts. |
|
|
|
// logger('api_post: ' . print_r($_POST,true)); |
|
|
|
if (requestdata('htmlstatus')) { |
|
$txt = requestdata('htmlstatus'); |
|
if ((strpos($txt, '<') !== false) || (strpos($txt, '>') !== false)) { |
|
$txt = html2bb_video($txt); |
|
|
|
$config = HTMLPurifier_Config::createDefault(); |
|
$config->set('Cache.DefinitionImpl', null); |
|
|
|
$purifier = new HTMLPurifier($config); |
|
$txt = $purifier->purify($txt); |
|
|
|
$_REQUEST['body'] = html2bbcode($txt); |
|
} |
|
|
|
} else { |
|
$_REQUEST['body'] = requestdata('status'); |
|
} |
|
|
|
$_REQUEST['title'] = requestdata('title'); |
|
|
|
$parent = requestdata('in_reply_to_status_id'); |
|
|
|
// Twidere sends "-1" if it is no reply ... |
|
if ($parent == -1) { |
|
$parent = ""; |
|
} |
|
|
|
if (ctype_digit($parent)) { |
|
$_REQUEST['parent'] = $parent; |
|
} else { |
|
$_REQUEST['parent_uri'] = $parent; |
|
} |
|
|
|
if (requestdata('lat') && requestdata('long')) { |
|
$_REQUEST['coord'] = sprintf("%s %s", requestdata('lat'), requestdata('long')); |
|
} |
|
$_REQUEST['profile_uid'] = api_user(); |
|
|
|
if ($parent) { |
|
$_REQUEST['type'] = 'net-comment'; |
|
} else { |
|
// Check for throttling (maximum posts per day, week and month) |
|
$throttle_day = get_config('system','throttle_limit_day'); |
|
if ($throttle_day > 0) { |
|
$datefrom = date("Y-m-d H:i:s", time() - 24*60*60); |
|
|
|
$r = q("SELECT COUNT(*) AS `posts_day` FROM `item` WHERE `uid`=%d AND `wall` |
|
AND `created` > '%s' AND `id` = `parent`", |
|
intval(api_user()), dbesc($datefrom)); |
|
|
|
if (dbm::is_result($r)) { |
|
$posts_day = $r[0]["posts_day"]; |
|
} else { |
|
$posts_day = 0; |
|
} |
|
|
|
if ($posts_day > $throttle_day) { |
|
logger('Daily posting limit reached for user '.api_user(), LOGGER_DEBUG); |
|
// die(api_error($type, sprintf(t("Daily posting limit of %d posts reached. The post was rejected."), $throttle_day))); |
|
throw new TooManyRequestsException(sprintf(t("Daily posting limit of %d posts reached. The post was rejected."), $throttle_day)); |
|
} |
|
} |
|
|
|
$throttle_week = get_config('system','throttle_limit_week'); |
|
if ($throttle_week > 0) { |
|
$datefrom = date("Y-m-d H:i:s", time() - 24*60*60*7); |
|
|
|
$r = q("SELECT COUNT(*) AS `posts_week` FROM `item` WHERE `uid`=%d AND `wall` |
|
AND `created` > '%s' AND `id` = `parent`", |
|
intval(api_user()), dbesc($datefrom)); |
|
|
|
if (dbm::is_result($r)) { |
|
$posts_week = $r[0]["posts_week"]; |
|
} else { |
|
$posts_week = 0; |
|
} |
|
|
|
if ($posts_week > $throttle_week) { |
|
logger('Weekly posting limit reached for user '.api_user(), LOGGER_DEBUG); |
|
// die(api_error($type, sprintf(t("Weekly posting limit of %d posts reached. The post was rejected."), $throttle_week))); |
|
throw new TooManyRequestsException(sprintf(t("Weekly posting limit of %d posts reached. The post was rejected."), $throttle_week)); |
|
} |
|
} |
|
|
|
$throttle_month = get_config('system','throttle_limit_month'); |
|
if ($throttle_month > 0) { |
|
$datefrom = date("Y-m-d H:i:s", time() - 24*60*60*30); |
|
|
|
$r = q("SELECT COUNT(*) AS `posts_month` FROM `item` WHERE `uid`=%d AND `wall` |
|
AND `created` > '%s' AND `id` = `parent`", |
|
intval(api_user()), dbesc($datefrom)); |
|
|
|
if (dbm::is_result($r)) { |
|
$posts_month = $r[0]["posts_month"]; |
|
} else { |
|
$posts_month = 0; |
|
} |
|
|
|
if ($posts_month > $throttle_month) { |
|
logger('Monthly posting limit reached for user '.api_user(), LOGGER_DEBUG); |
|
// die(api_error($type, sprintf(t("Monthly posting limit of %d posts reached. The post was rejected."), $throttle_month))); |
|
throw new TooManyRequestsException(sprintf(t("Monthly posting limit of %d posts reached. The post was rejected."), $throttle_month)); |
|
} |
|
} |
|
|
|
$_REQUEST['type'] = 'wall'; |
|
} |
|
|
|
if (x($_FILES, 'media')) { |
|
// upload the image if we have one |
|
$_REQUEST['hush'] = 'yeah'; //tell wall_upload function to return img info instead of echo |
|
$media = wall_upload_post($a); |
|
if (strlen($media) > 0) { |
|
$_REQUEST['body'] .= "\n\n" . $media; |
|
} |
|
} |
|
|
|
// 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()); |
|
if (dbm::is_result($r)) { |
|
$phototypes = Photo::supportedTypes(); |
|
$ext = $phototypes[$r[0]['type']]; |
|
$_REQUEST['body'] .= "\n\n" . '[url=' . App::get_baseurl() . '/photos/' . $r[0]['nickname'] . '/image/' . $r[0]['resource-id'] . ']'; |
|
$_REQUEST['body'] .= '[img]' . App::get_baseurl() . '/photo/' . $r[0]['resource-id'] . '-' . $r[0]['scale'] . '.' . $ext . '[/img][/url]'; |
|
} |
|
} |
|
|
|
// set this so that the item_post() function is quiet and doesn't redirect or emit json |
|
|
|
$_REQUEST['api_source'] = true; |
|
|
|
if (!x($_REQUEST, "source")) { |
|
$_REQUEST["source"] = api_source(); |
|
} |
|
|
|
// call out normal post function |
|
item_post($a); |
|
|
|
// this should output the last post (the one we just posted). |
|
return api_status_show($type); |
|
} |
|
|
|
/// @TODO move to top of file or somwhere better |
|
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($type) { |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
logger('no user'); |
|
throw new ForbiddenException(); |
|
} |
|
|
|
$user_info = api_get_user($a); |
|
|
|
if (!x($_FILES, 'media')) { |
|
// Output error |
|
throw new BadRequestException("No media."); |
|
} |
|
|
|
$media = wall_upload_post($a, false); |
|
if (!$media) { |
|
// Output error |
|
throw new InternalServerErrorException(); |
|
} |
|
|
|
$returndata = array(); |
|
$returndata["media_id"] = $media["id"]; |
|
$returndata["media_id_string"] = (string)$media["id"]; |
|
$returndata["size"] = $media["size"]; |
|
$returndata["image"] = array("w" => $media["width"], |
|
"h" => $media["height"], |
|
"image_type" => $media["type"]); |
|
|
|
logger("Media uploaded: " . print_r($returndata, true), LOGGER_DEBUG); |
|
|
|
return array("media" => $returndata); |
|
} |
|
|
|
/// @TODO move to top of file or somwhere better |
|
api_register_func('api/media/upload','api_media_upload', true, API_METHOD_POST); |
|
|
|
function api_status_show($type) { |
|
|
|
$a = get_app(); |
|
|
|
$user_info = api_get_user($a); |
|
|
|
logger('api_status_show: user_info: '.print_r($user_info, true), LOGGER_DEBUG); |
|
|
|
if ($type == "raw") { |
|
$privacy_sql = "AND `item`.`allow_cid`='' AND `item`.`allow_gid`='' AND `item`.`deny_cid`='' AND `item`.`deny_gid`=''"; |
|
} else { |
|
$privacy_sql = ""; |
|
} |
|
|
|
// get last public wall message |
|
$lastwall = q("SELECT `item`.* |
|
FROM `item` |
|
WHERE `item`.`contact-id` = %d AND `item`.`uid` = %d |
|
AND ((`item`.`author-link` IN ('%s', '%s')) OR (`item`.`owner-link` IN ('%s', '%s'))) |
|
AND `item`.`type` != 'activity' $privacy_sql |
|
ORDER BY `item`.`id` DESC |
|
LIMIT 1", |
|
intval($user_info['cid']), |
|
intval(api_user()), |
|
dbesc($user_info['url']), |
|
dbesc(normalise_link($user_info['url'])), |
|
dbesc($user_info['url']), |
|
dbesc(normalise_link($user_info['url'])) |
|
); |
|
|
|
if (dbm::is_result($lastwall)) { |
|
$lastwall = $lastwall[0]; |
|
|
|
$in_reply_to = api_in_reply_to($lastwall); |
|
|
|
$converted = api_convert_item($lastwall); |
|
|
|
if ($type == "xml") { |
|
$geo = "georss:point"; |
|
} else { |
|
$geo = "geo"; |
|
} |
|
|
|
$status_info = array( |
|
'created_at' => api_date($lastwall['created']), |
|
'id' => intval($lastwall['id']), |
|
'id_str' => (string) $lastwall['id'], |
|
'text' => $converted["text"], |
|
'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'), |
|
'truncated' => false, |
|
'in_reply_to_status_id' => $in_reply_to['status_id'], |
|
'in_reply_to_status_id_str' => $in_reply_to['status_id_str'], |
|
'in_reply_to_user_id' => $in_reply_to['user_id'], |
|
'in_reply_to_user_id_str' => $in_reply_to['user_id_str'], |
|
'in_reply_to_screen_name' => $in_reply_to['screen_name'], |
|
'user' => $user_info, |
|
$geo => NULL, |
|
'coordinates' => "", |
|
'place' => "", |
|
'contributors' => "", |
|
'is_quote_status' => false, |
|
'retweet_count' => 0, |
|
'favorite_count' => 0, |
|
'favorited' => $lastwall['starred'] ? true : false, |
|
'retweeted' => false, |
|
'possibly_sensitive' => false, |
|
'lang' => "", |
|
'statusnet_html' => $converted["html"], |
|
'statusnet_conversation_id' => $lastwall['parent'], |
|
); |
|
|
|
if (count($converted["attachments"]) > 0) { |
|
$status_info["attachments"] = $converted["attachments"]; |
|
} |
|
|
|
if (count($converted["entities"]) > 0) { |
|
$status_info["entities"] = $converted["entities"]; |
|
} |
|
|
|
if (($lastwall['item_network'] != "") AND ($status["source"] == 'web')) { |
|
$status_info["source"] = network_to_name($lastwall['item_network'], $user_info['url']); |
|
} elseif (($lastwall['item_network'] != "") AND (network_to_name($lastwall['item_network'], $user_info['url']) != $status_info["source"])) { |
|
$status_info["source"] = trim($status_info["source"].' ('.network_to_name($lastwall['item_network'], $user_info['url']).')'); |
|
} |
|
|
|
// "uid" and "self" are only needed for some internal stuff, so remove it from here |
|
unset($status_info["user"]["uid"]); |
|
unset($status_info["user"]["self"]); |
|
} |
|
|
|
logger('status_info: '.print_r($status_info, true), LOGGER_DEBUG); |
|
|
|
if ($type == "raw") { |
|
return $status_info; |
|
} |
|
|
|
return api_format_data("statuses", $type, array('status' => $status_info)); |
|
|
|
} |
|
|
|
/** |
|
* Returns extended information of a given user, specified by ID or screen name as per the required id parameter. |
|
* The author's most recent status will be returned inline. |
|
* http://developer.twitter.com/doc/get/users/show |
|
*/ |
|
function api_users_show($type) { |
|
|
|
$a = get_app(); |
|
|
|
$user_info = api_get_user($a); |
|
$lastwall = q("SELECT `item`.* |
|
FROM `item` |
|
INNER JOIN `contact` ON `contact`.`id`=`item`.`contact-id` AND `contact`.`uid` = `item`.`uid` |
|
WHERE `item`.`uid` = %d AND `verb` = '%s' AND `item`.`contact-id` = %d |
|
AND ((`item`.`author-link` IN ('%s', '%s')) OR (`item`.`owner-link` IN ('%s', '%s'))) |
|
AND `type`!='activity' |
|
AND `item`.`allow_cid`='' AND `item`.`allow_gid`='' AND `item`.`deny_cid`='' AND `item`.`deny_gid`='' |
|
ORDER BY `id` DESC |
|
LIMIT 1", |
|
intval(api_user()), |
|
dbesc(ACTIVITY_POST), |
|
intval($user_info['cid']), |
|
dbesc($user_info['url']), |
|
dbesc(normalise_link($user_info['url'])), |
|
dbesc($user_info['url']), |
|
dbesc(normalise_link($user_info['url'])) |
|
); |
|
|
|
if (dbm::is_result($lastwall)) { |
|
$lastwall = $lastwall[0]; |
|
|
|
$in_reply_to = api_in_reply_to($lastwall); |
|
|
|
$converted = api_convert_item($lastwall); |
|
|
|
if ($type == "xml") { |
|
$geo = "georss:point"; |
|
} else { |
|
$geo = "geo"; |
|
} |
|
|
|
$user_info['status'] = array( |
|
'text' => $converted["text"], |
|
'truncated' => false, |
|
'created_at' => api_date($lastwall['created']), |
|
'in_reply_to_status_id' => $in_reply_to['status_id'], |
|
'in_reply_to_status_id_str' => $in_reply_to['status_id_str'], |
|
'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'), |
|
'id' => intval($lastwall['contact-id']), |
|
'id_str' => (string) $lastwall['contact-id'], |
|
'in_reply_to_user_id' => $in_reply_to['user_id'], |
|
'in_reply_to_user_id_str' => $in_reply_to['user_id_str'], |
|
'in_reply_to_screen_name' => $in_reply_to['screen_name'], |
|
$geo => NULL, |
|
'favorited' => $lastwall['starred'] ? true : false, |
|
'statusnet_html' => $converted["html"], |
|
'statusnet_conversation_id' => $lastwall['parent'], |
|
); |
|
|
|
if (count($converted["attachments"]) > 0) { |
|
$user_info["status"]["attachments"] = $converted["attachments"]; |
|
} |
|
|
|
if (count($converted["entities"]) > 0) { |
|
$user_info["status"]["entities"] = $converted["entities"]; |
|
} |
|
|
|
if (($lastwall['item_network'] != "") AND ($user_info["status"]["source"] == 'web')) { |
|
$user_info["status"]["source"] = network_to_name($lastwall['item_network'], $user_info['url']); |
|
} |
|
|
|
if (($lastwall['item_network'] != "") AND (network_to_name($lastwall['item_network'], $user_info['url']) != $user_info["status"]["source"])) { |
|
$user_info["status"]["source"] = trim($user_info["status"]["source"] . ' (' . network_to_name($lastwall['item_network'], $user_info['url']) . ')'); |
|
} |
|
|
|
} |
|
|
|
// "uid" and "self" are only needed for some internal stuff, so remove it from here |
|
unset($user_info["uid"]); |
|
unset($user_info["self"]); |
|
|
|
return api_format_data("user", $type, array('user' => $user_info)); |
|
|
|
} |
|
|
|
/// @TODO move to top of file or somewhere better |
|
api_register_func('api/users/show','api_users_show'); |
|
api_register_func('api/externalprofile/show','api_users_show'); |
|
|
|
function api_users_search($type) { |
|
|
|
$a = get_app(); |
|
|
|
$page = (x($_REQUEST, 'page') ? $_REQUEST['page'] - 1 : 0); |
|
|
|
$userlist = array(); |
|
|
|
if (x($_GET, 'q')) { |
|
$r = q("SELECT id FROM `contact` WHERE `uid` = 0 AND `name` = '%s'", dbesc($_GET["q"])); |
|
|
|
if (!dbm::is_result($r)) { |
|
$r = q("SELECT `id` FROM `contact` WHERE `uid` = 0 AND `nick` = '%s'", dbesc($_GET["q"])); |
|
} |
|
|
|
if (dbm::is_result($r)) { |
|
$k = 0; |
|
foreach ($r AS $user) { |
|
$user_info = api_get_user($a, $user["id"], "json"); |
|
|
|
if ($type == "xml") { |
|
$userlist[$k++.":user"] = $user_info; |
|
} else { |
|
$userlist[] = $user_info; |
|
} |
|
} |
|
$userlist = array("users" => $userlist); |
|
} else { |
|
throw new BadRequestException("User not found."); |
|
} |
|
} else { |
|
throw new BadRequestException("User not found."); |
|
} |
|
return api_format_data("users", $type, $userlist); |
|
} |
|
|
|
/// @TODO move to top of file or somewhere better |
|
api_register_func('api/users/search','api_users_search'); |
|
|
|
/** |
|
* |
|
* http://developer.twitter.com/doc/get/statuses/home_timeline |
|
* |
|
* TODO: Optional parameters |
|
* TODO: Add reply info |
|
*/ |
|
function api_statuses_home_timeline($type) { |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
throw new ForbiddenException(); |
|
} |
|
|
|
unset($_REQUEST["user_id"]); |
|
unset($_GET["user_id"]); |
|
|
|
unset($_REQUEST["screen_name"]); |
|
unset($_GET["screen_name"]); |
|
|
|
$user_info = api_get_user($a); |
|
// get last newtork messages |
|
|
|
// params |
|
$count = (x($_REQUEST, 'count') ? $_REQUEST['count'] : 20); |
|
$page = (x($_REQUEST, 'page') ? $_REQUEST['page'] - 1 : 0); |
|
if ($page < 0) { |
|
$page = 0; |
|
} |
|
$since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0); |
|
$max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0); |
|
//$since_id = 0;//$since_id = (x($_REQUEST, 'since_id')?$_REQUEST['since_id'] : 0); |
|
$exclude_replies = (x($_REQUEST, 'exclude_replies') ? 1 : 0); |
|
$conversation_id = (x($_REQUEST, 'conversation_id') ? $_REQUEST['conversation_id'] : 0); |
|
|
|
$start = $page * $count; |
|
|
|
$sql_extra = ''; |
|
if ($max_id > 0) { |
|
$sql_extra .= ' AND `item`.`id` <= ' . intval($max_id); |
|
} |
|
if ($exclude_replies > 0) { |
|
$sql_extra .= ' AND `item`.`parent` = `item`.`id`'; |
|
} |
|
if ($conversation_id > 0) { |
|
$sql_extra .= ' AND `item`.`parent` = ' . intval($conversation_id); |
|
} |
|
|
|
$r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`, |
|
`contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, |
|
`contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`, |
|
`contact`.`id` AS `cid` |
|
FROM `item` |
|
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid` |
|
AND (NOT `contact`.`blocked` OR `contact`.`pending`) |
|
WHERE `item`.`uid` = %d AND `verb` = '%s' |
|
AND `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted` |
|
$sql_extra |
|
AND `item`.`id`>%d |
|
ORDER BY `item`.`id` DESC LIMIT %d ,%d ", |
|
intval(api_user()), |
|
dbesc(ACTIVITY_POST), |
|
intval($since_id), |
|
intval($start), intval($count) |
|
); |
|
|
|
$ret = api_format_items($r, $user_info, false, $type); |
|
|
|
// Set all posts from the query above to seen |
|
$idarray = array(); |
|
foreach ($r AS $item) { |
|
$idarray[] = intval($item["id"]); |
|
} |
|
|
|
$idlist = implode(",", $idarray); |
|
|
|
if ($idlist != "") { |
|
$unseen = q("SELECT `id` FROM `item` WHERE `unseen` AND `id` IN (%s)", $idlist); |
|
|
|
if ($unseen) { |
|
$r = q("UPDATE `item` SET `unseen` = 0 WHERE `unseen` AND `id` IN (%s)", $idlist); |
|
} |
|
} |
|
|
|
$data = array('status' => $ret); |
|
switch ($type) { |
|
case "atom": |
|
case "rss": |
|
$data = api_rss_extra($a, $data, $user_info); |
|
break; |
|
} |
|
|
|
return api_format_data("statuses", $type, $data); |
|
} |
|
|
|
/// @TODO move to top of file or somewhere better |
|
api_register_func('api/statuses/home_timeline','api_statuses_home_timeline', true); |
|
api_register_func('api/statuses/friends_timeline','api_statuses_home_timeline', true); |
|
|
|
function api_statuses_public_timeline($type) { |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
throw new ForbiddenException(); |
|
} |
|
|
|
$user_info = api_get_user($a); |
|
// get last newtork messages |
|
|
|
// params |
|
$count = (x($_REQUEST, 'count') ? $_REQUEST['count'] : 20); |
|
$page = (x($_REQUEST, 'page') ? $_REQUEST['page'] -1 : 0); |
|
if ($page < 0) { |
|
$page = 0; |
|
} |
|
$since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0); |
|
$max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0); |
|
//$since_id = 0;//$since_id = (x($_REQUEST, 'since_id')?$_REQUEST['since_id'] : 0); |
|
$exclude_replies = (x($_REQUEST, 'exclude_replies') ? 1 : 0); |
|
$conversation_id = (x($_REQUEST, 'conversation_id') ? $_REQUEST['conversation_id'] : 0); |
|
|
|
$start = $page * $count; |
|
|
|
if ($max_id > 0) { |
|
$sql_extra = 'AND `item`.`id` <= ' . intval($max_id); |
|
} |
|
if ($exclude_replies > 0) { |
|
$sql_extra .= ' AND `item`.`parent` = `item`.`id`'; |
|
} |
|
if ($conversation_id > 0) { |
|
$sql_extra .= ' AND `item`.`parent` = ' . intval($conversation_id); |
|
} |
|
|
|
$r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`, |
|
`contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, |
|
`contact`.`network`, `contact`.`thumb`, `contact`.`self`, `contact`.`writable`, |
|
`contact`.`id` AS `cid`, |
|
`user`.`nickname`, `user`.`hidewall` |
|
FROM `item` |
|
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid` |
|
AND (NOT `contact`.`blocked` OR `contact`.`pending`) |
|
STRAIGHT_JOIN `user` ON `user`.`uid` = `item`.`uid` |
|
AND NOT `user`.`hidewall` |
|
WHERE `verb` = '%s' AND `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` |
|
AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' |
|
AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' |
|
AND NOT `item`.`private` AND `item`.`wall` |
|
$sql_extra |
|
AND `item`.`id`>%d |
|
ORDER BY `item`.`id` DESC LIMIT %d, %d ", |
|
dbesc(ACTIVITY_POST), |
|
intval($since_id), |
|
intval($start), |
|
intval($count)); |
|
|
|
$ret = api_format_items($r, $user_info, false, $type); |
|
|
|
$data = array('status' => $ret); |
|
switch ($type) { |
|
case "atom": |
|
case "rss": |
|
$data = api_rss_extra($a, $data, $user_info); |
|
break; |
|
} |
|
|
|
return api_format_data("statuses", $type, $data); |
|
} |
|
|
|
/// @TODO move to top of file or somewhere better |
|
api_register_func('api/statuses/public_timeline','api_statuses_public_timeline', true); |
|
|
|
/** |
|
* @TODO nothing to say? |
|
*/ |
|
function api_statuses_show($type) { |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
throw new ForbiddenException(); |
|
} |
|
|
|
$user_info = api_get_user($a); |
|
|
|
// params |
|
$id = intval($a->argv[3]); |
|
|
|
if ($id == 0) { |
|
$id = intval($_REQUEST["id"]); |
|
} |
|
|
|
// Hotot workaround |
|
if ($id == 0) { |
|
$id = intval($a->argv[4]); |
|
} |
|
|
|
logger('API: api_statuses_show: ' . $id); |
|
|
|
$conversation = (x($_REQUEST, 'conversation') ? 1 : 0); |
|
|
|
$sql_extra = ''; |
|
if ($conversation) { |
|
$sql_extra .= " AND `item`.`parent` = %d ORDER BY `id` ASC "; |
|
} else { |
|
$sql_extra .= " AND `item`.`id` = %d"; |
|
} |
|
|
|
$r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`, |
|
`contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, |
|
`contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`, |
|
`contact`.`id` AS `cid` |
|
FROM `item` |
|
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid` |
|
AND (NOT `contact`.`blocked` OR `contact`.`pending`) |
|
WHERE `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted` |
|
AND `item`.`uid` = %d AND `item`.`verb` = '%s' |
|
$sql_extra", |
|
intval(api_user()), |
|
dbesc(ACTIVITY_POST), |
|
intval($id) |
|
); |
|
|
|
/// @TODO How about copying this to above methods which don't check $r ? |
|
if (!dbm::is_result($r)) { |
|
throw new BadRequestException("There is no status with this id."); |
|
} |
|
|
|
$ret = api_format_items($r,$user_info, false, $type); |
|
|
|
if ($conversation) { |
|
$data = array('status' => $ret); |
|
return api_format_data("statuses", $type, $data); |
|
} else { |
|
$data = array('status' => $ret[0]); |
|
return api_format_data("status", $type, $data); |
|
} |
|
} |
|
|
|
/// @TODO move to top of file or somewhere better |
|
api_register_func('api/statuses/show','api_statuses_show', true); |
|
|
|
/** |
|
* @TODO nothing to say? |
|
*/ |
|
function api_conversation_show($type) { |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
throw new ForbiddenException(); |
|
} |
|
|
|
$user_info = api_get_user($a); |
|
|
|
// params |
|
$id = intval($a->argv[3]); |
|
$count = (x($_REQUEST, 'count') ? $_REQUEST['count'] : 20); |
|
$page = (x($_REQUEST, 'page') ? $_REQUEST['page'] - 1 : 0); |
|
if ($page < 0) { |
|
$page = 0; |
|
} |
|
$since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0); |
|
$max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0); |
|
|
|
$start = $page*$count; |
|
|
|
if ($id == 0) { |
|
$id = intval($_REQUEST["id"]); |
|
} |
|
|
|
// Hotot workaround |
|
if ($id == 0) { |
|
$id = intval($a->argv[4]); |
|
} |
|
|
|
logger('API: api_conversation_show: '.$id); |
|
|
|
$r = q("SELECT `parent` FROM `item` WHERE `id` = %d", intval($id)); |
|
if (dbm::is_result($r)) { |
|
$id = $r[0]["parent"]; |
|
} |
|
|
|
$sql_extra = ''; |
|
|
|
if ($max_id > 0) { |
|
$sql_extra = ' AND `item`.`id` <= ' . intval($max_id); |
|
} |
|
|
|
// Not sure why this query was so complicated. We should keep it here for a while, |
|
// just to make sure that we really don't need it. |
|
// FROM `item` INNER JOIN (SELECT `uri`,`parent` FROM `item` WHERE `id` = %d) AS `temp1` |
|
// ON (`item`.`thr-parent` = `temp1`.`uri` AND `item`.`parent` = `temp1`.`parent`) |
|
|
|
$r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`, |
|
`contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, |
|
`contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`, |
|
`contact`.`id` AS `cid` |
|
FROM `item` |
|
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid` |
|
AND (NOT `contact`.`blocked` OR `contact`.`pending`) |
|
WHERE `item`.`parent` = %d AND `item`.`visible` |
|
AND NOT `item`.`moderated` AND NOT `item`.`deleted` |
|
AND `item`.`uid` = %d AND `item`.`verb` = '%s' |
|
AND `item`.`id`>%d $sql_extra |
|
ORDER BY `item`.`id` DESC LIMIT %d ,%d", |
|
intval($id), intval(api_user()), |
|
dbesc(ACTIVITY_POST), |
|
intval($since_id), |
|
intval($start), intval($count) |
|
); |
|
|
|
if (!dbm::is_result($r)) { |
|
throw new BadRequestException("There is no status with this id."); |
|
} |
|
|
|
$ret = api_format_items($r, $user_info, false, $type); |
|
|
|
$data = array('status' => $ret); |
|
return api_format_data("statuses", $type, $data); |
|
} |
|
|
|
/// @TODO move to top of file or somewhere better |
|
api_register_func('api/conversation/show','api_conversation_show', true); |
|
api_register_func('api/statusnet/conversation','api_conversation_show', true); |
|
|
|
/** |
|
* @TODO nothing to say? |
|
*/ |
|
function api_statuses_repeat($type) { |
|
global $called_api; |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
throw new ForbiddenException(); |
|
} |
|
|
|
$user_info = api_get_user($a); |
|
|
|
// params |
|
$id = intval($a->argv[3]); |
|
|
|
if ($id == 0) { |
|
$id = intval($_REQUEST["id"]); |
|
} |
|
|
|
// Hotot workaround |
|
if ($id == 0) { |
|
$id = intval($a->argv[4]); |
|
} |
|
|
|
logger('API: api_statuses_repeat: '.$id); |
|
|
|
$r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`, `contact`.`nick` as `reply_author`, |
|
`contact`.`name`, `contact`.`photo` as `reply_photo`, `contact`.`url` as `reply_url`, `contact`.`rel`, |
|
`contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`, |
|
`contact`.`id` AS `cid` |
|
FROM `item` |
|
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid` |
|
AND (NOT `contact`.`blocked` OR `contact`.`pending`) |
|
WHERE `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted` |
|
AND NOT `item`.`private` AND `item`.`allow_cid` = '' AND `item`.`allow`.`gid` = '' |
|
AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' |
|
$sql_extra |
|
AND `item`.`id`=%d", |
|
intval($id) |
|
); |
|
|
|
/// @TODO other style than above functions! |
|
if (dbm::is_result($r) && $r[0]['body'] != "") { |
|
if (strpos($r[0]['body'], "[/share]") !== false) { |
|
$pos = strpos($r[0]['body'], "[share"); |
|
$post = substr($r[0]['body'], $pos); |
|
} else { |
|
$post = share_header($r[0]['author-name'], $r[0]['author-link'], $r[0]['author-avatar'], $r[0]['guid'], $r[0]['created'], $r[0]['plink']); |
|
|
|
$post .= $r[0]['body']; |
|
$post .= "[/share]"; |
|
} |
|
$_REQUEST['body'] = $post; |
|
$_REQUEST['profile_uid'] = api_user(); |
|
$_REQUEST['type'] = 'wall'; |
|
$_REQUEST['api_source'] = true; |
|
|
|
if (!x($_REQUEST, "source")) { |
|
$_REQUEST["source"] = api_source(); |
|
} |
|
|
|
item_post($a); |
|
} else { |
|
throw new ForbiddenException(); |
|
} |
|
|
|
// this should output the last post (the one we just posted). |
|
$called_api = null; |
|
return api_status_show($type); |
|
} |
|
|
|
/// @TODO move to top of file or somewhere better |
|
api_register_func('api/statuses/retweet','api_statuses_repeat', true, API_METHOD_POST); |
|
|
|
/** |
|
* @TODO nothing to say? |
|
*/ |
|
function api_statuses_destroy($type) { |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
throw new ForbiddenException(); |
|
} |
|
|
|
$user_info = api_get_user($a); |
|
|
|
// params |
|
$id = intval($a->argv[3]); |
|
|
|
if ($id == 0) { |
|
$id = intval($_REQUEST["id"]); |
|
} |
|
|
|
// Hotot workaround |
|
if ($id == 0) { |
|
$id = intval($a->argv[4]); |
|
} |
|
|
|
logger('API: api_statuses_destroy: '.$id); |
|
|
|
$ret = api_statuses_show($type); |
|
|
|
drop_item($id, false); |
|
|
|
return $ret; |
|
} |
|
|
|
/// @TODO move to top of file or somewhere better |
|
api_register_func('api/statuses/destroy','api_statuses_destroy', true, API_METHOD_DELETE); |
|
|
|
/** |
|
* @TODO Nothing more than an URL to say? |
|
* http://developer.twitter.com/doc/get/statuses/mentions |
|
*/ |
|
function api_statuses_mentions($type) { |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
throw new ForbiddenException(); |
|
} |
|
|
|
unset($_REQUEST["user_id"]); |
|
unset($_GET["user_id"]); |
|
|
|
unset($_REQUEST["screen_name"]); |
|
unset($_GET["screen_name"]); |
|
|
|
$user_info = api_get_user($a); |
|
// get last newtork messages |
|
|
|
|
|
// params |
|
$count = (x($_REQUEST, 'count') ? $_REQUEST['count'] : 20); |
|
$page = (x($_REQUEST, 'page') ? $_REQUEST['page'] -1 : 0); |
|
if ($page < 0) { |
|
$page = 0; |
|
} |
|
$since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0); |
|
$max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0); |
|
//$since_id = 0;//$since_id = (x($_REQUEST, 'since_id')?$_REQUEST['since_id'] : 0); |
|
|
|
$start = $page * $count; |
|
|
|
// Ugly code - should be changed |
|
$myurl = App::get_baseurl() . '/profile/'. $a->user['nickname']; |
|
$myurl = substr($myurl,strpos($myurl, '://') + 3); |
|
//$myurl = str_replace(array('www.','.'),array('','\\.'),$myurl); |
|
$myurl = str_replace('www.', '', $myurl); |
|
$diasp_url = str_replace('/profile/', '/u/', $myurl); |
|
|
|
if ($max_id > 0) { |
|
$sql_extra = ' AND `item`.`id` <= ' . intval($max_id); |
|
} |
|
|
|
$r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`, |
|
`contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, |
|
`contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`, |
|
`contact`.`id` AS `cid` |
|
FROM `item` FORCE INDEX (`uid_id`) |
|
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid` |
|
AND (NOT `contact`.`blocked` OR `contact`.`pending`) |
|
WHERE `item`.`uid` = %d AND `verb` = '%s' |
|
AND NOT (`item`.`author-link` IN ('https://%s', 'http://%s')) |
|
AND `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted` |
|
AND `item`.`parent` IN (SELECT `iid` FROM `thread` WHERE `uid` = %d AND `mention` AND !`ignored`) |
|
$sql_extra |
|
AND `item`.`id`>%d |
|
ORDER BY `item`.`id` DESC LIMIT %d ,%d ", |
|
intval(api_user()), |
|
dbesc(ACTIVITY_POST), |
|
dbesc(protect_sprintf($myurl)), |
|
dbesc(protect_sprintf($myurl)), |
|
intval(api_user()), |
|
intval($since_id), |
|
intval($start), intval($count) |
|
); |
|
|
|
$ret = api_format_items($r, $user_info, false, $type); |
|
|
|
$data = array('status' => $ret); |
|
switch ($type) { |
|
case "atom": |
|
case "rss": |
|
$data = api_rss_extra($a, $data, $user_info); |
|
break; |
|
} |
|
|
|
return api_format_data("statuses", $type, $data); |
|
} |
|
|
|
/// @TODO move to top of file or somewhere better |
|
api_register_func('api/statuses/mentions','api_statuses_mentions', true); |
|
api_register_func('api/statuses/replies','api_statuses_mentions', true); |
|
|
|
function api_statuses_user_timeline($type) { |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
throw new ForbiddenException(); |
|
} |
|
|
|
$user_info = api_get_user($a); |
|
// get last network messages |
|
|
|
logger("api_statuses_user_timeline: api_user: ". api_user() . |
|
"\nuser_info: ".print_r($user_info, true) . |
|
"\n_REQUEST: ".print_r($_REQUEST, true), |
|
LOGGER_DEBUG); |
|
|
|
// params |
|
$count = (x($_REQUEST, 'count') ? $_REQUEST['count'] : 20); |
|
$page = (x($_REQUEST, 'page') ? $_REQUEST['page'] -1 : 0); |
|
if ($page < 0) { |
|
$page = 0; |
|
} |
|
$since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0); |
|
//$since_id = 0;//$since_id = (x($_REQUEST, 'since_id')?$_REQUEST['since_id'] : 0); |
|
$exclude_replies = (x($_REQUEST, 'exclude_replies') ? 1 : 0); |
|
$conversation_id = (x($_REQUEST, 'conversation_id') ? $_REQUEST['conversation_id'] : 0); |
|
|
|
$start = $page * $count; |
|
|
|
$sql_extra = ''; |
|
if ($user_info['self'] == 1) { |
|
$sql_extra .= " AND `item`.`wall` = 1 "; |
|
} |
|
|
|
if ($exclude_replies > 0) { |
|
$sql_extra .= ' AND `item`.`parent` = `item`.`id`'; |
|
} |
|
if ($conversation_id > 0) { |
|
$sql_extra .= ' AND `item`.`parent` = ' . intval($conversation_id); |
|
} |
|
|
|
$r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`, |
|
`contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, |
|
`contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`, |
|
`contact`.`id` AS `cid` |
|
FROM `item` FORCE INDEX (`uid_contactid_id`) |
|
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid` |
|
AND (NOT `contact`.`blocked` OR `contact`.`pending`) |
|
WHERE `item`.`uid` = %d AND `verb` = '%s' |
|
AND `item`.`contact-id` = %d |
|
AND `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted` |
|
$sql_extra |
|
AND `item`.`id`>%d |
|
ORDER BY `item`.`id` DESC LIMIT %d ,%d ", |
|
intval(api_user()), |
|
dbesc(ACTIVITY_POST), |
|
intval($user_info['cid']), |
|
intval($since_id), |
|
intval($start), intval($count) |
|
); |
|
|
|
$ret = api_format_items($r, $user_info, true, $type); |
|
|
|
$data = array('status' => $ret); |
|
switch ($type) { |
|
case "atom": |
|
case "rss": |
|
$data = api_rss_extra($a, $data, $user_info); |
|
break; |
|
} |
|
|
|
return api_format_data("statuses", $type, $data); |
|
} |
|
|
|
/// @TODO move to top of file or somwhere better |
|
api_register_func('api/statuses/user_timeline','api_statuses_user_timeline', true); |
|
|
|
/** |
|
* Star/unstar an item |
|
* param: id : id of the item |
|
* |
|
* api v1 : https://web.archive.org/web/20131019055350/https://dev.twitter.com/docs/api/1/post/favorites/create/%3Aid |
|
*/ |
|
function api_favorites_create_destroy($type) { |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
throw new ForbiddenException(); |
|
} |
|
|
|
// for versioned api. |
|
/// @TODO We need a better global soluton |
|
$action_argv_id = 2; |
|
if ($a->argv[1] == "1.1") { |
|
$action_argv_id = 3; |
|
} |
|
|
|
if ($a->argc <= $action_argv_id) { |
|
throw new BadRequestException("Invalid request."); |
|
} |
|
$action = str_replace("." . $type, "", $a->argv[$action_argv_id]); |
|
if ($a->argc == $action_argv_id + 2) { |
|
$itemid = intval($a->argv[$action_argv_id + 1]); |
|
} else { |
|
/// @TODO use x() to check if _REQUEST contains 'id' |
|
$itemid = intval($_REQUEST['id']); |
|
} |
|
|
|
$item = q("SELECT * FROM `item` WHERE `id`=%d AND `uid`=%d LIMIT 1", |
|
$itemid, api_user()); |
|
|
|
if (!dbm::is_result($item) || count($item) == 0) { |
|
throw new BadRequestException("Invalid item."); |
|
} |
|
|
|
switch ($action) { |
|
case "create": |
|
$item[0]['starred'] = 1; |
|
break; |
|
case "destroy": |
|
$item[0]['starred'] = 0; |
|
break; |
|
default: |
|
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()); |
|
|
|
q("UPDATE thread SET starred=%d WHERE iid=%d AND uid=%d", |
|
$item[0]['starred'], $itemid, api_user()); |
|
|
|
if ($r === false) { |
|
throw new InternalServerErrorException("DB error"); |
|
} |
|
|
|
|
|
$user_info = api_get_user($a); |
|
$rets = api_format_items($item, $user_info, false, $type); |
|
$ret = $rets[0]; |
|
|
|
$data = array('status' => $ret); |
|
switch ($type) { |
|
case "atom": |
|
case "rss": |
|
$data = api_rss_extra($a, $data, $user_info); |
|
} |
|
|
|
return api_format_data("status", $type, $data); |
|
} |
|
|
|
/// @TODO move to top of file or somwhere better |
|
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($type) { |
|
global $called_api; |
|
|
|
$a = get_app(); |
|
|
|
if (api_user() === false) { |
|
throw new ForbiddenException(); |
|
} |
|
|
|
$called_api = array(); |
|
|
|
$user_info = api_get_user($a); |
|
|
|
// in friendica starred item are private |
|
// return favorites only for self |
|
logger('api_favorites: self:' . $user_info['self']); |
|
|
|
if ($user_info['self'] == 0) { |
|
$ret = array(); |
|
} else { |
|
$sql_extra = ""; |
|
|
|
// params |
|
$since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0); |
|
$max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0); |
|
$count = (x($_GET, 'count') ? $_GET['count'] : 20); |
|
$page = (x($_REQUEST, 'page') ? $_REQUEST['page'] -1 : 0); |
|
if ($page < 0) { |
|
$page = 0; |
|
} |
|
|
|
$start = $page*$count; |
|
|
|
if ($max_id > 0) { |
|
$sql_extra .= ' AND `item`.`id` <= ' . intval($max_id); |
|
} |
|
|
|
$r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`, |
|
`contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, |
|
`contact`.`network`, `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`, |
|
`contact`.`id` AS `cid` |
|
FROM `item`, `contact` |
|
WHERE `item`.`uid` = %d |
|
AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 |
|
AND `item`.`starred` = 1 |
|
AND `contact`.`id` = `item`.`contact-id` |
|
AND (NOT `contact`.`blocked` OR `contact`.`pending`) |
|
$sql_extra |
|
AND `item`.`id`>%d |
|
ORDER BY `item`.`id` DESC LIMIT %d ,%d ", |
|
intval(api_user()), |
|
intval($since_id), |
|
intval($start), intval($count) |
|
); |
|
|
|
$ret = api_format_items($r,$user_info, false, $type); |
|
|
|
} |
|
|
|
$data = array('status' => $ret); |
|
switch ($type) { |
|
case "atom": |
|
case "rss": |
|
$data = api_rss_extra($a, $data, $user_info); |
|
} |
|
|
|
return api_format_data("statuses", $type, $data); |
|
} |
|
|
|
/// @TODO move to top of file or somwhere better |
|
api_register_func('api/favorites','api_favorites', true); |
|
|
|
function api_format_messages($item, $recipient, $sender) { |
|
// standard meta information |
|
$ret=Array( |
|
'id' => $item['id'], |
|
'sender_id' => $sender['id'] , |
|
'text' => "", |
|
'recipient_id' => $recipient['id'], |
|
'created_at' => api_date($item['created']), |
|
'sender_screen_name' => $sender['screen_name'], |
|
'recipient_screen_name' => $recipient['screen_name'], |
|
'sender' => $sender, |
|
'recipient' => $recipient, |
|
'title' => "", |
|
'friendica_seen' => $item['seen'], |
|
'friendica_parent_uri' => $item['parent-uri'], |
|
); |
|
|
|
// "uid" and "self" are only needed for some internal stuff, so remove it from here |
|
unset($ret["sender"]["uid"]); |
|
unset($ret["sender"]["self"]); |
|
unset($ret["recipient"]["uid"]); |
|
unset($ret["recipient"]["self"]); |
|
|
|
//don't send title to regular StatusNET requests to avoid confusing these apps |
|
if (x($_GET, 'getText')) { |
|
$ret['title'] = $item['title'] ; |
|
if ($_GET['getText'] == 'html') { |
|
$ret['text'] = bbcode($item['body'], false, false); |
|
} elseif ($_GET['getText'] == 'plain') { |
|
//$ret['text'] = html2plain(bbcode($item['body'], false, false, true), 0); |
|
$ret['text'] = trim(html2plain(bbcode(api_clean_plain_items($item['body']), false, false, 2, true), 0)); |
|
} |
|
} else { |
|
$ret['text'] = $item['title'] . "\n" . html2plain(bbcode(api_clean_plain_items($item['body']), false, false, 2, true), 0); |
|
} |
|
if (x($_GET, 'getUserObjects') && $_GET['getUserObjects'] == 'false') { |
|
unset($ret['sender']); |
|
unset($ret['recipient']); |
|
} |
|
|
|
return $ret; |
|
} |
|
|
|
function api_convert_item($item) { |
|
$body = $item['body']; |
|
$attachments = api_get_attachments($body); |
|
|
|
// Workaround for ostatus messages where the title is identically to the body |
|
$html = bbcode(api_clean_plain_items($body), false, false, 2, true); |
|
$statusbody = trim(html2plain($html, 0)); |
|
|
|
// handle data: images |
|
$statusbody = api_format_items_embeded_images($item,$statusbody); |
|
|
|
$statustitle = trim($item['title']); |
|
|
|
if (($statustitle != '') and (strpos($statusbody, $statustitle) !== false)) { |
|
$statustext = trim($statusbody); |
|
} else { |
|
$statustext = trim($statustitle."\n\n".$statusbody); |
|
} |
|
|
|
if (($item["network"] == NETWORK_FEED) and (strlen($statustext)> 1000)) { |
|
$statustext = substr($statustext, 0, 1000)."... \n".$item["plink"]; |
|
} |
|
|
|
$statushtml = trim(bbcode($body, false, false)); |
|
|
|
$search = array("<br>", "<blockquote>", "</blockquote>", |
|
"<h1>", "</h1>", "<h2>", "</h2>", |
|
"<h3>", "</h3>", "<h4>", "</h4>", |
|
"<h5>", "</h5>", "<h6>", "</h6>"); |
|
$replace = array("<br>\n", "\n<blockquote>", "</blockquote>\n", |
|
"\n<h1>", "</h1>\n", "\n<h2>", "</h2>\n", |
|
"\n<h3>", "</h3>\n", "\n<h4>", "</h4>\n", |
|
"\n<h5>", "</h5>\n", "\n<h6>", "</h6>\n"); |
|
$statushtml = str_replace($search, $replace, $statushtml); |
|
|
|
if ($item['title'] != "") { |
|
$statushtml = "<h4>" . bbcode($item['title']) . "</h4>\n" . $statushtml; |
|
} |
|
|
|
$entities = api_get_entitities($statustext, $body); |
|
|
|
return array( |
|
"text" => $statustext, |
|
"html" => $statushtml, |
|
"attachments" => $attachments, |
|
"entities" => $entities |
|
); |
|
} |
|
|
|
function api_get_attachments(&$body) { |
|
|
|
$text = $body; |
|
$text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $text); |
|
|
|
$URLSearchString = "^\[\]"; |
|
$ret = preg_match_all("/\[img\]([$URLSearchString]*)\[\/img\]/ism", $text, $images); |
|
|
|
if (!$ret) { |
|
return false; |
|
} |
|
|
|
$attachments = array(); |
|
|
|
foreach ($images[1] AS $image) { |
|
$imagedata = get_photo_info($image); |
|
|
|
if ($imagedata) { |
|
$attachments[] = array("url" => $image, "mimetype" => $imagedata["mime"], "size" => $imagedata["size"]); |
|
} |
|
} |
|
|
|
if (strstr($_SERVER['HTTP_USER_AGENT'], "AndStatus")) { |
|
foreach ($images[0] AS $orig) { |
|
$body = str_replace($orig, "", $body); |
|
} |
|
} |
|
|
|
return $attachments; |
|
} |
|
|
|
function api_get_entitities(&$text, $bbcode) { |
|
/* |
|
To-Do: |
|
* Links at the first character of the post |
|
*/ |
|
|
|
$a = get_app(); |
|
|
|
$include_entities = strtolower(x($_REQUEST, 'include_entities') ? $_REQUEST['include_entities'] : "false"); |
|
|
|
if ($include_entities != "true") { |
|
|
|
preg_match_all("/\[img](.*?)\[\/img\]/ism", $bbcode, $images); |
|
|
|
foreach ($images[1] AS $image) { |
|
$replace = proxy_url($image); |
|
$text = str_replace($image, $replace, $text); |
|
} |
|
return array(); |
|
} |
|
|
|
$bbcode = bb_CleanPictureLinks($bbcode); |
|
|
|
// Change pure links in text to bbcode uris |
|
$bbcode = preg_replace("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1[url=$2]$2[/url]', $bbcode); |
|
|
|
$entities = array(); |
|
$entities["hashtags"] = array(); |
|
$entities["symbols"] = array(); |
|
$entities["urls"] = array(); |
|
$entities["user_mentions"] = array(); |
|
|
|
$URLSearchString = "^\[\]"; |
|
|
|
$bbcode = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",'#$2',$bbcode); |
|
|
|
$bbcode = preg_replace("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",'[url=$1]$2[/url]',$bbcode); |
|
//$bbcode = preg_replace("/\[url\](.*?)\[\/url\]/ism",'[url=$1]$1[/url]',$bbcode); |
|
$bbcode = preg_replace("/\[video\](.*?)\[\/video\]/ism",'[url=$1]$1[/url]',$bbcode); |
|
|
|
$bbcode = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", |
|
'[url=https://www.youtube.com/watch?v=$1]https://www.youtube.com/watch?v=$1[/url]', $bbcode); |
|
$bbcode = preg_replace("/\[youtube\](.*?)\[\/youtube\]/ism",'[url=$1]$1[/url]',$bbcode); |
|
|
|
$bbcode = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", |
|
'[url=https://vimeo.com/$1]https://vimeo.com/$1[/url]', $bbcode); |
|
$bbcode = preg_replace("/\[vimeo\](.*?)\[\/vimeo\]/ism",'[url=$1]$1[/url]',$bbcode); |
|
|
|
$bbcode = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $bbcode); |
|
|
|
//preg_match_all("/\[url\]([$URLSearchString]*)\[\/url\]/ism", $bbcode, $urls1); |
|
preg_match_all("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $bbcode, $urls); |
|
|
|
$ordered_urls = array(); |
|
foreach ($urls[1] AS $id => $url) { |
|
//$start = strpos($text, $url, $offset); |
|
$start = iconv_strpos($text, $url, 0, "UTF-8"); |
|
if (!($start === false)) { |
|
$ordered_urls[$start] = array("url" => $url, "title" => $urls[2][$id]); |
|
} |
|
} |
|
|
|
ksort($ordered_urls); |
|
|
|
$offset = 0; |
|
//foreach ($urls[1] AS $id=>$url) { |
|
foreach ($ordered_urls AS $url) { |
|
if ((substr($url["title"], 0, 7) != "http://") AND (substr($url["title"], 0, 8) != "https://") AND |
|
!strpos($url["title"], "http://") AND !strpos($url["title"], "https://")) |
|
$display_url = $url["title"]; |
|
else { |
|
$display_url = str_replace(array("http://www.", "https://www."), array("", ""), $url["url"]); |
|
$display_url = str_replace(array("http://", "https://"), array("", ""), $display_url); |
|
|
|
if (strlen($display_url) > 26) |
|
$display_url = substr($display_url, 0, 25)."…"; |
|
} |
|
|
|
//$start = strpos($text, $url, $offset); |
|
$start = iconv_strpos($text, $url["url"], $offset, "UTF-8"); |
|
if (!($start === false)) { |
|
$entities["urls"][] = array("url" => $url["url"], |
|
"expanded_url" => $url["url"], |
|
"display_url" => $display_url, |
|
"indices" => array($start, $start+strlen($url["url"]))); |
|
$offset = $start + 1; |
|
} |
|
} |
|
|
|
preg_match_all("/\[img](.*?)\[\/img\]/ism", $bbcode, $images); |
|
$ordered_images = array(); |
|
foreach ($images[1] AS $image) { |
|
//$start = strpos($text, $url, $offset); |
|
$start = iconv_strpos($text, $image, 0, "UTF-8"); |
|
if (!($start === false)) |
|
$ordered_images[$start] = $image; |
|
} |
|
//$entities["media"] = array(); |
|
$offset = 0; |
|
|
|
foreach ($ordered_images AS $url) { |
|
$display_url = str_replace(array("http://www.", "https://www."), array("", ""), $url); |
|
$display_url = str_replace(array("http://", "https://"), array("", ""), $display_url); |
|
|
|
if (strlen($display_url) > 26) |
|
$display_url = substr($display_url, 0, 25)."…"; |
|
|
|
$start = iconv_strpos($text, $url, $offset, "UTF-8"); |
|
if (!($start === false)) { |
|
$image = get_photo_info($url); |
|
if ($image) { |
|
// If image cache is activated, then use the following sizes: |
|
// thumb (150), small (340), medium (600) and large (1024) |
|
if (!get_config("system", "proxy_disabled")) { |
|
$media_url = proxy_url($url); |
|
|
|
$sizes = array(); |
|
$scale = scale_image($image[0], $image[1], 150); |
|
$sizes["thumb"] = array("w" => $scale["width"], "h" => $scale["height"], "resize" => "fit"); |
|
|
|
if (($image[0] > 150) OR ($image[1] > 150)) { |
|
$scale = scale_image($image[0], $image[1], 340); |
|
$sizes[" |