From d7c129fb1330d04e7a568fd70e494e83cb9b0e09 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Tue, 10 Jun 2014 20:14:51 +0200 Subject: [PATCH] Appnet: Bidirectional sync with app.net --- appnet/appnet.css | 4 +- appnet/appnet.php | 820 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 770 insertions(+), 54 deletions(-) diff --git a/appnet/appnet.css b/appnet/appnet.css index 231f4306a..b1d8d27e3 100755 --- a/appnet/appnet.css +++ b/appnet/appnet.css @@ -1,4 +1,4 @@ -#appnet-disconnect-label, #appnet-token-label, +#appnet-import-label, #appnet-disconnect-label, #appnet-token-label, #appnet-enable-label, #appnet-bydefault-label, #appnet-clientid-label, #appnet-clientsecret-label { float: left; @@ -6,7 +6,7 @@ margin-top: 10px; } -#appnet-disconnect, #appnet-token, +#appnet-import, #appnet-disconnect, #appnet-token, #appnet-checkbox, #appnet-bydefault, #appnet-clientid, #appnet-clientsecret { float: left; diff --git a/appnet/appnet.php b/appnet/appnet.php index e30ecdca4..d175ad589 100644 --- a/appnet/appnet.php +++ b/appnet/appnet.php @@ -2,17 +2,24 @@ /** * Name: App.net Connector - * Description: Post to app.net - * Version: 0.1 + * Description: app.net postings import and export + * Version: 0.2 * Author: Michael Vogel */ +/* + Some marker in the post so that reimported posts can be treated better. (BBCode over app.net?) +*/ + +define('APPNET_DEFAULT_POLL_INTERVAL', 5); // given in minutes + function appnet_install() { - register_hook('post_local', 'addon/appnet/appnet.php', 'appnet_post_local'); - register_hook('notifier_normal', 'addon/appnet/appnet.php', 'appnet_send'); - register_hook('jot_networks', 'addon/appnet/appnet.php', 'appnet_jot_nets'); - register_hook('connector_settings', 'addon/appnet/appnet.php', 'appnet_settings'); - register_hook('connector_settings_post', 'addon/appnet/appnet.php', 'appnet_settings_post'); + register_hook('post_local', 'addon/appnet/appnet.php', 'appnet_post_local'); + register_hook('notifier_normal', 'addon/appnet/appnet.php', 'appnet_send'); + register_hook('jot_networks', 'addon/appnet/appnet.php', 'appnet_jot_nets'); + register_hook('cron', 'addon/appnet/appnet.php', 'appnet_cron'); + register_hook('connector_settings', 'addon/appnet/appnet.php', 'appnet_settings'); + register_hook('connector_settings_post','addon/appnet/appnet.php', 'appnet_settings_post'); } @@ -20,6 +27,7 @@ function appnet_uninstall() { unregister_hook('post_local', 'addon/appnet/appnet.php', 'appnet_post_local'); unregister_hook('notifier_normal', 'addon/appnet/appnet.php', 'appnet_send'); unregister_hook('jot_networks', 'addon/appnet/appnet.php', 'appnet_jot_nets'); + unregister_hook('cron', 'addon/appnet/appnet.php', 'appnet_cron'); unregister_hook('connector_settings', 'addon/appnet/appnet.php', 'appnet_settings'); unregister_hook('connector_settings_post', 'addon/appnet/appnet.php', 'appnet_settings_post'); } @@ -27,27 +35,27 @@ function appnet_uninstall() { function appnet_module() {} function appnet_content(&$a) { - if(! local_user()) { - notice( t('Permission denied.') . EOL); - return ''; - } + if(! local_user()) { + notice( t('Permission denied.') . EOL); + return ''; + } - require_once("mod/settings.php"); - settings_init($a); + require_once("mod/settings.php"); + settings_init($a); - if (isset($a->argv[1])) - switch ($a->argv[1]) { - case "connect": - $o = appnet_connect($a); - break; - default: - $o = print_r($a->argv, true); - break; - } - else + if (isset($a->argv[1])) + switch ($a->argv[1]) { + case "connect": + $o = appnet_connect($a); + break; + default: + $o = print_r($a->argv, true); + break; + } + else $o = appnet_connect($a); - return $o; + return $o; } function appnet_connect(&$a) { @@ -108,6 +116,11 @@ function appnet_settings(&$a,&$s) { $def_enabled = get_pconfig(local_user(),'appnet','post_by_default'); $def_checked = (($def_enabled) ? ' checked="checked" ' : ''); + $importenabled = get_pconfig(local_user(),'appnet','import'); + $importchecked = (($importenabled) ? ' checked="checked" ' : ''); + + $ownid = get_pconfig(local_user(),'appnet','ownid'); + $s .= ''; $s .= '

'. t('App.net Export').'

'; $s .= '
'; @@ -123,6 +136,9 @@ function appnet_settings(&$a,&$s) { try { $userdata = $app->getUser(); + if ($ownid != $userdata["id"]) + set_pconfig(local_user(),'appnet','ownid', $userdata["id"]); + $s .= '

'. t('Currently connected to: ') .''.$userdata["username"].'
'.$userdata["description"]["text"].'

'; $s .= '
'; $s .= ''; @@ -133,8 +149,13 @@ function appnet_settings(&$a,&$s) { $s .= ''; $s .= ''; $s .= '
'; + + $s .= ''; + $s .= ''; + $s .= '
'; + } - catch (AppDotNetException $e) { + catch (AppDotNetException $e) { $s .= t("

Error fetching user profile. Please clear the configuration and try again.

"); } //$s .= print_r($userdata, true); @@ -193,6 +214,9 @@ function appnet_settings_post(&$a,&$b) { del_pconfig(local_user(), 'appnet', 'clientsecret'); del_pconfig(local_user(), 'appnet', 'clientid'); del_pconfig(local_user(), 'appnet', 'token'); + del_pconfig(local_user(), 'appnet', 'post'); + del_pconfig(local_user(), 'appnet', 'post_by_default'); + del_pconfig(local_user(), 'appnet', 'import'); } if (isset($_POST["clientsecret"])) @@ -204,58 +228,109 @@ function appnet_settings_post(&$a,&$b) { if (isset($_POST["token"]) AND ($_POST["token"] != "")) set_pconfig(local_user(),'appnet','token', $_POST['token']); - set_pconfig(local_user(),'appnet','post',intval($_POST['appnet'])); - set_pconfig(local_user(),'appnet','post_by_default',intval($_POST['appnet_bydefault'])); + set_pconfig(local_user(), 'appnet', 'post', intval($_POST['appnet'])); + set_pconfig(local_user(), 'appnet', 'post_by_default', intval($_POST['appnet_bydefault'])); + set_pconfig(local_user(), 'appnet', 'import', intval($_POST['appnet_import'])); } } function appnet_post_local(&$a,&$b) { - if($b['edit']) return; - if((! local_user()) || (local_user() != $b['uid'])) - return; + if((local_user()) && (local_user() == $b['uid']) && (!$b['private']) && (!$b['parent'])) { + $appnet_post = intval(get_pconfig(local_user(),'appnet','post')); + $appnet_enable = (($appnet_post && x($_REQUEST,'appnet_enable')) ? intval($_REQUEST['appnet_enable']) : 0); - if($b['private'] || $b['parent']) - return; + // if API is used, default to the chosen settings + if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'appnet','post_by_default'))) + $appnet_enable = 1; - $post = intval(get_pconfig(local_user(),'appnet','post')); + if(! $appnet_enable) + return; - $enable = (($post && x($_REQUEST,'appnet_enable')) ? intval($_REQUEST['appnet_enable']) : 0); + if(strlen($b['postopts'])) + $b['postopts'] .= ','; - if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'appnet','post_by_default'))) - $enable = 1; - - if(!$enable) - return; - - if(strlen($b['postopts'])) - $b['postopts'] .= ','; - - $b['postopts'] .= 'appnet'; + $b['postopts'] .= 'appnet'; + } } function appnet_send(&$a,&$b) { logger('appnet_send: invoked for post '.$b['id']." ".$b['app']); - if($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited'])) - return; + if (!get_pconfig($b["uid"],'appnet','import')) { + if($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited'])) + return; + } - if(! strstr($b['postopts'],'appnet')) - return; + if($b['parent'] != $b['id']) { + logger("appnet_send: parameter ".print_r($b, true), LOGGER_DATA); - if($b['parent'] != $b['id']) + // Looking if its a reply to an app.net post + if ((substr($b["parent-uri"], 0, 5) != "adn::") AND (substr($b["extid"], 0, 5) != "adn::") AND (substr($b["thr-parent"], 0, 5) != "adn::")) { + logger("appnet_send: no app.net post ".$b["parent"]); + return; + } + + $r = q("SELECT * FROM item WHERE item.uri = '%s' AND item.uid = %d LIMIT 1", + dbesc($b["thr-parent"]), + intval($b["uid"])); + + if(!count($r)) { + logger("appnet_send: no parent found ".$b["thr-parent"]); + return; + } else { + $iscomment = true; + $orig_post = $r[0]; + } + + $nicknameplain = preg_replace("=https?://alpha.app.net/(.*)=ism", "$1", $orig_post["author-link"]); + $nickname = "@[url=".$orig_post["author-link"]."]".$nicknameplain."[/url]"; + $nicknameplain = "@".$nicknameplain; + + logger("appnet_send: comparing ".$nickname." and ".$nicknameplain." with ".$b["body"], LOGGER_DEBUG); + if ((strpos($b["body"], $nickname) === false) AND (strpos($b["body"], $nicknameplain) === false)) + $b["body"] = $nickname." ".$b["body"]; + + logger("appnet_send: parent found ".print_r($orig_post, true), LOGGER_DATA); + } else { + $iscomment = false; + + if($b['private'] OR !strstr($b['postopts'],'appnet')) + return; + } + + if (($b['verb'] == ACTIVITY_POST) AND $b['deleted']) + appnet_action($a, $b["uid"], substr($orig_post["uri"], 5), "delete"); + + if($b['verb'] == ACTIVITY_LIKE) { + logger("appnet_send: ".print_r($b, true), LOGGER_DEBUG); + logger("appnet_send: parameter 2 ".substr($b["thr-parent"], 5), LOGGER_DEBUG); + if ($b['deleted']) + appnet_action($a, $b["uid"], substr($b["thr-parent"], 5), "unlike"); + else + appnet_action($a, $b["uid"], substr($b["thr-parent"], 5), "like"); + return; + } + + if($b['deleted'] || ($b['created'] !== $b['edited'])) return; $token = get_pconfig($b['uid'],'appnet','token'); if($token) { + + // If it's a repeated message from app.net then do a native repost and exit + if (appnet_is_repost($a, $b['uid'], $b['body'])) + return; + + require_once 'addon/appnet/AppDotNet.php'; - $clientId = get_pconfig(local_user(),'appnet','clientid'); - $clientSecret = get_pconfig(local_user(),'appnet','clientsecret'); + $clientId = get_pconfig($b["uid"],'appnet','clientid'); + $clientSecret = get_pconfig($b["uid"],'appnet','clientsecret'); $app = new AppDotNet($clientId, $clientSecret); $app->setAccessToken($token); @@ -293,6 +368,28 @@ function appnet_send(&$a,&$b) { unlink($tempfile); } + // Adding a link to the original post, if it is a root post + if($b['parent'] == $b['id']) + $data["annotations"][] = array( + "type" => "net.app.core.crosspost", + "value" => array("canonical_url" => $b["plink"]) + ); + + // Adding the original post + $data["annotations"][] = array( + "type" => "com.friendica.post", + "value" => array( + "uri" => $b["uri"], + "title" => $b["title"], + "body" => substr($b["body"], 0, 4000), // To-Do: Better shortening + "tag" => $b["tag"], + "author-name" => $b["author-name"], + "author-link" => $b["author-link"], + "author-avatar" => $b["author-avatar"], + ) + ); + + // To-Do // Alle Links verkürzen @@ -318,14 +415,633 @@ function appnet_send(&$a,&$b) { //print_r($post); $data["entities"]["parse_links"] = true; - $data["entities"]["parse_markdown_links"] = true; + $data["entities"]["parse_markdown_links"] = true; + + if ($iscomment) + $data["reply_to"] = substr($orig_post["uri"], 5); try { $ret = $app->createPost($post["text"], $data); logger("appnet_send: send message ".$b["id"]." result: ".print_r($ret, true), LOGGER_DEBUG); + + if ($iscomment) { + logger('appnet_send: Update extid '.$ret["id"]." for post id ".$b['id']); + q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d", + dbesc("adn::".$ret["id"]), + intval($b['id']) + ); + } } catch (AppDotNetException $e) { logger("appnet_send: Error sending message ".$b["id"]); } } } + +function appnet_action($a, $uid, $pid, $action) { + require_once 'addon/appnet/AppDotNet.php'; + + $token = get_pconfig($uid,'appnet','token'); + $clientId = get_pconfig($uid,'appnet','clientid'); + $clientSecret = get_pconfig($uid,'appnet','clientsecret'); + + $app = new AppDotNet($clientId, $clientSecret); + $app->setAccessToken($token); + + logger("appnet_action '".$action."' ID: ".$pid, LOGGER_DATA); + + try { + switch ($action) { + case "delete": + $result = $app->deletePost($pid); + break; + case "like": + $result = $app->starPost($pid); + break; + case "unlike": + $result = $app->unstarPost($pid); + break; + } + logger("appnet_action '".$action."' send, result: " . print_r($result, true), LOGGER_DEBUG); + } + catch (AppDotNetException $e) { + logger("appnet_action: Error sending action ".$action." pid ".$pid, LOGGER_DEBUG); + } +} + +function appnet_is_repost($a, $uid, $body) { + $body = trim($body); + + // Skip if it isn't a pure repeated messages + // Does it start with a share? + if (strpos($body, "[share") > 0) + return(false); + + // Does it end with a share? + if (strlen($body) > (strrpos($body, "[/share]") + 8)) + return(false); + + $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); + // Skip if there is no shared message in there + if ($body == $attributes) + return(false); + + $link = ""; + preg_match("/link='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $link = $matches[1]; + + preg_match('/link="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $link = $matches[1]; + + $id = preg_replace("=https?://alpha.app.net/(.*)/post/(.*)=ism", "$2", $link); + if ($id == $link) + return(false); + + logger('appnet_is_repost: Reposting id '.$id.' for user '.$uid, LOGGER_DEBUG); + + require_once 'addon/appnet/AppDotNet.php'; + + $token = get_pconfig($uid,'appnet','token'); + $clientId = get_pconfig($uid,'appnet','clientid'); + $clientSecret = get_pconfig($uid,'appnet','clientsecret'); + + $app = new AppDotNet($clientId, $clientSecret); + $app->setAccessToken($token); + + try { + $result = $app->repost($id); + logger('appnet_is_repost: result '.print_r($result, true), LOGGER_DEBUG); + return true; + } + catch (AppDotNetException $e) { + logger('appnet_is_repost: error doing repost', LOGGER_DEBUG); + return false; + } +} + +function appnet_fetchstream($a, $uid) { + require_once("addon/appnet/AppDotNet.php"); + require_once('include/items.php'); + + $token = get_pconfig($uid,'appnet','token'); + $clientId = get_pconfig($uid,'appnet','clientid'); + $clientSecret = get_pconfig($uid,'appnet','clientsecret'); + + $app = new AppDotNet($clientId, $clientSecret); + $app->setAccessToken($token); + + $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1", + intval($uid)); + + if(count($r)) + $me = $r[0]; + else { + logger("appnet_fetchstream: Own contact not found for user ".$uid, LOGGER_DEBUG); + return; + } + + $user = q("SELECT * FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1", + intval($uid) + ); + + if(count($user)) + $user = $user[0]; + else { + logger("appnet_fetchstream: Own user not found for user ".$uid, LOGGER_DEBUG); + return; + } + + $ownid = get_pconfig($uid,'appnet','ownid'); + + // Fetch stream + $param = array("count" => 200, "include_deleted" => false, "include_directed_posts" => true, + "include_html" => false, "include_post_annotations" => true); + + $lastid = get_pconfig($uid, 'appnet', 'laststreamid'); + + if ($lastid <> "") + $param["since_id"] = $lastid; + + try { + $stream = $app->getUserStream($param); + } + catch (AppDotNetException $e) { + logger("appnet_fetchstream: Error fetching stream for user ".$uid); + } + + $stream = array_reverse($stream); + foreach ($stream AS $post) { + $postarray = appnet_createpost($a, $uid, $post, $me, $user, $ownid, true); + + $item = item_store($postarray); + logger('appnet_fetchstream: User '.$uid.' posted stream item '.$item); + + $lastid = $post["id"]; + + if (($item != 0) AND ($postarray['contact-id'] != $me["id"])) { + $r = q("SELECT `thread`.`iid` AS `parent` FROM `thread` + INNER JOIN `item` ON `thread`.`iid` = `item`.`parent` AND `thread`.`uid` = `item`.`uid` + WHERE `item`.`id` = %d AND `thread`.`mention` LIMIT 1", dbesc($item)); + + if (count($r)) { + require_once('include/enotify.php'); + notification(array( + 'type' => NOTIFY_COMMENT, + 'notify_flags' => $user['notify-flags'], + 'language' => $user['language'], + 'to_name' => $user['username'], + 'to_email' => $user['email'], + 'uid' => $user['uid'], + 'item' => $postarray, + 'link' => $a->get_baseurl() . '/display/' . $user['nickname'] . '/' . $item, + 'source_name' => $postarray['author-name'], + 'source_link' => $postarray['author-link'], + 'source_photo' => $postarray['author-avatar'], + 'verb' => ACTIVITY_POST, + 'otype' => 'item', + 'parent' => $r[0]["parent"], + )); + } + } + } + + set_pconfig($uid, 'appnet', 'laststreamid', $lastid); + + // Fetch mentions + $param = array("count" => 200, "include_deleted" => false, "include_directed_posts" => true, + "include_html" => false, "include_post_annotations" => true); + + $lastid = get_pconfig($uid, 'appnet', 'lastmentionid'); + + if ($lastid <> "") + $param["since_id"] = $lastid; + + try { + $mentions = $app->getUserMentions("me", $param); + } + catch (AppDotNetException $e) { + logger("appnet_fetchstream: Error fetching mentions for user ".$uid); + } + + $mentions = array_reverse($mentions); + foreach ($mentions AS $post) { + $postarray = appnet_createpost($a, $uid, $post, $me, $user, $ownid, false); + + if (isset($postarray["id"])) + $item = $postarray["id"]; + elseif (isset($postarray["body"])) { + $item = item_store($postarray); + logger('appnet_fetchstream: User '.$uid.' posted mention item '.$item); + } else + $item = 0; + + $lastid = $post["id"]; + + if (($item != 0) AND ($postarray['contact-id'] != $me["id"])) { + require_once('include/enotify.php'); + notification(array( + 'type' => NOTIFY_TAGSELF, + 'notify_flags' => $user['notify-flags'], + 'language' => $user['language'], + 'to_name' => $user['username'], + 'to_email' => $user['email'], + 'uid' => $user['uid'], + 'item' => $postarray, + 'link' => $a->get_baseurl() . '/display/' . $user['nickname'] . '/' . $item, + 'source_name' => $postarray['author-name'], + 'source_link' => $postarray['author-link'], + 'source_photo' => $postarray['author-avatar'], + 'verb' => ACTIVITY_TAG, + 'otype' => 'item' + )); + } + } + + set_pconfig($uid, 'appnet', 'lastmentionid', $lastid); + + +/* To-Do + $param = array("interaction_actions" => "star"); + $interactions = $app->getMyInteractions($param); + foreach ($interactions AS $interaction) + appnet_dolike($a, $uid, $interaction); +*/ +} + +function appnet_createpost($a, $uid, $post, $me, $user, $ownid, $createuser, $threadcompletion = true) { + require_once('include/items.php'); + + if ($post["machine_only"]) + return; + + if ($post["is_deleted"]) + return; + + $postarray = array(); + $postarray['gravity'] = 0; + $postarray['uid'] = $uid; + $postarray['wall'] = 0; + $postarray['verb'] = ACTIVITY_POST; + $postarray['network'] = dbesc(NETWORK_APPNET); + $postarray['uri'] = "adn::".$post["id"]; + + $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($postarray['uri']), + intval($uid) + ); + + if (count($r)) + return($r[0]); + + $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1", + dbesc($postarray['uri']), + intval($uid) + ); + + if (count($r)) + return($r[0]); + + $postarray['parent-uri'] = "adn::".$post["thread_id"]; + if (isset($post["reply_to"]) AND ($post["reply_to"] != "")) { + $postarray['thr-parent'] = "adn::".$post["reply_to"]; + + // Complete the thread if the parent doesn't exists + if ($threadcompletion) { + $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($postarray['thr-parent']), + intval($uid) + ); + if (!count($r)) { + require_once("addon/appnet/AppDotNet.php"); + + $token = get_pconfig($uid,'appnet','token'); + $clientId = get_pconfig($uid,'appnet','clientid'); + $clientSecret = get_pconfig($uid,'appnet','clientsecret'); + + $app = new AppDotNet($clientId, $clientSecret); + $app->setAccessToken($token); + + $param = array("count" => 200, "include_deleted" => false, "include_directed_posts" => true, + "include_html" => false, "include_post_annotations" => true); + try { + $thread = $app->getPostReplies($post["thread_id"], $param); + } + catch (AppDotNetException $e) { + logger("appnet_createpost: Error fetching thread for user ".$uid); + } + $thread = array_reverse($thread); + foreach ($thread AS $tpost) { + $threadpost = appnet_createpost($a, $uid, $tpost, $me, $user, $ownid, $createuser, false); + $item = item_store($threadpost); + } + } + } + } else + $postarray['thr-parent'] = $postarray['uri']; + + $postarray['plink'] = $post["canonical_url"]; + + if (($post["user"]["id"] != $ownid) OR ($postarray['thr-parent'] == $postarray['uri'])) { + $postarray['owner-name'] = $post["user"]["name"]; + $postarray['owner-link'] = $post["user"]["canonical_url"]; + $postarray['owner-avatar'] = $post["user"]["avatar_image"]["url"]; + $postarray['contact-id'] = appnet_fetchcontact($a, $uid, $post["user"], $me, $createuser); + } else { + $postarray['owner-name'] = $me["name"]; + $postarray['owner-link'] = $me["url"]; + $postarray['owner-avatar'] = $me["thumb"]; + $postarray['contact-id'] = $me["id"]; + } + + $links = array(); + + if (is_array($post["repost_of"])) { + $postarray['author-name'] = $post["repost_of"]["user"]["name"]; + $postarray['author-link'] = $post["repost_of"]["user"]["canonical_url"]; + $postarray['author-avatar'] = $post["repost_of"]["user"]["avatar_image"]["url"]; + + $content = $post["repost_of"]; + } else { + $postarray['author-name'] = $postarray['owner-name']; + $postarray['author-link'] = $postarray['owner-link']; + $postarray['author-avatar'] = $postarray['owner-avatar']; + + $content = $post; + } + + if (is_array($content["entities"])) { + $converted = appnet_expand_entities($a, $content["text"], $content["entities"]); + $postarray['body'] = $converted["body"]; + $postarray['tag'] = $converted["tags"]; + } else + $postarray['body'] = $content["text"]; + + if (is_array($content["annotations"])) + $postarray['body'] = appnet_expand_annotations($a, $postarray['body'], $content["annotations"]); + + if (sizeof($content["entities"]["links"])) + foreach($content["entities"]["links"] AS $link) { + $url = normalise_link($link["url"]); + $links[$url] = $link["url"]; + } + + if (sizeof($content["annotations"])) + foreach($content["annotations"] AS $annotation) { + if ($annotation[type] == "net.app.core.oembed") { + if (isset($annotation["value"]["embeddable_url"])) { + $url = normalise_link($annotation["value"]["embeddable_url"]); + if (isset($links[$url])) + unset($links[$url]); + } + } elseif ($annotation[type] == "com.friendica.post") { + $links = array(); + if (isset($annotation["value"]["embeddable_url"])) + $postarray['title'] = $annotation["value"]["title"]; + + if (isset($annotation["value"]["body"])) + $postarray['body'] = $annotation["value"]["body"]; + + if (isset($annotation["value"]["tag"])) + $postarray['tag'] = $annotation["value"]["tag"]; + + if (isset($annotation["value"]["author-name"])) + $postarray['author-name'] = $annotation["value"]["author-name"]; + + if (isset($annotation["value"]["author-link"])) + $postarray['author-link'] = $annotation["value"]["author-link"]; + + if (isset($annotation["value"]["author-avatar"])) + $postarray['author-avatar'] = $annotation["value"]["author-avatar"]; + } + + } + + if (sizeof($links)) { + $link = array_pop($links); + $url = "[url=".$link."]".$link."[/url]"; + + $removedlink = trim(str_replace($url, "", $postarray['body'])); + + if (($removedlink == "") OR strstr($postarray['body'], $removedlink)) + $postarray['body'] = $removedlink; + + $postarray['body'] .= add_page_info($link); + } + + $postarray['created'] = datetime_convert('UTC','UTC',$post["created_at"]); + $postarray['edited'] = datetime_convert('UTC','UTC',$post["created_at"]); + + $postarray['app'] = $post["source"]["name"]; + + return($postarray); + //print_r($postarray); + //print_r($post); +} + +function appnet_expand_entities($a, $body, $entities) { + + if (!function_exists('substr_unicode')) { + function substr_unicode($str, $s, $l = null) { + return join("", array_slice( + preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY), $s, $l)); + } + } + + $tags_arr = array(); + $replace = array(); + + foreach ($entities["mentions"] AS $mention) { + $url = "@[url=https://alpha.app.net/".rawurlencode($mention["name"])."]".$mention["name"]."[/url]"; + $tags_arr["@".$mention["name"]] = $url; + $replace[$mention["pos"]] = array("pos"=> $mention["pos"], "len"=> $mention["len"], "replace"=> $url); + } + + foreach ($entities["hashtags"] AS $hashtag) { + $url = "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag["name"])."]".$hashtag["name"]."[/url]"; + $tags_arr["#".$hashtag["name"]] = $url; + $replace[$hashtag["pos"]] = array("pos"=> $hashtag["pos"], "len"=> $hashtag["len"], "replace"=> $url); + } + + foreach ($entities["links"] AS $links) { + $url = "[url=".$links["url"]."]".$links["text"]."[/url]"; + $replace[$links["pos"]] = array("pos"=> $links["pos"], "len"=> $links["len"], "replace"=> $url); + } + + + if (sizeof($replace)) { + krsort($replace); + foreach ($replace AS $entity) { + $pre = substr_unicode($body, 0, $entity["pos"]); + $post = substr_unicode($body, $entity["pos"] + $entity["len"]); + + $body = $pre.$entity["replace"].$post; + } + } + + return(array("body" => $body, "tags" => implode($tags_arr, ","))); +} + +function appnet_expand_annotations($a, $body, $annotations) { + foreach ($annotations AS $annotation) { + if ($annotation["value"]["type"] == "photo") { + if (($annotation["value"]["thumbnail_large_url"] != "") AND ($annotation["value"]["url"] != "")) + $body .= "\n[url=".$annotation["value"]["url"]."][img]".$annotation["value"]["thumbnail_large_url"]."[/img][/url]"; + elseif ($annotation["value"]["url"] != "") + $body .= "\n[img]".$annotation["value"]["url"]."[/img]"; + } + } + return $body; +} + +function appnet_fetchcontact($a, $uid, $contact, $me, $create_user) { + $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1", + intval($uid), dbesc("adn::".$contact["id"])); + + if(!count($r) AND !$create_user) + return($me); + + + if (count($r) AND ($r[0]["readonly"] OR $r[0]["blocked"])) { + logger("appnet_fetchcontact: Contact '".$r[0]["nick"]."' is blocked or readonly.", LOGGER_DEBUG); + return(-1); + } + + if(!count($r)) { + // create contact record + q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`, + `name`, `nick`, `photo`, `network`, `rel`, `priority`, + `writable`, `blocked`, `readonly`, `pending` ) + VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0 ) ", + intval($uid), + dbesc(datetime_convert()), + dbesc($contact["canonical_url"]), + dbesc(normalise_link($contact["canonical_url"])), + dbesc($contact["username"]."@app.net"), + dbesc("adn::".$contact["id"]), + dbesc(''), + dbesc("adn::".$contact["id"]), + dbesc($contact["name"]), + dbesc($contact["username"]), + dbesc($contact["avatar_image"]["url"]), + dbesc(NETWORK_APPNET), + intval(CONTACT_IS_FRIEND), + intval(1), + intval(1) + ); + + $r = q("SELECT * FROM `contact` WHERE `alias` = '%s' AND `uid` = %d LIMIT 1", + dbesc("adn::".$contact["id"]), + intval($uid) + ); + + if(! count($r)) + return(false); + + $contact_id = $r[0]['id']; + + $g = q("SELECT def_gid FROM user WHERE uid = %d LIMIT 1", + intval($uid) + ); + + if($g && intval($g[0]['def_gid'])) { + require_once('include/group.php'); + group_add_member($uid,'',$contact_id,$g[0]['def_gid']); + } + + require_once("Photo.php"); + + $photos = import_profile_photo($contact["avatar_image"]["url"],$uid,$contact_id); + + q("UPDATE `contact` SET `photo` = '%s', + `thumb` = '%s', + `micro` = '%s', + `name-date` = '%s', + `uri-date` = '%s', + `avatar-date` = '%s' + WHERE `id` = %d", + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($contact_id) + ); + + } else { + // update profile photos once every two weeks as we have no notification of when they change. + + //$update_photo = (($r[0]['avatar-date'] < datetime_convert('','','now -2 days')) ? true : false); + $update_photo = ($r[0]['avatar-date'] < datetime_convert('','','now -12 hours')); +$update_photo = true; + // check that we have all the photos, this has been known to fail on occasion + + if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro']) || ($update_photo)) { + + logger("appnet_fetchcontact: Updating contact ".$contact["username"], LOGGER_DEBUG); + + require_once("Photo.php"); + + $photos = import_profile_photo($contact["avatar_image"]["url"], $uid, $r[0]['id']); + + q("UPDATE `contact` SET `photo` = '%s', + `thumb` = '%s', + `micro` = '%s', + `name-date` = '%s', + `uri-date` = '%s', + `avatar-date` = '%s', + `url` = '%s', + `nurl` = '%s', + `addr` = '%s', + `name` = '%s', + `nick` = '%s' + WHERE `id` = %d", + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($contact["canonical_url"]), + dbesc(normalise_link($contact["canonical_url"])), + dbesc($contact["username"]."@app.net"), + dbesc($contact["name"]), + dbesc($contact["username"]), + intval($r[0]['id']) + ); + } + } + + return($r[0]["id"]); +} + +function appnet_cron($a,$b) { + $last = get_config('appnet','last_poll'); + + $poll_interval = intval(get_config('appnet','poll_interval')); + if(! $poll_interval) + $poll_interval = APPNET_DEFAULT_POLL_INTERVAL; + + if($last) { + $next = $last + ($poll_interval * 60); + if($next > time()) { + logger('appnet_cron: poll intervall not reached'); + return; + } + } + logger('appnet_cron: cron_start'); + + $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'appnet' AND `k` = 'import' AND `v` = '1' ORDER BY RAND()"); + if(count($r)) { + foreach($r as $rr) { + logger('appnet_cron: importing timeline from user '.$rr['uid']); + appnet_fetchstream($a, $rr["uid"]); + } + } + + logger('appnet_cron: cron_end'); + + set_config('appnet','last_poll', time()); +}