diff --git a/twitter/lang/C/messages.po b/twitter/lang/C/messages.po index b1236421..d943b4f1 100644 --- a/twitter/lang/C/messages.po +++ b/twitter/lang/C/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-10-08 22:25-0400\n" +"POT-Creation-Date: 2021-11-23 18:33-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,27 +17,27 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: twitter.php:224 +#: twitter.php:213 msgid "Post to Twitter" msgstr "" -#: twitter.php:269 +#: twitter.php:258 msgid "" "You submitted an empty PIN, please Sign In with Twitter again to get a new " "one." msgstr "" -#: twitter.php:329 twitter.php:333 +#: twitter.php:318 twitter.php:322 msgid "Twitter Import/Export/Mirror" msgstr "" -#: twitter.php:340 +#: twitter.php:329 msgid "" "No consumer key pair for Twitter found. Please contact your site " "administrator." msgstr "" -#: twitter.php:352 +#: twitter.php:341 msgid "" "At this Friendica instance the Twitter addon was enabled but you have not " "yet connected your account to your Twitter account. To do so click the " @@ -46,42 +46,42 @@ msgid "" "be posted to Twitter." msgstr "" -#: twitter.php:353 +#: twitter.php:342 msgid "Log in with Twitter" msgstr "" -#: twitter.php:355 +#: twitter.php:344 msgid "Copy the PIN from Twitter here" msgstr "" -#: twitter.php:360 twitter.php:415 twitter.php:803 +#: twitter.php:349 twitter.php:404 twitter.php:924 msgid "Save Settings" msgstr "" -#: twitter.php:362 twitter.php:417 +#: twitter.php:351 twitter.php:406 msgid "An error occured: " msgstr "" -#: twitter.php:379 +#: twitter.php:368 msgid "Currently connected to: " msgstr "" -#: twitter.php:380 twitter.php:390 +#: twitter.php:369 twitter.php:379 msgid "Disconnect" msgstr "" -#: twitter.php:397 +#: twitter.php:386 msgid "Allow posting to Twitter" msgstr "" -#: twitter.php:397 +#: twitter.php:386 msgid "" "If enabled all your public 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." msgstr "" -#: twitter.php:400 +#: twitter.php:389 msgid "" "Note: Due to your privacy settings (Hide your profile " "details from unknown viewers?) the link potentially included in public " @@ -89,23 +89,23 @@ msgid "" "the visitor that the access to your profile has been restricted." msgstr "" -#: twitter.php:403 +#: twitter.php:392 msgid "Send public postings to Twitter by default" msgstr "" -#: twitter.php:406 +#: twitter.php:395 msgid "Mirror all posts from twitter that are no replies" msgstr "" -#: twitter.php:409 +#: twitter.php:398 msgid "Import the remote timeline" msgstr "" -#: twitter.php:412 +#: twitter.php:401 msgid "Automatically create contacts" msgstr "" -#: twitter.php:412 +#: twitter.php:401 msgid "" "This will automatically create a contact in Friendica as soon as you receive " "a message from an existing contact via the Twitter network. If you do not " @@ -113,15 +113,25 @@ msgid "" "from whom you would like to see posts here." msgstr "" -#: twitter.php:805 +#: twitter.php:557 +msgid "" +"Please connect a Twitter account in your Social Network settings to import " +"Twitter posts." +msgstr "" + +#: twitter.php:564 +msgid "Twitter post not found." +msgstr "" + +#: twitter.php:926 msgid "Consumer key" msgstr "" -#: twitter.php:806 +#: twitter.php:927 msgid "Consumer secret" msgstr "" -#: twitter.php:1002 +#: twitter.php:1123 #, php-format msgid "%s on Twitter" msgstr "" diff --git a/twitter/twitter.php b/twitter/twitter.php index 8b7451b8..e3e664f3 100644 --- a/twitter/twitter.php +++ b/twitter/twitter.php @@ -114,6 +114,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('item_by_link' , __FILE__, 'twitter_item_by_link'); Hook::register('parse_link' , __FILE__, 'twitter_parse_link'); Logger::info("installed twitter"); } @@ -411,25 +412,35 @@ function twitter_settings(App $a, &$s) function twitter_hook_fork(App $a, array &$b) { + DI::logger()->debug('twitter_hook_fork', $b); + if ($b['name'] != 'notifier_normal') { return; } $post = $b['data']; - // Deleting and editing is not supported by the addon (deleting could, but isn't by now) - if ($post['deleted'] || ($post['created'] !== $post['edited'])) { + // Deletion checks are done in twitter_delete_item() + if ($post['deleted']) { + return; + } + + // Editing is not supported by the addon + if ($post['created'] !== $post['edited']) { + DI::logger()->info('Editing is not supported by the addon'); $b['execute'] = false; return; } // if post comes from twitter don't send it back if (($post['extid'] == Protocol::TWITTER) || twitter_get_id($post['extid'])) { + DI::logger()->info('If post comes from twitter don\'t send it back'); $b['execute'] = false; return; } if (substr($post['app'], 0, 7) == 'Twitter') { + DI::logger()->info('No Twitter app'); $b['execute'] = false; return; } @@ -444,10 +455,11 @@ function twitter_hook_fork(App $a, array &$b) } else { // Comments are never exported when we don't import the twitter timeline if (!strstr($post['postopts'], 'twitter') || ($post['parent'] != $post['id']) || $post['private']) { + DI::logger()->info('Comments are never exported when we don\'t import the twitter timeline'); $b['execute'] = false; return; } - } + } } function twitter_post_local(App $a, array &$b) @@ -482,7 +494,7 @@ function twitter_post_local(App $a, array &$b) function twitter_probe_detect(App $a, array &$hookData) { // Don't overwrite an existing result - if ($hookData['result']) { + if (isset($hookData['result'])) { return; } @@ -494,6 +506,13 @@ function twitter_probe_detect(App $a, array &$hookData) if (preg_match('=([^@]+)@(?:mobile\.)?twitter\.com$=i', $hookData['uri'], $matches)) { $nick = $matches[1]; } elseif (preg_match('=^https?://(?:mobile\.)?twitter\.com/(.+)=i', $hookData['uri'], $matches)) { + if (strpos($matches[1], '/') !== false) { + // Status case: https://twitter.com//status/ + // Not a contact + $hookData['result'] = false; + return; + } + $nick = $matches[1]; } else { return; @@ -506,6 +525,52 @@ function twitter_probe_detect(App $a, array &$hookData) } } +function twitter_item_by_link(App $a, array &$hookData) +{ + // Don't overwrite an existing result + if (isset($hookData['item_id'])) { + return; + } + + // Relevancy check + if (!preg_match('#^https?://(?:mobile\.|www\.)?twitter.com/[^/]+/status/(\d+).*#', $hookData['uri'], $matches)) { + return; + } + + // From now on, any early return should abort the whole chain since we've established it was a Twitter URL + $hookData['item_id'] = false; + + // Node-level configuration check + if (empty(DI::config()->get('twitter', 'consumerkey')) || empty(DI::config()->get('twitter', 'consumersecret'))) { + return; + } + + // No anonymous import + if (!$hookData['uid']) { + return; + } + + if ( + empty(DI::pConfig()->get($hookData['uid'], 'twitter', 'oauthtoken')) + || empty(DI::pConfig()->get($hookData['uid'], 'twitter', 'oauthsecret')) + ) { + notice(DI::l10n()->t('Please connect a Twitter account in your Social Network settings to import Twitter posts.')); + return; + } + + $status = twitter_statuses_show($matches[1]); + + if (empty($status->id_str)) { + notice(DI::l10n()->t('Twitter post not found.')); + return; + } + + $item = twitter_createpost($a, $hookData['uid'], $status, [], true, false, false); + if (!empty($item)) { + $hookData['item_id'] = Item::insert($item); + } +} + function twitter_api_post(string $apiPath, string $pid, int $uid): ?bool { if (empty($pid)) { @@ -568,9 +633,16 @@ function twitter_get_id(string $uri) function twitter_post_hook(App $a, array &$b) { + DI::logger()->info('twitter_post_hook', $b); + + if ($b['deleted']) { + twitter_delete_item($b); + return; + } + // Post to Twitter if (!DI::pConfig()->get($b["uid"], 'twitter', 'import') - && ($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited']))) { + && ($b['private'] || ($b['created'] !== $b['edited']))) { return; } @@ -620,39 +692,21 @@ function twitter_post_hook(App $a, array &$b) } } - /** - * @TODO This can't work at the moment: - * - Posts created on Friendica and mirrored to Twitter don't have a Twitter ID - * - Posts created on Twitter and mirrored on Friendica do not trigger the notifier hook this is part of. - */ - //if (($b['verb'] == Activity::POST) && $b['deleted']) { - // twitter_api_post('statuses/destroy', twitter_get_id($thr_parent['uri']), $b['uid']); - //} - if ($b['verb'] == Activity::LIKE) { Logger::info('Like', ['uid' => $b['uid'], 'id' => twitter_get_id($b["thr-parent"])]); - twitter_api_post($b['deleted'] ? 'favorites/destroy' : 'favorites/create', twitter_get_id($b["thr-parent"]), $b["uid"]); + twitter_api_post('favorites/create', twitter_get_id($b['thr-parent']), $b['uid']); return; } if ($b['verb'] == Activity::ANNOUNCE) { Logger::info('Retweet', ['uid' => $b['uid'], 'id' => twitter_get_id($b["thr-parent"])]); - if ($b['deleted']) { - /** - * @TODO This can't work at the moment: - * - Twitter post reshare removal doesn't seem to trigger the notifier hook this is part of - */ - //twitter_api_post('statuses/destroy', twitter_get_id($thr_parent['extid']), $b['uid']); - } else { - twitter_retweet($b["uid"], twitter_get_id($b["thr-parent"])); - } - + twitter_retweet($b['uid'], twitter_get_id($b['thr-parent'])); return; } - if ($b['deleted'] || ($b['created'] !== $b['edited'])) { + if ($b['created'] !== $b['edited']) { return; } @@ -789,6 +843,71 @@ function twitter_post_hook(App $a, array &$b) } } +function twitter_delete_item(array $item) +{ + if (!$item['deleted']) { + return; + } + + if ($item['parent'] != $item['id']) { + Logger::debug('Deleting comment/announce', ['item' => $item]); + + // Looking if it's a reply to a twitter post + if (!twitter_get_id($item['parent-uri']) && + !twitter_get_id($item['extid']) && + !twitter_get_id($item['thr-parent'])) { + Logger::info('No twitter post', ['parent' => $item['parent']]); + return; + } + + $condition = ['uri' => $item['thr-parent'], 'uid' => $item['uid']]; + $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' => $item['thr-parent']]); + return; + } + + Logger::debug('Parent found', ['parent' => $thr_parent]); + } else { + if (!strstr($item['extid'], 'twitter')) { + DI::logger()->info('Not a Twitter post', ['extid' => $item['extid']]); + return; + } + + // Don't delete if the post doesn't belong to us. + // This is a check for forum postings + $self = DBA::selectFirst('contact', ['id'], ['uid' => $item['uid'], 'self' => true]); + if ($item['contact-id'] != $self['id']) { + DI::logger()->info('Don\'t delete if the post doesn\'t belong to the user', ['contact-id' => $item['contact-id'], 'self' => $self['id']]); + return; + } + } + + /** + * @TODO Remaining caveat: Comments posted on Twitter and imported in Friendica do not trigger any Notifier task, + * possibly because they are private to the user and don't require any remote deletion notifications sent. + * Comments posted on Friendica and mirrored on Twitter trigger the Notifier task and the Twitter counter-part + * will be deleted accordingly. + */ + if ($item['verb'] == Activity::POST) { + Logger::info('Delete post/comment', ['uid' => $item['uid'], 'id' => twitter_get_id($item['extid'])]); + twitter_api_post('statuses/destroy', twitter_get_id($item['extid']), $item['uid']); + return; + } + + if ($item['verb'] == Activity::LIKE) { + Logger::info('Unlike', ['uid' => $item['uid'], 'id' => twitter_get_id($item['thr-parent'])]); + twitter_api_post('favorites/destroy', twitter_get_id($item['thr-parent']), $item['uid']); + return; + } + + if ($item['verb'] == Activity::ANNOUNCE && !empty($thr_parent['uri'])) { + Logger::info('Unretweet', ['uid' => $item['uid'], 'extid' => $thr_parent['uri'], 'id' => twitter_get_id($thr_parent['uri'])]); + twitter_api_post('statuses/unretweet', twitter_get_id($thr_parent['uri']), $item['uid']); + return; + } +} + function twitter_addon_admin_post(App $a) { $consumerkey = trim($_POST['consumerkey'] ?? '');