Merge pull request #1211 from MrPetovan/task/11022-twitter-support-delete

[twitter] Add support for unretweet and post/comment deletion
This commit is contained in:
Philipp 2021-11-27 13:29:19 +01:00 committed by GitHub
commit 13ce3aa0a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 178 additions and 49 deletions

View file

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 <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."
msgstr ""
#: twitter.php:400
#: twitter.php:389
msgid ""
"<strong>Note</strong>: Due to your privacy settings (<em>Hide your profile "
"details from unknown viewers?</em>) 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 ""

View file

@ -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,6 +455,7 @@ 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;
}
@ -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/<nick>/status/<status id>
// 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'] ?? '');