X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=twitter%2Ftwitter.php;h=e1265d5d3cb5b5b8c311b7ee888eb5904dfa078f;hb=61d95f53021ac9ccb73cba0c89107cc6bac7f14b;hp=5167e022375cc8a1e77fa5476d2dbb2530cb6607;hpb=1e451a3490402e87872c9f41c08ae2fecac48643;p=friendica-addons.git diff --git a/twitter/twitter.php b/twitter/twitter.php index 5167e022..e1265d5d 100644 --- a/twitter/twitter.php +++ b/twitter/twitter.php @@ -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; @@ -80,15 +81,14 @@ use Friendica\Model\Contact; use Friendica\Model\Conversation; use Friendica\Model\Group; 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,6 +111,7 @@ function twitter_install() 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"); } @@ -240,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'); @@ -258,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); } } } @@ -350,8 +349,9 @@ function twitter_settings(App $a, &$s) } else { $s .= '

Invalid Twitter info

-
'; - Logger::info('Invalid twitter info (verify credentials).', ['auth' => TwitterOAuth::class]); + + '; + Logger::notice('Invalid twitter info (verify credentials).', ['auth' => TwitterOAuth::class]); } $s .= '
'; @@ -398,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; @@ -465,9 +465,9 @@ function twitter_probe_detect(App $a, array &$hookData) return; } - if (preg_match('=(.*)@twitter.com=i', $hookData['uri'], $matches)) { + if (preg_match('=([^@]+)@(?:mobile\.)?twitter\.com$=i', $hookData['uri'], $matches)) { $nick = $matches[1]; - } elseif (preg_match('=https?://(?:mobile\.)?twitter.com/(.*)=i', $hookData['uri'], $matches)) { + } elseif (preg_match('=^https?://(?:mobile\.)?twitter\.com/(.+)=i', $hookData['uri'], $matches)) { $nick = $matches[1]; } else { return; @@ -482,6 +482,10 @@ function twitter_probe_detect(App $a, array &$hookData) 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'); @@ -491,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) @@ -519,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; } @@ -567,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; @@ -586,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; } @@ -604,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'])) { @@ -632,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"]; @@ -640,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; } @@ -666,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); @@ -686,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); @@ -694,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'; @@ -713,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']]); } } @@ -728,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) @@ -819,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)) { @@ -836,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) @@ -851,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]"; @@ -862,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")) { @@ -877,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 * @@ -888,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)) { @@ -908,9 +1018,8 @@ function twitter_do_mirrorpost(App $a, $uid, $post) $item['author-name'], $item['author-link'], $item['author-avatar'], - '', - $item['created'], - $item['plink'] + $item['plink'], + $item['created'] ); $datarray['body'] .= $item['body'] . '[/share]'; @@ -924,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'])) { @@ -952,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 == ""); @@ -970,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; } @@ -981,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) { @@ -995,20 +1107,17 @@ function twitter_fetchtimeline(App $a, $uid) } if (!stristr($post->source, $application_name)) { - $_SESSION["authenticated"] = true; - $_SESSION["uid"] = $uid; - - Logger::log('Preparing Twitter ID ' . $post->id_str . ' for user ' . $uid, Logger::DEBUG); + Logger::info('Preparing mirror post', ['twitter-id' => $post->id_str, 'uid' => $uid]); - $_REQUEST = twitter_do_mirrorpost($a, $uid, $post); + $mirrorpost = 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); } } } @@ -1047,33 +1156,39 @@ 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; } +/** + * @param $data + * @return array + */ function twitter_user_to_contact($data) { if (empty($data->id_str)) { - return -1; + return []; } $baseurl = 'https://twitter.com'; @@ -1113,18 +1228,22 @@ function twitter_fetch_contact($uid, $data, $create_user) if (DBA::isResult($pcontact)) { $cid = $pcontact['id']; } else { - $cid = Contact::getIdForURL($fields['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)) { @@ -1150,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; } @@ -1166,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(); @@ -1208,7 +1327,7 @@ function twitter_fetchuser($screen_name) $parameters = ['screen_name' => $screen_name]; $user = $connection->get('users/show', $parameters); } catch (TwitterOAuthException $e) { - Logger::log('twitter_fetchuser: Error fetching user ' . $screen_name . ': ' . $e->getMessage()); + Logger::warning('Error fetching user', ['user' => $screen_name, 'message' => $e->getMessage()]); return null; } @@ -1283,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); @@ -1304,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); @@ -1343,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)) { @@ -1351,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]; } /** @@ -1388,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) { @@ -1462,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; } @@ -1509,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; } @@ -1531,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 []; } @@ -1606,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']; @@ -1631,9 +1747,8 @@ function twitter_createpost(App $a, $uid, $post, array $self, $create_user, $onl $quoted['author-name'], $quoted['author-link'], $quoted['author-avatar'], - "", - $quoted['created'], - $quoted['plink'] + $quoted['plink'], + $quoted['created'] ); $postarray['body'] .= $quoted['body'] . '[/share]'; @@ -1662,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 = []; @@ -1672,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; } @@ -1686,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; } @@ -1723,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'); @@ -1736,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; } @@ -1747,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; } @@ -1771,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) { @@ -1801,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; } @@ -1825,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; } } @@ -1854,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; } @@ -1915,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; } @@ -1972,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'); @@ -1983,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); }