From 67a87ccb1e99cc8f693a0509c98088b6daa97fb5 Mon Sep 17 00:00:00 2001 From: paule Date: Wed, 1 Nov 2023 04:45:41 +0100 Subject: [PATCH 001/108] fix: CatchUnoptimizedMedia, prevent skipping of imported posts --- app/Console/Commands/CatchUnoptimizedMedia.php | 5 +++-- app/Media.php | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/Console/Commands/CatchUnoptimizedMedia.php b/app/Console/Commands/CatchUnoptimizedMedia.php index a62bd8651..4887cb8e3 100644 --- a/app/Console/Commands/CatchUnoptimizedMedia.php +++ b/app/Console/Commands/CatchUnoptimizedMedia.php @@ -41,8 +41,8 @@ class CatchUnoptimizedMedia extends Command public function handle() { Media::whereNull('processed_at') - ->where('created_at', '>', now()->subHours(1)) - ->where('skip_optimize', '!=', true) + // This is commented out because Instagram imported posts would not get uploaded to remote storage + // ->where('created_at', '>', now()->subHours(1)) ->whereNull('remote_url') ->whereNotNull('status_id') ->whereNotNull('media_path') @@ -52,6 +52,7 @@ class CatchUnoptimizedMedia extends Command ]) ->chunk(50, function($medias) { foreach ($medias as $media) { + if ($media->skip_optimize) continue; ImageOptimize::dispatch($media); } }); diff --git a/app/Media.php b/app/Media.php index b3f9ccba0..1c709e7f3 100644 --- a/app/Media.php +++ b/app/Media.php @@ -21,7 +21,8 @@ class Media extends Model protected $casts = [ 'srcset' => 'array', - 'deleted_at' => 'datetime' + 'deleted_at' => 'datetime', + 'skip_optimize' => 'boolean' ]; public function status() @@ -91,7 +92,7 @@ class Media extends Model case 'audio': $verb = 'Audio'; break; - + case 'image': $verb = 'Image'; break; @@ -99,7 +100,7 @@ class Media extends Model case 'video': $verb = 'Video'; break; - + default: $verb = 'Document'; break; From 960f3849f24c30d02d482e1c66addc79d9ea5cbd Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 11 Nov 2024 21:47:47 -0700 Subject: [PATCH 002/108] Update AP helpers, reject statuses with invalid dates --- app/Util/ActivityPub/Helpers.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index 782404836..c54dc8e3d 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -298,6 +298,21 @@ class Helpers return null; } + public static function validateTimestamp($timestamp) + { + try { + $date = Carbon::parse($timestamp); + $now = Carbon::now(); + $tenYearsAgo = $now->copy()->subYears(10); + $isMoreThanTenYearsOld = $date->lt($tenYearsAgo); + $tomorrow = $now->copy()->addDay(); + $isMoreThanOneDayFuture = $date->gt($tomorrow); + return !($isMoreThanTenYearsOld || $isMoreThanOneDayFuture); + } catch (\Exception $e) { + return false; + } + } + public static function statusFirstOrFetch($url, $replyTo = false) { $url = self::validateUrl($url); @@ -329,6 +344,10 @@ class Helpers return; } + if(!self::validateTimestamp($res['published'])) { + return; + } + if (config('autospam.live_filters.enabled')) { $filters = config('autospam.live_filters.filters'); if (! empty($filters) && isset($res['content']) && ! empty($res['content']) && strlen($filters) > 3) { From bdc395dc8e1e10812f15e34d42e392e45de348bf Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 11 Nov 2024 21:48:49 -0700 Subject: [PATCH 003/108] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aa1f0fda..783781abb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Release Notes ## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.12.3...dev) +- Update AP helpers, reject statuses with invalid dates ([960f3849](https://github.com/pixelfed/pixelfed/commit/960f3849)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev) From cf38a508cae1d8451c0a4aef488a63098224015b Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 11 Nov 2024 21:49:41 -0700 Subject: [PATCH 004/108] Lint --- app/Util/ActivityPub/Helpers.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index c54dc8e3d..93cab7754 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -307,7 +307,8 @@ class Helpers $isMoreThanTenYearsOld = $date->lt($tenYearsAgo); $tomorrow = $now->copy()->addDay(); $isMoreThanOneDayFuture = $date->gt($tomorrow); - return !($isMoreThanTenYearsOld || $isMoreThanOneDayFuture); + + return ! ($isMoreThanTenYearsOld || $isMoreThanOneDayFuture); } catch (\Exception $e) { return false; } @@ -344,7 +345,7 @@ class Helpers return; } - if(!self::validateTimestamp($res['published'])) { + if (! self::validateTimestamp($res['published'])) { return; } From c392cda2e7aec757decca80deecfb09fde611f05 Mon Sep 17 00:00:00 2001 From: "root (Deneir)" Date: Tue, 12 Nov 2024 20:39:14 +0100 Subject: [PATCH 005/108] internationalization of site/contact --- resources/lang/de/site.php | 12 ++++++++++++ resources/lang/en/site.php | 11 +++++++++++ resources/views/site/contact.blade.php | 20 ++++++++++---------- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/resources/lang/de/site.php b/resources/lang/de/site.php index 92f81e03b..d820b0cc6 100644 --- a/resources/lang/de/site.php +++ b/resources/lang/de/site.php @@ -16,4 +16,16 @@ return [ 'contact-us' => 'Kontaktiere uns', 'places' => 'Orte', 'profiles' => 'Profile', + + // site/contact + 'you_can_contact_the_admins' => 'Du kannst die Administration kontaktieren, indem du', + 'by_using_the_form_below' => 'das folgende Formular verwendest', + 'or' => 'oder', + 'by_sending_an_email_to' => 'eine E-Mail sendest an', + 'the_admins_have_not_set_a_contact_email_address' => 'Die Administration hat keine Kontakt-Adresse angegeben', + 'Message' => 'Nachricht', + 'request_response_from_admins' => 'Um Antwort der Administration bitten', + 'Submit' => 'Absenden', + 'log_in_to_send_a_message' => 'melde dich an, um eine Nachricht zu senden', + 'Please' => 'Bitte', ]; diff --git a/resources/lang/en/site.php b/resources/lang/en/site.php index bb8a408ba..b06c5dcdd 100644 --- a/resources/lang/en/site.php +++ b/resources/lang/en/site.php @@ -17,4 +17,15 @@ return [ 'places' => 'Places', 'profiles' => 'Profiles', + // site/contact + 'you_can_contact_the_admins' => 'You can contact the admins', + 'by_using_the_form_below' => 'by using the form below', + 'or' => 'or', + 'by_sending_an_email_to' => 'by sending an email to', + 'the_admins_have_not_set_a_contact_email_address' => 'The admins have not set a contact email address', + 'Message' => 'Message', + 'request_response_from_admins' => 'Request response from admins', + 'Submit' => 'Submit', + 'log_in_to_send_a_message' => 'log in to send a message', + 'Please' => 'Please', ]; diff --git a/resources/views/site/contact.blade.php b/resources/views/site/contact.blade.php index 984752f30..2ea96554e 100644 --- a/resources/views/site/contact.blade.php +++ b/resources/views/site/contact.blade.php @@ -10,38 +10,38 @@ @auth

@if(config('instance.email') && config('instance.contact.enabled')) - You can contact the admins by sending an email to {{config('instance.email')}} or by using the form below. + {{__('site.you_can_contact_the_admins')}} {{__('site.by_sending_an_email_to')}} {{config('instance.email')}} {{__('site.or')}} {{__('site.by_using_the_form_below')}}. @elseif(config('instance.email') && !config('instance.contact.enabled')) - You can contact the admins by sending an email to {{config('instance.email')}}. + {{__('site.you_can_contact_the_admins')}} {{__('site.by_sending_an_email_to')}} {{config('instance.email')}}. @elseif(!config('instance.email') && config('instance.contact.enabled')) - You can contact the admins by using the form below. + {{__('site.you_can_contact_the_admins')}} {{__('site.by_using_the_form_below')}}. @else - The admins have not set a contact email address. + {{__('the_admins_have_not_set_a_contact_email_address')}} @endif

@if(config('instance.contact.enabled'))
@csrf
- + 0/500
- +
- +
@endif @else

@if(config('instance.email') && config('instance.contact.enabled')) - You can contact the admins by sending an email to {{config('instance.email')}} or log in to send a message. + {{__('site.you_can_contact_the_admins')}} {{__('site.by_sending_an_email_to')}} {{config('instance.email')}} {{__('site.or')}} {{__('site.log_in_to_send_a_message')}}. @elseif (!config('instance.email') && config('instance.contact.enabled')) - The admins have not set a contact email address. Please log in to send a message. + {{__('the_admins_have_not_set_a_contact_email_address')}}. {{__('site.Please')}} {{__('site.log_in_to_send_a_message')}}. @elseif (config('instance.email') && !config('instance.contact.enabled')) - You can contact the admins by sending an email to {{config('instance.email')}}. + {{__('site.you_can_contact_the_admins')}} {{__('site.by_sending_an_email_to')}} {{config('instance.email')}}. @endif

@endauth From 8c58d20700c9548e7ddd650e51fd761846012d9b Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 15 Nov 2024 02:54:34 -0700 Subject: [PATCH 006/108] Update composer.lock --- composer.lock | 234 +++++++++++++++++++++++++------------------------- 1 file changed, 118 insertions(+), 116 deletions(-) diff --git a/composer.lock b/composer.lock index 8819e991a..9c873f80b 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.325.5", + "version": "3.327.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "195d003c902a741de53008c839cbcebddbe1f326" + "reference": "3d52ec587989b136e486f94eff3dd316465aeb42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/195d003c902a741de53008c839cbcebddbe1f326", - "reference": "195d003c902a741de53008c839cbcebddbe1f326", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3d52ec587989b136e486f94eff3dd316465aeb42", + "reference": "3d52ec587989b136e486f94eff3dd316465aeb42", "shasum": "" }, "require": { @@ -154,9 +154,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.325.5" + "source": "https://github.com/aws/aws-sdk-php/tree/3.327.1" }, - "time": "2024-11-08T19:12:57+00:00" + "time": "2024-11-15T01:53:30+00:00" }, { "name": "bacon/bacon-qr-code", @@ -2378,16 +2378,16 @@ }, { "name": "laravel/framework", - "version": "v11.30.0", + "version": "v11.31.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "dff716442d9c229d716be82ccc9a7de52eb97193" + "reference": "365090ed2c68244e3141cdb5e247cdf3dfba2c40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/dff716442d9c229d716be82ccc9a7de52eb97193", - "reference": "dff716442d9c229d716be82ccc9a7de52eb97193", + "url": "https://api.github.com/repos/laravel/framework/zipball/365090ed2c68244e3141cdb5e247cdf3dfba2c40", + "reference": "365090ed2c68244e3141cdb5e247cdf3dfba2c40", "shasum": "" }, "require": { @@ -2583,7 +2583,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-10-30T15:00:34+00:00" + "time": "2024-11-12T15:36:15+00:00" }, { "name": "laravel/helpers", @@ -2644,16 +2644,16 @@ }, { "name": "laravel/horizon", - "version": "v5.29.2", + "version": "v5.29.3", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "d9c39ce4e9050b33a2ff9d2cee21646a18d4cc92" + "reference": "a48d242759704e598242074daf0060bbeb6ed46d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/d9c39ce4e9050b33a2ff9d2cee21646a18d4cc92", - "reference": "d9c39ce4e9050b33a2ff9d2cee21646a18d4cc92", + "url": "https://api.github.com/repos/laravel/horizon/zipball/a48d242759704e598242074daf0060bbeb6ed46d", + "reference": "a48d242759704e598242074daf0060bbeb6ed46d", "shasum": "" }, "require": { @@ -2718,22 +2718,22 @@ ], "support": { "issues": "https://github.com/laravel/horizon/issues", - "source": "https://github.com/laravel/horizon/tree/v5.29.2" + "source": "https://github.com/laravel/horizon/tree/v5.29.3" }, - "time": "2024-10-16T21:36:57+00:00" + "time": "2024-11-07T21:51:45+00:00" }, { "name": "laravel/passport", - "version": "v12.3.0", + "version": "v12.3.1", "source": { "type": "git", "url": "https://github.com/laravel/passport.git", - "reference": "ca63a86697a4fa091c7dcabe88ebba91d97c785d" + "reference": "0d95ca9cc9c80bdf64d04dcf04542720e3d5d55c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/passport/zipball/ca63a86697a4fa091c7dcabe88ebba91d97c785d", - "reference": "ca63a86697a4fa091c7dcabe88ebba91d97c785d", + "url": "https://api.github.com/repos/laravel/passport/zipball/0d95ca9cc9c80bdf64d04dcf04542720e3d5d55c", + "reference": "0d95ca9cc9c80bdf64d04dcf04542720e3d5d55c", "shasum": "" }, "require": { @@ -2796,20 +2796,20 @@ "issues": "https://github.com/laravel/passport/issues", "source": "https://github.com/laravel/passport" }, - "time": "2024-08-05T13:44:51+00:00" + "time": "2024-11-11T20:15:28+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.1", + "version": "v0.3.2", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "0f3848a445562dac376b27968f753c65e7e1036e" + "reference": "0e0535747c6b8d6d10adca8b68293cf4517abb0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/0f3848a445562dac376b27968f753c65e7e1036e", - "reference": "0f3848a445562dac376b27968f753c65e7e1036e", + "url": "https://api.github.com/repos/laravel/prompts/zipball/0e0535747c6b8d6d10adca8b68293cf4517abb0f", + "reference": "0e0535747c6b8d6d10adca8b68293cf4517abb0f", "shasum": "" }, "require": { @@ -2825,7 +2825,7 @@ "require-dev": { "illuminate/collections": "^10.0|^11.0", "mockery/mockery": "^1.5", - "pestphp/pest": "^2.3", + "pestphp/pest": "^2.3|^3.4", "phpstan/phpstan": "^1.11", "phpstan/phpstan-mockery": "^1.1" }, @@ -2853,22 +2853,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.1" + "source": "https://github.com/laravel/prompts/tree/v0.3.2" }, - "time": "2024-10-09T19:42:26+00:00" + "time": "2024-11-12T14:59:47+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.3.5", + "version": "v1.3.6", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c" + "reference": "f865a58ea3a0107c336b7045104c75243fa59d96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f865a58ea3a0107c336b7045104c75243fa59d96", + "reference": "f865a58ea3a0107c336b7045104c75243fa59d96", "shasum": "" }, "require": { @@ -2916,7 +2916,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-09-23T13:33:08+00:00" + "time": "2024-11-11T17:06:04+00:00" }, { "name": "laravel/tinker", @@ -4129,16 +4129,16 @@ }, { "name": "monolog/monolog", - "version": "3.7.0", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" + "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8", - "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/32e515fdc02cdafbe4593e30a9350d486b125b67", + "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67", "shasum": "" }, "require": { @@ -4158,12 +4158,14 @@ "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "^10.5.17", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", "predis/predis": "^1.1 || ^2", - "ruflin/elastica": "^7", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", "symfony/mailer": "^5.4 || ^6", "symfony/mime": "^5.4 || ^6" }, @@ -4214,7 +4216,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.7.0" + "source": "https://github.com/Seldaek/monolog/tree/3.8.0" }, "funding": [ { @@ -4226,7 +4228,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:40:51+00:00" + "time": "2024-11-12T13:57:08+00:00" }, { "name": "mtdowling/jmespath.php", @@ -4981,21 +4983,21 @@ }, { "name": "pbmedia/laravel-ffmpeg", - "version": "8.5.0", + "version": "8.6.0", "source": { "type": "git", "url": "https://github.com/protonemedia/laravel-ffmpeg.git", - "reference": "44f260839e68ce8c785d502f99b998729cdb5321" + "reference": "f14efc53e8a52b53a237a9910b32e795dafcf8bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protonemedia/laravel-ffmpeg/zipball/44f260839e68ce8c785d502f99b998729cdb5321", - "reference": "44f260839e68ce8c785d502f99b998729cdb5321", + "url": "https://api.github.com/repos/protonemedia/laravel-ffmpeg/zipball/f14efc53e8a52b53a237a9910b32e795dafcf8bc", + "reference": "f14efc53e8a52b53a237a9910b32e795dafcf8bc", "shasum": "" }, "require": { "illuminate/contracts": "^10.0|^11.0", - "php": "^8.1|^8.2|^8.3", + "php": "^8.1|^8.2|^8.3|^8.4", "php-ffmpeg/php-ffmpeg": "^1.2", "ramsey/collection": "^2.0" }, @@ -5047,7 +5049,7 @@ ], "support": { "issues": "https://github.com/protonemedia/laravel-ffmpeg/issues", - "source": "https://github.com/protonemedia/laravel-ffmpeg/tree/8.5.0" + "source": "https://github.com/protonemedia/laravel-ffmpeg/tree/8.6.0" }, "funding": [ { @@ -5055,25 +5057,25 @@ "type": "github" } ], - "time": "2024-03-12T11:20:32+00:00" + "time": "2024-11-12T16:12:23+00:00" }, { "name": "php-ffmpeg/php-ffmpeg", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/PHP-FFMpeg/PHP-FFMpeg.git", - "reference": "785a5ba05dd88b3b8146f85f18476b259b23917c" + "reference": "5e7b15710a8607e8a3a2d9fbe2c150a99b924fa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/785a5ba05dd88b3b8146f85f18476b259b23917c", - "reference": "785a5ba05dd88b3b8146f85f18476b259b23917c", + "url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/5e7b15710a8607e8a3a2d9fbe2c150a99b924fa5", + "reference": "5e7b15710a8607e8a3a2d9fbe2c150a99b924fa5", "shasum": "" }, "require": { "evenement/evenement": "^3.0", - "php": "^8.0 || ^8.1 || ^8.2 || ^8.3", + "php": "^8.0 || ^8.1 || ^8.2 || ^8.3 || ^8.4", "psr/log": "^1.0 || ^2.0 || ^3.0", "spatie/temporary-directory": "^2.0", "symfony/cache": "^5.4 || ^6.0 || ^7.0", @@ -5081,7 +5083,7 @@ }, "require-dev": { "mockery/mockery": "^1.5", - "phpunit/phpunit": "^9.5.10" + "phpunit/phpunit": "^9.5.10 || ^10.0" }, "suggest": { "php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg" @@ -5142,9 +5144,9 @@ ], "support": { "issues": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/issues", - "source": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/tree/v1.2.0" + "source": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/tree/v1.3.0" }, - "time": "2024-01-02T10:37:01+00:00" + "time": "2024-11-12T15:39:52+00:00" }, { "name": "phpoption/phpoption", @@ -7314,16 +7316,16 @@ }, { "name": "symfony/console", - "version": "v7.1.7", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "3284aafcac338b6e86fd955ee4d794cbe434151a" + "reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/3284aafcac338b6e86fd955ee4d794cbe434151a", - "reference": "3284aafcac338b6e86fd955ee4d794cbe434151a", + "url": "https://api.github.com/repos/symfony/console/zipball/ff04e5b5ba043d2badfb308197b9e6b42883fcd5", + "reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5", "shasum": "" }, "require": { @@ -7387,7 +7389,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.7" + "source": "https://github.com/symfony/console/tree/v7.1.8" }, "funding": [ { @@ -7403,7 +7405,7 @@ "type": "tidelift" } ], - "time": "2024-11-05T15:34:55+00:00" + "time": "2024-11-06T14:23:19+00:00" }, { "name": "symfony/css-selector", @@ -7834,16 +7836,16 @@ }, { "name": "symfony/http-client", - "version": "v6.4.14", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "05d88cbd816ad6e0202edd9a9963cb9d615b8826" + "reference": "cb4073c905cd12b8496d24ac428a9228c1750670" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/05d88cbd816ad6e0202edd9a9963cb9d615b8826", - "reference": "05d88cbd816ad6e0202edd9a9963cb9d615b8826", + "url": "https://api.github.com/repos/symfony/http-client/zipball/cb4073c905cd12b8496d24ac428a9228c1750670", + "reference": "cb4073c905cd12b8496d24ac428a9228c1750670", "shasum": "" }, "require": { @@ -7907,7 +7909,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.14" + "source": "https://github.com/symfony/http-client/tree/v6.4.15" }, "funding": [ { @@ -7923,7 +7925,7 @@ "type": "tidelift" } ], - "time": "2024-11-05T16:39:55+00:00" + "time": "2024-11-13T13:40:18+00:00" }, { "name": "symfony/http-client-contracts", @@ -8005,16 +8007,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.1.7", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "5183b61657807099d98f3367bcccb850238b17a9" + "reference": "f4419ec69ccfc3f725a4de7c20e4e57626d10112" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5183b61657807099d98f3367bcccb850238b17a9", - "reference": "5183b61657807099d98f3367bcccb850238b17a9", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f4419ec69ccfc3f725a4de7c20e4e57626d10112", + "reference": "f4419ec69ccfc3f725a4de7c20e4e57626d10112", "shasum": "" }, "require": { @@ -8024,12 +8026,12 @@ }, "conflict": { "doctrine/dbal": "<3.6", - "symfony/cache": "<6.4" + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4|^7.0", + "symfony/cache": "^6.4.12|^7.1.5", "symfony/dependency-injection": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", @@ -8062,7 +8064,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.1.7" + "source": "https://github.com/symfony/http-foundation/tree/v7.1.8" }, "funding": [ { @@ -8078,20 +8080,20 @@ "type": "tidelift" } ], - "time": "2024-11-06T09:02:46+00:00" + "time": "2024-11-09T09:16:45+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.1.7", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "7f137cda31fd41e422edcdc01915f2c095b84399" + "reference": "33fef24e3dc79d6d30bf4936531f2f4bd2ca189e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/7f137cda31fd41e422edcdc01915f2c095b84399", - "reference": "7f137cda31fd41e422edcdc01915f2c095b84399", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/33fef24e3dc79d6d30bf4936531f2f4bd2ca189e", + "reference": "33fef24e3dc79d6d30bf4936531f2f4bd2ca189e", "shasum": "" }, "require": { @@ -8176,7 +8178,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.1.7" + "source": "https://github.com/symfony/http-kernel/tree/v7.1.8" }, "funding": [ { @@ -8192,7 +8194,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T09:54:34+00:00" + "time": "2024-11-13T14:25:32+00:00" }, { "name": "symfony/mailer", @@ -9065,16 +9067,16 @@ }, { "name": "symfony/process", - "version": "v7.1.7", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "9b8a40b7289767aa7117e957573c2a535efe6585" + "reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/9b8a40b7289767aa7117e957573c2a535efe6585", - "reference": "9b8a40b7289767aa7117e957573c2a535efe6585", + "url": "https://api.github.com/repos/symfony/process/zipball/42783370fda6e538771f7c7a36e9fa2ee3a84892", + "reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892", "shasum": "" }, "require": { @@ -9106,7 +9108,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.7" + "source": "https://github.com/symfony/process/tree/v7.1.8" }, "funding": [ { @@ -9122,7 +9124,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T09:25:12+00:00" + "time": "2024-11-06T14:23:19+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -9373,16 +9375,16 @@ }, { "name": "symfony/string", - "version": "v7.1.6", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "61b72d66bf96c360a727ae6232df5ac83c71f626" + "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/61b72d66bf96c360a727ae6232df5ac83c71f626", - "reference": "61b72d66bf96c360a727ae6232df5ac83c71f626", + "url": "https://api.github.com/repos/symfony/string/zipball/591ebd41565f356fcd8b090fe64dbb5878f50281", + "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281", "shasum": "" }, "require": { @@ -9440,7 +9442,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.6" + "source": "https://github.com/symfony/string/tree/v7.1.8" }, "funding": [ { @@ -9456,7 +9458,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-11-13T13:31:21+00:00" }, { "name": "symfony/translation", @@ -9706,16 +9708,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.1.7", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "f6ea51f669760cacd7464bf7eaa0be87b8072db1" + "reference": "7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f6ea51f669760cacd7464bf7eaa0be87b8072db1", - "reference": "f6ea51f669760cacd7464bf7eaa0be87b8072db1", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8", + "reference": "7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8", "shasum": "" }, "require": { @@ -9769,7 +9771,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.1.7" + "source": "https://github.com/symfony/var-dumper/tree/v7.1.8" }, "funding": [ { @@ -9785,7 +9787,7 @@ "type": "tidelift" } ], - "time": "2024-11-05T15:34:55+00:00" + "time": "2024-11-08T15:46:42+00:00" }, { "name": "symfony/var-exporter", @@ -10515,16 +10517,16 @@ "packages-dev": [ { "name": "fakerphp/faker", - "version": "v1.23.1", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" + "reference": "a136842a532bac9ecd8a1c723852b09915d7db50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/a136842a532bac9ecd8a1c723852b09915d7db50", + "reference": "a136842a532bac9ecd8a1c723852b09915d7db50", "shasum": "" }, "require": { @@ -10572,9 +10574,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.0" }, - "time": "2024-01-02T13:46:09+00:00" + "time": "2024-11-07T15:11:20+00:00" }, { "name": "filp/whoops", @@ -10766,16 +10768,16 @@ }, { "name": "laravel/telescope", - "version": "v5.2.4", + "version": "v5.2.5", "source": { "type": "git", "url": "https://github.com/laravel/telescope.git", - "reference": "749369e996611d803e7c1b57929b482dd676008d" + "reference": "f68386a8d816c9e3a011b8301bfd263213bf00d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/telescope/zipball/749369e996611d803e7c1b57929b482dd676008d", - "reference": "749369e996611d803e7c1b57929b482dd676008d", + "url": "https://api.github.com/repos/laravel/telescope/zipball/f68386a8d816c9e3a011b8301bfd263213bf00d4", + "reference": "f68386a8d816c9e3a011b8301bfd263213bf00d4", "shasum": "" }, "require": { @@ -10829,9 +10831,9 @@ ], "support": { "issues": "https://github.com/laravel/telescope/issues", - "source": "https://github.com/laravel/telescope/tree/v5.2.4" + "source": "https://github.com/laravel/telescope/tree/v5.2.5" }, - "time": "2024-10-29T15:35:13+00:00" + "time": "2024-10-31T17:06:07+00:00" }, { "name": "mockery/mockery", From bcc8b8842fce8dc67599baddade40032929f98f2 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 01:55:32 -0700 Subject: [PATCH 007/108] Update DirectMessageController, fix ordering bug --- app/Http/Controllers/DirectMessageController.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/DirectMessageController.php b/app/Http/Controllers/DirectMessageController.php index 1a30032cd..bc4e60885 100644 --- a/app/Http/Controllers/DirectMessageController.php +++ b/app/Http/Controllers/DirectMessageController.php @@ -372,7 +372,7 @@ class DirectMessageController extends Controller ->exists(); if ($recipient->domain == null && $hidden == false && ! $nf) { - $notification = new Notification(); + $notification = new Notification; $notification->profile_id = $recipient->id; $notification->actor_id = $profile->id; $notification->action = 'dm'; @@ -405,6 +405,8 @@ class DirectMessageController extends Controller { $this->validate($request, [ 'pid' => 'required', + 'max_id' => 'sometimes|integer', + 'min_id' => 'sometimes|integer', ]); $user = $request->user(); abort_if($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id), 403, 'Invalid permissions for this action'); @@ -423,9 +425,10 @@ class DirectMessageController extends Controller return $q->where([['from_id', $pid], ['to_id', $uid], ])->orWhere([['from_id', $uid], ['to_id', $pid]]); }) - ->latest() + ->orderBy('id', 'asc') ->take(8) - ->get(); + ->get() + ->reverse(); } elseif ($max_id) { $res = DirectMessage::select('*') ->where('id', '<', $max_id) @@ -433,7 +436,7 @@ class DirectMessageController extends Controller return $q->where([['from_id', $pid], ['to_id', $uid], ])->orWhere([['from_id', $uid], ['to_id', $pid]]); }) - ->latest() + ->orderBy('id', 'desc') ->take(8) ->get(); } else { @@ -441,7 +444,7 @@ class DirectMessageController extends Controller return $q->where([['from_id', $pid], ['to_id', $uid], ])->orWhere([['from_id', $uid], ['to_id', $pid]]); }) - ->latest() + ->orderBy('id', 'desc') ->take(8) ->get(); } @@ -636,7 +639,7 @@ class DirectMessageController extends Controller $status->in_reply_to_profile_id = $recipient->id; $status->save(); - $media = new Media(); + $media = new Media; $media->status_id = $status->id; $media->profile_id = $profile->id; $media->user_id = $user->id; From 044d410c49c3228a9dd50be2c6bca263993a56c6 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 02:06:25 -0700 Subject: [PATCH 008/108] Update DirectMessageController, fix ordering bug --- .../Controllers/DirectMessageController.php | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/DirectMessageController.php b/app/Http/Controllers/DirectMessageController.php index bc4e60885..5b07f6adb 100644 --- a/app/Http/Controllers/DirectMessageController.php +++ b/app/Http/Controllers/DirectMessageController.php @@ -421,9 +421,10 @@ class DirectMessageController extends Controller if ($min_id) { $res = DirectMessage::select('*') ->where('id', '>', $min_id) - ->where(function ($q) use ($pid, $uid) { - return $q->where([['from_id', $pid], ['to_id', $uid], - ])->orWhere([['from_id', $uid], ['to_id', $pid]]); + ->where(function ($query) use ($pid, $uid) { + $query->where('from_id', $pid)->where('to_id', $uid); + })->orWhere(function ($query) use ($pid, $uid) { + $query->where('from_id', $uid)->where('to_id', $pid); }) ->orderBy('id', 'asc') ->take(8) @@ -432,17 +433,19 @@ class DirectMessageController extends Controller } elseif ($max_id) { $res = DirectMessage::select('*') ->where('id', '<', $max_id) - ->where(function ($q) use ($pid, $uid) { - return $q->where([['from_id', $pid], ['to_id', $uid], - ])->orWhere([['from_id', $uid], ['to_id', $pid]]); + ->where(function ($query) use ($pid, $uid) { + $query->where('from_id', $pid)->where('to_id', $uid); + })->orWhere(function ($query) use ($pid, $uid) { + $query->where('from_id', $uid)->where('to_id', $pid); }) ->orderBy('id', 'desc') ->take(8) ->get(); } else { - $res = DirectMessage::where(function ($q) use ($pid, $uid) { - return $q->where([['from_id', $pid], ['to_id', $uid], - ])->orWhere([['from_id', $uid], ['to_id', $pid]]); + $res = DirectMessage::where(function ($query) use ($pid, $uid) { + $query->where('from_id', $pid)->where('to_id', $uid); + })->orWhere(function ($query) use ($pid, $uid) { + $query->where('from_id', $uid)->where('to_id', $pid); }) ->orderBy('id', 'desc') ->take(8) From 3d8ae361d77fc7be8c3f51fe6a9fa3eba5d0c169 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 02:09:01 -0700 Subject: [PATCH 009/108] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 783781abb..18e8c874f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.12.3...dev) - Update AP helpers, reject statuses with invalid dates ([960f3849](https://github.com/pixelfed/pixelfed/commit/960f3849)) +- Update DirectMessage API, fix broken threading ([044d410c](https://github.com/pixelfed/pixelfed/commit/044d410c)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev) From 79d73c508c4467b3b637f5b367a89f45439a2186 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 02:24:00 -0700 Subject: [PATCH 010/108] Update Inbox --- app/Util/ActivityPub/Inbox.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Util/ActivityPub/Inbox.php b/app/Util/ActivityPub/Inbox.php index dd9b1578b..8cca59cfc 100644 --- a/app/Util/ActivityPub/Inbox.php +++ b/app/Util/ActivityPub/Inbox.php @@ -417,8 +417,8 @@ class Inbox return; } - $msg = $activity['content']; - $msgText = strip_tags($activity['content']); + $msg = Purify::clean($activity['content']); + $msgText = strip_tags($msg); if (Str::startsWith($msgText, '@'.$profile->username)) { $len = strlen('@'.$profile->username); From 0917953dbc5269a74abf5bf0cbd43d3e29fe9630 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 02:29:11 -0700 Subject: [PATCH 011/108] Update Status, remove unused method --- app/Status.php | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/app/Status.php b/app/Status.php index d665464ae..8b69c199c 100644 --- a/app/Status.php +++ b/app/Status.php @@ -308,46 +308,6 @@ class Status extends Model return $this->comments()->orderBy('created_at', 'desc')->take(3); } - public function toActivityPubObject() - { - if($this->local == false) { - return; - } - $profile = $this->profile; - $to = $this->scopeToAudience('to'); - $cc = $this->scopeToAudience('cc'); - return [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $this->permalink(), - 'type' => 'Create', - 'actor' => $profile->permalink(), - 'published' => str_replace('+00:00', 'Z', $this->created_at->format(DATE_RFC3339_EXTENDED)), - 'to' => $to, - 'cc' => $cc, - 'object' => [ - 'id' => $this->url(), - 'type' => 'Note', - 'summary' => null, - 'inReplyTo' => null, - 'published' => str_replace('+00:00', 'Z', $this->created_at->format(DATE_RFC3339_EXTENDED)), - 'url' => $this->url(), - 'attributedTo' => $this->profile->url(), - 'to' => $to, - 'cc' => $cc, - 'sensitive' => (bool) $this->is_nsfw, - 'content' => $this->rendered, - 'attachment' => $this->media->map(function($media) { - return [ - 'type' => 'Document', - 'mediaType' => $media->mime, - 'url' => $media->url(), - 'name' => null - ]; - })->toArray() - ] - ]; - } - public function scopeToAudience($audience) { if(!in_array($audience, ['to', 'cc']) || $this->local == false) { From fb8dbb95dbc9be91d3e95da11a5be7a538bdd124 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 02:53:39 -0700 Subject: [PATCH 012/108] Update Status caption render logic --- app/Http/Resources/StatusStateless.php | 92 +++---- .../ActivityPub/StatusTransformer.php | 87 +++--- .../ActivityPub/Verb/CreateNote.php | 258 +++++++++--------- app/Transformer/ActivityPub/Verb/Note.php | 244 +++++++++-------- app/Transformer/ActivityPub/Verb/Question.php | 191 ++++++------- .../ActivityPub/Verb/UpdateNote.php | 239 ++++++++-------- .../Api/Mastodon/v1/StatusTransformer.php | 78 +++--- .../Api/StatusStatelessTransformer.php | 115 ++++---- app/Transformer/Api/StatusTransformer.php | 123 ++++----- 9 files changed, 719 insertions(+), 708 deletions(-) diff --git a/app/Http/Resources/StatusStateless.php b/app/Http/Resources/StatusStateless.php index df451cc53..0a7bbe8d4 100644 --- a/app/Http/Resources/StatusStateless.php +++ b/app/Http/Resources/StatusStateless.php @@ -2,18 +2,17 @@ namespace App\Http\Resources; -use Illuminate\Http\Resources\Json\JsonResource; -use Cache; +use App\Models\CustomEmoji; use App\Services\AccountService; use App\Services\HashidService; use App\Services\LikeService; use App\Services\MediaService; use App\Services\MediaTagService; -use App\Services\StatusHashtagService; -use App\Services\StatusLabelService; -use App\Services\StatusMentionService; use App\Services\PollService; -use App\Models\CustomEmoji; +use App\Services\StatusHashtagService; +use App\Services\StatusMentionService; +use App\Util\Lexer\Autolink; +use Illuminate\Http\Resources\Json\JsonResource; class StatusStateless extends JsonResource { @@ -28,49 +27,50 @@ class StatusStateless extends JsonResource $status = $this; $taggedPeople = MediaTagService::get($status->id); $poll = $status->type === 'poll' ? PollService::get($status->id) : null; + $autoLink = $status->caption ? Autolink::create()->autolink($status->caption) : null; return [ - '_v' => 1, - 'id' => (string) $status->id, + '_v' => 1, + 'id' => (string) $status->id, //'gid' => $status->group_id ? (string) $status->group_id : null, - 'shortcode' => HashidService::encode($status->id), - 'uri' => $status->url(), - 'url' => $status->url(), - 'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null, - 'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null, - 'reblog' => null, - 'content' => $status->rendered ?? $status->caption, - 'content_text' => $status->caption, - 'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)), - 'emojis' => CustomEmoji::scan($status->caption), - 'reblogs_count' => $status->reblogs_count ?? 0, - 'favourites_count' => $status->likes_count ?? 0, - 'reblogged' => null, - 'favourited' => null, - 'muted' => null, - 'sensitive' => (bool) $status->is_nsfw, - 'spoiler_text' => $status->cw_summary ?? '', - 'visibility' => $status->scope ?? $status->visibility, - 'application' => [ - 'name' => 'web', - 'website' => null - ], - 'language' => null, - 'mentions' => StatusMentionService::get($status->id), - 'pf_type' => $status->type ?? $status->setType(), - 'reply_count' => (int) $status->reply_count, - 'comments_disabled' => (bool) $status->comments_disabled, - 'thread' => false, - 'replies' => [], - 'parent' => [], - 'place' => $status->place, - 'local' => (bool) $status->local, - 'taggedPeople' => $taggedPeople, - 'liked_by' => LikeService::likedBy($status), - 'media_attachments' => MediaService::get($status->id), - 'account' => AccountService::get($status->profile_id, true), - 'tags' => StatusHashtagService::statusTags($status->id), - 'poll' => $poll + 'shortcode' => HashidService::encode($status->id), + 'uri' => $status->url(), + 'url' => $status->url(), + 'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null, + 'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null, + 'reblog' => null, + 'content' => $autoLink, + 'content_text' => $status->caption, + 'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)), + 'emojis' => CustomEmoji::scan($status->caption), + 'reblogs_count' => $status->reblogs_count ?? 0, + 'favourites_count' => $status->likes_count ?? 0, + 'reblogged' => null, + 'favourited' => null, + 'muted' => null, + 'sensitive' => (bool) $status->is_nsfw, + 'spoiler_text' => $status->cw_summary ?? '', + 'visibility' => $status->scope ?? $status->visibility, + 'application' => [ + 'name' => 'web', + 'website' => null, + ], + 'language' => null, + 'mentions' => StatusMentionService::get($status->id), + 'pf_type' => $status->type ?? $status->setType(), + 'reply_count' => (int) $status->reply_count, + 'comments_disabled' => (bool) $status->comments_disabled, + 'thread' => false, + 'replies' => [], + 'parent' => [], + 'place' => $status->place, + 'local' => (bool) $status->local, + 'taggedPeople' => $taggedPeople, + 'liked_by' => LikeService::likedBy($status), + 'media_attachments' => MediaService::get($status->id), + 'account' => AccountService::get($status->profile_id, true), + 'tags' => StatusHashtagService::statusTags($status->id), + 'poll' => $poll, ]; } } diff --git a/app/Transformer/ActivityPub/StatusTransformer.php b/app/Transformer/ActivityPub/StatusTransformer.php index f5d5ea531..c0c378c7b 100644 --- a/app/Transformer/ActivityPub/StatusTransformer.php +++ b/app/Transformer/ActivityPub/StatusTransformer.php @@ -2,59 +2,62 @@ namespace App\Transformer\ActivityPub; -use App\Status; -use League\Fractal; use App\Services\MediaService; +use App\Status; +use App\Util\Lexer\Autolink; +use League\Fractal; class StatusTransformer extends Fractal\TransformerAbstract { public function transform(Status $status) { + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + return [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', - [ - 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', - 'featured' => [ - 'https://pixelfed.org/ns#featured' => ['@type' => '@id'], - ], + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', + [ + 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', + 'featured' => [ + 'https://pixelfed.org/ns#featured' => ['@type' => '@id'], + ], + ], ], - ], - 'id' => $status->url(), + 'id' => $status->url(), - // TODO: handle other types - 'type' => 'Note', + // TODO: handle other types + 'type' => 'Note', - // XXX: CW Title - 'summary' => null, - 'content' => $status->rendered ?? $status->caption, - 'inReplyTo' => null, + // XXX: CW Title + 'summary' => null, + 'content' => $content, + 'inReplyTo' => null, - // TODO: fix date format - 'published' => $status->created_at->toAtomString(), - 'url' => $status->url(), - 'attributedTo' => $status->profile->permalink(), - 'to' => [ - // TODO: handle proper scope - 'https://www.w3.org/ns/activitystreams#Public', - ], - 'cc' => [ - // TODO: add cc's - $status->profile->permalink('/followers'), - ], - 'sensitive' => (bool) $status->is_nsfw, - 'atomUri' => $status->url(), - 'inReplyToAtomUri' => null, - 'attachment' => MediaService::activitypub($status->id), - 'tag' => [], - 'location' => $status->place_id ? [ - 'type' => 'Place', - 'name' => $status->place->name, - 'longitude' => $status->place->long, - 'latitude' => $status->place->lat, - 'country' => $status->place->country + // TODO: fix date format + 'published' => $status->created_at->toAtomString(), + 'url' => $status->url(), + 'attributedTo' => $status->profile->permalink(), + 'to' => [ + // TODO: handle proper scope + 'https://www.w3.org/ns/activitystreams#Public', + ], + 'cc' => [ + // TODO: add cc's + $status->profile->permalink('/followers'), + ], + 'sensitive' => (bool) $status->is_nsfw, + 'atomUri' => $status->url(), + 'inReplyToAtomUri' => null, + 'attachment' => MediaService::activitypub($status->id), + 'tag' => [], + 'location' => $status->place_id ? [ + 'type' => 'Place', + 'name' => $status->place->name, + 'longitude' => $status->place->long, + 'latitude' => $status->place->lat, + 'country' => $status->place->country, ] : null, - ]; + ]; } } diff --git a/app/Transformer/ActivityPub/Verb/CreateNote.php b/app/Transformer/ActivityPub/Verb/CreateNote.php index 55fdfa8f4..287393572 100644 --- a/app/Transformer/ActivityPub/Verb/CreateNote.php +++ b/app/Transformer/ActivityPub/Verb/CreateNote.php @@ -2,140 +2,144 @@ namespace App\Transformer\ActivityPub\Verb; -use App\Status; -use League\Fractal; use App\Models\CustomEmoji; +use App\Status; +use App\Util\Lexer\Autolink; use Illuminate\Support\Str; +use League\Fractal; class CreateNote extends Fractal\TransformerAbstract { - public function transform(Status $status) - { - $mentions = $status->mentions->map(function ($mention) { - $webfinger = $mention->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - return [ - 'type' => 'Mention', - 'href' => $mention->permalink(), - 'name' => $name - ]; - })->toArray(); + public function transform(Status $status) + { + $mentions = $status->mentions->map(function ($mention) { + $webfinger = $mention->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; - if($status->in_reply_to_id != null) { - $parent = $status->parent()->profile; - if($parent) { - $webfinger = $parent->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - $reply = [ - 'type' => 'Mention', - 'href' => $parent->permalink(), - 'name' => $name - ]; - $mentions = array_merge($reply, $mentions); - } - } + return [ + 'type' => 'Mention', + 'href' => $mention->permalink(), + 'name' => $name, + ]; + })->toArray(); - $hashtags = $status->hashtags->map(function ($hashtag) { - return [ - 'type' => 'Hashtag', - 'href' => $hashtag->url(), - 'name' => "#{$hashtag->name}", - ]; - })->toArray(); + if ($status->in_reply_to_id != null) { + $parent = $status->parent()->profile; + if ($parent) { + $webfinger = $parent->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; + $reply = [ + 'type' => 'Mention', + 'href' => $parent->permalink(), + 'name' => $name, + ]; + $mentions = array_merge($reply, $mentions); + } + } - $emojis = CustomEmoji::scan($status->caption, true) ?? []; - $emoji = array_merge($emojis, $mentions); - $tags = array_merge($emoji, $hashtags); + $hashtags = $status->hashtags->map(function ($hashtag) { + return [ + 'type' => 'Hashtag', + 'href' => $hashtag->url(), + 'name' => "#{$hashtag->name}", + ]; + })->toArray(); - return [ - '@context' => [ - 'https://w3id.org/security/v1', - 'https://www.w3.org/ns/activitystreams', - [ - 'Hashtag' => 'as:Hashtag', - 'sensitive' => 'as:sensitive', - 'schema' => 'http://schema.org/', - 'pixelfed' => 'http://pixelfed.org/ns#', - 'commentsEnabled' => [ - '@id' => 'pixelfed:commentsEnabled', - '@type' => 'schema:Boolean' - ], - 'capabilities' => [ - '@id' => 'pixelfed:capabilities', - '@container' => '@set' - ], - 'announce' => [ - '@id' => 'pixelfed:canAnnounce', - '@type' => '@id' - ], - 'like' => [ - '@id' => 'pixelfed:canLike', - '@type' => '@id' - ], - 'reply' => [ - '@id' => 'pixelfed:canReply', - '@type' => '@id' - ], - 'toot' => 'http://joinmastodon.org/ns#', - 'Emoji' => 'toot:Emoji', - 'blurhash' => 'toot:blurhash', - ] - ], - 'id' => $status->permalink(), - 'type' => 'Create', - 'actor' => $status->profile->permalink(), - 'published' => $status->created_at->toAtomString(), - 'to' => $status->scopeToAudience('to'), - 'cc' => $status->scopeToAudience('cc'), - 'object' => [ - 'id' => $status->url(), - 'type' => 'Note', - 'summary' => $status->is_nsfw ? $status->cw_summary : null, - 'content' => $status->rendered ?? $status->caption, - 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, - 'published' => $status->created_at->toAtomString(), - 'url' => $status->url(), - 'attributedTo' => $status->profile->permalink(), - 'to' => $status->scopeToAudience('to'), - 'cc' => $status->scopeToAudience('cc'), - 'sensitive' => (bool) $status->is_nsfw, - 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { - $res = [ - 'type' => $media->activityVerb(), - 'mediaType' => $media->mime, - 'url' => $media->url(), - 'name' => $media->caption, - ]; - if($media->blurhash) { - $res['blurhash'] = $media->blurhash; - } - if($media->width) { - $res['width'] = $media->width; - } - if($media->height) { - $res['height'] = $media->height; - } - return $res; - })->toArray(), - 'tag' => $tags, - 'commentsEnabled' => (bool) !$status->comments_disabled, - 'capabilities' => [ - 'announce' => 'https://www.w3.org/ns/activitystreams#Public', - 'like' => 'https://www.w3.org/ns/activitystreams#Public', - 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public' - ], - 'location' => $status->place_id ? [ - 'type' => 'Place', - 'name' => $status->place->name, - 'longitude' => $status->place->long, - 'latitude' => $status->place->lat, - 'country' => $status->place->country - ] : null, - ] - ]; - } + $emojis = CustomEmoji::scan($status->caption, true) ?? []; + $emoji = array_merge($emojis, $mentions); + $tags = array_merge($emoji, $hashtags); + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + + return [ + '@context' => [ + 'https://w3id.org/security/v1', + 'https://www.w3.org/ns/activitystreams', + [ + 'Hashtag' => 'as:Hashtag', + 'sensitive' => 'as:sensitive', + 'schema' => 'http://schema.org/', + 'pixelfed' => 'http://pixelfed.org/ns#', + 'commentsEnabled' => [ + '@id' => 'pixelfed:commentsEnabled', + '@type' => 'schema:Boolean', + ], + 'capabilities' => [ + '@id' => 'pixelfed:capabilities', + '@container' => '@set', + ], + 'announce' => [ + '@id' => 'pixelfed:canAnnounce', + '@type' => '@id', + ], + 'like' => [ + '@id' => 'pixelfed:canLike', + '@type' => '@id', + ], + 'reply' => [ + '@id' => 'pixelfed:canReply', + '@type' => '@id', + ], + 'toot' => 'http://joinmastodon.org/ns#', + 'Emoji' => 'toot:Emoji', + 'blurhash' => 'toot:blurhash', + ], + ], + 'id' => $status->permalink(), + 'type' => 'Create', + 'actor' => $status->profile->permalink(), + 'published' => $status->created_at->toAtomString(), + 'to' => $status->scopeToAudience('to'), + 'cc' => $status->scopeToAudience('cc'), + 'object' => [ + 'id' => $status->url(), + 'type' => 'Note', + 'summary' => $status->is_nsfw ? $status->cw_summary : null, + 'content' => $content, + 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, + 'published' => $status->created_at->toAtomString(), + 'url' => $status->url(), + 'attributedTo' => $status->profile->permalink(), + 'to' => $status->scopeToAudience('to'), + 'cc' => $status->scopeToAudience('cc'), + 'sensitive' => (bool) $status->is_nsfw, + 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { + $res = [ + 'type' => $media->activityVerb(), + 'mediaType' => $media->mime, + 'url' => $media->url(), + 'name' => $media->caption, + ]; + if ($media->blurhash) { + $res['blurhash'] = $media->blurhash; + } + if ($media->width) { + $res['width'] = $media->width; + } + if ($media->height) { + $res['height'] = $media->height; + } + + return $res; + })->toArray(), + 'tag' => $tags, + 'commentsEnabled' => (bool) ! $status->comments_disabled, + 'capabilities' => [ + 'announce' => 'https://www.w3.org/ns/activitystreams#Public', + 'like' => 'https://www.w3.org/ns/activitystreams#Public', + 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public', + ], + 'location' => $status->place_id ? [ + 'type' => 'Place', + 'name' => $status->place->name, + 'longitude' => $status->place->long, + 'latitude' => $status->place->lat, + 'country' => $status->place->country, + ] : null, + ], + ]; + } } diff --git a/app/Transformer/ActivityPub/Verb/Note.php b/app/Transformer/ActivityPub/Verb/Note.php index 1350641d4..52656efec 100644 --- a/app/Transformer/ActivityPub/Verb/Note.php +++ b/app/Transformer/ActivityPub/Verb/Note.php @@ -2,133 +2,137 @@ namespace App\Transformer\ActivityPub\Verb; -use App\Status; -use League\Fractal; use App\Models\CustomEmoji; +use App\Status; +use App\Util\Lexer\Autolink; use Illuminate\Support\Str; +use League\Fractal; class Note extends Fractal\TransformerAbstract { - public function transform(Status $status) - { + public function transform(Status $status) + { - $mentions = $status->mentions->map(function ($mention) { - $webfinger = $mention->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - return [ - 'type' => 'Mention', - 'href' => $mention->permalink(), - 'name' => $name - ]; - })->toArray(); + $mentions = $status->mentions->map(function ($mention) { + $webfinger = $mention->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; - if($status->in_reply_to_id != null) { - $parent = $status->parent()->profile; - if($parent) { - $webfinger = $parent->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - $reply = [ - 'type' => 'Mention', - 'href' => $parent->permalink(), - 'name' => $name - ]; - array_push($mentions, $reply); - } - } - - $hashtags = $status->hashtags->map(function ($hashtag) { - return [ - 'type' => 'Hashtag', - 'href' => $hashtag->url(), - 'name' => "#{$hashtag->name}", - ]; - })->toArray(); + return [ + 'type' => 'Mention', + 'href' => $mention->permalink(), + 'name' => $name, + ]; + })->toArray(); - $emojis = CustomEmoji::scan($status->caption, true) ?? []; - $emoji = array_merge($emojis, $mentions); - $tags = array_merge($emoji, $hashtags); + if ($status->in_reply_to_id != null) { + $parent = $status->parent()->profile; + if ($parent) { + $webfinger = $parent->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; + $reply = [ + 'type' => 'Mention', + 'href' => $parent->permalink(), + 'name' => $name, + ]; + array_push($mentions, $reply); + } + } - return [ - '@context' => [ - 'https://w3id.org/security/v1', - 'https://www.w3.org/ns/activitystreams', - [ - 'Hashtag' => 'as:Hashtag', - 'sensitive' => 'as:sensitive', - 'schema' => 'http://schema.org/', - 'pixelfed' => 'http://pixelfed.org/ns#', - 'commentsEnabled' => [ - '@id' => 'pixelfed:commentsEnabled', - '@type' => 'schema:Boolean' - ], - 'capabilities' => [ - '@id' => 'pixelfed:capabilities', - '@container' => '@set' - ], - 'announce' => [ - '@id' => 'pixelfed:canAnnounce', - '@type' => '@id' - ], - 'like' => [ - '@id' => 'pixelfed:canLike', - '@type' => '@id' - ], - 'reply' => [ - '@id' => 'pixelfed:canReply', - '@type' => '@id' - ], - 'toot' => 'http://joinmastodon.org/ns#', - 'Emoji' => 'toot:Emoji', - 'blurhash' => 'toot:blurhash', - ] - ], - 'id' => $status->url(), - 'type' => 'Note', - 'summary' => $status->is_nsfw ? $status->cw_summary : null, - 'content' => $status->rendered ?? $status->caption, - 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, - 'published' => $status->created_at->toAtomString(), - 'url' => $status->url(), - 'attributedTo' => $status->profile->permalink(), - 'to' => $status->scopeToAudience('to'), - 'cc' => $status->scopeToAudience('cc'), - 'sensitive' => (bool) $status->is_nsfw, - 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { - $res = [ - 'type' => $media->activityVerb(), - 'mediaType' => $media->mime, - 'url' => $media->url(), - 'name' => $media->caption, - ]; - if($media->blurhash) { - $res['blurhash'] = $media->blurhash; - } - if($media->width) { - $res['width'] = $media->width; - } - if($media->height) { - $res['height'] = $media->height; - } - return $res; - })->toArray(), - 'tag' => $tags, - 'commentsEnabled' => (bool) !$status->comments_disabled, - 'capabilities' => [ - 'announce' => 'https://www.w3.org/ns/activitystreams#Public', - 'like' => 'https://www.w3.org/ns/activitystreams#Public', - 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public' - ], - 'location' => $status->place_id ? [ - 'type' => 'Place', - 'name' => $status->place->name, - 'longitude' => $status->place->long, - 'latitude' => $status->place->lat, - 'country' => $status->place->country - ] : null, - ]; - } + $hashtags = $status->hashtags->map(function ($hashtag) { + return [ + 'type' => 'Hashtag', + 'href' => $hashtag->url(), + 'name' => "#{$hashtag->name}", + ]; + })->toArray(); + + $emojis = CustomEmoji::scan($status->caption, true) ?? []; + $emoji = array_merge($emojis, $mentions); + $tags = array_merge($emoji, $hashtags); + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + + return [ + '@context' => [ + 'https://w3id.org/security/v1', + 'https://www.w3.org/ns/activitystreams', + [ + 'Hashtag' => 'as:Hashtag', + 'sensitive' => 'as:sensitive', + 'schema' => 'http://schema.org/', + 'pixelfed' => 'http://pixelfed.org/ns#', + 'commentsEnabled' => [ + '@id' => 'pixelfed:commentsEnabled', + '@type' => 'schema:Boolean', + ], + 'capabilities' => [ + '@id' => 'pixelfed:capabilities', + '@container' => '@set', + ], + 'announce' => [ + '@id' => 'pixelfed:canAnnounce', + '@type' => '@id', + ], + 'like' => [ + '@id' => 'pixelfed:canLike', + '@type' => '@id', + ], + 'reply' => [ + '@id' => 'pixelfed:canReply', + '@type' => '@id', + ], + 'toot' => 'http://joinmastodon.org/ns#', + 'Emoji' => 'toot:Emoji', + 'blurhash' => 'toot:blurhash', + ], + ], + 'id' => $status->url(), + 'type' => 'Note', + 'summary' => $status->is_nsfw ? $status->cw_summary : null, + 'content' => $content, + 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, + 'published' => $status->created_at->toAtomString(), + 'url' => $status->url(), + 'attributedTo' => $status->profile->permalink(), + 'to' => $status->scopeToAudience('to'), + 'cc' => $status->scopeToAudience('cc'), + 'sensitive' => (bool) $status->is_nsfw, + 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { + $res = [ + 'type' => $media->activityVerb(), + 'mediaType' => $media->mime, + 'url' => $media->url(), + 'name' => $media->caption, + ]; + if ($media->blurhash) { + $res['blurhash'] = $media->blurhash; + } + if ($media->width) { + $res['width'] = $media->width; + } + if ($media->height) { + $res['height'] = $media->height; + } + + return $res; + })->toArray(), + 'tag' => $tags, + 'commentsEnabled' => (bool) ! $status->comments_disabled, + 'capabilities' => [ + 'announce' => 'https://www.w3.org/ns/activitystreams#Public', + 'like' => 'https://www.w3.org/ns/activitystreams#Public', + 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public', + ], + 'location' => $status->place_id ? [ + 'type' => 'Place', + 'name' => $status->place->name, + 'longitude' => $status->place->long, + 'latitude' => $status->place->lat, + 'country' => $status->place->country, + ] : null, + ]; + } } diff --git a/app/Transformer/ActivityPub/Verb/Question.php b/app/Transformer/ActivityPub/Verb/Question.php index fb9313fb1..3d53ebcb5 100644 --- a/app/Transformer/ActivityPub/Verb/Question.php +++ b/app/Transformer/ActivityPub/Verb/Question.php @@ -3,104 +3,107 @@ namespace App\Transformer\ActivityPub\Verb; use App\Status; -use League\Fractal; +use App\Util\Lexer\Autolink; use Illuminate\Support\Str; +use League\Fractal; class Question extends Fractal\TransformerAbstract { - public function transform(Status $status) - { - $mentions = $status->mentions->map(function ($mention) { - $webfinger = $mention->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - return [ - 'type' => 'Mention', - 'href' => $mention->permalink(), - 'name' => $name - ]; - })->toArray(); + public function transform(Status $status) + { + $mentions = $status->mentions->map(function ($mention) { + $webfinger = $mention->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; - $hashtags = $status->hashtags->map(function ($hashtag) { - return [ - 'type' => 'Hashtag', - 'href' => $hashtag->url(), - 'name' => "#{$hashtag->name}", - ]; - })->toArray(); - $tags = array_merge($mentions, $hashtags); + return [ + 'type' => 'Mention', + 'href' => $mention->permalink(), + 'name' => $name, + ]; + })->toArray(); - return [ - '@context' => [ - 'https://w3id.org/security/v1', - 'https://www.w3.org/ns/activitystreams', - [ - 'Hashtag' => 'as:Hashtag', - 'sensitive' => 'as:sensitive', - 'schema' => 'http://schema.org/', - 'pixelfed' => 'http://pixelfed.org/ns#', - 'commentsEnabled' => [ - '@id' => 'pixelfed:commentsEnabled', - '@type' => 'schema:Boolean' - ], - 'capabilities' => [ - '@id' => 'pixelfed:capabilities', - '@container' => '@set' - ], - 'announce' => [ - '@id' => 'pixelfed:canAnnounce', - '@type' => '@id' - ], - 'like' => [ - '@id' => 'pixelfed:canLike', - '@type' => '@id' - ], - 'reply' => [ - '@id' => 'pixelfed:canReply', - '@type' => '@id' - ], - 'toot' => 'http://joinmastodon.org/ns#', - 'Emoji' => 'toot:Emoji' - ] - ], - 'id' => $status->url(), - 'type' => 'Question', - 'summary' => null, - 'content' => $status->rendered ?? $status->caption, - 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, - 'published' => $status->created_at->toAtomString(), - 'url' => $status->url(), - 'attributedTo' => $status->profile->permalink(), - 'to' => $status->scopeToAudience('to'), - 'cc' => $status->scopeToAudience('cc'), - 'sensitive' => (bool) $status->is_nsfw, - 'attachment' => [], - 'tag' => $tags, - 'commentsEnabled' => (bool) !$status->comments_disabled, - 'capabilities' => [ - 'announce' => 'https://www.w3.org/ns/activitystreams#Public', - 'like' => 'https://www.w3.org/ns/activitystreams#Public', - 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public' - ], - 'location' => $status->place_id ? [ - 'type' => 'Place', - 'name' => $status->place->name, - 'longitude' => $status->place->long, - 'latitude' => $status->place->lat, - 'country' => $status->place->country - ] : null, - 'endTime' => $status->poll->expires_at->toAtomString(), - 'oneOf' => collect($status->poll->poll_options)->map(function($option, $index) use($status) { - return [ - 'type' => 'Note', - 'name' => $option, - 'replies' => [ - 'type' => 'Collection', - 'totalItems' => $status->poll->cached_tallies[$index] - ] - ]; - }) - ]; - } + $hashtags = $status->hashtags->map(function ($hashtag) { + return [ + 'type' => 'Hashtag', + 'href' => $hashtag->url(), + 'name' => "#{$hashtag->name}", + ]; + })->toArray(); + $tags = array_merge($mentions, $hashtags); + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + + return [ + '@context' => [ + 'https://w3id.org/security/v1', + 'https://www.w3.org/ns/activitystreams', + [ + 'Hashtag' => 'as:Hashtag', + 'sensitive' => 'as:sensitive', + 'schema' => 'http://schema.org/', + 'pixelfed' => 'http://pixelfed.org/ns#', + 'commentsEnabled' => [ + '@id' => 'pixelfed:commentsEnabled', + '@type' => 'schema:Boolean', + ], + 'capabilities' => [ + '@id' => 'pixelfed:capabilities', + '@container' => '@set', + ], + 'announce' => [ + '@id' => 'pixelfed:canAnnounce', + '@type' => '@id', + ], + 'like' => [ + '@id' => 'pixelfed:canLike', + '@type' => '@id', + ], + 'reply' => [ + '@id' => 'pixelfed:canReply', + '@type' => '@id', + ], + 'toot' => 'http://joinmastodon.org/ns#', + 'Emoji' => 'toot:Emoji', + ], + ], + 'id' => $status->url(), + 'type' => 'Question', + 'summary' => null, + 'content' => $content, + 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, + 'published' => $status->created_at->toAtomString(), + 'url' => $status->url(), + 'attributedTo' => $status->profile->permalink(), + 'to' => $status->scopeToAudience('to'), + 'cc' => $status->scopeToAudience('cc'), + 'sensitive' => (bool) $status->is_nsfw, + 'attachment' => [], + 'tag' => $tags, + 'commentsEnabled' => (bool) ! $status->comments_disabled, + 'capabilities' => [ + 'announce' => 'https://www.w3.org/ns/activitystreams#Public', + 'like' => 'https://www.w3.org/ns/activitystreams#Public', + 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public', + ], + 'location' => $status->place_id ? [ + 'type' => 'Place', + 'name' => $status->place->name, + 'longitude' => $status->place->long, + 'latitude' => $status->place->lat, + 'country' => $status->place->country, + ] : null, + 'endTime' => $status->poll->expires_at->toAtomString(), + 'oneOf' => collect($status->poll->poll_options)->map(function ($option, $index) use ($status) { + return [ + 'type' => 'Note', + 'name' => $option, + 'replies' => [ + 'type' => 'Collection', + 'totalItems' => $status->poll->cached_tallies[$index], + ], + ]; + }), + ]; + } } diff --git a/app/Transformer/ActivityPub/Verb/UpdateNote.php b/app/Transformer/ActivityPub/Verb/UpdateNote.php index bdbb20c45..09d0b5a2b 100644 --- a/app/Transformer/ActivityPub/Verb/UpdateNote.php +++ b/app/Transformer/ActivityPub/Verb/UpdateNote.php @@ -2,132 +2,135 @@ namespace App\Transformer\ActivityPub\Verb; -use App\Status; -use League\Fractal; use App\Models\CustomEmoji; +use App\Status; +use App\Util\Lexer\Autolink; use Illuminate\Support\Str; +use League\Fractal; class UpdateNote extends Fractal\TransformerAbstract { - public function transform(Status $status) - { - $mentions = $status->mentions->map(function ($mention) { - $webfinger = $mention->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - return [ - 'type' => 'Mention', - 'href' => $mention->permalink(), - 'name' => $name - ]; - })->toArray(); + public function transform(Status $status) + { + $mentions = $status->mentions->map(function ($mention) { + $webfinger = $mention->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; - if($status->in_reply_to_id != null) { - $parent = $status->parent()->profile; - if($parent) { - $webfinger = $parent->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - $reply = [ - 'type' => 'Mention', - 'href' => $parent->permalink(), - 'name' => $name - ]; - $mentions = array_merge($reply, $mentions); - } - } + return [ + 'type' => 'Mention', + 'href' => $mention->permalink(), + 'name' => $name, + ]; + })->toArray(); - $hashtags = $status->hashtags->map(function ($hashtag) { - return [ - 'type' => 'Hashtag', - 'href' => $hashtag->url(), - 'name' => "#{$hashtag->name}", - ]; - })->toArray(); + if ($status->in_reply_to_id != null) { + $parent = $status->parent()->profile; + if ($parent) { + $webfinger = $parent->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; + $reply = [ + 'type' => 'Mention', + 'href' => $parent->permalink(), + 'name' => $name, + ]; + $mentions = array_merge($reply, $mentions); + } + } - $emojis = CustomEmoji::scan($status->caption, true) ?? []; - $emoji = array_merge($emojis, $mentions); - $tags = array_merge($emoji, $hashtags); + $hashtags = $status->hashtags->map(function ($hashtag) { + return [ + 'type' => 'Hashtag', + 'href' => $hashtag->url(), + 'name' => "#{$hashtag->name}", + ]; + })->toArray(); - $latestEdit = $status->edits()->latest()->first(); + $emojis = CustomEmoji::scan($status->caption, true) ?? []; + $emoji = array_merge($emojis, $mentions); + $tags = array_merge($emoji, $hashtags); - return [ - '@context' => [ - 'https://w3id.org/security/v1', - 'https://www.w3.org/ns/activitystreams', - [ - 'Hashtag' => 'as:Hashtag', - 'sensitive' => 'as:sensitive', - 'schema' => 'http://schema.org/', - 'pixelfed' => 'http://pixelfed.org/ns#', - 'commentsEnabled' => [ - '@id' => 'pixelfed:commentsEnabled', - '@type' => 'schema:Boolean' - ], - 'capabilities' => [ - '@id' => 'pixelfed:capabilities', - '@container' => '@set' - ], - 'announce' => [ - '@id' => 'pixelfed:canAnnounce', - '@type' => '@id' - ], - 'like' => [ - '@id' => 'pixelfed:canLike', - '@type' => '@id' - ], - 'reply' => [ - '@id' => 'pixelfed:canReply', - '@type' => '@id' - ], - 'toot' => 'http://joinmastodon.org/ns#', - 'Emoji' => 'toot:Emoji' - ] - ], - 'id' => $status->permalink('#updates/' . $latestEdit->id), - 'type' => 'Update', - 'actor' => $status->profile->permalink(), - 'published' => $latestEdit->created_at->toAtomString(), - 'to' => $status->scopeToAudience('to'), - 'cc' => $status->scopeToAudience('cc'), - 'object' => [ - 'id' => $status->url(), - 'type' => 'Note', - 'summary' => $status->is_nsfw ? $status->cw_summary : null, - 'content' => $status->rendered ?? $status->caption, - 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, - 'published' => $status->created_at->toAtomString(), - 'url' => $status->url(), - 'attributedTo' => $status->profile->permalink(), - 'to' => $status->scopeToAudience('to'), - 'cc' => $status->scopeToAudience('cc'), - 'sensitive' => (bool) $status->is_nsfw, - 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { - return [ - 'type' => $media->activityVerb(), - 'mediaType' => $media->mime, - 'url' => $media->url(), - 'name' => $media->caption, - ]; - })->toArray(), - 'tag' => $tags, - 'commentsEnabled' => (bool) !$status->comments_disabled, - 'updated' => $latestEdit->created_at->toAtomString(), - 'capabilities' => [ - 'announce' => 'https://www.w3.org/ns/activitystreams#Public', - 'like' => 'https://www.w3.org/ns/activitystreams#Public', - 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public' - ], - 'location' => $status->place_id ? [ - 'type' => 'Place', - 'name' => $status->place->name, - 'longitude' => $status->place->long, - 'latitude' => $status->place->lat, - 'country' => $status->place->country - ] : null, - ] - ]; - } + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $latestEdit = $status->edits()->latest()->first(); + + return [ + '@context' => [ + 'https://w3id.org/security/v1', + 'https://www.w3.org/ns/activitystreams', + [ + 'Hashtag' => 'as:Hashtag', + 'sensitive' => 'as:sensitive', + 'schema' => 'http://schema.org/', + 'pixelfed' => 'http://pixelfed.org/ns#', + 'commentsEnabled' => [ + '@id' => 'pixelfed:commentsEnabled', + '@type' => 'schema:Boolean', + ], + 'capabilities' => [ + '@id' => 'pixelfed:capabilities', + '@container' => '@set', + ], + 'announce' => [ + '@id' => 'pixelfed:canAnnounce', + '@type' => '@id', + ], + 'like' => [ + '@id' => 'pixelfed:canLike', + '@type' => '@id', + ], + 'reply' => [ + '@id' => 'pixelfed:canReply', + '@type' => '@id', + ], + 'toot' => 'http://joinmastodon.org/ns#', + 'Emoji' => 'toot:Emoji', + ], + ], + 'id' => $status->permalink('#updates/'.$latestEdit->id), + 'type' => 'Update', + 'actor' => $status->profile->permalink(), + 'published' => $latestEdit->created_at->toAtomString(), + 'to' => $status->scopeToAudience('to'), + 'cc' => $status->scopeToAudience('cc'), + 'object' => [ + 'id' => $status->url(), + 'type' => 'Note', + 'summary' => $status->is_nsfw ? $status->cw_summary : null, + 'content' => $content, + 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, + 'published' => $status->created_at->toAtomString(), + 'url' => $status->url(), + 'attributedTo' => $status->profile->permalink(), + 'to' => $status->scopeToAudience('to'), + 'cc' => $status->scopeToAudience('cc'), + 'sensitive' => (bool) $status->is_nsfw, + 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { + return [ + 'type' => $media->activityVerb(), + 'mediaType' => $media->mime, + 'url' => $media->url(), + 'name' => $media->caption, + ]; + })->toArray(), + 'tag' => $tags, + 'commentsEnabled' => (bool) ! $status->comments_disabled, + 'updated' => $latestEdit->created_at->toAtomString(), + 'capabilities' => [ + 'announce' => 'https://www.w3.org/ns/activitystreams#Public', + 'like' => 'https://www.w3.org/ns/activitystreams#Public', + 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public', + ], + 'location' => $status->place_id ? [ + 'type' => 'Place', + 'name' => $status->place->name, + 'longitude' => $status->place->long, + 'latitude' => $status->place->lat, + 'country' => $status->place->country, + ] : null, + ], + ]; + } } diff --git a/app/Transformer/Api/Mastodon/v1/StatusTransformer.php b/app/Transformer/Api/Mastodon/v1/StatusTransformer.php index bfbc3d58b..4516bd874 100644 --- a/app/Transformer/Api/Mastodon/v1/StatusTransformer.php +++ b/app/Transformer/Api/Mastodon/v1/StatusTransformer.php @@ -2,48 +2,50 @@ namespace App\Transformer\Api\Mastodon\v1; -use App\Status; -use League\Fractal; -use Cache; use App\Services\MediaService; use App\Services\ProfileService; use App\Services\StatusHashtagService; +use App\Status; +use App\Util\Lexer\Autolink; +use League\Fractal; class StatusTransformer extends Fractal\TransformerAbstract { - public function transform(Status $status) - { - return [ - 'id' => (string) $status->id, - 'created_at' => $status->created_at->toJSON(), - 'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null, - 'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null, - 'sensitive' => (bool) $status->is_nsfw, - 'spoiler_text' => $status->cw_summary ?? '', - 'visibility' => $status->visibility ?? $status->scope, - 'language' => 'en', - 'uri' => $status->permalink(''), - 'url' => $status->url(), - 'replies_count' => $status->reply_count ?? 0, - 'reblogs_count' => $status->reblogs_count ?? 0, - 'favourites_count' => $status->likes_count ?? 0, - 'reblogged' => $status->shared(), - 'favourited' => $status->liked(), - 'muted' => false, - 'bookmarked' => false, - 'content' => $status->rendered ?? $status->caption ?? '', - 'reblog' => null, - 'application' => [ - 'name' => 'web', - 'website' => null - ], - 'mentions' => [], - 'emojis' => [], - 'card' => null, - 'poll' => null, - 'media_attachments' => MediaService::get($status->id), - 'account' => ProfileService::get($status->profile_id, true), - 'tags' => StatusHashtagService::statusTags($status->id), - ]; - } + public function transform(Status $status) + { + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + + return [ + 'id' => (string) $status->id, + 'created_at' => $status->created_at->toJSON(), + 'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null, + 'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null, + 'sensitive' => (bool) $status->is_nsfw, + 'spoiler_text' => $status->cw_summary ?? '', + 'visibility' => $status->visibility ?? $status->scope, + 'language' => 'en', + 'uri' => $status->permalink(''), + 'url' => $status->url(), + 'replies_count' => $status->reply_count ?? 0, + 'reblogs_count' => $status->reblogs_count ?? 0, + 'favourites_count' => $status->likes_count ?? 0, + 'reblogged' => $status->shared(), + 'favourited' => $status->liked(), + 'muted' => false, + 'bookmarked' => false, + 'content' => $content, + 'reblog' => null, + 'application' => [ + 'name' => 'web', + 'website' => null, + ], + 'mentions' => [], + 'emojis' => [], + 'card' => null, + 'poll' => null, + 'media_attachments' => MediaService::get($status->id), + 'account' => ProfileService::get($status->profile_id, true), + 'tags' => StatusHashtagService::statusTags($status->id), + ]; + } } diff --git a/app/Transformer/Api/StatusStatelessTransformer.php b/app/Transformer/Api/StatusStatelessTransformer.php index 3c2c02d60..bf6e7597e 100644 --- a/app/Transformer/Api/StatusStatelessTransformer.php +++ b/app/Transformer/Api/StatusStatelessTransformer.php @@ -2,76 +2,73 @@ namespace App\Transformer\Api; -use App\Status; -use League\Fractal; -use Cache; +use App\Models\CustomEmoji; use App\Services\AccountService; use App\Services\HashidService; use App\Services\LikeService; use App\Services\MediaService; use App\Services\MediaTagService; -use App\Services\StatusService; +use App\Services\PollService; use App\Services\StatusHashtagService; use App\Services\StatusLabelService; use App\Services\StatusMentionService; -use App\Services\PollService; -use App\Models\CustomEmoji; +use App\Services\StatusService; +use App\Status; use App\Util\Lexer\Autolink; +use League\Fractal; class StatusStatelessTransformer extends Fractal\TransformerAbstract { - public function transform(Status $status) - { - $taggedPeople = MediaTagService::get($status->id); - $poll = $status->type === 'poll' ? PollService::get($status->id) : null; - $rendered = config('exp.autolink') ? - ( $status->caption ? Autolink::create()->autolink($status->caption) : '' ) : - ( $status->rendered ?? $status->caption ); + public function transform(Status $status) + { + $taggedPeople = MediaTagService::get($status->id); + $poll = $status->type === 'poll' ? PollService::get($status->id) : null; + $rendered = $status->caption ? Autolink::create()->autolink($status->caption) : null; - return [ - '_v' => 1, - 'id' => (string) $status->id, - //'gid' => $status->group_id ? (string) $status->group_id : null, - 'shortcode' => HashidService::encode($status->id), - 'uri' => $status->url(), - 'url' => $status->url(), - 'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null, - 'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null, - 'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id, false) : null, - 'content' => $rendered, - 'content_text' => $status->caption, - 'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)), - 'emojis' => CustomEmoji::scan($status->caption), - 'reblogs_count' => $status->reblogs_count ?? 0, - 'favourites_count' => $status->likes_count ?? 0, - 'reblogged' => null, - 'favourited' => null, - 'muted' => null, - 'sensitive' => (bool) $status->is_nsfw, - 'spoiler_text' => $status->cw_summary ?? '', - 'visibility' => $status->scope ?? $status->visibility, - 'application' => [ - 'name' => 'web', - 'website' => null - ], - 'language' => null, - 'mentions' => StatusMentionService::get($status->id), - 'pf_type' => $status->type ?? $status->setType(), - 'reply_count' => (int) $status->reply_count, - 'comments_disabled' => (bool) $status->comments_disabled, - 'thread' => false, - 'replies' => [], - 'parent' => [], - 'place' => $status->place, - 'local' => (bool) $status->local, - 'taggedPeople' => $taggedPeople, - 'label' => StatusLabelService::get($status), - 'liked_by' => LikeService::likedBy($status), - 'media_attachments' => MediaService::get($status->id), - 'account' => AccountService::get($status->profile_id, true), - 'tags' => StatusHashtagService::statusTags($status->id), - 'poll' => $poll, - 'edited_at' => $status->edited_at ? str_replace('+00:00', 'Z', $status->edited_at->format(DATE_RFC3339_EXTENDED)) : null, - ]; - } + return [ + '_v' => 1, + 'id' => (string) $status->id, + //'gid' => $status->group_id ? (string) $status->group_id : null, + 'shortcode' => HashidService::encode($status->id), + 'uri' => $status->url(), + 'url' => $status->url(), + 'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null, + 'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null, + 'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id, false) : null, + 'content' => $rendered, + 'content_text' => $status->caption, + 'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)), + 'emojis' => CustomEmoji::scan($status->caption), + 'reblogs_count' => $status->reblogs_count ?? 0, + 'favourites_count' => $status->likes_count ?? 0, + 'reblogged' => null, + 'favourited' => null, + 'muted' => null, + 'sensitive' => (bool) $status->is_nsfw, + 'spoiler_text' => $status->cw_summary ?? '', + 'visibility' => $status->scope ?? $status->visibility, + 'application' => [ + 'name' => 'web', + 'website' => null, + ], + 'language' => null, + 'mentions' => StatusMentionService::get($status->id), + 'pf_type' => $status->type ?? $status->setType(), + 'reply_count' => (int) $status->reply_count, + 'comments_disabled' => (bool) $status->comments_disabled, + 'thread' => false, + 'replies' => [], + 'parent' => [], + 'place' => $status->place, + 'local' => (bool) $status->local, + 'taggedPeople' => $taggedPeople, + 'label' => StatusLabelService::get($status), + 'liked_by' => LikeService::likedBy($status), + 'media_attachments' => MediaService::get($status->id), + 'account' => AccountService::get($status->profile_id, true), + 'tags' => StatusHashtagService::statusTags($status->id), + 'poll' => $poll, + 'edited_at' => $status->edited_at ? str_replace('+00:00', 'Z', $status->edited_at->format(DATE_RFC3339_EXTENDED)) : null, + ]; + } } diff --git a/app/Transformer/Api/StatusTransformer.php b/app/Transformer/Api/StatusTransformer.php index 22a840ce0..6f9364194 100644 --- a/app/Transformer/Api/StatusTransformer.php +++ b/app/Transformer/Api/StatusTransformer.php @@ -2,80 +2,75 @@ namespace App\Transformer\Api; -use App\Like; -use App\Status; -use League\Fractal; -use Cache; +use App\Models\CustomEmoji; +use App\Services\BookmarkService; use App\Services\HashidService; use App\Services\LikeService; use App\Services\MediaService; use App\Services\MediaTagService; -use App\Services\StatusService; +use App\Services\PollService; +use App\Services\ProfileService; use App\Services\StatusHashtagService; use App\Services\StatusLabelService; use App\Services\StatusMentionService; -use App\Services\ProfileService; -use Illuminate\Support\Str; -use App\Services\PollService; -use App\Models\CustomEmoji; -use App\Services\BookmarkService; +use App\Services\StatusService; +use App\Status; use App\Util\Lexer\Autolink; +use League\Fractal; class StatusTransformer extends Fractal\TransformerAbstract { - public function transform(Status $status) - { - $pid = request()->user()->profile_id; - $taggedPeople = MediaTagService::get($status->id); - $poll = $status->type === 'poll' ? PollService::get($status->id, $pid) : null; - $rendered = config('exp.autolink') ? - ( $status->caption ? Autolink::create()->autolink($status->caption) : '' ) : - ( $status->rendered ?? $status->caption ); + public function transform(Status $status) + { + $pid = request()->user()->profile_id; + $taggedPeople = MediaTagService::get($status->id); + $poll = $status->type === 'poll' ? PollService::get($status->id, $pid) : null; + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; - return [ - '_v' => 1, - 'id' => (string) $status->id, - 'shortcode' => HashidService::encode($status->id), - 'uri' => $status->url(), - 'url' => $status->url(), - 'in_reply_to_id' => (string) $status->in_reply_to_id, - 'in_reply_to_account_id' => (string) $status->in_reply_to_profile_id, - 'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id) : null, - 'content' => $rendered, - 'content_text' => $status->caption, - 'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)), - 'emojis' => CustomEmoji::scan($status->caption), - 'reblogs_count' => 0, - 'favourites_count' => $status->likes_count ?? 0, - 'reblogged' => $status->shared(), - 'favourited' => $status->liked(), - 'muted' => null, - 'sensitive' => (bool) $status->is_nsfw, - 'spoiler_text' => $status->cw_summary ?? '', - 'visibility' => $status->scope ?? $status->visibility, - 'application' => [ - 'name' => 'web', - 'website' => null - ], - 'language' => null, - 'mentions' => StatusMentionService::get($status->id), - 'pf_type' => $status->type ?? $status->setType(), - 'reply_count' => (int) $status->reply_count, - 'comments_disabled' => (bool) $status->comments_disabled, - 'thread' => false, - 'replies' => [], - 'parent' => [], - 'place' => $status->place, - 'local' => (bool) $status->local, - 'taggedPeople' => $taggedPeople, - 'label' => StatusLabelService::get($status), - 'liked_by' => LikeService::likedBy($status), - 'media_attachments' => MediaService::get($status->id), - 'account' => ProfileService::get($status->profile_id, true), - 'tags' => StatusHashtagService::statusTags($status->id), - 'poll' => $poll, - 'bookmarked' => BookmarkService::get($pid, $status->id), - 'edited_at' => $status->edited_at ? str_replace('+00:00', 'Z', $status->edited_at->format(DATE_RFC3339_EXTENDED)) : null, - ]; - } + return [ + '_v' => 1, + 'id' => (string) $status->id, + 'shortcode' => HashidService::encode($status->id), + 'uri' => $status->url(), + 'url' => $status->url(), + 'in_reply_to_id' => (string) $status->in_reply_to_id, + 'in_reply_to_account_id' => (string) $status->in_reply_to_profile_id, + 'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id) : null, + 'content' => $content, + 'content_text' => $status->caption, + 'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)), + 'emojis' => CustomEmoji::scan($status->caption), + 'reblogs_count' => 0, + 'favourites_count' => $status->likes_count ?? 0, + 'reblogged' => $status->shared(), + 'favourited' => $status->liked(), + 'muted' => null, + 'sensitive' => (bool) $status->is_nsfw, + 'spoiler_text' => $status->cw_summary ?? '', + 'visibility' => $status->scope ?? $status->visibility, + 'application' => [ + 'name' => 'web', + 'website' => null, + ], + 'language' => null, + 'mentions' => StatusMentionService::get($status->id), + 'pf_type' => $status->type ?? $status->setType(), + 'reply_count' => (int) $status->reply_count, + 'comments_disabled' => (bool) $status->comments_disabled, + 'thread' => false, + 'replies' => [], + 'parent' => [], + 'place' => $status->place, + 'local' => (bool) $status->local, + 'taggedPeople' => $taggedPeople, + 'label' => StatusLabelService::get($status), + 'liked_by' => LikeService::likedBy($status), + 'media_attachments' => MediaService::get($status->id), + 'account' => ProfileService::get($status->profile_id, true), + 'tags' => StatusHashtagService::statusTags($status->id), + 'poll' => $poll, + 'bookmarked' => BookmarkService::get($pid, $status->id), + 'edited_at' => $status->edited_at ? str_replace('+00:00', 'Z', $status->edited_at->format(DATE_RFC3339_EXTENDED)) : null, + ]; + } } From ecfc967429b44ed6654c44cad0c6e96691364bd0 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 02:56:58 -0700 Subject: [PATCH 013/108] Update PublicApiController --- app/Http/Controllers/PublicApiController.php | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/PublicApiController.php b/app/Http/Controllers/PublicApiController.php index b0e2efc40..1c9781935 100644 --- a/app/Http/Controllers/PublicApiController.php +++ b/app/Http/Controllers/PublicApiController.php @@ -31,8 +31,8 @@ class PublicApiController extends Controller public function __construct() { - $this->fractal = new Fractal\Manager(); - $this->fractal->setSerializer(new ArraySerializer()); + $this->fractal = new Fractal\Manager; + $this->fractal->setSerializer(new ArraySerializer); } protected function getUserData($user) @@ -74,7 +74,7 @@ class PublicApiController extends Controller abort_if(! in_array($cached['visibility'], ['public', 'unlisted']), 403); $res = ['status' => $cached]; } else { - $item = new Fractal\Resource\Item($status, new StatusStatelessTransformer()); + $item = new Fractal\Resource\Item($status, new StatusStatelessTransformer); $res = [ 'status' => $this->fractal->createData($item)->toArray(), ]; @@ -141,7 +141,7 @@ class PublicApiController extends Controller $replies = $status->comments() ->whereNull('reblog_of_id') ->whereIn('scope', $scope) - ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') + ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') ->where('id', '>=', $request->min_id) ->orderBy('id', 'desc') ->paginate($limit); @@ -150,7 +150,7 @@ class PublicApiController extends Controller $replies = $status->comments() ->whereNull('reblog_of_id') ->whereIn('scope', $scope) - ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') + ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') ->where('id', '<=', $request->max_id) ->orderBy('id', 'desc') ->paginate($limit); @@ -159,12 +159,12 @@ class PublicApiController extends Controller $replies = Status::whereInReplyToId($status->id) ->whereNull('reblog_of_id') ->whereIn('scope', $scope) - ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') + ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') ->orderBy('id', 'desc') ->paginate($limit); } - $resource = new Fractal\Resource\Collection($replies, new StatusStatelessTransformer(), 'data'); + $resource = new Fractal\Resource\Collection($replies, new StatusStatelessTransformer, 'data'); $resource->setPaginator(new IlluminatePaginatorAdapter($replies)); $res = $this->fractal->createData($resource)->toArray(); @@ -271,7 +271,6 @@ class PublicApiController extends Controller 'id', 'uri', 'caption', - 'rendered', 'profile_id', 'type', 'in_reply_to_id', @@ -405,7 +404,6 @@ class PublicApiController extends Controller 'id', 'uri', 'caption', - 'rendered', 'profile_id', 'type', 'in_reply_to_id', @@ -456,7 +454,6 @@ class PublicApiController extends Controller 'id', 'uri', 'caption', - 'rendered', 'profile_id', 'type', 'in_reply_to_id', From 9f7cc52c246653009aa04eee6adb59e2e9e6fc5f Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 03:00:47 -0700 Subject: [PATCH 014/108] Update ApiV1Controller, fix bookmark bug. Closes #5216 --- app/Http/Controllers/Api/ApiV1Controller.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index 56617d57f..e64759e24 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -1878,7 +1878,7 @@ class ApiV1Controller extends Controller $media->original_sha256 = $hash; $media->size = $photo->getSize(); $media->mime = $mime; - $media->caption = $request->input('description') ?? ""; + $media->caption = $request->input('description') ?? ''; $media->filter_class = $filterClass; $media->filter_name = $filterName; if ($license) { @@ -2106,7 +2106,7 @@ class ApiV1Controller extends Controller $media->original_sha256 = $hash; $media->size = $photo->getSize(); $media->mime = $mime; - $media->caption = $request->input('description') ?? ""; + $media->caption = $request->input('description') ?? ''; $media->filter_class = $filterClass; $media->filter_name = $filterName; if ($license) { @@ -3951,6 +3951,7 @@ class ApiV1Controller extends Controller abort_unless($request->user()->tokenCan('write'), 403); $status = Status::findOrFail($id); + $user = $request->user(); $pid = $request->user()->profile_id; $account = AccountService::get($status->profile_id); abort_if(isset($account['moved'], $account['moved']['id']), 422, 'Cannot bookmark a post from an account that has migrated'); @@ -3994,6 +3995,7 @@ class ApiV1Controller extends Controller $status = Status::findOrFail($id); $pid = $request->user()->profile_id; + $user = $request->user(); abort_if($user->has_roles && ! UserRoleService::can('can-bookmark', $user->id), 403, 'Invalid permissions for this action'); abort_if($status->in_reply_to_id || $status->reblog_of_id, 404); From 27960c3a0af3280388f239b5c0d79926c197c45a Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 03:01:10 -0700 Subject: [PATCH 015/108] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18e8c874f..bab1bf93f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.12.3...dev) - Update AP helpers, reject statuses with invalid dates ([960f3849](https://github.com/pixelfed/pixelfed/commit/960f3849)) - Update DirectMessage API, fix broken threading ([044d410c](https://github.com/pixelfed/pixelfed/commit/044d410c)) +- Update Status caption render logic ([fb8dbb95](https://github.com/pixelfed/pixelfed/commit/fb8dbb95)) +- Update ApiV1Controller, fix bookmark bug. Closes #5216 ([9f7cc52c](https://github.com/pixelfed/pixelfed/commit/9f7cc52c)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev) From 9eeb7b67414963a9851bbfd52f7486c0fe1c92d9 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 03:45:52 -0700 Subject: [PATCH 016/108] Update Status caption logic, stop storing duplicate html caption in db and defer to cached StatusService rendering --- app/Console/Commands/TransformImports.php | 50 +- app/Http/Controllers/Api/ApiV1Controller.php | 5 +- .../Controllers/Api/ApiV1Dot1Controller.php | 6 +- app/Http/Controllers/CommentController.php | 14 +- app/Http/Controllers/ComposeController.php | 14 +- .../Controllers/DirectMessageController.php | 12 +- .../Controllers/GroupFederationController.php | 170 ++-- .../Controllers/InternalApiController.php | 760 +++++++++--------- app/Http/Controllers/MicroController.php | 105 ++- app/Http/Controllers/SearchController.php | 13 +- .../Stories/StoryApiV1Controller.php | 3 +- .../Controllers/StoryComposeController.php | 4 +- app/Jobs/GroupPipeline/NewStatusPipeline.php | 181 ++--- app/Jobs/StatusPipeline/StatusEntityLexer.php | 7 +- .../StatusRemoteUpdatePipeline.php | 3 +- app/Services/Status/UpdateStatusService.php | 228 +++--- app/Util/ActivityPub/Helpers.php | 3 +- app/Util/ActivityPub/Inbox.php | 3 - 18 files changed, 776 insertions(+), 805 deletions(-) diff --git a/app/Console/Commands/TransformImports.php b/app/Console/Commands/TransformImports.php index a5a4dbb7a..6b6efa6e3 100644 --- a/app/Console/Commands/TransformImports.php +++ b/app/Console/Commands/TransformImports.php @@ -2,17 +2,16 @@ namespace App\Console\Commands; -use Illuminate\Console\Command; -use App\Models\ImportPost; -use App\Services\ImportService; use App\Media; +use App\Models\ImportPost; use App\Profile; -use App\Status; -use Storage; use App\Services\AccountService; +use App\Services\ImportService; use App\Services\MediaPathService; +use App\Status; +use Illuminate\Console\Command; use Illuminate\Support\Str; -use App\Util\Lexer\Autolink; +use Storage; class TransformImports extends Command { @@ -35,23 +34,24 @@ class TransformImports extends Command */ public function handle() { - if(!config('import.instagram.enabled')) { + if (! config('import.instagram.enabled')) { return; } $ips = ImportPost::whereNull('status_id')->where('skip_missing_media', '!=', true)->take(500)->get(); - if(!$ips->count()) { + if (! $ips->count()) { return; } - foreach($ips as $ip) { + foreach ($ips as $ip) { $id = $ip->user_id; $pid = $ip->profile_id; $profile = Profile::find($pid); - if(!$profile) { + if (! $profile) { $ip->skip_missing_media = true; $ip->save(); + continue; } @@ -63,39 +63,43 @@ class TransformImports extends Command ->where('creation_day', $ip->creation_day) ->exists(); - if($exists == true) { + if ($exists == true) { $ip->skip_missing_media = true; $ip->save(); + continue; } $idk = ImportService::getId($ip->user_id, $ip->creation_year, $ip->creation_month, $ip->creation_day); - if(!$idk) { + if (! $idk) { $ip->skip_missing_media = true; $ip->save(); + continue; } - if(Storage::exists('imports/' . $id . '/' . $ip->filename) === false) { + if (Storage::exists('imports/'.$id.'/'.$ip->filename) === false) { ImportService::clearAttempts($profile->id); ImportService::getPostCount($profile->id, true); $ip->skip_missing_media = true; $ip->save(); + continue; } $missingMedia = false; - foreach($ip->media as $ipm) { + foreach ($ip->media as $ipm) { $fileName = last(explode('/', $ipm['uri'])); - $og = 'imports/' . $id . '/' . $fileName; - if(!Storage::exists($og)) { + $og = 'imports/'.$id.'/'.$fileName; + if (! Storage::exists($og)) { $missingMedia = true; } } - if($missingMedia === true) { + if ($missingMedia === true) { $ip->skip_missing_media = true; $ip->save(); + continue; } @@ -103,7 +107,6 @@ class TransformImports extends Command $status = new Status; $status->profile_id = $pid; $status->caption = $caption; - $status->rendered = strlen(trim($caption)) ? Autolink::create()->autolink($ip->caption) : null; $status->type = $ip->post_type; $status->scope = 'unlisted'; @@ -112,20 +115,21 @@ class TransformImports extends Command $status->created_at = now()->parse($ip->creation_date); $status->save(); - foreach($ip->media as $ipm) { + foreach ($ip->media as $ipm) { $fileName = last(explode('/', $ipm['uri'])); $ext = last(explode('.', $fileName)); $basePath = MediaPathService::get($profile); - $og = 'imports/' . $id . '/' . $fileName; - if(!Storage::exists($og)) { + $og = 'imports/'.$id.'/'.$fileName; + if (! Storage::exists($og)) { $ip->skip_missing_media = true; $ip->save(); + continue; } $size = Storage::size($og); $mime = Storage::mimeType($og); - $newFile = Str::random(40) . '.' . $ext; - $np = $basePath . '/' . $newFile; + $newFile = Str::random(40).'.'.$ext; + $np = $basePath.'/'.$newFile; Storage::move($og, $np); $media = new Media; $media->profile_id = $pid; diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index e64759e24..0a2b88c2e 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -3490,8 +3490,7 @@ class ApiV1Controller extends Controller return []; } - $content = strip_tags($request->input('status')); - $rendered = Autolink::create()->autolink($content); + $content = $request->filled('status') ? strip_tags(Purify::clean($request->input('status'))) : null; $cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false); $spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null; @@ -3505,7 +3504,6 @@ class ApiV1Controller extends Controller $status = new Status; $status->caption = $content; - $status->rendered = $rendered; $status->scope = $visibility; $status->visibility = $visibility; $status->profile_id = $user->profile_id; @@ -3530,7 +3528,6 @@ class ApiV1Controller extends Controller if (! $in_reply_to_id) { $status = new Status; $status->caption = $content; - $status->rendered = $rendered; $status->profile_id = $user->profile_id; $status->is_nsfw = $cw; $status->cw_summary = $spoilerText; diff --git a/app/Http/Controllers/Api/ApiV1Dot1Controller.php b/app/Http/Controllers/Api/ApiV1Dot1Controller.php index 38550e5fe..2002b06a2 100644 --- a/app/Http/Controllers/Api/ApiV1Dot1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Dot1Controller.php @@ -37,7 +37,6 @@ use App\Status; use App\StatusArchived; use App\User; use App\UserSetting; -use App\Util\Lexer\Autolink; use App\Util\Lexer\RestrictedNames; use Cache; use DB; @@ -49,6 +48,7 @@ use Jenssegers\Agent\Agent; use League\Fractal; use League\Fractal\Serializer\ArraySerializer; use Mail; +use Purify; class ApiV1Dot1Controller extends Controller { @@ -1293,14 +1293,12 @@ class ApiV1Dot1Controller extends Controller return []; } - $content = strip_tags($request->input('status')); - $rendered = Autolink::create()->autolink($content); + $content = $request->filled('status') ? strip_tags(Purify::clean($request->input('status'))) : null; $cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false); $spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null; $status = new Status; $status->caption = $content; - $status->rendered = $rendered; $status->profile_id = $user->profile_id; $status->is_nsfw = $cw; $status->cw_summary = $spoilerText; diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php index 1dd985723..5e3f1ad7f 100644 --- a/app/Http/Controllers/CommentController.php +++ b/app/Http/Controllers/CommentController.php @@ -8,12 +8,12 @@ use App\Services\StatusService; use App\Status; use App\Transformer\Api\StatusTransformer; use App\UserFilter; -use App\Util\Lexer\Autolink; use Auth; use DB; use Illuminate\Http\Request; use League\Fractal; use League\Fractal\Serializer\ArraySerializer; +use Purify; class CommentController extends Controller { @@ -56,12 +56,10 @@ class CommentController extends Controller $reply = DB::transaction(function () use ($comment, $status, $profile, $nsfw) { $scope = $profile->is_private == true ? 'private' : 'public'; - $autolink = Autolink::create()->autolink($comment); - $reply = new Status(); + $reply = new Status; $reply->profile_id = $profile->id; $reply->is_nsfw = $nsfw; - $reply->caption = e($comment); - $reply->rendered = $autolink; + $reply->caption = Purify::clean($comment); $reply->in_reply_to_id = $status->id; $reply->in_reply_to_profile_id = $status->profile_id; $reply->scope = $scope; @@ -76,9 +74,9 @@ class CommentController extends Controller CommentPipeline::dispatch($status, $reply); if ($request->ajax()) { - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $entity = new Fractal\Resource\Item($reply, new StatusTransformer()); + $fractal = new Fractal\Manager; + $fractal->setSerializer(new ArraySerializer); + $entity = new Fractal\Resource\Item($reply, new StatusTransformer); $entity = $fractal->createData($entity)->toArray(); $response = [ 'code' => 200, diff --git a/app/Http/Controllers/ComposeController.php b/app/Http/Controllers/ComposeController.php index 480b9c8e4..4ea8e2267 100644 --- a/app/Http/Controllers/ComposeController.php +++ b/app/Http/Controllers/ComposeController.php @@ -25,7 +25,6 @@ use App\Services\UserStorageService; use App\Status; use App\Transformer\Api\MediaTransformer; use App\UserFilter; -use App\Util\Lexer\Autolink; use App\Util\Media\Filter; use App\Util\Media\License; use Auth; @@ -43,8 +42,8 @@ class ComposeController extends Controller public function __construct() { $this->middleware('auth'); - $this->fractal = new Fractal\Manager(); - $this->fractal->setSerializer(new ArraySerializer()); + $this->fractal = new Fractal\Manager; + $this->fractal->setSerializer(new ArraySerializer); } public function show(Request $request) @@ -112,14 +111,14 @@ class ComposeController extends Controller abort_if(MediaBlocklistService::exists($hash) == true, 451); - $media = new Media(); + $media = new Media; $media->status_id = null; $media->profile_id = $profile->id; $media->user_id = $user->id; $media->media_path = $path; $media->original_sha256 = $hash; $media->size = $photo->getSize(); - $media->caption = ""; + $media->caption = ''; $media->mime = $mime; $media->filter_class = $filterClass; $media->filter_name = $filterName; @@ -151,7 +150,7 @@ class ComposeController extends Controller $user->save(); Cache::forget($limitKey); - $resource = new Fractal\Resource\Item($media, new MediaTransformer()); + $resource = new Fractal\Resource\Item($media, new MediaTransformer); $res = $this->fractal->createData($resource)->toArray(); $res['preview_url'] = $preview_url; $res['url'] = $url; @@ -571,7 +570,6 @@ class ComposeController extends Controller } $status->caption = strip_tags($request->caption); - $status->rendered = Autolink::create()->autolink($status->caption); $status->scope = 'draft'; $status->visibility = 'draft'; $status->profile_id = $profile->id; @@ -693,7 +691,6 @@ class ComposeController extends Controller $status->visibility = $visibility; $status->scope = $visibility; $status->type = 'text'; - $status->rendered = Autolink::create()->autolink($status->caption); $status->entities = json_encode(array_merge([ 'timg' => [ 'version' => 0, @@ -806,7 +803,6 @@ class ComposeController extends Controller $status = new Status; $status->profile_id = $request->user()->profile_id; $status->caption = $request->input('caption'); - $status->rendered = Autolink::create()->autolink($status->caption); $status->visibility = 'draft'; $status->scope = 'draft'; $status->type = 'poll'; diff --git a/app/Http/Controllers/DirectMessageController.php b/app/Http/Controllers/DirectMessageController.php index 5b07f6adb..3f8d61036 100644 --- a/app/Http/Controllers/DirectMessageController.php +++ b/app/Http/Controllers/DirectMessageController.php @@ -22,6 +22,7 @@ use App\Services\WebfingerService; use App\Status; use App\UserFilter; use App\Util\ActivityPub\Helpers; +use App\Util\Lexer\Autolink; use Illuminate\Http\Request; use Illuminate\Support\Str; @@ -326,7 +327,6 @@ class DirectMessageController extends Controller $status = new Status; $status->profile_id = $profile->id; $status->caption = $msg; - $status->rendered = $msg; $status->visibility = 'direct'; $status->scope = 'direct'; $status->in_reply_to_profile_id = $recipient->id; @@ -636,7 +636,6 @@ class DirectMessageController extends Controller $status = new Status; $status->profile_id = $profile->id; $status->caption = null; - $status->rendered = null; $status->visibility = 'direct'; $status->scope = 'direct'; $status->in_reply_to_profile_id = $recipient->id; @@ -830,6 +829,11 @@ class DirectMessageController extends Controller { $profile = $dm->author; $url = $dm->recipient->sharedInbox ?? $dm->recipient->inbox_url; + $status = $dm->status; + + if (! $status) { + return; + } $tags = [ [ @@ -839,6 +843,8 @@ class DirectMessageController extends Controller ], ]; + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $body = [ '@context' => [ 'https://w3id.org/security/v1', @@ -854,7 +860,7 @@ class DirectMessageController extends Controller 'id' => $dm->status->url(), 'type' => 'Note', 'summary' => null, - 'content' => $dm->status->rendered ?? $dm->status->caption, + 'content' => $content, 'inReplyTo' => null, 'published' => $dm->status->created_at->toAtomString(), 'url' => $dm->status->url(), diff --git a/app/Http/Controllers/GroupFederationController.php b/app/Http/Controllers/GroupFederationController.php index 7f45f74a4..0e5879b01 100644 --- a/app/Http/Controllers/GroupFederationController.php +++ b/app/Http/Controllers/GroupFederationController.php @@ -2,102 +2,106 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Cache; use App\Models\Group; use App\Models\GroupPost; -use App\Status; use App\Models\InstanceActor; use App\Services\MediaService; +use App\Status; +use App\Util\Lexer\Autolink; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Cache; class GroupFederationController extends Controller { - public function getGroupObject(Request $request, $id) - { - $group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($id); - $res = $this->showGroupObject($group); - return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); - } + public function getGroupObject(Request $request, $id) + { + $group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($id); + $res = $this->showGroupObject($group); - public function showGroupObject($group) - { - return Cache::remember('ap:groups:object:' . $group->id, 3600, function() use($group) { - return [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $group->url(), - 'inbox' => $group->permalink('/inbox'), - 'name' => $group->name, - 'outbox' => $group->permalink('/outbox'), - 'summary' => $group->description, - 'type' => 'Group', - 'attributedTo' => [ - 'type' => 'Person', - 'id' => $group->admin->permalink() - ], - // 'endpoints' => [ - // 'sharedInbox' => config('app.url') . '/f/inbox' - // ], - 'preferredUsername' => 'gid_' . $group->id, - 'publicKey' => [ - 'id' => $group->permalink('#main-key'), - 'owner' => $group->permalink(), - 'publicKeyPem' => InstanceActor::first()->public_key, - ], - 'url' => $group->permalink() - ]; + return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } - if($group->metadata && isset($group->metadata['avatar'])) { - $res['icon'] = [ - 'type' => 'Image', - 'url' => $group->metadata['avatar']['url'] - ]; - } + public function showGroupObject($group) + { + return Cache::remember('ap:groups:object:'.$group->id, 3600, function () use ($group) { + return [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $group->url(), + 'inbox' => $group->permalink('/inbox'), + 'name' => $group->name, + 'outbox' => $group->permalink('/outbox'), + 'summary' => $group->description, + 'type' => 'Group', + 'attributedTo' => [ + 'type' => 'Person', + 'id' => $group->admin->permalink(), + ], + // 'endpoints' => [ + // 'sharedInbox' => config('app.url') . '/f/inbox' + // ], + 'preferredUsername' => 'gid_'.$group->id, + 'publicKey' => [ + 'id' => $group->permalink('#main-key'), + 'owner' => $group->permalink(), + 'publicKeyPem' => InstanceActor::first()->public_key, + ], + 'url' => $group->permalink(), + ]; - if($group->metadata && isset($group->metadata['header'])) { - $res['image'] = [ - 'type' => 'Image', - 'url' => $group->metadata['header']['url'] - ]; - } - ksort($res); - return $res; - }); - } + if ($group->metadata && isset($group->metadata['avatar'])) { + $res['icon'] = [ + 'type' => 'Image', + 'url' => $group->metadata['avatar']['url'], + ]; + } - public function getStatusObject(Request $request, $gid, $sid) - { - $group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($gid); - $gp = GroupPost::whereGroupId($gid)->findOrFail($sid); - $status = Status::findOrFail($gp->status_id); - // permission check + if ($group->metadata && isset($group->metadata['header'])) { + $res['image'] = [ + 'type' => 'Image', + 'url' => $group->metadata['header']['url'], + ]; + } + ksort($res); - $res = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $gp->url(), + return $res; + }); + } - 'type' => 'Note', + public function getStatusObject(Request $request, $gid, $sid) + { + $group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($gid); + $gp = GroupPost::whereGroupId($gid)->findOrFail($sid); + $status = Status::findOrFail($gp->status_id); + // permission check + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $res = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $gp->url(), - 'summary' => null, - 'content' => $status->rendered ?? $status->caption, - 'inReplyTo' => null, + 'type' => 'Note', - 'published' => $status->created_at->toAtomString(), - 'url' => $gp->url(), - 'attributedTo' => $status->profile->permalink(), - 'to' => [ - 'https://www.w3.org/ns/activitystreams#Public', - $group->permalink('/followers'), - ], - 'cc' => [], - 'sensitive' => (bool) $status->is_nsfw, - 'attachment' => MediaService::activitypub($status->id), - 'target' => [ - 'type' => 'Collection', - 'id' => $group->permalink('/wall'), - 'attributedTo' => $group->permalink() - ] - ]; - // ksort($res); - return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); - } + 'summary' => null, + 'content' => $content, + 'inReplyTo' => null, + + 'published' => $status->created_at->toAtomString(), + 'url' => $gp->url(), + 'attributedTo' => $status->profile->permalink(), + 'to' => [ + 'https://www.w3.org/ns/activitystreams#Public', + $group->permalink('/followers'), + ], + 'cc' => [], + 'sensitive' => (bool) $status->is_nsfw, + 'attachment' => MediaService::activitypub($status->id), + 'target' => [ + 'type' => 'Collection', + 'id' => $group->permalink('/wall'), + 'attributedTo' => $group->permalink(), + ], + ]; + + // ksort($res); + return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } } diff --git a/app/Http/Controllers/InternalApiController.php b/app/Http/Controllers/InternalApiController.php index 299c9ceb6..e2795d6fc 100644 --- a/app/Http/Controllers/InternalApiController.php +++ b/app/Http/Controllers/InternalApiController.php @@ -2,442 +2,424 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; -use App\{ - AccountInterstitial, - Bookmark, - DirectMessage, - DiscoverCategory, - Hashtag, - Follower, - Like, - Media, - MediaTag, - Notification, - Profile, - StatusHashtag, - Status, - User, - UserFilter, -}; -use Auth,Cache; -use Illuminate\Support\Facades\Redis; -use Carbon\Carbon; -use League\Fractal; -use App\Transformer\Api\{ - AccountTransformer, - StatusTransformer, - // StatusMediaContainerTransformer, -}; -use App\Util\Media\Filter; -use App\Jobs\StatusPipeline\NewStatusPipeline; +use App\AccountInterstitial; +use App\Bookmark; +use App\DirectMessage; +use App\DiscoverCategory; +use App\Follower; use App\Jobs\ModPipeline\HandleSpammerPipeline; -use League\Fractal\Serializer\ArraySerializer; -use League\Fractal\Pagination\IlluminatePaginatorAdapter; -use Illuminate\Validation\Rule; -use Illuminate\Support\Str; -use App\Services\MediaTagService; +use App\Profile; +use App\Services\BookmarkService; +use App\Services\DiscoverService; use App\Services\ModLogService; use App\Services\PublicTimelineService; -use App\Services\SnowflakeService; use App\Services\StatusService; use App\Services\UserFilterService; -use App\Services\DiscoverService; -use App\Services\BookmarkService; +use App\Status; // StatusMediaContainerTransformer, +use App\Transformer\Api\StatusTransformer; +use App\User; +use Auth; +use Cache; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Redis; +use Illuminate\Validation\Rule; +use League\Fractal; +use League\Fractal\Serializer\ArraySerializer; class InternalApiController extends Controller { - protected $fractal; + protected $fractal; - public function __construct() - { - $this->middleware('auth'); - $this->fractal = new Fractal\Manager(); - $this->fractal->setSerializer(new ArraySerializer()); - } + public function __construct() + { + $this->middleware('auth'); + $this->fractal = new Fractal\Manager; + $this->fractal->setSerializer(new ArraySerializer); + } - // deprecated v2 compose api - public function compose(Request $request) - { - return redirect('/'); - } + // deprecated v2 compose api + public function compose(Request $request) + { + return redirect('/'); + } - // deprecated - public function discover(Request $request) - { - return; - } + // deprecated + public function discover(Request $request) {} - public function discoverPosts(Request $request) - { - $pid = $request->user()->profile_id; - $filters = UserFilterService::filters($pid); - $forYou = DiscoverService::getForYou(); - $posts = $forYou->take(50)->map(function($post) { - return StatusService::get($post); - }) - ->filter(function($post) use($filters) { - return $post && - isset($post['account']) && - isset($post['account']['id']) && - !in_array($post['account']['id'], $filters); - }) - ->take(12) - ->values(); - return response()->json(compact('posts')); - } + public function discoverPosts(Request $request) + { + $pid = $request->user()->profile_id; + $filters = UserFilterService::filters($pid); + $forYou = DiscoverService::getForYou(); + $posts = $forYou->take(50)->map(function ($post) { + return StatusService::get($post); + }) + ->filter(function ($post) use ($filters) { + return $post && + isset($post['account']) && + isset($post['account']['id']) && + ! in_array($post['account']['id'], $filters); + }) + ->take(12) + ->values(); - public function directMessage(Request $request, $profileId, $threadId) - { - $profile = Auth::user()->profile; + return response()->json(compact('posts')); + } - if($profileId != $profile->id) { - abort(403); - } + public function directMessage(Request $request, $profileId, $threadId) + { + $profile = Auth::user()->profile; - $msg = DirectMessage::whereToId($profile->id) - ->orWhere('from_id',$profile->id) - ->findOrFail($threadId); + if ($profileId != $profile->id) { + abort(403); + } - $thread = DirectMessage::with('status')->whereIn('to_id', [$profile->id, $msg->from_id]) - ->whereIn('from_id', [$profile->id,$msg->from_id]) - ->orderBy('created_at', 'asc') - ->paginate(30); + $msg = DirectMessage::whereToId($profile->id) + ->orWhere('from_id', $profile->id) + ->findOrFail($threadId); - return response()->json(compact('msg', 'profile', 'thread'), 200, [], JSON_PRETTY_PRINT); - } + $thread = DirectMessage::with('status')->whereIn('to_id', [$profile->id, $msg->from_id]) + ->whereIn('from_id', [$profile->id, $msg->from_id]) + ->orderBy('created_at', 'asc') + ->paginate(30); - public function statusReplies(Request $request, int $id) - { - $this->validate($request, [ - 'limit' => 'nullable|int|min:1|max:6' - ]); - $parent = Status::whereScope('public')->findOrFail($id); - $limit = $request->input('limit') ?? 3; - $children = Status::whereInReplyToId($parent->id) - ->orderBy('created_at', 'desc') - ->take($limit) - ->get(); - $resource = new Fractal\Resource\Collection($children, new StatusTransformer()); - $res = $this->fractal->createData($resource)->toArray(); + return response()->json(compact('msg', 'profile', 'thread'), 200, [], JSON_PRETTY_PRINT); + } - return response()->json($res); - } + public function statusReplies(Request $request, int $id) + { + $this->validate($request, [ + 'limit' => 'nullable|int|min:1|max:6', + ]); + $parent = Status::whereScope('public')->findOrFail($id); + $limit = $request->input('limit') ?? 3; + $children = Status::whereInReplyToId($parent->id) + ->orderBy('created_at', 'desc') + ->take($limit) + ->get(); + $resource = new Fractal\Resource\Collection($children, new StatusTransformer); + $res = $this->fractal->createData($resource)->toArray(); - public function stories(Request $request) - { + return response()->json($res); + } - } + public function stories(Request $request) {} - public function discoverCategories(Request $request) - { - $categories = DiscoverCategory::whereActive(true)->orderBy('order')->take(10)->get(); - $res = $categories->map(function($item) { - return [ - 'name' => $item->name, - 'url' => $item->url(), - 'thumb' => $item->thumb() - ]; - }); - return response()->json($res); - } + public function discoverCategories(Request $request) + { + $categories = DiscoverCategory::whereActive(true)->orderBy('order')->take(10)->get(); + $res = $categories->map(function ($item) { + return [ + 'name' => $item->name, + 'url' => $item->url(), + 'thumb' => $item->thumb(), + ]; + }); - public function modAction(Request $request) - { - abort_unless(Auth::user()->is_admin, 400); - $this->validate($request, [ - 'action' => [ - 'required', - 'string', - Rule::in([ - 'addcw', - 'remcw', - 'unlist', - 'spammer' - ]) - ], - 'item_id' => 'required|integer|min:1', - 'item_type' => [ - 'required', - 'string', - Rule::in(['profile', 'status']) - ] - ]); + return response()->json($res); + } - $action = $request->input('action'); - $item_id = $request->input('item_id'); - $item_type = $request->input('item_type'); + public function modAction(Request $request) + { + abort_unless(Auth::user()->is_admin, 400); + $this->validate($request, [ + 'action' => [ + 'required', + 'string', + Rule::in([ + 'addcw', + 'remcw', + 'unlist', + 'spammer', + ]), + ], + 'item_id' => 'required|integer|min:1', + 'item_type' => [ + 'required', + 'string', + Rule::in(['profile', 'status']), + ], + ]); - $status = Status::findOrFail($item_id); - $author = User::whereProfileId($status->profile_id)->first(); - abort_if($author && $author->is_admin, 422, 'Cannot moderate administrator accounts'); + $action = $request->input('action'); + $item_id = $request->input('item_id'); + $item_type = $request->input('item_type'); - switch($action) { - case 'addcw': - $status->is_nsfw = true; - $status->save(); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\Status::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'cw', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); + $status = Status::findOrFail($item_id); + $author = User::whereProfileId($status->profile_id)->first(); + abort_if($author && $author->is_admin, 422, 'Cannot moderate administrator accounts'); - if($status->uri == null) { - $media = $status->media; - $ai = new AccountInterstitial; - $ai->user_id = $status->profile->user_id; - $ai->type = 'post.cw'; - $ai->view = 'account.moderation.post.cw'; - $ai->item_type = 'App\Status'; - $ai->item_id = $status->id; - $ai->has_media = (bool) $media->count(); - $ai->blurhash = $media->count() ? $media->first()->blurhash : null; - $ai->meta = json_encode([ - 'caption' => $status->caption, - 'created_at' => $status->created_at, - 'type' => $status->type, - 'url' => $status->url(), - 'is_nsfw' => $status->is_nsfw, - 'scope' => $status->scope, - 'reblog' => $status->reblog_of_id, - 'likes_count' => $status->likes_count, - 'reblogs_count' => $status->reblogs_count, - ]); - $ai->save(); + switch ($action) { + case 'addcw': + $status->is_nsfw = true; + $status->save(); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\Status::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'cw', + 'message' => 'Success!', + ]) + ->accessLevel('admin') + ->save(); - $u = $status->profile->user; - $u->has_interstitial = true; - $u->save(); - } - break; + if ($status->uri == null) { + $media = $status->media; + $ai = new AccountInterstitial; + $ai->user_id = $status->profile->user_id; + $ai->type = 'post.cw'; + $ai->view = 'account.moderation.post.cw'; + $ai->item_type = 'App\Status'; + $ai->item_id = $status->id; + $ai->has_media = (bool) $media->count(); + $ai->blurhash = $media->count() ? $media->first()->blurhash : null; + $ai->meta = json_encode([ + 'caption' => $status->caption, + 'created_at' => $status->created_at, + 'type' => $status->type, + 'url' => $status->url(), + 'is_nsfw' => $status->is_nsfw, + 'scope' => $status->scope, + 'reblog' => $status->reblog_of_id, + 'likes_count' => $status->likes_count, + 'reblogs_count' => $status->reblogs_count, + ]); + $ai->save(); - case 'remcw': - $status->is_nsfw = false; - $status->save(); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\Status::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'remove_cw', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); - if($status->uri == null) { - $ai = AccountInterstitial::whereUserId($status->profile->user_id) - ->whereType('post.cw') - ->whereItemId($status->id) - ->whereItemType('App\Status') - ->first(); - $ai->delete(); - } - break; + $u = $status->profile->user; + $u->has_interstitial = true; + $u->save(); + } + break; - case 'unlist': - $status->scope = $status->visibility = 'unlisted'; - $status->save(); - PublicTimelineService::del($status->id); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\Status::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'unlist', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); + case 'remcw': + $status->is_nsfw = false; + $status->save(); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\Status::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'remove_cw', + 'message' => 'Success!', + ]) + ->accessLevel('admin') + ->save(); + if ($status->uri == null) { + $ai = AccountInterstitial::whereUserId($status->profile->user_id) + ->whereType('post.cw') + ->whereItemId($status->id) + ->whereItemType('App\Status') + ->first(); + $ai->delete(); + } + break; - if($status->uri == null) { - $media = $status->media; - $ai = new AccountInterstitial; - $ai->user_id = $status->profile->user_id; - $ai->type = 'post.unlist'; - $ai->view = 'account.moderation.post.unlist'; - $ai->item_type = 'App\Status'; - $ai->item_id = $status->id; - $ai->has_media = (bool) $media->count(); - $ai->blurhash = $media->count() ? $media->first()->blurhash : null; - $ai->meta = json_encode([ - 'caption' => $status->caption, - 'created_at' => $status->created_at, - 'type' => $status->type, - 'url' => $status->url(), - 'is_nsfw' => $status->is_nsfw, - 'scope' => $status->scope, - 'reblog' => $status->reblog_of_id, - 'likes_count' => $status->likes_count, - 'reblogs_count' => $status->reblogs_count, - ]); - $ai->save(); + case 'unlist': + $status->scope = $status->visibility = 'unlisted'; + $status->save(); + PublicTimelineService::del($status->id); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\Status::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'unlist', + 'message' => 'Success!', + ]) + ->accessLevel('admin') + ->save(); - $u = $status->profile->user; - $u->has_interstitial = true; - $u->save(); - } - break; + if ($status->uri == null) { + $media = $status->media; + $ai = new AccountInterstitial; + $ai->user_id = $status->profile->user_id; + $ai->type = 'post.unlist'; + $ai->view = 'account.moderation.post.unlist'; + $ai->item_type = 'App\Status'; + $ai->item_id = $status->id; + $ai->has_media = (bool) $media->count(); + $ai->blurhash = $media->count() ? $media->first()->blurhash : null; + $ai->meta = json_encode([ + 'caption' => $status->caption, + 'created_at' => $status->created_at, + 'type' => $status->type, + 'url' => $status->url(), + 'is_nsfw' => $status->is_nsfw, + 'scope' => $status->scope, + 'reblog' => $status->reblog_of_id, + 'likes_count' => $status->likes_count, + 'reblogs_count' => $status->reblogs_count, + ]); + $ai->save(); - case 'spammer': - HandleSpammerPipeline::dispatch($status->profile); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\User::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'spammer', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); - break; - } + $u = $status->profile->user; + $u->has_interstitial = true; + $u->save(); + } + break; - StatusService::del($status->id, true); - return ['msg' => 200]; - } + case 'spammer': + HandleSpammerPipeline::dispatch($status->profile); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\User::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'spammer', + 'message' => 'Success!', + ]) + ->accessLevel('admin') + ->save(); + break; + } - public function composePost(Request $request) - { - abort(400, 'Endpoint deprecated'); - } + StatusService::del($status->id, true); - public function bookmarks(Request $request) - { - $pid = $request->user()->profile_id; - $res = Bookmark::whereProfileId($pid) - ->orderByDesc('created_at') - ->simplePaginate(10) - ->map(function($bookmark) use($pid) { - $status = StatusService::get($bookmark->status_id, false); - if(!$status) { - return false; - } - $status['bookmarked_at'] = str_replace('+00:00', 'Z', $bookmark->created_at->format(DATE_RFC3339_EXTENDED)); + return ['msg' => 200]; + } - if($status) { - BookmarkService::add($pid, $status['id']); - } - return $status; - }) - ->filter(function($bookmark) { - return $bookmark && isset($bookmark['id']); - }) - ->values(); + public function composePost(Request $request) + { + abort(400, 'Endpoint deprecated'); + } - return response()->json($res); - } + public function bookmarks(Request $request) + { + $pid = $request->user()->profile_id; + $res = Bookmark::whereProfileId($pid) + ->orderByDesc('created_at') + ->simplePaginate(10) + ->map(function ($bookmark) use ($pid) { + $status = StatusService::get($bookmark->status_id, false); + if (! $status) { + return false; + } + $status['bookmarked_at'] = str_replace('+00:00', 'Z', $bookmark->created_at->format(DATE_RFC3339_EXTENDED)); - public function accountStatuses(Request $request, $id) - { - $this->validate($request, [ - 'only_media' => 'nullable', - 'pinned' => 'nullable', - 'exclude_replies' => 'nullable', - 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'since_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'limit' => 'nullable|integer|min:1|max:24' - ]); + if ($status) { + BookmarkService::add($pid, $status['id']); + } - $profile = Profile::whereNull('status')->findOrFail($id); + return $status; + }) + ->filter(function ($bookmark) { + return $bookmark && isset($bookmark['id']); + }) + ->values(); - $limit = $request->limit ?? 9; - $max_id = $request->max_id; - $min_id = $request->min_id; - $scope = $request->only_media == true ? - ['photo', 'photo:album', 'video', 'video:album'] : - ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply']; + return response()->json($res); + } - if($profile->is_private) { - if(!Auth::check()) { - return response()->json([]); - } - $pid = Auth::user()->profile->id; - $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - return $following->push($pid)->toArray(); - }); - $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : []; - } else { - if(Auth::check()) { - $pid = Auth::user()->profile->id; - $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - return $following->push($pid)->toArray(); - }); - $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted']; - } else { - $visibility = ['public', 'unlisted']; - } - } + public function accountStatuses(Request $request, $id) + { + $this->validate($request, [ + 'only_media' => 'nullable', + 'pinned' => 'nullable', + 'exclude_replies' => 'nullable', + 'max_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX, + 'since_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX, + 'min_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX, + 'limit' => 'nullable|integer|min:1|max:24', + ]); - $dir = $min_id ? '>' : '<'; - $id = $min_id ?? $max_id; - $timeline = Status::select( - 'id', - 'uri', - 'caption', - 'rendered', - 'profile_id', - 'type', - 'in_reply_to_id', - 'reblog_of_id', - 'is_nsfw', - 'likes_count', - 'reblogs_count', - 'scope', - 'local', - 'created_at', - 'updated_at' - )->whereProfileId($profile->id) - ->whereIn('type', $scope) - ->where('id', $dir, $id) - ->whereIn('visibility', $visibility) - ->latest() - ->limit($limit) - ->get(); + $profile = Profile::whereNull('status')->findOrFail($id); - $resource = new Fractal\Resource\Collection($timeline, new StatusTransformer()); - $res = $this->fractal->createData($resource)->toArray(); + $limit = $request->limit ?? 9; + $max_id = $request->max_id; + $min_id = $request->min_id; + $scope = $request->only_media == true ? + ['photo', 'photo:album', 'video', 'video:album'] : + ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply']; - return response()->json($res); - } + if ($profile->is_private) { + if (! Auth::check()) { + return response()->json([]); + } + $pid = Auth::user()->profile->id; + $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) { + $following = Follower::whereProfileId($pid)->pluck('following_id'); - public function remoteProfile(Request $request, $id) - { - return redirect('/i/web/profile/' . $id); - } + return $following->push($pid)->toArray(); + }); + $visibility = in_array($profile->id, $following) == true ? ['public', 'unlisted', 'private'] : []; + } else { + if (Auth::check()) { + $pid = Auth::user()->profile->id; + $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) { + $following = Follower::whereProfileId($pid)->pluck('following_id'); - public function remoteStatus(Request $request, $profileId, $statusId) - { - return redirect('/i/web/post/' . $statusId); - } + return $following->push($pid)->toArray(); + }); + $visibility = in_array($profile->id, $following) == true ? ['public', 'unlisted', 'private'] : ['public', 'unlisted']; + } else { + $visibility = ['public', 'unlisted']; + } + } - public function requestEmailVerification(Request $request) - { - $pid = $request->user()->profile_id; - $exists = Redis::sismember('email:manual', $pid); - return view('account.email.request_verification', compact('exists')); - } + $dir = $min_id ? '>' : '<'; + $id = $min_id ?? $max_id; + $timeline = Status::select( + 'id', + 'uri', + 'caption', + 'profile_id', + 'type', + 'in_reply_to_id', + 'reblog_of_id', + 'is_nsfw', + 'likes_count', + 'reblogs_count', + 'scope', + 'local', + 'created_at', + 'updated_at' + )->whereProfileId($profile->id) + ->whereIn('type', $scope) + ->where('id', $dir, $id) + ->whereIn('visibility', $visibility) + ->latest() + ->limit($limit) + ->get(); - public function requestEmailVerificationStore(Request $request) - { - $pid = $request->user()->profile_id; - Redis::sadd('email:manual', $pid); - return redirect('/i/verify-email')->with(['status' => 'Successfully sent manual verification request!']); - } + $resource = new Fractal\Resource\Collection($timeline, new StatusTransformer); + $res = $this->fractal->createData($resource)->toArray(); + + return response()->json($res); + } + + public function remoteProfile(Request $request, $id) + { + return redirect('/i/web/profile/'.$id); + } + + public function remoteStatus(Request $request, $profileId, $statusId) + { + return redirect('/i/web/post/'.$statusId); + } + + public function requestEmailVerification(Request $request) + { + $pid = $request->user()->profile_id; + $exists = Redis::sismember('email:manual', $pid); + + return view('account.email.request_verification', compact('exists')); + } + + public function requestEmailVerificationStore(Request $request) + { + $pid = $request->user()->profile_id; + Redis::sadd('email:manual', $pid); + + return redirect('/i/verify-email')->with(['status' => 'Successfully sent manual verification request!']); + } } diff --git a/app/Http/Controllers/MicroController.php b/app/Http/Controllers/MicroController.php index 420083f0f..62e0ae969 100644 --- a/app/Http/Controllers/MicroController.php +++ b/app/Http/Controllers/MicroController.php @@ -2,66 +2,65 @@ namespace App\Http\Controllers; +use App\Status; +use Auth; +use DB; use Illuminate\Http\Request; -use App\{ - Profile, - Status, -}; -use Auth, DB, Purify; use Illuminate\Validation\Rule; class MicroController extends Controller { - public function __construct() - { - $this->middleware('auth'); - } + public function __construct() + { + $this->middleware('auth'); + } - public function composeText(Request $request) - { - $this->validate($request, [ - 'type' => [ - 'required', - 'string', - Rule::in(['text']) - ], - 'title' => 'nullable|string|max:140', - 'content' => 'required|string|max:500', - 'visibility' => [ - 'required', - 'string', - Rule::in([ - 'public', - 'unlisted', - 'private', - 'draft' - ]) - ] - ]); - $profile = Auth::user()->profile; - $title = $request->input('title'); - $content = $request->input('content'); - $visibility = $request->input('visibility'); + public function composeText(Request $request) + { + $this->validate($request, [ + 'type' => [ + 'required', + 'string', + Rule::in(['text']), + ], + 'title' => 'nullable|string|max:140', + 'content' => 'required|string|max:500', + 'visibility' => [ + 'required', + 'string', + Rule::in([ + 'public', + 'unlisted', + 'private', + 'draft', + ]), + ], + ]); + $profile = Auth::user()->profile; + $title = $request->input('title'); + $content = $request->input('content'); + $visibility = $request->input('visibility'); - $status = DB::transaction(function() use($profile, $content, $visibility, $title) { - $status = new Status; - $status->type = 'text'; - $status->profile_id = $profile->id; - $status->caption = strip_tags($content); - $status->rendered = Purify::clean($content); - $status->is_nsfw = false; + $status = DB::transaction(function () use ($profile, $content, $visibility, $title) { + $status = new Status; + $status->type = 'text'; + $status->profile_id = $profile->id; + $status->caption = strip_tags($content); + $status->is_nsfw = false; - // TODO: remove deprecated visibility in favor of scope - $status->visibility = $visibility; - $status->scope = $visibility; - $status->entities = json_encode(['title'=>$title]); - $status->save(); - return $status; - }); + // TODO: remove deprecated visibility in favor of scope + $status->visibility = $visibility; + $status->scope = $visibility; + $status->entities = json_encode(['title' => $title]); + $status->save(); - $fractal = new \League\Fractal\Manager(); - $fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer()); - $s = new \League\Fractal\Resource\Item($status, new \App\Transformer\Api\StatusTransformer()); - return $fractal->createData($s)->toArray(); - } + return $status; + }); + + $fractal = new \League\Fractal\Manager; + $fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer); + $s = new \League\Fractal\Resource\Item($status, new \App\Transformer\Api\StatusTransformer); + + return $fractal->createData($s)->toArray(); + } } diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 9388d3abd..8e4296e5b 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -8,6 +8,7 @@ use App\Profile; use App\Services\WebfingerService; use App\Status; use App\Util\ActivityPub\Helpers; +use App\Util\Lexer\Autolink; use Auth; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; @@ -320,17 +321,21 @@ class SearchController extends Controller if (Status::whereUri($tag)->whereLocal(false)->exists()) { $item = Status::whereUri($tag)->first(); + if (! $item) { + return; + } $media = $item->firstMedia(); $url = null; if ($media) { $url = $media->remote_url; } + $content = $item->caption ? Autolink::create()->autolink($item->caption) : null; $this->tokens['posts'] = [[ 'count' => 0, 'url' => "/i/web/post/_/$item->profile_id/$item->id", 'type' => 'status', 'username' => $item->profile->username, - 'caption' => $item->rendered ?? $item->caption, + 'caption' => $content, 'thumb' => $url, 'timestamp' => $item->created_at->diffForHumans(), ]]; @@ -340,17 +345,21 @@ class SearchController extends Controller if (isset($remote['type']) && $remote['type'] == 'Note') { $item = Helpers::statusFetch($tag); + if (! $item) { + return; + } $media = $item->firstMedia(); $url = null; if ($media) { $url = $media->remote_url; } + $content = $item->caption ? Autolink::create()->autolink($item->caption) : null; $this->tokens['posts'] = [[ 'count' => 0, 'url' => "/i/web/post/_/$item->profile_id/$item->id", 'type' => 'status', 'username' => $item->profile->username, - 'caption' => $item->rendered ?? $item->caption, + 'caption' => $content, 'thumb' => $url, 'timestamp' => $item->created_at->diffForHumans(), ]]; diff --git a/app/Http/Controllers/Stories/StoryApiV1Controller.php b/app/Http/Controllers/Stories/StoryApiV1Controller.php index 5d0a15160..48599fc3a 100644 --- a/app/Http/Controllers/Stories/StoryApiV1Controller.php +++ b/app/Http/Controllers/Stories/StoryApiV1Controller.php @@ -281,7 +281,7 @@ class StoryApiV1Controller extends Controller $photo = $request->file('file'); $path = $this->storeMedia($photo, $user); - $story = new Story(); + $story = new Story; $story->duration = $request->input('duration', 3); $story->profile_id = $user->profile_id; $story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo'; @@ -418,7 +418,6 @@ class StoryApiV1Controller extends Controller $status->type = 'story:reply'; $status->profile_id = $pid; $status->caption = $text; - $status->rendered = $text; $status->scope = 'direct'; $status->visibility = 'direct'; $status->in_reply_to_profile_id = $story->profile_id; diff --git a/app/Http/Controllers/StoryComposeController.php b/app/Http/Controllers/StoryComposeController.php index c8b0599a6..e02e2d219 100644 --- a/app/Http/Controllers/StoryComposeController.php +++ b/app/Http/Controllers/StoryComposeController.php @@ -54,7 +54,7 @@ class StoryComposeController extends Controller $photo = $request->file('file'); $path = $this->storePhoto($photo, $user); - $story = new Story(); + $story = new Story; $story->duration = 3; $story->profile_id = $user->profile_id; $story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo'; @@ -403,7 +403,6 @@ class StoryComposeController extends Controller $status->profile_id = $pid; $status->type = 'story:reaction'; $status->caption = $text; - $status->rendered = $text; $status->scope = 'direct'; $status->visibility = 'direct'; $status->in_reply_to_profile_id = $story->profile_id; @@ -477,7 +476,6 @@ class StoryComposeController extends Controller $status->type = 'story:reply'; $status->profile_id = $pid; $status->caption = $text; - $status->rendered = $text; $status->scope = 'direct'; $status->visibility = 'direct'; $status->in_reply_to_profile_id = $story->profile_id; diff --git a/app/Jobs/GroupPipeline/NewStatusPipeline.php b/app/Jobs/GroupPipeline/NewStatusPipeline.php index 4d8eeca5c..d791d81a4 100644 --- a/app/Jobs/GroupPipeline/NewStatusPipeline.php +++ b/app/Jobs/GroupPipeline/NewStatusPipeline.php @@ -2,129 +2,122 @@ namespace App\Jobs\GroupPipeline; -use App\Notification; use App\Hashtag; use App\Mention; -use App\Profile; -use App\Status; -use App\StatusHashtag; -use App\Models\GroupPostHashtag; use App\Models\GroupPost; -use Cache; +use App\Models\GroupPostHashtag; +use App\Profile; +use App\Services\StatusService; +use App\Status; +use App\Util\Lexer\Autolink; +use App\Util\Lexer\Extractor; use DB; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Redis; -use App\Services\MediaStorageService; -use App\Services\NotificationService; -use App\Services\StatusService; -use App\Util\Lexer\Autolink; -use App\Util\Lexer\Extractor; class NewStatusPipeline implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - protected $status; - protected $gp; - protected $tags; - protected $mentions; + protected $status; - public function __construct(Status $status, GroupPost $gp) - { - $this->status = $status; - $this->gp = $gp; - } + protected $gp; - public function handle() - { - $status = $this->status; + protected $tags; - $autolink = Autolink::create() - ->setAutolinkActiveUsersOnly(true) - ->setBaseHashPath("/groups/{$status->group_id}/topics/") - ->setBaseUserPath("/groups/{$status->group_id}/username/") - ->autolink($status->caption); + protected $mentions; + + public function __construct(Status $status, GroupPost $gp) + { + $this->status = $status; + $this->gp = $gp; + } + + public function handle() + { + $status = $this->status; + + $autolink = Autolink::create() + ->setAutolinkActiveUsersOnly(true) + ->setBaseHashPath("/groups/{$status->group_id}/topics/") + ->setBaseUserPath("/groups/{$status->group_id}/username/") + ->autolink($status->caption); $entities = Extractor::create()->extract($status->caption); + $status->entities = null; + $status->save(); - $autolink = str_replace('/discover/tags/', '/groups/' . $status->group_id . '/topics/', $autolink); + $this->tags = array_unique($entities['hashtags']); + $this->mentions = array_unique($entities['mentions']); - $status->rendered = nl2br($autolink); - $status->entities = null; - $status->save(); + if (count($this->tags)) { + $this->storeHashtags(); + } - $this->tags = array_unique($entities['hashtags']); - $this->mentions = array_unique($entities['mentions']); + if (count($this->mentions)) { + $this->storeMentions($this->mentions); + } + } - if(count($this->tags)) { - $this->storeHashtags(); - } + protected function storeHashtags() + { + $tags = $this->tags; + $status = $this->status; + $gp = $this->gp; - if(count($this->mentions)) { - $this->storeMentions($this->mentions); - } - } + foreach ($tags as $tag) { + if (mb_strlen($tag) > 124) { + continue; + } - protected function storeHashtags() - { - $tags = $this->tags; - $status = $this->status; - $gp = $this->gp; + DB::transaction(function () use ($status, $tag, $gp) { + $slug = str_slug($tag, '-', false); + $hashtag = Hashtag::firstOrCreate( + ['name' => $tag, 'slug' => $slug] + ); + GroupPostHashtag::firstOrCreate( + [ + 'group_id' => $status->group_id, + 'group_post_id' => $gp->id, + 'status_id' => $status->id, + 'hashtag_id' => $hashtag->id, + 'profile_id' => $status->profile_id, + ] + ); - foreach ($tags as $tag) { - if(mb_strlen($tag) > 124) { - continue; - } + }); + } - DB::transaction(function () use ($status, $tag, $gp) { - $slug = str_slug($tag, '-', false); - $hashtag = Hashtag::firstOrCreate( - ['name' => $tag, 'slug' => $slug] - ); - GroupPostHashtag::firstOrCreate( - [ - 'group_id' => $status->group_id, - 'group_post_id' => $gp->id, - 'status_id' => $status->id, - 'hashtag_id' => $hashtag->id, - 'profile_id' => $status->profile_id, - ] - ); + if (count($this->mentions)) { + $this->storeMentions(); + } + StatusService::del($status->id); + } - }); - } + protected function storeMentions() + { + $mentions = $this->mentions; + $status = $this->status; - if(count($this->mentions)) { - $this->storeMentions(); - } - StatusService::del($status->id); - } + foreach ($mentions as $mention) { + $mentioned = Profile::whereUsername($mention)->first(); - protected function storeMentions() - { - $mentions = $this->mentions; - $status = $this->status; + if (empty($mentioned) || ! isset($mentioned->id)) { + continue; + } - foreach ($mentions as $mention) { - $mentioned = Profile::whereUsername($mention)->first(); + DB::transaction(function () use ($status, $mentioned) { + $m = new Mention; + $m->status_id = $status->id; + $m->profile_id = $mentioned->id; + $m->save(); - if (empty($mentioned) || !isset($mentioned->id)) { - continue; - } - - DB::transaction(function () use ($status, $mentioned) { - $m = new Mention(); - $m->status_id = $status->id; - $m->profile_id = $mentioned->id; - $m->save(); - - MentionPipeline::dispatch($status, $m); - }); - } - StatusService::del($status->id); - } + MentionPipeline::dispatch($status, $m); + }); + } + StatusService::del($status->id); + } } diff --git a/app/Jobs/StatusPipeline/StatusEntityLexer.php b/app/Jobs/StatusPipeline/StatusEntityLexer.php index 4d19c7d8a..8fe767417 100644 --- a/app/Jobs/StatusPipeline/StatusEntityLexer.php +++ b/app/Jobs/StatusPipeline/StatusEntityLexer.php @@ -91,11 +91,6 @@ class StatusEntityLexer implements ShouldQueue public function storeEntities() { $this->storeHashtags(); - DB::transaction(function () { - $status = $this->status; - $status->rendered = nl2br($this->autolink); - $status->save(); - }); } public function storeHashtags() @@ -146,7 +141,7 @@ class StatusEntityLexer implements ShouldQueue } DB::transaction(function () use ($status, $mentioned) { - $m = new Mention(); + $m = new Mention; $m->status_id = $status->id; $m->profile_id = $mentioned->id; $m->save(); diff --git a/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php b/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php index 7ef7a3366..b216c0531 100644 --- a/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php +++ b/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php @@ -120,8 +120,7 @@ class StatusRemoteUpdatePipeline implements ShouldQueue protected function updateImmediateAttributes($status, $activity) { if (isset($activity['content'])) { - $status->caption = strip_tags($activity['content']); - $status->rendered = Purify::clean($activity['content']); + $status->caption = strip_tags(Purify::clean($activity['content'])); } if (isset($activity['sensitive'])) { diff --git a/app/Services/Status/UpdateStatusService.php b/app/Services/Status/UpdateStatusService.php index d0a69c451..7ef4d440c 100644 --- a/app/Services/Status/UpdateStatusService.php +++ b/app/Services/Status/UpdateStatusService.php @@ -3,135 +3,133 @@ namespace App\Services\Status; use App\Media; -use App\ModLog; -use App\Status; use App\Models\StatusEdit; -use Purify; -use App\Util\Lexer\Autolink; +use App\ModLog; use App\Services\MediaService; use App\Services\MediaStorageService; use App\Services\StatusService; +use App\Status; +use Purify; class UpdateStatusService { - public static function call(Status $status, $attributes) - { - self::createPreviousEdit($status); - self::updateMediaAttachements($status, $attributes); - self::handleImmediateAttributes($status, $attributes); - self::createEdit($status, $attributes); + public static function call(Status $status, $attributes) + { + self::createPreviousEdit($status); + self::updateMediaAttachements($status, $attributes); + self::handleImmediateAttributes($status, $attributes); + self::createEdit($status, $attributes); - return StatusService::get($status->id); - } + return StatusService::get($status->id); + } - public static function updateMediaAttachements(Status $status, $attributes) - { - $count = $status->media()->count(); - if($count === 0 || $count === 1) { - return; - } + public static function updateMediaAttachements(Status $status, $attributes) + { + $count = $status->media()->count(); + if ($count === 0 || $count === 1) { + return; + } - $oids = $status->media()->orderBy('order')->pluck('id')->map(function($m) { return (string) $m; }); - $nids = collect($attributes['media_ids']); + $oids = $status->media()->orderBy('order')->pluck('id')->map(function ($m) { + return (string) $m; + }); + $nids = collect($attributes['media_ids']); - if($oids->toArray() === $nids->toArray()) { - return; - } + if ($oids->toArray() === $nids->toArray()) { + return; + } - foreach($oids->diff($nids)->values()->toArray() as $mid) { - $media = Media::find($mid); - if(!$media) { - continue; - } - $media->status_id = null; - $media->save(); - MediaStorageService::delete($media, true); - } + foreach ($oids->diff($nids)->values()->toArray() as $mid) { + $media = Media::find($mid); + if (! $media) { + continue; + } + $media->status_id = null; + $media->save(); + MediaStorageService::delete($media, true); + } - $nids->each(function($nid, $idx) { - $media = Media::find($nid); - if(!$media) { - return; - } - $media->order = $idx; - $media->save(); - }); - MediaService::del($status->id); - } + $nids->each(function ($nid, $idx) { + $media = Media::find($nid); + if (! $media) { + return; + } + $media->order = $idx; + $media->save(); + }); + MediaService::del($status->id); + } - public static function handleImmediateAttributes(Status $status, $attributes) - { - if(isset($attributes['status'])) { - $cleaned = Purify::clean($attributes['status']); - $status->caption = $cleaned; - $status->rendered = nl2br(Autolink::create()->autolink($cleaned)); - } else { - $status->caption = null; - $status->rendered = null; - } - if(isset($attributes['sensitive'])) { - if($status->is_nsfw != (bool) $attributes['sensitive'] && - (bool) $attributes['sensitive'] == false) - { - $exists = ModLog::whereObjectType('App\Status::class') - ->whereObjectId($status->id) - ->whereAction('admin.status.moderate') - ->exists(); - if(!$exists) { - $status->is_nsfw = (bool) $attributes['sensitive']; - } - } else { - $status->is_nsfw = (bool) $attributes['sensitive']; - } - } - if(isset($attributes['spoiler_text'])) { - $status->cw_summary = Purify::clean($attributes['spoiler_text']); - } else { - $status->cw_summary = null; - } - if(isset($attributes['location'])) { - if (isset($attributes['location']['id'])) { - $status->place_id = $attributes['location']['id']; - } else { - $status->place_id = null; - } - } - if($status->cw_summary && !$status->is_nsfw) { - $status->cw_summary = null; - } - $status->edited_at = now(); - $status->save(); - StatusService::del($status->id); - } + public static function handleImmediateAttributes(Status $status, $attributes) + { + if (isset($attributes['status'])) { + $cleaned = Purify::clean($attributes['status']); + $status->caption = $cleaned; + } else { + $status->caption = null; + } + if (isset($attributes['sensitive'])) { + if ($status->is_nsfw != (bool) $attributes['sensitive'] && + (bool) $attributes['sensitive'] == false) { + $exists = ModLog::whereObjectType('App\Status::class') + ->whereObjectId($status->id) + ->whereAction('admin.status.moderate') + ->exists(); + if (! $exists) { + $status->is_nsfw = (bool) $attributes['sensitive']; + } + } else { + $status->is_nsfw = (bool) $attributes['sensitive']; + } + } + if (isset($attributes['spoiler_text'])) { + $status->cw_summary = Purify::clean($attributes['spoiler_text']); + } else { + $status->cw_summary = null; + } + if (isset($attributes['location'])) { + if (isset($attributes['location']['id'])) { + $status->place_id = $attributes['location']['id']; + } else { + $status->place_id = null; + } + } + if ($status->cw_summary && ! $status->is_nsfw) { + $status->cw_summary = null; + } + $status->edited_at = now(); + $status->save(); + StatusService::del($status->id); + } - public static function createPreviousEdit(Status $status) - { - if(!$status->edits()->count()) { - StatusEdit::create([ - 'status_id' => $status->id, - 'profile_id' => $status->profile_id, - 'caption' => $status->caption, - 'spoiler_text' => $status->cw_summary, - 'is_nsfw' => $status->is_nsfw, - 'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(), - 'created_at' => $status->created_at - ]); - } - } + public static function createPreviousEdit(Status $status) + { + if (! $status->edits()->count()) { + StatusEdit::create([ + 'status_id' => $status->id, + 'profile_id' => $status->profile_id, + 'caption' => $status->caption, + 'spoiler_text' => $status->cw_summary, + 'is_nsfw' => $status->is_nsfw, + 'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(), + 'created_at' => $status->created_at, + ]); + } + } - public static function createEdit(Status $status, $attributes) - { - $cleaned = isset($attributes['status']) ? Purify::clean($attributes['status']) : null; - $spoiler_text = isset($attributes['spoiler_text']) ? Purify::clean($attributes['spoiler_text']) : null; - $sensitive = isset($attributes['sensitive']) ? $attributes['sensitive'] : null; - $mids = $status->media()->count() ? $status->media()->orderBy('order')->pluck('id')->toArray() : null; - StatusEdit::create([ - 'status_id' => $status->id, - 'profile_id' => $status->profile_id, - 'caption' => $cleaned, - 'spoiler_text' => $spoiler_text, - 'is_nsfw' => $sensitive, - 'ordered_media_attachment_ids' => $mids - ]); - } + public static function createEdit(Status $status, $attributes) + { + $cleaned = isset($attributes['status']) ? Purify::clean($attributes['status']) : null; + $spoiler_text = isset($attributes['spoiler_text']) ? Purify::clean($attributes['spoiler_text']) : null; + $sensitive = isset($attributes['sensitive']) ? $attributes['sensitive'] : null; + $mids = $status->media()->count() ? $status->media()->orderBy('order')->pluck('id')->toArray() : null; + StatusEdit::create([ + 'status_id' => $status->id, + 'profile_id' => $status->profile_id, + 'caption' => $cleaned, + 'spoiler_text' => $spoiler_text, + 'is_nsfw' => $sensitive, + 'ordered_media_attachment_ids' => $mids, + ]); + } } diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index 93cab7754..bbc64dc26 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -694,8 +694,7 @@ class Helpers $status->url = isset($res['url']) ? $res['url'] : $url; $status->uri = isset($res['url']) ? $res['url'] : $url; $status->object_url = $id; - $status->caption = strip_tags($res['content']); - $status->rendered = Purify::clean($res['content']); + $status->caption = strip_tags(Purify::clean($res['content'])); $status->created_at = Carbon::parse($ts)->tz('UTC'); $status->in_reply_to_id = null; $status->local = false; diff --git a/app/Util/ActivityPub/Inbox.php b/app/Util/ActivityPub/Inbox.php index 8cca59cfc..e98b48c49 100644 --- a/app/Util/ActivityPub/Inbox.php +++ b/app/Util/ActivityPub/Inbox.php @@ -438,7 +438,6 @@ class Inbox $status = new Status; $status->profile_id = $actor->id; $status->caption = $msgText; - $status->rendered = $msg; $status->visibility = 'direct'; $status->scope = 'direct'; $status->url = $activity['id']; @@ -1081,7 +1080,6 @@ class Inbox $status->uri = $url; $status->object_url = $url; $status->caption = $text; - $status->rendered = $text; $status->scope = 'direct'; $status->visibility = 'direct'; $status->in_reply_to_profile_id = $story->profile_id; @@ -1199,7 +1197,6 @@ class Inbox $status->profile_id = $actorProfile->id; $status->type = 'story:reply'; $status->caption = $text; - $status->rendered = $text; $status->url = $url; $status->uri = $url; $status->object_url = $url; From eac2c19601180efdbb1a2b43435b4b48700be6ab Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 04:03:08 -0700 Subject: [PATCH 017/108] Update AutolinkService, optimize lookups --- app/Services/AutolinkService.php | 58 +++++++++----------------------- 1 file changed, 15 insertions(+), 43 deletions(-) diff --git a/app/Services/AutolinkService.php b/app/Services/AutolinkService.php index f0f3278ff..c494ab151 100644 --- a/app/Services/AutolinkService.php +++ b/app/Services/AutolinkService.php @@ -2,53 +2,25 @@ namespace App\Services; -use Cache; use App\Profile; -use Illuminate\Support\Str; -use Illuminate\Support\Facades\Http; -use App\Util\Webfinger\WebfingerUrl; +use Cache; +use Purify; class AutolinkService { - const CACHE_KEY = 'pf:services:autolink:'; + const CACHE_KEY = 'pf:services:autolink:mue:'; - public static function mentionedUsernameExists($username) - { - $key = 'pf:services:autolink:userexists:' . hash('sha256', $username); + public static function mentionedUsernameExists($username) + { + if (str_starts_with($username, '@')) { + if (substr_count($username, '@') === 1) { + $username = substr($username, 1); + } + } + $name = Purify::clean(strtolower($username)); - return Cache::remember($key, 3600, function() use($username) { - $remote = Str::of($username)->contains('@'); - $profile = Profile::whereUsername($username)->first(); - if($profile) { - if($profile->domain != null) { - $instance = InstanceService::getByDomain($profile->domain); - if($instance && $instance->banned == true) { - return false; - } - } - return true; - } else { - if($remote) { - $parts = explode('@', $username); - $domain = last($parts); - $instance = InstanceService::getByDomain($domain); - - if($instance) { - if($instance->banned == true) { - return false; - } else { - $wf = WebfingerUrl::generateWebfingerUrl($username); - $res = Http::head($wf); - return $res->ok(); - } - } else { - $wf = WebfingerUrl::generateWebfingerUrl($username); - $res = Http::head($wf); - return $res->ok(); - } - } - } - return false; - }); - } + return Cache::remember(self::CACHE_KEY.base64_encode($name), 7200, function () use ($name) { + return Profile::where('username', $name)->exists(); + }); + } } From 3437d76e624e13fe5bd26290a81d35c2410f0d5d Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 04:04:16 -0700 Subject: [PATCH 018/108] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bab1bf93f..1730d85fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,14 @@ # Release Notes ## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.12.3...dev) + +### Updates - Update AP helpers, reject statuses with invalid dates ([960f3849](https://github.com/pixelfed/pixelfed/commit/960f3849)) - Update DirectMessage API, fix broken threading ([044d410c](https://github.com/pixelfed/pixelfed/commit/044d410c)) - Update Status caption render logic ([fb8dbb95](https://github.com/pixelfed/pixelfed/commit/fb8dbb95)) - Update ApiV1Controller, fix bookmark bug. Closes #5216 ([9f7cc52c](https://github.com/pixelfed/pixelfed/commit/9f7cc52c)) +- Update Status caption logic, stop storing duplicate html caption in db and defer to cached StatusService rendering ([9eeb7b67](https://github.com/pixelfed/pixelfed/commit/9eeb7b67)) +- Update AutolinkService, optimize lookups ([eac2c196](https://github.com/pixelfed/pixelfed/commit/eac2c196)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev) From 51bbb85bb0c36991465fa6896754280a1e7aed48 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 04:17:41 -0700 Subject: [PATCH 019/108] Update Helpers.php --- app/Util/ActivityPub/Helpers.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index bbc64dc26..f5944047c 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -552,7 +552,6 @@ class Helpers 'url' => $url, 'object_url' => $id, 'caption' => isset($activity['content']) ? Purify::clean(strip_tags($activity['content'])) : null, - 'rendered' => isset($activity['content']) ? Purify::clean($activity['content']) : null, 'created_at' => Carbon::parse($ts)->tz('UTC'), 'in_reply_to_id' => $reply_to, 'local' => false, From 26ab575b137f0a6a05e9d3a3b085c3f6c6da0f12 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 2 Dec 2024 16:18:28 -0700 Subject: [PATCH 020/108] Create funding.json --- funding.json | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 funding.json diff --git a/funding.json b/funding.json new file mode 100644 index 000000000..350036247 --- /dev/null +++ b/funding.json @@ -0,0 +1,68 @@ +{ + "version": "v1.0.0", + "entity": { + "type": "individual", + "role": "owner", + "name": "Daniel Supernault", + "email": "danielsupernault@gmail.com", + "phone": "", + "description": "I'm the developer behind Pixelfed, an open-source, federated photo-sharing platform that prioritizes privacy, community, and creativity. With a passion for building ethical alternatives to mainstream social networks, I have championed decentralized technologies and empowered users to share their stories without compromising their digital autonomy. As the driving force behind Pixelfed, I combine innovative development with a thoughtful approach to user experience, fostering an online space that feels personal, authentic, and inclusive.", + "webpageUrl": { + "url": "https://github.com/pixelfed/pixelfed" + } + }, + "projects": [ + { + "guid": "pixelfed", + "name": "Pixelfed", + "description": "Pixelfed is a free, open-source photo-sharing platform designed to put users in control of their content. Built on the principles of decentralization, Pixelfed offers a refreshing alternative to traditional social media, prioritizing privacy, community, and ethical design. Whether you're an artist, photographer, or someone who loves sharing moments, Pixelfed lets you connect and create without intrusive ads, algorithms, or data exploitation. Fully federated and part of the Fediverse, Pixelfed empowers users to join or host their own instances while still connecting with a global network of creatives and communities. It's not just a platform—it's a movement toward a better, more user-centered internet.", + "webpageUrl": { + "url": "https://github.com/pixelfed/pixelfed" + }, + "repositoryUrl": { + "url": "https://github.com/pixelfed/pixelfed", + }, + "licenses": ["spdx:AGPL-3.0"], + "tags": ["activitypub", "fediverse", "laravel", "pixelfed"] + } + ], + "funding": { + "channels": [ + { + "guid": "github-sponsors", + "type": "payment-provider", + "address": "https://github.com/sponsors/dansup", + "description": "Sponsor me through Github." + }, + { + "guid": "paypal-sponsors", + "type": "payment-provider", + "address": "https://www.paypal.com/paypalme/dansup", + "description": "Sponsor me through Paypal." + } + ], + "plans": [ + { + "guid": "developer-time", + "status": "active", + "name": "Developer compensation", + "description": "This will cover the cost of one developer working part-time on the projects.", + "amount": 0, + "currency": "USD", + "frequency": "monthly", + "channels": ["github-sponsors", "paypal-sponsors"] + }, + { + "guid": "support-plan", + "status": "active", + "name": "Support plan", + "description": "Pay anything you wish/can to show your support for the projects.", + "amount": 0, + "currency": "USD", + "frequency": "one-time", + "channels": ["github-sponsors", "paypal-sponsors"] + } + ], + "history": [] + } +} From 8cd01b2edef08cd16d64ac8936ca645af65bbbbf Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Mon, 2 Dec 2024 16:19:48 -0700 Subject: [PATCH 021/108] Update funding.json --- funding.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/funding.json b/funding.json index 350036247..507d8441e 100644 --- a/funding.json +++ b/funding.json @@ -20,7 +20,7 @@ "url": "https://github.com/pixelfed/pixelfed" }, "repositoryUrl": { - "url": "https://github.com/pixelfed/pixelfed", + "url": "https://github.com/pixelfed/pixelfed" }, "licenses": ["spdx:AGPL-3.0"], "tags": ["activitypub", "fediverse", "laravel", "pixelfed"] From 669a68d27b0a8ed34ff272a987685ad584e7534f Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 3 Dec 2024 15:16:56 +0100 Subject: [PATCH 022/108] feat: apply filters with WebGL --- package-lock.json | 34 ++ package.json | 1 + .../assets/js/components/ComposeModal.vue | 491 +++++++----------- resources/assets/js/components/filters.js | 290 +++++++++++ 4 files changed, 507 insertions(+), 309 deletions(-) create mode 100644 resources/assets/js/components/filters.js diff --git a/package-lock.json b/package-lock.json index ec29866a7..825688500 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "vue-loading-overlay": "^3.3.3", "vue-timeago": "^5.1.2", "vue-tribute": "^1.0.7", + "webgl-media-editor": "^0.0.1", "zuck.js": "^1.6.0" }, "devDependencies": { @@ -5775,6 +5776,12 @@ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" }, + "node_modules/gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==", + "license": "MIT" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -9971,6 +9978,15 @@ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.1.tgz", "integrity": "sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==" }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -10048,6 +10064,12 @@ "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "dev": true }, + "node_modules/twgl.js": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/twgl.js/-/twgl.js-5.5.4.tgz", + "integrity": "sha512-6kFOmijOpmblTN9CCwOTCxK4lPg7rCyQjLuub6EMOlEp89Ex6yUcsMjsmH7andNPL2NE3XmHdqHeP5gVKKPhxw==", + "license": "MIT" + }, "node_modules/twitter-text": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/twitter-text/-/twitter-text-2.0.5.tgz", @@ -10535,6 +10557,18 @@ "node": ">= 8" } }, + "node_modules/webgl-media-editor": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/webgl-media-editor/-/webgl-media-editor-0.0.1.tgz", + "integrity": "sha512-TxnuRl3rpWa1Cia/pn+vh+0iz3yDNwzsrnRGJ61YkdZAYuimu2afBivSHv0RK73hKza6Y/YoRCkuEcsFmtxPNw==", + "license": "AGPL-3.0-only", + "dependencies": { + "cropperjs": "^1.6.2", + "gl-matrix": "^3.4.3", + "throttle-debounce": "^5.0.2", + "twgl.js": "^5.5.4" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 7724f040c..691972ced 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "vue-loading-overlay": "^3.3.3", "vue-timeago": "^5.1.2", "vue-tribute": "^1.0.7", + "webgl-media-editor": "^0.0.1", "zuck.js": "^1.6.0" }, "collective": { diff --git a/resources/assets/js/components/ComposeModal.vue b/resources/assets/js/components/ComposeModal.vue index 94f6f5e13..1ec98c64f 100644 --- a/resources/assets/js/components/ComposeModal.vue +++ b/resources/assets/js/components/ComposeModal.vue @@ -1,6 +1,6 @@ Post Post - Next @@ -342,8 +341,8 @@
-
- +
-
- -
-
-
- -
+ +

-
- +
+
@@ -427,7 +398,7 @@
- +
@@ -780,7 +751,7 @@
- +

{{video.title ? video.title.slice(0,70) : 'Untitled'}}

{{video.description ? video.description.slice(0,90) : 'No description'}}

@@ -839,13 +810,6 @@
- -
-
- -

Applying filters...

-
-
@@ -875,13 +839,17 @@ import 'cropperjs/dist/cropper.css'; import Autocomplete from '@trevoreyre/autocomplete-vue' import '@trevoreyre/autocomplete-vue/dist/style.css' import VueTribute from 'vue-tribute' +import { MediaEditor, MediaEditorPreview, MediaEditorFilterMenu } from 'webgl-media-editor/vue2' +import { filterEffects } from './filters'; export default { components: { VueCropper, Autocomplete, - VueTribute + VueTribute, + MediaEditorPreview, + MediaEditorFilterMenu }, data() { @@ -892,10 +860,9 @@ export default { composeText: '', composeTextLength: 0, nsfw: false, - filters: [], - currentFilter: false, ids: [], media: [], + files: [], carouselCursor: 0, uploading: false, uploadProgress: 100, @@ -923,7 +890,6 @@ export default { }, namedPages: [ - 'filteringMedia', 'cropPhoto', 'tagPeople', 'addLocation', @@ -1044,13 +1010,26 @@ export default { collectionsPage: 1, collectionsCanLoadMore: false, spoilerText: undefined, - isFilteringMedia: false, - filteringMediaTimeout: undefined, - filteringRemainingCount: 0, isPosting: false, } }, + created() { + this.editor = new MediaEditor({ + effects: filterEffects, + onEdit: (index, {effect, intensity, crop}) => { + if (index >= this.files.length) return + const file = this.files[index] + + this.$set(file, 'editState', { effect, intensity, crop }) + }, + onRenderPreview: (sourceIndex, previewUrl) => { + const media = this.media[sourceIndex] + if (media) media.preview_url = previewUrl + }, + }) + }, + computed: { spoilerTextLength: function() { return this.spoilerText ? this.spoilerText.length : 0; @@ -1058,7 +1037,6 @@ export default { }, beforeMount() { - this.filters = window.App.util.filters.sort(); axios.get('/api/compose/v0/settings') .then(res => { this.composeSettings = res.data; @@ -1075,8 +1053,12 @@ export default { }); }, - mounted() { - this.mediaWatcher(); + destroyed() { + this.files.forEach(fileInfo => { + URL.revokeObjectURL(fileInfo.url); + }) + this.files.length = this.media.length = 0 + this.editor = undefined }, methods: { @@ -1156,39 +1138,55 @@ export default { this.mode = 'text'; }, - mediaWatcher() { - let self = this; - $(document).on('change', '#pf-dz', function(e) { - self.mediaUpload(); - }); - }, + onInputFile(event) { + const input = event.target + const files = Array.from(input.files) + input.value = null; - mediaUpload() { let self = this; - self.uploading = true; - let io = document.querySelector('#pf-dz'); - if(!io.files.length) { - self.uploading = false; - } - Array.prototype.forEach.call(io.files, function(io, i) { - if(self.media && self.media.length + i >= self.config.uploader.album_limit) { + + files.forEach((file, i) => { + if(self.media && self.media.length >= self.config.uploader.album_limit) { swal('Error', 'You can only upload ' + self.config.uploader.album_limit + ' photos per album', 'error'); - self.uploading = false; self.page = 2; return; } - let type = io.type; let acceptedMimes = self.config.uploader.media_types.split(','); - let validated = $.inArray(type, acceptedMimes); + let validated = $.inArray(file.type, acceptedMimes); if(validated == -1) { swal('Invalid File Type', 'The file you are trying to add is not a valid mime type. Please upload a '+self.config.uploader.media_types+' only.', 'error'); - self.uploading = false; self.page = 2; return; } + const type = file.type.replace(/\/.*/, '') + const url = URL.createObjectURL(file) + const preview_url = type === 'image' ? url : '/storage/no-preview.png' + + this.files.push({ file, editState: undefined }) + this.media.push({ url, preview_url, type }) + }) + + if (this.media.length) { + this.page = 3 + } else { + this.page = 2 + } + }, + + async mediaUpload() { + this.uploading = true; + + const uploadPromises = this.files.map(async (fileInfo, i) => { + let file = fileInfo.file + const media = this.media[i] + + if (media.type === 'image' && fileInfo.editState) { + file = await this.editor.toBlob(i) + } + let form = new FormData(); - form.append('file', io); + form.append('file', file); let xhrConfig = { onUploadProgress: function(e) { @@ -1197,12 +1195,13 @@ export default { } }; - axios.post('/api/compose/v0/media/upload', form, xhrConfig) + const self = this + + await axios.post('/api/compose/v0/media/upload', form, xhrConfig) .then(function(e) { self.uploadProgress = 100; self.ids.push(e.data.id); - self.media.push(e.data); - self.uploading = false; + Object.assign(media, e.data) setTimeout(function() { // if(type === 'video/mp4') { // self.pageTitle = 'Edit Video Details'; @@ -1216,131 +1215,100 @@ export default { }).catch(function(e) { switch(e.response.status) { case 403: - self.uploading = false; - io.value = null; swal('Account size limit reached', 'Contact your admin for assistance.', 'error'); self.page = 2; break; case 413: - self.uploading = false; - io.value = null; - swal('File is too large', 'The file you uploaded has the size of ' + self.formatBytes(io.size) + '. Unfortunately, only images up to ' + self.formatBytes(self.config.uploader.max_photo_size * 1024) + ' are supported.\nPlease resize the file and try again.', 'error'); + swal('File is too large', 'The file you uploaded has the size of ' + self.formatBytes(file.size) + '. Unfortunately, only images up to ' + self.formatBytes(self.config.uploader.max_photo_size * 1024) + ' are supported.\nPlease resize the file and try again.', 'error'); self.page = 2; break; case 451: - self.uploading = false; - io.value = null; swal('Banned Content', 'This content has been banned and cannot be uploaded.', 'error'); self.page = 2; break; case 429: - self.uploading = false; - io.value = null; swal('Limit Reached', 'You can upload up to 250 photos or videos per day and you\'ve reached that limit. Please try again later.', 'error'); self.page = 2; break; case 500: - self.uploading = false; - io.value = null; swal('Error', e.response.data.message, 'error'); self.page = 2; break; default: - self.uploading = false; - io.value = null; swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error'); self.page = 2; break; } + + throw e }); - io.value = null; - self.uploadProgress = 0; }); + + await Promise.all(uploadPromises).finally(() => { + this.uploadProgress = 0; + this.uploading = false; + }); }, - toggleFilter(e, filter) { - this.media[this.carouselCursor].filter_class = filter; - this.currentFilter = filter; - }, - - deleteMedia() { + async deleteMedia() { if(window.confirm('Are you sure you want to delete this media?') == false) { return; } let id = this.media[this.carouselCursor].id; - axios.delete('/api/compose/v0/media/delete', { - params: { - id: id - } - }).then(res => { - this.ids.splice(this.carouselCursor, 1); - this.media.splice(this.carouselCursor, 1); - if(this.media.length == 0) { - this.ids = []; - this.media = []; - this.carouselCursor = 0; - } else { - this.carouselCursor = 0; - } - }).catch(err => { - swal('Whoops!', 'An error occured when attempting to delete this, please try again', 'error'); - }); + if (id) { + try { + await axios.delete('/api/compose/v0/media/delete', { + params: { + id: id + } + }) + } + catch(err) { + swal('Whoops!', 'An error occured when attempting to delete this, please try again', 'error'); + return + } + } + this.ids.splice(this.carouselCursor, 1); + this.media.splice(this.carouselCursor, 1); + + URL.revokeObjectURL(this.files[this.carouselCursor]?.url) + this.files.splice(this.carouselCursor, 1) + + if(this.media.length == 0) { + this.ids = []; + this.media = []; + this.carouselCursor = 0; + } else { + this.carouselCursor = 0; + } }, mediaReorder(dir) { - const m = this.media; - const cur = this.carouselCursor; - const pla = m[cur]; - let res = []; - let cursor = 0; + const prevIndex = this.carouselCursor + const newIndex = prevIndex + (dir === 'prev' ? -1 : 1) - if(dir == 'prev') { - if(cur == 0) { - for (let i = cursor; i < m.length - 1; i++) { - res[i] = m[i+1]; - } - res[m.length - 1] = pla; - cursor = 0; - } else { - res = this.handleSwap(m, cur, cur - 1); - cursor = cur - 1; - } - } else { - if(cur == m.length - 1) { - res = m; - let lastItem = res.pop(); - res.unshift(lastItem); - cursor = m.length - 1; - } else { - res = this.handleSwap(m, cur, cur + 1); - cursor = cur + 1; - } - } - this.$nextTick(() => { - this.media = res; - this.carouselCursor = cursor; - }) + if (newIndex < 0 || newIndex >= this.media.length) return + + const [removedFile] = this.files.splice(prevIndex, 1) + const [removedMedia] = this.media.splice(prevIndex, 1) + const [removedId] = this.ids.splice(prevIndex, 1) + + this.files.splice(newIndex, 0, removedFile) + this.media.splice(newIndex, 0, removedMedia) + this.ids.splice(newIndex, 0, removedId) + this.carouselCursor = newIndex }, - handleSwap(arr, index1, index2) { - if (index1 >= 0 && index1 < arr.length && index2 >= 0 && index2 < arr.length) { - const temp = arr[index1]; - arr[index1] = arr[index2]; - arr[index2] = temp; - return arr; - } - }, - - compose() { + async compose() { let state = this.composeState; - if(this.uploadProgress != 100 || this.ids.length == 0) { + if(this.files.length == 0) { return; } @@ -1353,11 +1321,14 @@ export default { switch(state) { case 'publish': this.isPosting = true; - let count = this.media.filter(m => m.filter_class && !m.hasOwnProperty('is_filtered')).length; - if(count) { - this.applyFilterToMedia(); - return; + + try { + await this.mediaUpload().finally(() => this.isPosting = false) + } catch { + this.isPosting = false; + return } + if(this.composeSettings.media_descriptions === true) { let count = this.media.filter(m => { return !m.hasOwnProperty('alt') || m.alt.length < 2; @@ -1420,6 +1391,8 @@ export default { this.defineErrorMessage(err); break; } + }).finally(() => { + this.isPosting = false; }); return; break; @@ -1488,10 +1461,6 @@ export default { switch(this.mode) { case 'photo': switch(this.page) { - case 'filteringMedia': - this.page = 2; - break; - case 'addText': this.page = 1; break; @@ -1526,10 +1495,6 @@ export default { case 'video': switch(this.page) { - case 'filteringMedia': - this.page = 2; - break; - case 'licensePicker': this.page = 'video-2'; break; @@ -1550,10 +1515,6 @@ export default { this.page = 1; break; - case 'filteringMedia': - this.page = 2; - break; - case 'textOptions': this.page = 'addText'; break; @@ -1593,31 +1554,14 @@ export default { this.page = 2; break; - case 'filteringMedia': - break; - case 'cropPhoto': - this.pageLoading = true; - let self = this; - this.$refs.cropper.getCroppedCanvas({ - maxWidth: 4096, - maxHeight: 4096, - fillColor: '#fff', - imageSmoothingEnabled: false, - imageSmoothingQuality: 'high', - }).toBlob(function(blob) { - self.mediaCropped = true; - let data = new FormData(); - data.append('file', blob); - data.append('id', self.ids[self.carouselCursor]); - let url = '/api/compose/v0/media/update'; - axios.post(url, data).then(res => { - self.media[self.carouselCursor].url = res.data.url; - self.pageLoading = false; - self.page = 2; - }).catch(err => { - }); - }); + const { editState } = this.files[this.carouselCursor] + const croppedState = { + ...editState, + crop: this.$refs.cropper.getData() + } + this.editor.setEditState(this.carouselCursor, croppedState) + this.page = 2; break; case 2: @@ -1764,111 +1708,6 @@ export default { }); }, - applyFilterToMedia() { - // this is where the magic happens - let count = this.media.filter(m => m.filter_class).length; - if(count) { - this.page = 'filteringMedia'; - this.filteringRemainingCount = count; - this.$nextTick(() => { - this.isFilteringMedia = true; - Promise.all(this.media.map(media => { - return this.applyFilterToMediaSave(media); - })).catch(err => { - console.error(err); - swal('Oops!', 'An error occurred while applying filters to your media. Please refresh the page and try again. If the problem persist, please try a different web browser.', 'error'); - }); - }) - } else { - this.page = 3; - } - }, - - async applyFilterToMediaSave(media) { - if(!media.filter_class) { - return; - } - - // Load image - const image = document.createElement('img'); - image.src = media.url; - await new Promise((resolve, reject) => { - image.addEventListener('load', () => resolve()); - image.addEventListener('error', () => { - reject(new Error('Failed to load image')); - }); - }); - - // Create canvas - let canvas; - let usingOffscreenCanvas = false; - if('OffscreenCanvas' in window) { - canvas = new OffscreenCanvas(image.width, image.height); - usingOffscreenCanvas = true; - } else { - canvas = document.createElement('canvas'); - canvas.width = image.width; - canvas.height = image.height; - } - - // Draw image with filter to canvas - const ctx = canvas.getContext('2d'); - if (!ctx) { - throw new Error('Failed to get canvas context'); - } - if (!('filter' in ctx)) { - throw new Error('Canvas filter not supported'); - } - ctx.filter = App.util.filterCss[media.filter_class]; - ctx.drawImage(image, 0, 0, image.width, image.height); - ctx.save(); - - // Convert canvas to blob - let blob; - if(usingOffscreenCanvas) { - blob = await canvas.convertToBlob({ - type: media.mime, - quality: 1, - }); - } else { - blob = await new Promise((resolve, reject) => { - canvas.toBlob(blob => { - if(blob) { - resolve(blob); - } else { - reject( - new Error('Failed to convert canvas to blob'), - ); - } - }, media.mime, 1); - }); - } - - // Upload blob / Update media - const data = new FormData(); - data.append('file', blob); - data.append('id', media.id); - await axios.post('/api/compose/v0/media/update', data); - media.is_filtered = true; - this.updateFilteringMedia(); - }, - - updateFilteringMedia() { - this.filteringRemainingCount--; - this.filteringMediaTimeout = setTimeout(() => this.filteringMediaTimeoutJob(), 500); - }, - - filteringMediaTimeoutJob() { - if(this.filteringRemainingCount === 0) { - this.isFilteringMedia = false; - clearTimeout(this.filteringMediaTimeout); - setTimeout(() => this.compose(), 500); - } else { - clearTimeout(this.filteringMediaTimeout); - this.filteringMediaTimeout = setTimeout(() => this.filteringMediaTimeoutJob(), 1000); - } - }, - tagSearch(input) { if (input.length < 1) { return []; } let self = this; @@ -2059,6 +1898,11 @@ export default { this.collectionsCanLoadMore = true; }); } + }, + watch: { + files(value) { + this.editor.setSources(value.map(f => f.file)) + }, } } @@ -2111,5 +1955,34 @@ export default { } } } + .media-editor { + background-color: transparent; + border: none !important; + box-shadow: none !important; + font-size: 12px; + + --height-menu-row: 5rem; + --gap-preview: 0rem; + --height-menu-row-scroll: 10rem; + + --color-bg-button: transparent; /*var(--light);*/ + --color-bg-preview: transparent; /*var(--light-gray);*/ + --color-bg-button-hover: var(--light-gray); + --color-bg-acc: var(--card-bg); + + --color-fnt-default: var(--body-color); + --color-fnt-acc: var(--text-lighter); + + --color-scrollbar-thumb: var(--light-gray); + --color-scrollbar-both: var(--light-gray) transparent; + + --color-slider-thumb: var(--text-lighter); + --color-slider-progress: var(--light-gray); + --color-slider-track: var(--light); + + --color-crop-outline: var(--light-gray); + --color-crop-dashed: #ffffffde; + --color-crop-overlay: #00000082; + } } diff --git a/resources/assets/js/components/filters.js b/resources/assets/js/components/filters.js new file mode 100644 index 000000000..59579c809 --- /dev/null +++ b/resources/assets/js/components/filters.js @@ -0,0 +1,290 @@ +export const filterEffects = [ + { + name: '1984', + ops: [ + { type: 'sepia', intensity: 0.5 }, + { type: 'hue_rotate', angle: -30 }, + { type: 'adjust_color', brightness: 0, contrast: 0, saturation: 0.4 }, + ], + }, + { + name: 'Azen', + ops: [ + { type: 'sepia', intensity: 0.2 }, + { type: 'adjust_color', brightness: 0.15, contrast: 0, saturation: 0.4 }, + ], + }, + { + name: 'Astairo', + ops: [ + { type: 'sepia', intensity: 0.35 }, + { type: 'adjust_color', brightness: 0.2, contrast: 0.1, saturation: 0.3 }, + ], + }, + { + name: 'Grasbee', + ops: [ + { type: 'sepia', intensity: 0.5 }, + { type: 'adjust_color', brightness: 0, contrast: 0.2, saturation: 0.8 }, + ], + }, + { + name: 'Bookrun', + ops: [ + { type: 'sepia', intensity: 0.4 }, + { type: 'adjust_color', brightness: 0.1, contrast: 0.25, saturation: -0.1 }, + { type: 'hue_rotate', angle: -2 }, + ], + }, + { + name: 'Borough', + ops: [ + { type: 'sepia', intensity: 0.25 }, + { type: 'adjust_color', brightness: 0.25, contrast: 0.25, saturation: 0 }, + { type: 'hue_rotate', angle: 5 }, + ], + }, + { + name: 'Farms', + ops: [ + { type: 'sepia', intensity: 0.25 }, + { type: 'adjust_color', brightness: 0.25, contrast: 0.25, saturation: 0.35 }, + { type: 'hue_rotate', angle: -5 }, + ], + }, + { + name: 'Hairsadone', + ops: [ + { type: 'sepia', intensity: 0.15 }, + { type: 'adjust_color', brightness: 0.25, contrast: 0.25, saturation: 0 }, + { type: 'hue_rotate', angle: 5 }, + ], + }, + { + name: 'Cleana', + ops: [ + { type: 'sepia', intensity: 0.5 }, + { type: 'adjust_color', brightness: 0.25, contrast: 0.15, saturation: -0.1 }, + { type: 'hue_rotate', angle: -2 }, + ], + }, + { + name: 'Catpatch', + ops: [ + { type: 'sepia', intensity: 0.35 }, + { type: 'adjust_color', brightness: 0, contrast: .5, saturation: 0.1 }, + ], + }, + { + name: 'Earlyworm', + ops: [ + { type: 'sepia', intensity: 0.25 }, + { type: 'adjust_color', brightness: 0.25, contrast: 0.15, saturation: -0.1 }, + { type: 'hue_rotate', angle: -5 }, + ], + }, + { + name: 'Plaid', + ops: [{ type: 'adjust_color', brightness: 0.1, contrast: 0.1, saturation: 0 }], + }, + { + name: 'Kyo', + ops: [ + { type: 'sepia', intensity: 0.25 }, + { type: 'adjust_color', brightness: 0.2, contrast: 0.15, saturation: 0.35 }, + { type: 'hue_rotate', angle: -5 }, + ], + }, + { + name: 'Yefe', + ops: [ + { type: 'sepia', intensity: 0.4 }, + { type: 'adjust_color', brightness: 0.2, contrast: 0.5, saturation: 0.4 }, + { type: 'hue_rotate', angle: -10 }, + ], + }, + { + name: 'Godess', + ops: [ + { type: 'sepia', intensity: 0.5 }, + { type: 'adjust_color', brightness: 0.05, contrast: 0.05, saturation: 0.35 }, + ], + }, + { + name: 'Yards', + ops: [ + { type: 'sepia', intensity: 0.25 }, + { type: 'adjust_color', brightness: 0.2, contrast: 0.2, saturation: 0.05 }, + { type: 'hue_rotate', angle: -15 }, + ], + }, + { + name: 'Quill', + ops: [{ type: 'adjust_color', brightness: 0.25, contrast: -0.15, saturation: -1 }], + }, + { + name: 'Juno', + ops: [ + { type: 'sepia', intensity: 0.35 }, + { type: 'adjust_color', brightness: 0.15, contrast: 0.15, saturation: 0.8 }, + ], + }, + { + name: 'Rankine', + ops: [ + { type: 'sepia', intensity: 0.15 }, + { type: 'adjust_color', brightness: 0.1, contrast: 0.5, saturation: 0 }, + { type: 'hue_rotate', angle: -10 }, + ], + }, + { + name: 'Mark', + ops: [ + { type: 'sepia', intensity: 0.25 }, + { type: 'adjust_color', brightness: 0.3, contrast: 0.2, saturation: 0.25 }, + ], + }, + { + name: 'Chill', + ops: [{ type: 'adjust_color', brightness: 0, contrast: 0.5, saturation: 0.1 }], + }, + { + name: 'Van', + ops: [ + { type: 'sepia', intensity: 0.25 }, + { type: 'adjust_color', brightness: 0.05, contrast: 0.05, saturation: 1 }, + ], + }, + { + name: 'Apache', + ops: [ + { type: 'sepia', intensity: 0.35 }, + { type: 'adjust_color', brightness: 0.05, contrast: 0.05, saturation: 0.75 }, + ], + }, + { + name: 'May', + ops: [{ type: 'adjust_color', brightness: 0.15, contrast: 0.1, saturation: 0.1 }], + }, + { + name: 'Ceres', + ops: [ + { type: 'adjust_color', brightness: 0.4, contrast: -0.05, saturation: -1 }, + { type: 'sepia', intensity: 0.35 }, + ], + }, + { + name: 'Knoxville', + ops: [ + { type: 'sepia', intensity: 0.25 }, + { type: 'adjust_color', brightness: -0.1, contrast: 0.5, saturation: 0 }, + { type: 'hue_rotate', angle: -15 }, + ], + }, + { + name: 'Felicity', + ops: [{ type: 'adjust_color', brightness: 0.25, contrast: 0.1, saturation: 0.1 }], + }, + { + name: 'Sandblast', + ops: [ + { type: 'sepia', intensity: 0.15 }, + { type: 'adjust_color', brightness: 0.2, contrast: 0, saturation: 0 }, + ], + }, + { + name: 'Daisy', + ops: [ + { type: 'sepia', intensity: 0.75 }, + { type: 'adjust_color', brightness: 0.25, contrast: -0.25, saturation: 0.4 }, + ], + }, + { + name: 'Elevate', + ops: [ + { type: 'sepia', intensity: 0.25 }, + { type: 'adjust_color', brightness: 0.2, contrast: 0.25, saturation: -0.1 }, + ], + }, + { + name: 'Nevada', + ops: [ + { type: 'sepia', intensity: 0.25 }, + { type: 'adjust_color', brightness: -0.1, contrast: 0.5, saturation: 0 }, + { type: 'hue_rotate', angle: -15 }, + ], + }, + { + name: 'Futura', + ops: [ + { type: 'sepia', intensity: 0.15 }, + { type: 'adjust_color', brightness: 0.25, contrast: 0.25, saturation: 0.2 }, + ], + }, + { + name: 'Sleepy', + ops: [ + { type: 'sepia', intensity: 0.15 }, + { type: 'adjust_color', brightness: 0.25, contrast: 0.25, saturation: 0.2 }, + ], + }, + { + name: 'Steward', + ops: [ + { type: 'sepia', intensity: 0.35 }, + { type: 'adjust_color', brightness: 0.25, contrast: 0.1, saturation: 0.25 }, + ], + }, + { + name: 'Savoy', + ops: [ + { type: 'sepia', intensity: 0.4 }, + { type: 'adjust_color', brightness: 0.2, contrast: -0.1, saturation: 0.4 }, + { type: 'hue_rotate', angle: -10 }, + ], + }, + { + name: 'Blaze', + ops: [ + { type: 'sepia', intensity: 0.25 }, + { type: 'adjust_color', brightness: -0.05, contrast: 0.5, saturation: 0 }, + { type: 'hue_rotate', angle: -15 }, + ], + }, + { + name: 'Apricot', + ops: [ + { type: 'sepia', intensity: 0.25 }, + { type: 'adjust_color', brightness: 0.1, contrast: 0.1, saturation: 0 }, + ], + }, + { + name: 'Gloming', + ops: [ + { type: 'sepia', intensity: 0.35 }, + { type: 'adjust_color', brightness: 0.15, contrast: 0.2, saturation: 0.3 }, + ], + }, + { + name: 'Walter', + ops: [ + { type: 'sepia', intensity: 0.35 }, + { type: 'adjust_color', brightness: 0.25, contrast: -0.2, saturation: 0.4 }, + ], + }, + { + name: 'Poplar', + ops: [ + { type: 'adjust_color', brightness: 0.2, contrast: -0.15, saturation: -0.95 }, + { type: 'sepia', intensity: 0.5 }, + ], + }, + { + name: 'Xenon', + ops: [ + { type: 'sepia', intensity: 0.45 }, + { type: 'adjust_color', brightness: 0.75, contrast: 0.25, saturation: 0.3 }, + { type: 'hue_rotate', angle: -5 }, + ], + }, + ] From 639df41093a9865d0413af77c70015ab96d02b94 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 5 Dec 2024 22:36:08 -0700 Subject: [PATCH 023/108] Update DirectMessageController, remove 72h limit for admins --- app/Http/Controllers/DirectMessageController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/DirectMessageController.php b/app/Http/Controllers/DirectMessageController.php index 3f8d61036..b2e0df3a2 100644 --- a/app/Http/Controllers/DirectMessageController.php +++ b/app/Http/Controllers/DirectMessageController.php @@ -307,7 +307,9 @@ class DirectMessageController extends Controller $user = $request->user(); abort_if($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id), 403, 'Invalid permissions for this action'); - abort_if($user->created_at->gt(now()->subHours(72)), 400, 'You need to wait a bit before you can DM another account'); + if (! $user->is_admin) { + abort_if($user->created_at->gt(now()->subHours(72)), 400, 'You need to wait a bit before you can DM another account'); + } $profile = $user->profile; $recipient = Profile::where('id', '!=', $profile->id)->findOrFail($request->input('to_id')); From 543d83e7eda5ca1a72920a235da2ce40b98871ae Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 5 Dec 2024 23:19:08 -0700 Subject: [PATCH 024/108] Fix newlines --- app/Transformer/ActivityPub/StatusTransformer.php | 2 +- app/Transformer/ActivityPub/Verb/CreateNote.php | 2 +- app/Transformer/ActivityPub/Verb/Note.php | 2 +- app/Transformer/ActivityPub/Verb/UpdateNote.php | 2 +- app/Transformer/Api/Mastodon/v1/StatusTransformer.php | 2 +- app/Transformer/Api/StatusStatelessTransformer.php | 2 +- app/Transformer/Api/StatusTransformer.php | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/Transformer/ActivityPub/StatusTransformer.php b/app/Transformer/ActivityPub/StatusTransformer.php index c0c378c7b..ec7a71d6e 100644 --- a/app/Transformer/ActivityPub/StatusTransformer.php +++ b/app/Transformer/ActivityPub/StatusTransformer.php @@ -11,7 +11,7 @@ class StatusTransformer extends Fractal\TransformerAbstract { public function transform(Status $status) { - $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; return [ '@context' => [ diff --git a/app/Transformer/ActivityPub/Verb/CreateNote.php b/app/Transformer/ActivityPub/Verb/CreateNote.php index 287393572..1061dadb7 100644 --- a/app/Transformer/ActivityPub/Verb/CreateNote.php +++ b/app/Transformer/ActivityPub/Verb/CreateNote.php @@ -52,7 +52,7 @@ class CreateNote extends Fractal\TransformerAbstract $emojis = CustomEmoji::scan($status->caption, true) ?? []; $emoji = array_merge($emojis, $mentions); $tags = array_merge($emoji, $hashtags); - $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; return [ '@context' => [ diff --git a/app/Transformer/ActivityPub/Verb/Note.php b/app/Transformer/ActivityPub/Verb/Note.php index 52656efec..0c8e20c03 100644 --- a/app/Transformer/ActivityPub/Verb/Note.php +++ b/app/Transformer/ActivityPub/Verb/Note.php @@ -53,7 +53,7 @@ class Note extends Fractal\TransformerAbstract $emojis = CustomEmoji::scan($status->caption, true) ?? []; $emoji = array_merge($emojis, $mentions); $tags = array_merge($emoji, $hashtags); - $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; return [ '@context' => [ diff --git a/app/Transformer/ActivityPub/Verb/UpdateNote.php b/app/Transformer/ActivityPub/Verb/UpdateNote.php index 09d0b5a2b..07f0c7e11 100644 --- a/app/Transformer/ActivityPub/Verb/UpdateNote.php +++ b/app/Transformer/ActivityPub/Verb/UpdateNote.php @@ -53,7 +53,7 @@ class UpdateNote extends Fractal\TransformerAbstract $emoji = array_merge($emojis, $mentions); $tags = array_merge($emoji, $hashtags); - $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; $latestEdit = $status->edits()->latest()->first(); return [ diff --git a/app/Transformer/Api/Mastodon/v1/StatusTransformer.php b/app/Transformer/Api/Mastodon/v1/StatusTransformer.php index 4516bd874..202b82d54 100644 --- a/app/Transformer/Api/Mastodon/v1/StatusTransformer.php +++ b/app/Transformer/Api/Mastodon/v1/StatusTransformer.php @@ -13,7 +13,7 @@ class StatusTransformer extends Fractal\TransformerAbstract { public function transform(Status $status) { - $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; return [ 'id' => (string) $status->id, diff --git a/app/Transformer/Api/StatusStatelessTransformer.php b/app/Transformer/Api/StatusStatelessTransformer.php index bf6e7597e..db454fc9b 100644 --- a/app/Transformer/Api/StatusStatelessTransformer.php +++ b/app/Transformer/Api/StatusStatelessTransformer.php @@ -23,7 +23,7 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract { $taggedPeople = MediaTagService::get($status->id); $poll = $status->type === 'poll' ? PollService::get($status->id) : null; - $rendered = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $rendered = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; return [ '_v' => 1, diff --git a/app/Transformer/Api/StatusTransformer.php b/app/Transformer/Api/StatusTransformer.php index 6f9364194..a4abfad68 100644 --- a/app/Transformer/Api/StatusTransformer.php +++ b/app/Transformer/Api/StatusTransformer.php @@ -25,7 +25,7 @@ class StatusTransformer extends Fractal\TransformerAbstract $pid = request()->user()->profile_id; $taggedPeople = MediaTagService::get($status->id); $poll = $status->type === 'poll' ? PollService::get($status->id, $pid) : null; - $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; return [ '_v' => 1, From 56c07b7abd3bbbd6eac8343c15c241bc3324f057 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 5 Dec 2024 23:20:19 -0700 Subject: [PATCH 025/108] Update StatusService.php --- app/Services/StatusService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/StatusService.php b/app/Services/StatusService.php index 621574f09..64a434d32 100644 --- a/app/Services/StatusService.php +++ b/app/Services/StatusService.php @@ -10,7 +10,7 @@ use League\Fractal\Serializer\ArraySerializer; class StatusService { - const CACHE_KEY = 'pf:services:status:'; + const CACHE_KEY = 'pf:services:status:v1:'; public static function key($id, $publicOnly = true) { From b21cdc07a0d038f10324090d69dcdc286ee8466a Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 5 Dec 2024 23:23:46 -0700 Subject: [PATCH 026/108] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1730d85fd..510e7e813 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ - Update ApiV1Controller, fix bookmark bug. Closes #5216 ([9f7cc52c](https://github.com/pixelfed/pixelfed/commit/9f7cc52c)) - Update Status caption logic, stop storing duplicate html caption in db and defer to cached StatusService rendering ([9eeb7b67](https://github.com/pixelfed/pixelfed/commit/9eeb7b67)) - Update AutolinkService, optimize lookups ([eac2c196](https://github.com/pixelfed/pixelfed/commit/eac2c196)) +- Update DirectMessageController, remove 72h limit for admins ([639df410](https://github.com/pixelfed/pixelfed/commit/639df410)) +- Update StatusService, fix newlines ([56c07b7a](https://github.com/pixelfed/pixelfed/commit/56c07b7a)) +- ([](https://github.com/pixelfed/pixelfed/commit/)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev) From 45986707149ea2abdaaee0381731f3158dc758bc Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 5 Dec 2024 23:59:10 -0700 Subject: [PATCH 027/108] Update confirm email template, add plaintext link. Fixes #5375 --- resources/views/emails/confirm_email.blade.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resources/views/emails/confirm_email.blade.php b/resources/views/emails/confirm_email.blade.php index ee8dcd2db..17b5e8318 100644 --- a/resources/views/emails/confirm_email.blade.php +++ b/resources/views/emails/confirm_email.blade.php @@ -11,6 +11,12 @@ Confirm Email

This link expires after 24 hours.


+ + If the link above is not working, please copy the following address into your web browser: +

+ {{ $verify->url() }} +
+

Thanks,
{{ config('pixelfed.domain.app') }} From 222d577c91430d81554c7f9ccf841b9da1e6346a Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 5 Dec 2024 23:59:37 -0700 Subject: [PATCH 028/108] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 510e7e813..8c23f2966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ - Update AutolinkService, optimize lookups ([eac2c196](https://github.com/pixelfed/pixelfed/commit/eac2c196)) - Update DirectMessageController, remove 72h limit for admins ([639df410](https://github.com/pixelfed/pixelfed/commit/639df410)) - Update StatusService, fix newlines ([56c07b7a](https://github.com/pixelfed/pixelfed/commit/56c07b7a)) -- ([](https://github.com/pixelfed/pixelfed/commit/)) +- Update confirm email template, add plaintext link. Fixes #5375 ([45986707](https://github.com/pixelfed/pixelfed/commit/45986707)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev) From 1ee9af3772372669891d017a8f4ba0f2030a309f Mon Sep 17 00:00:00 2001 From: Sri Aspari Date: Fri, 6 Dec 2024 14:01:08 +0700 Subject: [PATCH 029/108] Fix sqlite database migration --- .../migrations/2024_05_20_062706_update_group_posts_table.php | 1 + 1 file changed, 1 insertion(+) diff --git a/database/migrations/2024_05_20_062706_update_group_posts_table.php b/database/migrations/2024_05_20_062706_update_group_posts_table.php index 99f272be9..48ecde38b 100644 --- a/database/migrations/2024_05_20_062706_update_group_posts_table.php +++ b/database/migrations/2024_05_20_062706_update_group_posts_table.php @@ -12,6 +12,7 @@ return new class extends Migration public function up(): void { Schema::table('group_posts', function (Blueprint $table) { + $table->dropUnique(['status_id']); $table->dropColumn('status_id'); $table->dropColumn('reply_child_id'); $table->dropColumn('in_reply_to_id'); From 77da9ad8e912f3559204e33d71f9e701029c3efc Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 6 Dec 2024 00:44:16 -0700 Subject: [PATCH 030/108] Update UserVerifyEmail command --- app/Console/Commands/UserVerifyEmail.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/UserVerifyEmail.php b/app/Console/Commands/UserVerifyEmail.php index 3b3cac5ef..b0461ca79 100644 --- a/app/Console/Commands/UserVerifyEmail.php +++ b/app/Console/Commands/UserVerifyEmail.php @@ -5,8 +5,9 @@ namespace App\Console\Commands; use Illuminate\Console\Command; use Illuminate\Support\Str; use App\User; +use Illuminate\Contracts\Console\PromptsForMissingInput; -class UserVerifyEmail extends Command +class UserVerifyEmail extends Command implements PromptsForMissingInput { /** * The name and signature of the console command. @@ -39,13 +40,19 @@ class UserVerifyEmail extends Command */ public function handle() { - $user = User::whereUsername($this->argument('username'))->first(); + $username = $this->argument('username'); + $user = User::whereUsername($username)->first(); if(!$user) { $this->error('Username not found'); return; } + if($user->email_verified_at) { + $this->error('Email already verified ' . $user->email_verified_at->diffForHumans()); + return; + } + $user->email_verified_at = now(); $user->save(); $this->info('Successfully verified email address for ' . $user->username); From 79039ba5957be5f0b87370b5f599badbb9b5b6b8 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 6 Dec 2024 03:15:39 -0700 Subject: [PATCH 031/108] Update StatusStatelessTransformer, refactor the caption field to be compliant with the MastoAPI. Fixes #5364 --- app/Services/StatusService.php | 2 +- app/Transformer/Api/StatusStatelessTransformer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Services/StatusService.php b/app/Services/StatusService.php index 64a434d32..de2f4d112 100644 --- a/app/Services/StatusService.php +++ b/app/Services/StatusService.php @@ -10,7 +10,7 @@ use League\Fractal\Serializer\ArraySerializer; class StatusService { - const CACHE_KEY = 'pf:services:status:v1:'; + const CACHE_KEY = 'pf:services:status:v1.1:'; public static function key($id, $publicOnly = true) { diff --git a/app/Transformer/Api/StatusStatelessTransformer.php b/app/Transformer/Api/StatusStatelessTransformer.php index db454fc9b..9f52ab50a 100644 --- a/app/Transformer/Api/StatusStatelessTransformer.php +++ b/app/Transformer/Api/StatusStatelessTransformer.php @@ -23,7 +23,7 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract { $taggedPeople = MediaTagService::get($status->id); $poll = $status->type === 'poll' ? PollService::get($status->id) : null; - $rendered = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; + $rendered = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ""; return [ '_v' => 1, From 1f30beccf3d2323f25272ea082bd591ab5b0da27 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 6 Dec 2024 03:23:55 -0700 Subject: [PATCH 032/108] Update Transformers to comply with MastoAPI --- app/Transformer/ActivityPub/StatusTransformer.php | 2 +- app/Transformer/ActivityPub/Verb/CreateNote.php | 2 +- app/Transformer/ActivityPub/Verb/Note.php | 2 +- app/Transformer/ActivityPub/Verb/UpdateNote.php | 2 +- app/Transformer/Api/Mastodon/v1/StatusTransformer.php | 2 +- app/Transformer/Api/StatusTransformer.php | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Transformer/ActivityPub/StatusTransformer.php b/app/Transformer/ActivityPub/StatusTransformer.php index ec7a71d6e..c7f61b88b 100644 --- a/app/Transformer/ActivityPub/StatusTransformer.php +++ b/app/Transformer/ActivityPub/StatusTransformer.php @@ -11,7 +11,7 @@ class StatusTransformer extends Fractal\TransformerAbstract { public function transform(Status $status) { - $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ""; return [ '@context' => [ diff --git a/app/Transformer/ActivityPub/Verb/CreateNote.php b/app/Transformer/ActivityPub/Verb/CreateNote.php index 1061dadb7..cf2f0fb51 100644 --- a/app/Transformer/ActivityPub/Verb/CreateNote.php +++ b/app/Transformer/ActivityPub/Verb/CreateNote.php @@ -52,7 +52,7 @@ class CreateNote extends Fractal\TransformerAbstract $emojis = CustomEmoji::scan($status->caption, true) ?? []; $emoji = array_merge($emojis, $mentions); $tags = array_merge($emoji, $hashtags); - $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ""; return [ '@context' => [ diff --git a/app/Transformer/ActivityPub/Verb/Note.php b/app/Transformer/ActivityPub/Verb/Note.php index 0c8e20c03..bc34761cd 100644 --- a/app/Transformer/ActivityPub/Verb/Note.php +++ b/app/Transformer/ActivityPub/Verb/Note.php @@ -53,7 +53,7 @@ class Note extends Fractal\TransformerAbstract $emojis = CustomEmoji::scan($status->caption, true) ?? []; $emoji = array_merge($emojis, $mentions); $tags = array_merge($emoji, $hashtags); - $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ""; return [ '@context' => [ diff --git a/app/Transformer/ActivityPub/Verb/UpdateNote.php b/app/Transformer/ActivityPub/Verb/UpdateNote.php index 07f0c7e11..4199f7230 100644 --- a/app/Transformer/ActivityPub/Verb/UpdateNote.php +++ b/app/Transformer/ActivityPub/Verb/UpdateNote.php @@ -53,7 +53,7 @@ class UpdateNote extends Fractal\TransformerAbstract $emoji = array_merge($emojis, $mentions); $tags = array_merge($emoji, $hashtags); - $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ""; $latestEdit = $status->edits()->latest()->first(); return [ diff --git a/app/Transformer/Api/Mastodon/v1/StatusTransformer.php b/app/Transformer/Api/Mastodon/v1/StatusTransformer.php index 202b82d54..16ff4cc30 100644 --- a/app/Transformer/Api/Mastodon/v1/StatusTransformer.php +++ b/app/Transformer/Api/Mastodon/v1/StatusTransformer.php @@ -13,7 +13,7 @@ class StatusTransformer extends Fractal\TransformerAbstract { public function transform(Status $status) { - $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ""; return [ 'id' => (string) $status->id, diff --git a/app/Transformer/Api/StatusTransformer.php b/app/Transformer/Api/StatusTransformer.php index a4abfad68..4dc08b618 100644 --- a/app/Transformer/Api/StatusTransformer.php +++ b/app/Transformer/Api/StatusTransformer.php @@ -25,7 +25,7 @@ class StatusTransformer extends Fractal\TransformerAbstract $pid = request()->user()->profile_id; $taggedPeople = MediaTagService::get($status->id); $poll = $status->type === 'poll' ? PollService::get($status->id, $pid) : null; - $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : null; + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ""; return [ '_v' => 1, From 8ed8305f5dfeb4183b72a46d0460b3fb1c16e00b Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Fri, 6 Dec 2024 05:04:49 -0700 Subject: [PATCH 033/108] Update ApiV1Controller.php --- app/Http/Controllers/Api/ApiV1Controller.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index 0a2b88c2e..27aebedea 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -1426,6 +1426,8 @@ class ApiV1Controller extends Controller $status['favourited'] = true; $status['favourites_count'] = $status['favourites_count'] + 1; + $status['bookmarked'] = BookmarkService::get($user->profile_id, $status['id']); + $status['reblogged'] = ReblogService::get($user->profile_id, $status['id']); return $this->json($status); } @@ -1484,6 +1486,8 @@ class ApiV1Controller extends Controller $status['favourited'] = false; $status['favourites_count'] = isset($ogStatus) ? $ogStatus->likes_count : $status['favourites_count'] - 1; + $status['bookmarked'] = BookmarkService::get($user->profile_id, $status['id']); + $status['reblogged'] = ReblogService::get($user->profile_id, $status['id']); return $this->json($status); } @@ -3490,7 +3494,7 @@ class ApiV1Controller extends Controller return []; } - $content = $request->filled('status') ? strip_tags(Purify::clean($request->input('status'))) : null; + $content = $request->filled('status') ? strip_tags($request->input('status')) : null; $cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false); $spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null; @@ -3695,6 +3699,8 @@ class ApiV1Controller extends Controller ReblogService::add($user->profile_id, $status->id); $res = StatusService::getMastodon($status->id); $res['reblogged'] = true; + $res['favourited'] = LikeService::liked($user->profile_id, $status->id); + $res['bookmarked'] = BookmarkService::get($user->profile_id, $status->id); return $this->json($res); } @@ -3741,6 +3747,8 @@ class ApiV1Controller extends Controller $res = StatusService::getMastodon($status->id); $res['reblogged'] = false; + $res['favourited'] = LikeService::liked($user->profile_id, $status->id); + $res['bookmarked'] = BookmarkService::get($user->profile_id, $status->id); return $this->json($res); } From 5ffcb5c13c0ffb649470aced6b5f1658dcc1cb62 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 8 Dec 2024 03:24:17 -0700 Subject: [PATCH 034/108] Update user.blade.php --- resources/views/atom/user.blade.php | 88 +++++++++++++++-------------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/resources/views/atom/user.blade.php b/resources/views/atom/user.blade.php index b0c2ee073..badb1f8de 100644 --- a/resources/views/atom/user.blade.php +++ b/resources/views/atom/user.blade.php @@ -1,46 +1,52 @@ -` won't get parsed as short tags */ -'' . PHP_EOL -?> +' . PHP_EOL ?> - {{$permalink}} - {{$profile['username']}} on Pixelfed - {{$profile['note']}} - @if($items && count($items)) -{{$items[0]['created_at']}} - @endif + {{$permalink}} + {{$profile['username']}} on {{config('pixelfed.domain.app', 'Pixelfed')}} + {{strip_tags($profile['note'])}} + @if($items && count($items)) + {{$items[0]['created_at']}} + @endif - - {{$profile['username']}} - {{$profile['url']}} - + + {{$profile['username']}} + {{$profile['url']}} + - - - @if($items && count($items)) - @foreach($items as $item) - - {{ $item['url'] }} - {{ $item['content_text'] ? $item['content_text'] : "No caption" }} - {{ $item['created_at'] }} - - {{$profile['username']}} - {{$profile['url']}} - - - -

{!! $item['content'] !!}

- ]]> -
- - @if($item['content'] && strlen($item['content'])) - {{ $item['content'] }} - @endif - -
- @endforeach - -@endif + + + @if($items && count($items)) + @foreach($items as $item) + + {{ $item['url'] }} + {{ $item['content_text'] ? $item['content_text'] : "No caption" }} + {{ $item['created_at'] }} + + {{$profile['username']}} + {{$profile['url']}} + + + + @foreach($item['media_attachments'] as $media) + {{ $media['description'] ?? '' }} + @endforeach +
+

{!! $item['content'] !!}

+ ]]> + + + @if($item['content'] && strlen($item['content'])) + {{ $item['content'] }} + @endif + @foreach($item['media_attachments'] as $media) + + @endforeach + + @endforeach + @endif From 163fc3805dbe70cf49fb0df2aa96edeb3dfd70b0 Mon Sep 17 00:00:00 2001 From: "root (Deneir)" Date: Mon, 9 Dec 2024 18:28:56 +0100 Subject: [PATCH 035/108] internationalization of site/about, site/fediverse, site/opensource --- resources/lang/de/site.php | 40 +++++++++++++++++++++- resources/lang/en/site.php | 40 +++++++++++++++++++++- resources/views/site/about.blade.php | 41 ++++++++++++----------- resources/views/site/fediverse.blade.php | 10 +++--- resources/views/site/opensource.blade.php | 4 +-- 5 files changed, 106 insertions(+), 29 deletions(-) diff --git a/resources/lang/de/site.php b/resources/lang/de/site.php index d820b0cc6..c6392015f 100644 --- a/resources/lang/de/site.php +++ b/resources/lang/de/site.php @@ -28,4 +28,42 @@ return [ 'Submit' => 'Absenden', 'log_in_to_send_a_message' => 'melde dich an, um eine Nachricht zu senden', 'Please' => 'Bitte', -]; + + // site/about + 'photo_sharing_for_everyone' => 'Fotos teilen. Für Alle', + 'pixelfed_is_an_image_sharing_platform_etc' => 'Pixelfed ist eine Plattform zum Teilen von Bildern. Eine ethische Alternative zu zentralisierten Plattformen', // this is actually never used because it's a fallback for config_cache('app.description') and config_cache('app.short_description') which seem to be impossible to set to empty when saved via /admin/settings?t=branding + 'feature_packed' => 'Voller Funktionen.', + 'the_best_for_the_brightest' => 'Das Beste für die schönsten 📸', + 'albums' => 'Alben', + 'share_posts_with_up_to' => 'Teile Beiträge mit bis zu', + 'photos' => 'Fotos', + 'comments' => 'Kommentare', + 'comment_on_a_post_or_send_a_reply' => 'Kommentiere und beantworte Beiträge', + 'collections' => 'Sammlungen', + 'organize_and_share_collections_of_multiple_posts' => 'Organisiere und teile Sammlungen von mehreren Beiträgen', + 'discover' => 'Entdecken', + 'explore_categories_hashtags_and_topics' => 'Erkunde Kategorien, Hashtags und Themen', + 'photo_filters' => 'Foto-Filter', + 'add_a_special_touch_to_your_photos' => 'Verleih deinen Fotos das gewisse Etwas', + 'stories' => 'Stories', + 'share_moments_with_your_followers_that_disappear_etc' => 'Teile Momente mit deinen Followern für 24 Stunden', + 'people_have_shared' => 'Leute haben', + 'photos_and_videos_on' => 'Fotos und Videos geteilt auf', + 'sign_up_today' => 'Melde dich jetzt an', + 'and_join_our_community_of_photographers_from_etc' => 'und werde Teil unserer Community von Fotograf*innen auf der ganzen Welt.', + + //site/fediverse + 'is_a_portmanteau_of_federation_and_universe_etc' => 'ist ein Kofferwort aus “federation” (Föderation) und “universe” (Universum). Es ist ein gebräuchlicher, unverbindlicher Name für einen Zusammenschluss von Servern sozialer Netzwerke, die auf verschiedene Arten von Medien spezialisiert sind.', + 'supported_fediverse_projects' => 'Unterstützte Fediverse-Projekte', + 'some_of_the_better_known_fediverse_projects_include' => 'Einige der bekanntesten Fediverse-Projekte sind:', + 'a_federated_microblogging_alternative' => 'Eine föderierte Mikroblogging-Alternative', + + // site/opensource + 'the_software_that_powers_this_website_is_called' => 'Die Software, mit der diese Website läuft, heißt', + 'and_anyone_can' => 'und jede*r kann sie', + 'download' => 'herunter laden', + 'opensource.or' => 'oder ihren Quellcode', + 'view' => 'ansehen', + 'the_source_code_and_run_their_own_instance' => 'und eine eigene Instanz betreiben!', + 'open_source_in_pixelfed' => 'Open Source in Pixelfed', +]; \ No newline at end of file diff --git a/resources/lang/en/site.php b/resources/lang/en/site.php index b06c5dcdd..d1eb1be42 100644 --- a/resources/lang/en/site.php +++ b/resources/lang/en/site.php @@ -28,4 +28,42 @@ return [ 'Submit' => 'Submit', 'log_in_to_send_a_message' => 'log in to send a message', 'Please' => 'Please', -]; + + // site/about + 'photo_sharing_for_everyone' => 'Photo Sharing. For Everyone', + 'pixelfed_is_an_image_sharing_platform_etc' => 'Pixelfed is an image sharing platform, an ethical alternative to centralized platforms.', // this is actually never used because it's a fallback for config_cache('app.description') and config_cache('app.short_description') which seem to be impossible to set to empty when saved via /admin/settings?t=branding + 'feature_packed' => 'Feature Packed.', + 'the_best_for_the_brightest' => 'The best for the brightest 📸', + 'albums' => 'Albums', + 'share_posts_with_up_to' => 'Share posts with up to', + 'photos' => 'photos', + 'comments' => 'Comments', + 'comment_on_a_post_or_send_a_reply' => 'Comment on a post, or send a reply', + 'collections' => 'Collections', + 'organize_and_share_collections_of_multiple_posts' => 'Organize and share collections of multiple posts', + 'discover' => 'Discover', + 'explore_categories_hashtags_and_topics' => 'Explore categories, hashtags and topics', + 'photo_filters' => 'Photo Filters', + 'add_a_special_touch_to_your_photos' => 'Add a special touch to your photos', + 'stories' => 'Stories', + 'share_moments_with_your_followers_that_disappear_etc' => 'Share moments with your followers that disappear after 24 hours', + 'people_have_shared' => 'people have shared', + 'photos_and_videos_on' => 'photos and videos on', + 'sign_up_today' => 'Sign up today', + 'and_join_our_community_of_photographers_from_etc' => 'and join our community of photographers from around the world.', + + // site/fediverse + 'is_a_portmanteau_of_federation_and_universe_etc' => 'is a portmanteau of “federation” and “universe”. It is a common, informal name for a federation of social network servers, specializing in different types of media.', + 'supported_fediverse_projects' => 'Supported Fediverse Projects', + 'some_of_the_better_known_fediverse_projects_include' => 'Some of the better known fediverse projects include:', + 'a_federated_microblogging_alternative' => 'A federated microblogging alternative.', + + // site/opensource + 'the_software_that_powers_this_website_is_called' => 'The software that powers this website is called', + 'and_anyone_can' => 'and anyone can', + 'download' => 'download', + 'opensource.or' => 'or', + 'view' => 'view', + 'the_source_code_and_run_their_own_instance' => 'the source code and run their own instance!', + 'open_source_in_pixelfed' => 'Open source in Pixelfed', +]; \ No newline at end of file diff --git a/resources/views/site/about.blade.php b/resources/views/site/about.blade.php index 7f5c3ccbf..85ec2920c 100644 --- a/resources/views/site/about.blade.php +++ b/resources/views/site/about.blade.php @@ -36,10 +36,11 @@ -

{{ config_cache('about.title') ?? 'Photo Sharing. For Everyone' }}

+ +

{{ config_cache('about.title') ?? __('site.photo_sharing_for_everyone') }}

- {!! config_cache('app.description') ?? config_cache('app.short_description') ?? 'Pixelfed is an image sharing platform, an ethical alternative to centralized platforms.'!!} + {!! config_cache('app.description') ?? config_cache('app.short_description') ?? __('site.pixelfed_is_an_image_sharing_platform_etc') !!}

@@ -126,10 +127,10 @@
-

Feature Packed.

+

{{__('site.feature_packed')}}

-

The best for the brightest 📸

+

{{__('site.the_best_for_the_brightest')}}

@@ -137,8 +138,8 @@
-

Albums

-

Share posts with up to {{config_cache('pixelfed.max_album_length')}} photos

+

{{__('site.albums')}}

+

{{__('site.share_posts_with_up_to')}} {{config_cache('pixelfed.max_album_length')}} {{__('site.photos')}}

@@ -152,8 +153,8 @@
-

Comments

-

Comment on a post, or send a reply

+

{{__('site.comments')}}

+

{{__('site.comment_on_a_post_or_send_a_reply')}}

@@ -161,8 +162,8 @@
-

Collections

-

Organize and share collections of multiple posts

+

{{__('site.collections')}}

+

{{__('site.organize_and_share_collections_of_multiple_posts')}}

@@ -176,8 +177,8 @@
-

Discover

-

Explore categories, hashtags and topics

+

{{__('site.discover')}}

+

{{__('site.explore_categories_hashtags_and_topics')}}

@@ -185,8 +186,8 @@
-

Photo Filters

-

Add a special touch to your photos

+

{{__('site.photo_filters')}}

+

{{__('site.add_a_special_touch_to_your_photos')}}

@@ -200,8 +201,8 @@
-

Stories

-

Share moments with your followers that disappear after 24 hours

+

{{__('site.stories')}}

+

{{__('site.share_moments_with_your_followers_that_disappear_etc')}}

@@ -214,15 +215,15 @@

{{$user_count}} - people have shared + {{__('site.people_have_shared')}} {{$post_count}} - photos and videos on {{config_cache('app.name')}}! + {{__('site.photos_and_videos_on')}} {{config_cache('app.name')}}!

@if(config_cache('pixelfed.open_registration'))

- Sign up today - and join our community of photographers from around the world. + {{__('site.sign_up_today')}} + {{__('site.and_join_our_community_of_photographers_from_etc')}}

@endif
diff --git a/resources/views/site/fediverse.blade.php b/resources/views/site/fediverse.blade.php index 0e2e3e761..39fb57ae9 100644 --- a/resources/views/site/fediverse.blade.php +++ b/resources/views/site/fediverse.blade.php @@ -7,15 +7,15 @@

-

Fediverse is a portmanteau of “federation” and “universe”. It is a common, informal name for a federation of social network servers, specializing in different types of media.

-

Supported Fediverse Projects

-

Some of the better known fediverse projects include:

+

Fediverse {{__('site.is_a_portmanteau_of_federation_and_universe_etc')}}

+

{{__('site.supported_fediverse_projects')}}

+

{{__('site.some_of_the_better_known_fediverse_projects_include')}}

    -
  • Mastodon – A federated microblogging alternative.
  • +
  • Mastodon – {{__('site.a_federated_microblogging_alternative')}}
@endsection @push('meta') - + @endpush diff --git a/resources/views/site/opensource.blade.php b/resources/views/site/opensource.blade.php index cb2e7c771..b764f2015 100644 --- a/resources/views/site/opensource.blade.php +++ b/resources/views/site/opensource.blade.php @@ -7,10 +7,10 @@

-

The software that powers this website is called Pixelfed and anyone can download or view the source code and run their own instance!

+

{{__('site.the_software_that_powers_this_website_is_called')}} Pixelfed {{__('site.and_anyone_can')}} {{__('site.download')}} {{__('site.opensource.or')}} {{__('site.view')}} {{__('site.the_source_code_and_run_their_own_instance')}}

@endsection @push('meta') - + @endpush From 567579e5f8965874091277b886e7c0974684de14 Mon Sep 17 00:00:00 2001 From: "root (Deneir)" Date: Tue, 10 Dec 2024 00:38:28 +0100 Subject: [PATCH 036/108] internationalization of settings/home --- resources/lang/de/home.php | 33 +++++++++++++++ resources/lang/en/home.php | 33 +++++++++++++++ resources/views/settings/home.blade.php | 54 ++++++++++++------------- 3 files changed, 93 insertions(+), 27 deletions(-) create mode 100644 resources/lang/de/home.php create mode 100644 resources/lang/en/home.php diff --git a/resources/lang/de/home.php b/resources/lang/de/home.php new file mode 100644 index 000000000..af9896476 --- /dev/null +++ b/resources/lang/de/home.php @@ -0,0 +1,33 @@ + 'Konto-Einstellungen', + 'change_profile_photo' => 'Profilfoto ändern', + 'select_a_profile_photo' => 'Wähle dein Profilfoto', + 'must_be_a_jpeg_or_png_max_avatar_size' => 'Muss jpeg oder png sein. Maximale Größe:', + 'upload' => 'Hochladen', + 'delete_profile_photo' => 'Profilfoto löschen', + 'name' => 'Name', + 'your_name' => 'Dein Name', + 'website' => 'Website', + 'bio' => 'Bio', + 'add_a_bio_here' => 'Deine Biografie', + 'language' => 'Sprache', + 'pronouns' => 'Pronomen', + 'select_pronouns' => 'Wähle Pronomen', + 'select_up_to_4_pronouns_that_will_appear_on_etc' => 'Wähle bis zu 4 Pronomen, die auf deinem Profil angezeigt werden.', + 'account_aliases' => 'Konto-Alias', + 'manage_account_alias' => 'Verwalte dein Aliaskonto', + 'to_move_from_another_account_to_this_one_first_etc' => 'Um von einem anderen Konto hierher zu migrieren, musst du zuerst einen Alias anlegen', + 'account_migrate' => 'Konto-Migration', + 'migrate_to_another_account' => 'Ziehe dein Konto um', + 'to_redirect_this_account_to_a_different_one_etc' => 'Um dieses Konto auf ein anderes umzuleiten (wo es unterstützt wird).', + 'storage_usage' => 'Speichernutzung', + 'storage_used' => 'Belegter Speicher', + 'submit' => 'Absenden', + 'are_you_sure_you_want_to_delete_your_profile_photo' => 'Bist du sicher, dass du dein Profilfoto löschen möchtest?', + 'error' => 'Error', + 'an_error_occured_please_try_again_later' => 'An error occured, please try again later', + +]; \ No newline at end of file diff --git a/resources/lang/en/home.php b/resources/lang/en/home.php new file mode 100644 index 000000000..27ac8d643 --- /dev/null +++ b/resources/lang/en/home.php @@ -0,0 +1,33 @@ + 'Account Settings', + 'change_profile_photo' => 'Change Profile Photo', + 'select_a_profile_photo' => 'Select a profile photo', + 'must_be_a_jpeg_or_png_max_avatar_size' => 'Must be a jpeg or png. Max avatar size:', + 'upload' => 'Upload', + 'delete_profile_photo' => 'Delete Profile Photo', + 'name' => 'Name', + 'your_name' => 'Your Name', + 'website' => 'Website', + 'bio' => 'Bio', + 'add_a_bio_here' => 'Add a bio here', + 'language' => 'Language', + 'pronouns' => 'Pronouns', + 'select_pronouns' => 'Select Pronoun(s)', + 'select_up_to_4_pronouns_that_will_appear_on_etc' => 'Select up to 4 pronouns that will appear on your profile.', + 'account_aliases' => 'Account Aliases', + 'manage_account_alias' => 'Manage account alias', + 'to_move_from_another_account_to_this_one_first_etc' => 'To move from another account to this one, first you need to create an alias.', + 'account_migrate' => 'Account Migrate', + 'migrate_to_another_account' => 'Migrate to another account', + 'to_redirect_this_account_to_a_different_one_etc' => 'To redirect this account to a different one (where supported).', + 'storage_usage' => 'Storage Usage', + 'storage_used' => 'Storage Used', + 'submit' => 'Submit', + 'are_you_sure_you_want_to_delete_your_profile_photo' => 'Are you sure you want to delete your profile photo?', + 'error' => 'Fehler', + 'an_error_occured_please_try_again_later' => 'Es ist ein Fehler aufgetreten, versuche es bitte später noch einmal', + +]; \ No newline at end of file diff --git a/resources/views/settings/home.blade.php b/resources/views/settings/home.blade.php index 1ecd6bca8..ab1a8d4fe 100644 --- a/resources/views/settings/home.blade.php +++ b/resources/views/settings/home.blade.php @@ -3,7 +3,7 @@ @section('section')
-

Account Settings

+

{{__('home.account_settings')}}


@@ -13,7 +13,7 @@

{{Auth::user()->username}}

- +

@@ -21,41 +21,41 @@
- +
-

Must be a jpeg or png. Max avatar size:

+

{{__('home.must_be_a_jpeg_or_png_max_avatar_size')}}

-

+

- Delete Profile Photo + {{__('home.delete_profile_photo')}}

@csrf
- +
- +
- +
- +
- +