Compare commits

...

52 commits

Author SHA1 Message Date
Tobias Diekershoff fca2d609c9 Merge pull request 'Bluesky: Fix following of a contact and adding a post' (#1559) from heluecht/friendica-addons:bluesky-fix-follow into 2024.09-rc
Reviewed-on: friendica/friendica-addons#1559
2024-10-27 08:42:34 +01:00
Michael 8b694fbb4c Bluesky: Fix following of a contact and adding a post 2024-10-27 04:50:45 +00:00
Tobias Diekershoff 5a9dafec70 Merge pull request 'Bluesky: "block" now works / label names are now displayed' (#1558) from heluecht/friendica-addons:bluesky-block into 2024.09-rc
Reviewed-on: friendica/friendica-addons#1558
2024-10-24 19:09:54 +02:00
Michael 586ebe9699 Bluesky: "block" now works / label names are now displayed 2024-10-23 12:14:40 +00:00
Tobias Diekershoff 10bd219bd1 Merge pull request 'Bluesky: Fixes "E_WARNING: Undefined property: stdClass::$post"' (#1557) from heluecht/friendica-addons:warning into 2024.09-rc
Reviewed-on: friendica/friendica-addons#1557
2024-10-20 21:45:55 +02:00
Michael 08c17c9dd4 Bluesky: Fixes "E_WARNING: Undefined property: stdClass::$post" 2024-10-19 07:41:24 +00:00
Tobias Diekershoff feb7722f72 Merge pull request 'Bluesky: New option to complete threads' (#1556) from heluecht/friendica-addons:bluesky-complete-threads into develop
Reviewed-on: friendica/friendica-addons#1556
2024-10-06 15:07:14 +02:00
Michael f8f63532f4 Bluesky: New option to complete threads 2024-10-02 07:54:58 +00:00
Tobias Diekershoff ef37aa60e3 Merge pull request 'Bluesky: Preparation for video posts' (#1554) from heluecht/friendica-addons:hls into develop
Reviewed-on: friendica/friendica-addons#1554
2024-09-17 07:02:47 +02:00
Michael 6f3ba10466 Bluesky: Preparation for video posts 2024-09-17 07:02:47 +02:00
Tobias Diekershoff 778b9e3f61 Merge pull request 'More and updated icons for the smiley pack' (#1555) from heluecht/friendica-addons:loma-patch into develop
Reviewed-on: friendica/friendica-addons#1555
2024-09-17 07:02:03 +02:00
loma-one 10521115c4 More and updated icons for the smiley pack 2024-09-16 21:20:11 +00:00
Tobias Diekershoff 956233ff1d Merge pull request 'Bluesky: Fix for the handling of invalid profiles' (#1553) from heluecht/friendica-addons:bluesky-fix into develop
Reviewed-on: friendica/friendica-addons#1553
2024-09-11 19:42:59 +02:00
Michael 14e1c96775 Bluesky: Fix for the handling of invalid profiles 2024-09-10 10:26:05 +00:00
Tobias Diekershoff 7a7dbb579d Merge pull request 'invidious updated' (#1537) from loma-one/friendica-addons:develop into develop
Reviewed-on: friendica/friendica-addons#1537
2024-09-08 08:50:39 +02:00
loma-one 712edf4236 invidious/invidious.php aktualisiert
Further addresses have been added, which are now redirected.
2024-09-08 08:50:39 +02:00
Tobias Diekershoff 5c0cddfc1d Merge pull request 'unicode_smilies updated' (#1536) from loma-one/friendica-addons:loma-one-patch-1 into develop
Reviewed-on: friendica/friendica-addons#1536
2024-09-08 08:50:12 +02:00
loma-one 3dc77b2102 unicode_smilies/unicode_smilies.php aktualisiert
Addition of the unicode character ‘asterism’ & ‘outlines white star’
2024-09-07 21:04:43 +02:00
Tobias Diekershoff 6c43a14198 Merge pull request '"fetchFull" is replaced by "get"' (#1535) from heluecht/friendica-addons:fetchfull into develop
Reviewed-on: friendica/friendica-addons#1535
2024-09-06 07:22:58 +02:00
Michael 0dfb345f85 "fetchFull" is replaced by "get" 2024-09-06 07:22:58 +02:00
Tobias Diekershoff ab837dfec5 Merge pull request 'Bluesky: probing for bluesky handles' (#1534) from heluecht/friendica-addons:bluesky-handle into develop
Reviewed-on: friendica/friendica-addons#1534
2024-09-06 07:20:55 +02:00
Michael 2f9076bffd Bluesky: probing for bluesky handles 2024-09-04 04:02:30 +00:00
Tobias Diekershoff 454e9834bf Merge pull request 'Bluesky: Improve DID detection for custom PDS' (#1533) from heluecht/friendica-addons:bluesky-pds into develop
Reviewed-on: friendica/friendica-addons#1533
2024-09-02 06:32:54 +02:00
Michael 50930c301d Bluesky: Improve DID detection for custom PDS 2024-09-02 06:32:54 +02:00
Philipp Holzer 3457ab2f3f Merge pull request 'Add safe.directory config' (#1532) from nupplaPhil/friendica-addons:bug/ci into develop
Reviewed-on: friendica/friendica-addons#1532
2024-08-23 20:24:16 +02:00
Philipp Holzer 276c27678f
[CI] Add safe.directory config 2024-08-20 18:07:51 +02:00
Tobias Diekershoff cd95ca1a0a Merge branch 'stable' into develop 2024-08-17 16:55:10 +02:00
Tobias Diekershoff 5c04e7136f Merge branch '2024.06-rc' into stable 2024-08-17 16:54:44 +02:00
heluecht 179382d8a9 Merge pull request 'updated translations' (#1531) from tobias/friendica-addons:20240815-lng into 2024.06-rc
Reviewed-on: friendica/friendica-addons#1531
2024-08-15 07:55:36 +02:00
Tobias Diekershoff a55f80cb39 updated translations 2024-08-15 07:55:36 +02:00
Tobias Diekershoff 4ad7d61893 Merge pull request 'Bluesky/Tumblr: Improved statistics' (#1530) from heluecht/friendica-addons:stats2 into 2024.06-rc
Reviewed-on: friendica/friendica-addons#1530
2024-08-14 08:09:37 +02:00
Michael 4bfdb45e81 Bluesky/Tumblr: Improved statistics 2024-08-12 20:24:09 +00:00
Tobias Diekershoff 4414471100 Merge pull request 'Ratioed: add help text' (#1528) from mexon/friendica-addons:mat/ratioed-help into develop
Reviewed-on: friendica/friendica-addons#1528
2024-08-09 13:58:48 +02:00
Matthew Exon 46a55f13f7 Ratioed: add help text 2024-08-09 13:58:48 +02:00
Tobias Diekershoff a97cccb6b2 Merge pull request 'Statistics: inbound / outbound for Tumblr and Bluesky' (#1529) from heluecht/friendica-addons:stats into 2024.06-rc
Reviewed-on: friendica/friendica-addons#1529
2024-08-09 13:55:51 +02:00
Michael c0535db742 Statistics: inbound / outbound for Tumblr and Bluesky 2024-08-09 13:55:51 +02:00
Tobias Diekershoff 0c04b086cb Merge pull request 'Remove old version conversion code' (#1526) from mexon/friendica-addons:mat/remove-conversion into develop
Reviewed-on: friendica/friendica-addons#1526
2024-07-29 19:17:39 +02:00
Matthew Exon 589cf712cc Remove old version conversion code 2024-07-20 13:00:07 +02:00
heluecht ce53e48cb2 Merge pull request 'More comprehensible check for root user contact' (#1525) from mexon/friendica-addons:mat/mailstream-clarify-log into develop
Reviewed-on: friendica/friendica-addons#1525
2024-07-20 11:57:32 +02:00
Matthew Exon f3db763c59 More comprehensible check for root user contact 2024-07-14 18:50:56 +02:00
heluecht 4e5998c73d Merge pull request 'Mailstream: streamline log lines' (#1522) from mexon/friendica-addons:mat/mailstream-log into develop
Reviewed-on: friendica/friendica-addons#1522
2024-07-14 18:10:10 +02:00
Matthew Exon 5f27f72b0d Streamline log lines 2024-07-01 19:12:10 +02:00
Tobias Diekershoff b0a95ca2d2 Merge pull request 'fix for curweather' (#1521) from haheute/friendica-addons:fix-curweather into 2024.06-rc
Reviewed-on: friendica/friendica-addons#1521
2024-06-28 18:27:26 +02:00
Hannes Heute b2108c7a4c fix for curweather 2024-06-27 11:44:15 +02:00
Hypolite Petovan abca07b29d Merge pull request 'Add Relatica to blockbot fediverse client list' (#1520) from hankg/friendica-addons:2024.06-rc into 2024.06-rc
Reviewed-on: friendica/friendica-addons#1520
Reviewed-by: Hypolite Petovan <hypolite@mrpetovan.com>
2024-06-25 01:07:09 +02:00
Hypolite Petovan d3dcd5428c Merge pull request 'mat/ratioed-plugin-2' (#1519) from mexon/friendica-addons:mat/ratioed-plugin-2 into develop
Reviewed-on: friendica/friendica-addons#1519
Reviewed-by: Hypolite Petovan <hypolite@mrpetovan.com>
2024-06-23 21:25:47 +02:00
Matthew Exon 18e512cc8b Ratioed: move panel class into separate file 2024-06-23 15:43:05 +02:00
Matthew Exon 7d5446a778 Ratioed: remove unnecessary uninstall function 2024-06-23 15:15:06 +02:00
Hypolite Petovan 38ea90104d Merge pull request 'New addon providing additional statistics for moderation' (#1518) from mexon/friendica-addons:mat/ratioed-plugin into develop
Reviewed-on: friendica/friendica-addons#1518
2024-06-23 14:13:34 +02:00
Matthew Exon 08b46e5536 New addon providing additional statistics for moderation 2024-06-22 18:56:32 +02:00
Hypolite Petovan 010261c1dc Merge pull request '[Hotfix] Fix REPO_URL for woodpecker' (#1490) from nupplaPhil/friendica-addons:bug/repo_link into stable
Reviewed-on: friendica/friendica-addons#1490
Reviewed-by: Hypolite Petovan <hypolite@mrpetovan.com>
2024-03-22 16:03:06 +01:00
Philipp Holzer 3f26f9785e
[Hotfix] Fix REPO_URL for woodpecker 2024-03-22 15:53:47 +01:00
84 changed files with 1195 additions and 400 deletions

View file

@ -4,6 +4,9 @@ pipeline:
clone_friendica_base: clone_friendica_base:
image: alpine/git image: alpine/git
commands: commands:
- git config --global user.email "no-reply@friendi.ca"
- git config --global user.name "Friendica"
- git config --global --add safe.directory $CI_WORKSPACE
- git clone https://github.com/friendica/friendica.git . - git clone https://github.com/friendica/friendica.git .
- git checkout $CI_COMMIT_BRANCH - git checkout $CI_COMMIT_BRANCH
when: when:

View file

@ -9,6 +9,9 @@ pipeline:
clone_friendica_base: clone_friendica_base:
image: alpine/git image: alpine/git
commands: commands:
- git config --global user.email "no-reply@friendi.ca"
- git config --global user.name "Friendica"
- git config --global --add safe.directory $CI_WORKSPACE
- git clone https://github.com/friendica/friendica.git . - git clone https://github.com/friendica/friendica.git .
- git checkout $CI_COMMIT_BRANCH - git checkout $CI_COMMIT_BRANCH
when: when:

View file

@ -4,6 +4,9 @@ pipeline:
clone_friendica_base: clone_friendica_base:
image: alpine/git image: alpine/git
commands: commands:
- git config --global user.email "no-reply@friendi.ca"
- git config --global user.name "Friendica"
- git config --global --add safe.directory $CI_WORKSPACE
- git clone https://github.com/friendica/friendica.git . - git clone https://github.com/friendica/friendica.git .
- git checkout $CI_COMMIT_BRANCH - git checkout $CI_COMMIT_BRANCH
when: when:

View file

@ -21,6 +21,9 @@ pipeline:
clone_friendica_base: clone_friendica_base:
image: alpine/git image: alpine/git
commands: commands:
- git config --global user.email "no-reply@friendi.ca"
- git config --global user.name "Friendica"
- git config --global --add safe.directory $CI_WORKSPACE
- git clone https://github.com/friendica/friendica.git . - git clone https://github.com/friendica/friendica.git .
- git checkout $CI_COMMIT_BRANCH - git checkout $CI_COMMIT_BRANCH
clone_friendica_addon: clone_friendica_addon:

View file

@ -9,6 +9,9 @@ pipeline:
clone_friendica_base: clone_friendica_base:
image: alpine/git image: alpine/git
commands: commands:
- git config --global user.email "no-reply@friendi.ca"
- git config --global user.name "Friendica"
- git config --global --add safe.directory $CI_WORKSPACE
- git clone https://github.com/friendica/friendica.git . - git clone https://github.com/friendica/friendica.git .
- git checkout $CI_COMMIT_BRANCH - git checkout $CI_COMMIT_BRANCH
when: when:

View file

@ -1,7 +1,7 @@
<?php <?php
/** /**
* Name: Bluesky Connector * Name: Bluesky Connector
* Description: Post to Bluesky * Description: Post to Bluesky, import timelines and feeds
* Version: 1.1 * Version: 1.1
* Author: Michael Vogel <https://pirati.ca/profile/heluecht> * Author: Michael Vogel <https://pirati.ca/profile/heluecht>
* *
@ -10,6 +10,8 @@
* - Outgoing mentions * - Outgoing mentions
* *
* At some point in time: * At some point in time:
* - post videos
* - direct messages
* - Sending Quote shares https://atproto.com/lexicons/app-bsky-embed#appbskyembedrecord and https://atproto.com/lexicons/app-bsky-embed#appbskyembedrecordwithmedia * - Sending Quote shares https://atproto.com/lexicons/app-bsky-embed#appbskyembedrecord and https://atproto.com/lexicons/app-bsky-embed#appbskyembedrecordwithmedia
* *
* Possibly not possible: * Possibly not possible:
@ -45,10 +47,12 @@ use Friendica\Model\Tag;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Network\HTTPClient\Client\HttpClientAccept; use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions; use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
use Friendica\Object\Image; use Friendica\Object\Image;
use Friendica\Protocol\Activity; use Friendica\Protocol\Activity;
use Friendica\Protocol\Relay; use Friendica\Protocol\Relay;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
use Friendica\Util\Strings; use Friendica\Util\Strings;
const BLUESKY_DEFAULT_POLL_INTERVAL = 10; // given in minutes const BLUESKY_DEFAULT_POLL_INTERVAL = 10; // given in minutes
@ -129,8 +133,13 @@ function bluesky_probe_detect(array &$hookData)
if (parse_url($hookData['uri'], PHP_URL_SCHEME) == 'did') { if (parse_url($hookData['uri'], PHP_URL_SCHEME) == 'did') {
$did = $hookData['uri']; $did = $hookData['uri'];
} elseif (preg_match('#^' . BLUESKY_WEB . '/profile/(.+)#', $hookData['uri'], $matches)) { } elseif (parse_url($hookData['uri'], PHP_URL_PATH) == $hookData['uri'] && strpos($hookData['uri'], '@') === false) {
$did = bluesky_get_did($matches[1]); $did = bluesky_get_did($hookData['uri'], $pconfig['uid']);
if (empty($did)) {
return;
}
} elseif (Network::isValidHttpUrl($hookData['uri'])) {
$did = bluesky_get_did_by_profile($hookData['uri'], $pconfig['uid']);
if (empty($did)) { if (empty($did)) {
return; return;
} }
@ -144,23 +153,18 @@ function bluesky_probe_detect(array &$hookData)
} }
$data = bluesky_xrpc_get($pconfig['uid'], 'app.bsky.actor.getProfile', ['actor' => $did]); $data = bluesky_xrpc_get($pconfig['uid'], 'app.bsky.actor.getProfile', ['actor' => $did]);
if (empty($data)) { if (empty($data) || empty($data->did)) {
return; return;
} }
$hookData['result'] = bluesky_get_contact_fields($data, 0, $pconfig['uid'], false); $hookData['result'] = bluesky_get_contact_fields($data, 0, $pconfig['uid'], true);
$hookData['result']['baseurl'] = bluesky_get_pds($did);
// Preparing probe data. This differs slightly from the contact array // Preparing probe data. This differs slightly from the contact array
$hookData['result']['about'] = HTML::toBBCode($data->description ?? '');
$hookData['result']['photo'] = $data->avatar ?? ''; $hookData['result']['photo'] = $data->avatar ?? '';
$hookData['result']['header'] = $data->banner ?? '';
$hookData['result']['batch'] = ''; $hookData['result']['batch'] = '';
$hookData['result']['notify'] = ''; $hookData['result']['notify'] = '';
$hookData['result']['poll'] = ''; $hookData['result']['poll'] = '';
$hookData['result']['poco'] = ''; $hookData['result']['poco'] = '';
$hookData['result']['pubkey'] = '';
$hookData['result']['priority'] = 0; $hookData['result']['priority'] = 0;
$hookData['result']['guid'] = ''; $hookData['result']['guid'] = '';
} }
@ -177,21 +181,21 @@ function bluesky_item_by_link(array &$hookData)
return; return;
} }
if (!preg_match('#^' . BLUESKY_WEB . '/profile/(.+)/post/(.+)#', $hookData['uri'], $matches)) { if (!preg_match('#/profile/(.+)/post/(.+)#', $hookData['uri'], $matches)) {
return; return;
} }
$did = bluesky_get_did($matches[1]); $did = bluesky_get_did($matches[1], $hookData['uid']);
if (empty($did)) { if (empty($did)) {
return; return;
} }
Logger::debug('Found bluesky post', ['url' => $hookData['uri'], 'handle' => $matches[1], 'did' => $did, 'cid' => $matches[2]]); Logger::debug('Found bluesky post', ['url' => $hookData['uri'], 'did' => $did, 'cid' => $matches[2]]);
$uri = 'at://' . $did . '/app.bsky.feed.post/' . $matches[2]; $uri = 'at://' . $did . '/app.bsky.feed.post/' . $matches[2];
$uri = bluesky_fetch_missing_post($uri, $hookData['uid'], $hookData['uid'], Item::PR_FETCHED, 0, 0, 0); $uri = bluesky_fetch_missing_post($uri, $hookData['uid'], $hookData['uid'], Item::PR_FETCHED, 0, 0, 0);
Logger::debug('Got post', ['profile' => $matches[1], 'cid' => $matches[2], 'result' => $uri]); Logger::debug('Got post', ['did' => $did, 'cid' => $matches[2], 'result' => $uri]);
if (!empty($uri)) { if (!empty($uri)) {
$item = Post::selectFirst(['id'], ['uri' => $uri, 'uid' => $hookData['uid']]); $item = Post::selectFirst(['id'], ['uri' => $uri, 'uid' => $hookData['uid']]);
if (!empty($item['id'])) { if (!empty($item['id'])) {
@ -274,14 +278,12 @@ function bluesky_block(array &$hook_data)
return; return;
} }
Logger::debug('Check if contact is bluesky', ['data' => $hook_data]); if ($hook_data['contact']['network'] != Protocol::BLUESKY) {
$contact = DBA::selectFirst('contact', [], ['network' => Protocol::BLUESKY, 'url' => $hook_data['url'], 'uid' => [0, $hook_data['uid']]]);
if (empty($contact)) {
return; return;
} }
$record = [ $record = [
'subject' => $contact['url'], 'subject' => $hook_data['contact']['url'],
'createdAt' => DateTimeFormat::utcNow(DateTimeFormat::ATOM), 'createdAt' => DateTimeFormat::utcNow(DateTimeFormat::ATOM),
'$type' => 'app.bsky.graph.block' '$type' => 'app.bsky.graph.block'
]; ];
@ -294,9 +296,9 @@ function bluesky_block(array &$hook_data)
$activity = bluesky_xrpc_post($hook_data['uid'], 'com.atproto.repo.createRecord', $post); $activity = bluesky_xrpc_post($hook_data['uid'], 'com.atproto.repo.createRecord', $post);
if (!empty($activity->uri)) { if (!empty($activity->uri)) {
$cdata = Contact::getPublicAndUserContactID($hook_data['contact']['id'], $hook_data['uid']); $ucid = Contact::getUserContactId($hook_data['contact']['id'], $hook_data['uid']);
if (!empty($cdata['user'])) { if ($ucid) {
Contact::remove($cdata['user']); Contact::remove($ucid);
} }
Logger::debug('Successfully blocked contact', ['url' => $hook_data['contact']['url'], 'uri' => $activity->uri]); Logger::debug('Successfully blocked contact', ['url' => $hook_data['contact']['url'], 'uri' => $activity->uri]);
} }
@ -344,15 +346,16 @@ function bluesky_settings(array &$data)
return; return;
} }
$enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post') ?? false; $enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post') ?? false;
$def_enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post_by_default') ?? false; $def_enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'post_by_default') ?? false;
$pds = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'pds'); $pds = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'pds');
$handle = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'handle'); $handle = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'handle');
$did = bluesky_get_user_did(DI::userSession()->getLocalUserId()); $did = bluesky_get_user_did(DI::userSession()->getLocalUserId());
$token = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'access_token'); $token = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'access_token');
$import = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'import') ?? false; $import = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'import') ?? false;
$import_feeds = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'import_feeds') ?? false; $import_feeds = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'import_feeds') ?? false;
$custom_handle = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'friendica_handle') ?? false; $complete_threads = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'complete_threads') ?? false;
$custom_handle = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'bluesky', 'friendica_handle') ?? false;
if (DI::config()->get('bluesky', 'friendica_handles')) { if (DI::config()->get('bluesky', 'friendica_handles')) {
$self = User::getById(DI::userSession()->getLocalUserId(), ['nickname']); $self = User::getById(DI::userSession()->getLocalUserId(), ['nickname']);
@ -367,16 +370,17 @@ function bluesky_settings(array &$data)
$t = Renderer::getMarkupTemplate('connector_settings.tpl', 'addon/bluesky/'); $t = Renderer::getMarkupTemplate('connector_settings.tpl', 'addon/bluesky/');
$html = Renderer::replaceMacros($t, [ $html = Renderer::replaceMacros($t, [
'$enable' => ['bluesky', DI::l10n()->t('Enable Bluesky Post Addon'), $enabled], '$enable' => ['bluesky', DI::l10n()->t('Enable Bluesky Post Addon'), $enabled],
'$bydefault' => ['bluesky_bydefault', DI::l10n()->t('Post to Bluesky by default'), $def_enabled], '$bydefault' => ['bluesky_bydefault', DI::l10n()->t('Post to Bluesky by default'), $def_enabled],
'$import' => ['bluesky_import', DI::l10n()->t('Import the remote timeline'), $import], '$import' => ['bluesky_import', DI::l10n()->t('Import the remote timeline'), $import],
'$import_feeds' => ['bluesky_import_feeds', DI::l10n()->t('Import the pinned feeds'), $import_feeds, DI::l10n()->t('When activated, Posts will be imported from all the feeds that you pinned in Bluesky.')], '$import_feeds' => ['bluesky_import_feeds', DI::l10n()->t('Import the pinned feeds'), $import_feeds, DI::l10n()->t('When activated, Posts will be imported from all the feeds that you pinned in Bluesky.')],
'$custom_handle' => $friendica_handle, '$complete_threads' => ['bluesky_complete_threads', DI::l10n()->t('Complete the threads'), $complete_threads, DI::l10n()->t('When activated, the system fetches additional replies for the posts in the timeline. This leads to more complete threads.')],
'$pds' => ['bluesky_pds', DI::l10n()->t('Personal Data Server'), $pds, DI::l10n()->t('The personal data server (PDS) is the system that hosts your profile.'), '', 'readonly'], '$custom_handle' => $friendica_handle,
'$handle' => ['bluesky_handle', DI::l10n()->t('Bluesky handle'), $handle, '', '', $custom_handle ? 'readonly' : ''], '$pds' => ['bluesky_pds', DI::l10n()->t('Personal Data Server'), $pds, DI::l10n()->t('The personal data server (PDS) is the system that hosts your profile.'), '', 'readonly'],
'$did' => ['bluesky_did', DI::l10n()->t('Bluesky DID'), $did, DI::l10n()->t('This is the unique identifier. It will be fetched automatically, when the handle is entered.'), '', 'readonly'], '$handle' => ['bluesky_handle', DI::l10n()->t('Bluesky handle'), $handle, '', '', $custom_handle ? 'readonly' : ''],
'$password' => ['bluesky_password', DI::l10n()->t('Bluesky app password'), '', DI::l10n()->t("Please don't add your real password here, but instead create a specific app password in the Bluesky settings.")], '$did' => ['bluesky_did', DI::l10n()->t('Bluesky DID'), $did, DI::l10n()->t('This is the unique identifier. It will be fetched automatically, when the handle is entered.'), '', 'readonly'],
'$status' => bluesky_get_status($handle, $did, $pds, $token), '$password' => ['bluesky_password', DI::l10n()->t('Bluesky app password'), '', DI::l10n()->t("Please don't add your real password here, but instead create a specific app password in the Bluesky settings.")],
'$status' => bluesky_get_status($handle, $did, $pds, $token),
]); ]);
$data = [ $data = [
@ -444,6 +448,7 @@ function bluesky_settings_post(array &$b)
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'handle', $handle); DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'handle', $handle);
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'import', intval($_POST['bluesky_import'])); DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'import', intval($_POST['bluesky_import']));
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'import_feeds', intval($_POST['bluesky_import_feeds'])); DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'import_feeds', intval($_POST['bluesky_import_feeds']));
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'complete_threads', intval($_POST['bluesky_complete_threads']));
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'friendica_handle', intval($_POST['bluesky_friendica_handle'] ?? false)); DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'bluesky', 'friendica_handle', intval($_POST['bluesky_friendica_handle'] ?? false));
if (!empty($handle)) { if (!empty($handle)) {
@ -532,15 +537,15 @@ function bluesky_cron()
Logger::debug('Refresh the token', ['uid' => $pconfig['uid']]); Logger::debug('Refresh the token', ['uid' => $pconfig['uid']]);
bluesky_get_token($pconfig['uid']); bluesky_get_token($pconfig['uid']);
Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_notifications.php', $pconfig['uid'], $last); Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_notifications.php', $pconfig['uid']);
if (DI::pConfig()->get($pconfig['uid'], 'bluesky', 'import')) { if (DI::pConfig()->get($pconfig['uid'], 'bluesky', 'import')) {
Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_timeline.php', $pconfig['uid'], $last); Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_timeline.php', $pconfig['uid']);
} }
if (DI::pConfig()->get($pconfig['uid'], 'bluesky', 'import_feeds')) { if (DI::pConfig()->get($pconfig['uid'], 'bluesky', 'import_feeds')) {
Logger::debug('Fetch feeds for user', ['uid' => $pconfig['uid']]); Logger::debug('Fetch feeds for user', ['uid' => $pconfig['uid']]);
$feeds = bluesky_get_feeds($pconfig['uid']); $feeds = bluesky_get_feeds($pconfig['uid']);
foreach ($feeds as $feed) { foreach ($feeds as $feed) {
Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_feed.php', $pconfig['uid'], $feed, $last); Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_feed.php', $pconfig['uid'], $feed);
} }
} }
Logger::debug('Polling done for user', ['uid' => $pconfig['uid']]); Logger::debug('Polling done for user', ['uid' => $pconfig['uid']]);
@ -708,7 +713,7 @@ function bluesky_create_activity(array $item, stdClass $parent = null)
} }
$activity = bluesky_xrpc_post($uid, 'com.atproto.repo.createRecord', $post); $activity = bluesky_xrpc_post($uid, 'com.atproto.repo.createRecord', $post);
if (empty($activity)) { if (empty($activity->uri)) {
return; return;
} }
Logger::debug('Activity done', ['return' => $activity]); Logger::debug('Activity done', ['return' => $activity]);
@ -792,7 +797,7 @@ function bluesky_create_post(array $item, stdClass $root = null, stdClass $paren
]; ];
$parent = bluesky_xrpc_post($uid, 'com.atproto.repo.createRecord', $post); $parent = bluesky_xrpc_post($uid, 'com.atproto.repo.createRecord', $post);
if (empty($parent)) { if (empty($parent->uri)) {
if ($part == 0) { if ($part == 0) {
Worker::defer(); Worker::defer();
} }
@ -970,11 +975,12 @@ function bluesky_upload_blob(int $uid, array $photo): ?stdClass
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]); 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]);
$data = bluesky_post($uid, '/xrpc/com.atproto.repo.uploadBlob', $content, ['Content-type' => $photo['type'], 'Authorization' => ['Bearer ' . bluesky_get_token($uid)]]); $data = bluesky_post($uid, '/xrpc/com.atproto.repo.uploadBlob', $content, ['Content-type' => $photo['type'], 'Authorization' => ['Bearer ' . bluesky_get_token($uid)]]);
if (empty($data)) { if (empty($data) || empty($data->blob)) {
Logger::info('Uploading failed', ['uid' => $uid, 'retrial' => $retrial, 'height' => $new_height, 'width' => $new_width, 'size' => $new_size, 'orig-height' => $height, 'orig-width' => $width, 'orig-size' => $size]); Logger::info('Uploading failed', ['uid' => $uid, 'retrial' => $retrial, 'height' => $new_height, 'width' => $new_width, 'size' => $new_size, 'orig-height' => $height, 'orig-width' => $width, 'orig-size' => $size]);
return null; return null;
} }
Item::incrementOutbound(Protocol::BLUESKY);
Logger::debug('Uploaded blob', ['return' => $data, 'uid' => $uid, 'retrial' => $retrial, 'height' => $new_height, 'width' => $new_width, 'size' => $new_size, 'orig-height' => $height, 'orig-width' => $width, 'orig-size' => $size]); Logger::debug('Uploaded blob', ['return' => $data, 'uid' => $uid, 'retrial' => $retrial, 'height' => $new_height, 'width' => $new_width, 'size' => $new_size, 'orig-height' => $height, 'orig-width' => $width, 'orig-size' => $size]);
return $data->blob; return $data->blob;
} }
@ -990,7 +996,7 @@ function bluesky_delete_post(string $uri, int $uid)
Logger::debug('Deleted', ['parts' => $parts]); Logger::debug('Deleted', ['parts' => $parts]);
} }
function bluesky_fetch_timeline(int $uid, int $last_poll) function bluesky_fetch_timeline(int $uid)
{ {
$data = bluesky_xrpc_get($uid, 'app.bsky.feed.getTimeline'); $data = bluesky_xrpc_get($uid, 'app.bsky.feed.getTimeline');
if (empty($data)) { if (empty($data)) {
@ -1001,15 +1007,44 @@ function bluesky_fetch_timeline(int $uid, int $last_poll)
return; return;
} }
$last_post = DBA::selectFirst('post-thread-user', ['received'], ['network' => Protocol::BLUESKY, 'uid' => $uid], ['order' => ['received' => true]]);
$last_poll = !empty($last_post['received']) ? strtotime($last_post['received']) : 0;
foreach (array_reverse($data->feed) as $entry) { foreach (array_reverse($data->feed) as $entry) {
$causer = bluesky_get_contact($entry->post->author, 0, $uid);
if (!empty($entry->reply)) {
if (!empty($entry->reply->root)) {
bluesky_complete_post($entry->reply->root, $uid, Item::PR_COMMENT, $causer['id'], $last_poll);
}
if (!empty($entry->reply->parent)) {
bluesky_complete_post($entry->reply->parent, $uid, Item::PR_COMMENT, $causer['id'], $last_poll);
}
}
bluesky_process_post($entry->post, $uid, $uid, Item::PR_NONE, 0, 0, $last_poll); bluesky_process_post($entry->post, $uid, $uid, Item::PR_NONE, 0, 0, $last_poll);
if (!empty($entry->reason)) { if (!empty($entry->reason)) {
bluesky_process_reason($entry->reason, bluesky_get_uri($entry->post), $uid); bluesky_process_reason($entry->reason, bluesky_get_uri($entry->post), $uid);
} }
} }
}
// @todo Support paging function bluesky_complete_post(stdClass $post, int $uid, int $post_reason, int $causer, int $last_poll): int
// [cursor] => 1684670516000::bafyreidq3ilwslmlx72jf5vrk367xcc63s6lrhzlyup2bi3zwcvso6w2vi {
$complete = DI::pConfig()->get($uid, 'bluesky', 'complete_threads');
$existing_uri = bluesky_fetch_post(bluesky_get_uri($post), $uid);
if (!empty($existing_uri)) {
$comments = Post::countPosts(['thr-parent' => $existing_uri, 'gravity' => Item::GRAVITY_COMMENT]);
if (($post->replyCount <= $comments) || !$complete) {
return bluesky_fetch_uri_id($existing_uri, $uid);
}
}
if ($complete) {
$uri = bluesky_fetch_missing_post($post->uri, $uid, $uid, $post_reason, $causer, 0, $last_poll, '', true);
$uri_id = bluesky_fetch_uri_id($uri, $uid);
} else {
$uri_id = bluesky_process_post($post, $uid, $uid, $post_reason, $causer, 0, $last_poll);
}
return $uri_id;
} }
function bluesky_process_reason(stdClass $reason, string $uri, int $uid) function bluesky_process_reason(stdClass $reason, string $uri, int $uid)
@ -1048,17 +1083,21 @@ function bluesky_process_reason(stdClass $reason, string $uri, int $uid)
$item['owner-link'] = $item['author-link']; $item['owner-link'] = $item['author-link'];
$item['owner-avatar'] = $item['author-avatar']; $item['owner-avatar'] = $item['author-avatar'];
if (Item::insert($item)) { if (Item::insert($item)) {
$cdata = Contact::getPublicAndUserContactID($contact['id'], $uid); $pcid = Contact::getPublicContactId($contact['id'], $uid);
Item::update(['post-reason' => Item::PR_ANNOUNCEMENT, 'causer-id' => $cdata['public']], ['uri' => $uri, 'uid' => $uid]); Item::update(['post-reason' => Item::PR_ANNOUNCEMENT, 'causer-id' => $pcid], ['uri' => $uri, 'uid' => $uid]);
} }
} }
function bluesky_fetch_notifications(int $uid, int $last_poll) function bluesky_fetch_notifications(int $uid)
{ {
$data = bluesky_xrpc_get($uid, 'app.bsky.notification.listNotifications'); $data = bluesky_xrpc_get($uid, 'app.bsky.notification.listNotifications');
if (empty($data->notifications)) { if (empty($data->notifications)) {
return; return;
} }
$last_post = DBA::selectFirst('post-thread-user', ['received'], ['network' => Protocol::BLUESKY, 'uid' => $uid], ['order' => ['received' => true]]);
$last_poll = !empty($last_post['received']) ? strtotime($last_post['received']) : 0;
foreach ($data->notifications as $notification) { foreach ($data->notifications as $notification) {
$uri = bluesky_get_uri($notification); $uri = bluesky_get_uri($notification);
if (Post::exists(['uri' => $uri, 'uid' => $uid]) || Post::exists(['extid' => $uri, 'uid' => $uid])) { if (Post::exists(['uri' => $uri, 'uid' => $uid]) || Post::exists(['extid' => $uri, 'uid' => $uid])) {
@ -1068,7 +1107,7 @@ function bluesky_fetch_notifications(int $uid, int $last_poll)
Logger::debug('Process notification', ['uid' => $uid, 'reason' => $notification->reason, 'uri' => $uri, 'indexedAt' => $notification->indexedAt]); Logger::debug('Process notification', ['uid' => $uid, 'reason' => $notification->reason, 'uri' => $uri, 'indexedAt' => $notification->indexedAt]);
switch ($notification->reason) { switch ($notification->reason) {
case 'like': case 'like':
$item = bluesky_get_header($notification, $uri, $uid, $uid); $item = bluesky_get_header($notification, $uri, $uid, $uid, $last_poll);
$item['gravity'] = Item::GRAVITY_ACTIVITY; $item['gravity'] = Item::GRAVITY_ACTIVITY;
$item['body'] = $item['verb'] = Activity::LIKE; $item['body'] = $item['verb'] = Activity::LIKE;
$item['thr-parent'] = bluesky_get_uri($notification->record->subject); $item['thr-parent'] = bluesky_get_uri($notification->record->subject);
@ -1082,7 +1121,7 @@ function bluesky_fetch_notifications(int $uid, int $last_poll)
break; break;
case 'repost': case 'repost':
$item = bluesky_get_header($notification, $uri, $uid, $uid); $item = bluesky_get_header($notification, $uri, $uid, $uid, $last_poll);
$item['gravity'] = Item::GRAVITY_ACTIVITY; $item['gravity'] = Item::GRAVITY_ACTIVITY;
$item['body'] = $item['verb'] = Activity::ANNOUNCE; $item['body'] = $item['verb'] = Activity::ANNOUNCE;
$item['thr-parent'] = bluesky_get_uri($notification->record->subject); $item['thr-parent'] = bluesky_get_uri($notification->record->subject);
@ -1125,7 +1164,7 @@ function bluesky_fetch_notifications(int $uid, int $last_poll)
} }
} }
function bluesky_fetch_feed(int $uid, string $feed, int $last_poll) function bluesky_fetch_feed(int $uid, string $feed)
{ {
$data = bluesky_xrpc_get($uid, 'app.bsky.feed.getFeed', ['feed' => $feed]); $data = bluesky_xrpc_get($uid, 'app.bsky.feed.getFeed', ['feed' => $feed]);
if (empty($data)) { if (empty($data)) {
@ -1136,8 +1175,11 @@ function bluesky_fetch_feed(int $uid, string $feed, int $last_poll)
return; return;
} }
$last_post = DBA::selectFirst('post-thread-user', ['received'], ['network' => Protocol::BLUESKY, 'uid' => $uid], ['order' => ['received' => true]]);
$last_poll = !empty($last_post['received']) ? strtotime($last_post['received']) : 0;
$feeddata = bluesky_xrpc_get($uid, 'app.bsky.feed.getFeedGenerator', ['feed' => $feed]); $feeddata = bluesky_xrpc_get($uid, 'app.bsky.feed.getFeedGenerator', ['feed' => $feed]);
if (!empty($feeddata)) { if (!empty($feeddata) && !empty($feeddata->view)) {
$feedurl = $feeddata->view->uri; $feedurl = $feeddata->view->uri;
$feedname = $feeddata->view->displayName; $feedname = $feeddata->view->displayName;
} else { } else {
@ -1150,10 +1192,11 @@ function bluesky_fetch_feed(int $uid, string $feed, int $last_poll)
$languages = $entry->post->record->langs ?? []; $languages = $entry->post->record->langs ?? [];
if (!Relay::isWantedLanguage($entry->post->record->text, 0, $contact['id'] ?? 0, $languages)) { if (!Relay::isWantedLanguage($entry->post->record->text, 0, $contact['id'] ?? 0, $languages)) {
Logger::debug('Unwanted language detected', ['text' => $entry->post->record->text]); Logger::debug('Unwanted language detected', ['languages' => $languages, 'text' => $entry->post->record->text]);
continue; continue;
} }
$uri_id = bluesky_process_post($entry->post, $uid, $uid, Item::PR_TAG, 0, 0, $last_poll); $causer = bluesky_get_contact($entry->post->author, 0, $uid);
$uri_id = bluesky_complete_post($entry->post, $uid, Item::PR_TAG, $causer['id'], $last_poll);
if (!empty($uri_id)) { if (!empty($uri_id)) {
$stored = Post\Category::storeFileByURIId($uri_id, $uid, Post\Category::SUBCRIPTION, $feedname, $feedurl); $stored = Post\Category::storeFileByURIId($uri_id, $uid, Post\Category::SUBCRIPTION, $feedname, $feedurl);
Logger::debug('Stored tag subscription for user', ['uri-id' => $uri_id, 'uid' => $uid, 'name' => $feedname, 'url' => $feedurl, 'stored' => $stored]); Logger::debug('Stored tag subscription for user', ['uri-id' => $uri_id, 'uid' => $uid, 'name' => $feedname, 'url' => $feedurl, 'stored' => $stored]);
@ -1181,7 +1224,7 @@ function bluesky_process_post(stdClass $post, int $uid, int $fetch_uid, int $pos
Logger::debug('Importing post', ['uid' => $uid, 'indexedAt' => $post->indexedAt, 'uri' => $post->uri, 'cid' => $post->cid, 'root' => $post->record->reply->root ?? '']); Logger::debug('Importing post', ['uid' => $uid, 'indexedAt' => $post->indexedAt, 'uri' => $post->uri, 'cid' => $post->cid, 'root' => $post->record->reply->root ?? '']);
$item = bluesky_get_header($post, $uri, $uid, $fetch_uid); $item = bluesky_get_header($post, $uri, $uid, $fetch_uid, $last_poll);
$item = bluesky_get_content($item, $post->record, $uri, $uid, $fetch_uid, $level, $last_poll); $item = bluesky_get_content($item, $post->record, $uri, $uid, $fetch_uid, $level, $last_poll);
if (empty($item)) { if (empty($item)) {
return 0; return 0;
@ -1205,7 +1248,7 @@ function bluesky_process_post(stdClass $post, int $uid, int $fetch_uid, int $pos
return bluesky_fetch_uri_id($uri, $uid); return bluesky_fetch_uri_id($uri, $uid);
} }
function bluesky_get_header(stdClass $post, string $uri, int $uid, int $fetch_uid): array function bluesky_get_header(stdClass $post, string $uri, int $uid, int $fetch_uid, int $last_poll = 0): array
{ {
$parts = bluesky_get_uri_parts($uri); $parts = bluesky_get_uri_parts($uri);
if (empty($post->author) || empty($post->cid) || empty($parts->rkey)) { if (empty($post->author) || empty($post->cid) || empty($parts->rkey)) {
@ -1218,6 +1261,7 @@ function bluesky_get_header(stdClass $post, string $uri, int $uid, int $fetch_ui
'wall' => false, 'wall' => false,
'uri' => $uri, 'uri' => $uri,
'guid' => $post->cid, 'guid' => $post->cid,
'received' => DateTimeFormat::utc($post->indexedAt, DateTimeFormat::MYSQL),
'private' => Item::UNLISTED, 'private' => Item::UNLISTED,
'verb' => Activity::POST, 'verb' => Activity::POST,
'contact-id' => $contact['id'], 'contact-id' => $contact['id'],
@ -1228,6 +1272,10 @@ function bluesky_get_header(stdClass $post, string $uri, int $uid, int $fetch_ui
'source' => json_encode($post), 'source' => json_encode($post),
]; ];
if (($last_poll != 0) && strtotime($item['received']) < $last_poll) {
unset($item['received']);
}
$account = Contact::selectFirstAccountUser(['pid'], ['id' => $contact['id']]); $account = Contact::selectFirstAccountUser(['pid'], ['id' => $contact['id']]);
$item['author-id'] = $account['pid']; $item['author-id'] = $account['pid'];
@ -1247,6 +1295,7 @@ function bluesky_get_header(stdClass $post, string $uri, int $uid, int $fetch_ui
// When "ver" is set to "1" it was flagged by some automated process. // When "ver" is set to "1" it was flagged by some automated process.
if (empty($label->ver)) { if (empty($label->ver)) {
$item['sensitive'] = true; $item['sensitive'] = true;
$item['content-warning'] = $label->val ?? '';
Logger::debug('Sensitive content', ['uri-id' => $item['uri-id'], 'label' => $label]); Logger::debug('Sensitive content', ['uri-id' => $item['uri-id'], 'label' => $label]);
} }
} }
@ -1326,10 +1375,6 @@ function bluesky_get_content(array $item, stdClass $record, string $uri, int $ui
$item['created'] = DateTimeFormat::utc($record->createdAt, DateTimeFormat::MYSQL); $item['created'] = DateTimeFormat::utc($record->createdAt, DateTimeFormat::MYSQL);
$item['transmitted-languages'] = $record->langs ?? []; $item['transmitted-languages'] = $record->langs ?? [];
if (($last_poll != 0) && strtotime($item['created']) > $last_poll) {
$item['received'] = $item['created'];
}
return $item; return $item;
} }
@ -1407,6 +1452,19 @@ function bluesky_add_media(stdClass $embed, array $item, int $fetch_uid, int $le
} }
break; break;
case 'app.bsky.embed.video#view':
$media = [
'uri-id' => $item['uri-id'],
'type' => Post\Media::HLS,
'url' => $embed->playlist,
'preview' => $embed->thumbnail,
'description' => $embed->alt ?? '',
'height' => $embed->aspectRatio->height,
'width' => $embed->aspectRatio->width,
];
Post\Media::insert($media);
break;
case 'app.bsky.embed.external#view': case 'app.bsky.embed.external#view':
$media = [ $media = [
'uri-id' => $item['uri-id'], 'uri-id' => $item['uri-id'],
@ -1506,10 +1564,10 @@ function bluesky_get_uri_parts(string $uri): ?stdClass
return $class; return $class;
} }
function bluesky_fetch_missing_post(string $uri, int $uid, int $fetch_uid, int $post_reason, int $causer, int $level, int $last_poll, string $fallback = ''): string function bluesky_fetch_missing_post(string $uri, int $uid, int $fetch_uid, int $post_reason, int $causer, int $level, int $last_poll, string $fallback = '', bool $always_fetch = false): string
{ {
$fetched_uri = bluesky_fetch_post($uri, $uid); $fetched_uri = bluesky_fetch_post($uri, $uid);
if (!empty($fetched_uri)) { if (!$always_fetch && !empty($fetched_uri)) {
return $fetched_uri; return $fetched_uri;
} }
@ -1529,7 +1587,7 @@ function bluesky_fetch_missing_post(string $uri, int $uid, int $fetch_uid, int $
Logger::debug('Fetch missing post', ['level' => $level, 'uid' => $uid, 'uri' => $uri]); Logger::debug('Fetch missing post', ['level' => $level, 'uid' => $uid, 'uri' => $uri]);
$data = bluesky_xrpc_get($fetch_uid, 'app.bsky.feed.getPostThread', ['uri' => $fetch_uri]); $data = bluesky_xrpc_get($fetch_uid, 'app.bsky.feed.getPostThread', ['uri' => $fetch_uri]);
if (empty($data)) { if (empty($data) || empty($data->thread)) {
Logger::info('Thread was not fetched', ['level' => $level, 'uid' => $uid, 'uri' => $uri, 'fallback' => $fallback]); Logger::info('Thread was not fetched', ['level' => $level, 'uid' => $uid, 'uri' => $uri, 'fallback' => $fallback]);
return $fallback; return $fallback;
} }
@ -1537,13 +1595,34 @@ function bluesky_fetch_missing_post(string $uri, int $uid, int $fetch_uid, int $
Logger::debug('Reply count', ['level' => $level, 'uid' => $uid, 'uri' => $uri]); Logger::debug('Reply count', ['level' => $level, 'uid' => $uid, 'uri' => $uri]);
if ($causer != 0) { if ($causer != 0) {
$cdata = Contact::getPublicAndUserContactID($causer, $uid); $causer = Contact::getPublicContactId($causer, $uid);
$causer = $cdata['public'] ?? 0;
} }
if (!empty($data->thread->parent)) {
$parents = bluesky_fetch_parents($data->thread->parent, $uid);
foreach ($parents as $parent) {
$uri_id = bluesky_process_post($parent, $uid, $fetch_uid, Item::PR_FETCHED, $causer, $level, $last_poll);
Logger::debug('Parent created', ['uri-id' => $uri_id]);
}
}
return bluesky_process_thread($data->thread, $uid, $fetch_uid, $post_reason, $causer, $level, $last_poll); return bluesky_process_thread($data->thread, $uid, $fetch_uid, $post_reason, $causer, $level, $last_poll);
} }
function bluesky_fetch_parents(stdClass $parent, int $uid, array $parents = []): array
{
if (!empty($parent->parent)) {
$parents = bluesky_fetch_parents($parent->parent, $uid, $parents);
}
if (!empty($parent->post) && empty(bluesky_fetch_post(bluesky_get_uri($parent->post), $uid))) {
$parents[] = $parent->post;
}
return $parents;
}
function bluesky_fetch_post(string $uri, int $uid): string function bluesky_fetch_post(string $uri, int $uid): string
{ {
if (Post::exists(['uri' => $uri, 'uid' => [$uid, 0]])) { if (Post::exists(['uri' => $uri, 'uid' => [$uid, 0]])) {
@ -1678,10 +1757,14 @@ function bluesky_get_contact_fields(stdClass $author, int $uid, int $fetch_uid,
return $fields; return $fields;
} }
$fields['baseurl'] = bluesky_get_pds($author->did); $data = bluesky_get(BLUESKY_DIRECTORY . '/' . $author->did);
if (!empty($fields['baseurl'])) { if (!empty($data)) {
GServer::check($fields['baseurl'], Protocol::BLUESKY); $fields['baseurl'] = bluesky_get_pds('', $data);
$fields['gsid'] = GServer::getID($fields['baseurl'], true); if (!empty($fields['baseurl'])) {
GServer::check($fields['baseurl'], Protocol::BLUESKY);
$fields['gsid'] = GServer::getID($fields['baseurl'], true);
}
$fields['pubkey'] = bluesky_get_public_key('', $data);
} }
$data = bluesky_xrpc_get($fetch_uid, 'app.bsky.actor.getProfile', ['actor' => $author->did]); $data = bluesky_xrpc_get($fetch_uid, 'app.bsky.actor.getProfile', ['actor' => $author->did]);
@ -1748,6 +1831,55 @@ function bluesky_get_preferences(int $uid): ?stdClass
return $data; return $data;
} }
function bluesky_get_did_by_profile(string $url, int $uid): string
{
if (preg_match('#/profile/(.+)#', $url, $matches)) {
$did = bluesky_get_did($matches[1], $uid);
if (!empty($did)) {
return $did;
}
}
try {
$curlResult = DI::httpClient()->get($url, HttpClientAccept::HTML, [HttpClientOptions::REQUEST => HttpClientRequest::CONTACTINFO]);
} catch (\Throwable $th) {
return '';
}
if (!$curlResult->isSuccess()) {
return '';
}
$profile = $curlResult->getBodyString();
if (empty($profile)) {
return '';
}
$doc = new DOMDocument();
try {
@$doc->loadHTML($profile);
} catch (\Throwable $th) {
return '';
}
$xpath = new DOMXPath($doc);
$list = $xpath->query('//p[@id]');
foreach ($list as $node) {
foreach ($node->attributes as $attribute) {
if ($attribute->name == 'id') {
$ids[$attribute->value] = $node->textContent;
}
}
}
if (empty($ids['bsky_handle']) || empty($ids['bsky_did'])) {
return '';
}
if (!bluesky_valid_did($ids['bsky_did'], $ids['bsky_handle'])) {
Logger::notice('Invalid DID', ['handle' => $ids['bsky_handle'], 'did' => $ids['bsky_did']]);
return '';
}
return $ids['bsky_did'];
}
function bluesky_get_did_by_wellknown(string $handle): string function bluesky_get_did_by_wellknown(string $handle): string
{ {
$curlResult = DI::httpClient()->get('http://' . $handle . '/.well-known/atproto-did'); $curlResult = DI::httpClient()->get('http://' . $handle . '/.well-known/atproto-did');
@ -1757,7 +1889,6 @@ function bluesky_get_did_by_wellknown(string $handle): string
Logger::notice('Invalid DID', ['handle' => $handle, 'did' => $did]); Logger::notice('Invalid DID', ['handle' => $handle, 'did' => $did]);
return ''; return '';
} }
Logger::debug('Got DID by wellknown', ['handle' => $handle, 'did' => $did]);
return $did; return $did;
} }
return ''; return '';
@ -1776,14 +1907,13 @@ function bluesky_get_did_by_dns(string $handle): string
Logger::notice('Invalid DID', ['handle' => $handle, 'did' => $did]); Logger::notice('Invalid DID', ['handle' => $handle, 'did' => $did]);
return ''; return '';
} }
Logger::debug('Got DID by DNS', ['handle' => $handle, 'did' => $did]);
return $did; return $did;
} }
} }
return ''; return '';
} }
function bluesky_get_did(string $handle): string function bluesky_get_did(string $handle, int $uid): string
{ {
if ($handle == '') { if ($handle == '') {
return ''; return '';
@ -1793,23 +1923,39 @@ function bluesky_get_did(string $handle): string
$handle .= '.' . BLUESKY_HOSTNAME; $handle .= '.' . BLUESKY_HOSTNAME;
} }
// Deactivated at the moment, since it isn't reliable by now // At first we use the user PDS. That should cover most cases.
//$did = bluesky_get_did_by_dns($handle); $pds = DI::pConfig()->get($uid, 'bluesky', 'pds');
//if ($did != '') { if (!empty($pds)) {
// return $did; $data = bluesky_get($pds . '/xrpc/com.atproto.identity.resolveHandle?handle=' . urlencode($handle));
//} if (!empty($data) && !empty($data->did)) {
Logger::debug('Got DID by user PDS call', ['handle' => $handle, 'did' => $data->did]);
//$did = bluesky_get_did_by_wellknown($handle); return $data->did;
//if ($did != '') { }
// return $did;
//}
$data = bluesky_get(BLUESKY_PDS . '/xrpc/com.atproto.identity.resolveHandle?handle=' . urlencode($handle));
if (empty($data) || empty($data->did)) {
return '';
} }
Logger::debug('Got DID by PDS call', ['handle' => $handle, 'did' => $data->did]);
return $data->did; // Then we query the DNS, which is used for third party handles (DNS should be faster than wellknown)
$did = bluesky_get_did_by_dns($handle);
if ($did != '') {
Logger::debug('Got DID by DNS', ['handle' => $handle, 'did' => $did]);
return $did;
}
// Then we query wellknown, which should mostly cover the rest.
$did = bluesky_get_did_by_wellknown($handle);
if ($did != '') {
Logger::debug('Got DID by wellknown', ['handle' => $handle, 'did' => $did]);
return $did;
}
// And finally we use the default PDS from Bluesky.
$data = bluesky_get(BLUESKY_PDS . '/xrpc/com.atproto.identity.resolveHandle?handle=' . urlencode($handle));
if (!empty($data) && !empty($data->did)) {
Logger::debug('Got DID by system PDS call', ['handle' => $handle, 'did' => $data->did]);
return $data->did;
}
Logger::notice('No DID detected', ['handle' => $handle]);
return '';
} }
function bluesky_get_user_did(int $uid, bool $refresh = false): ?string function bluesky_get_user_did(int $uid, bool $refresh = false): ?string
@ -1826,7 +1972,7 @@ function bluesky_get_user_did(int $uid, bool $refresh = false): ?string
return null; return null;
} }
$did = bluesky_get_did($handle); $did = bluesky_get_did($handle, $uid);
if (empty($did)) { if (empty($did)) {
return null; return null;
} }
@ -1857,9 +2003,11 @@ function bluesky_get_user_pds(int $uid): ?string
return $pds; return $pds;
} }
function bluesky_get_pds(string $did): ?string function bluesky_get_pds(string $did, stdClass $data = null): ?string
{ {
$data = bluesky_get(BLUESKY_DIRECTORY . '/' . $did); if (empty($data)) {
$data = bluesky_get(BLUESKY_DIRECTORY . '/' . $did);
}
if (empty($data) || empty($data->service)) { if (empty($data) || empty($data->service)) {
return null; return null;
} }
@ -1873,6 +2021,22 @@ function bluesky_get_pds(string $did): ?string
return null; return null;
} }
function bluesky_get_public_key(string $did, stdClass $data = null): ?string
{
if (empty($data)) {
$data = bluesky_get(BLUESKY_DIRECTORY . '/' . $did);
}
if (empty($data) || empty($data->verificationMethod)) {
return null;
}
foreach ($data->verificationMethod as $method) {
if (!empty($method->publicKeyMultibase)) {
return $method->publicKeyMultibase;
}
}
return null;
}
function bluesky_valid_did(string $did, string $handle): bool function bluesky_valid_did(string $did, string $handle): bool
{ {
$data = bluesky_get(BLUESKY_DIRECTORY . '/' . $did); $data = bluesky_get(BLUESKY_DIRECTORY . '/' . $did);
@ -1902,7 +2066,7 @@ function bluesky_refresh_token(int $uid): string
$token = DI::pConfig()->get($uid, 'bluesky', 'refresh_token'); $token = DI::pConfig()->get($uid, 'bluesky', 'refresh_token');
$data = bluesky_post($uid, '/xrpc/com.atproto.server.refreshSession', '', ['Authorization' => ['Bearer ' . $token]]); $data = bluesky_post($uid, '/xrpc/com.atproto.server.refreshSession', '', ['Authorization' => ['Bearer ' . $token]]);
if (empty($data)) { if (empty($data) || empty($data->accessJwt)) {
return ''; return '';
} }
@ -1921,7 +2085,7 @@ function bluesky_create_token(int $uid, string $password): string
} }
$data = bluesky_post($uid, '/xrpc/com.atproto.server.createSession', json_encode(['identifier' => $did, 'password' => $password]), ['Content-type' => 'application/json']); $data = bluesky_post($uid, '/xrpc/com.atproto.server.createSession', json_encode(['identifier' => $did, 'password' => $password]), ['Content-type' => 'application/json']);
if (empty($data)) { if (empty($data) || empty($data->accessJwt)) {
DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_TOKEN_FAIL); DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_TOKEN_FAIL);
return ''; return '';
} }
@ -1936,7 +2100,11 @@ function bluesky_create_token(int $uid, string $password): string
function bluesky_xrpc_post(int $uid, string $url, $parameters): ?stdClass function bluesky_xrpc_post(int $uid, string $url, $parameters): ?stdClass
{ {
return bluesky_post($uid, '/xrpc/' . $url, json_encode($parameters), ['Content-type' => 'application/json', 'Authorization' => ['Bearer ' . bluesky_get_token($uid)]]); $data = bluesky_post($uid, '/xrpc/' . $url, json_encode($parameters), ['Content-type' => 'application/json', 'Authorization' => ['Bearer ' . bluesky_get_token($uid)]]);
if (!empty($data)) {
Item::incrementOutbound(Protocol::BLUESKY);
}
return $data;
} }
function bluesky_post(int $uid, string $url, string $params, array $headers): ?stdClass function bluesky_post(int $uid, string $url, string $params, array $headers): ?stdClass
@ -1954,14 +2122,18 @@ function bluesky_post(int $uid, string $url, string $params, array $headers): ?s
return null; return null;
} }
$data = json_decode($curlResult->getBodyString());
if (!$curlResult->isSuccess()) { if (!$curlResult->isSuccess()) {
Logger::notice('API Error', ['error' => json_decode($curlResult->getBodyString()) ?: $curlResult->getBodyString()]); Logger::notice('API Error', ['url' => $url, 'code' => $curlResult->getReturnCode(), 'error' => $data ?: $curlResult->getBodyString()]);
DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_API_FAIL); if (!$data) {
return null; DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_API_FAIL);
return null;
}
$data->code = $curlResult->getReturnCode();
} }
DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_SUCCESS); DI::pConfig()->set($uid, 'bluesky', 'status', BLUEKSY_STATUS_SUCCESS);
return json_decode($curlResult->getBodyString()); return $data;
} }
function bluesky_xrpc_get(int $uid, string $url, array $parameters = []): ?stdClass function bluesky_xrpc_get(int $uid, string $url, array $parameters = []): ?stdClass
@ -1975,7 +2147,14 @@ function bluesky_xrpc_get(int $uid, string $url, array $parameters = []): ?stdCl
return null; return null;
} }
$data = bluesky_get($pds . '/xrpc/' . $url, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]]]); $headers = ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]];
$languages = User::getWantedLanguages($uid);
if (!empty($languages)) {
$headers['Accept-Language'] = implode(',', $languages);
}
$data = bluesky_get($pds . '/xrpc/' . $url, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => $headers]);
DI::pConfig()->set($uid, 'bluesky', 'status', is_null($data) ? BLUEKSY_STATUS_API_FAIL : BLUEKSY_STATUS_SUCCESS); DI::pConfig()->set($uid, 'bluesky', 'status', is_null($data) ? BLUEKSY_STATUS_API_FAIL : BLUEKSY_STATUS_SUCCESS);
return $data; return $data;
} }
@ -1989,10 +2168,15 @@ function bluesky_get(string $url, string $accept_content = HttpClientAccept::DEF
return null; return null;
} }
$data = json_decode($curlResult->getBodyString());
if (!$curlResult->isSuccess()) { if (!$curlResult->isSuccess()) {
Logger::notice('API Error', ['url' => $url, 'error' => json_decode($curlResult->getBodyString()) ?: $curlResult->getBodyString()]); Logger::notice('API Error', ['url' => $url, 'code' => $curlResult->getReturnCode(), 'error' => $data ?: $curlResult->getBodyString()]);
return null; if (!$data) {
return null;
}
$data->code = $curlResult->getReturnCode();
} }
return json_decode($curlResult->getBodyString()); Item::incrementInbound(Protocol::BLUESKY);
return $data;
} }

View file

@ -6,11 +6,11 @@ function bluesky_feed_run($argv, $argc)
{ {
require_once 'addon/bluesky/bluesky.php'; require_once 'addon/bluesky/bluesky.php';
if ($argc != 4) { if ($argc < 3) {
return; return;
} }
Logger::debug('Importing feed - start', ['user' => $argv[1], 'feed' => $argv[2], 'last_poll' => $argv[3]]); Logger::debug('Importing feed - start', ['user' => $argv[1], 'feed' => $argv[2]]);
bluesky_fetch_feed($argv[1], $argv[2], $argv[3]); bluesky_fetch_feed($argv[1], $argv[2]);
Logger::debug('Importing feed - done', ['user' => $argv[1], 'feed' => $argv[2], 'last_poll' => $argv[3]]); Logger::debug('Importing feed - done', ['user' => $argv[1], 'feed' => $argv[2]]);
} }

View file

@ -6,11 +6,11 @@ function bluesky_notifications_run($argv, $argc)
{ {
require_once 'addon/bluesky/bluesky.php'; require_once 'addon/bluesky/bluesky.php';
if ($argc != 3) { if ($argc < 2) {
return; return;
} }
Logger::notice('importing notifications - start', ['user' => $argv[1], 'last_poll' => $argv[2]]); Logger::notice('importing notifications - start', ['user' => $argv[1]]);
bluesky_fetch_notifications($argv[1], $argv[2]); bluesky_fetch_notifications($argv[1]);
Logger::notice('importing notifications - done', ['user' => $argv[1], 'last_poll' => $argv[2]]); Logger::notice('importing notifications - done', ['user' => $argv[1]]);
} }

View file

@ -6,11 +6,11 @@ function bluesky_timeline_run($argv, $argc)
{ {
require_once 'addon/bluesky/bluesky.php'; require_once 'addon/bluesky/bluesky.php';
if ($argc != 3) { if ($argc < 2) {
return; return;
} }
Logger::notice('importing timeline - start', ['user' => $argv[1], 'last_poll' => $argv[2]]); Logger::notice('importing timeline - start', ['user' => $argv[1]]);
bluesky_fetch_timeline($argv[1], $argv[2]); bluesky_fetch_timeline($argv[1]);
Logger::notice('importing timeline - done', ['user' => $argv[1], 'last_poll' => $argv[2]]); Logger::notice('importing timeline - done', ['user' => $argv[1]]);
} }

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-22 05:31+0000\n" "POT-Creation-Date: 2024-09-29 18:16+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,131 +17,117 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: bluesky.php:325 #: bluesky.php:335
msgid "Save Settings" msgid "Save Settings"
msgstr "" msgstr ""
#: bluesky.php:326 #: bluesky.php:336
msgid "Allow your users to use your hostname for their Bluesky handles" msgid "Allow your users to use your hostname for their Bluesky handles"
msgstr "" msgstr ""
#: bluesky.php:326 #: bluesky.php:336
#, php-format #, php-format
msgid "" msgid "Before enabling this option, you have to setup a wildcard domain configuration and you have to enable wildcard requests in your webserver configuration. On Apache this is done by adding \"ServerAlias *.%s\" to your HTTP configuration. You don't need to change the HTTPS configuration."
"Before enabling this option, you have to setup a wildcard domain "
"configuration and you have to enable wildcard requests in your webserver "
"configuration. On Apache this is done by adding \"ServerAlias *.%s\" to your "
"HTTP configuration. You don't need to change the HTTPS configuration."
msgstr "" msgstr ""
#: bluesky.php:354 #: bluesky.php:365
#, php-format #, php-format
msgid "Allow to use %s as your Bluesky handle." msgid "Allow to use %s as your Bluesky handle."
msgstr "" msgstr ""
#: bluesky.php:354 #: bluesky.php:365
#, php-format #, php-format
msgid "" msgid "When enabled, you can use %s as your Bluesky handle. After you enabled this option, please go to https://bsky.app/settings and select to change your handle. Select that you have got your own domain. Then enter %s and select \"No DNS Panel\". Then select \"Verify Text File\"."
"When enabled, you can use %s as your Bluesky handle. After you enabled this "
"option, please go to https://bsky.app/settings and select to change your "
"handle. Select that you have got your own domain. Then enter %s and select "
"\"No DNS Panel\". Then select \"Verify Text File\"."
msgstr ""
#: bluesky.php:361
msgid "Enable Bluesky Post Addon"
msgstr ""
#: bluesky.php:362
msgid "Post to Bluesky by default"
msgstr ""
#: bluesky.php:363
msgid "Import the remote timeline"
msgstr ""
#: bluesky.php:364
msgid "Import the pinned feeds"
msgstr ""
#: bluesky.php:364
msgid ""
"When activated, Posts will be imported from all the feeds that you pinned in "
"Bluesky."
msgstr ""
#: bluesky.php:366
msgid "Personal Data Server"
msgstr ""
#: bluesky.php:366
msgid "The personal data server (PDS) is the system that hosts your profile."
msgstr ""
#: bluesky.php:367
msgid "Bluesky handle"
msgstr ""
#: bluesky.php:368
msgid "Bluesky DID"
msgstr ""
#: bluesky.php:368
msgid ""
"This is the unique identifier. It will be fetched automatically, when the "
"handle is entered."
msgstr ""
#: bluesky.php:369
msgid "Bluesky app password"
msgstr ""
#: bluesky.php:369
msgid ""
"Please don't add your real password here, but instead create a specific app "
"password in the Bluesky settings."
msgstr "" msgstr ""
#: bluesky.php:375 #: bluesky.php:375
msgid "Enable Bluesky Post Addon"
msgstr ""
#: bluesky.php:376
msgid "Post to Bluesky by default"
msgstr ""
#: bluesky.php:377
msgid "Import the remote timeline"
msgstr ""
#: bluesky.php:378
msgid "Import the pinned feeds"
msgstr ""
#: bluesky.php:378
msgid "When activated, Posts will be imported from all the feeds that you pinned in Bluesky."
msgstr ""
#: bluesky.php:379
msgid "Complete the threads"
msgstr ""
#: bluesky.php:379
msgid "When activated, the system fetches additional replies for the posts in the timeline. This leads to more complete threads."
msgstr ""
#: bluesky.php:381
msgid "Personal Data Server"
msgstr ""
#: bluesky.php:381
msgid "The personal data server (PDS) is the system that hosts your profile."
msgstr ""
#: bluesky.php:382
msgid "Bluesky handle"
msgstr ""
#: bluesky.php:383
msgid "Bluesky DID"
msgstr ""
#: bluesky.php:383
msgid "This is the unique identifier. It will be fetched automatically, when the handle is entered."
msgstr ""
#: bluesky.php:384
msgid "Bluesky app password"
msgstr ""
#: bluesky.php:384
msgid "Please don't add your real password here, but instead create a specific app password in the Bluesky settings."
msgstr ""
#: bluesky.php:390
msgid "Bluesky Import/Export" msgid "Bluesky Import/Export"
msgstr "" msgstr ""
#: bluesky.php:385 #: bluesky.php:400
msgid "" msgid "You are not authenticated. Please enter your handle and the app password."
"You are not authenticated. Please enter your handle and the app password."
msgstr "" msgstr ""
#: bluesky.php:405 #: bluesky.php:420
msgid "" msgid "You are authenticated to Bluesky. For security reasons the password isn't stored."
"You are authenticated to Bluesky. For security reasons the password isn't "
"stored."
msgstr "" msgstr ""
#: bluesky.php:407 #: bluesky.php:422
msgid "" msgid "The communication with the personal data server service (PDS) is established."
"The communication with the personal data server service (PDS) is established."
msgstr "" msgstr ""
#: bluesky.php:409 #: bluesky.php:424
msgid "Communication issues with the personal data server service (PDS)." msgid "Communication issues with the personal data server service (PDS)."
msgstr "" msgstr ""
#: bluesky.php:411 #: bluesky.php:426
msgid "" msgid "The DID for the provided handle could not be detected. Please check if you entered the correct handle."
"The DID for the provided handle could not be detected. Please check if you "
"entered the correct handle."
msgstr "" msgstr ""
#: bluesky.php:413 #: bluesky.php:428
msgid "The personal data server service (PDS) could not be detected." msgid "The personal data server service (PDS) could not be detected."
msgstr "" msgstr ""
#: bluesky.php:415 #: bluesky.php:430
msgid "" msgid "The authentication with the provided handle and password failed. Please check if you entered the correct password."
"The authentication with the provided handle and password failed. Please "
"check if you entered the correct password."
msgstr "" msgstr ""
#: bluesky.php:484 #: bluesky.php:492
msgid "Post to Bluesky" msgid "Post to Bluesky"
msgstr "" msgstr ""

View file

@ -3,6 +3,7 @@
{{include file="field_checkbox.tpl" field=$bydefault}} {{include file="field_checkbox.tpl" field=$bydefault}}
{{include file="field_checkbox.tpl" field=$import}} {{include file="field_checkbox.tpl" field=$import}}
{{include file="field_checkbox.tpl" field=$import_feeds}} {{include file="field_checkbox.tpl" field=$import_feeds}}
{{include file="field_checkbox.tpl" field=$complete_threads}}
{{if $custom_handle}} {{if $custom_handle}}
{{include file="field_checkbox.tpl" field=$custom_handle}} {{include file="field_checkbox.tpl" field=$custom_handle}}
{{/if}} {{/if}}

View file

@ -152,7 +152,7 @@ function curweather_network_mod_init(string &$body)
function curweather_addon_settings_post($post) function curweather_addon_settings_post($post)
{ {
if (!DI::userSession()->getLocalUserId() || empty($_POST['curweather-settings-submit'])) { if (!DI::userSession()->getLocalUserId() || empty($_POST['curweather-submit'])) {
return; return;
} }

View file

@ -5,6 +5,7 @@
# #
# Translators: # Translators:
# bob lebonche <lebonche@tutanota.com>, 2021 # bob lebonche <lebonche@tutanota.com>, 2021
# cracrayol, 2024
# Hypolite Petovan <hypolite@mrpetovan.com>, 2022 # Hypolite Petovan <hypolite@mrpetovan.com>, 2022
# Hypolite Petovan <hypolite@mrpetovan.com>, 2016 # Hypolite Petovan <hypolite@mrpetovan.com>, 2016
# ea1cd8241cb389ffb6f92bc6891eff5d_dc12308 <70dced5587d47e18d88f9298024d96f8_93383>, 2015 # ea1cd8241cb389ffb6f92bc6891eff5d_dc12308 <70dced5587d47e18d88f9298024d96f8_93383>, 2015
@ -15,8 +16,8 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-21 19:14-0500\n" "POT-Creation-Date: 2021-11-21 19:14-0500\n"
"PO-Revision-Date: 2014-06-22 11:34+0000\n" "PO-Revision-Date: 2014-06-22 11:34+0000\n"
"Last-Translator: Hypolite Petovan <hypolite@mrpetovan.com>, 2022\n" "Last-Translator: cracrayol, 2024\n"
"Language-Team: French (http://www.transifex.com/Friendica/friendica/language/fr/)\n" "Language-Team: French (http://app.transifex.com/Friendica/friendica/language/fr/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -45,7 +46,7 @@ msgstr "Vent"
#: curweather.php:140 #: curweather.php:140
msgid "Last Updated" msgid "Last Updated"
msgstr "Dernière mise-à-jour" msgstr "Dernière mise à jour"
#: curweather.php:141 #: curweather.php:141
msgid "Data by" msgid "Data by"

View file

@ -10,7 +10,7 @@ $a->strings['Current Weather'] = 'Météo actuelle';
$a->strings['Relative Humidity'] = 'Humidité relative'; $a->strings['Relative Humidity'] = 'Humidité relative';
$a->strings['Pressure'] = 'Pression'; $a->strings['Pressure'] = 'Pression';
$a->strings['Wind'] = 'Vent'; $a->strings['Wind'] = 'Vent';
$a->strings['Last Updated'] = 'Dernière mise-à-jour'; $a->strings['Last Updated'] = 'Dernière mise à jour';
$a->strings['Data by'] = 'Données de'; $a->strings['Data by'] = 'Données de';
$a->strings['Show on map'] = 'Montrer sur la carte'; $a->strings['Show on map'] = 'Montrer sur la carte';
$a->strings['There was a problem accessing the weather data. But have a look'] = 'Une erreur est survenue lors de l\'accès aux données météo. Vous pouvez quand même jeter un oeil'; $a->strings['There was a problem accessing the weather data. But have a look'] = 'Une erreur est survenue lors de l\'accès aux données météo. Vous pouvez quand même jeter un oeil';

View file

@ -6,6 +6,7 @@
# Translators: # Translators:
# bob lebonche <lebonche@tutanota.com>, 2021 # bob lebonche <lebonche@tutanota.com>, 2021
# ButterflyOfFire, 2020 # ButterflyOfFire, 2020
# cracrayol, 2024
# Hypolite Petovan <hypolite@mrpetovan.com>, 2016 # Hypolite Petovan <hypolite@mrpetovan.com>, 2016
msgid "" msgid ""
msgstr "" msgstr ""
@ -13,8 +14,8 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-21 19:14-0500\n" "POT-Creation-Date: 2021-11-21 19:14-0500\n"
"PO-Revision-Date: 2014-06-23 08:27+0000\n" "PO-Revision-Date: 2014-06-23 08:27+0000\n"
"Last-Translator: bob lebonche <lebonche@tutanota.com>, 2021\n" "Last-Translator: cracrayol, 2024\n"
"Language-Team: French (http://www.transifex.com/Friendica/friendica/language/fr/)\n" "Language-Team: French (http://app.transifex.com/Friendica/friendica/language/fr/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -29,7 +30,7 @@ msgstr "Remplacer les coordonnées par le nom de la localité la plus proche dan
#: geonames.php:136 #: geonames.php:136
msgid "Enable Geonames Addon" msgid "Enable Geonames Addon"
msgstr "Activer l'application complémentaire Geonames" msgstr "Activer l'extension Geonames"
#: geonames.php:141 #: geonames.php:141
msgid "Geonames Settings" msgid "Geonames Settings"

View file

@ -6,5 +6,5 @@ function string_plural_select_fr($n){
if (($n == 0 || $n == 1)) { return 0; } else if ($n != 0 && $n % 1000000 == 0) { return 1; } else { return 2; } if (($n == 0 || $n == 1)) { return 0; } else if ($n != 0 && $n % 1000000 == 0) { return 1; } else { return 2; }
}} }}
$a->strings['Replace numerical coordinates by the nearest populated location name in your posts.'] = 'Remplacer les coordonnées par le nom de la localité la plus proche dans votre publication.'; $a->strings['Replace numerical coordinates by the nearest populated location name in your posts.'] = 'Remplacer les coordonnées par le nom de la localité la plus proche dans votre publication.';
$a->strings['Enable Geonames Addon'] = 'Activer l\'application complémentaire Geonames'; $a->strings['Enable Geonames Addon'] = 'Activer l\'extension Geonames';
$a->strings['Geonames Settings'] = 'Paramètres Geonames'; $a->strings['Geonames Settings'] = 'Paramètres Geonames';

View file

@ -6,6 +6,7 @@
# Translators: # Translators:
# bob lebonche <lebonche@tutanota.com>, 2021 # bob lebonche <lebonche@tutanota.com>, 2021
# ButterflyOfFire, 2020 # ButterflyOfFire, 2020
# cracrayol, 2024
# Hypolite Petovan <hypolite@mrpetovan.com>, 2016 # Hypolite Petovan <hypolite@mrpetovan.com>, 2016
msgid "" msgid ""
msgstr "" msgstr ""
@ -13,8 +14,8 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-21 19:14-0500\n" "POT-Creation-Date: 2021-11-21 19:14-0500\n"
"PO-Revision-Date: 2014-06-23 08:30+0000\n" "PO-Revision-Date: 2014-06-23 08:30+0000\n"
"Last-Translator: bob lebonche <lebonche@tutanota.com>, 2021\n" "Last-Translator: cracrayol, 2024\n"
"Language-Team: French (http://www.transifex.com/Friendica/friendica/language/fr/)\n" "Language-Team: French (http://app.transifex.com/Friendica/friendica/language/fr/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -29,7 +30,7 @@ msgstr "Permettre le filtrage des notifications de commentaires par courriel sur
#: gnot.php:64 #: gnot.php:64
msgid "Enable this addon?" msgid "Enable this addon?"
msgstr "Activer cette application complémentaire ?" msgstr "Activer cette extension ?"
#: gnot.php:69 #: gnot.php:69
msgid "Gnot Settings" msgid "Gnot Settings"

View file

@ -6,6 +6,6 @@ function string_plural_select_fr($n){
if (($n == 0 || $n == 1)) { return 0; } else if ($n != 0 && $n % 1000000 == 0) { return 1; } else { return 2; } if (($n == 0 || $n == 1)) { return 0; } else if ($n != 0 && $n % 1000000 == 0) { return 1; } else { return 2; }
}} }}
$a->strings['Allows threading of email comment notifications on Gmail and anonymising the subject line.'] = 'Permettre le filtrage des notifications de commentaires par courriel sur Gmail et l\'anonymisation de l\'objet.'; $a->strings['Allows threading of email comment notifications on Gmail and anonymising the subject line.'] = 'Permettre le filtrage des notifications de commentaires par courriel sur Gmail et l\'anonymisation de l\'objet.';
$a->strings['Enable this addon?'] = 'Activer cette application complémentaire ?'; $a->strings['Enable this addon?'] = 'Activer cette extension ?';
$a->strings['Gnot Settings'] = 'Paramètres Gnot'; $a->strings['Gnot Settings'] = 'Paramètres Gnot';
$a->strings['[Friendica:Notify] Comment to conversation #%d'] = '[Friendica:Notify] Commentaire vers conversation #%d'; $a->strings['[Friendica:Notify] Comment to conversation #%d'] = '[Friendica:Notify] Commentaire vers conversation #%d';

View file

@ -5,6 +5,7 @@
# #
# Translators: # Translators:
# bob lebonche <lebonche@tutanota.com>, 2021 # bob lebonche <lebonche@tutanota.com>, 2021
# cracrayol, 2024
# Marie Olive <lacellule101@gmail.com>, 2018 # Marie Olive <lacellule101@gmail.com>, 2018
# ea1cd8241cb389ffb6f92bc6891eff5d_dc12308 <70dced5587d47e18d88f9298024d96f8_93383>, 2015 # ea1cd8241cb389ffb6f92bc6891eff5d_dc12308 <70dced5587d47e18d88f9298024d96f8_93383>, 2015
msgid "" msgid ""
@ -13,8 +14,8 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-02-01 18:15+0100\n" "POT-Creation-Date: 2021-02-01 18:15+0100\n"
"PO-Revision-Date: 2014-06-23 08:33+0000\n" "PO-Revision-Date: 2014-06-23 08:33+0000\n"
"Last-Translator: bob lebonche <lebonche@tutanota.com>, 2021\n" "Last-Translator: cracrayol, 2024\n"
"Language-Team: French (http://www.transifex.com/Friendica/friendica/language/fr/)\n" "Language-Team: French (http://app.transifex.com/Friendica/friendica/language/fr/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -50,7 +51,7 @@ msgid ""
"Libravatar addon is installed, too. Please disable Libravatar addon or this " "Libravatar addon is installed, too. Please disable Libravatar addon or this "
"Gravatar addon.<br>The Libravatar addon will fall back to Gravatar if " "Gravatar addon.<br>The Libravatar addon will fall back to Gravatar if "
"nothing was found at Libravatar." "nothing was found at Libravatar."
msgstr "L'application complémentaire Libravatar est aussi installée. Merci de désactiver l'application complémentaire Libravatar ou cette application complémentaire Gravatar. L'application complémentaire se repliera sur Gravatar si rien n'est trouvé dans Libravatar." msgstr "L'extension Libravatar est aussi installée. Merci de désactiver l'extension Libravatar ou cette extension Gravatar. L'extension se repliera sur Gravatar si rien n'est trouvé dans Libravatar."
#: gravatar.php:102 #: gravatar.php:102
msgid "Save Settings" msgid "Save Settings"

View file

@ -11,7 +11,7 @@ $a->strings['monster face'] = 'Face de monstre';
$a->strings['computer generated face'] = 'visage généré par ordinateur'; $a->strings['computer generated face'] = 'visage généré par ordinateur';
$a->strings['retro arcade style face'] = 'Face style retro arcade'; $a->strings['retro arcade style face'] = 'Face style retro arcade';
$a->strings['Information'] = 'Information'; $a->strings['Information'] = 'Information';
$a->strings['Libravatar addon is installed, too. Please disable Libravatar addon or this Gravatar addon.<br>The Libravatar addon will fall back to Gravatar if nothing was found at Libravatar.'] = 'L\'application complémentaire Libravatar est aussi installée. Merci de désactiver l\'application complémentaire Libravatar ou cette application complémentaire Gravatar. L\'application complémentaire se repliera sur Gravatar si rien n\'est trouvé dans Libravatar.'; $a->strings['Libravatar addon is installed, too. Please disable Libravatar addon or this Gravatar addon.<br>The Libravatar addon will fall back to Gravatar if nothing was found at Libravatar.'] = 'L\'extension Libravatar est aussi installée. Merci de désactiver l\'extension Libravatar ou cette extension Gravatar. L\'extension se repliera sur Gravatar si rien n\'est trouvé dans Libravatar.';
$a->strings['Save Settings'] = 'Sauvegarder les paramètres.'; $a->strings['Save Settings'] = 'Sauvegarder les paramètres.';
$a->strings['Default avatar image'] = 'Image par défaut d\'avatar'; $a->strings['Default avatar image'] = 'Image par défaut d\'avatar';
$a->strings['Select default avatar image if none was found at Gravatar. See README'] = 'Sélectionner l\'avatar par défaut, si aucun n\'est trouvé sur Gravatar. Voir Lisezmoi.'; $a->strings['Select default avatar image if none was found at Gravatar. See README'] = 'Sélectionner l\'avatar par défaut, si aucun n\'est trouvé sur Gravatar. Voir Lisezmoi.';

View file

@ -5,6 +5,7 @@
# #
# Translators: # Translators:
# bob lebonche <lebonche@tutanota.com>, 2021 # bob lebonche <lebonche@tutanota.com>, 2021
# cracrayol, 2024
# Hypolite Petovan <hypolite@mrpetovan.com>, 2016 # Hypolite Petovan <hypolite@mrpetovan.com>, 2016
msgid "" msgid ""
msgstr "" msgstr ""
@ -12,8 +13,8 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-21 19:17-0500\n" "POT-Creation-Date: 2021-11-21 19:17-0500\n"
"PO-Revision-Date: 2014-06-23 08:37+0000\n" "PO-Revision-Date: 2014-06-23 08:37+0000\n"
"Last-Translator: bob lebonche <lebonche@tutanota.com>, 2021\n" "Last-Translator: cracrayol, 2024\n"
"Language-Team: French (http://www.transifex.com/Friendica/friendica/language/fr/)\n" "Language-Team: French (http://app.transifex.com/Friendica/friendica/language/fr/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -26,7 +27,7 @@ msgstr "Publier sur Insanejournal"
#: ijpost.php:61 #: ijpost.php:61
msgid "Enable InsaneJournal Post Addon" msgid "Enable InsaneJournal Post Addon"
msgstr "Activer l'application complémentaire InsaneJournalPost" msgstr "Activer l'extension InsaneJournal"
#: ijpost.php:62 #: ijpost.php:62
msgid "InsaneJournal username" msgid "InsaneJournal username"

View file

@ -6,7 +6,7 @@ function string_plural_select_fr($n){
if (($n == 0 || $n == 1)) { return 0; } else if ($n != 0 && $n % 1000000 == 0) { return 1; } else { return 2; } if (($n == 0 || $n == 1)) { return 0; } else if ($n != 0 && $n % 1000000 == 0) { return 1; } else { return 2; }
}} }}
$a->strings['Post to Insanejournal'] = 'Publier sur Insanejournal'; $a->strings['Post to Insanejournal'] = 'Publier sur Insanejournal';
$a->strings['Enable InsaneJournal Post Addon'] = 'Activer l\'application complémentaire InsaneJournalPost'; $a->strings['Enable InsaneJournal Post Addon'] = 'Activer l\'extension InsaneJournal';
$a->strings['InsaneJournal username'] = 'Identifiant du InsaneJournal'; $a->strings['InsaneJournal username'] = 'Identifiant du InsaneJournal';
$a->strings['InsaneJournal password'] = 'Mot de passe du InsaneJournal'; $a->strings['InsaneJournal password'] = 'Mot de passe du InsaneJournal';
$a->strings['Post to InsaneJournal by default'] = 'Publier sur le InsaneJournal par défaut'; $a->strings['Post to InsaneJournal by default'] = 'Publier sur le InsaneJournal par défaut';

View file

@ -96,6 +96,8 @@ function invidious_render(array &$b)
$b['html'] = preg_replace("~https?://(?:www\.)?youtube\.com/watch\?v=(.*?)~ism", $server . '/watch?v=$1', $b['html']); $b['html'] = preg_replace("~https?://(?:www\.)?youtube\.com/watch\?v=(.*?)~ism", $server . '/watch?v=$1', $b['html']);
$b['html'] = preg_replace("~https?://(?:www\.)?youtube\.com/embed/(.*?)~ism", $server . '/embed/$1', $b['html']); $b['html'] = preg_replace("~https?://(?:www\.)?youtube\.com/embed/(.*?)~ism", $server . '/embed/$1', $b['html']);
$b['html'] = preg_replace("~https?://(?:www\.)?youtube\.com/shorts/(.*?)~ism", $server . '/shorts/$1', $b['html']); $b['html'] = preg_replace("~https?://(?:www\.)?youtube\.com/shorts/(.*?)~ism", $server . '/shorts/$1', $b['html']);
$b['html'] = preg_replace ("/https?:\/\/music.youtube.com\/(.*?)/ism", $server . '/watch?v=$1', $b['html']);
$b['html'] = preg_replace ("/https?:\/\/m.youtube.com\/(.*?)/ism", $server . '/watch?v=$1', $b['html']);
$b['html'] = preg_replace("/https?:\/\/youtu.be\/(.*?)/ism", $server . '/watch?v=$1', $b['html']); $b['html'] = preg_replace("/https?:\/\/youtu.be\/(.*?)/ism", $server . '/watch?v=$1', $b['html']);
if ($original != $b['html']) { if ($original != $b['html']) {

View file

@ -5,14 +5,15 @@
# #
# Translators: # Translators:
# bob lebonche <lebonche@tutanota.com>, 2021 # bob lebonche <lebonche@tutanota.com>, 2021
# cracrayol, 2024
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: friendica\n" "Project-Id-Version: friendica\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-21 19:14-0500\n" "POT-Creation-Date: 2021-11-21 19:14-0500\n"
"PO-Revision-Date: 2015-07-07 15:14+0000\n" "PO-Revision-Date: 2015-07-07 15:14+0000\n"
"Last-Translator: bob lebonche <lebonche@tutanota.com>, 2021\n" "Last-Translator: cracrayol, 2024\n"
"Language-Team: French (http://www.transifex.com/Friendica/friendica/language/fr/)\n" "Language-Team: French (http://app.transifex.com/Friendica/friendica/language/fr/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -21,7 +22,7 @@ msgstr ""
#: krynn.php:127 #: krynn.php:127
msgid "Enable Krynn Addon" msgid "Enable Krynn Addon"
msgstr "Activer l'application complémentaire Krynn" msgstr "Activer l'extension Krynn"
#: krynn.php:132 #: krynn.php:132
msgid "Krynn Settings" msgid "Krynn Settings"

View file

@ -5,5 +5,5 @@ function string_plural_select_fr($n){
$n = intval($n); $n = intval($n);
if (($n == 0 || $n == 1)) { return 0; } else if ($n != 0 && $n % 1000000 == 0) { return 1; } else { return 2; } if (($n == 0 || $n == 1)) { return 0; } else if ($n != 0 && $n % 1000000 == 0) { return 1; } else { return 2; }
}} }}
$a->strings['Enable Krynn Addon'] = 'Activer l\'application complémentaire Krynn'; $a->strings['Enable Krynn Addon'] = 'Activer l\'extension Krynn';
$a->strings['Krynn Settings'] = 'Paramètres de Krynn'; $a->strings['Krynn Settings'] = 'Paramètres de Krynn';

View file

@ -6,7 +6,6 @@
* Author: Matthew Exon <http://mat.exon.name> * Author: Matthew Exon <http://mat.exon.name>
*/ */
use Friendica\App;
use Friendica\Content\Text\BBCode; use Friendica\Content\Text\BBCode;
use Friendica\Core\Hook; use Friendica\Core\Hook;
use Friendica\Core\Logger; use Friendica\Core\Logger;
@ -16,12 +15,11 @@ use Friendica\Core\Worker;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Network\HTTPClient\Client\HttpClientAccept; use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Protocol\Activity; use Friendica\Protocol\Activity;
use Friendica\Util\DateTimeFormat;
/** /**
* Sets up the addon hooks and the database table * Sets up the addon hooks and the database table
@ -34,26 +32,7 @@ function mailstream_install()
Hook::register('post_remote_end', 'addon/mailstream/mailstream.php', 'mailstream_post_hook'); Hook::register('post_remote_end', 'addon/mailstream/mailstream.php', 'mailstream_post_hook');
Hook::register('mailstream_send_hook', 'addon/mailstream/mailstream.php', 'mailstream_send_hook'); Hook::register('mailstream_send_hook', 'addon/mailstream/mailstream.php', 'mailstream_send_hook');
Logger::info("mailstream: installed"); Logger::info("installed mailstream");
}
/**
* Enforces that mailstream_install has set up the current version
*/
function mailstream_check_version()
{
if (!is_null(DI::config()->get('mailstream', 'dbversion'))) {
DI::config()->delete('mailstream', 'dbversion');
Logger::info("mailstream_check_version: old version detected, reinstalling");
mailstream_install();
Hook::loadHooks();
Hook::add(
'mailstream_convert_table_entries',
'addon/mailstream/mailstream.php',
'mailstream_convert_table_entries'
);
Hook::fork(Worker::PRIORITY_LOW, 'mailstream_convert_table_entries');
}
} }
/** /**
@ -72,10 +51,12 @@ function mailstream_addon_admin(string &$o)
{ {
$frommail = DI::config()->get('mailstream', 'frommail'); $frommail = DI::config()->get('mailstream', 'frommail');
$template = Renderer::getMarkupTemplate('admin.tpl', 'addon/mailstream/'); $template = Renderer::getMarkupTemplate('admin.tpl', 'addon/mailstream/');
$config = ['frommail', $config = [
'frommail',
DI::l10n()->t('From Address'), DI::l10n()->t('From Address'),
$frommail, $frommail,
DI::l10n()->t('Email address that stream items will appear to be from.')]; DI::l10n()->t('Email address that stream items will appear to be from.')
];
$o .= Renderer::replaceMacros($template, [ $o .= Renderer::replaceMacros($template, [
'$frommail' => $config, '$frommail' => $config,
'$submit' => DI::l10n()->t('Save Settings') '$submit' => DI::l10n()->t('Save Settings')
@ -105,7 +86,7 @@ function mailstream_generate_id(string $uri): string
$host = DI::baseUrl()->getHost(); $host = DI::baseUrl()->getHost();
$resource = hash('md5', $uri); $resource = hash('md5', $uri);
$message_id = "<" . $resource . "@" . $host . ">"; $message_id = "<" . $resource . "@" . $host . ">";
Logger::debug('mailstream: Generated message ID ' . $message_id . ' for URI ' . $uri); Logger::debug('generated message ID', ['id' => $message_id, 'uri' => $uri]);
return $message_id; return $message_id;
} }
@ -114,20 +95,20 @@ function mailstream_send_hook(array $data)
$criteria = array('uid' => $data['uid'], 'contact-id' => $data['contact-id'], 'uri' => $data['uri']); $criteria = array('uid' => $data['uid'], 'contact-id' => $data['contact-id'], 'uri' => $data['uri']);
$item = Post::selectFirst([], $criteria); $item = Post::selectFirst([], $criteria);
if (empty($item)) { if (empty($item)) {
Logger::error('mailstream_send_hook could not find item'); Logger::error('could not find item');
return; return;
} }
$user = User::getById($item['uid']); $user = User::getById($item['uid']);
if (empty($user)) { if (empty($user)) {
Logger::error('mailstream_send_hook could not fund user', ['uid' => $item['uid']]); Logger::error('could not find user', ['uid' => $item['uid']]);
return; return;
} }
if (!mailstream_send($data['message_id'], $item, $user)) { if (!mailstream_send($data['message_id'], $item, $user)) {
Logger::debug('mailstream_send_hook send failed, will retry', $data); Logger::debug('send failed, will retry', $data);
if (!Worker::defer()) { if (!Worker::defer()) {
Logger::error('mailstream_send_hook failed and could not defer', $data); Logger::error('failed and could not defer', $data);
} }
} }
} }
@ -142,35 +123,33 @@ function mailstream_send_hook(array $data)
*/ */
function mailstream_post_hook(array &$item) function mailstream_post_hook(array &$item)
{ {
mailstream_check_version(); if ($item['uid'] === 0) {
Logger::debug('mailstream: root user, skipping item ' . $item['id']);
return;
}
if (!DI::pConfig()->get($item['uid'], 'mailstream', 'enabled')) { if (!DI::pConfig()->get($item['uid'], 'mailstream', 'enabled')) {
Logger::debug('mailstream: not enabled.', ['item' => $item['id'], ' uid ' => $item['uid']]); Logger::debug('mailstream: not enabled.', ['item' => $item['id'], ' uid ' => $item['uid']]);
return; return;
} }
if (!$item['uid']) {
Logger::debug('mailstream: no uid for item ' . $item['id']);
return;
}
if (!$item['contact-id']) { if (!$item['contact-id']) {
Logger::debug('mailstream: no contact-id for item ' . $item['id']); Logger::debug('no contact-id', ['item' => $item['id']]);
return; return;
} }
if (!$item['uri']) { if (!$item['uri']) {
Logger::debug('mailstream: no uri for item ' . $item['id']); Logger::debug('no uri', ['item' => $item['id']]);
return; return;
} }
if ($item['verb'] == Activity::ANNOUNCE) { if ($item['verb'] == Activity::ANNOUNCE) {
Logger::debug('mailstream: announce item ', ['item' => $item['id']]); Logger::debug('ignoring announce', ['item' => $item['id']]);
return; return;
} }
if (DI::pConfig()->get($item['uid'], 'mailstream', 'nolikes')) { if (DI::pConfig()->get($item['uid'], 'mailstream', 'nolikes')) {
if ($item['verb'] == Activity::LIKE) { if ($item['verb'] == Activity::LIKE) {
Logger::debug('mailstream: like item ' . $item['id']); Logger::debug('ignoring like', ['item' => $item['id']]);
return; return;
} }
if ($item['verb'] == Activity::DISLIKE) { if ($item['verb'] == Activity::DISLIKE) {
Logger::debug('mailstream: dislike item ' . $item['id']); Logger::debug('ignoring dislike', ['item' => $item['id']]);
return; return;
} }
} }
@ -219,14 +198,17 @@ function mailstream_do_images(array &$item, array &$attachments)
$cookiejar = tempnam(System::getTempPath(), 'cookiejar-mailstream-'); $cookiejar = tempnam(System::getTempPath(), 'cookiejar-mailstream-');
try { try {
$curlResult = DI::httpClient()->fetchFull($url, HttpClientAccept::DEFAULT, 0, $cookiejar); $curlResult = DI::httpClient()->get($url, HttpClientAccept::DEFAULT, [HttpClientOptions::COOKIEJAR => $cookiejar]);
if (!$curlResult->isSuccess()) { if (!$curlResult->isSuccess()) {
Logger::debug('mailstream: fetch image url failed', [ Logger::debug('mailstream: fetch image url failed', [
'url' => $url, 'item_id' => $item['id'], 'return_code' => $curlResult->getReturnCode()]); 'url' => $url,
'item_id' => $item['id'],
'return_code' => $curlResult->getReturnCode()
]);
continue; continue;
} }
} catch (InvalidArgumentException $e) { } catch (InvalidArgumentException $e) {
Logger::error('mailstream_do_images exception fetching url', ['url' => $url, 'item_id' => $item['id']]); Logger::error('exception fetching url', ['url' => $url, 'item_id' => $item['id']]);
continue; continue;
} }
$attachments[$url] = [ $attachments[$url] = [
@ -327,13 +309,12 @@ function mailstream_subject(array $item): string
} }
$contact = Contact::selectFirst([], ['id' => $item['contact-id'], 'uid' => $item['uid']]); $contact = Contact::selectFirst([], ['id' => $item['contact-id'], 'uid' => $item['uid']]);
if (!DBA::isResult($contact)) { if (!DBA::isResult($contact)) {
Logger::error( Logger::error('no contact', [
'mailstream_subject no contact for item', 'item' => $item['id'],
['id' => $item['id'], 'plink' => $item['plink'],
'plink' => $item['plink'], 'contact id' => $item['contact-id'],
'contact id' => $item['contact-id'], 'uid' => $item['uid']
'uid' => $item['uid']] ]);
);
return DI::l10n()->t("Friendica post"); return DI::l10n()->t("Friendica post");
} }
if ($contact['network'] === 'dfrn') { if ($contact['network'] === 'dfrn') {
@ -370,21 +351,20 @@ function mailstream_subject(array $item): string
function mailstream_send(string $message_id, array $item, array $user): bool function mailstream_send(string $message_id, array $item, array $user): bool
{ {
if (!is_array($item)) { if (!is_array($item)) {
Logger::error('mailstream_send item is empty', ['message_id' => $message_id]); Logger::error('item is empty', ['message_id' => $message_id]);
return false; return false;
} }
if (!$item['visible']) { if (!$item['visible']) {
Logger::debug('mailstream_send item not yet visible', ['item uri' => $item['uri']]); Logger::debug('item not yet visible', ['item uri' => $item['uri']]);
return false; return false;
} }
if (!$message_id) { if (!$message_id) {
Logger::error('mailstream_send no message ID supplied', ['item uri' => $item['uri'], Logger::error('no message ID supplied', ['item uri' => $item['uri'], 'user email' => $user['email']]);
'user email' => $user['email']]);
return true; return true;
} }
require_once (dirname(__file__) . '/phpmailer/class.phpmailer.php'); require_once(dirname(__file__) . '/phpmailer/class.phpmailer.php');
$item['body'] = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']); $item['body'] = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']);
@ -429,21 +409,24 @@ function mailstream_send(string $message_id, array $item, array $user): bool
$item['body'] = BBCode::convertForUriId($item['uri-id'], $item['body'], BBCode::CONNECTORS); $item['body'] = BBCode::convertForUriId($item['uri-id'], $item['body'], BBCode::CONNECTORS);
$item['url'] = DI::baseUrl() . '/display/' . $item['guid']; $item['url'] = DI::baseUrl() . '/display/' . $item['guid'];
$mail->Body = Renderer::replaceMacros($template, [ $mail->Body = Renderer::replaceMacros($template, [
'$upstream' => DI::l10n()->t('Upstream'), '$upstream' => DI::l10n()->t('Upstream'),
'$uri' => DI::l10n()->t('URI'), '$uri' => DI::l10n()->t('URI'),
'$local' => DI::l10n()->t('Local'), '$local' => DI::l10n()->t('Local'),
'$item' => $item]); '$item' => $item
]);
$mail->Body = mailstream_html_wrap($mail->Body); $mail->Body = mailstream_html_wrap($mail->Body);
if (!$mail->Send()) { if (!$mail->Send()) {
throw new Exception($mail->ErrorInfo); throw new Exception($mail->ErrorInfo);
} }
Logger::debug('mailstream_send sent message', ['message ID' => $mail->MessageID, Logger::debug('sent message', [
'subject' => $mail->Subject, 'message ID' => $mail->MessageID,
'address' => $address]); 'subject' => $mail->Subject,
'address' => $address
]);
} catch (phpmailerException $e) { } catch (phpmailerException $e) {
Logger::debug('mailstream_send PHPMailer exception sending message ' . $message_id . ': ' . $e->errorMessage()); Logger::debug('PHPMailer exception sending message', ['id' => $message_id, 'error' => $e->errorMessage()]);
} catch (Exception $e) { } catch (Exception $e) {
Logger::debug('mailstream_send exception sending message ' . $message_id . ': ' . $e->getMessage()); Logger::debug('exception sending message', ['id' => $message_id, 'error' => $e->getMessage()]);
} }
return true; return true;
@ -467,29 +450,6 @@ function mailstream_html_wrap(string &$text)
return $text; return $text;
} }
/**
* Convert v1 mailstream table entries to v2 workerqueue items
*/
function mailstream_convert_table_entries()
{
$ms_item_ids = DBA::selectToArray('mailstream_item', [], ['message-id', 'uri', 'uid', 'contact-id'], ["`mailstream_item`.`completed` IS NULL"]);
Logger::debug('mailstream_convert_table_entries processing ' . count($ms_item_ids) . ' items');
foreach ($ms_item_ids as $ms_item_id) {
$send_hook_data = array('uid' => $ms_item_id['uid'],
'contact-id' => $ms_item_id['contact-id'],
'uri' => $ms_item_id['uri'],
'message_id' => $ms_item_id['message-id'],
'tries' => 0);
if (!$ms_item_id['message-id'] || !strlen($ms_item_id['message-id'])) {
Logger::info('mailstream_convert_table_entries: item has no message-id.', ['item' => $ms_item_id['id'], 'uri' => $ms_item_id['uri']]);
continue;
}
Logger::info('mailstream_convert_table_entries: convert item to workerqueue', $send_hook_data);
Hook::fork(Worker::PRIORITY_LOW, 'mailstream_send_hook', $send_hook_data);
}
DBA::e('DROP TABLE `mailstream_item`');
}
/** /**
* Form for configuring mailstream features for a user * Form for configuring mailstream features for a user
* *

View file

@ -1,5 +1,4 @@
<?php <?php
/* /*
* Name: Mastodon Custom Emojis * Name: Mastodon Custom Emojis
* Description: Replace emojis shortcodes in Mastodon posts with their originating server custom emojis images. * Description: Replace emojis shortcodes in Mastodon posts with their originating server custom emojis images.
@ -9,7 +8,6 @@
* Status: Unsupported * Status: Unsupported
*/ */
use Friendica\App;
use Friendica\Content\Smilies; use Friendica\Content\Smilies;
use Friendica\Core\Cache\Enum\Duration; use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Hook; use Friendica\Core\Hook;
@ -78,7 +76,7 @@ function mastodoncustomemojis_fetch_custom_emojis_for_url($api_base_url)
$api_url = $api_base_url . '/api/v1/custom_emojis'; $api_url = $api_base_url . '/api/v1/custom_emojis';
$fetchResult = DI::httpClient()->fetchFull($api_url); $fetchResult = DI::httpClient()->get($api_url);
if ($fetchResult->isSuccess()) { if ($fetchResult->isSuccess()) {
$emojis_array = json_decode($fetchResult->getBodyString(), true); $emojis_array = json_decode($fetchResult->getBodyString(), true);

213
ratioed/RatioedPanel.php Normal file
View file

@ -0,0 +1,213 @@
<?php
namespace Friendica\Addon\ratioed;
use Friendica\Content\Pager;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\User;
use Friendica\Module\Moderation\Users\Active;
/**
* This class implements the "Behaviour" panel in Moderation/Users
*/
class RatioedPanel extends Active
{
protected function content(array $request = []): string
{
Active::content();
if (isset(DI::args()->getArgv()[1]) and DI::args()->getArgv()[1] === 'help') {
$template = Renderer::getMarkupTemplate('/help.tpl', 'addon/ratioed/');
return Renderer::replaceMacros($template, array('$config' => DI::baseUrl() . '/settings/addon'));
}
$action = $this->parameters['action'] ?? '';
$uid = $this->parameters['uid'] ?? 0;
if ($uid) {
$user = User::getById($uid, ['username', 'blocked']);
if (!$user) {
$this->systemMessages->addNotice($this->t('User not found'));
$this->baseUrl->redirect('moderation/users');
}
}
switch ($action) {
case 'delete':
if ($this->session->getLocalUserId() != $uid) {
self::checkFormSecurityTokenRedirectOnError('moderation/users/active', 'moderation_users_active', 't');
// delete user
User::remove($uid);
$this->systemMessages->addNotice($this->t('User "%s" deleted', $user['username']));
} else {
$this->systemMessages->addNotice($this->t('You can\'t remove yourself'));
}
$this->baseUrl->redirect('moderation/users/active');
break;
case 'block':
self::checkFormSecurityTokenRedirectOnError('moderation/users/active', 'moderation_users_active', 't');
User::block($uid);
$this->systemMessages->addNotice($this->t('User "%s" blocked', $user['username']));
$this->baseUrl->redirect('moderation/users/active');
break;
}
$pager = new Pager($this->l10n, $this->args->getQueryString(), 100);
$valid_orders = [
'name',
'email',
'register_date',
'last-activity',
'last-item',
'page-flags',
];
$order = 'last-item';
$order_direction = '-';
if (!empty($request['o'])) {
$new_order = $request['o'];
if ($new_order[0] === '-') {
$order_direction = '-';
$new_order = substr($new_order, 1);
}
if (in_array($new_order, $valid_orders)) {
$order = $new_order;
}
}
$users = User::getList($pager->getStart(), $pager->getItemsPerPage(), 'active', $order, ($order_direction == '-'));
$users = array_map($this->setupUserCallback(), $users);
$header_titles = [
$this->t('Name'),
$this->t('Email'),
$this->t('Register date'),
$this->t('Last login'),
$this->t('Last public item'),
$this->t('Type'),
$this->t('Blocked by'),
$this->t('Comments last 24h'),
$this->t('Reactions last 24h'),
$this->t('Ratio last 24h'),
];
$field_names = [
'name',
'email',
'register_date',
'login_date',
'lastitem_date',
'page_flags',
'blocked_by',
'comments',
'reactions',
'ratio',
];
$th_users = array_map(null, $header_titles, $valid_orders, $field_names);
$count = $this->database->count('user', ["`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired` AND `uid` != ?", 0]);
$t = Renderer::getMarkupTemplate('ratioed.tpl', 'addon/ratioed');
return self::getTabsHTML('ratioed') . Renderer::replaceMacros($t, [
// strings //
'$title' => $this->t('Moderation'),
'$help_url' => $this->baseUrl . '/ratioed/help',
'$page' => $this->t('Behaviour'),
'$select_all' => $this->t('select all'),
'$delete' => $this->t('Delete'),
'$block' => $this->t('Block'),
'$blocked' => $this->t('User blocked'),
'$siteadmin' => $this->t('Site admin'),
'$accountexpired' => $this->t('Account expired'),
'$h_newuser' => $this->t('Create a new user'),
'$th_users' => $th_users,
'$order_users' => $order,
'$order_direction_users' => $order_direction,
'$confirm_delete_multi' => $this->t('Selected users will be deleted!\n\nEverything these users had posted on this site will be permanently deleted!\n\nAre you sure?'),
'$confirm_delete' => $this->t('The user {0} will be deleted!\n\nEverything this user has posted on this site will be permanently deleted!\n\nAre you sure?'),
'$form_security_token' => self::getFormSecurityToken('moderation_users_active'),
// values //
'$baseurl' => $this->baseUrl,
'$query_string' => $this->args->getQueryString(),
'$users' => $users,
'$count' => $count,
'$pager' => $pager->renderFull($count),
]);
}
protected function setupUserCallback(): \Closure
{
Logger::debug("ratioed: setupUserCallback");
$parentCallback = parent::setupUserCallback();
return function ($user) use ($parentCallback) {
$blocked_count = DBA::count('user-contact', ['uid' => $user['uid'], 'is-blocked' => 1]);
$user['blocked_by'] = $blocked_count;
$self_contact_result = DBA::p('SELECT admin_contact.id AS user_contact_uid FROM contact AS admin_contact JOIN contact AS user_contact ON admin_contact.`uri-id` = user_contact.`uri-id` AND admin_contact.self = 0 AND user_contact.self = 1 WHERE user_contact.uid = ?', $user['uid']);
if (DBA::isResult($self_contact_result)) {
$self_contact_result_row = DBA::fetch($self_contact_result);
$user['user_contact_uid'] = $self_contact_result_row['user_contact_uid'];
}
else {
$user['user_contact_uid'] = NULL;
}
if ($user['user_contact_uid']) {
$post_engagement_result = DBA::p('SELECT SUM(`comments`) AS `comment_count`, SUM(`activities`) AS `activities_count` FROM `post-engagement` WHERE `post-engagement`.created > DATE_SUB(now(), INTERVAL 1 DAY) AND `post-engagement`.`owner-id` = ?', $user['user_contact_uid']);
if (DBA::isResult($post_engagement_result)) {
$post_engagement_result_row = DBA::fetch($post_engagement_result);
$user['comments'] = $post_engagement_result_row['comment_count'];
$user['reactions'] = $post_engagement_result_row['activities_count'];
if ($user['reactions'] > 0) {
$user['ratio'] = number_format($user['comments'] / $user['reactions'], 1, '.', '');
$user['ratioed'] = (float)($user['ratio']) >= 2.0;
}
else {
if ($user['comments'] == 0) {
$user['ratio'] = '0';
$user['ratioed'] = false;
}
else {
$user['ratio'] = '∞';
$user['ratioed'] = false;
}
}
}
else {
$user['comments'] = 'error';
$user['reactions'] = 'error';
$user['ratio'] = 'error';
$user['ratioed'] = false;
}
}
else {
$user['comments'] = 'error';
$user['reactions'] = 'error';
$user['ratio'] = 'error';
$user['ratioed'] = false;
}
$user = $parentCallback($user);
Logger::debug("ratioed: setupUserCallback", [
'uid' => $user['uid'],
'blocked_by' => $user['blocked_by'],
'comments' => $user['comments'],
'reactions' => $user['reactions'],
'ratio' => $user['ratio'],
'ratioed' => $user['ratioed'],
]);
return $user;
};
}
}

58
ratioed/ratioed.php Normal file
View file

@ -0,0 +1,58 @@
<?php
/**
* Name: Ratioed
* Description: Additional moderation user table with statistics about user behaviour
* Version: 0.1
* Author: Matthew Exon <http://mat.exon.name>
*/
use Friendica\Addon\ratioed\RatioedPanel;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\DI;
/**
* Sets up the addon hooks and updates data in the database if needed
*/
function ratioed_install()
{
Hook::register('moderation_users_tabs', 'addon/ratioed/ratioed.php', 'ratioed_users_tabs');
Logger::info("ratioed: installed");
}
/**
* This is a statement rather than an actual function definition. The simple
* existence of this method is checked to figure out if the addon offers a
* module.
*/
function ratioed_module() {}
/**
* @brief Adds additional users tab to the moderation panel
*
* @param array $arr Parameters, including "tabs" which is the list to modify, and "selectedTab", which is the currently selected tab ID
*/
function ratioed_users_tabs(array &$arr) {
Logger::debug("ratioed: users tabs");
array_push($arr['tabs'], [
'label' => DI::l10n()->t('Behaviour'),
'url' => 'ratioed',
'sel' => $arr['selectedTab'] == 'ratioed' ? 'active' : '',
'title' => DI::l10n()->t('Statistics about users behaviour'),
'id' => 'admin-users-ratioed',
'accesskey' => 'r',
]);
}
/**
* @brief Displays the ratioed tab in the moderation panel
*/
function ratioed_content() {
Logger::debug("ratioed: content");
$ratioed = DI::getDice()->create(RatioedPanel::class, [$_SERVER]);
$httpException = DI::getDice()->create(Friendica\Module\Special\HTTPException::class);
$ratioed->run($httpException);
}

View file

@ -0,0 +1,92 @@
<div class="panel 'help-content-wrapper">
<div class="panel-body">
<h2>Ratioed Plugin Help</h2>
<p>
This plugin provides administrators with additional statistics about
the behaviour of users. These may be useful as early warning signs
that warrant more carefully watching the behaviour of a user. They
are <em>not</em> suitable as a trigger for instantly blocking,
muting, or reporting a user, since they lack context.
</p>
<p>
The name of the plugin comes
from <a href="https://knowyourmeme.com/editorials/guides/what-is-the-ratio-and-what-does-it-mean-to-get-ratioed-twitters-1-rule-explained">"The
Ratio"</a>, a well-known quick rule of thumb:
</p>
<blockquote>
If the Replies:RT ratio is greater than 2:1, you done messed up.
</blockquote>
<p>
To "get ratioed" is to receive a large number of comments in a short
space of time, with relatively few likes or boosts. If commenters
were enthusiastic about the posts, they would also have liked or
boosted them. Receiving many comments without such likes or boosts
indicates the comments were probably angry. This anger may or may
not be justified, but either way this is probably something
moderators should be aware of.
</p>
<p>
This plugin allows viewing of an actual ratio, calculated over the
last 24 hours. This is a useful timeframe for sudden dogpiling
events that administrators might not otherwise notice. The plugin
also calculates other statistics.
</p>
<h3>Explanation of Statistics</h3>
<h4>Blocked by</h4>
<p>
This summarises the number of users on remote servers that have
blocked this user.
</p>
<p>
Note that the ActivityPub spec expressly says that
implementations "SHOULD NOT" forward such block messages to
remote servers. Nevertheless some implementations do this
anyway, notably Mastodon. This statistic can only count block
messages from servers that do this, as well as blocks from local
users. As such, it is usually an undercount.
</p>
<p>
The reason the spec recommends against forwarding these messages
is that they can lead to retaliation. For this reason, this
plugin deliberately does not provide any way to investigate
exactly who blocked the user.
</p>
<h4>Comments last 24h</h4>
<p>
This gives the number of comments made on the top-level posts that
this user made within the last 24 hours.
</p>
<h4>Reactions last 24h</h4>
<p>
This collects the number of likes, boosts, or other "one-click"
interactions made on the user's top-level posts within the last 24
hours.
</p>
<h4>Ratio last 24h</h4>
<p>
This is the ratio between "Comments last 24h" and "Reactions last
24h". It is intended to approximate the traditional ratio as
understood on Twitter.
</p>
<h3>Performance</h3>
<p>
The statistics are computed from scratch each time the page loads.
It's possible that this might put a heavy load on the database. and
the page may take a long time to load.
</p>
<h3>Extending</h3>
<p>
Suggestions for additional statistics are welcome, especially from
moderators. This plugin should be considered a sandbox for
experimentation, so it is not necessary to prove that any statistic
is correlated with unwanted behaviour.
</p>
<p>
However, this plugin does deal with potentially sensitive
information. Even if moderators do in principle have access to all
information, it should not necessarily be highlighted. Statistics
should be kept anonymous and neutral. Also, they should be
presented only to moderators, not to the users themselves.
</p>
</div>
</div>

View file

@ -0,0 +1,164 @@
<script type="text/javascript" src="view/theme/frio/js/mod_admin.js?v={{constant('\Friendica\App::VERSION')}}"></script>
<link rel="stylesheet" href="view/theme/frio/css/mod_admin.css?v={{constant('\Friendica\App::VERSION')}}" type="text/css" media="screen"/>
<div id="admin-users" class="adminpage generic-page-wrapper">
<h1>{{$title}} - {{$page}} ({{$count}})</h1>
<p>
<a href="{{$base_url}}/moderation/users/create" class="btn btn-primary"><i class="fa fa-user-plus"></i> {{$h_newuser}}</a>
</p>
<form action="{{$baseurl}}/{{$query_string}}" method="post">
<input type="hidden" name="form_security_token" value="{{$form_security_token}}">
<table id="users" class="table table-hover">
<thead>
<tr>
<th>
<div class="checkbox">
<input type="checkbox" id="admin-settings-users-select" class="selecttoggle" data-select-class="users_ckbx"/>
<label for="admin-settings-users-select"></label>
</div>
</th>
<th></th>
{{foreach $th_users as $k=>$th}}
{{if $k < 2 || $order_users == $th.1 || ($k==5 && !in_array($order_users,[$th_users.2.1, $th_users.3.1, $th_users.4.1])) }}
<th class="th-{{$k}}">
<a href="{{$baseurl}}/moderation/users/active?o={{if $order_direction_users == "+"}}-{{/if}}{{$th.1}}" class="table-order">
{{if $order_users == $th.1}}
{{if $order_direction_users == "+"}}
&#8595;
{{else}}
&#8593;
{{/if}}
{{else}}
&#8597;
{{/if}}
{{$th.0}}
</a>
</th>
{{/if}}
{{/foreach}}
<th></th>
</tr>
</thead>
<tbody>
{{foreach $users as $u}}
<tr id="user-{{$u.uid}}" class="{{if $u.ratioed}}blocked{{/if}}">
<td>
{{if $u.is_deletable}}
<div class="checkbox">
<input type="checkbox" class="users_ckbx" id="id_user_{{$u.uid}}" name="user[]" value="{{$u.uid}}"/>
<label for="id_user_{{$u.uid}}"></label>
</div>
{{else}}
&nbsp;
{{/if}}
</td>
<td><img class="avatar-nano" src="{{$u.micro}}" title="{{$u.nickname}}"></td>
<td><a href="{{$u.url}}" title="{{$u.nickname}}"> {{$u.name}}</a></td>
<td>{{$u.email}}</td>
{{if $order_users == $th_users.2.1}}
<td>{{$u.register_date}}</td>
{{/if}}
{{if $order_users == $th_users.3.1}}
<td>{{$u.login_date}}</td>
{{/if}}
{{if $order_users == $th_users.4.1}}
<td>{{$u.lastitem_date}}</td>
{{/if}}
{{if !in_array($order_users,[$th_users.2.1, $th_users.3.1, $th_users.4.1]) }}
<td>
<i class="fa
{{if $u.page_flags_raw==0}}fa-user{{/if}} {{* PAGE_NORMAL *}}
{{if $u.page_flags_raw==1}}fa-bullhorn{{/if}} {{* PAGE_SOAPBOX *}}
{{if $u.page_flags_raw==2}}fa-users{{/if}} {{* PAGE_COMMUNITY *}}
{{if $u.page_flags_raw==3}}fa-heart{{/if}} {{* PAGE_FREELOVE *}}
{{if $u.page_flags_raw==4}}fa-rss{{/if}} {{* PAGE_BLOG *}}
{{if $u.page_flags_raw==5}}fa-user-secret{{/if}} {{* PAGE_PRVGROUP *}}
" title="{{$u.page_flags}}">
</i>
{{if $u.page_flags_raw==0 && $u.account_type_raw > 0}}
<i class="fa
{{if $u.account_type_raw==1}}fa-sitemap{{/if}} {{* ACCOUNT_TYPE_ORGANISATION *}}
{{if $u.account_type_raw==2}}fa-newspaper-o{{/if}} {{* ACCOUNT_TYPE_NEWS *}}
{{if $u.account_type_raw==3}}fa-comments{{/if}} {{* ACCOUNT_TYPE_COMMUNITY *}}
" title="{{$u.account_type}}">
</i>
{{/if}}
{{if $u.is_admin}}<i class="fa fa-user-secret text-primary" title="{{$siteadmin}}"></i>{{/if}}
{{if $u.account_expired}}<i class="fa fa-clock-o text-warning" title="{{$accountexpired}}"></i>{{/if}}
</td>
{{/if}}
<td class="text-right">
<button type="button" class="btn-link admin-settings-action-link" onclick="return details({{$u.uid}})"><span class="caret"></span></button>
</td>
</tr>
<tr id="user-{{$u.uid}}-detail" class=" details hidden {{if $u.blocked != 0}}blocked{{/if}}">
<td>&nbsp;</td>
<td colspan="4">
{{if $order_users != $th_users.2.1}}
<p>
<a href="{{$baseurl}}/ratioed?o={{if $order_direction_users == "+"}}-{{/if}}{{$th_users.2.1}}" class="btn-link table-order">
&#8597; {{$th_users.2.0}}</a> : {{$u.register_date}}
</p>
{{/if}}
{{if $order_users != $th_users.3.1}}
<p>
<a href="{{$baseurl}}/ratioed?o={{if $order_direction_users == "+"}}-{{/if}}{{$th_users.3.1}}" class="btn-link table-order">
&#8597; {{$th_users.3.0}}</a> : {{$u.login_date}}
</p>
{{/if}}
{{if $order_users != $th_users.4.1}}
<p>
<a href="{{$baseurl}}/ratioed?o={{if $order_direction_users == "+"}}-{{/if}}{{$th_users.4.1}}" class="btn-link table-order">
&#8597; {{$th_users.4.0}}</a> : {{$u.lastitem_date}}
</p>
{{/if}}
{{if in_array($order_users,[$th_users.2.1, $th_users.3.1, $th_users.4.1]) }}
<p>
<a href="{{$baseurl}}/ratioed?o={{if $order_direction_users == "+"}}-{{/if}}{{$th_users.5.1}}" class="btn-link table-order">
&#8597; {{$th_users.5.0}}</a> : {{$u.page_flags}}{{if $u.page_flags_raw==0 && $u.account_type_raw > 0}}, {{$u.account_type}}{{/if}} {{if $u.is_admin}}({{$siteadmin}}){{/if}} {{if $u.account_expired}}({{$accountexpired}}){{/if}}
</p>
{{/if}}
{{foreach $th_users as $k=>$th}}
{{if $order_users != $th.1 && $k > 5}}
<p>
{{$th.0}} : {{$u[$th.2]}}
</p>
{{/if}}
{{/foreach}}
</td>
<td class="text-right">
{{if $u.is_deletable}}
<a href="{{$baseurl}}/moderation/users/active/block/{{$u.uid}}?t={{$form_security_token}}" class="admin-settings-action-link" title="{{$block}}">
<i class="fa fa-ban" aria-hidden="true"></i>
</a>
<a href="{{$baseurl}}/moderation/users/active/delete/{{$u.uid}}?t={{$form_security_token}}" class="admin-settings-action-link" title="{{$delete}}" onclick="return confirm_delete('{{$confirm_delete}}','{{$u.name}}')">
<i class="fa fa-trash" aria-hidden="true"></i>
</a>
{{else}}
&nbsp;
{{/if}}
</td>
</tr>
{{/foreach}}
</tbody>
</table>
<div class="panel-footer">
<button type="submit" name="page_users_block" value="1" class="btn btn-warning">
<i class="fa fa-ban" aria-hidden="true"></i> {{$block}}
</button>
<button type="submit" name="page_users_delete" value="1" class="btn btn-danger" onclick="return confirm_delete('{{$confirm_delete_multi}}')">
<i class="fa fa-trash" aria-hidden="true"></i> {{$delete}}
</button>
</div>
{{$pager nofilter}}
</form>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 B

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 908 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -2,30 +2,28 @@
/* /*
* Name: Smiley Pack * Name: Smiley Pack
* Description: Pack of smileys that make master too AOLish. * Description: Pack of smileys that make master too AOLish.
* Version: 1.05 * Version: 1.06
* Author: Thomas Willingham (based on Mike Macgirvin's Adult Smile template) * Author: Thomas Willingham (based on Mike Macgirvin's Adult Smile template)
* Author: Matthias Ebers <https://loma.ml/profile/one> * Author: Matthias Ebers <https://loma.ml/profile/one>
* All smileys from sites offering them as Public Domain * All smileys from sites offering them as Public Domain
*/ */
use Friendica\App;
use Friendica\Core\Hook; use Friendica\Core\Hook;
use Friendica\DI; use Friendica\DI;
function smiley_pack_install() { function smiley_pack_install()
{
Hook::register('smilie', 'addon/smiley_pack/smiley_pack.php', 'smiley_pack_smilies'); Hook::register('smilie', 'addon/smiley_pack/smiley_pack.php', 'smiley_pack_smilies');
} }
function smiley_pack_smilies(array &$b) function smiley_pack_smilies(array &$b)
{ {
#Smileys are split into various directories by the intended range of emotions. This is in case we get too big and need to modularise things. We can then cut and paste the right lines, move the right directory, and just change the name of the addon to happy_smilies or whatever. #Smileys are split into various directories by the intended range of emotions. This is in case we get too big and need to modularise things. We can then cut and paste the right lines, move the right directory, and just change the name of the addon to happy_smilies or whatever.
#Be careful with invocation strings. If you have a smiley called foo, and another called foobar, typing :foobar will call foo. Avoid this with clever naming, using ~ instead of : #Be careful with invocation strings. If you have a smiley called foo, and another called foobar, typing :foobar will call foo. Avoid this with clever naming, using ~ instead of :
#when all else fails. #when all else fails.
#Animal smileys.
#Animal smileys.
$b['texts'][] = ':bunnyflowers:'; $b['texts'][] = ':bunnyflowers:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/animals/bunnyflowers.gif' . '" alt="' . ':bunnyflowers:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/animals/bunnyflowers.gif' . '" alt="' . ':bunnyflowers:' . '" />';
@ -50,7 +48,7 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':cow:'; $b['texts'][] = ':cow:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/animals/cow.gif' . '" alt="' . ':cow:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/animals/cow.gif' . '" alt="' . ':cow:' . '" />';
$b['texts'][] = ':crab:'; $b['texts'][] = ':crab:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/animals/crab.gif' . '" alt="' . ':crab:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/animals/crab.gif' . '" alt="' . ':crab:' . '" />';
@ -71,7 +69,7 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':horse:'; $b['texts'][] = ':horse:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/animals/horse.gif' . '" alt="' . ':horse:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/animals/horse.gif' . '" alt="' . ':horse:' . '" />';
$b['texts'][] = ':parrot:'; $b['texts'][] = ':parrot:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/animals/parrot.gif' . '" alt="' . ':parrot:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/animals/parrot.gif' . '" alt="' . ':parrot:' . '" />';
@ -99,16 +97,13 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':pig:'; $b['texts'][] = ':pig:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/animals/pig.gif' . '" alt="' . ':pig:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/animals/pig.gif' . '" alt="' . ':pig:' . '" />';
#Baby Smileys
#Baby Smileys
$b['texts'][] = ':baby:'; $b['texts'][] = ':baby:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/babies/baby.gif' . '" alt="' . ':baby:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/babies/baby.gif' . '" alt="' . ':baby:' . '" />';
$b['texts'][] = ':babycot:'; $b['texts'][] = ':babycot:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/babies/babycot.gif' . '" alt="' . ':babycot:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/babies/babycot.gif' . '" alt="' . ':babycot:' . '" />';
$b['texts'][] = ':pregnant:'; $b['texts'][] = ':pregnant:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/babies/pregnant.gif' . '" alt="' . ':pregnant:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/babies/pregnant.gif' . '" alt="' . ':pregnant:' . '" />';
@ -116,11 +111,10 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':stork:'; $b['texts'][] = ':stork:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/babies/stork.gif' . '" alt="' . ':stork:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/babies/stork.gif' . '" alt="' . ':stork:' . '" />';
#Confused Smileys
#Confused Smileys
$b['texts'][] = ':confused:'; $b['texts'][] = ':confused:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/confused/confused.gif' . '" alt="' . ':confused:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/confused/confused.gif' . '" alt="' . ':confused:' . '" />';
$b['texts'][] = ':shrug:'; $b['texts'][] = ':shrug:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/confused/shrug.gif' . '" alt="' . ':shrug:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/confused/shrug.gif' . '" alt="' . ':shrug:' . '" />';
@ -130,13 +124,12 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':dazed:'; $b['texts'][] = ':dazed:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/confused/dazed.gif' . '" alt="' . ':dazed:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/confused/dazed.gif' . '" alt="' . ':dazed:' . '" />';
#Cool Smileys
#Cool Smileys
$b['texts'][] = ':affro:'; $b['texts'][] = ':affro:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/cool/affro.gif' . '" alt="' . ':affro:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/cool/affro.gif' . '" alt="' . ':affro:' . '" />';
#Devil/Angel Smileys #Devil/Angel Smileys
$b['texts'][] = ':angel:'; $b['texts'][] = ':angel:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/devilangel/angel.gif' . '" alt="' . ':angel:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/devilangel/angel.gif' . '" alt="' . ':angel:' . '" />';
@ -152,20 +145,20 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':devillish:'; $b['texts'][] = ':devillish:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/devilangel/devil.gif' . '" alt="' . ':devillish:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/devilangel/devil.gif' . '" alt="' . ':devillish:' . '" />';
$b['texts'][] = ':daseesaw:'; $b['texts'][] = ':daseesaw:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/devilangel/daseesaw.gif' . '" alt="' . ':daseesaw:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/devilangel/daseesaw.gif' . '" alt="' . ':daseesaw:' . '" />';
$b['texts'][] = ':turnevil:'; $b['texts'][] = ':turnevil:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/devilangel/turnevil.gif' . '" alt="' . ':turnevil:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/devilangel/turnevil.gif' . '" alt="' . ':turnevil:' . '" />';
$b['texts'][] = ':saint:'; $b['texts'][] = ':saint:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/devilangel/saint.gif' . '" alt="' . ':saint:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/devilangel/saint.gif' . '" alt="' . ':saint:' . '" />';
$b['texts'][] = ':graveside:'; $b['texts'][] = ':graveside:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/devilangel/graveside.gif' . '" alt="' . ':graveside:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/devilangel/graveside.gif' . '" alt="' . ':graveside:' . '" />';
#Unpleasent smileys. #Unpleasent smileys.
$b['texts'][] = ':toilet:'; $b['texts'][] = ':toilet:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/disgust/toilet.gif' . '" alt="' . ':toilet:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/disgust/toilet.gif' . '" alt="' . ':toilet:' . '" />';
@ -176,7 +169,7 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':fartblush:'; $b['texts'][] = ':fartblush:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/disgust/fartblush.gif' . '" alt="' . ':fartblush:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/disgust/fartblush.gif' . '" alt="' . ':fartblush:' . '" />';
#Drinks #Drinks
$b['texts'][] = ':tea:'; $b['texts'][] = ':tea:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/drink/tea.gif' . '" alt="' . ':tea:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/drink/tea.gif' . '" alt="' . ':tea:' . '" />';
@ -184,7 +177,7 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':drool:'; $b['texts'][] = ':drool:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/drool/drool.gif' . '" alt="' . ':drool:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/drool/drool.gif' . '" alt="' . ':drool:' . '" />';
#Sad smileys #Sad smileys
$b['texts'][] = ':crying:'; $b['texts'][] = ':crying:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sad/crying.png' . '" alt="' . ':crying:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sad/crying.png' . '" alt="' . ':crying:' . '" />';
@ -195,12 +188,12 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':sigh:'; $b['texts'][] = ':sigh:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sad/sigh.gif' . '" alt="' . ':sigh:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sad/sigh.gif' . '" alt="' . ':sigh:' . '" />';
#Smoking - only one smiley in here, maybe it needs moving elsewhere? #Smoking - only one smiley in here, maybe it needs moving elsewhere?
$b['texts'][] = ':smoking:'; $b['texts'][] = ':smoking:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/smoking/smoking.gif' . '" alt="' . ':smoking:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/smoking/smoking.gif' . '" alt="' . ':smoking:' . '" />';
#Sport smileys #Sport smileys
$b['texts'][] = ':basketball:'; $b['texts'][] = ':basketball:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sport/basketball.gif' . '" alt="' . ':basketball:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sport/basketball.gif' . '" alt="' . ':basketball:' . '" />';
@ -231,11 +224,11 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':snooker:'; $b['texts'][] = ':snooker:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sport/snooker.gif' . '" alt="' . ':snooker:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sport/snooker.gif' . '" alt="' . ':snooker:' . '" />';
$b['texts'][] = ':horseriding:'; $b['texts'][] = ':horseriding:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sport/horseriding.gif' . '" alt="' . ':horseriding:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sport/horseriding.gif' . '" alt="' . ':horseriding:' . '" />';
#Love smileys #Love smileys
$b['texts'][] = ':iloveyou:'; $b['texts'][] = ':iloveyou:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/love/iloveyou.gif' . '" alt="' . ':iloveyou:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/love/iloveyou.gif' . '" alt="' . ':iloveyou:' . '" />';
@ -255,7 +248,7 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':loveheart:'; $b['texts'][] = ':loveheart:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/love/loveheart.gif' . '" alt="' . ':loveheart:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/love/loveheart.gif' . '" alt="' . ':loveheart:' . '" />';
#Tired/Sleep smileys #Tired/Sleep smileys
$b['texts'][] = ':countsheep'; $b['texts'][] = ':countsheep';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/tired/countsheep.gif' . '" alt="' . ':countsheep:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/tired/countsheep.gif' . '" alt="' . ':countsheep:' . '" />';
@ -269,7 +262,7 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':yawn:'; $b['texts'][] = ':yawn:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/tired/yawn.gif' . '" alt="' . ':yawn:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/tired/yawn.gif' . '" alt="' . ':yawn:' . '" />';
#Fight/Flame/Violent smileys #Fight/Flame/Violent smileys
$b['texts'][] = ':2guns:'; $b['texts'][] = ':2guns:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fight/2guns.gif' . '" alt="' . ':2guns:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fight/2guns.gif' . '" alt="' . ':2guns:' . '" />';
@ -313,7 +306,7 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':acid:'; $b['texts'][] = ':acid:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fight/acid.gif' . '" alt="' . ':acid:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fight/acid.gif' . '" alt="' . ':acid:' . '" />';
#Fantasy smileys - monsters and dragons fantasy. The other type of fantasy belongs in adult smileys #Fantasy smileys - monsters and dragons fantasy. The other type of fantasy belongs in adult smileys
$b['texts'][] = ':alienmonster:'; $b['texts'][] = ':alienmonster:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fantasy/alienmonster.gif' . '" alt="' . ':alienmonster:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fantasy/alienmonster.gif' . '" alt="' . ':alienmonster:' . '" />';
@ -336,7 +329,7 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':mummy:'; $b['texts'][] = ':mummy:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fantasy/mummy.gif' . '" alt="' . ':mummy:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fantasy/mummy.gif' . '" alt="' . ':mummy:' . '" />';
#Food smileys #Food smileys
$b['texts'][] = ':apple:'; $b['texts'][] = ':apple:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/food/apple.gif' . '" alt="' . ':apple:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/food/apple.gif' . '" alt="' . ':apple:' . '" />';
@ -368,7 +361,7 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':birthdaycake:'; $b['texts'][] = ':birthdaycake:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/food/birthdaycake.gif' . '" alt="' . ':birthdaycake:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/food/birthdaycake.gif' . '" alt="' . ':birthdaycake:' . '" />';
#Happy smileys #Happy smileys
$b['texts'][] = ':cloud9:'; $b['texts'][] = ':cloud9:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/happy/cloud9.gif' . '" alt="' . ':cloud9:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/happy/cloud9.gif' . '" alt="' . ':cloud9:' . '" />';
@ -376,7 +369,7 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':tearsofjoy:'; $b['texts'][] = ':tearsofjoy:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/happy/tearsofjoy.gif' . '" alt="' . ':tearsofjoy:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/happy/tearsofjoy.gif' . '" alt="' . ':tearsofjoy:' . '" />';
#Repsect smileys #Repsect smileys
$b['texts'][] = ':bow:'; $b['texts'][] = ':bow:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/respect/bow.gif' . '" alt="' . ':bow:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/respect/bow.gif' . '" alt="' . ':bow:' . '" />';
@ -390,7 +383,19 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':number1:'; $b['texts'][] = ':number1:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/respect/number1.gif' . '" alt="' . ':number1:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/respect/number1.gif' . '" alt="' . ':number1:' . '" />';
#Laugh smileys $b['texts'][] = ':cc_cc:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/respect/cc.png' . '" alt="' . ':cc_cc:' . '" />';
$b['texts'][] = ':cc_by:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/respect/ccby.png' . '" alt="' . ':cc_by:' . '" />';
$b['texts'][] = ':cc_sa:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/respect/ccsa.png' . '" alt="' . ':cc_sa:' . '" />';
$b['texts'][] = ':cc_0:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/respect/cc0.png' . '" alt="' . ':cc_0:' . '" />';
#Laugh smileys
$b['texts'][] = ':hahaha:'; $b['texts'][] = ':hahaha:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/laugh/hahaha.gif' . '" alt="' . ':hahaha:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/laugh/hahaha.gif' . '" alt="' . ':hahaha:' . '" />';
@ -401,24 +406,23 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':rofl:'; $b['texts'][] = ':rofl:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/laugh/rofl.gif' . '" alt="' . ':rofl:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/laugh/rofl.gif' . '" alt="' . ':rofl:' . '" />';
#Music smileys #Music smileys
$b['texts'][] = ':drums:'; $b['texts'][] = ':drums:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/music/drums.gif' . '" alt="' . ':drums:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/music/drums.gif' . '" alt="' . ':drums:' . '" />';
$b['texts'][] = ':guitar:'; $b['texts'][] = ':guitar:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/music/guitar.gif' . '" alt="' . ':guitar:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/music/guitar.gif' . '" alt="' . ':guitar:' . '" />';
$b['texts'][] = ':trumpet:'; $b['texts'][] = ':trumpet:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/music/trumpet.gif' . '" alt="' . ':trumpet:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/music/trumpet.gif' . '" alt="' . ':trumpet:' . '" />';
#Smileys that used to be in core #Smileys that used to be in core
$b['texts'][] = ':headbang:'; $b['texts'][] = ':headbang:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/oldcore/headbang.gif' . '" alt="' . ':headbang:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/oldcore/headbang.gif' . '" alt="' . ':headbang:' . '" />';
$b['texts'][] = ':beard:'; $b['texts'][] = ':beard:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/oldcore/beard.png' . '" alt="' . ':beard:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/oldcore/beard.png' . '" alt="' . ':beard:' . '" />';
$b['texts'][] = ':whitebeard:'; $b['texts'][] = ':whitebeard:';
@ -436,7 +440,7 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':headdesk:'; $b['texts'][] = ':headdesk:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/oldcore/headbang.gif' . '" alt="' . ':headdesk:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/oldcore/headbang.gif' . '" alt="' . ':headdesk:' . '" />';
#These two are still in core, so oldcore isn't strictly right, but we don't want too many directories #These two are still in core, so oldcore isn't strictly right, but we don't want too many directories
$b['texts'][] = ':-d'; $b['texts'][] = ':-d';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/oldcore/laughing.gif' . '" alt="' . ':-d' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/oldcore/laughing.gif' . '" alt="' . ':-d' . '" />';
@ -444,8 +448,8 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':-o'; $b['texts'][] = ':-o';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/oldcore/surprised.gif' . '" alt="' . ':-o' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/oldcore/surprised.gif' . '" alt="' . ':-o' . '" />';
# Regex killers - stick these at the bottom so they appear at the end of the English and # Regex killers - stick these at the bottom so they appear at the end of the English and
# at the start of $OtherLanguage. # at the start of $OtherLanguage.
$b['texts'][] = ':cool:'; $b['texts'][] = ':cool:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/cool/cool.gif' . '" alt="' . ':cool:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/cool/cool.gif' . '" alt="' . ':cool:' . '" />';
@ -455,7 +459,7 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':golf:'; $b['texts'][] = ':golf:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sport/golf.gif' . '" alt="' . ':golf:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sport/golf.gif' . '" alt="' . ':golf:' . '" />';
$b['texts'][] = ':football:'; $b['texts'][] = ':football:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sport/football.gif' . '" alt="' . ':football:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/sport/football.gif' . '" alt="' . ':football:' . '" />';
@ -480,63 +484,167 @@ function smiley_pack_smilies(array &$b)
$b['texts'][] = ':gangs:'; $b['texts'][] = ':gangs:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fight/gangs.gif' . '" alt="' . ':gangs:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fight/gangs.gif' . '" alt="' . ':gangs:' . '" />';
$b['texts'][] = ':dj:'; $b['texts'][] = ':dj:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/music/dj.gif' . '" alt="' . ':dj:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/music/dj.gif' . '" alt="' . ':dj:' . '" />';
$b['texts'][] = ':elvis:'; $b['texts'][] = ':elvis:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/music/elvis.gif' . '" alt="' . ':elivs:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/music/elvis.gif' . '" alt="' . ':elivs:' . '" />';
$b['texts'][] = ':violin:'; $b['texts'][] = ':violin:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/music/violin.gif' . '" alt="' . ':violin:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/music/violin.gif' . '" alt="' . ':violin:' . '" />';
# New Gif Emoji (@one@loma.ml) # New Gif Emoji (@one@loma.ml)
# Fediverse # Fediverse
$b['texts'][] = ':friendica:'; $b['texts'][] = ':friendica:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/friendica.gif' . '" alt="' . ':friendica:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/friendica.png' . '" alt="' . ':friendica:' . '" />';
$b['texts'][] = ':fediverse:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/fediverse.gif' . '" alt="' . ':fediverse:' . '" />';
$b['texts'][] = ':mastodon:'; $b['texts'][] = ':mastodon:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/mastodon.gif' . '" alt="' . ':mastodon:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/mastodon.gif' . '" alt="' . ':mastodon:' . '" />';
$b['texts'][] = ':pleroma:'; $b['texts'][] = ':pleroma:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/pleroma.gif' . '" alt="' . ':pleroma:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/pleroma.gif' . '" alt="' . ':pleroma:' . '" />';
$b['texts'][] = ':misskey:'; $b['texts'][] = ':misskey:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/misskey.gif' . '" alt="' . ':misskey:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/misskey.gif' . '" alt="' . ':misskey:' . '" />';
$b['texts'][] = ':diaspora:'; $b['texts'][] = ':diaspora:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/diaspora.gif' . '" alt="' . ':diaspora:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/diaspora.png' . '" alt="' . ':diaspora:' . '" />';
$b['texts'][] = ':hubzilla:'; $b['texts'][] = ':hubzilla:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/hubzilla.gif' . '" alt="' . ':hubzilla:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/hubzilla.png' . '" alt="' . ':hubzilla:' . '" />';
$b['texts'][] = ':pixelfed:'; $b['texts'][] = ':pixelfed:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/pixelfed.gif' . '" alt="' . ':pixelfeed:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/pixelfed.gif' . '" alt="' . ':pixelfeed:' . '" />';
$b['texts'][] = ':nextcloud:'; $b['texts'][] = ':nextcloud:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/nextcloud.gif' . '" alt="' . ':nextcloud:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/nextcloud.gif' . '" alt="' . ':nextcloud:' . '" />';
$b['texts'][] = ':activitypub:'; $b['texts'][] = ':activitypub:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/ap.gif' . '" alt="' . ':activitypub:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/fediverse/ap.gif' . '" alt="' . ':activitypub:' . '" />';
# ccc # ccc
$b['texts'][] = ':ccc event:'; $b['texts'][] = ':ccc event:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/ccc/ccc.gif' . '" alt="' . ':ccc event:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/ccc/ccc.gif' . '" alt="' . ':ccc event:' . '" />';
# Commercial # Commercial
$b['texts'][] = ':youtube:'; $b['texts'][] = ':youtube:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/youtube.gif' . '" alt="' . ':youtube:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/youtube.gif' . '" alt="' . ':youtube:' . '" />';
$b['texts'][] = ':spotify:'; $b['texts'][] = ':spotify:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/spotify.gif' . '" alt="' . ':spotify:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/spotify.gif' . '" alt="' . ':spotify:' . '" />';
$b['texts'][] = ':twitter:'; $b['texts'][] = ':twitter:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/twitter.gif' . '" alt="' . ':twitter:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/twitter.gif' . '" alt="' . ':twitter:' . '" />';
$b['texts'][] = ':twitch:'; $b['texts'][] = ':twitch:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/twitch.gif' . '" alt="' . ':twitch:' . '" />'; $b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/twitch.gif' . '" alt="' . ':twitch:' . '" />';
$b['texts'][] = ':facebook:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/facebook.gif' . '" alt="' . ':facebook:' . '" />';
$b['texts'][] = ':threads:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/threads.png' . '" alt="' . ':threads:' . '" />';
$b['texts'][] = ':google:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/google.gif' . '" alt="' . ':google:' . '" />';
$b['texts'][] = ':signal:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/signal.gif' . '" alt="' . ':signal:' . '" />';
$b['texts'][] = ':tiktok:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/tiktok.gif' . '" alt="' . ':tiktok:' . '" />';
$b['texts'][] = ':whatsapp:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/whatsapp.gif' . '" alt="' . ':whatsapp:' . '" />';
$b['texts'][] = ':instagram:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/instagram.gif' . '" alt="' . ':instagram:' . '" />';
$b['texts'][] = ':telegram:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/telegram.gif' . '" alt="' . ':telegram:' . '" />';
$b['texts'][] = ':windows:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/windows.png' . '" alt="' . ':windows:' . '" />';
$b['texts'][] = ':github:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/github.png' . '" alt="' . ':github:' . '" />';
$b['texts'][] = ':threema:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/commercial/threema.png' . '" alt="' . ':threema:' . '" />';
# nonCommercial
$b['texts'][] = ':invidious:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/noncommercial/invidious.gif' . '" alt="' . ':invidious:' . '" />';
$b['texts'][] = ':bluesky:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/noncommercial/bluesky.png' . '" alt="' . ':bluesky:' . '" />';
$b['texts'][] = ':vivaldi:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/noncommercial/vivaldi.png' . '" alt="' . ':vivaldi:' . '" />';
# opensource
$b['texts'][] = ':firefox:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/firefox.png' . '" alt="' . ':firefox:' . '" />';
$b['texts'][] = ':linuxopensuse:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/opensuse.png' . '" alt="' . ':linuxopensuse:' . '" />';
$b['texts'][] = ':linuxdebian:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/debian.png' . '" alt="' . ':linuxdebian:' . '" />';
$b['texts'][] = ':linuxfedora:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/fedora.png' . '" alt="' . ':linuxfedora:' . '" />';
$b['texts'][] = ':linuxubuntu:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/ubuntu.png' . '" alt="' . ':linuxubuntu:' . '" />';
$b['texts'][] = ':linuxmint:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/mint.png' . '" alt="' . ':linuxmint:' . '" />';
$b['texts'][] = ':fdroid:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/fdroid.png' . '" alt="' . ':fdroid:' . '" />';
$b['texts'][] = ':tutanota:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/tutanota.png' . '" alt="' . ':tutanota:' . '" />';
$b['texts'][] = ':raspi:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/raspi.png' . '" alt="' . ':raspi:' . '" />';
$b['texts'][] = ':linux:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/linux.png' . '" alt="' . ':linux:' . '" />';
$b['texts'][] = ':kde:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/kde.png' . '" alt="' . ':kde:' . '" />';
$b['texts'][] = ':firefoxnightly:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/firefoxnightly.png' . '" alt="' . ':firefoxnightly:' . '" />';
$b['texts'][] = ':archlinux:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/archlinux.png' . '" alt="' . ':archlinux:' . '" />';
$b['texts'][] = ':thunderbird:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/thunderbird.png' . '" alt="' . ':thunderbird:' . '" />';
$b['texts'][] = ':vivaldi:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/vivaldi.png' . '" alt="' . ':vivaldi:' . '" />';
$b['texts'][] = ':jabber:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/jabber.png' . '" alt="' . ':jabber:' . '" />';
$b['texts'][] = ':matrix:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/matrix.png' . '" alt="' . ':matrix:' . '" />';
$b['texts'][] = ':xmpp:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/xmpp.png' . '" alt="' . ':xmpp:' . '" />';
$b['texts'][] = ':foss:';
$b['icons'][] = '<img class="smiley" src="' . DI::baseUrl() . '/addon/smiley_pack/icons/opensource/foss.png' . '" alt="' . ':foss:' . '" />';
} }

View file

@ -133,6 +133,7 @@ function tumblr_item_by_link(array &$hookData)
Logger::debug('Got post', ['blog' => $matches[1], 'id' => $matches[2], 'result' => $result->response->posts]); Logger::debug('Got post', ['blog' => $matches[1], 'id' => $matches[2], 'result' => $result->response->posts]);
if (!empty($result->response->posts)) { if (!empty($result->response->posts)) {
$hookData['item_id'] = tumblr_process_post($result->response->posts[0], $hookData['uid'], Item::PR_FETCHED); $hookData['item_id'] = tumblr_process_post($result->response->posts[0], $hookData['uid'], Item::PR_FETCHED);
Item::incrementInbound(Protocol::TUMBLR);
} }
} }
@ -203,9 +204,9 @@ function tumblr_block(array &$hook_data)
$hook_data['result'] = ($result->meta->status <= 399); $hook_data['result'] = ($result->meta->status <= 399);
if ($hook_data['result']) { if ($hook_data['result']) {
$cdata = Contact::getPublicAndUserContactID($hook_data['contact']['id'], $hook_data['uid']); $ucid = Contact::getUserContactId($hook_data['contact']['id'], $hook_data['uid']);
if (!empty($cdata['user'])) { if ($ucid) {
Contact::remove($cdata['user']); Contact::remove($ucid);
} }
} }
} }
@ -238,9 +239,7 @@ function tumblr_get_contact_uuid(array $contact): string
* existence of this method is checked to figure out if the addon offers a * existence of this method is checked to figure out if the addon offers a
* module. * module.
*/ */
function tumblr_module() function tumblr_module() {}
{
}
function tumblr_content() function tumblr_content()
{ {
@ -756,6 +755,7 @@ function tumblr_fetch_tags(int $uid, int $last_poll)
$post = Post::selectFirst(['uri-id'], ['id' => $id]); $post = Post::selectFirst(['uri-id'], ['id' => $id]);
$stored = Post\Category::storeFileByURIId($post['uri-id'], $uid, Post\Category::SUBCRIPTION, $tag); $stored = Post\Category::storeFileByURIId($post['uri-id'], $uid, Post\Category::SUBCRIPTION, $tag);
Logger::debug('Stored tag subscription for user', ['uri-id' => $post['uri-id'], 'uid' => $uid, 'tag' => $tag, 'stored' => $stored]); Logger::debug('Stored tag subscription for user', ['uri-id' => $post['uri-id'], 'uid' => $uid, 'tag' => $tag, 'stored' => $stored]);
Item::incrementInbound(Protocol::TUMBLR);
} }
} }
} }
@ -795,6 +795,7 @@ function tumblr_fetch_dashboard(int $uid, int $last_poll)
Logger::debug('Importing post', ['uid' => $uid, 'created' => date(DateTimeFormat::MYSQL, $post->timestamp), 'id' => $post->id_string]); Logger::debug('Importing post', ['uid' => $uid, 'created' => date(DateTimeFormat::MYSQL, $post->timestamp), 'id' => $post->id_string]);
tumblr_process_post($post, $uid, Item::PR_NONE, $last_poll); tumblr_process_post($post, $uid, Item::PR_NONE, $last_poll);
Item::incrementInbound(Protocol::TUMBLR);
DI::pConfig()->set($uid, 'tumblr', 'last_id', $last); DI::pConfig()->set($uid, 'tumblr', 'last_id', $last);
} }
@ -1167,6 +1168,7 @@ function tumblr_get_contact_fields(stdClass $blog, int $uid, bool $update): arra
Logger::notice('Error fetching blog info', ['meta' => $info->meta, 'response' => $info->response, 'errors' => $info->errors]); Logger::notice('Error fetching blog info', ['meta' => $info->meta, 'response' => $info->response, 'errors' => $info->errors]);
return $fields; return $fields;
} }
Item::incrementInbound(Protocol::TUMBLR);
$avatar = $info->response->blog->avatar; $avatar = $info->response->blog->avatar;
if (!empty($avatar)) { if (!empty($avatar)) {
@ -1231,6 +1233,8 @@ function tumblr_get_blogs(int $uid): array
return []; return [];
} }
Item::incrementInbound(Protocol::TUMBLR);
$blogs = []; $blogs = [];
foreach ($userinfo->response->user->blogs as $blog) { foreach ($userinfo->response->user->blogs as $blog) {
$blogs[$blog->uuid] = $blog->name; $blogs[$blog->uuid] = $blog->name;
@ -1287,10 +1291,11 @@ function tumblr_get_contact_by_url(string $url, int $uid): ?array
if ($info->meta->status > 399) { if ($info->meta->status > 399) {
Logger::notice('Error fetching blog info', ['meta' => $info->meta, 'response' => $info->response, 'errors' => $info->errors, 'blog' => $blog, 'uid' => $uid]); Logger::notice('Error fetching blog info', ['meta' => $info->meta, 'response' => $info->response, 'errors' => $info->errors, 'blog' => $blog, 'uid' => $uid]);
return null; return null;
} else {
Logger::debug('Got data', ['blog' => $blog, 'meta' => $info->meta]);
} }
Logger::debug('Got data', ['blog' => $blog, 'meta' => $info->meta]);
Item::incrementInbound(Protocol::TUMBLR);
$baseurl = 'https://tumblr.com'; $baseurl = 'https://tumblr.com';
$url = $baseurl . '/' . $info->response->blog->name; $url = $baseurl . '/' . $info->response->blog->name;
@ -1355,6 +1360,7 @@ function tumblr_get(int $uid, string $url, array $parameters = []): stdClass
*/ */
function tumblr_post(int $uid, string $url, array $parameters): stdClass function tumblr_post(int $uid, string $url, array $parameters): stdClass
{ {
Item::incrementOutbound(Protocol::TUMBLR);
$url = 'https://api.tumblr.com/v2/' . $url; $url = 'https://api.tumblr.com/v2/' . $url;
$curlResult = DI::httpClient()->post($url, $parameters, ['Authorization' => ['Bearer ' . tumblr_get_token($uid)]]); $curlResult = DI::httpClient()->post($url, $parameters, ['Authorization' => ['Bearer ' . tumblr_get_token($uid)]]);

View file

@ -1293,6 +1293,8 @@ function unicode_smilies_smilies(array &$b)
Smilies::add($b, ':microscope:', '&#x1F52C'); Smilies::add($b, ':microscope:', '&#x1F52C');
Smilies::add($b, ':telescope:', '&#x1F52D'); Smilies::add($b, ':telescope:', '&#x1F52D');
Smilies::add($b, ':satellite antenna:', '&#x1F4E1'); Smilies::add($b, ':satellite antenna:', '&#x1F4E1');
Smilies::add($b, ':asterism:', '&#8258');
Smilies::add($b, ':outlines white star:', '&#9885');
// medical // medical
Smilies::add($b, ':syringe:', '&#x1F489'); Smilies::add($b, ':syringe:', '&#x1F489');