]> git.mxchange.org Git - friendica-addons.git/blobdiff - twitter/twitter.php
Merge pull request 'Bluesky: Tags are now supported' (#1438) from heluecht/friendica...
[friendica-addons.git] / twitter / twitter.php
index d0f9b5bfef967a37becbf9f695b4c2bb7f175415..b01345b74959931fc7257890004afe58f3419fd5 100644 (file)
@@ -46,19 +46,23 @@ use Friendica\Model\Item;
 use Friendica\Model\Post;
 use Friendica\Core\Config\Util\ConfigFileManager;
 use Friendica\Model\Photo;
+use Friendica\Object\Image;
 use GuzzleHttp\Client;
+use GuzzleHttp\Exception\RequestException;
 use GuzzleHttp\HandlerStack;
 use GuzzleHttp\Subscriber\Oauth\Oauth1;
 
+const TWITTER_IMAGE_SIZE = [2000000, 1000000, 500000, 100000, 50000];
+
 function twitter_install()
 {
-       Hook::register('load_config'            , __FILE__, 'twitter_load_config');
-       Hook::register('connector_settings'     , __FILE__, 'twitter_settings');
+       Hook::register('load_config', __FILE__, 'twitter_load_config');
+       Hook::register('connector_settings', __FILE__, 'twitter_settings');
        Hook::register('connector_settings_post', __FILE__, 'twitter_settings_post');
-       Hook::register('hook_fork'              , __FILE__, 'twitter_hook_fork');
-       Hook::register('post_local'             , __FILE__, 'twitter_post_local');
-       Hook::register('notifier_normal'        , __FILE__, 'twitter_post_hook');
-       Hook::register('jot_networks'           , __FILE__, 'twitter_jot_nets');
+       Hook::register('hook_fork', __FILE__, 'twitter_hook_fork');
+       Hook::register('post_local', __FILE__, 'twitter_post_local');
+       Hook::register('notifier_normal', __FILE__, 'twitter_post_hook');
+       Hook::register('jot_networks', __FILE__, 'twitter_jot_nets');
 }
 
 function twitter_load_config(ConfigFileManager $loader)
@@ -90,12 +94,25 @@ function twitter_settings_post()
                return;
        }
 
+       $api_key       = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'twitter', 'api_key');
+       $api_secret    = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'twitter', 'api_secret');
+       $access_token  = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'twitter', 'access_token');
+       $access_secret = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'twitter', 'access_secret');
+
        DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'twitter', 'post',            (bool)$_POST['twitter-enable']);
        DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'twitter', 'post_by_default', (bool)$_POST['twitter-default']);
        DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'twitter', 'api_key',         $_POST['twitter-api-key']);
        DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'twitter', 'api_secret',      $_POST['twitter-api-secret']);
        DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'twitter', 'access_token',    $_POST['twitter-access-token']);
        DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'twitter', 'access_secret',   $_POST['twitter-access-secret']);
+
+       if (
+               empty(DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'twitter', 'last_status')) ||
+               ($api_key != $_POST['twitter-api-key']) || ($api_secret != $_POST['twitter-api-secret']) ||
+               ($access_token != $_POST['twitter-access-token']) || ($access_secret != $_POST['twitter-access-secret'])
+       ) {
+               twitter_test_connection(DI::userSession()->getLocalUserId());
+       }
 }
 
 function twitter_settings(array &$data)
@@ -112,6 +129,14 @@ function twitter_settings(array &$data)
        $access_token  = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'twitter', 'access_token');
        $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['code']) && !empty($last_status['reason'])) {
+               $status_title = sprintf('%d - %s', $last_status['code'], $last_status['reason']);
+       } else {
+               $status_title = DI::l10n()->t('No status.');
+       }
+       $status_content = $last_status['content'] ?? '';
+
        $t    = Renderer::getMarkupTemplate('connector_settings.tpl', 'addon/twitter/');
        $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.')],
@@ -120,7 +145,9 @@ function twitter_settings(array &$data)
                '$api_secret'    => ['twitter-api-secret', DI::l10n()->t('API Secret'), $api_secret],
                '$access_token'  => ['twitter-access-token', DI::l10n()->t('Access Token'), $access_token],
                '$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_title'  => ['twitter-status-title', DI::l10n()->t('Last Status Summary'), $status_title, '', '', 'readonly'],
+               '$status'        => ['twitter-status', DI::l10n()->t('Last Status Content'), $status_content, '', '', 'readonly'],
        ]);
 
        $data = [
@@ -142,8 +169,10 @@ function twitter_hook_fork(array &$b)
 
        $post = $b['data'];
 
-       if ($post['deleted'] || $post['private'] || ($post['created'] !== $post['edited']) ||
-               !strstr($post['postopts'], 'twitter') || ($post['gravity'] != Item::GRAVITY_PARENT)) {
+       if (
+               $post['deleted'] || $post['private'] || ($post['created'] !== $post['edited']) ||
+               !strstr($post['postopts'], 'twitter') || ($post['gravity'] != Item::GRAVITY_PARENT)
+       ) {
                $b['execute'] = false;
                return;
        }
@@ -210,17 +239,21 @@ function twitter_post_hook(array &$b)
        if (!empty($msgarr['images']) || !empty($msgarr['remote_images'])) {
                Logger::info('Got images', ['id' => $b['id'], 'images' => $msgarr['images'] ?? []]);
 
+               $retrial = Worker::getRetrial();
+               if ($retrial > 4) {
+                       return;
+               }
                foreach ($msgarr['images'] ?? [] as $image) {
                        if (count($media_ids) == 4) {
                                continue;
                        }
                        try {
-                               $media_ids[] = twitter_upload_image($b['uid'], $image, $b);
-                       } catch (\Throwable $th) {
-                               Logger::warning('Error while uploading image', ['image' => $image, 'code' => $th->getCode(), 'message' => $th->getMessage()]);
-                Worker::defer();
-                break;
-            }
+                               $media_ids[] = twitter_upload_image($b['uid'], $image, $retrial);
+                       } catch (RequestException $exception) {
+                               Logger::warning('Error while uploading image', ['image' => $image, 'code' => $exception->getCode(), 'message' => $exception->getMessage()]);
+                               Worker::defer();
+                               return;
+                       }
                }
        }
 
@@ -231,9 +264,17 @@ function twitter_post_hook(array &$b)
                try {
                        $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]);
-               } catch (\Throwable $th) {
-                       Logger::warning('Error while posting message', ['part' => $key, 'id' => $b['id'], 'code' => $th->getCode(), 'message' => $th->getMessage()]);
-                       Worker::defer();
+               } catch (RequestException $exception) {
+                       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();
+                       }
                        break;
                }
 
@@ -257,7 +298,7 @@ function twitter_post_status(int $uid, string $status, array $media_ids = [], st
        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'])) {
                $photo = Photo::selectFirst([], ['id' => $image['id']]);
@@ -265,13 +306,22 @@ function twitter_upload_image(int $uid, array $image)
                $photo = Photo::createPhotoForExternalResource($image['url']);
        }
 
-       $parameters = [
-               'name' => 'media_data',
-               'contents' => base64_encode(Photo::getImageForPhoto($photo))
-       ];
+       $picturedata = Photo::getImageForPhoto($photo);
+
+       $picture = new Image($picturedata, $photo['type']);
+       $height  = $picture->getHeight();
+       $width   = $picture->getWidth();
+       $size    = strlen($picturedata);
 
-    Logger::info('Uploading', ['uid' => $uid, 'image' => $image]);
-       $media = twitter_post($uid, 'https://upload.twitter.com/1.1/media/upload.json', 'multipart', [$parameters]);
+       $picture     = Photo::resizeToFileSize($picture, TWITTER_IMAGE_SIZE[$retrial]);
+       $new_height  = $picture->getHeight();
+       $new_width   = $picture->getWidth();
+       $picturedata = $picture->asString();
+       $new_size    = strlen($picturedata);
+
+       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)]);
+       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)) {
                $media_id = $media->media_id_string;
@@ -287,7 +337,7 @@ function twitter_upload_image(int $uid, array $image)
                        Logger::info('Metadata create', ['uid' => $uid, 'data' => $data, 'return' => $ret]);
                }
        } else {
-               Logger::error('Failed upload', ['uid' => $uid, 'image' => $image['url'], 'return' => $media]);
+               Logger::error('Failed upload', ['uid' => $uid, 'size' => strlen($picturedata), 'image' => $image['url'], 'return' => $media]);
                throw new Exception('Failed upload of ' . $image['url']);
        }
 
@@ -297,7 +347,7 @@ function twitter_upload_image(int $uid, array $image)
 function twitter_post(int $uid, string $url, string $type, array $data): stdClass
 {
        $stack = HandlerStack::create();
-       
+
        $middleware = new Oauth1([
                'consumer_key'    => DI::pConfig()->get($uid, 'twitter', 'api_key'),
                'consumer_secret' => DI::pConfig()->get($uid, 'twitter', 'api_secret'),
@@ -306,14 +356,60 @@ function twitter_post(int $uid, string $url, string $type, array $data): stdClas
        ]);
 
        $stack->push($middleware);
-       
+
        $client = new Client([
                'handler' => $stack
        ]);
 
        $response = $client->post($url, ['auth' => 'oauth', $type => $data]);
+       $body     = $response->getBody()->getContents();
+
+       $status = [
+               'code'    => $response->getStatusCode(),
+               'reason'  => $response->getReasonPhrase(),
+               'content' => $body
+       ];
 
-       $content = json_decode($response->getBody()->getContents()) ?? new stdClass;
+       DI::pConfig()->set($uid, 'twitter', 'last_status', $status);
+
+       $content = json_decode($body) ?? new stdClass;
        Logger::debug('Success', ['content' => $content]);
        return $content;
 }
+
+function twitter_test_connection(int $uid)
+{
+       $stack = HandlerStack::create();
+
+       $middleware = new Oauth1([
+               'consumer_key'    => DI::pConfig()->get($uid, 'twitter', 'api_key'),
+               'consumer_secret' => DI::pConfig()->get($uid, 'twitter', 'api_secret'),
+               'token'           => DI::pConfig()->get($uid, 'twitter', 'access_token'),
+               'token_secret'    => DI::pConfig()->get($uid, 'twitter', 'access_secret'),
+       ]);
+
+       $stack->push($middleware);
+
+       $client = new Client([
+               'handler' => $stack
+       ]);
+
+       try {
+               $response = $client->get('https://api.twitter.com/2/users/me', ['auth' => 'oauth']);
+               $status = [
+                       'code'   => $response->getStatusCode(),
+                       'reason'  => $response->getReasonPhrase(),
+                       'content' => $response->getBody()->getContents()
+               ];
+               DI::pConfig()->set(1, 'twitter', 'last_status',  $status);
+               Logger::info('Test successful', ['uid' => $uid]);
+       } catch (RequestException $exception) {
+               $status = [
+                       'code'    => $exception->getCode(),
+                       'reason'  => $exception->getResponse()->getReasonPhrase(),
+                       'content' => $exception->getMessage()
+               ];
+               DI::pConfig()->set(1, 'twitter', 'last_status',  $status);
+               Logger::info('Test failed', ['uid' => $uid]);
+       }
+}