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;
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;
$nickname = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $contact["url"]);
$nickname = str_replace("@twitter.com", "", $nickname);
- $uid = $a->user["uid"];
+ $uid = $a->getUserId();
$ckey = DI::config()->get('twitter', 'consumerkey');
$csecret = DI::config()->get('twitter', 'consumersecret');
if (!local_user()) {
return;
}
+
+ $user = User::getById(local_user());
+
DI::page()['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . DI::baseUrl()->get() . '/addon/twitter/twitter.css' . '" media="all" />' . "\r\n";
/* * *
* 1) Check that we have global consumer key & secret
$s .= Renderer::replaceMacros($field_checkbox, [
'$field' => ['twitter-enable', DI::l10n()->t('Allow posting to Twitter'), $enabled, DI::l10n()->t('If enabled all your <strong>public</strong> postings can be posted to the associated Twitter account. You can choose to do so by default (here) or for every posting separately in the posting options when writing the entry.')]
]);
- if ($a->user['hidewall']) {
+ if ($user['hidewall']) {
$s .= '<p>' . DI::l10n()->t('<strong>Note</strong>: Due to your privacy settings (<em>Hide your profile details from unknown viewers?</em>) the link potentially included in public postings relayed to Twitter will lead the visitor to a blank page informing the visitor that the access to your profile has been restricted.') . '</p>';
}
$s .= Renderer::replaceMacros($field_checkbox, [
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;
Logger::debug('before action', ['action' => $action, 'pid' => $pid, 'data' => $post]);
- switch ($action) {
- case 'delete':
- // To-Do: $result = $connection->post('statuses/destroy', $post);
- $result = [];
- break;
- case 'like':
- $result = $connection->post('favorites/create', $post);
- if ($connection->getLastHttpCode() != 200) {
- Logger::warning('Unable to create favorite', ['result' => $result]);
- }
- break;
- case 'unlike':
- $result = $connection->post('favorites/destroy', $post);
- if ($connection->getLastHttpCode() != 200) {
- Logger::warning('Unable to destroy favorite', ['result' => $result]);
- }
- break;
- default:
- Logger::warning('Unhandled action', ['action' => $action]);
- $result = [];
+ try {
+ switch ($action) {
+ case 'delete':
+ // To-Do: $result = $connection->post('statuses/destroy', $post);
+ $result = [];
+ break;
+ case 'like':
+ $result = $connection->post('favorites/create', $post);
+ if ($connection->getLastHttpCode() != 200) {
+ Logger::warning('Unable to create favorite', ['result' => $result]);
+ }
+ break;
+ case 'unlike':
+ $result = $connection->post('favorites/destroy', $post);
+ if ($connection->getLastHttpCode() != 200) {
+ Logger::warning('Unable to destroy favorite', ['result' => $result]);
+ }
+ break;
+ default:
+ Logger::warning('Unhandled action', ['action' => $action]);
+ $result = [];
+ }
+ } catch (TwitterOAuthException $twitterOAuthException) {
+ Logger::warning('Unable to communicate with twitter', ['action' => $action, 'data' => $post, 'code' => $twitterOAuthException->getCode(), 'exception' => $twitterOAuthException]);
}
Logger::info('after action', ['action' => $action, 'result' => $result]);
return;
}
+ $b['body'] = Post\Media::addAttachmentsToBody($b['uri-id'], $b['body']);
+
$thr_parent = null;
if ($b['parent'] != $b['id']) {
}
$condition = ['uri' => $b["thr-parent"], 'uid' => $b["uid"]];
- $thr_parent = Item::selectFirst(['uri', 'extid', 'author-nick', 'author-network'], $condition);
+ $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;
$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"];
if (($msg == "") && isset($msgarr["title"])) {
- $msg = Plaintext::shorten($msgarr["title"], $max_char - 50);
+ $msg = Plaintext::shorten($msgarr["title"], $max_char - 50, $b['uid']);
}
// Add the link to the body if the type isn't a photo or there are more than 4 images in the post
$data = ['media_id' => $media->media_id_string,
'alt_text' => ['text' => substr($image['description'], 0, 420)]];
$ret = $cb->media_metadata_create($data);
- Logger::info('Metadata create', ['id' => $b['id'], 'data' => $data, 'return' => json_encode($ret)]);
+ Logger::info('Metadata create', ['id' => $b['id'], 'data' => $data, 'return' => $ret]);
}
} else {
- Logger::error('Failed upload', ['id' => $b['id'], 'image' => $image['url']]);
+ Logger::error('Failed upload', ['id' => $b['id'], 'image' => $image['url'], 'return' => $media]);
throw new Exception('Failed upload of ' . $image['url']);
}
}
Logger::notice('Start deleting expired posts');
- $r = Item::select(['id', 'guid'], ['deleted' => true, 'network' => Protocol::TWITTER]);
- while ($row = DBA::fetch($r)) {
+ $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']]);
+ Item::markForDeletionById($row['id']);
}
DBA::close($r);
$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]";
}
}
- $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")) {
}
}
+function twitter_statuses_show(string $id, TwitterOAuth $twitterOAuth = null)
+{
+ if ($twitterOAuth === null) {
+ $ckey = DI::config()->get('twitter', 'consumerkey');
+ $csecret = DI::config()->get('twitter', 'consumersecret');
+
+ if (empty($ckey) || empty($csecret)) {
+ return new stdClass();
+ }
+
+ $twitterOAuth = new TwitterOAuth($ckey, $csecret);
+ }
+
+ $parameters = ['trim_user' => false, 'tweet_mode' => 'extended', 'id' => $id, 'include_ext_alt_text' => true];
+
+ return $twitterOAuth->get('statuses/show', $parameters);
+}
+
/**
* Parse Twitter status URLs since Twitter removed OEmbed
*
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);
+ $status = twitter_statuses_show($matches[1]);
if (empty($status->id)) {
return;
'location' => $data->location,
'about' => $data->description,
'photo' => twitter_fix_avatar($data->profile_image_url_https),
+ 'header' => $data->profile_banner_url ?? $data->profile_background_image_url_https,
];
return $fields;
*
* @param string $body
* @param stdClass $status
- * @param string $picture
* @return array
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
-function twitter_expand_entities($body, stdClass $status, $picture)
+function twitter_expand_entities($body, stdClass $status)
{
$plain = $body;
+ $contains_urls = false;
$taglist = [];
];
}
- // This URL if set will be used to add an attachment at the bottom of the post
- $attachmentUrl = '';
-
foreach ($status->entities->urls ?? [] as $url) {
$plain = str_replace($url->url, '', $plain);
if ($url->url && $url->expanded_url && $url->display_url) {
-
// Quote tweet, we just remove the quoted tweet URL from the body, the share block will be added later.
if (!empty($status->quoted_status) && isset($status->quoted_status_id_str)
&& substr($url->expanded_url, -strlen($status->quoted_status_id_str)) == $status->quoted_status_id_str
continue;
}
- $expanded_url = $url->expanded_url;
-
- $final_url = DI::httpRequest()->finalUrl($url->expanded_url);
+ $contains_urls = true;
- $oembed_data = OEmbed::fetchURL($final_url);
-
- if (empty($oembed_data) || empty($oembed_data->type)) {
- continue;
- }
+ $expanded_url = $url->expanded_url;
// Quickfix: Workaround for URL with '[' and ']' in it
if (strpos($expanded_url, '[') || strpos($expanded_url, ']')) {
$expanded_url = $url->url;
}
- if ($oembed_data->type == 'video') {
- $attachmentUrl = $expanded_url;
- $replace = '';
- } elseif (($oembed_data->type == 'photo') && isset($oembed_data->url)) {
- $replace = '[url=' . $expanded_url . '][img]' . $oembed_data->url . '[/img][/url]';
- } elseif ($oembed_data->type != 'link') {
- $replace = '[url=' . $expanded_url . ']' . $url->display_url . '[/url]';
- } else {
- $img_str = DI::httpRequest()->fetch($final_url, 4);
-
- $tempfile = tempnam(get_temppath(), 'cache');
- file_put_contents($tempfile, $img_str);
-
- // See http://php.net/manual/en/function.exif-imagetype.php#79283
- if (filesize($tempfile) > 11) {
- $mime = image_type_to_mime_type(exif_imagetype($tempfile));
- } else {
- $mime = false;
- }
-
- unlink($tempfile);
-
- if (substr($mime, 0, 6) == 'image/') {
- $replace = '[img]' . $final_url . '[/img]';
- } else {
- $attachmentUrl = $expanded_url;
- $replace = '';
- }
- }
-
$replacementList[$url->indices[0]] = [
- 'replace' => $replace,
+ 'replace' => '[url=' . $expanded_url . ']' . $url->display_url . '[/url]',
'length' => $url->indices[1] - $url->indices[0],
];
}
$body = Strings::substringReplace($body, $parameters['replace'], $startIndex, $parameters['length']);
}
- // Footer will be taken care of with a share block in the case of a quote
- if (empty($status->quoted_status)) {
- $footer = '';
- if ($attachmentUrl) {
- $footer = "\n" . PageInfo::getFooterFromUrl($attachmentUrl, false, $picture);
- }
+ $body = trim($body);
- if (trim($footer)) {
- $body .= $footer;
- } elseif ($picture) {
- $body .= "\n\n[img]" . $picture . "[/img]\n";
- } else {
- $body = PageInfo::searchAndAppendToBody($body);
+ return ['body' => trim($body), 'plain' => trim($plain), 'taglist' => $taglist, 'urls' => $contains_urls];
+}
+
+/**
+ * Store entity attachments
+ *
+ * @param integer $uriid
+ * @param object $post Twitter object with the post
+ */
+function twitter_store_attachments(int $uriid, $post)
+{
+ if (!empty($post->extended_entities->media)) {
+ foreach ($post->extended_entities->media AS $medium) {
+ switch ($medium->type) {
+ case 'photo':
+ $attachment = ['uri-id' => $uriid, 'type' => Post\Media::IMAGE];
+
+ $attachment['url'] = $medium->media_url_https . '?name=large';
+ $attachment['width'] = $medium->sizes->large->w;
+ $attachment['height'] = $medium->sizes->large->h;
+
+ if ($medium->sizes->small->w != $attachment['width']) {
+ $attachment['preview'] = $medium->media_url_https . '?name=small';
+ $attachment['preview-width'] = $medium->sizes->small->w;
+ $attachment['preview-height'] = $medium->sizes->small->h;
+ }
+
+ $attachment['name'] = $medium->display_url ?? null;
+ $attachment['description'] = $medium->ext_alt_text ?? null;
+ Logger::debug('Photo attachment', ['attachment' => $attachment]);
+ Post\Media::insert($attachment);
+ break;
+ case 'video':
+ case 'animated_gif':
+ $attachment = ['uri-id' => $uriid, 'type' => Post\Media::VIDEO];
+ if (is_array($medium->video_info->variants)) {
+ $bitrate = 0;
+ // We take the video with the highest bitrate
+ foreach ($medium->video_info->variants AS $variant) {
+ if (($variant->content_type == 'video/mp4') && ($variant->bitrate >= $bitrate)) {
+ $attachment['url'] = $variant->url;
+ $bitrate = $variant->bitrate;
+ }
+ }
+ }
+
+ $attachment['name'] = $medium->display_url ?? null;
+ $attachment['preview'] = $medium->media_url_https . ':small';
+ $attachment['preview-width'] = $medium->sizes->small->w;
+ $attachment['preview-height'] = $medium->sizes->small->h;
+ $attachment['description'] = $medium->ext_alt_text ?? null;
+ Logger::debug('Video attachment', ['attachment' => $attachment]);
+ Post\Media::insert($attachment);
+ break;
+ default:
+ Logger::notice('Unknown media type', ['medium' => $medium]);
+ }
}
}
- return ['body' => $body, 'plain' => trim($plain), 'taglist' => $taglist];
+ if (!empty($post->entities->urls)) {
+ foreach ($post->entities->urls as $url) {
+ $attachment = ['uri-id' => $uriid, 'type' => Post\Media::UNKNOWN, 'url' => $url->expanded_url, 'name' => $url->display_url];
+ Logger::debug('Attached link', ['attachment' => $attachment]);
+ Post\Media::insert($attachment);
+ }
+ }
}
/**
* @brief Fetch media entities and add media links to the body
*
- * @param object $post Twitter object with the post
- * @param array $postarray Array of the item that is about to be posted
- *
- * @return $picture string Image URL or empty string
+ * @param object $post Twitter object with the post
+ * @param array $postarray Array of the item that is about to be posted
+ * @param integer $uriid URI Id used to store tags. -1 = don't store tags for this post.
*/
-function twitter_media_entities($post, array &$postarray)
+function twitter_media_entities($post, array &$postarray, int $uriid = -1)
{
// There are no media entities? So we quit.
if (empty($post->extended_entities->media)) {
- return '';
- }
-
- // When the post links to an external page, we only take one picture.
- // We only do this when there is exactly one media.
- if ((count($post->entities->urls) > 0) && (count($post->extended_entities->media) == 1)) {
- $medium = $post->extended_entities->media[0];
- $picture = '';
- foreach ($post->entities->urls as $link) {
- // Let's make sure the external link url matches the media url
- if ($medium->url == $link->url && isset($medium->media_url_https)) {
- $picture = $medium->media_url_https;
- $postarray['body'] = str_replace($medium->url, '', $postarray['body']);
- return $picture;
- }
- }
+ return;
}
-
-
// This is a pure media post, first search for all media urls
$media = [];
foreach ($post->extended_entities->media AS $medium) {
}
$postarray['object-type'] = Activity\ObjectType::IMAGE;
+ $postarray['post-type'] = Item::PT_IMAGE;
break;
case 'video':
+ // Currently deactivated, since this causes the video to be display before the content
+ // We have to figure out a better way for declaring the post type and the display style.
+ //$postarray['post-type'] = Item::PT_VIDEO;
case 'animated_gif':
if (!empty($medium->ext_alt_text)) {
Logger::info('Got text description', ['alt_text' => $medium->ext_alt_text]);
}
}
break;
- // The following code will only be activated for test reasons
- //default:
- // $postarray['body'] .= print_r($medium, true);
}
}
+ if ($uriid != -1) {
+ foreach ($media AS $key => $value) {
+ $postarray['body'] = str_replace($key, '', $postarray['body']);
+ }
+ return;
+ }
+
// Now we replace the media urls.
foreach ($media AS $key => $value) {
$postarray['body'] = str_replace($key, "\n" . $value . "\n", $postarray['body']);
}
-
- return '';
}
/**
$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])) {
+ if (Post::exists(['extid' => $postarray['uri'], 'uid' => $uid])) {
Logger::info('Item found', ['extid' => $postarray['uri']]);
return [];
}
if ($post->in_reply_to_status_id_str != "") {
$thr_parent = "twitter::" . $post->in_reply_to_status_id_str;
- $item = Item::selectFirst(['uri'], ['uri' => $thr_parent, 'uid' => $uid]);
+ $item = Post::selectFirst(['uri'], ['uri' => $thr_parent, 'uid' => $uid]);
if (!DBA::isResult($item)) {
- $item = Item::selectFirst(['uri'], ['extid' => $thr_parent, 'uid' => $uid]);
+ $item = Post::selectFirst(['uri'], ['extid' => $thr_parent, 'uid' => $uid]);
}
if (DBA::isResult($item)) {
$postarray['app'] = strip_tags($post->source);
if ($post->user->protected) {
- $postarray['private'] = 1;
+ $postarray['private'] = Item::PRIVATE;
$postarray['allow_cid'] = '<' . $self['id'] . '>';
} else {
- $postarray['private'] = 0;
+ $postarray['private'] = Item::UNLISTED;
$postarray['allow_cid'] = '';
}
}
// Search for media links
- $picture = twitter_media_entities($post, $postarray);
+ twitter_media_entities($post, $postarray, $uriid);
+
+ $converted = twitter_expand_entities($postarray['body'], $post);
+
+ // When the post contains external links then images or videos are just "decorations".
+ if (!empty($converted['urls'])) {
+ $postarray['post-type'] = Item::PT_NOTE;
+ }
- $converted = twitter_expand_entities($postarray['body'], $post, $picture);
$postarray['body'] = $converted['body'];
$postarray['created'] = DateTimeFormat::utc($post->created_at);
$postarray['edited'] = DateTimeFormat::utc($post->created_at);
if ($uriid > 0) {
twitter_store_tags($uriid, $converted['taglist']);
+ twitter_store_attachments($uriid, $post);
}
- $statustext = $converted["plain"];
-
if (!empty($post->place->name)) {
$postarray["location"] = $post->place->name;
}
$postarray['thr-parent'] = $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'];
// To avoid recursive share blocks we just provide the link to avoid removing quote context.
$postarray['body'] .= "\n\nhttps://twitter.com/" . $post->quoted_status->user->screen_name . "/status/" . $post->quoted_status->id_str;
} else {
- $quoted = twitter_createpost($a, $uid, $post->quoted_status, $self, false, false, true, $uriid);
+ $quoted = twitter_createpost($a, 0, $post->quoted_status, $self, false, false, true);
if (!empty($quoted['body'])) {
+ Item::insert($quoted);
+ $post = Post::selectFirst(['guid', 'uri-id'], ['uri' => $quoted['uri'], 'uid' => 0]);
+ Logger::info('Stored quoted post', ['uid' => $uid, 'uri-id' => $uriid, 'post' => $post]);
+
$postarray['body'] .= "\n" . BBCode::getShareOpeningTag(
$quoted['author-name'],
$quoted['author-link'],
$quoted['author-avatar'],
$quoted['plink'],
- $quoted['created']
+ $quoted['created'],
+ $post['guid'] ?? ''
);
$postarray['body'] .= $quoted['body'] . '[/share]';
$posts = [];
while (!empty($post->in_reply_to_status_id_str)) {
- $parameters = ["trim_user" => false, "tweet_mode" => "extended", "id" => $post->in_reply_to_status_id_str, "include_ext_alt_text" => true];
-
try {
- $post = $connection->get('statuses/show', $parameters);
+ $post = twitter_statuses_show($post->in_reply_to_status_id_str, $connection);
} catch (TwitterOAuthException $e) {
Logger::warning('Error fetching parent post', ['uid' => $uid, 'post' => $post->id_str, 'message' => $e->getMessage()]);
break;
}
if (empty($post)) {
- Logger::log("twitter_fetchparentposts: Can't fetch post " . $parameters['id'], Logger::DEBUG);
+ Logger::info("twitter_fetchparentposts: Can't fetch post");
break;
}
if (empty($post->id_str)) {
- Logger::log("twitter_fetchparentposts: This is not a post " . json_encode($post), Logger::DEBUG);
+ Logger::info("twitter_fetchparentposts: This is not a post", ['post' => $post]);
break;
}
- if (Item::exists(['uri' => 'twitter::' . $post->id_str, 'uid' => $uid])) {
+ if (Post::exists(['uri' => 'twitter::' . $post->id_str, 'uid' => $uid])) {
break;
}