Merge pull request 'Nluesky/Twitter: Resize picture before uploading' (#1411) from heluecht/friendica-addons:resize into develop

Reviewed-on: friendica/friendica-addons#1411
This commit is contained in:
Hypolite Petovan 2023-08-15 22:38:49 +02:00
commit fb9e6e5df6
3 changed files with 89 additions and 22 deletions

View file

@ -43,6 +43,7 @@ use Friendica\Model\Photo;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Network\HTTPClient\Client\HttpClientAccept; use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions; use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Object\Image;
use Friendica\Protocol\Activity; use Friendica\Protocol\Activity;
use Friendica\Protocol\Relay; use Friendica\Protocol\Relay;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
@ -50,6 +51,7 @@ use Friendica\Util\Strings;
const BLUESKY_DEFAULT_POLL_INTERVAL = 10; // given in minutes const BLUESKY_DEFAULT_POLL_INTERVAL = 10; // given in minutes
const BLUESKY_HOST = 'https://bsky.app'; // Hard wired until Bluesky will run on multiple systems const BLUESKY_HOST = 'https://bsky.app'; // Hard wired until Bluesky will run on multiple systems
const BLUESKY_IMAGE_SIZE = [1000000, 500000, 100000, 50000];
function bluesky_install() function bluesky_install()
{ {
@ -634,6 +636,12 @@ function bluesky_create_post(array $item, stdClass $root = null, stdClass $paren
if ($key == count($msg['parts']) - 1) { if ($key == count($msg['parts']) - 1) {
$record = bluesky_add_embed($uid, $msg, $record); $record = bluesky_add_embed($uid, $msg, $record);
if (empty($record)) {
if (Worker::getRetrial() < 3) {
Worker::defer();
}
return;
}
} }
$post = [ $post = [
@ -644,6 +652,9 @@ function bluesky_create_post(array $item, stdClass $root = null, stdClass $paren
$parent = bluesky_xrpc_post($uid, 'com.atproto.repo.createRecord', $post); $parent = bluesky_xrpc_post($uid, 'com.atproto.repo.createRecord', $post);
if (empty($parent)) { if (empty($parent)) {
if ($part == 0) {
Worker::defer();
}
return; return;
} }
Logger::debug('Posting done', ['return' => $parent]); Logger::debug('Posting done', ['return' => $parent]);
@ -663,6 +674,7 @@ function bluesky_get_urls(string $body): array
// Remove all hashtag and mention links // Remove all hashtag and mention links
$body = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $body); $body = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $body);
$body = BBCode::expandVideoLinks($body);
$urls = []; $urls = [];
// Search for pure links // Search for pure links
@ -746,12 +758,15 @@ function bluesky_add_embed(int $uid, array $msg, array $record): array
if (($msg['type'] != 'link') && !empty($msg['images'])) { if (($msg['type'] != 'link') && !empty($msg['images'])) {
$images = []; $images = [];
foreach ($msg['images'] as $image) { foreach ($msg['images'] as $image) {
$photo = Photo::selectFirst(['resource-id'], ['id' => $image['id']]); if (count($images) == 4) {
$photo = Photo::selectFirst([], ["`resource-id` = ? AND `scale` > ?", $photo['resource-id'], 0], ['order' => ['scale']]); continue;
$blob = bluesky_upload_blob($uid, $photo);
if (!empty($blob) && count($images) < 4) {
$images[] = ['alt' => $image['description'] ?? '', 'image' => $blob];
} }
$photo = Photo::selectFirst([], ['id' => $image['id']]);
$blob = bluesky_upload_blob($uid, $photo);
if (empty($blob)) {
return [];
}
$images[] = ['alt' => $image['description'] ?? '', 'image' => $blob];
} }
if (!empty($images)) { if (!empty($images)) {
$record['embed'] = ['$type' => 'app.bsky.embed.images', 'images' => $images]; $record['embed'] = ['$type' => 'app.bsky.embed.images', 'images' => $images];
@ -778,13 +793,29 @@ function bluesky_add_embed(int $uid, array $msg, array $record): array
function bluesky_upload_blob(int $uid, array $photo): ?stdClass function bluesky_upload_blob(int $uid, array $photo): ?stdClass
{ {
$retrial = Worker::getRetrial();
$content = Photo::getImageForPhoto($photo); $content = Photo::getImageForPhoto($photo);
$picture = new Image($content, $photo['type']);
$height = $picture->getHeight();
$width = $picture->getWidth();
$size = strlen($content);
$picture = Photo::resizeToFileSize($picture, BLUESKY_IMAGE_SIZE[$retrial]);
$new_height = $picture->getHeight();
$new_width = $picture->getWidth();
$content = $picture->asString();
$new_size = strlen($content);
Logger::info('Uploading', ['uid' => $uid, 'retrial' => $retrial, 'height' => $new_height, 'width' => $new_width, 'size' => $new_size, 'orig-height' => $height, 'orig-width' => $width, 'orig-size' => $size]);
$data = bluesky_post($uid, '/xrpc/com.atproto.repo.uploadBlob', $content, ['Content-type' => $photo['type'], 'Authorization' => ['Bearer ' . bluesky_get_token($uid)]]); $data = bluesky_post($uid, '/xrpc/com.atproto.repo.uploadBlob', $content, ['Content-type' => $photo['type'], 'Authorization' => ['Bearer ' . bluesky_get_token($uid)]]);
if (empty($data)) { if (empty($data)) {
Logger::info('Uploading failed', ['uid' => $uid, 'retrial' => $retrial, 'height' => $new_height, 'width' => $new_width, 'size' => $new_size, 'orig-height' => $height, 'orig-width' => $width, 'orig-size' => $size]);
return null; return null;
} }
Logger::debug('Uploaded blob', ['return' => $data]); Logger::debug('Uploaded blob', ['return' => $data, 'uid' => $uid, 'retrial' => $retrial, 'height' => $new_height, 'width' => $new_width, 'size' => $new_size, 'orig-height' => $height, 'orig-width' => $width, 'orig-size' => $size]);
return $data->blob; return $data->blob;
} }

View file

@ -5,3 +5,4 @@
{{include file="field_input.tpl" field=$api_secret}} {{include file="field_input.tpl" field=$api_secret}}
{{include file="field_input.tpl" field=$access_token}} {{include file="field_input.tpl" field=$access_token}}
{{include file="field_input.tpl" field=$access_secret}} {{include file="field_input.tpl" field=$access_secret}}
{{include file="field_textarea.tpl" field=$status}}

View file

@ -47,12 +47,12 @@ use Friendica\Model\Post;
use Friendica\Core\Config\Util\ConfigFileManager; use Friendica\Core\Config\Util\ConfigFileManager;
use Friendica\Model\Photo; use Friendica\Model\Photo;
use Friendica\Object\Image; use Friendica\Object\Image;
use Friendica\Util\Images;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\HandlerStack; use GuzzleHttp\HandlerStack;
use GuzzleHttp\Subscriber\Oauth\Oauth1; use GuzzleHttp\Subscriber\Oauth\Oauth1;
const TWITTER_MAX_IMAGE_SIZE = 500000; const TWITTER_IMAGE_SIZE = [2000000, 1000000, 500000, 100000, 50000];
function twitter_install() function twitter_install()
{ {
@ -116,6 +116,15 @@ function twitter_settings(array &$data)
$access_token = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'twitter', 'access_token'); $access_token = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'twitter', 'access_token');
$access_secret = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'twitter', 'access_secret'); $access_secret = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'twitter', 'access_secret');
$last_status = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'twitter', 'last_status');
if (empty($last_status)) {
$status = DI::l10n()->t('No status.');
} elseif (!empty($last_status['code'])) {
$status = print_r($last_status, true);
} else {
$status = print_r($last_status, true);
}
$t = Renderer::getMarkupTemplate('connector_settings.tpl', 'addon/twitter/'); $t = Renderer::getMarkupTemplate('connector_settings.tpl', 'addon/twitter/');
$html = Renderer::replaceMacros($t, [ $html = Renderer::replaceMacros($t, [
'$enable' => ['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.')], '$enable' => ['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.')],
@ -125,6 +134,7 @@ function twitter_settings(array &$data)
'$access_token' => ['twitter-access-token', DI::l10n()->t('Access Token'), $access_token], '$access_token' => ['twitter-access-token', DI::l10n()->t('Access Token'), $access_token],
'$access_secret' => ['twitter-access-secret', DI::l10n()->t('Access Secret'), $access_secret], '$access_secret' => ['twitter-access-secret', DI::l10n()->t('Access Secret'), $access_secret],
'$help' => DI::l10n()->t('Each user needs to register their own app to be able to post to Twitter. Please visit https://developer.twitter.com/en/portal/projects-and-apps to register a project. Inside the project you then have to register an app. You will find the needed data for the connector on the page "Keys and token" in the app settings.'), '$help' => DI::l10n()->t('Each user needs to register their own app to be able to post to Twitter. Please visit https://developer.twitter.com/en/portal/projects-and-apps to register a project. Inside the project you then have to register an app. You will find the needed data for the connector on the page "Keys and token" in the app settings.'),
'$status' => ['twitter-status', DI::l10n()->t('Last Status'), $status, '', '', 'readonly'],
]); ]);
$data = [ $data = [
@ -216,16 +226,19 @@ function twitter_post_hook(array &$b)
if (!empty($msgarr['images']) || !empty($msgarr['remote_images'])) { if (!empty($msgarr['images']) || !empty($msgarr['remote_images'])) {
Logger::info('Got images', ['id' => $b['id'], 'images' => $msgarr['images'] ?? []]); Logger::info('Got images', ['id' => $b['id'], 'images' => $msgarr['images'] ?? []]);
$retrial = Worker::getRetrial();
if ($retrial > 4) {
return;
}
foreach ($msgarr['images'] ?? [] as $image) { foreach ($msgarr['images'] ?? [] as $image) {
if (count($media_ids) == 4) { if (count($media_ids) == 4) {
continue; continue;
} }
try { try {
$media_ids[] = twitter_upload_image($b['uid'], $image, $b); $media_ids[] = twitter_upload_image($b['uid'], $image, $retrial);
} catch (\Throwable $th) { } catch (RequestException $exception) {
Logger::warning('Error while uploading image', ['image' => $image, 'code' => $th->getCode(), 'message' => $th->getMessage()]); Logger::warning('Error while uploading image', ['image' => $image, 'code' => $exception->getCode(), 'message' => $exception->getMessage()]);
// Currently don't defer to avoid a loop. Worker::defer();
//Worker::defer();
return; return;
} }
} }
@ -238,9 +251,17 @@ function twitter_post_hook(array &$b)
try { try {
$id = twitter_post_status($b['uid'], $part, $media_ids, $in_reply_to_tweet_id); $id = twitter_post_status($b['uid'], $part, $media_ids, $in_reply_to_tweet_id);
Logger::info('twitter_post send', ['part' => $key, 'id' => $b['id'], 'result' => $id]); Logger::info('twitter_post send', ['part' => $key, 'id' => $b['id'], 'result' => $id]);
} catch (\Throwable $th) { } catch (RequestException $exception) {
Logger::warning('Error while posting message', ['part' => $key, 'id' => $b['id'], 'code' => $th->getCode(), 'message' => $th->getMessage()]); Logger::warning('Error while posting message', ['part' => $key, 'id' => $b['id'], 'code' => $exception->getCode(), 'message' => $exception->getMessage()]);
$status = [
'code' => $exception->getCode(),
'reason' => $exception->getResponse()->getReasonPhrase(),
'content' => $exception->getMessage()
];
DI::pConfig()->set($b['uid'], 'twitter', 'last_status', $status);
if ($key == 0) {
Worker::defer(); Worker::defer();
}
break; break;
} }
@ -264,7 +285,7 @@ function twitter_post_status(int $uid, string $status, array $media_ids = [], st
return $response->data->id; return $response->data->id;
} }
function twitter_upload_image(int $uid, array $image) function twitter_upload_image(int $uid, array $image, int $retrial)
{ {
if (!empty($image['id'])) { if (!empty($image['id'])) {
$photo = Photo::selectFirst([], ['id' => $image['id']]); $photo = Photo::selectFirst([], ['id' => $image['id']]);
@ -274,14 +295,20 @@ function twitter_upload_image(int $uid, array $image)
$picturedata = Photo::getImageForPhoto($photo); $picturedata = Photo::getImageForPhoto($photo);
$type = Images::getMimeTypeByData($picturedata, $photo['filename'], $photo['type']); $picture = new Image($picturedata, $photo['type']);
$height = $picture->getHeight();
$picture = Photo::resizeToFileSize(new Image($picturedata, $type), TWITTER_MAX_IMAGE_SIZE); $width = $picture->getWidth();
$size = strlen($picturedata);
$picture = Photo::resizeToFileSize($picture, TWITTER_IMAGE_SIZE[$retrial]);
$new_height = $picture->getHeight();
$new_width = $picture->getWidth();
$picturedata = $picture->asString(); $picturedata = $picture->asString();
$new_size = strlen($picturedata);
Logger::info('Uploading', ['uid' => $uid, 'size' => strlen($picturedata), 'type' => @getimagesizefromstring($picturedata), 'photo' => $photo]); Logger::info('Uploading', ['uid' => $uid, 'retrial' => $retrial, 'height' => $new_height, 'width' => $new_width, 'size' => $new_size, 'orig-height' => $height, 'orig-width' => $width, 'orig-size' => $size, 'image' => $image]);
$media = twitter_post($uid, 'https://upload.twitter.com/1.1/media/upload.json', 'form_params', ['media' => base64_encode($picturedata)]); $media = twitter_post($uid, 'https://upload.twitter.com/1.1/media/upload.json', 'form_params', ['media' => base64_encode($picturedata)]);
Logger::info('Uploading done', ['uid' => $uid, 'retrial' => $retrial, 'height' => $new_height, 'width' => $new_width, 'size' => $new_size, 'orig-height' => $height, 'orig-width' => $width, 'orig-size' => $size, 'image' => $image]);
if (isset($media->media_id_string)) { if (isset($media->media_id_string)) {
$media_id = $media->media_id_string; $media_id = $media->media_id_string;
@ -323,6 +350,14 @@ function twitter_post(int $uid, string $url, string $type, array $data): stdClas
$response = $client->post($url, ['auth' => 'oauth', $type => $data]); $response = $client->post($url, ['auth' => 'oauth', $type => $data]);
$status = [
'code' => $response->getStatusCode(),
'reason' => $response->getReasonPhrase(),
'content' => $response->getBody()->getContents()
];
DI::pConfig()->set($uid, 'twitter', 'last_status', $status);
$content = json_decode($response->getBody()->getContents()) ?? new stdClass; $content = json_decode($response->getBody()->getContents()) ?? new stdClass;
Logger::debug('Success', ['content' => $content]); Logger::debug('Success', ['content' => $content]);
return $content; return $content;