]> git.mxchange.org Git - friendica-addons.git/blobdiff - twitter/twitter.php
ItemContent is replaced
[friendica-addons.git] / twitter / twitter.php
index d07021a8332d224a70e35c95319a65f615286712..e1265d5d3cb5b5b8c311b7ee888eb5904dfa078f 100644 (file)
@@ -67,6 +67,7 @@ use Abraham\TwitterOAuth\TwitterOAuthException;
 use Codebird\Codebird;
 use Friendica\App;
 use Friendica\Content\OEmbed;
+use Friendica\Content\PageInfo;
 use Friendica\Content\Text\BBCode;
 use Friendica\Content\Text\Plaintext;
 use Friendica\Core\Hook;
@@ -79,17 +80,15 @@ use Friendica\DI;
 use Friendica\Model\Contact;
 use Friendica\Model\Conversation;
 use Friendica\Model\Group;
-use Friendica\Model\GServer;
 use Friendica\Model\Item;
-use Friendica\Model\ItemContent;
 use Friendica\Model\ItemURI;
+use Friendica\Model\Post;
 use Friendica\Model\Tag;
 use Friendica\Model\User;
 use Friendica\Protocol\Activity;
 use Friendica\Util\ConfigFileLoader;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Images;
-use Friendica\Util\Network;
 use Friendica\Util\Strings;
 
 require_once __DIR__ . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
@@ -111,29 +110,12 @@ 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');
+       Hook::register('parse_link'             , __FILE__, 'twitter_parse_link');
        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 +164,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;
        }
 }
 
@@ -259,9 +241,9 @@ function twitter_settings_post(App $a)
                                DI::pConfig()->set(local_user(), 'twitter', 'oauthsecret', $token['oauth_token_secret']);
                                DI::pConfig()->set(local_user(), 'twitter', 'post', 1);
                        } catch(Exception $e) {
-                               info($e->getMessage());
+                               notice($e->getMessage());
                        } catch(TwitterOAuthException $e) {
-                               info($e->getMessage());
+                               notice($e->getMessage());
                        }
                        //  reload the Addon Settings page, if we don't do it see Bug #42
                        DI::baseUrl()->redirect('settings/connectors');
@@ -277,8 +259,6 @@ function twitter_settings_post(App $a)
                        if (!intval($_POST['twitter-mirror'])) {
                                DI::pConfig()->delete(local_user(), 'twitter', 'lastid');
                        }
-
-                       info(DI::l10n()->t('Twitter settings updated.') . EOL);
                }
        }
 }
@@ -369,8 +349,9 @@ function twitter_settings(App $a, &$s)
                                } else {
                                        $s .= '<div id="twitter-info" >
                                        <p>Invalid Twitter info</p>
-                               </div>';
-                                       Logger::info('Invalid twitter info (verify credentials).', ['auth' => TwitterOAuth::class]);
+                                       <button type="submit" name="twitter-disconnect" value="1">' . DI::l10n()->t('Disconnect') . '</button>
+                                       </div>';
+                                       Logger::notice('Invalid twitter info (verify credentials).', ['auth' => TwitterOAuth::class]);
                                }
                                $s .= '<div class="clear"></div>';
 
@@ -417,19 +398,19 @@ function twitter_hook_fork(App $a, array &$b)
        }
 
        // if post comes from twitter don't send it back
-       if ($post['extid'] == Protocol::TWITTER) {
+       if (($post['extid'] == Protocol::TWITTER) || twitter_get_id($post['extid'])) {
                $b['execute'] = false;
                return;
        }
 
-       if ($post['app'] == 'Twitter') {
+       if (substr($post['app'], 0, 7) == 'Twitter') {
                $b['execute'] = false;
                return;
        }
 
        if (DI::pConfig()->get($post['uid'], 'twitter', 'import')) {
                // Don't fork if it isn't a reply to a twitter post
-               if (($post['parent'] != $post['id']) && !Item::exists(['id' => $post['parent'], 'network' => Protocol::TWITTER])) {
+               if (($post['parent'] != $post['id']) && !Post::exists(['id' => $post['parent'], 'network' => Protocol::TWITTER])) {
                        Logger::notice('No twitter parent found', ['item' => $post['id']]);
                        $b['execute'] = false;
                        return;
@@ -472,8 +453,39 @@ 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('=([^@]+)@(?:mobile\.)?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)
 {
+       if (empty($pid)) {
+               return;
+       }
+
        $ckey = DI::config()->get('twitter', 'consumerkey');
        $csecret = DI::config()->get('twitter', 'consumersecret');
        $otoken = DI::pConfig()->get($uid, 'twitter', 'oauthtoken');
@@ -483,24 +495,45 @@ 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::warning('Unable to create favorite', ['result' => $result]);
+                       }
                        break;
-               case "unlike":
+               case 'unlike':
                        $result = $connection->post('favorites/destroy', $post);
+                       if ($connection->getLastHttpCode() != 200) {
+                               Logger::warning('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_get_id(string $uri)
+{
+       if ((substr($uri, 0, 9) != 'twitter::') || (strlen($uri) <= 9)) {
+               return 0;
+       }
+
+       $id = substr($uri, 9);
+       if (!is_numeric($id)) {
+               return 0;
+       }
+
+       return (int)$id;
 }
 
 function twitter_post_hook(App $a, array &$b)
@@ -511,41 +544,38 @@ function twitter_post_hook(App $a, array &$b)
                return;
        }
 
+       $thr_parent = null;
+
        if ($b['parent'] != $b['id']) {
-               Logger::log("twitter_post_hook: parameter " . print_r($b, true), Logger::DATA);
+               Logger::debug('Got comment', ['item' => $b]);
 
                // Looking if its a reply to a twitter post
-               if ((substr($b["parent-uri"], 0, 9) != "twitter::")
-                       && (substr($b["extid"], 0, 9) != "twitter::")
-                       && (substr($b["thr-parent"], 0, 9) != "twitter::"))
-               {
-                       Logger::log("twitter_post_hook: no twitter post " . $b["parent"]);
+               if (!twitter_get_id($b["parent-uri"]) &&
+                       !twitter_get_id($b["extid"]) &&
+                       !twitter_get_id($b["thr-parent"])) {
+                       Logger::info('No twitter post', ['parent' => $b["parent"]]);
                        return;
                }
 
                $condition = ['uri' => $b["thr-parent"], 'uid' => $b["uid"]];
-               $orig_post = Item::selectFirst([], $condition);
-               if (!DBA::isResult($orig_post)) {
-                       Logger::log("twitter_post_hook: no parent found " . $b["thr-parent"]);
+               $thr_parent = Post::selectFirst(['uri', 'extid', 'author-link', 'author-nick', 'author-network'], $condition);
+               if (!DBA::isResult($thr_parent)) {
+                       Logger::warning('No parent found', ['thr-parent' => $b["thr-parent"]]);
                        return;
-               } else {
-                       $iscomment = true;
                }
 
+               if ($thr_parent['author-network'] == Protocol::TWITTER) {
+                       $nickname = '@[url=' . $thr_parent['author-link'] . ']' . $thr_parent['author-nick'] . '[/url]';
+                       $nicknameplain = '@' . $thr_parent['author-nick'];
 
-               $nicknameplain = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $orig_post["author-link"]);
-               $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nicknameplain . "[/url]";
-               $nicknameplain = "@" . $nicknameplain;
-
-               Logger::log("twitter_post_hook: comparing " . $nickname . " and " . $nicknameplain . " with " . $b["body"], Logger::DEBUG);
-               if ((strpos($b["body"], $nickname) === false) && (strpos($b["body"], $nicknameplain) === false)) {
-                       $b["body"] = $nickname . " " . $b["body"];
+                       Logger::info('Comparing', ['nickname' => $nickname, 'nicknameplain' => $nicknameplain, 'body' => $b["body"]]);
+                       if ((strpos($b["body"], $nickname) === false) && (strpos($b["body"], $nicknameplain) === false)) {
+                               $b["body"] = $nickname . " " . $b["body"];
+                       }
                }
 
-               Logger::log("twitter_post_hook: parent found " . print_r($orig_post, true), Logger::DATA);
+               Logger::debug('Parent found', ['parent' => $thr_parent]);
        } else {
-               $iscomment = false;
-
                if ($b['private'] || !strstr($b['postopts'], 'twitter')) {
                        return;
                }
@@ -559,15 +589,26 @@ function twitter_post_hook(App $a, array &$b)
        }
 
        if (($b['verb'] == Activity::POST) && $b['deleted']) {
-               twitter_action($a, $b["uid"], substr($orig_post["uri"], 9), "delete");
+               twitter_action($a, $b['uid'], twitter_get_id($thr_parent['uri']), 'delete');
        }
 
        if ($b['verb'] == Activity::LIKE) {
-               Logger::log("twitter_post_hook: parameter 2 " . substr($b["thr-parent"], 9), Logger::DEBUG);
+               Logger::info('Like', ['uid' => $b['uid'], 'id' => twitter_get_id($b["thr-parent"])]);
                if ($b['deleted']) {
-                       twitter_action($a, $b["uid"], substr($b["thr-parent"], 9), "unlike");
+                       twitter_action($a, $b["uid"], twitter_get_id($b["thr-parent"]), "unlike");
                } else {
-                       twitter_action($a, $b["uid"], substr($b["thr-parent"], 9), "like");
+                       twitter_action($a, $b["uid"], twitter_get_id($b["thr-parent"]), "like");
+               }
+
+               return;
+       }
+
+       if ($b['verb'] == Activity::ANNOUNCE) {
+               Logger::info('Retweet', ['uid' => $b['uid'], 'id' => twitter_get_id($b["thr-parent"])]);
+               if ($b['deleted']) {
+                       twitter_action($a, $b['uid'], twitter_get_id($thr_parent['extid']), 'delete');
+               } else {
+                       twitter_retweet($b["uid"], twitter_get_id($b["thr-parent"]));
                }
 
                return;
@@ -578,7 +619,7 @@ function twitter_post_hook(App $a, array &$b)
        }
 
        // if post comes from twitter don't send it back
-       if ($b['extid'] == Protocol::TWITTER) {
+       if (($b['extid'] == Protocol::TWITTER) || twitter_get_id($b['extid'])) {
                return;
        }
 
@@ -596,7 +637,7 @@ function twitter_post_hook(App $a, array &$b)
        $osecret = DI::pConfig()->get($b['uid'], 'twitter', 'oauthsecret');
 
        if ($ckey && $csecret && $otoken && $osecret) {
-               Logger::log('twitter: we have customer key and oauth stuff, going to send.', Logger::DEBUG);
+               Logger::info('We have customer key and oauth stuff, going to send.');
 
                // If it's a repeated message from twitter then do a native retweet and exit
                if (twitter_is_retweet($a, $b['uid'], $b['body'])) {
@@ -624,7 +665,7 @@ function twitter_post_hook(App $a, array &$b)
 
                $b['body'] = twitter_update_mentions($b['body']);
 
-               $msgarr = ItemContent::getPlaintextPost($b, $max_char, true, BBCode::TWITTER);
+               $msgarr = Plaintext::getPost($b, $max_char, true, BBCode::TWITTER);
                Logger::info('Got plaintext', ['id' => $b['id'], 'message' => $msgarr]);
                $msg = $msgarr["text"];
 
@@ -632,17 +673,13 @@ function twitter_post_hook(App $a, array &$b)
                        $msg = Plaintext::shorten($msgarr["title"], $max_char - 50);
                }
 
-               if (!empty($msgarr['url']) && ($msgarr['url'] == $b['plink']) && !empty($msgarr['images']) && (count($msgarr['images']) <= 4)) {
-                       $url_added = false;
-               } elseif (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
-                       $msg .= "\n" . $msgarr["url"];
-                       $url_added = true;
-               } else {
-                       $url_added = false;
+               // Add the link to the body if the type isn't a photo or there are more than 4 images in the post
+               if (!empty($msgarr['url']) && (($msgarr['type'] != 'photo') || empty($msgarr['images']) || (count($msgarr['images']) > 4))) {
+                       $msg .= "\n" . $msgarr['url'];
                }
 
                if (empty($msg)) {
-                       Logger::info('Empty message', ['id' => $b['id']]);
+                       Logger::notice('Empty message', ['id' => $b['id']]);
                        return;
                }
 
@@ -658,7 +695,7 @@ function twitter_post_hook(App $a, array &$b)
                                                continue;
                                        }
 
-                                       $img_str = Network::fetchUrl($image['url']);
+                                       $img_str = DI::httpRequest()->fetch($image['url']);
 
                                        $tempfile = tempnam(get_temppath(), 'cache');
                                        file_put_contents($tempfile, $img_str);
@@ -678,7 +715,8 @@ function twitter_post_hook(App $a, array &$b)
                                                        Logger::info('Metadata create', ['id' => $b['id'], 'data' => $data, 'return' => json_encode($ret)]);
                                                }
                                        } else {
-                                               throw new Exception('Failed upload', ['id' => $b['id'], 'image' => $image['url']]);
+                                               Logger::error('Failed upload', ['id' => $b['id'], 'image' => $image['url']]);
+                                               throw new Exception('Failed upload of ' . $image['url']);
                                        }
                                }
                                $post['media_ids'] = implode(',', $media_ids);
@@ -686,14 +724,14 @@ function twitter_post_hook(App $a, array &$b)
                                        unset($post['media_ids']);
                                }
                        } catch (Exception $e) {
-                               Logger::info('Exception when trying to send to Twitter', ['id' => $b['id'], 'message' => $e->getMessage()]);
+                               Logger::warning('Exception when trying to send to Twitter', ['id' => $b['id'], 'message' => $e->getMessage()]);
                        }
                }
 
                $post['status'] = $msg;
 
-               if ($iscomment) {
-                       $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
+               if ($thr_parent) {
+                       $post['in_reply_to_status_id'] = twitter_get_id($thr_parent['uri']);
                }
 
                $url = 'statuses/update';
@@ -705,10 +743,10 @@ function twitter_post_hook(App $a, array &$b)
                }
 
                if (!empty($result->errors)) {
-                       Logger::info('Send to Twitter failed', ['id' => $b['id'], 'error' => $result->errors]);
+                       Logger::error('Send to Twitter failed', ['id' => $b['id'], 'error' => $result->errors]);
                        Worker::defer();
-               } elseif ($iscomment) {
-                       Logger::info('Update extid', ['id' => $b['id'], 'extid' => $result->id_str]);
+               } elseif ($thr_parent) {
+                       Logger::notice('Post send, updating extid', ['id' => $b['id'], 'extid' => $result->id_str]);
                        Item::update(['extid' => "twitter::" . $result->id_str], ['id' => $b['id']]);
                }
        }
@@ -720,7 +758,6 @@ function twitter_addon_admin_post(App $a)
        $consumersecret = !empty($_POST['consumersecret']) ? Strings::escapeTags(trim($_POST['consumersecret'])) : '';
        DI::config()->set('twitter', 'consumerkey', $consumerkey);
        DI::config()->set('twitter', 'consumersecret', $consumersecret);
-       info(DI::l10n()->t('Settings updated.') . EOL);
 }
 
 function twitter_addon_admin(App $a, &$o)
@@ -811,14 +848,18 @@ function twitter_expire(App $a)
                return;
        }
 
-       $r = Item::select(['id', 'guid'], ['deleted' => true, 'network' => Protocol::TWITTER]);
-       while ($row = DBA::fetch($r)) {
+       Logger::notice('Start deleting expired posts');
+
+       $r = Post::select(['id', 'guid'], ['deleted' => true, 'network' => Protocol::TWITTER]);
+       while ($row = Post::fetch($r)) {
                Logger::info('[twitter] Delete expired item', ['id' => $row['id'], 'guid' => $row['guid'], 'callstack' => \Friendica\Core\System::callstack()]);
                DBA::delete('item', ['id' => $row['id']]);
        }
        DBA::close($r);
 
-       Logger::notice('twitter_expire: expire_start');
+       Logger::notice('End deleting expired posts');
+
+       Logger::notice('Start expiry');
 
        $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'import' AND `v` = '1' ORDER BY RAND()");
        if (DBA::isResult($r)) {
@@ -828,7 +869,7 @@ function twitter_expire(App $a)
                }
        }
 
-       Logger::notice('twitter_expire: expire_end');
+       Logger::notice('End expiry');
 }
 
 function twitter_prepare_body(App $a, array &$b)
@@ -843,7 +884,7 @@ function twitter_prepare_body(App $a, array &$b)
                $item["plink"] = DI::baseUrl()->get() . "/display/" . $item["guid"];
 
                $condition = ['uri' => $item["thr-parent"], 'uid' => local_user()];
-               $orig_post = Item::selectFirst(['author-link'], $condition);
+               $orig_post = Post::selectFirst(['author-link'], $condition);
                if (DBA::isResult($orig_post)) {
                        $nicknameplain = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $orig_post["author-link"]);
                        $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nicknameplain . "[/url]";
@@ -854,7 +895,7 @@ function twitter_prepare_body(App $a, array &$b)
                        }
                }
 
-               $msgarr = ItemContent::getPlaintextPost($item, $max_char, true, BBCode::TWITTER);
+               $msgarr = Plaintext::getPost($item, $max_char, true, BBCode::TWITTER);
                $msg = $msgarr["text"];
 
                if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
@@ -869,6 +910,87 @@ function twitter_prepare_body(App $a, array &$b)
        }
 }
 
+/**
+ * Parse Twitter status URLs since Twitter removed OEmbed
+ *
+ * @param App   $a
+ * @param array $b Expected format:
+ *                 [
+ *                      'url' => [URL to parse],
+ *                      'format' => 'json'|'',
+ *                      'text' => Output parameter
+ *                 ]
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ */
+function twitter_parse_link(App $a, array &$b)
+{
+       // Only handle Twitter status URLs
+       if (!preg_match('#^https?://(?:mobile\.|www\.)?twitter.com/[^/]+/status/(\d+).*#', $b['url'], $matches)) {
+               return;
+       }
+
+       $ckey = DI::config()->get('twitter', 'consumerkey');
+       $csecret = DI::config()->get('twitter', 'consumersecret');
+
+       if (empty($ckey) || empty($csecret)) {
+               return;
+       }
+
+       $connection = new TwitterOAuth($ckey, $csecret);
+
+       $parameters = ['trim_user' => false, 'tweet_mode' => 'extended', 'id' => $matches[1], 'include_ext_alt_text' => true];
+
+       $status = $connection->get('statuses/show', $parameters);
+
+       if (empty($status->id)) {
+               return;
+       }
+
+       $item = twitter_createpost($a, 0, $status, [], true, false, true);
+
+       if ($b['format'] == 'json') {
+               $images = [];
+               foreach ($status->extended_entities->media ?? [] as $media) {
+                       if (!empty($media->media_url_https)) {
+                               $images[] = [
+                                       'src'    => $media->media_url_https,
+                                       'width'  => $media->sizes->thumb->w,
+                                       'height' => $media->sizes->thumb->h,
+                               ];
+                       }
+               }
+
+               $b['text'] = [
+                       'data' => [
+                               'type' => 'link',
+                               'url' => $item['plink'],
+                               'title' => DI::l10n()->t('%s on Twitter', $status->user->name),
+                               'text' => BBCode::toPlaintext($item['body'], false),
+                               'images' => $images,
+                       ],
+                       'contentType' => 'attachment',
+                       'success' => true,
+               ];
+       } else {
+               $b['text'] = BBCode::getShareOpeningTag(
+                       $item['author-name'],
+                       $item['author-link'],
+                       $item['author-avatar'],
+                       $item['plink'],
+                       $item['created']
+               );
+               $b['text'] .= $item['body'] . '[/share]';
+       }
+}
+
+
+/*********************
+ *
+ * General functions
+ *
+ *********************/
+
+
 /**
  * @brief Build the item array for the mirrored post
  *
@@ -880,12 +1002,8 @@ function twitter_prepare_body(App $a, array &$b)
  */
 function twitter_do_mirrorpost(App $a, $uid, $post)
 {
-       $datarray['api_source'] = true;
-       $datarray['profile_uid'] = $uid;
-       $datarray['extid'] = Protocol::TWITTER;
-       $datarray['message_id'] = Item::newURI($uid, Protocol::TWITTER . ':' . $post->id);
-       $datarray['protocol'] = Conversation::PARCEL_TWITTER;
-       $datarray['source'] = json_encode($post);
+       $datarray['uid'] = $uid;
+       $datarray['extid'] = 'twitter::' . $post->id;
        $datarray['title'] = '';
 
        if (!empty($post->retweeted_status)) {
@@ -896,13 +1014,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]';
@@ -916,7 +1033,7 @@ function twitter_do_mirrorpost(App $a, $uid, $post)
                $datarray['body'] = $item['body'];
        }
 
-       $datarray['source'] = $item['app'];
+       $datarray['app'] = $item['app'];
        $datarray['verb'] = $item['verb'];
 
        if (isset($item['location'])) {
@@ -944,13 +1061,16 @@ function twitter_fetchtimeline(App $a, $uid)
                $application_name = DI::baseUrl()->getHostname();
        }
 
-       $has_picture = false;
-
-       require_once 'mod/item.php';
-       require_once 'mod/share.php';
-
        $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
 
+       // Ensure to have the own contact
+       try {
+               twitter_fetch_own_contact($a, $uid);
+       } catch (TwitterOAuthException $e) {
+               Logger::warning('Error fetching own contact', ['uid' => $uid, 'message' => $e->getMessage()]);
+               return;
+       }
+
        $parameters = ["exclude_replies" => true, "trim_user" => false, "contributor_details" => true, "include_rts" => true, "tweet_mode" => "extended", "include_ext_alt_text" => true];
 
        $first_time = ($lastid == "");
@@ -962,7 +1082,7 @@ function twitter_fetchtimeline(App $a, $uid)
        try {
                $items = $connection->get('statuses/user_timeline', $parameters);
        } catch (TwitterOAuthException $e) {
-               Logger::notice('Error fetching timeline', ['user' => $uid, 'message' => $e->getMessage()]);
+               Logger::warning('Error fetching timeline', ['uid' => $uid, 'message' => $e->getMessage()]);
                return;
        }
 
@@ -973,7 +1093,7 @@ function twitter_fetchtimeline(App $a, $uid)
 
        $posts = array_reverse($items);
 
-       Logger::log('Starting from ID ' . $lastid . ' for user ' . $uid, Logger::DEBUG);
+       Logger::notice('Start processing posts', ['from' => $lastid, 'user' => $uid, 'count' => count($posts)]);
 
        if (count($posts)) {
                foreach ($posts as $post) {
@@ -987,20 +1107,17 @@ function twitter_fetchtimeline(App $a, $uid)
                        }
 
                        if (!stristr($post->source, $application_name)) {
-                               $_SESSION["authenticated"] = true;
-                               $_SESSION["uid"] = $uid;
+                               Logger::info('Preparing mirror post', ['twitter-id' => $post->id_str, 'uid' => $uid]);
 
-                               Logger::log('Preparing Twitter ID ' . $post->id_str . ' for user ' . $uid, Logger::DEBUG);
+                               $mirrorpost = twitter_do_mirrorpost($a, $uid, $post);
 
-                               $_REQUEST = twitter_do_mirrorpost($a, $uid, $post);
-
-                               if (empty($_REQUEST['body'])) {
+                               if (empty($mirrorpost['body'])) {
                                        continue;
                                }
 
-                               Logger::log('Posting Twitter ID ' . $post->id_str . ' for user ' . $uid);
+                               Logger::info('Posting mirror post', ['twitter-id' => $post->id_str, 'uid' => $uid]);
 
-                               item_post($a);
+                               Post\Delayed::add($mirrorpost['extid'], $mirrorpost, PRIORITY_MEDIUM, true);
                        }
                }
        }
@@ -1039,63 +1156,94 @@ 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::warning('Error fetching friendship status', ['uid' => $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);
-       $baseurl = "https://twitter.com";
-       $url = $baseurl . "/" . $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;
+}
+
+function twitter_fetch_contact($uid, $data, $create_user)
+{
+       $fields = twitter_user_to_contact($data);
+
+       if (empty($fields)) {
+               return -1;
+       }
 
-       $fields = ['url' => $url, 'network' => Protocol::TWITTER,
-               'alias' => 'twitter::' . $data->id_str,
-               'baseurl' => $baseurl, 'gsid' => GServer::getID($baseurl),
-               'name' => $data->name, 'nick' => $data->screen_name, 'addr' => $addr,
-               'location' => $data->location, 'about' => $data->description];
+       // 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, false, $fields);
        }
 
        if (!empty($cid)) {
                DBA::update('contact', $fields, ['id' => $cid]);
-               Contact::updateAvatar($avatar, 0, $cid);
+               Contact::updateAvatar($cid, $avatar);
+       } else {
+               Logger::warning('No contact found', ['fields' => $fields]);
        }
 
        $contact = DBA::selectFirst('contact', [], ['uid' => $uid, 'alias' => "twitter::" . $data->id_str]);
-       if (!DBA::isResult($contact) && !$create_user) {
-               Logger::info('User contact not found', ['uid' => $uid, 'twitter-id' => $data->id_str]);
+       if (!DBA::isResult($contact) && empty($cid)) {
+               Logger::warning('User contact not found', ['uid' => $uid, 'twitter-id' => $data->id_str]);
                return 0;
+       } elseif (!$create_user) {
+               return $cid;
        }
 
        if (!DBA::isResult($contact)) {
@@ -1104,7 +1252,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;
@@ -1121,10 +1269,10 @@ function twitter_fetch_contact($uid, $data, $create_user)
 
                Group::addMember(User::getDefaultGroup($uid), $contact_id);
 
-               Contact::updateAvatar($avatar, $uid, $contact_id);
+               Contact::updateAvatar($contact_id, $avatar);
        } else {
                if ($contact["readonly"] || $contact["blocked"]) {
-                       Logger::log("twitter_fetch_contact: Contact '" . $contact["nick"] . "' is blocked or readonly.", Logger::DEBUG);
+                       Logger::notice('Contact is blocked or readonly.', ['nickname' => $contact["nick"]]);
                        return -1;
                }
 
@@ -1137,7 +1285,7 @@ function twitter_fetch_contact($uid, $data, $create_user)
                        $update = true;
                }
 
-               Contact::updateAvatar($avatar, $uid, $contact['id']);
+               Contact::updateAvatar($contact['id'], $avatar);
 
                if ($contact['name'] != $data->name) {
                        $fields['name-date'] = $fields['uri-date'] = DateTimeFormat::utcNow();
@@ -1163,48 +1311,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::warning('Error fetching user', ['user' => $screen_name, 'message' => $e->getMessage()]);
+               return null;
        }
 
        if (!is_object($user)) {
-               return;
+               return null;
        }
 
-       $contact_id = twitter_fetch_contact($uid, $user, true);
-
-       return $contact_id;
+       return $user;
 }
 
 /**
@@ -1271,7 +1402,7 @@ function twitter_expand_entities($body, stdClass $status, $picture)
 
                        $expanded_url = $url->expanded_url;
 
-                       $final_url = Network::finalUrl($url->expanded_url);
+                       $final_url = DI::httpRequest()->finalUrl($url->expanded_url);
 
                        $oembed_data = OEmbed::fetchURL($final_url);
 
@@ -1292,7 +1423,7 @@ function twitter_expand_entities($body, stdClass $status, $picture)
                        } elseif ($oembed_data->type != 'link') {
                                $replace = '[url=' . $expanded_url . ']' . $url->display_url . '[/url]';
                        } else {
-                               $img_str = Network::fetchUrl($final_url, true, 4);
+                               $img_str = DI::httpRequest()->fetch($final_url, 4);
 
                                $tempfile = tempnam(get_temppath(), 'cache');
                                file_put_contents($tempfile, $img_str);
@@ -1331,7 +1462,7 @@ function twitter_expand_entities($body, stdClass $status, $picture)
        if (empty($status->quoted_status)) {
                $footer = '';
                if ($attachmentUrl) {
-                       $footer = add_page_info($attachmentUrl, false, $picture);
+                       $footer = "\n" . PageInfo::getFooterFromUrl($attachmentUrl, false, $picture);
                }
 
                if (trim($footer)) {
@@ -1339,11 +1470,11 @@ function twitter_expand_entities($body, stdClass $status, $picture)
                } elseif ($picture) {
                        $body .= "\n\n[img]" . $picture . "[/img]\n";
                } else {
-                       $body = add_page_info_to_body($body);
+                       $body = PageInfo::searchAndAppendToBody($body);
                }
        }
 
-       return ['body' => $body, 'plain' => $plain, 'taglist' => $taglist];
+       return ['body' => $body, 'plain' => trim($plain), 'taglist' => $taglist];
 }
 
 /**
@@ -1376,6 +1507,8 @@ function twitter_media_entities($post, array &$postarray)
                }
        }
 
+
+
        // This is a pure media post, first search for all media urls
        $media = [];
        foreach ($post->extended_entities->media AS $medium) {
@@ -1450,36 +1583,32 @@ function twitter_createpost(App $a, $uid, $post, array $self, $create_user, $onl
        $postarray['uri'] = "twitter::" . $post->id_str;
        $postarray['protocol'] = Conversation::PARCEL_TWITTER;
        $postarray['source'] = json_encode($post);
+       $postarray['direction'] = Conversation::PULL;
 
        if (empty($uriid)) {
                $uriid = $postarray['uri-id'] = ItemURI::insert(['uri' => $postarray['uri']]);
        }
 
        // Don't import our own comments
-       if (Item::exists(['extid' => $postarray['uri'], 'uid' => $uid])) {
-               Logger::log("Item with extid " . $postarray['uri'] . " found.", Logger::DEBUG);
+       if (Post::exists(['extid' => $postarray['uri'], 'uid' => $uid])) {
+               Logger::info('Item found', ['extid' => $postarray['uri']]);
                return [];
        }
 
        $contactid = 0;
 
        if ($post->in_reply_to_status_id_str != "") {
-               $parent = "twitter::" . $post->in_reply_to_status_id_str;
+               $thr_parent = "twitter::" . $post->in_reply_to_status_id_str;
 
-               $fields = ['uri', 'parent-uri', 'parent'];
-               $parent_item = Item::selectFirst($fields, ['uri' => $parent, 'uid' => $uid]);
-               if (!DBA::isResult($parent_item)) {
-                       $parent_item = Item::selectFirst($fields, ['extid' => $parent, 'uid' => $uid]);
+               $item = Post::selectFirst(['uri'], ['uri' => $thr_parent, 'uid' => $uid]);
+               if (!DBA::isResult($item)) {
+                       $item = Post::selectFirst(['uri'], ['extid' => $thr_parent, 'uid' => $uid]);
                }
 
-               if (DBA::isResult($parent_item)) {
-                       $postarray['thr-parent'] = $parent_item['uri'];
-                       $postarray['parent-uri'] = $parent_item['parent-uri'];
-                       $postarray['parent'] = $parent_item['parent'];
+               if (DBA::isResult($item)) {
+                       $postarray['thr-parent'] = $item['uri'];
                        $postarray['object-type'] = Activity\ObjectType::COMMENT;
                } else {
-                       $postarray['thr-parent'] = $postarray['uri'];
-                       $postarray['parent-uri'] = $postarray['uri'];
                        $postarray['object-type'] = Activity\ObjectType::NOTE;
                }
 
@@ -1497,14 +1626,13 @@ function twitter_createpost(App $a, $uid, $post, array $self, $create_user, $onl
                                $postarray['owner-link']   = $r[0]["url"];
                                $postarray['owner-avatar'] = $r[0]["photo"];
                        } else {
-                               Logger::log("No self contact for user " . $uid, Logger::DEBUG);
+                               Logger::error('No self contact found', ['uid' => $uid]);
                                return [];
                        }
                }
                // Don't create accounts of people who just comment something
                $create_user = false;
        } else {
-               $postarray['parent-uri'] = $postarray['uri'];
                $postarray['object-type'] = Activity\ObjectType::NOTE;
        }
 
@@ -1519,7 +1647,7 @@ function twitter_createpost(App $a, $uid, $post, array $self, $create_user, $onl
        if (($contactid == 0) && !$only_existing_contact) {
                $contactid = $self['id'];
        } elseif ($contactid <= 0) {
-               Logger::log("Contact ID is zero or less than zero.", Logger::DEBUG);
+               Logger::info('Contact ID is zero or less than zero.');
                return [];
        }
 
@@ -1594,9 +1722,9 @@ function twitter_createpost(App $a, $uid, $post, array $self, $create_user, $onl
                        $postarray['object-type'] = Activity\ObjectType::NOTE;
 
                        $postarray['thr-parent'] = $retweet['uri'];
-                       $postarray['parent-uri'] = $retweet['uri'];
                } else {
                        $retweet['source'] = $postarray['source'];
+                       $retweet['direction'] = $postarray['direction'];
                        $retweet['private'] = $postarray['private'];
                        $retweet['allow_cid'] = $postarray['allow_cid'];
                        $retweet['contact-id'] = $postarray['contact-id'];
@@ -1615,13 +1743,12 @@ function twitter_createpost(App $a, $uid, $post, array $self, $create_user, $onl
                } else {
                        $quoted = twitter_createpost($a, $uid, $post->quoted_status, $self, false, false, true, $uriid);
                        if (!empty($quoted['body'])) {
-                               $postarray['body'] .= "\n" . share_header(
+                               $postarray['body'] .= "\n" . BBCode::getShareOpeningTag(
                                                $quoted['author-name'],
                                                $quoted['author-link'],
                                                $quoted['author-avatar'],
-                                               "",
-                                               $quoted['created'],
-                                               $quoted['plink']
+                                               $quoted['plink'],
+                                               $quoted['created']
                                        );
 
                                $postarray['body'] .= $quoted['body'] . '[/share]';
@@ -1650,7 +1777,7 @@ function twitter_store_tags(int $uriid, array $taglist)
 
 function twitter_fetchparentposts(App $a, $uid, $post, TwitterOAuth $connection, array $self)
 {
-       Logger::log("twitter_fetchparentposts: Fetching for user " . $uid . " and post " . $post->id_str, Logger::DEBUG);
+       Logger::info('Fetching parent posts', ['user' => $uid, 'post' => $post->id_str]);
 
        $posts = [];
 
@@ -1660,7 +1787,7 @@ function twitter_fetchparentposts(App $a, $uid, $post, TwitterOAuth $connection,
                try {
                        $post = $connection->get('statuses/show', $parameters);
                } catch (TwitterOAuthException $e) {
-                       Logger::log('twitter_fetchparentposts: Error fetching for user ' . $uid . ' and post ' . $post->id_str . ': ' . $e->getMessage());
+                       Logger::warning('Error fetching parent post', ['uid' => $uid, 'post' => $post->id_str, 'message' => $e->getMessage()]);
                        break;
                }
 
@@ -1674,7 +1801,7 @@ function twitter_fetchparentposts(App $a, $uid, $post, TwitterOAuth $connection,
                        break;
                }
 
-               if (Item::exists(['uri' => 'twitter::' . $post->id_str, 'uid' => $uid])) {
+               if (Post::exists(['uri' => 'twitter::' . $post->id_str, 'uid' => $uid])) {
                        break;
                }
 
@@ -1711,7 +1838,7 @@ function twitter_fetchhometimeline(App $a, $uid)
        $create_user = DI::pConfig()->get($uid, 'twitter', 'create_user');
        $mirror_posts = DI::pConfig()->get($uid, 'twitter', 'mirror_posts');
 
-       Logger::log("Fetching timeline for user " . $uid, Logger::DEBUG);
+       Logger::info('Fetching timeline', ['uid' => $uid]);
 
        $application_name = DI::config()->get('twitter', 'application_name');
 
@@ -1724,7 +1851,7 @@ function twitter_fetchhometimeline(App $a, $uid)
        try {
                $own_contact = twitter_fetch_own_contact($a, $uid);
        } catch (TwitterOAuthException $e) {
-               Logger::log('Error fetching own contact for user ' . $uid . ': ' . $e->getMessage());
+               Logger::warning('Error fetching own contact', ['uid' => $uid, 'message' => $e->getMessage()]);
                return;
        }
 
@@ -1735,13 +1862,13 @@ function twitter_fetchhometimeline(App $a, $uid)
        if (DBA::isResult($r)) {
                $own_id = $r[0]["nick"];
        } else {
-               Logger::log("Own twitter contact not found for user " . $uid);
+               Logger::warning('Own twitter contact not found', ['uid' => $uid]);
                return;
        }
 
        $self = User::getOwnerDataById($uid);
        if ($self === false) {
-               Logger::log("Own contact not found for user " . $uid);
+               Logger::warning('Own contact not found', ['uid' => $uid]);
                return;
        }
 
@@ -1759,23 +1886,23 @@ function twitter_fetchhometimeline(App $a, $uid)
        try {
                $items = $connection->get('statuses/home_timeline', $parameters);
        } catch (TwitterOAuthException $e) {
-               Logger::log('Error fetching home timeline for user ' . $uid . ': ' . $e->getMessage());
+               Logger::warning('Error fetching home timeline', ['uid' => $uid, 'message' => $e->getMessage()]);
                return;
        }
 
        if (!is_array($items)) {
-               Logger::log('No array while fetching home timeline for user ' . $uid . ': ' . print_r($items, true));
+               Logger::warning('home timeline is no array', ['items' => $items]);
                return;
        }
 
        if (empty($items)) {
-               Logger::log('No new timeline content for user ' . $uid, Logger::INFO);
+               Logger::notice('No new timeline content', ['uid' => $uid]);
                return;
        }
 
        $posts = array_reverse($items);
 
-       Logger::log('Fetching timeline from ID ' . $lastid . ' for user ' . $uid . ' ' . sizeof($posts) . ' items', Logger::DEBUG);
+       Logger::notice('Processing timeline', ['lastid' => $lastid, 'uid' => $uid, 'count' => count($posts)]);
 
        if (count($posts)) {
                foreach ($posts as $post) {
@@ -1789,12 +1916,12 @@ function twitter_fetchhometimeline(App $a, $uid)
                        }
 
                        if (stristr($post->source, $application_name) && $post->user->screen_name == $own_id) {
-                               Logger::log("Skip previously sent post", Logger::DEBUG);
+                               Logger::info("Skip previously sent post");
                                continue;
                        }
 
                        if ($mirror_posts && $post->user->screen_name == $own_id && $post->in_reply_to_status_id_str == "") {
-                               Logger::log("Skip post that will be mirrored", Logger::DEBUG);
+                               Logger::info("Skip post that will be mirrored");
                                continue;
                        }
 
@@ -1813,10 +1940,10 @@ function twitter_fetchhometimeline(App $a, $uid)
 
                        $notify = false;
 
-                       if (($postarray['uri'] == $postarray['parent-uri']) && ($postarray['author-link'] == $postarray['owner-link'])) {
+                       if (empty($postarray['thr-parent'])) {
                                $contact = DBA::selectFirst('contact', [], ['id' => $postarray['contact-id'], 'self' => false]);
-                               if (DBA::isResult($contact)) {
-                                       $notify = Item::isRemoteSelf($contact, $postarray);
+                               if (DBA::isResult($contact) && Item::isRemoteSelf($contact, $postarray)) {
+                                       $notify = PRIORITY_MEDIUM;
                                }
                        }
 
@@ -1842,12 +1969,12 @@ function twitter_fetchhometimeline(App $a, $uid)
        try {
                $items = $connection->get('statuses/mentions_timeline', $parameters);
        } catch (TwitterOAuthException $e) {
-               Logger::log('Error fetching mentions: ' . $e->getMessage());
+               Logger::warning('Error fetching mentions', ['uid' => $uid, 'message' => $e->getMessage()]);
                return;
        }
 
        if (!is_array($items)) {
-               Logger::log("Error fetching mentions: " . print_r($items, true), Logger::DEBUG);
+               Logger::warning("mentions are no arrays", ['items' => $items]);
                return;
        }
 
@@ -1903,7 +2030,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;
                }
 
@@ -1960,8 +2087,12 @@ function twitter_is_retweet(App $a, $uid, $body)
        if ($id == $link) {
                return false;
        }
+       return twitter_retweet($uid, $id);
+}
 
-       Logger::log('twitter_is_retweet: Retweeting id ' . $id . ' for user ' . $uid, Logger::DEBUG);
+function twitter_retweet(int $uid, int $id, int $item_id = 0)
+{
+       Logger::info('Retweeting', ['user' => $uid, 'id' => $id]);
 
        $ckey    = DI::config()->get('twitter', 'consumerkey');
        $csecret = DI::config()->get('twitter', 'consumersecret');
@@ -1971,7 +2102,12 @@ function twitter_is_retweet(App $a, $uid, $body)
        $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
        $result = $connection->post('statuses/retweet/' . $id);
 
-       Logger::log('twitter_is_retweet: result ' . print_r($result, true), Logger::DEBUG);
+       Logger::info('Retweeted', ['user' => $uid, 'id' => $id, 'result' => $result]);
+
+       if (!empty($item_id) && !empty($result->id_str)) {
+               Logger::notice('Update extid', ['id' => $item_id, 'extid' => $result->id_str]);
+               Item::update(['extid' => "twitter::" . $result->id_str], ['id' => $item_id]);
+       }
 
        return !isset($result->errors);
 }