From 2f2b75ba5015d05279f291f110f2781800938d34 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Tue, 23 Nov 2021 17:51:12 -0500 Subject: [PATCH 1/4] [twitter] Improve probe_detect hook function - Prevent tweet URLs to be considered as contact URLs --- twitter/twitter.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/twitter/twitter.php b/twitter/twitter.php index 8b7451b82..f31efe976 100644 --- a/twitter/twitter.php +++ b/twitter/twitter.php @@ -482,7 +482,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 +494,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; From 93e5d97fdd24528215151be86fc161facf690709 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Tue, 23 Nov 2021 17:52:52 -0500 Subject: [PATCH 2/4] [twitter] Add item_by_link hook function --- twitter/twitter.php | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/twitter/twitter.php b/twitter/twitter.php index f31efe976..d93639bc7 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"); } @@ -513,6 +514,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)) { From 86a204af7d935c77afe2f20a5a20beb3998be6d3 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Tue, 23 Nov 2021 17:59:08 -0500 Subject: [PATCH 3/4] [twitter] Add support for unretweet and post/comment deletion 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. --- twitter/twitter.php | 115 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 90 insertions(+), 25 deletions(-) diff --git a/twitter/twitter.php b/twitter/twitter.php index d93639bc7..e3e664f36 100644 --- a/twitter/twitter.php +++ b/twitter/twitter.php @@ -412,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; } @@ -445,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) @@ -622,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; } @@ -674,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; } @@ -843,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'] ?? ''); From f90b9c6c86eea17a67873ea491aa423f7ce14642 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Tue, 23 Nov 2021 18:33:50 -0500 Subject: [PATCH 4/4] [twitter] Updated main translation file after adding some strings --- twitter/lang/C/messages.po | 56 ++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/twitter/lang/C/messages.po b/twitter/lang/C/messages.po index b1236421f..d943b4f1d 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 ""