From a26e90b2024ca3dbdccfd44ae5a33e11aafa7ae2 Mon Sep 17 00:00:00 2001 From: "Dr. Tobias Quathamer" Date: Fri, 5 Jan 2024 00:12:20 +0100 Subject: [PATCH 1/3] Initial commit of url_replace addon. This closes https://github.com/friendica/friendica/issues/13220 --- url_replace/LICENSE.md | 21 ++++++ url_replace/README.md | 17 +++++ url_replace/lang/C/messages.po | 50 +++++++++++++ url_replace/templates/admin.tpl | 5 ++ url_replace/url_replace.php | 127 ++++++++++++++++++++++++++++++++ 5 files changed, 220 insertions(+) create mode 100644 url_replace/LICENSE.md create mode 100644 url_replace/README.md create mode 100644 url_replace/lang/C/messages.po create mode 100644 url_replace/templates/admin.tpl create mode 100644 url_replace/url_replace.php diff --git a/url_replace/LICENSE.md b/url_replace/LICENSE.md new file mode 100644 index 00000000..09dc8f64 --- /dev/null +++ b/url_replace/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright © 2024 Dr. Tobias Quathamer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/url_replace/README.md b/url_replace/README.md new file mode 100644 index 00000000..278de91b --- /dev/null +++ b/url_replace/README.md @@ -0,0 +1,17 @@ +# URL replace + +This addon will replace all occurrences of specified URLs with the address of +alternative servers in all displayed postings on a Friendica node. + +You can use this to switch from Twitter (or X) to a nitter instance, from +YouTube to an invidious instance, or from some news sites to 12ft.io. + +Note: If you are using the twitter connector on your server, the links to the +contacts profile pages will not be replaced by this addon. Only links in the +body of the postings are affected. + +## Why + +- Access a website without JavaScript enabled to prevent JavaScript analytics + and potential IP-based tracking +- Avoid seeing ads on YouTube videos diff --git a/url_replace/lang/C/messages.po b/url_replace/lang/C/messages.po new file mode 100644 index 00000000..c09ad6d9 --- /dev/null +++ b/url_replace/lang/C/messages.po @@ -0,0 +1,50 @@ +# ADDON url_replace +# Copyright (C) +# This file is distributed under the same license as the Friendica url_replace addon package. +# +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-01-05 00:06+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: url_replace.php:54 +msgid "Nitter server" +msgstr "" + +#: url_replace.php:56 +msgid "Specify the URL with protocol. The default is https://nitter.net." +msgstr "" + +#: url_replace.php:62 +msgid "Invidious server" +msgstr "" + +#: url_replace.php:64 +msgid "Specify the URL with protocol. The default is https://yewtu.be." +msgstr "" + +#: url_replace.php:70 +msgid "Sites which are accessed through 12ft.io" +msgstr "" + +#: url_replace.php:72 +msgid "Specify the URLs with protocol, one per line." +msgstr "" + +#: url_replace.php:76 +msgid "Save settings" +msgstr "" + +#: url_replace.php:125 +msgid "(URL replace addon enabled for X, YouTube and some news sites.)" +msgstr "" diff --git a/url_replace/templates/admin.tpl b/url_replace/templates/admin.tpl new file mode 100644 index 00000000..b99e6355 --- /dev/null +++ b/url_replace/templates/admin.tpl @@ -0,0 +1,5 @@ +{{include file="field_input.tpl" field=$nitter_server}} +{{include file="field_input.tpl" field=$invidious_server}} +{{include file="field_textarea.tpl" field=$twelvefeet_sites}} + +
diff --git a/url_replace/url_replace.php b/url_replace/url_replace.php new file mode 100644 index 00000000..51bc10fe --- /dev/null +++ b/url_replace/url_replace.php @@ -0,0 +1,127 @@ + + * Maintainer: Dr. Tobias Quathamer + */ +use Friendica\Core\Hook; +use Friendica\Core\Renderer; +use Friendica\DI; + +function url_replace_install() +{ + Hook::register('prepare_body_final', 'addon/url_replace/url_replace.php', 'url_replace_render'); +} + +/** + * Handle sent data from admin settings + */ +function url_replace_addon_admin_post() +{ + DI::config()->set('url_replace', 'nitter_server', rtrim(trim($_POST['nitter_server']), '/')); + DI::config()->set('url_replace', 'invidious_server', rtrim(trim($_POST['invidious_server']), '/')); + // Convert twelvefeet_sites into an array before setting the new value + $twelvefeet_sites = explode(PHP_EOL, $_POST['twelvefeet_sites']); + // Normalize URLs by using lower case, removing a trailing slash and whitespace + $twelvefeet_sites = array_map(fn ($value): string => rtrim(trim(strtolower($value)), '/'), $twelvefeet_sites); + // Do not store empty lines or duplicates + $twelvefeet_sites = array_filter($twelvefeet_sites, fn ($value): bool => !empty($value)); + $twelvefeet_sites = array_unique($twelvefeet_sites); + // Ensure a protocol and default to HTTPS + $twelvefeet_sites = array_map( + fn ($value): string => substr($value, 0, 4) !== 'http' ? 'https://'.$value : $value, + $twelvefeet_sites + ); + asort($twelvefeet_sites); + DI::config()->set('url_replace', 'twelvefeet_sites', $twelvefeet_sites); +} + +/** + * Hook into admin settings to enable choosing a different server + * for twitter, youtube, and news sites. + */ +function url_replace_addon_admin(string &$o) +{ + $nitter_server = DI::config()->get('url_replace', 'nitter_server'); + $invidious_server = DI::config()->get('url_replace', 'invidious_server'); + $twelvefeet_sites = implode(PHP_EOL, DI::config()->get('url_replace', 'twelvefeet_sites')); + $t = Renderer::getMarkupTemplate('admin.tpl', 'addon/url_replace/'); + $o = Renderer::replaceMacros($t, [ + '$nitter_server' => [ + 'nitter_server', + DI::l10n()->t('Nitter server'), + $nitter_server, + DI::l10n()->t('Specify the URL with protocol. The default is https://nitter.net.'), + null, + 'placeholder="https://nitter.net"', + ], + '$invidious_server' => [ + 'invidious_server', + DI::l10n()->t('Invidious server'), + $invidious_server, + DI::l10n()->t('Specify the URL with protocol. The default is https://yewtu.be.'), + null, + 'placeholder="https://yewtu.be"', + ], + '$twelvefeet_sites' => [ + 'twelvefeet_sites', + DI::l10n()->t('Sites which are accessed through 12ft.io'), + $twelvefeet_sites, + DI::l10n()->t('Specify the URLs with protocol, one per line.'), + null, + 'rows=6' + ], + '$submit' => DI::l10n()->t('Save settings'), + ]); +} + +/** + * Replace proprietary URLs with their specified counterpart + */ +function url_replace_render(array &$b) +{ + $replaced = false; + + $nitter_server = DI::config()->get('url_replace', 'nitter_server'); + if (empty($nitter_server)) { + $nitter_server = 'https://nitter.net'; + } + + $invidious_server = DI::config()->get('url_replace', 'invidious_server'); + if (empty($invidious_server)) { + $invidious_server = 'https://yewtu.be'; + } + + // Handle some of twitter and youtube + $replacements = [ + 'https://mobile.twitter.com' => $nitter_server, + 'https://twitter.com' => $nitter_server, + 'https://mobile.x.com' => $nitter_server, + 'https://x.com' => $nitter_server, + 'https://www.youtube.com' => $invidious_server, + 'https://youtube.com' => $invidious_server, + 'https://m.youtube.com' => $invidious_server, + 'https://youtu.be' => $invidious_server, + ]; + foreach ($replacements as $server => $replacement) { + if (strpos($b['html'], $server) !== false) { + $b['html'] = str_replace($server, $replacement, $b['html']); + $replaced = true; + } + } + + $twelvefeet_sites = DI::config()->get('url_replace', 'twelvefeet_sites'); + foreach ($twelvefeet_sites as $twelvefeet_site) { + if (strpos($b['html'], $twelvefeet_site) !== false) { + $b['html'] = str_replace($twelvefeet_site, 'https://12ft.io/'.$twelvefeet_site, $b['html']); + $replaced = true; + } + } + + + if ($replaced) { + $b['html'] .= '

' . DI::l10n()->t('(URL replace addon enabled for X, YouTube and some news sites.)') . '

'; + } +} From bddb7f4d49d1f36120b0054d2763aa7c89d03f17 Mon Sep 17 00:00:00 2001 From: "Dr. Tobias Quathamer" Date: Mon, 8 Jan 2024 10:36:21 +0100 Subject: [PATCH 2/3] Fix PHP warning and use quotes --- url_replace/url_replace.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/url_replace/url_replace.php b/url_replace/url_replace.php index 51bc10fe..4c61b6c2 100644 --- a/url_replace/url_replace.php +++ b/url_replace/url_replace.php @@ -71,7 +71,7 @@ function url_replace_addon_admin(string &$o) $twelvefeet_sites, DI::l10n()->t('Specify the URLs with protocol, one per line.'), null, - 'rows=6' + 'rows="6"' ], '$submit' => DI::l10n()->t('Save settings'), ]); @@ -113,6 +113,9 @@ function url_replace_render(array &$b) } $twelvefeet_sites = DI::config()->get('url_replace', 'twelvefeet_sites'); + if (empty($twelvefeet_sites)) { + $twelvefeet_sites = []; + } foreach ($twelvefeet_sites as $twelvefeet_site) { if (strpos($b['html'], $twelvefeet_site) !== false) { $b['html'] = str_replace($twelvefeet_site, 'https://12ft.io/'.$twelvefeet_site, $b['html']); From 13fd713b66cdce186f30cb70bdcf237814f961b5 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Fri, 12 Jan 2024 01:16:01 -0500 Subject: [PATCH 3/3] [various] Rename ICanHandleHttpResponses->getBody to getBodyString - Depends on https://github.com/friendica/friendica/pull/13826 --- bluesky/bluesky.php | 14 +++++++------- discourse/discourse.php | 4 ++-- dwpost/dwpost.php | 2 +- ijpost/ijpost.php | 2 +- libertree/libertree.php | 2 +- ljpost/ljpost.php | 2 +- mailstream/mailstream.php | 2 +- mastodoncustomemojis/mastodoncustomemojis.php | 2 +- openstreetmap/openstreetmap.php | 2 +- tumblr/tumblr.php | 10 +++++----- twitter/twitter.php | 4 ++-- webdav_storage/src/WebDav.php | 4 ++-- wppost/wppost.php | 2 +- 13 files changed, 26 insertions(+), 26 deletions(-) diff --git a/bluesky/bluesky.php b/bluesky/bluesky.php index dac95437..a2c85ad3 100644 --- a/bluesky/bluesky.php +++ b/bluesky/bluesky.php @@ -1111,11 +1111,11 @@ function bluesky_process_post(stdClass $post, int $uid, int $post_reason, int $l $uri = bluesky_get_uri($post); if ($id = Post::selectFirst(['id'], ['uri' => $uri, 'uid' => $uid])) { - return $id['id']; + return $id['id']; } if ($id = Post::selectFirst(['id'], ['extid' => $uri, 'uid' => $uid])) { - return $id['id']; + return $id['id']; } Logger::debug('Importing post', ['uid' => $uid, 'indexedAt' => $post->indexedAt, 'uri' => $post->uri, 'cid' => $post->cid, 'root' => $post->record->reply->root ?? '']); @@ -1251,7 +1251,7 @@ function bluesky_get_text(stdClass $record, int $uri_id): string $url = DI::baseUrl() . '/search?tag=' . urlencode($feature->tag); $linktext = '#' . $feature->tag; break; - + default: Logger::notice('Unhandled feature type', ['type' => $feature->$type, 'feature' => $feature, 'record' => $record]); break; @@ -1736,13 +1736,13 @@ function bluesky_post(int $uid, string $url, string $params, array $headers): ?s } if (!$curlResult->isSuccess()) { - Logger::notice('API Error', ['error' => json_decode($curlResult->getBody()) ?: $curlResult->getBody()]); + Logger::notice('API Error', ['error' => json_decode($curlResult->getBodyString()) ?: $curlResult->getBodyString()]); DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_API_FAIL); return null; } DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_SUCCESS); - return json_decode($curlResult->getBody()); + return json_decode($curlResult->getBodyString()); } function bluesky_xrpc_get(int $uid, string $url, array $parameters = []): ?stdClass @@ -1767,9 +1767,9 @@ function bluesky_get(string $url, string $accept_content = HttpClientAccept::DEF } if (!$curlResult->isSuccess()) { - Logger::notice('API Error', ['error' => json_decode($curlResult->getBody()) ?: $curlResult->getBody()]); + Logger::notice('API Error', ['error' => json_decode($curlResult->getBodyString()) ?: $curlResult->getBodyString()]); return null; } - return json_decode($curlResult->getBody()); + return json_decode($curlResult->getBodyString()); } diff --git a/discourse/discourse.php b/discourse/discourse.php index 3d27c5b0..298b3f3e 100644 --- a/discourse/discourse.php +++ b/discourse/discourse.php @@ -126,7 +126,7 @@ function discourse_fetch_post($host, $topic, $pid) return false; } - $raw = $curlResult->getBody(); + $raw = $curlResult->getBodyString(); $data = json_decode($raw, true); $posts = $data['post_stream']['posts']; foreach($posts as $post) { @@ -162,7 +162,7 @@ function discourse_fetch_post_from_api(&$message, $post, $host) return false; } - $raw = $curlResult->getBody(); + $raw = $curlResult->getBodyString(); $data = json_decode($raw, true); if (empty($data)) { return false; diff --git a/dwpost/dwpost.php b/dwpost/dwpost.php index 6c1742e4..4ad5021f 100644 --- a/dwpost/dwpost.php +++ b/dwpost/dwpost.php @@ -192,7 +192,7 @@ EOT; Logger::debug('dwpost: data: ' . $xml); if ($dw_blog !== 'test') { - $x = DI::httpClient()->post($dw_blog, $xml, ['Content-Type' => 'text/xml'])->getBody(); + $x = DI::httpClient()->post($dw_blog, $xml, ['Content-Type' => 'text/xml'])->getBodyString(); } Logger::info('posted to dreamwidth: ' . ($x) ? $x : ''); diff --git a/ijpost/ijpost.php b/ijpost/ijpost.php index 55d3fb05..885bc289 100644 --- a/ijpost/ijpost.php +++ b/ijpost/ijpost.php @@ -186,7 +186,7 @@ EOT; Logger::debug('ijpost: data: ' . $xml); if ($ij_blog !== 'test') { - $x = DI::httpClient()->post($ij_blog, $xml, ['Content-Type' => 'text/xml'])->getBody(); + $x = DI::httpClient()->post($ij_blog, $xml, ['Content-Type' => 'text/xml'])->getBodyString(); } Logger::info('posted to insanejournal: ' . $x ? $x : ''); } diff --git a/libertree/libertree.php b/libertree/libertree.php index c0f896c0..f69c0aab 100644 --- a/libertree/libertree.php +++ b/libertree/libertree.php @@ -201,7 +201,7 @@ function libertree_send(array &$b) // 'token' => $ltree_api_token ]; - $result = DI::httpClient()->post($ltree_blog, $params)->getBody(); + $result = DI::httpClient()->post($ltree_blog, $params)->getBodyString(); Logger::notice('libertree: ' . $result); } } diff --git a/ljpost/ljpost.php b/ljpost/ljpost.php index f6f2b79a..fac44767 100644 --- a/ljpost/ljpost.php +++ b/ljpost/ljpost.php @@ -207,7 +207,7 @@ EOT; Logger::debug('ljpost: data: ' . $xml); if ($lj_blog !== 'test') { - $x = DI::httpClient()->post($lj_blog, $xml, ['Content-Type' => 'text/xml'])->getBody(); + $x = DI::httpClient()->post($lj_blog, $xml, ['Content-Type' => 'text/xml'])->getBodyString(); } Logger::info('posted to livejournal: ' . ($x) ? $x : ''); diff --git a/mailstream/mailstream.php b/mailstream/mailstream.php index d76ccf86..968ee4c0 100644 --- a/mailstream/mailstream.php +++ b/mailstream/mailstream.php @@ -221,7 +221,7 @@ function mailstream_do_images(array &$item, array &$attachments) continue; } $attachments[$url] = [ - 'data' => $curlResult->getBody(), + 'data' => $curlResult->getBodyString(), 'guid' => hash('crc32', $url), 'filename' => basename($components['path']), 'type' => $curlResult->getContentType() diff --git a/mastodoncustomemojis/mastodoncustomemojis.php b/mastodoncustomemojis/mastodoncustomemojis.php index c84f1a4f..f67054a1 100644 --- a/mastodoncustomemojis/mastodoncustomemojis.php +++ b/mastodoncustomemojis/mastodoncustomemojis.php @@ -82,7 +82,7 @@ function mastodoncustomemojis_fetch_custom_emojis_for_url($api_base_url) $fetchResult = DI::httpClient()->fetchFull($api_url); if ($fetchResult->isSuccess()) { - $emojis_array = json_decode($fetchResult->getBody(), true); + $emojis_array = json_decode($fetchResult->getBodyString(), true); if (is_array($emojis_array) && count($emojis_array)) { foreach ($emojis_array as $emoji) { diff --git a/openstreetmap/openstreetmap.php b/openstreetmap/openstreetmap.php index 3b7c4a58..ae22aff0 100644 --- a/openstreetmap/openstreetmap.php +++ b/openstreetmap/openstreetmap.php @@ -121,7 +121,7 @@ function openstreetmap_get_coordinates(array &$b) if (is_null($j)) { $curlResult = DI::httpClient()->get($nomserver . $args); if ($curlResult->isSuccess()) { - $j = json_decode($curlResult->getBody(), true); + $j = json_decode($curlResult->getBodyString(), true); DI::cache()->set($cachekey, $j, Duration::MONTH); } } diff --git a/tumblr/tumblr.php b/tumblr/tumblr.php index 4d5368b8..d6508ff8 100644 --- a/tumblr/tumblr.php +++ b/tumblr/tumblr.php @@ -1244,7 +1244,7 @@ function tumblr_get_contact_by_url(string $url, int $uid): ?array } catch (\Exception $e) { return null; } - $html = $curlResult->getBody(); + $html = $curlResult->getBodyString(); if (empty($html)) { return null; } @@ -1372,7 +1372,7 @@ function tumblr_delete(int $uid, string $url, array $parameters): stdClass */ function tumblr_format_result(ICanHandleHttpResponses $curlResult): stdClass { - $result = json_decode($curlResult->getBody()); + $result = json_decode($curlResult->getBodyString()); if (empty($result) || empty($result->meta)) { $result = new stdClass; $result->meta = new stdClass; @@ -1426,11 +1426,11 @@ function tumblr_get_token(int $uid, string $code = ''): string $curlResult = DI::httpClient()->post('https://api.tumblr.com/v2/oauth2/token', $parameters); if (!$curlResult->isSuccess()) { - Logger::info('Error fetching token', ['uid' => $uid, 'code' => $code, 'result' => $curlResult->getBody(), 'parameters' => $parameters]); + Logger::info('Error fetching token', ['uid' => $uid, 'code' => $code, 'result' => $curlResult->getBodyString(), 'parameters' => $parameters]); return ''; } - $result = json_decode($curlResult->getBody()); + $result = json_decode($curlResult->getBodyString()); if (empty($result)) { Logger::info('Invalid result when updating token', ['uid' => $uid]); return ''; @@ -1479,7 +1479,7 @@ function tumblr_exchange_token(int $uid): stdClass ]); $response = $client->post('oauth2/exchange', ['auth' => 'oauth']); - return json_decode($response->getBody()->getContents()); + return json_decode($response->getBodyString()->getContents()); } catch (RequestException $exception) { Logger::notice('Exchange failed', ['code' => $exception->getCode(), 'message' => $exception->getMessage()]); return new stdClass; diff --git a/twitter/twitter.php b/twitter/twitter.php index b01345b7..8c906daf 100644 --- a/twitter/twitter.php +++ b/twitter/twitter.php @@ -362,7 +362,7 @@ function twitter_post(int $uid, string $url, string $type, array $data): stdClas ]); $response = $client->post($url, ['auth' => 'oauth', $type => $data]); - $body = $response->getBody()->getContents(); + $body = $response->getBodyString()->getContents(); $status = [ 'code' => $response->getStatusCode(), @@ -399,7 +399,7 @@ function twitter_test_connection(int $uid) $status = [ 'code' => $response->getStatusCode(), 'reason' => $response->getReasonPhrase(), - 'content' => $response->getBody()->getContents() + 'content' => $response->getBodyString()->getContents() ]; DI::pConfig()->set(1, 'twitter', 'last_status', $status); Logger::info('Test successful', ['uid' => $uid]); diff --git a/webdav_storage/src/WebDav.php b/webdav_storage/src/WebDav.php index de9fc476..ddf2dae0 100644 --- a/webdav_storage/src/WebDav.php +++ b/webdav_storage/src/WebDav.php @@ -113,7 +113,7 @@ class WebDav implements ICanWriteToStorage $response = $this->client->request('propfind', $uri, $opts); $responseDoc = new \DOMDocument(); - $responseDoc->loadXML($response->getBody()); + $responseDoc->loadXML($response->getBodyString()); $responseDoc->formatOutput = true; $xpath = new \DOMXPath($responseDoc); @@ -205,7 +205,7 @@ class WebDav implements ICanWriteToStorage throw new ReferenceStorageException(sprintf('Invalid reference %s', $reference)); } - return $response->getBody(); + return $response->getBodyString(); } /** diff --git a/wppost/wppost.php b/wppost/wppost.php index 0405f78a..e27079aa 100644 --- a/wppost/wppost.php +++ b/wppost/wppost.php @@ -269,7 +269,7 @@ EOT; Logger::debug('wppost: data: ' . $xml); if ($wp_blog !== 'test') { - $x = DI::httpClient()->post($wp_blog, $xml)->getBody(); + $x = DI::httpClient()->post($wp_blog, $xml)->getBodyString(); } Logger::info('posted to wordpress: ' . (($x) ? $x : '')); }