Merge pull request #990 from MrPetovan/task/share-block-guid
[friendica-addons.git/.git] / twitter / twitter.php
index 9aeb0a3..f743d96 100644 (file)
@@ -83,7 +83,6 @@ use Friendica\Model\Item;
 use Friendica\Model\ItemContent;
 use Friendica\Model\ItemURI;
 use Friendica\Model\Tag;
-use Friendica\Model\Term;
 use Friendica\Model\User;
 use Friendica\Protocol\Activity;
 use Friendica\Util\ConfigFileLoader;
@@ -111,29 +110,11 @@ function twitter_install()
        Hook::register('expire'                 , __FILE__, 'twitter_expire');
        Hook::register('prepare_body'           , __FILE__, 'twitter_prepare_body');
        Hook::register('check_item_notification', __FILE__, 'twitter_check_item_notification');
+       Hook::register('probe_detect'           , __FILE__, 'twitter_probe_detect');
        Logger::info("installed twitter");
 }
 
-function twitter_uninstall()
-{
-       Hook::unregister('load_config'            , __FILE__, 'twitter_load_config');
-       Hook::unregister('connector_settings'     , __FILE__, 'twitter_settings');
-       Hook::unregister('connector_settings_post', __FILE__, 'twitter_settings_post');
-       Hook::unregister('hook_fork'              , __FILE__, 'twitter_hook_fork');
-       Hook::unregister('post_local'             , __FILE__, 'twitter_post_local');
-       Hook::unregister('notifier_normal'        , __FILE__, 'twitter_post_hook');
-       Hook::unregister('jot_networks'           , __FILE__, 'twitter_jot_nets');
-       Hook::unregister('cron'                   , __FILE__, 'twitter_cron');
-       Hook::unregister('follow'                 , __FILE__, 'twitter_follow');
-       Hook::unregister('expire'                 , __FILE__, 'twitter_expire');
-       Hook::unregister('prepare_body'           , __FILE__, 'twitter_prepare_body');
-       Hook::unregister('check_item_notification', __FILE__, 'twitter_check_item_notification');
-
-       // old setting - remove only
-       Hook::unregister('post_local_end'     , __FILE__, 'twitter_post_hook');
-       Hook::unregister('addon_settings'     , __FILE__, 'twitter_settings');
-       Hook::unregister('addon_settings_post', __FILE__, 'twitter_settings_post');
-}
+// Hook functions
 
 function twitter_load_config(App $a, ConfigFileLoader $loader)
 {
@@ -182,14 +163,14 @@ function twitter_follow(App $a, array &$contact)
        $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
        $connection->post('friendships/create', ['screen_name' => $nickname]);
 
-       twitter_fetchuser($a, $uid, $nickname);
+       $user = twitter_fetchuser($nickname);
 
-       $r = q("SELECT name,nick,url,addr,batch,notify,poll,request,confirm,poco,photo,priority,network,alias,pubkey
-               FROM `contact` WHERE `uid` = %d AND `nick` = '%s'",
-                               intval($uid),
-                               DBA::escape($nickname));
-       if (DBA::isResult($r)) {
-               $contact["contact"] = $r[0];
+       $contact_id = twitter_fetch_contact($uid, $user, true);
+
+       $contact = Contact::getById($contact_id, ['name', 'nick', 'url', 'addr', 'batch', 'notify', 'poll', 'request', 'confirm', 'poco', 'photo', 'priority', 'network', 'alias', 'pubkey']);
+
+       if (DBA::isResult($contact)) {
+               $contact["contact"] = $contact;
        }
 }
 
@@ -472,6 +453,33 @@ function twitter_post_local(App $a, array &$b)
        $b['postopts'] .= 'twitter';
 }
 
+function twitter_probe_detect(App $a, array &$hookData)
+{
+       // Don't overwrite an existing result
+       if ($hookData['result']) {
+               return;
+       }
+
+       // Avoid a lookup for the wrong network
+       if (!in_array($hookData['network'], ['', Protocol::TWITTER])) {
+               return;
+       }
+
+       if (preg_match('=(.*)@twitter.com=i', $hookData['uri'], $matches)) {
+               $nick = $matches[1];
+       } elseif (preg_match('=https?://(?:mobile\.)?twitter.com/(.*)=i', $hookData['uri'], $matches)) {
+               $nick = $matches[1];
+       } else {
+               return;
+       }
+
+       $user = twitter_fetchuser($nick);
+
+       if ($user) {
+               $hookData['result'] = twitter_user_to_contact($user);
+       }
+}
+
 function twitter_action(App $a, $uid, $pid, $action)
 {
        $ckey = DI::config()->get('twitter', 'consumerkey');
@@ -483,24 +491,31 @@ function twitter_action(App $a, $uid, $pid, $action)
 
        $post = ['id' => $pid];
 
-       Logger::log("twitter_action '" . $action . "' ID: " . $pid . " data: " . print_r($post, true), Logger::DATA);
+       Logger::debug('before action', ['action' => $action, 'pid' => $pid, 'data' => $post]);
 
        switch ($action) {
-               case "delete":
+               case 'delete':
                        // To-Do: $result = $connection->post('statuses/destroy', $post);
                        $result = [];
                        break;
-               case "like":
+               case 'like':
                        $result = $connection->post('favorites/create', $post);
+                       if ($connection->getLastHttpCode() != 200) {
+                               Logger::error('Unable to create favorite', ['result' => $result]);
+                       }
                        break;
-               case "unlike":
+               case 'unlike':
                        $result = $connection->post('favorites/destroy', $post);
+                       if ($connection->getLastHttpCode() != 200) {
+                               Logger::error('Unable to destroy favorite', ['result' => $result]);
+                       }
                        break;
                default:
-                       Logger::log('Unhandled action ' . $action, Logger::DEBUG);
+                       Logger::warning('Unhandled action', ['action' => $action]);
                        $result = [];
        }
-       Logger::log("twitter_action '" . $action . "' send, result: " . print_r($result, true), Logger::DEBUG);
+
+       Logger::info('after action', ['action' => $action, 'result' => $result]);
 }
 
 function twitter_post_hook(App $a, array &$b)
@@ -624,7 +639,7 @@ function twitter_post_hook(App $a, array &$b)
 
                $b['body'] = twitter_update_mentions($b['body']);
 
-               $msgarr = ItemContent::getPlaintextPost($b, $max_char, true, 8);
+               $msgarr = ItemContent::getPlaintextPost($b, $max_char, true, BBCode::TWITTER);
                Logger::info('Got plaintext', ['id' => $b['id'], 'message' => $msgarr]);
                $msg = $msgarr["text"];
 
@@ -854,7 +869,7 @@ function twitter_prepare_body(App $a, array &$b)
                        }
                }
 
-               $msgarr = ItemContent::getPlaintextPost($item, $max_char, true, 8);
+               $msgarr = ItemContent::getPlaintextPost($item, $max_char, true, BBCode::TWITTER);
                $msg = $msgarr["text"];
 
                if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
@@ -896,13 +911,12 @@ function twitter_do_mirrorpost(App $a, $uid, $post)
                        return [];
                }
 
-               $datarray['body'] = "\n" . share_header(
+               $datarray['body'] = "\n" . BBCode::getShareOpeningTag(
                        $item['author-name'],
                        $item['author-link'],
                        $item['author-avatar'],
-                       '',
-                       $item['created'],
-                       $item['plink']
+                       $item['plink'],
+                       $item['created']
                );
 
                $datarray['body'] .= $item['body'] . '[/share]';
@@ -1039,50 +1053,79 @@ function twitter_get_relation($uid, $target, $contact = [])
 
        try {
                $status = $connection->get('friendships/show', $parameters);
-       } catch (TwitterOAuthException $e) {
-               Logger::info('Error fetching friendship status', ['user' => $uid, 'target' => $target, 'message' => $e->getMessage()]);
-               return $relation;
-       }
+               if ($connection->getLastHttpCode() !== 200) {
+                       throw new Exception($status->errors[0]->message ?? 'HTTP response code ' . $connection->getLastHttpCode(), $status->errors[0]->code ?? $connection->getLastHttpCode());
+               }
 
-       $following = $status->relationship->source->following;
-       $followed = $status->relationship->source->followed_by;
+               $following = $status->relationship->source->following;
+               $followed = $status->relationship->source->followed_by;
+
+               if ($following && !$followed) {
+                       $relation = Contact::SHARING;
+               } elseif (!$following && $followed) {
+                       $relation = Contact::FOLLOWER;
+               } elseif ($following && $followed) {
+                       $relation = Contact::FRIEND;
+               } elseif (!$following && !$followed) {
+                       $relation = 0;
+               }
 
-       if ($following && !$followed) {
-               $relation = Contact::SHARING;
-       } elseif (!$following && $followed) {
-               $relation = Contact::FOLLOWER;
-       } elseif ($following && $followed) {
-               $relation = Contact::FRIEND;
-       } elseif (!$following && !$followed) {
-               $relation = 0;
+               Logger::info('Fetched friendship relation', ['user' => $uid, 'target' => $target, 'relation' => $relation]);
+       } catch (Throwable $e) {
+               Logger::error('Error fetching friendship status', ['user' => $uid, 'target' => $target, 'message' => $e->getMessage()]);
        }
 
-       Logger::info('Fetched friendship relation', ['user' => $uid, 'target' => $target, 'relation' => $relation]);
-
        return $relation;
 }
 
-function twitter_fetch_contact($uid, $data, $create_user)
+/**
+ * @param $data
+ * @return array
+ */
+function twitter_user_to_contact($data)
 {
        if (empty($data->id_str)) {
-               return -1;
+               return [];
        }
 
-       $avatar = twitter_fix_avatar($data->profile_image_url_https);
-       $url = "https://twitter.com/" . $data->screen_name;
-       $addr = $data->screen_name . "@twitter.com";
+       $baseurl = 'https://twitter.com';
+       $url = $baseurl . '/' . $data->screen_name;
+       $addr = $data->screen_name . '@twitter.com';
+
+       $fields = [
+               'url'      => $url,
+               'network'  => Protocol::TWITTER,
+               'alias'    => 'twitter::' . $data->id_str,
+               'baseurl'  => $baseurl,
+               'name'     => $data->name,
+               'nick'     => $data->screen_name,
+               'addr'     => $addr,
+               'location' => $data->location,
+               'about'    => $data->description,
+               'photo'    => twitter_fix_avatar($data->profile_image_url_https),
+       ];
+
+       return $fields;
+}
 
-       $fields = ['url' => $url, 'network' => Protocol::TWITTER,
-               'alias' => 'twitter::' . $data->id_str,
-               'name' => $data->name, 'nick' => $data->screen_name, 'addr' => $addr,
-                'location' => $data->location, 'about' => $data->description];
+function twitter_fetch_contact($uid, $data, $create_user)
+{
+       $fields = twitter_user_to_contact($data);
+
+       if (empty($fields)) {
+               return -1;
+       }
+
+       // photo comes from twitter_user_to_contact but shouldn't be saved directly in the contact row
+       $avatar = $fields['photo'];
+       unset($fields['photo']);
 
        // Update the public contact
        $pcontact = DBA::selectFirst('contact', ['id'], ['uid' => 0, 'alias' => "twitter::" . $data->id_str]);
        if (DBA::isResult($pcontact)) {
                $cid = $pcontact['id'];
        } else {
-               $cid = Contact::getIdForURL($url, 0, true, $fields);
+               $cid = Contact::getIdForURL($fields['url'], 0, true, $fields);
        }
 
        if (!empty($cid)) {
@@ -1102,7 +1145,7 @@ function twitter_fetch_contact($uid, $data, $create_user)
                // create contact record
                $fields['uid'] = $uid;
                $fields['created'] = DateTimeFormat::utcNow();
-               $fields['nurl'] = Strings::normaliseLink($url);
+               $fields['nurl'] = Strings::normaliseLink($fields['url']);
                $fields['poll'] = 'twitter::' . $data->id_str;
                $fields['rel'] = $relation;
                $fields['priority'] = 1;
@@ -1161,48 +1204,31 @@ function twitter_fetch_contact($uid, $data, $create_user)
        return $contact_id;
 }
 
-function twitter_fetchuser(App $a, $uid, $screen_name = "", $user_id = "")
+/**
+ * @param string $screen_name
+ * @return stdClass|null
+ * @throws Exception
+ */
+function twitter_fetchuser($screen_name)
 {
        $ckey = DI::config()->get('twitter', 'consumerkey');
        $csecret = DI::config()->get('twitter', 'consumersecret');
-       $otoken = DI::pConfig()->get($uid, 'twitter', 'oauthtoken');
-       $osecret = DI::pConfig()->get($uid, 'twitter', 'oauthsecret');
-
-       $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
-               intval($uid));
-
-       if (DBA::isResult($r)) {
-               $self = $r[0];
-       } else {
-               return;
-       }
-
-       $parameters = [];
-
-       if ($screen_name != "") {
-               $parameters["screen_name"] = $screen_name;
-       }
 
-       if ($user_id != "") {
-               $parameters["user_id"] = $user_id;
-       }
-
-       // Fetching user data
-       $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
        try {
+               // Fetching user data
+               $connection = new TwitterOAuth($ckey, $csecret);
+               $parameters = ['screen_name' => $screen_name];
                $user = $connection->get('users/show', $parameters);
        } catch (TwitterOAuthException $e) {
-               Logger::log('twitter_fetchuser: Error fetching user ' . $uid . ': ' . $e->getMessage());
-               return;
+               Logger::log('twitter_fetchuser: Error fetching user ' . $screen_name . ': ' . $e->getMessage());
+               return null;
        }
 
        if (!is_object($user)) {
-               return;
+               return null;
        }
 
-       $contact_id = twitter_fetch_contact($uid, $user, true);
-
-       return $contact_id;
+       return $user;
 }
 
 /**
@@ -1224,14 +1250,12 @@ function twitter_expand_entities($body, stdClass $status, $picture)
 {
        $plain = $body;
 
-       $tags = [];
        $taglist = [];
 
        $replacementList = [];
 
        foreach ($status->entities->hashtags AS $hashtag) {
                $replace = '#[url=' . DI::baseUrl()->get() . '/search?tag=' . $hashtag->text . ']' . $hashtag->text . '[/url]';
-               $tags['#' . $hashtag->text] = $replace;
                $taglist['#' . $hashtag->text] = ['#', $hashtag->text, ''];
 
                $replacementList[$hashtag->indices[0]] = [
@@ -1242,7 +1266,6 @@ function twitter_expand_entities($body, stdClass $status, $picture)
 
        foreach ($status->entities->user_mentions AS $mention) {
                $replace = '@[url=https://twitter.com/' . rawurlencode($mention->screen_name) . ']' . $mention->screen_name . '[/url]';
-               $tags['@' . $mention->screen_name] = $replace;
                $taglist['@' . $mention->screen_name] = ['@', $mention->screen_name, 'https://twitter.com/' . rawurlencode($mention->screen_name)];
 
                $replacementList[$mention->indices[0]] = [
@@ -1344,7 +1367,7 @@ function twitter_expand_entities($body, stdClass $status, $picture)
                }
        }
 
-       return ['body' => $body, 'tags' => $tags, 'plain' => $plain, 'taglist' => $taglist];
+       return ['body' => $body, 'plain' => $plain, 'taglist' => $taglist];
 }
 
 /**
@@ -1429,6 +1452,19 @@ function twitter_media_entities($post, array &$postarray)
        return '';
 }
 
+/**
+ * Undocumented function
+ *
+ * @param App $a
+ * @param integer $uid User ID
+ * @param object $post Incoming Twitter post
+ * @param array $self
+ * @param bool $create_user Should users be created?
+ * @param bool $only_existing_contact Only import existing contacts if set to "true"
+ * @param bool $noquote
+ * @param integer $uriid URI Id used to store tags. 0 = create a new one; -1 = don't store tags for this post.
+ * @return array item array
+ */
 function twitter_createpost(App $a, $uid, $post, array $self, $create_user, $only_existing_contact, $noquote, int $uriid = 0)
 {
        $postarray = [];
@@ -1544,7 +1580,6 @@ function twitter_createpost(App $a, $uid, $post, array $self, $create_user, $onl
 
        $converted = twitter_expand_entities($postarray['body'], $post, $picture);
        $postarray['body'] = $converted['body'];
-       $postarray['tag'] = implode(',', $converted['tags']);
        $postarray['created'] = DateTimeFormat::utc($post->created_at);
        $postarray['edited'] = DateTimeFormat::utc($post->created_at);
 
@@ -1597,23 +1632,26 @@ function twitter_createpost(App $a, $uid, $post, array $self, $create_user, $onl
                }
        }
 
-       if (!empty($post->quoted_status) && !$noquote) {
-               $quoted = twitter_createpost($a, $uid, $post->quoted_status, $self, false, false, true, $uriid);
-
-               if (!empty($quoted['body'])) {
-                       $postarray['body'] .= "\n" . share_header(
-                               $quoted['author-name'],
-                               $quoted['author-link'],
-                               $quoted['author-avatar'],
-                               "",
-                               $quoted['created'],
-                               $quoted['plink']
-                       );
-
-                       $postarray['body'] .= $quoted['body'] . '[/share]';
-               } else {
-                       // Quoted post author is blocked/ignored, so we just provide the link to avoid removing quote context.
+       if (!empty($post->quoted_status)) {
+               if ($noquote) {
+                       // To avoid recursive share blocks we just provide the link to avoid removing quote context.
                        $postarray['body'] .= "\n\nhttps://twitter.com/" . $post->quoted_status->user->screen_name . "/status/" . $post->quoted_status->id_str;
+               } else {
+                       $quoted = twitter_createpost($a, $uid, $post->quoted_status, $self, false, false, true, $uriid);
+                       if (!empty($quoted['body'])) {
+                               $postarray['body'] .= "\n" . BBCode::getShareOpeningTag(
+                                               $quoted['author-name'],
+                                               $quoted['author-link'],
+                                               $quoted['author-avatar'],
+                                               $quoted['plink'],
+                                               $quoted['created']
+                                       );
+
+                               $postarray['body'] .= $quoted['body'] . '[/share]';
+                       } else {
+                               // Quoted post author is blocked/ignored, so we just provide the link to avoid removing quote context.
+                               $postarray['body'] .= "\n\nhttps://twitter.com/" . $post->quoted_status->user->screen_name . "/status/" . $post->quoted_status->id_str;
+                       }
                }
        }
 
@@ -1805,7 +1843,7 @@ function twitter_fetchhometimeline(App $a, $uid)
                                }
                        }
 
-                       $item = Item::insert($postarray, false, $notify);
+                       $item = Item::insert($postarray, $notify);
                        $postarray["id"] = $item;
 
                        Logger::log('User ' . $uid . ' posted home timeline item ' . $item);
@@ -1888,7 +1926,7 @@ function twitter_fetch_own_contact(App $a, $uid)
                // Fetching user data
                // get() may throw TwitterOAuthException, but we will catch it later
                $user = $connection->get('account/verify_credentials');
-               if (empty($user) || empty($user->id_str)) {
+               if (empty($user->id_str)) {
                        return false;
                }