diff --git a/.editorconfig b/.editorconfig index 0510b0d44..8b31962a6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,8 +1,8 @@ root = true [*] +indent_style = space indent_size = 4 -indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true diff --git a/CHANGELOG.md b/CHANGELOG.md index 357f69f72..10b13f261 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,89 @@ # Release Notes ## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.9...dev) + +### Added +- Resilient Media Storage ([#4665](https://github.com/pixelfed/pixelfed/pull/4665)) ([fb1deb6](https://github.com/pixelfed/pixelfed/commit/fb1deb6)) +- Video WebP2P ([#4713](https://github.com/pixelfed/pixelfed/pull/4713)) ([0405ef12](https://github.com/pixelfed/pixelfed/commit/0405ef12)) +- Added user:2fa command to easily disable 2FA for given account ([c6408fd7](https://github.com/pixelfed/pixelfed/commit/c6408fd7)) +- Added `avatar:storage-deep-clean` command to dispatch remote avatar storage cleanup jobs ([c37b7cde](https://github.com/pixelfed/pixelfed/commit/c37b7cde)) +- Added S3 command to rewrite media urls ([5b3a5610](https://github.com/pixelfed/pixelfed/commit/5b3a5610)) +- Experimental home feed ([#4752](https://github.com/pixelfed/pixelfed/pull/4752)) ([c39b9afb](https://github.com/pixelfed/pixelfed/commit/c39b9afb)) +- Added `app:hashtag-cached-count-update` command to update cached_count of hashtags and add to scheduler to run every 25 minutes past the hour ([1e31fee6](https://github.com/pixelfed/pixelfed/commit/1e31fee6)) +- Added `app:hashtag-related-generate` command to generate related hashtags ([176b4ed7](https://github.com/pixelfed/pixelfed/commit/176b4ed7)) +- Added Mutual Followers API endpoint ([33dbbe46](https://github.com/pixelfed/pixelfed/commit/33dbbe46)) +- Added User Domain Blocks ([#4834](https://github.com/pixelfed/pixelfed/pull/4834)) ([fa0380ac](https://github.com/pixelfed/pixelfed/commit/fa0380ac)) + +### Federation +- Update Privacy Settings, add support for Mastodon `indexable` search flag ([fc24630e](https://github.com/pixelfed/pixelfed/commit/fc24630e)) +- Update AP Helpers, consume actor `indexable` attribute ([fbdcdd9d](https://github.com/pixelfed/pixelfed/commit/fbdcdd9d)) +- ([](https://github.com/pixelfed/pixelfed/commit/)) + +### Updates +- Update FollowerService, add forget method to RelationshipService call to reduce load when mass purging ([347e4f59](https://github.com/pixelfed/pixelfed/commit/347e4f59)) +- Update FollowServiceWarmCache, improve handling larger following/follower lists ([61a6d904](https://github.com/pixelfed/pixelfed/commit/61a6d904)) +- Update StoryApiV1Controller, add viewers route to view story viewers ([941736ce](https://github.com/pixelfed/pixelfed/commit/941736ce)) +- Update NotificationService, improve cache warming query ([2496386d](https://github.com/pixelfed/pixelfed/commit/2496386d)) +- Update StatusService, hydrate accounts on request instead of caching them along with status objects ([223661ec](https://github.com/pixelfed/pixelfed/commit/223661ec)) +- Update profile embed, fix resize ([dc23c21d](https://github.com/pixelfed/pixelfed/commit/dc23c21d)) +- Update Status model, improve thumb logic ([d969a973](https://github.com/pixelfed/pixelfed/commit/d969a973)) +- Update Status model, allow unlisted thumbnails ([1f0a45b7](https://github.com/pixelfed/pixelfed/commit/1f0a45b7)) +- Update StatusTagsPipeline, fix object tags and slug normalization ([d295e605](https://github.com/pixelfed/pixelfed/commit/d295e605)) +- Update Note and CreateNote transformers, include attachment blurhash, width and height ([ce1afe27](https://github.com/pixelfed/pixelfed/commit/ce1afe27)) +- Update ap helpers, store media attachment width and height if present ([8c969191](https://github.com/pixelfed/pixelfed/commit/8c969191)) +- Update Sign-in with Mastodon, allow usage when registrations are closed ([895dc4fa](https://github.com/pixelfed/pixelfed/commit/895dc4fa)) +- Update profile embeds, filter sensitive posts ([ede5ec3b](https://github.com/pixelfed/pixelfed/commit/ede5ec3b)) +- Update ApiV1Controller, hydrate reblog interactions. Fixes ([#4686](https://github.com/pixelfed/pixelfed/issues/4686)) ([135798eb](https://github.com/pixelfed/pixelfed/commit/135798eb)) +- Update AdminReportController, add `profile_id` to group by. Fixes ([#4685](https://github.com/pixelfed/pixelfed/issues/4685)) ([e4d3b196](https://github.com/pixelfed/pixelfed/commit/e4d3b196)) +- Update user:admin command, improve logic. Fixes ([#2465](https://github.com/pixelfed/pixelfed/issues/2465)) ([01bac511](https://github.com/pixelfed/pixelfed/commit/01bac511)) +- Update AP helpers, adjust RemoteAvatarFetch ttl from 24h to 3 months ([36b23fe3](https://github.com/pixelfed/pixelfed/commit/36b23fe3)) +- Update AvatarPipeline, improve refresh logic and garbage collection to purge old avatars ([82798b5e](https://github.com/pixelfed/pixelfed/commit/82798b5e)) +- Update CreateAvatar job, add processing constraints and set `is_remote` attribute ([319ced40](https://github.com/pixelfed/pixelfed/commit/319ced40)) +- Update RemoteStatusDelete and DecrementPostCount pipelines ([edbcf3ed](https://github.com/pixelfed/pixelfed/commit/edbcf3ed)) +- Update lexer regex, fix mention regex and add more tests ([778e83d3](https://github.com/pixelfed/pixelfed/commit/778e83d3)) +- Update StatusTransformer, generate autolink on request ([dfe2379b](https://github.com/pixelfed/pixelfed/commit/dfe2379b)) +- Update ComposeModal component, fix multi filter bug and allow media re-ordering before upload/posting ([56e315f6](https://github.com/pixelfed/pixelfed/commit/56e315f6)) +- Update ApiV1Dot1Controller, allow iar rate limits to be configurable ([28a80803](https://github.com/pixelfed/pixelfed/commit/28a80803)) +- Update ApiV1Dot1Controller, add domain to iar redirect ([1f82d47c](https://github.com/pixelfed/pixelfed/commit/1f82d47c)) +- Update ApiV1Dot1Controller, add configurable app confirm rate limit ttl ([4c6a0719](https://github.com/pixelfed/pixelfed/commit/4c6a0719)) +- Update LikePipeline, dispatch to feed queue. Fixes ([#4723](https://github.com/pixelfed/pixelfed/issues/4723)) ([da510089](https://github.com/pixelfed/pixelfed/commit/da510089)) +- Update AccountImport ([5a2d7e3e](https://github.com/pixelfed/pixelfed/commit/5a2d7e3e)) +- Update ImportPostController, fix IG bug with missing spaces between hashtags ([9c24157a](https://github.com/pixelfed/pixelfed/commit/9c24157a)) +- Update ApiV1Controller, fix mutes in home feed ([ddc21714](https://github.com/pixelfed/pixelfed/commit/ddc21714)) +- Update AP helpers, improve preferredUsername validation ([21218c79](https://github.com/pixelfed/pixelfed/commit/21218c79)) +- Update delete pipelines, properly invoke StatusHashtag delete events ([ce54d29c](https://github.com/pixelfed/pixelfed/commit/ce54d29c)) +- Update mail config ([0e431271](https://github.com/pixelfed/pixelfed/commit/0e431271)) +- Update hashtag following ([015b1b80](https://github.com/pixelfed/pixelfed/commit/015b1b80)) +- Update IncrementPostCount job, prevent overlap ([b2c9cc23](https://github.com/pixelfed/pixelfed/commit/b2c9cc23)) +- Update HashtagFollowService, fix cache invalidation bug ([84f4e885](https://github.com/pixelfed/pixelfed/commit/84f4e885)) +- Update Experimental Home Feed, fix remote posts, shares and reblogs ([c6a6b3ae](https://github.com/pixelfed/pixelfed/commit/c6a6b3ae)) +- Update HashtagService, improve count perf ([3327a008](https://github.com/pixelfed/pixelfed/commit/3327a008)) +- Update StatusHashtagService, remove problematic cache layer ([e5401f85](https://github.com/pixelfed/pixelfed/commit/e5401f85)) +- Update HomeFeedPipeline, fix tag filtering ([f105f4e8](https://github.com/pixelfed/pixelfed/commit/f105f4e8)) +- Update HashtagService, reduce cached_count cache ttl ([15f29f7d](https://github.com/pixelfed/pixelfed/commit/15f29f7d)) +- Update ApiV1Controller, fix include_reblogs param on timelines/home endpoint, and improve limit pagination logic ([287f903b](https://github.com/pixelfed/pixelfed/commit/287f903b)) +- Update StoryApiV1Controller, add self-carousel endpoint. Fixes ([#4352](https://github.com/pixelfed/pixelfed/issues/4352)) ([bcb88d5b](https://github.com/pixelfed/pixelfed/commit/bcb88d5b)) +- Update FollowServiceWarmCache, use more efficient query ([fe9b4c5a](https://github.com/pixelfed/pixelfed/commit/fe9b4c5a)) +- Update HomeFeedPipeline, observe mutes/blocks during fanout ([8548294c](https://github.com/pixelfed/pixelfed/commit/8548294c)) +- Update FederationController, add proper following/follower counts ([3204fb96](https://github.com/pixelfed/pixelfed/commit/3204fb96)) +- Update FederationController, add proper statuses counts ([3204fb96](https://github.com/pixelfed/pixelfed/commit/3204fb96)) +- Update Inbox handler, fix missing object_url and uri fields for direct statuses ([a0157fce](https://github.com/pixelfed/pixelfed/commit/a0157fce)) +- Update DirectMessageController, deliver direct delete activities to user inbox instead of sharedInbox ([d848792a](https://github.com/pixelfed/pixelfed/commit/d848792a)) +- Update DirectMessageController, dispatch deliver and delete actions to the job queue ([7f462a80](https://github.com/pixelfed/pixelfed/commit/7f462a80)) +- Update Inbox, improve story attribute collection ([06bee36c](https://github.com/pixelfed/pixelfed/commit/06bee36c)) +- Update DirectMessageController, dispatch local deletes to pipeline ([98186564](https://github.com/pixelfed/pixelfed/commit/98186564)) +- Update StatusPipeline, fix Direct and Story notification deletion ([4c95306f](https://github.com/pixelfed/pixelfed/commit/4c95306f)) +- Update Notifications.vue, fix deprecated DM action links for story activities ([4c3823b0](https://github.com/pixelfed/pixelfed/commit/4c3823b0)) +- Update ComposeModal, fix missing alttext post state ([0a068119](https://github.com/pixelfed/pixelfed/commit/0a068119)) +- Update PhotoAlbumPresenter.vue, fix fullscreen mode ([822e9888](https://github.com/pixelfed/pixelfed/commit/822e9888)) +- Update Timeline.vue, improve CHT pagination ([9c43e7e2](https://github.com/pixelfed/pixelfed/commit/9c43e7e2)) +- Update HomeFeedPipeline, fix StatusService validation ([041c0135](https://github.com/pixelfed/pixelfed/commit/041c0135)) +- Update Inbox, improve tombstone query efficiency ([759a4393](https://github.com/pixelfed/pixelfed/commit/759a4393)) +- Update AccountService, add setLastActive method ([ebbd98e7](https://github.com/pixelfed/pixelfed/commit/ebbd98e7)) +- Update ApiV1Controller, set last_active_at ([b6419545](https://github.com/pixelfed/pixelfed/commit/b6419545)) +- Update AdminShadowFilter, fix deleted profile bug ([a492a95a](https://github.com/pixelfed/pixelfed/commit/a492a95a)) +- Update FollowerService, add $silent param to remove method to more efficently purge relationships ([1664a5bc](https://github.com/pixelfed/pixelfed/commit/1664a5bc)) +- Update AP ProfileTransformer, add published attribute ([adfaa2b1](https://github.com/pixelfed/pixelfed/commit/adfaa2b1)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.11.9 (2023-08-21)](https://github.com/pixelfed/pixelfed/compare/v0.11.8...v0.11.9) diff --git a/app/Console/Commands/AddUserDomainBlock.php b/app/Console/Commands/AddUserDomainBlock.php new file mode 100644 index 000000000..6d5c192bf --- /dev/null +++ b/app/Console/Commands/AddUserDomainBlock.php @@ -0,0 +1,106 @@ +validateDomain($domain); + if(!$domain || empty($domain)) { + $this->error('Invalid domain'); + return; + } + $this->processBlocks($domain); + return; + } + + protected function validateDomain($domain) + { + if(!strpos($domain, '.')) { + return; + } + + if(str_starts_with($domain, 'https://')) { + $domain = str_replace('https://', '', $domain); + } + + if(str_starts_with($domain, 'http://')) { + $domain = str_replace('http://', '', $domain); + } + + $domain = strtolower(parse_url('https://' . $domain, PHP_URL_HOST)); + + $valid = filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME|FILTER_NULL_ON_FAILURE); + if(!$valid) { + return; + } + + if($domain === config('pixelfed.domain.app')) { + $this->error('Invalid domain'); + return; + } + + $confirmed = confirm('Are you sure you want to block ' . $domain . '?'); + if(!$confirmed) { + return; + } + + return $domain; + } + + protected function processBlocks($domain) + { + DefaultDomainBlock::updateOrCreate([ + 'domain' => $domain + ]); + progress( + label: 'Updating user domain blocks...', + steps: User::lazyById(500), + callback: fn ($user) => $this->performTask($user, $domain), + ); + } + + protected function performTask($user, $domain) + { + if(!$user->profile_id || $user->delete_after) { + return; + } + + if($user->status != null && $user->status != 'disabled') { + return; + } + + UserDomainBlock::updateOrCreate([ + 'profile_id' => $user->profile_id, + 'domain' => $domain + ]); + } +} diff --git a/app/Console/Commands/AvatarStorageDeepClean.php b/app/Console/Commands/AvatarStorageDeepClean.php new file mode 100644 index 000000000..5840142f5 --- /dev/null +++ b/app/Console/Commands/AvatarStorageDeepClean.php @@ -0,0 +1,115 @@ +info(' ____ _ ______ __ '); + $this->info(' / __ \(_) _____ / / __/__ ____/ / '); + $this->info(' / /_/ / / |/_/ _ \/ / /_/ _ \/ __ / '); + $this->info(' / ____/ /> info(' /_/ /_/_/|_|\___/_/_/ \___/\__,_/ '); + $this->info(' '); + $this->info(' Pixelfed Avatar Deep Cleaner'); + $this->line(' '); + $this->info(' Purge/delete old and outdated avatars from remote accounts'); + $this->line(' '); + + $storage = [ + 'cloud' => boolval(config_cache('pixelfed.cloud_storage')), + 'local' => boolval(config_cache('federation.avatars.store_local')) + ]; + + if(!$storage['cloud'] && !$storage['local']) { + $this->error('Remote avatars are not cached locally, there is nothing to purge. Aborting...'); + exit; + } + + $start = 0; + + if(!$this->confirm('Are you sure you want to proceed?')) { + $this->error('Aborting...'); + exit; + } + + if(!$this->activeCheck()) { + $this->info('Found existing deep cleaning job'); + if(!$this->confirm('Do you want to continue where you left off?')) { + $this->error('Aborting...'); + exit; + } else { + $start = Cache::has('cmd:asdp') ? (int) Cache::get('cmd:asdp') : (int) Storage::get('avatar-deep-clean.json'); + + if($start && $start < 1 || $start > PHP_INT_MAX) { + $this->error('Error fetching cached value'); + $this->error('Aborting...'); + exit; + } + } + } + + $count = Avatar::whereNotNull('cdn_url')->where('is_remote', true)->where('id', '>', $start)->count(); + $bar = $this->output->createProgressBar($count); + + foreach(Avatar::whereNotNull('cdn_url')->where('is_remote', true)->where('id', '>', $start)->lazyById(10, 'id') as $avatar) { + usleep(random_int(50, 1000)); + $this->counter++; + $this->handleAvatar($avatar); + $bar->advance(); + } + $bar->finish(); + } + + protected function updateCache($id) + { + Cache::put('cmd:asdp', $id); + if($this->counter % 5 === 0) { + Storage::put('avatar-deep-clean.json', $id); + } + } + + protected function activeCheck() + { + if(Storage::exists('avatar-deep-clean.json') || Cache::has('cmd:asdp')) { + return false; + } + + return true; + } + + protected function handleAvatar($avatar) + { + $this->updateCache($avatar->id); + $queues = ['feed', 'mmo', 'feed', 'mmo', 'feed', 'feed', 'mmo', 'low']; + $queue = $queues[random_int(0, 7)]; + AvatarStorageCleanup::dispatch($avatar)->onQueue($queue); + } +} diff --git a/app/Console/Commands/DeleteUserDomainBlock.php b/app/Console/Commands/DeleteUserDomainBlock.php new file mode 100644 index 000000000..405b6fe76 --- /dev/null +++ b/app/Console/Commands/DeleteUserDomainBlock.php @@ -0,0 +1,96 @@ +validateDomain($domain); + if(!$domain || empty($domain)) { + $this->error('Invalid domain'); + return; + } + $this->processUnblocks($domain); + return; + } + + protected function validateDomain($domain) + { + if(!strpos($domain, '.')) { + return; + } + + if(str_starts_with($domain, 'https://')) { + $domain = str_replace('https://', '', $domain); + } + + if(str_starts_with($domain, 'http://')) { + $domain = str_replace('http://', '', $domain); + } + + $domain = strtolower(parse_url('https://' . $domain, PHP_URL_HOST)); + + $valid = filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME|FILTER_NULL_ON_FAILURE); + if(!$valid) { + return; + } + + if($domain === config('pixelfed.domain.app')) { + return; + } + + $confirmed = confirm('Are you sure you want to unblock ' . $domain . '?'); + if(!$confirmed) { + return; + } + + return $domain; + } + + protected function processUnblocks($domain) + { + DefaultDomainBlock::whereDomain($domain)->delete(); + if(!UserDomainBlock::whereDomain($domain)->count()) { + $this->info('No results found!'); + return; + } + progress( + label: 'Updating user domain blocks...', + steps: UserDomainBlock::whereDomain($domain)->lazyById(500), + callback: fn ($domainBlock) => $this->performTask($domainBlock), + ); + } + + protected function performTask($domainBlock) + { + $domainBlock->deleteQuietly(); + } +} diff --git a/app/Console/Commands/HashtagCachedCountUpdate.php b/app/Console/Commands/HashtagCachedCountUpdate.php new file mode 100644 index 000000000..49f354e2b --- /dev/null +++ b/app/Console/Commands/HashtagCachedCountUpdate.php @@ -0,0 +1,57 @@ +option('limit'); + $tags = Hashtag::whereNull('cached_count')->limit($limit)->get(); + $count = count($tags); + if(!$count) { + return; + } + + $bar = $this->output->createProgressBar($count); + $bar->start(); + + foreach($tags as $tag) { + $count = DB::table('status_hashtags')->whereHashtagId($tag->id)->count(); + if(!$count) { + $tag->cached_count = 0; + $tag->saveQuietly(); + $bar->advance(); + continue; + } + $tag->cached_count = $count; + $tag->saveQuietly(); + $bar->advance(); + } + $bar->finish(); + $this->line(' '); + return; + } +} diff --git a/app/Console/Commands/HashtagRelatedGenerate.php b/app/Console/Commands/HashtagRelatedGenerate.php new file mode 100644 index 000000000..26fdb8b52 --- /dev/null +++ b/app/Console/Commands/HashtagRelatedGenerate.php @@ -0,0 +1,94 @@ + 'Which hashtag should we generate related tags for?', + ]; + } + + /** + * Execute the console command. + */ + public function handle() + { + $tag = $this->argument('tag'); + $hashtag = Hashtag::whereName($tag)->orWhere('slug', $tag)->first(); + if(!$hashtag) { + $this->error('Hashtag not found, aborting...'); + exit; + } + + $exists = HashtagRelated::whereHashtagId($hashtag->id)->exists(); + + if($exists) { + $confirmed = confirm('Found existing related tags, do you want to regenerate them?'); + if(!$confirmed) { + $this->error('Aborting...'); + exit; + } + } + + $this->info('Looking up #' . $tag . '...'); + + $tags = StatusHashtag::whereHashtagId($hashtag->id)->count(); + if(!$tags || $tags < 100) { + $this->error('Not enough posts found to generate related hashtags!'); + exit; + } + + $this->info('Found ' . $tags . ' posts that use that hashtag'); + $related = collect(HashtagRelatedService::fetchRelatedTags($tag)); + + $selected = multiselect( + label: 'Which tags do you want to generate?', + options: $related->pluck('name'), + required: true, + ); + + $filtered = $related->filter(fn($i) => in_array($i['name'], $selected))->all(); + $agg_score = $related->filter(fn($i) => in_array($i['name'], $selected))->sum('related_count'); + + HashtagRelated::updateOrCreate([ + 'hashtag_id' => $hashtag->id, + ], [ + 'related_tags' => array_values($filtered), + 'agg_score' => $agg_score, + 'last_calculated_at' => now() + ]); + + $this->info('Finished!'); + } +} diff --git a/app/Console/Commands/MediaCloudUrlRewrite.php b/app/Console/Commands/MediaCloudUrlRewrite.php new file mode 100644 index 000000000..54329f7c7 --- /dev/null +++ b/app/Console/Commands/MediaCloudUrlRewrite.php @@ -0,0 +1,140 @@ + 'The old S3 domain', + 'newDomain' => 'The new S3 domain' + ]; + } + /** + * The console command description. + * + * @var string + */ + protected $description = 'Rewrite S3 media urls from local users'; + + /** + * Execute the console command. + */ + public function handle() + { + $this->preflightCheck(); + $this->bootMessage(); + $this->confirmCloudUrl(); + } + + protected function preflightCheck() + { + if(config_cache('pixelfed.cloud_storage') != true) { + $this->info('Error: Cloud storage is not enabled!'); + $this->error('Aborting...'); + exit; + } + } + + protected function bootMessage() + { + $this->info(' ____ _ ______ __ '); + $this->info(' / __ \(_) _____ / / __/__ ____/ / '); + $this->info(' / /_/ / / |/_/ _ \/ / /_/ _ \/ __ / '); + $this->info(' / ____/ /> info(' /_/ /_/_/|_|\___/_/_/ \___/\__,_/ '); + $this->info(' '); + $this->info(' Media Cloud Url Rewrite Tool'); + $this->info(' ==='); + $this->info(' Old S3: ' . trim($this->argument('oldDomain'))); + $this->info(' New S3: ' . trim($this->argument('newDomain'))); + $this->info(' '); + } + + protected function confirmCloudUrl() + { + $disk = Storage::disk(config('filesystems.cloud'))->url('test'); + $domain = parse_url($disk, PHP_URL_HOST); + if(trim($this->argument('newDomain')) !== $domain) { + $this->error('Error: The new S3 domain you entered is not currently configured'); + exit; + } + + if(!$this->confirm('Confirm this is correct')) { + $this->error('Aborting...'); + exit; + } + + $this->updateUrls(); + } + + protected function updateUrls() + { + $this->info('Updating urls...'); + $oldDomain = trim($this->argument('oldDomain')); + $newDomain = trim($this->argument('newDomain')); + $disk = Storage::disk(config('filesystems.cloud')); + $count = Media::whereNotNull('cdn_url')->count(); + $bar = $this->output->createProgressBar($count); + $counter = 0; + $bar->start(); + foreach(Media::whereNotNull('cdn_url')->lazyById(1000, 'id') as $media) { + if(strncmp($media->media_path, 'http', 4) === 0) { + $bar->advance(); + continue; + } + $cdnHost = parse_url($media->cdn_url, PHP_URL_HOST); + if($oldDomain != $cdnHost || $newDomain == $cdnHost) { + $bar->advance(); + continue; + } + + $media->cdn_url = str_replace($oldDomain, $newDomain, $media->cdn_url); + + if($media->thumbnail_url != null) { + $thumbHost = parse_url($media->thumbnail_url, PHP_URL_HOST); + if($thumbHost == $oldDomain) { + $thumbUrl = $disk->url($media->thumbnail_path); + $media->thumbnail_url = $thumbUrl; + } + } + + if($media->optimized_url != null) { + $optiHost = parse_url($media->optimized_url, PHP_URL_HOST); + if($optiHost == $oldDomain) { + $optiUrl = str_replace($oldDomain, $newDomain, $media->optimized_url); + $media->optimized_url = $optiUrl; + } + } + + $media->save(); + $counter++; + $bar->advance(); + } + + $bar->finish(); + + $this->line(' '); + $this->info('Finished! Updated ' . $counter . ' total records!'); + $this->line(' '); + $this->info('Tip: Run `php artisan cache:clear` to purge cached urls'); + } +} diff --git a/app/Console/Commands/NotificationEpochUpdate.php b/app/Console/Commands/NotificationEpochUpdate.php new file mode 100644 index 000000000..e606b47ad --- /dev/null +++ b/app/Console/Commands/NotificationEpochUpdate.php @@ -0,0 +1,31 @@ + 'Which username should we toggle admin privileges for?', + ]; } /** @@ -38,16 +41,15 @@ class UserAdmin extends Command */ public function handle() { - $id = $this->argument('id'); - if(ctype_digit($id) == true) { - $user = User::find($id); - } else { - $user = User::whereUsername($id)->first(); - } + $id = $this->argument('username'); + + $user = User::whereUsername($id)->first(); + if(!$user) { $this->error('Could not find any user with that username or id.'); exit; } + $this->info('Found username: ' . $user->username); $state = $user->is_admin ? 'Remove admin privileges from this user?' : 'Add admin privileges to this user?'; $confirmed = $this->confirm($state); diff --git a/app/Console/Commands/UserToggle2FA.php b/app/Console/Commands/UserToggle2FA.php new file mode 100644 index 000000000..eed6843da --- /dev/null +++ b/app/Console/Commands/UserToggle2FA.php @@ -0,0 +1,61 @@ + 'Which username should we disable 2FA for?', + ]; + } + + /** + * Execute the console command. + */ + public function handle() + { + $user = User::whereUsername($this->argument('username'))->first(); + + if(!$user) { + $this->error('Could not find any user with that username'); + exit; + } + + if(!$user->{'2fa_enabled'}) { + $this->info('User did not have 2FA enabled!'); + return; + } + + $user->{'2fa_enabled'} = false; + $user->{'2fa_secret'} = null; + $user->{'2fa_backup_codes'} = null; + $user->save(); + + $this->info('Successfully disabled 2FA on this account!'); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 2b6510e35..8c2726b89 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -43,6 +43,9 @@ class Kernel extends ConsoleKernel $schedule->command('app:import-remove-deleted-accounts')->hourlyAt(37)->onOneServer(); $schedule->command('app:import-upload-clean-storage')->twiceDailyAt(1, 13, 32)->onOneServer(); } + + $schedule->command('app:notification-epoch-update')->weeklyOn(1, '2:21'); + $schedule->command('app:hashtag-cached-count-update')->hourlyAt(25); } /** diff --git a/app/Http/Controllers/Admin/AdminReportController.php b/app/Http/Controllers/Admin/AdminReportController.php index 4924acfa8..ac238f28c 100644 --- a/app/Http/Controllers/Admin/AdminReportController.php +++ b/app/Http/Controllers/Admin/AdminReportController.php @@ -643,7 +643,7 @@ trait AdminReportController $q->whereNull('admin_seen') : $q->whereNotNull('admin_seen'); }) - ->groupBy(['object_id', 'object_type']) + ->groupBy(['id', 'object_id', 'object_type', 'profile_id']) ->cursorPaginate(6) ->withQueryString() ); diff --git a/app/Http/Controllers/AdminShadowFilterController.php b/app/Http/Controllers/AdminShadowFilterController.php new file mode 100644 index 000000000..e181be5c1 --- /dev/null +++ b/app/Http/Controllers/AdminShadowFilterController.php @@ -0,0 +1,123 @@ +middleware(['auth','admin']); + } + + public function home(Request $request) + { + $filter = $request->input('filter'); + $searchQuery = $request->input('q'); + $filters = AdminShadowFilter::whereHas('profile') + ->when($filter, function($q, $filter) { + if($filter == 'all') { + return $q; + } else if($filter == 'inactive') { + return $q->whereActive(false); + } else { + return $q; + } + }, function($q, $filter) { + return $q->whereActive(true); + }) + ->when($searchQuery, function($q, $searchQuery) { + $ids = Profile::where('username', 'like', '%' . $searchQuery . '%') + ->limit(100) + ->pluck('id') + ->toArray(); + return $q->where('item_type', 'App\Profile')->whereIn('item_id', $ids); + }) + ->latest() + ->paginate(10) + ->withQueryString(); + + return view('admin.asf.home', compact('filters')); + } + + public function create(Request $request) + { + return view('admin.asf.create'); + } + + public function edit(Request $request, $id) + { + $filter = AdminShadowFilter::findOrFail($id); + $profile = AccountService::get($filter->item_id); + return view('admin.asf.edit', compact('filter', 'profile')); + } + + public function store(Request $request) + { + $this->validate($request, [ + 'username' => 'required', + 'active' => 'sometimes', + 'note' => 'sometimes', + 'hide_from_public_feeds' => 'sometimes' + ]); + + $profile = Profile::whereUsername($request->input('username'))->first(); + + if(!$profile) { + return back()->withErrors(['Invalid account']); + } + + if($profile->user && $profile->user->is_admin) { + return back()->withErrors(['Cannot filter an admin account']); + } + + $active = $request->has('active') && $request->has('hide_from_public_feeds'); + + AdminShadowFilter::updateOrCreate([ + 'item_id' => $profile->id, + 'item_type' => get_class($profile) + ], [ + 'is_local' => $profile->domain === null, + 'note' => $request->input('note'), + 'hide_from_public_feeds' => $request->has('hide_from_public_feeds'), + 'admin_id' => $request->user()->profile_id, + 'active' => $active + ]); + + AdminShadowFilterService::refresh(); + + return redirect('/i/admin/asf/home'); + } + + public function storeEdit(Request $request, $id) + { + $this->validate($request, [ + 'active' => 'sometimes', + 'note' => 'sometimes', + 'hide_from_public_feeds' => 'sometimes' + ]); + + $filter = AdminShadowFilter::findOrFail($id); + + $profile = Profile::findOrFail($filter->item_id); + + if($profile->user && $profile->user->is_admin) { + return back()->withErrors(['Cannot filter an admin account']); + } + + $active = $request->has('active'); + $filter->active = $active; + $filter->hide_from_public_feeds = $request->has('hide_from_public_feeds'); + $filter->note = $request->input('note'); + $filter->save(); + + AdminShadowFilterService::refresh(); + + return redirect('/i/admin/asf/home'); + } +} diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index 92d5d43ee..798d9ee55 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -11,35 +11,36 @@ use Laravel\Passport\Passport; use Auth, Cache, DB, Storage, URL; use Illuminate\Support\Facades\Redis; use App\{ - Avatar, - Bookmark, - Collection, - CollectionItem, - DirectMessage, - Follower, - FollowRequest, - Hashtag, - HashtagFollow, - Instance, - Like, - Media, - Notification, - Profile, - Status, - StatusHashtag, - User, - UserSetting, - UserFilter, + Avatar, + Bookmark, + Collection, + CollectionItem, + DirectMessage, + Follower, + FollowRequest, + Hashtag, + HashtagFollow, + Instance, + Like, + Media, + Notification, + Profile, + Status, + StatusHashtag, + User, + UserSetting, + UserFilter, }; +use App\Models\UserDomainBlock; use League\Fractal; use App\Transformer\Api\Mastodon\v1\{ - AccountTransformer, - MediaTransformer, - NotificationTransformer, - StatusTransformer, + AccountTransformer, + MediaTransformer, + NotificationTransformer, + StatusTransformer, }; use App\Transformer\Api\{ - RelationshipTransformer, + RelationshipTransformer, }; use App\Http\Controllers\FollowerController; use League\Fractal\Serializer\ArraySerializer; @@ -59,33 +60,35 @@ use App\Jobs\FollowPipeline\FollowPipeline; use App\Jobs\FollowPipeline\UnfollowPipeline; use App\Jobs\ImageOptimizePipeline\ImageOptimize; use App\Jobs\VideoPipeline\{ - VideoOptimize, - VideoPostProcess, - VideoThumbnail + VideoOptimize, + VideoPostProcess, + VideoThumbnail }; use App\Services\{ - AccountService, - BookmarkService, - BouncerService, - CollectionService, - FollowerService, - HashtagService, - InstanceService, - LikeService, - NetworkTimelineService, - NotificationService, - MediaService, - MediaPathService, + AccountService, + BookmarkService, + BouncerService, + CollectionService, + FollowerService, + HashtagService, + HashtagFollowService, + HomeTimelineService, + InstanceService, + LikeService, + NetworkTimelineService, + NotificationService, + MediaService, + MediaPathService, ProfileStatusService, - PublicTimelineService, - ReblogService, - RelationshipService, - SearchApiV2Service, - StatusService, - MediaBlocklistService, - SnowflakeService, - UserFilterService + PublicTimelineService, + ReblogService, + RelationshipService, + SearchApiV2Service, + StatusService, + MediaBlocklistService, + SnowflakeService, + UserFilterService }; use App\Util\Lexer\Autolink; use App\Util\Lexer\PrettyNumber; @@ -102,347 +105,351 @@ use Illuminate\Support\Facades\RateLimiter; use Purify; use Carbon\Carbon; use App\Http\Resources\MastoApi\FollowedTagResource; +use App\Jobs\HomeFeedPipeline\FeedWarmCachePipeline; +use App\Jobs\HomeFeedPipeline\HashtagUnfollowPipeline; class ApiV1Controller extends Controller { - protected $fractal; - const PF_API_ENTITY_KEY = "_pe"; + protected $fractal; + const PF_API_ENTITY_KEY = "_pe"; - public function __construct() - { - $this->fractal = new Fractal\Manager(); - $this->fractal->setSerializer(new ArraySerializer()); - } + public function __construct() + { + $this->fractal = new Fractal\Manager(); + $this->fractal->setSerializer(new ArraySerializer()); + } - public function json($res, $code = 200, $headers = []) - { - return response()->json($res, $code, $headers, JSON_UNESCAPED_SLASHES); - } + public function json($res, $code = 200, $headers = []) + { + return response()->json($res, $code, $headers, JSON_UNESCAPED_SLASHES); + } - public function getApp(Request $request) - { - if(!$request->user()) { - return response('', 403); - } + public function getApp(Request $request) + { + if(!$request->user()) { + return response('', 403); + } - $client = $request->user()->token()->client; - $res = [ - 'name' => $client->name, - 'website' => null, - 'vapid_key' => null - ]; + $client = $request->user()->token()->client; + $res = [ + 'name' => $client->name, + 'website' => null, + 'vapid_key' => null + ]; - return $this->json($res); - } + return $this->json($res); + } - public function apps(Request $request) - { - abort_if(!config_cache('pixelfed.oauth_enabled'), 404); + public function apps(Request $request) + { + abort_if(!config_cache('pixelfed.oauth_enabled'), 404); - $this->validate($request, [ - 'client_name' => 'required', - 'redirect_uris' => 'required' - ]); + $this->validate($request, [ + 'client_name' => 'required', + 'redirect_uris' => 'required' + ]); - $uris = implode(',', explode('\n', $request->redirect_uris)); + $uris = implode(',', explode('\n', $request->redirect_uris)); - $client = Passport::client()->forceFill([ - 'user_id' => null, - 'name' => e($request->client_name), - 'secret' => Str::random(40), - 'redirect' => $uris, - 'personal_access_client' => false, - 'password_client' => false, - 'revoked' => false, - ]); + $client = Passport::client()->forceFill([ + 'user_id' => null, + 'name' => e($request->client_name), + 'secret' => Str::random(40), + 'redirect' => $uris, + 'personal_access_client' => false, + 'password_client' => false, + 'revoked' => false, + ]); - $client->save(); + $client->save(); - $res = [ - 'id' => (string) $client->id, - 'name' => $client->name, - 'website' => null, - 'redirect_uri' => $client->redirect, - 'client_id' => (string) $client->id, - 'client_secret' => $client->secret, - 'vapid_key' => null - ]; + $res = [ + 'id' => (string) $client->id, + 'name' => $client->name, + 'website' => null, + 'redirect_uri' => $client->redirect, + 'client_id' => (string) $client->id, + 'client_secret' => $client->secret, + 'vapid_key' => null + ]; - return $this->json($res, 200, [ - 'Access-Control-Allow-Origin' => '*' - ]); - } + return $this->json($res, 200, [ + 'Access-Control-Allow-Origin' => '*' + ]); + } - /** - * GET /api/v1/accounts/verify_credentials - * - * - * @return \App\Transformer\Api\AccountTransformer - */ - public function verifyCredentials(Request $request) - { - $user = $request->user(); + /** + * GET /api/v1/accounts/verify_credentials + * + * + * @return \App\Transformer\Api\AccountTransformer + */ + public function verifyCredentials(Request $request) + { + $user = $request->user(); - abort_if(!$user, 403); - abort_if($user->status != null, 403); + abort_if(!$user, 403); + abort_if($user->status != null, 403); + AccountService::setLastActive($user->id); - $res = $request->has(self::PF_API_ENTITY_KEY) ? AccountService::get($user->profile_id) : AccountService::getMastodon($user->profile_id); + $res = $request->has(self::PF_API_ENTITY_KEY) ? AccountService::get($user->profile_id) : AccountService::getMastodon($user->profile_id); - $res['source'] = [ - 'privacy' => $res['locked'] ? 'private' : 'public', - 'sensitive' => false, - 'language' => $user->language ?? 'en', - 'note' => strip_tags($res['note']), - 'fields' => [] - ]; + $res['source'] = [ + 'privacy' => $res['locked'] ? 'private' : 'public', + 'sensitive' => false, + 'language' => $user->language ?? 'en', + 'note' => strip_tags($res['note']), + 'fields' => [] + ]; - return $this->json($res); - } + return $this->json($res); + } - /** - * GET /api/v1/accounts/{id} - * - * @param integer $id - * - * @return \App\Transformer\Api\AccountTransformer - */ - public function accountById(Request $request, $id) - { - $res = $request->has(self::PF_API_ENTITY_KEY) ? AccountService::get($id, true) : AccountService::getMastodon($id, true); - if(!$res) { - return response()->json(['error' => 'Record not found'], 404); - } - return $this->json($res); - } + /** + * GET /api/v1/accounts/{id} + * + * @param integer $id + * + * @return \App\Transformer\Api\AccountTransformer + */ + public function accountById(Request $request, $id) + { + $res = $request->has(self::PF_API_ENTITY_KEY) ? AccountService::get($id, true) : AccountService::getMastodon($id, true); + if(!$res) { + return response()->json(['error' => 'Record not found'], 404); + } + return $this->json($res); + } - /** - * PATCH /api/v1/accounts/update_credentials - * - * @return \App\Transformer\Api\AccountTransformer - */ - public function accountUpdateCredentials(Request $request) - { - abort_if(!$request->user(), 403); + /** + * PATCH /api/v1/accounts/update_credentials + * + * @return \App\Transformer\Api\AccountTransformer + */ + public function accountUpdateCredentials(Request $request) + { + abort_if(!$request->user(), 403); - if(config('pixelfed.bouncer.cloud_ips.ban_api')) { - abort_if(BouncerService::checkIp($request->ip()), 404); - } + if(config('pixelfed.bouncer.cloud_ips.ban_api')) { + abort_if(BouncerService::checkIp($request->ip()), 404); + } - $this->validate($request, [ - 'avatar' => 'sometimes|mimetypes:image/jpeg,image/png|max:' . config('pixelfed.max_avatar_size'), - 'display_name' => 'nullable|string|max:30', - 'note' => 'nullable|string|max:200', - 'locked' => 'nullable', - 'website' => 'nullable|string|max:120', - // 'source.privacy' => 'nullable|in:unlisted,public,private', - // 'source.sensitive' => 'nullable|boolean' - ], [ - 'required' => 'The :attribute field is required.', - 'avatar.mimetypes' => 'The file must be in jpeg or png format', - 'avatar.max' => 'The :attribute exceeds the file size limit of ' . PrettyNumber::size(config('pixelfed.max_avatar_size'), true, false), - ]); + $this->validate($request, [ + 'avatar' => 'sometimes|mimetypes:image/jpeg,image/png|max:' . config('pixelfed.max_avatar_size'), + 'display_name' => 'nullable|string|max:30', + 'note' => 'nullable|string|max:200', + 'locked' => 'nullable', + 'website' => 'nullable|string|max:120', + // 'source.privacy' => 'nullable|in:unlisted,public,private', + // 'source.sensitive' => 'nullable|boolean' + ], [ + 'required' => 'The :attribute field is required.', + 'avatar.mimetypes' => 'The file must be in jpeg or png format', + 'avatar.max' => 'The :attribute exceeds the file size limit of ' . PrettyNumber::size(config('pixelfed.max_avatar_size'), true, false), + ]); - $user = $request->user(); - $profile = $user->profile; - $settings = $user->settings; + $user = $request->user(); + AccountService::setLastActive($user->id); + $profile = $user->profile; + $settings = $user->settings; - $changes = false; - $other = array_merge(AccountService::defaultSettings()['other'], $settings->other ?? []); - $syncLicenses = false; - $licenseChanged = false; - $composeSettings = array_merge(AccountService::defaultSettings()['compose_settings'], $settings->compose_settings ?? []); + $changes = false; + $other = array_merge(AccountService::defaultSettings()['other'], $settings->other ?? []); + $syncLicenses = false; + $licenseChanged = false; + $composeSettings = array_merge(AccountService::defaultSettings()['compose_settings'], $settings->compose_settings ?? []); - if($request->has('avatar')) { - $av = Avatar::whereProfileId($profile->id)->first(); - if($av) { - $currentAvatar = storage_path('app/'.$av->media_path); - $file = $request->file('avatar'); - $path = "public/avatars/{$profile->id}"; - $name = strtolower(str_random(6)). '.' . $file->guessExtension(); - $request->file('avatar')->storePubliclyAs($path, $name); - $av->media_path = "{$path}/{$name}"; - $av->save(); - Cache::forget("avatar:{$profile->id}"); - Cache::forget('user:account:id:'.$user->id); - AvatarOptimize::dispatch($user->profile, $currentAvatar); - } - $changes = true; - } + if($request->has('avatar')) { + $av = Avatar::whereProfileId($profile->id)->first(); + if($av) { + $currentAvatar = storage_path('app/'.$av->media_path); + $file = $request->file('avatar'); + $path = "public/avatars/{$profile->id}"; + $name = strtolower(str_random(6)). '.' . $file->guessExtension(); + $request->file('avatar')->storePubliclyAs($path, $name); + $av->media_path = "{$path}/{$name}"; + $av->save(); + Cache::forget("avatar:{$profile->id}"); + Cache::forget('user:account:id:'.$user->id); + AvatarOptimize::dispatch($user->profile, $currentAvatar); + } + $changes = true; + } - if($request->has('source[language]')) { - $lang = $request->input('source[language]'); - if(in_array($lang, Localization::languages())) { - $user->language = $lang; - $changes = true; - $other['language'] = $lang; - } - } + if($request->has('source[language]')) { + $lang = $request->input('source[language]'); + if(in_array($lang, Localization::languages())) { + $user->language = $lang; + $changes = true; + $other['language'] = $lang; + } + } - if($request->has('website')) { - $website = $request->input('website'); - if($website != $profile->website) { - if($website) { - if(!strpos($website, '.')) { - $website = null; - } + if($request->has('website')) { + $website = $request->input('website'); + if($website != $profile->website) { + if($website) { + if(!strpos($website, '.')) { + $website = null; + } - if($website && !strpos($website, '://')) { - $website = 'https://' . $website; - } + if($website && !strpos($website, '://')) { + $website = 'https://' . $website; + } - $host = parse_url($website, PHP_URL_HOST); + $host = parse_url($website, PHP_URL_HOST); - $bannedInstances = InstanceService::getBannedDomains(); - if(in_array($host, $bannedInstances)) { - $website = null; - } - } - $profile->website = $website ? $website : null; - $changes = true; - } - } + $bannedInstances = InstanceService::getBannedDomains(); + if(in_array($host, $bannedInstances)) { + $website = null; + } + } + $profile->website = $website ? $website : null; + $changes = true; + } + } - if($request->has('display_name')) { - $displayName = $request->input('display_name'); - if($displayName !== $user->name) { - $user->name = $displayName; - $profile->name = $displayName; - $changes = true; - } - } + if($request->has('display_name')) { + $displayName = $request->input('display_name'); + if($displayName !== $user->name) { + $user->name = $displayName; + $profile->name = $displayName; + $changes = true; + } + } - if($request->has('note')) { - $note = $request->input('note'); - if($note !== strip_tags($profile->bio)) { - $profile->bio = Autolink::create()->autolink(strip_tags($note)); - $changes = true; - } - } + if($request->has('note')) { + $note = $request->input('note'); + if($note !== strip_tags($profile->bio)) { + $profile->bio = Autolink::create()->autolink(strip_tags($note)); + $changes = true; + } + } - if($request->has('locked')) { - $locked = $request->input('locked') == 'true'; - if($profile->is_private != $locked) { - $profile->is_private = $locked; - $changes = true; - } - } + if($request->has('locked')) { + $locked = $request->input('locked') == 'true'; + if($profile->is_private != $locked) { + $profile->is_private = $locked; + $changes = true; + } + } - if($request->has('reduce_motion')) { - $reduced = $request->input('reduce_motion'); - if($settings->reduce_motion != $reduced) { - $settings->reduce_motion = $reduced; - $changes = true; - } - } + if($request->has('reduce_motion')) { + $reduced = $request->input('reduce_motion'); + if($settings->reduce_motion != $reduced) { + $settings->reduce_motion = $reduced; + $changes = true; + } + } - if($request->has('high_contrast_mode')) { - $contrast = $request->input('high_contrast_mode'); - if($settings->high_contrast_mode != $contrast) { - $settings->high_contrast_mode = $contrast; - $changes = true; - } - } + if($request->has('high_contrast_mode')) { + $contrast = $request->input('high_contrast_mode'); + if($settings->high_contrast_mode != $contrast) { + $settings->high_contrast_mode = $contrast; + $changes = true; + } + } - if($request->has('video_autoplay')) { - $autoplay = $request->input('video_autoplay'); - if($settings->video_autoplay != $autoplay) { - $settings->video_autoplay = $autoplay; - $changes = true; - } - } + if($request->has('video_autoplay')) { + $autoplay = $request->input('video_autoplay'); + if($settings->video_autoplay != $autoplay) { + $settings->video_autoplay = $autoplay; + $changes = true; + } + } - if($request->has('license')) { - $license = $request->input('license'); - abort_if(!in_array($license, License::keys()), 422, 'Invalid media license id'); - $syncLicenses = $request->input('sync_licenses') == true; - abort_if($syncLicenses && Cache::get('pf:settings:mls_recently:'.$user->id) == 2, 422, 'You can only sync licenses twice per 24 hours'); - if($composeSettings['default_license'] != $license) { - $composeSettings['default_license'] = $license; - $licenseChanged = true; - $changes = true; - } - } + if($request->has('license')) { + $license = $request->input('license'); + abort_if(!in_array($license, License::keys()), 422, 'Invalid media license id'); + $syncLicenses = $request->input('sync_licenses') == true; + abort_if($syncLicenses && Cache::get('pf:settings:mls_recently:'.$user->id) == 2, 422, 'You can only sync licenses twice per 24 hours'); + if($composeSettings['default_license'] != $license) { + $composeSettings['default_license'] = $license; + $licenseChanged = true; + $changes = true; + } + } - if($request->has('media_descriptions')) { - $md = $request->input('media_descriptions') == true; - if($composeSettings['media_descriptions'] != $md) { - $composeSettings['media_descriptions'] = $md; - $changes = true; - } - } + if($request->has('media_descriptions')) { + $md = $request->input('media_descriptions') == true; + if($composeSettings['media_descriptions'] != $md) { + $composeSettings['media_descriptions'] = $md; + $changes = true; + } + } - if($request->has('crawlable')) { - $crawlable = $request->input('crawlable'); - if($settings->crawlable != $crawlable) { - $settings->crawlable = $crawlable; - $changes = true; - } - } + if($request->has('crawlable')) { + $crawlable = $request->input('crawlable'); + if($settings->crawlable != $crawlable) { + $settings->crawlable = $crawlable; + $changes = true; + } + } - if($request->has('show_profile_follower_count')) { - $show_profile_follower_count = $request->input('show_profile_follower_count'); - if($settings->show_profile_follower_count != $show_profile_follower_count) { - $settings->show_profile_follower_count = $show_profile_follower_count; - $changes = true; - } - } + if($request->has('show_profile_follower_count')) { + $show_profile_follower_count = $request->input('show_profile_follower_count'); + if($settings->show_profile_follower_count != $show_profile_follower_count) { + $settings->show_profile_follower_count = $show_profile_follower_count; + $changes = true; + } + } - if($request->has('show_profile_following_count')) { - $show_profile_following_count = $request->input('show_profile_following_count'); - if($settings->show_profile_following_count != $show_profile_following_count) { - $settings->show_profile_following_count = $show_profile_following_count; - $changes = true; - } - } + if($request->has('show_profile_following_count')) { + $show_profile_following_count = $request->input('show_profile_following_count'); + if($settings->show_profile_following_count != $show_profile_following_count) { + $settings->show_profile_following_count = $show_profile_following_count; + $changes = true; + } + } - if($request->has('public_dm')) { - $public_dm = $request->input('public_dm'); - if($settings->public_dm != $public_dm) { - $settings->public_dm = $public_dm; - $changes = true; - } - } + if($request->has('public_dm')) { + $public_dm = $request->input('public_dm'); + if($settings->public_dm != $public_dm) { + $settings->public_dm = $public_dm; + $changes = true; + } + } - if($request->has('source[privacy]')) { - $scope = $request->input('source[privacy]'); - if(in_array($scope, ['public', 'private', 'unlisted'])) { - if($composeSettings['default_scope'] != $scope) { - $composeSettings['default_scope'] = $profile->is_private ? 'private' : $scope; - $changes = true; - } - } - } + if($request->has('source[privacy]')) { + $scope = $request->input('source[privacy]'); + if(in_array($scope, ['public', 'private', 'unlisted'])) { + if($composeSettings['default_scope'] != $scope) { + $composeSettings['default_scope'] = $profile->is_private ? 'private' : $scope; + $changes = true; + } + } + } - if($request->has('disable_embeds')) { - $disabledEmbeds = $request->input('disable_embeds'); - if($other['disable_embeds'] != $disabledEmbeds) { - $other['disable_embeds'] = $disabledEmbeds; - $changes = true; - } - } + if($request->has('disable_embeds')) { + $disabledEmbeds = $request->input('disable_embeds'); + if($other['disable_embeds'] != $disabledEmbeds) { + $other['disable_embeds'] = $disabledEmbeds; + $changes = true; + } + } - if($changes) { - $settings->other = $other; - $settings->compose_settings = $composeSettings; - $settings->save(); - $user->save(); - $profile->save(); - Cache::forget('profile:settings:' . $profile->id); - Cache::forget('user:account:id:' . $profile->user_id); - Cache::forget('profile:follower_count:' . $profile->id); - Cache::forget('profile:following_count:' . $profile->id); - Cache::forget('profile:embed:' . $profile->id); - Cache::forget('profile:compose:settings:' . $user->id); - Cache::forget('profile:view:'.$user->username); - AccountService::del($user->profile_id); - } + if($changes) { + $settings->other = $other; + $settings->compose_settings = $composeSettings; + $settings->save(); + $user->save(); + $profile->save(); + Cache::forget('profile:settings:' . $profile->id); + Cache::forget('user:account:id:' . $profile->user_id); + Cache::forget('profile:follower_count:' . $profile->id); + Cache::forget('profile:following_count:' . $profile->id); + Cache::forget('profile:embed:' . $profile->id); + Cache::forget('profile:compose:settings:' . $user->id); + Cache::forget('profile:view:'.$user->username); + AccountService::del($user->profile_id); + } - if($syncLicenses && $licenseChanged) { - $key = 'pf:settings:mls_recently:'.$user->id; - $val = Cache::has($key) ? 2 : 1; - Cache::put($key, $val, 86400); - MediaSyncLicensePipeline::dispatch($user->id, $request->input('license')); - } + if($syncLicenses && $licenseChanged) { + $key = 'pf:settings:mls_recently:'.$user->id; + $val = Cache::has($key) ? 2 : 1; + Cache::put($key, $val, 86400); + MediaSyncLicensePipeline::dispatch($user->id, $request->input('license')); + } if($request->has(self::PF_API_ENTITY_KEY)) { $res = AccountService::get($user->profile_id, true); @@ -452,502 +459,505 @@ class ApiV1Controller extends Controller $res = array_merge($res, $other); } - return $this->json($res); - } + return $this->json($res); + } - /** - * GET /api/v1/accounts/{id}/followers - * - * @param integer $id - * - * @return \App\Transformer\Api\AccountTransformer - */ - public function accountFollowersById(Request $request, $id) - { - abort_if(!$request->user(), 403); + /** + * GET /api/v1/accounts/{id}/followers + * + * @param integer $id + * + * @return \App\Transformer\Api\AccountTransformer + */ + public function accountFollowersById(Request $request, $id) + { + abort_if(!$request->user(), 403); - $account = AccountService::get($id); - abort_if(!$account, 404); - $pid = $request->user()->profile_id; - $this->validate($request, [ - 'limit' => 'sometimes|integer|min:1|max:80' - ]); - $limit = $request->input('limit', 10); - $napi = $request->has(self::PF_API_ENTITY_KEY); + $account = AccountService::get($id); + abort_if(!$account, 404); + $pid = $request->user()->profile_id; + $this->validate($request, [ + 'limit' => 'sometimes|integer|min:1|max:80' + ]); + $limit = $request->input('limit', 10); + $napi = $request->has(self::PF_API_ENTITY_KEY); - if(intval($pid) !== intval($account['id'])) { - if($account['locked']) { - if(!FollowerService::follows($pid, $account['id'])) { - return []; - } - } + if(intval($pid) !== intval($account['id'])) { + if($account['locked']) { + if(!FollowerService::follows($pid, $account['id'])) { + return []; + } + } - if(AccountService::hiddenFollowers($id)) { - return []; - } + if(AccountService::hiddenFollowers($id)) { + return []; + } - if($request->has('page') && $request->user()->is_admin == false) { - $page = (int) $request->input('page'); - if(($page * $limit) >= 100) { - return []; - } - } - } - if($request->has('page')) { - $res = DB::table('followers') - ->select('id', 'profile_id', 'following_id') - ->whereFollowingId($account['id']) - ->orderByDesc('id') - ->simplePaginate($limit) - ->map(function($follower) use($napi) { - return $napi ? AccountService::get($follower->profile_id, true) : AccountService::getMastodon($follower->profile_id, true); - }) - ->filter(function($account) { - return $account && isset($account['id']); - }) - ->values() - ->toArray(); + if($request->has('page') && $request->user()->is_admin == false) { + $page = (int) $request->input('page'); + if(($page * $limit) >= 100) { + return []; + } + } + } + if($request->has('page')) { + $res = DB::table('followers') + ->select('id', 'profile_id', 'following_id') + ->whereFollowingId($account['id']) + ->orderByDesc('id') + ->simplePaginate($limit) + ->map(function($follower) use($napi) { + return $napi ? AccountService::get($follower->profile_id, true) : AccountService::getMastodon($follower->profile_id, true); + }) + ->filter(function($account) { + return $account && isset($account['id']); + }) + ->values() + ->toArray(); - return $this->json($res); - } - - $paginator = DB::table('followers') - ->select('id', 'profile_id', 'following_id') - ->whereFollowingId($account['id']) - ->orderByDesc('id') - ->cursorPaginate($limit) - ->withQueryString(); - - $link = null; - - if($paginator->onFirstPage()) { - if($paginator->hasMorePages()) { - $link = '<'.$paginator->nextPageUrl().'>; rel="prev"'; - } - } else { - if($paginator->previousPageUrl()) { - $link = '<'.$paginator->previousPageUrl().'>; rel="next"'; - } - - if($paginator->hasMorePages()) { - $link .= ($link ? ', ' : '') . '<'.$paginator->nextPageUrl().'>; rel="prev"'; - } - } - - $res = $paginator->map(function($follower) use($napi) { - return $napi ? AccountService::get($follower->profile_id, true) : AccountService::getMastodon($follower->profile_id, true); - }) - ->filter(function($account) { - return $account && isset($account['id']); - }) - ->values() - ->toArray(); - - $headers = isset($link) ? ['Link' => $link] : []; - return $this->json($res, 200, $headers); - } - - /** - * GET /api/v1/accounts/{id}/following - * - * @param integer $id - * - * @return \App\Transformer\Api\AccountTransformer - */ - public function accountFollowingById(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $account = AccountService::get($id); - abort_if(!$account, 404); - $pid = $request->user()->profile_id; - $this->validate($request, [ - 'limit' => 'sometimes|integer|min:1|max:80' - ]); - $limit = $request->input('limit', 10); - $napi = $request->has(self::PF_API_ENTITY_KEY); - - if(intval($pid) !== intval($account['id'])) { - if($account['locked']) { - if(!FollowerService::follows($pid, $account['id'])) { - return []; - } - } - - if(AccountService::hiddenFollowing($id)) { - return []; - } - - if($request->has('page') && $request->user()->is_admin == false) { - $page = (int) $request->input('page'); - if(($page * $limit) >= 100) { - return []; - } - } - } - - if($request->has('page')) { - $res = DB::table('followers') - ->select('id', 'profile_id', 'following_id') - ->whereProfileId($account['id']) - ->orderByDesc('id') - ->simplePaginate($limit) - ->map(function($follower) use($napi) { - return $napi ? AccountService::get($follower->following_id, true) : AccountService::getMastodon($follower->following_id, true); - }) - ->filter(function($account) { - return $account && isset($account['id']); - }) - ->values() - ->toArray(); - return $this->json($res); - } - - $paginator = DB::table('followers') - ->select('id', 'profile_id', 'following_id') - ->whereProfileId($account['id']) - ->orderByDesc('id') - ->cursorPaginate($limit) - ->withQueryString(); - - $link = null; - - if($paginator->onFirstPage()) { - if($paginator->hasMorePages()) { - $link = '<'.$paginator->nextPageUrl().'>; rel="prev"'; - } - } else { - if($paginator->previousPageUrl()) { - $link = '<'.$paginator->previousPageUrl().'>; rel="next"'; - } - - if($paginator->hasMorePages()) { - $link .= ($link ? ', ' : '') . '<'.$paginator->nextPageUrl().'>; rel="prev"'; - } - } - - $res = $paginator->map(function($follower) use($napi) { - return $napi ? AccountService::get($follower->following_id, true) : AccountService::getMastodon($follower->following_id, true); - }) - ->filter(function($account) { - return $account && isset($account['id']); - }) - ->values() - ->toArray(); - - $headers = isset($link) ? ['Link' => $link] : []; - return $this->json($res, 200, $headers); - } - - /** - * GET /api/v1/accounts/{id}/statuses - * - * @param integer $id - * - * @return \App\Transformer\Api\StatusTransformer - */ - public function accountStatusesById(Request $request, $id) - { - $user = $request->user(); - - $this->validate($request, [ - 'only_media' => 'nullable', - 'media_type' => 'sometimes|string|in:photo,video', - '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:100' - ]); - - $napi = $request->has(self::PF_API_ENTITY_KEY); - $profile = $napi ? AccountService::get($id, true) : AccountService::getMastodon($id, true); - - if(!$profile || !isset($profile['id']) || !$user) { - return $this->json(['error' => 'Account not found'], 404); + return $this->json($res); } - $limit = $request->limit ?? 20; - $max_id = $request->max_id; - $min_id = $request->min_id; + $paginator = DB::table('followers') + ->select('id', 'profile_id', 'following_id') + ->whereFollowingId($account['id']) + ->orderByDesc('id') + ->cursorPaginate($limit) + ->withQueryString(); - if(!$max_id && !$min_id) { - $min_id = 1; - } + $link = null; - $pid = $request->user()->profile_id; - $scope = $request->only_media == true ? - ['photo', 'photo:album', 'video', 'video:album'] : - ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply']; + if($paginator->onFirstPage()) { + if($paginator->hasMorePages()) { + $link = '<'.$paginator->nextPageUrl().'>; rel="prev"'; + } + } else { + if($paginator->previousPageUrl()) { + $link = '<'.$paginator->previousPageUrl().'>; rel="next"'; + } - if($request->only_media && $request->has('media_type')) { - $mt = $request->input('media_type'); - if($mt == 'video') { - $scope = ['video', 'video:album']; - } - } + if($paginator->hasMorePages()) { + $link .= ($link ? ', ' : '') . '<'.$paginator->nextPageUrl().'>; rel="prev"'; + } + } - if(intval($pid) === intval($profile['id'])) { - $visibility = ['public', 'unlisted', 'private']; - } else if($profile['locked']) { - $following = FollowerService::follows($pid, $profile['id']); - if(!$following) { - return response('', 403); - } - $visibility = ['public', 'unlisted', 'private']; - } else { - $following = FollowerService::follows($pid, $profile['id']); - $visibility = $following ? ['public', 'unlisted', 'private'] : ['public', 'unlisted']; - } + $res = $paginator->map(function($follower) use($napi) { + return $napi ? AccountService::get($follower->profile_id, true) : AccountService::getMastodon($follower->profile_id, true); + }) + ->filter(function($account) { + return $account && isset($account['id']); + }) + ->values() + ->toArray(); - $dir = $min_id ? '>' : '<'; - $id = $min_id ?? $max_id; - $res = Status::whereProfileId($profile['id']) - ->whereNull('in_reply_to_id') - ->whereNull('reblog_of_id') - ->whereIn('type', $scope) - ->where('id', $dir, $id) - ->whereIn('scope', $visibility) - ->limit($limit) - ->orderByDesc('id') - ->get() - ->map(function($s) use($user, $napi, $profile) { + $headers = isset($link) ? ['Link' => $link] : []; + return $this->json($res, 200, $headers); + } + + /** + * GET /api/v1/accounts/{id}/following + * + * @param integer $id + * + * @return \App\Transformer\Api\AccountTransformer + */ + public function accountFollowingById(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $account = AccountService::get($id); + abort_if(!$account, 404); + $pid = $request->user()->profile_id; + $this->validate($request, [ + 'limit' => 'sometimes|integer|min:1|max:80' + ]); + $limit = $request->input('limit', 10); + $napi = $request->has(self::PF_API_ENTITY_KEY); + + if(intval($pid) !== intval($account['id'])) { + if($account['locked']) { + if(!FollowerService::follows($pid, $account['id'])) { + return []; + } + } + + if(AccountService::hiddenFollowing($id)) { + return []; + } + + if($request->has('page') && $request->user()->is_admin == false) { + $page = (int) $request->input('page'); + if(($page * $limit) >= 100) { + return []; + } + } + } + + if($request->has('page')) { + $res = DB::table('followers') + ->select('id', 'profile_id', 'following_id') + ->whereProfileId($account['id']) + ->orderByDesc('id') + ->simplePaginate($limit) + ->map(function($follower) use($napi) { + return $napi ? AccountService::get($follower->following_id, true) : AccountService::getMastodon($follower->following_id, true); + }) + ->filter(function($account) { + return $account && isset($account['id']); + }) + ->values() + ->toArray(); + return $this->json($res); + } + + $paginator = DB::table('followers') + ->select('id', 'profile_id', 'following_id') + ->whereProfileId($account['id']) + ->orderByDesc('id') + ->cursorPaginate($limit) + ->withQueryString(); + + $link = null; + + if($paginator->onFirstPage()) { + if($paginator->hasMorePages()) { + $link = '<'.$paginator->nextPageUrl().'>; rel="prev"'; + } + } else { + if($paginator->previousPageUrl()) { + $link = '<'.$paginator->previousPageUrl().'>; rel="next"'; + } + + if($paginator->hasMorePages()) { + $link .= ($link ? ', ' : '') . '<'.$paginator->nextPageUrl().'>; rel="prev"'; + } + } + + $res = $paginator->map(function($follower) use($napi) { + return $napi ? AccountService::get($follower->following_id, true) : AccountService::getMastodon($follower->following_id, true); + }) + ->filter(function($account) { + return $account && isset($account['id']); + }) + ->values() + ->toArray(); + + $headers = isset($link) ? ['Link' => $link] : []; + return $this->json($res, 200, $headers); + } + + /** + * GET /api/v1/accounts/{id}/statuses + * + * @param integer $id + * + * @return \App\Transformer\Api\StatusTransformer + */ + public function accountStatusesById(Request $request, $id) + { + $user = $request->user(); + + $this->validate($request, [ + 'only_media' => 'nullable', + 'media_type' => 'sometimes|string|in:photo,video', + '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:100' + ]); + + $napi = $request->has(self::PF_API_ENTITY_KEY); + $profile = $napi ? AccountService::get($id, true) : AccountService::getMastodon($id, true); + + if(!$profile || !isset($profile['id']) || !$user) { + return $this->json(['error' => 'Account not found'], 404); + } + + $limit = $request->limit ?? 20; + $max_id = $request->max_id; + $min_id = $request->min_id; + + if(!$max_id && !$min_id) { + $min_id = 1; + } + + $pid = $request->user()->profile_id; + $scope = $request->only_media == true ? + ['photo', 'photo:album', 'video', 'video:album'] : + ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply']; + + if($request->only_media && $request->has('media_type')) { + $mt = $request->input('media_type'); + if($mt == 'video') { + $scope = ['video', 'video:album']; + } + } + + if(intval($pid) === intval($profile['id'])) { + $visibility = ['public', 'unlisted', 'private']; + } else if($profile['locked']) { + $following = FollowerService::follows($pid, $profile['id']); + if(!$following) { + return response('', 403); + } + $visibility = ['public', 'unlisted', 'private']; + } else { + $following = FollowerService::follows($pid, $profile['id']); + $visibility = $following ? ['public', 'unlisted', 'private'] : ['public', 'unlisted']; + } + + $dir = $min_id ? '>' : '<'; + $id = $min_id ?? $max_id; + $res = Status::whereProfileId($profile['id']) + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->whereIn('type', $scope) + ->where('id', $dir, $id) + ->whereIn('scope', $visibility) + ->limit($limit) + ->orderByDesc('id') + ->get() + ->map(function($s) use($user, $napi, $profile) { try { $status = $napi ? StatusService::get($s->id, false) : StatusService::getMastodon($s->id, false); } catch (\Exception $e) { return false; } - if($profile) { - $status['account'] = $profile; - } + if($profile) { + $status['account'] = $profile; + } - if($user && $status) { - $status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id); + if($user && $status) { + $status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id); $status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id); $status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id); - } - return $status; - }) - ->filter(function($s) { - return $s; - }) - ->values(); + } + return $status; + }) + ->filter(function($s) { + return $s; + }) + ->values(); - return $this->json($res); - } + return $this->json($res); + } - /** - * POST /api/v1/accounts/{id}/follow - * - * @param integer $id - * - * @return \App\Transformer\Api\RelationshipTransformer - */ - public function accountFollowById(Request $request, $id) - { - abort_if(!$request->user(), 403); + /** + * POST /api/v1/accounts/{id}/follow + * + * @param integer $id + * + * @return \App\Transformer\Api\RelationshipTransformer + */ + public function accountFollowById(Request $request, $id) + { + abort_if(!$request->user(), 403); - $user = $request->user(); + $user = $request->user(); + AccountService::setLastActive($user->id); - $target = Profile::where('id', '!=', $user->profile_id) - ->whereNull('status') - ->findOrFail($id); + $target = Profile::where('id', '!=', $user->profile_id) + ->whereNull('status') + ->findOrFail($id); - $private = (bool) $target->is_private; - $remote = (bool) $target->domain; - $blocked = UserFilter::whereUserId($target->id) - ->whereFilterType('block') - ->whereFilterableId($user->profile_id) - ->whereFilterableType('App\Profile') - ->exists(); + $private = (bool) $target->is_private; + $remote = (bool) $target->domain; + $blocked = UserFilter::whereUserId($target->id) + ->whereFilterType('block') + ->whereFilterableId($user->profile_id) + ->whereFilterableType('App\Profile') + ->exists(); - if($blocked == true) { - abort(400, 'You cannot follow this user.'); - } + if($blocked == true) { + abort(400, 'You cannot follow this user.'); + } - $isFollowing = Follower::whereProfileId($user->profile_id) - ->whereFollowingId($target->id) - ->exists(); + $isFollowing = Follower::whereProfileId($user->profile_id) + ->whereFollowingId($target->id) + ->exists(); - // Following already, return empty relationship - if($isFollowing == true) { - $res = RelationshipService::get($user->profile_id, $target->id) ?? []; - return $this->json($res); - } + // Following already, return empty relationship + if($isFollowing == true) { + $res = RelationshipService::get($user->profile_id, $target->id) ?? []; + return $this->json($res); + } - // Rate limits, max 7500 followers per account - if($user->profile->following_count && $user->profile->following_count >= Follower::MAX_FOLLOWING) { - abort(400, 'You cannot follow more than ' . Follower::MAX_FOLLOWING . ' accounts'); - } + // Rate limits, max 7500 followers per account + if($user->profile->following_count && $user->profile->following_count >= Follower::MAX_FOLLOWING) { + abort(400, 'You cannot follow more than ' . Follower::MAX_FOLLOWING . ' accounts'); + } - if($private == true) { - $follow = FollowRequest::firstOrCreate([ - 'follower_id' => $user->profile_id, - 'following_id' => $target->id - ]); - if($remote == true && config('federation.activitypub.remoteFollow') == true) { - (new FollowerController())->sendFollow($user->profile, $target); - } - } else { - $follower = Follower::firstOrCreate([ - 'profile_id' => $user->profile_id, - 'following_id' => $target->id - ]); + if($private == true) { + $follow = FollowRequest::firstOrCreate([ + 'follower_id' => $user->profile_id, + 'following_id' => $target->id + ]); + if($remote == true && config('federation.activitypub.remoteFollow') == true) { + (new FollowerController())->sendFollow($user->profile, $target); + } + } else { + $follower = Follower::firstOrCreate([ + 'profile_id' => $user->profile_id, + 'following_id' => $target->id + ]); - if($remote == true && config('federation.activitypub.remoteFollow') == true) { - (new FollowerController())->sendFollow($user->profile, $target); - } - FollowPipeline::dispatch($follower)->onQueue('high'); - } + if($remote == true && config('federation.activitypub.remoteFollow') == true) { + (new FollowerController())->sendFollow($user->profile, $target); + } + FollowPipeline::dispatch($follower)->onQueue('high'); + } - RelationshipService::refresh($user->profile_id, $target->id); - Cache::forget('profile:following:'.$target->id); - Cache::forget('profile:followers:'.$target->id); - Cache::forget('profile:following:'.$user->profile_id); - Cache::forget('profile:followers:'.$user->profile_id); - Cache::forget('api:local:exp:rec:'.$user->profile_id); - Cache::forget('user:account:id:'.$target->user_id); - Cache::forget('user:account:id:'.$user->id); - Cache::forget('profile:follower_count:'.$target->id); - Cache::forget('profile:follower_count:'.$user->profile_id); - Cache::forget('profile:following_count:'.$target->id); - Cache::forget('profile:following_count:'.$user->profile_id); - AccountService::del($user->profile_id); - AccountService::del($target->id); + RelationshipService::refresh($user->profile_id, $target->id); + Cache::forget('profile:following:'.$target->id); + Cache::forget('profile:followers:'.$target->id); + Cache::forget('profile:following:'.$user->profile_id); + Cache::forget('profile:followers:'.$user->profile_id); + Cache::forget('api:local:exp:rec:'.$user->profile_id); + Cache::forget('user:account:id:'.$target->user_id); + Cache::forget('user:account:id:'.$user->id); + Cache::forget('profile:follower_count:'.$target->id); + Cache::forget('profile:follower_count:'.$user->profile_id); + Cache::forget('profile:following_count:'.$target->id); + Cache::forget('profile:following_count:'.$user->profile_id); + AccountService::del($user->profile_id); + AccountService::del($target->id); - $res = RelationshipService::get($user->profile_id, $target->id); + $res = RelationshipService::get($user->profile_id, $target->id); - return $this->json($res); - } + return $this->json($res); + } - /** - * POST /api/v1/accounts/{id}/unfollow - * - * @param integer $id - * - * @return \App\Transformer\Api\RelationshipTransformer - */ - public function accountUnfollowById(Request $request, $id) - { - abort_if(!$request->user(), 403); + /** + * POST /api/v1/accounts/{id}/unfollow + * + * @param integer $id + * + * @return \App\Transformer\Api\RelationshipTransformer + */ + public function accountUnfollowById(Request $request, $id) + { + abort_if(!$request->user(), 403); - $user = $request->user(); + $user = $request->user(); + AccountService::setLastActive($user->id); - $target = Profile::where('id', '!=', $user->profile_id) - ->whereNull('status') - ->findOrFail($id); + $target = Profile::where('id', '!=', $user->profile_id) + ->whereNull('status') + ->findOrFail($id); - $private = (bool) $target->is_private; - $remote = (bool) $target->domain; + $private = (bool) $target->is_private; + $remote = (bool) $target->domain; - $isFollowing = Follower::whereProfileId($user->profile_id) - ->whereFollowingId($target->id) - ->exists(); + $isFollowing = Follower::whereProfileId($user->profile_id) + ->whereFollowingId($target->id) + ->exists(); - if($isFollowing == false) { - $followRequest = FollowRequest::whereFollowerId($user->profile_id) - ->whereFollowingId($target->id) - ->first(); - if($followRequest) { - $followRequest->delete(); - RelationshipService::refresh($target->id, $user->profile_id); - } - $resource = new Fractal\Resource\Item($target, new RelationshipTransformer()); - $res = $this->fractal->createData($resource)->toArray(); + if($isFollowing == false) { + $followRequest = FollowRequest::whereFollowerId($user->profile_id) + ->whereFollowingId($target->id) + ->first(); + if($followRequest) { + $followRequest->delete(); + RelationshipService::refresh($target->id, $user->profile_id); + } + $resource = new Fractal\Resource\Item($target, new RelationshipTransformer()); + $res = $this->fractal->createData($resource)->toArray(); - return $this->json($res); - } + return $this->json($res); + } - Follower::whereProfileId($user->profile_id) - ->whereFollowingId($target->id) - ->delete(); + Follower::whereProfileId($user->profile_id) + ->whereFollowingId($target->id) + ->delete(); - UnfollowPipeline::dispatch($user->profile_id, $target->id)->onQueue('high'); + UnfollowPipeline::dispatch($user->profile_id, $target->id)->onQueue('high'); - if($remote == true && config('federation.activitypub.remoteFollow') == true) { - (new FollowerController())->sendUndoFollow($user->profile, $target); - } + if($remote == true && config('federation.activitypub.remoteFollow') == true) { + (new FollowerController())->sendUndoFollow($user->profile, $target); + } - RelationshipService::refresh($user->profile_id, $target->id); - Cache::forget('profile:following:'.$target->id); - Cache::forget('profile:followers:'.$target->id); - Cache::forget('profile:following:'.$user->profile_id); - Cache::forget('profile:followers:'.$user->profile_id); - Cache::forget('api:local:exp:rec:'.$user->profile_id); - Cache::forget('user:account:id:'.$target->user_id); - Cache::forget('user:account:id:'.$user->id); - Cache::forget('profile:follower_count:'.$target->id); - Cache::forget('profile:follower_count:'.$user->profile_id); - Cache::forget('profile:following_count:'.$target->id); - Cache::forget('profile:following_count:'.$user->profile_id); - AccountService::del($user->profile_id); - AccountService::del($target->id); + RelationshipService::refresh($user->profile_id, $target->id); + Cache::forget('profile:following:'.$target->id); + Cache::forget('profile:followers:'.$target->id); + Cache::forget('profile:following:'.$user->profile_id); + Cache::forget('profile:followers:'.$user->profile_id); + Cache::forget('api:local:exp:rec:'.$user->profile_id); + Cache::forget('user:account:id:'.$target->user_id); + Cache::forget('user:account:id:'.$user->id); + Cache::forget('profile:follower_count:'.$target->id); + Cache::forget('profile:follower_count:'.$user->profile_id); + Cache::forget('profile:following_count:'.$target->id); + Cache::forget('profile:following_count:'.$user->profile_id); + AccountService::del($user->profile_id); + AccountService::del($target->id); - $res = RelationshipService::get($user->profile_id, $target->id); + $res = RelationshipService::get($user->profile_id, $target->id); - return $this->json($res); - } + return $this->json($res); + } - /** - * GET /api/v1/accounts/relationships - * - * @param array|integer $id - * - * @return \App\Services\RelationshipService - */ - public function accountRelationshipsById(Request $request) - { - abort_if(!$request->user(), 403); + /** + * GET /api/v1/accounts/relationships + * + * @param array|integer $id + * + * @return \App\Services\RelationshipService + */ + public function accountRelationshipsById(Request $request) + { + abort_if(!$request->user(), 403); - $this->validate($request, [ - 'id' => 'required|array|min:1|max:20', - 'id.*' => 'required|integer|min:1|max:' . PHP_INT_MAX - ]); - $napi = $request->has(self::PF_API_ENTITY_KEY); - $pid = $request->user()->profile_id ?? $request->user()->profile->id; - $res = collect($request->input('id')) - ->filter(function($id) use($pid) { - return intval($id) !== intval($pid); - }) - ->map(function($id) use($pid, $napi) { - return $napi ? - RelationshipService::getWithDate($pid, $id) : - RelationshipService::get($pid, $id); - }); - return $this->json($res); - } + $this->validate($request, [ + 'id' => 'required|array|min:1|max:20', + 'id.*' => 'required|integer|min:1|max:' . PHP_INT_MAX + ]); + $napi = $request->has(self::PF_API_ENTITY_KEY); + $pid = $request->user()->profile_id ?? $request->user()->profile->id; + $res = collect($request->input('id')) + ->filter(function($id) use($pid) { + return intval($id) !== intval($pid); + }) + ->map(function($id) use($pid, $napi) { + return $napi ? + RelationshipService::getWithDate($pid, $id) : + RelationshipService::get($pid, $id); + }); + return $this->json($res); + } - /** - * GET /api/v1/accounts/search - * - * - * - * @return \App\Transformer\Api\AccountTransformer - */ - public function accountSearch(Request $request) - { - abort_if(!$request->user(), 403); + /** + * GET /api/v1/accounts/search + * + * + * + * @return \App\Transformer\Api\AccountTransformer + */ + public function accountSearch(Request $request) + { + abort_if(!$request->user(), 403); - $this->validate($request, [ - 'q' => 'required|string|min:1|max:255', - 'limit' => 'nullable|integer|min:1|max:40', - 'resolve' => 'nullable' - ]); + $this->validate($request, [ + 'q' => 'required|string|min:1|max:255', + 'limit' => 'nullable|integer|min:1|max:40', + 'resolve' => 'nullable' + ]); - $user = $request->user(); - $query = $request->input('q'); - $limit = $request->input('limit') ?? 20; - $resolve = (bool) $request->input('resolve', false); - $q = '%' . $query . '%'; + $user = $request->user(); + AccountService::setLastActive($user->id); + $query = $request->input('q'); + $limit = $request->input('limit') ?? 20; + $resolve = (bool) $request->input('resolve', false); + $q = '%' . $query . '%'; - $profiles = Cache::remember('api:v1:accounts:search:' . sha1($query) . ':limit:' . $limit, 86400, function() use($q, $limit) { + $profiles = Cache::remember('api:v1:accounts:search:' . sha1($query) . ':limit:' . $limit, 86400, function() use($q, $limit) { return Profile::whereNull('status') - ->where('username', 'like', $q) - ->orWhere('name', 'like', $q) - ->limit($limit) - ->pluck('id') + ->where('username', 'like', $q) + ->orWhere('name', 'like', $q) + ->limit($limit) + ->pluck('id') ->map(function($id) { return AccountService::getMastodon($id); }) @@ -956,2303 +966,2421 @@ class ApiV1Controller extends Controller }); }); - return $this->json($profiles); - } - - /** - * GET /api/v1/blocks - * - * - * - * @return \App\Transformer\Api\AccountTransformer - */ - public function accountBlocks(Request $request) - { - abort_if(!$request->user(), 403); - - $this->validate($request, [ - 'limit' => 'nullable|integer|min:1|max:40', - 'page' => 'nullable|integer|min:1|max:10' - ]); - - $user = $request->user(); - $limit = $request->input('limit') ?? 40; - - $blocked = UserFilter::select('filterable_id','filterable_type','filter_type','user_id') - ->whereUserId($user->profile_id) - ->whereFilterableType('App\Profile') - ->whereFilterType('block') - ->orderByDesc('id') - ->simplePaginate($limit) - ->pluck('filterable_id') - ->map(function($id) { - return AccountService::get($id, true); - }) - ->filter(function($account) { - return $account && isset($account['id']); - }) - ->values(); - - return $this->json($blocked); - } - - /** - * POST /api/v1/accounts/{id}/block - * - * @param integer $id - * - * @return \App\Transformer\Api\RelationshipTransformer - */ - public function accountBlockById(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $user = $request->user(); - $pid = $user->profile_id ?? $user->profile->id; - - if(intval($id) === intval($pid)) { - abort(400, 'You cannot block yourself'); - } - - $profile = Profile::findOrFail($id); - - if($profile->user && $profile->user->is_admin == true) { - abort(400, 'You cannot block an admin'); - } - - $count = UserFilterService::blockCount($pid); - $maxLimit = intval(config('instance.user_filters.max_user_blocks')); - if($count == 0) { - $filterCount = UserFilter::whereUserId($pid) - ->whereFilterType('block') - ->get() - ->map(function($rec) { - return AccountService::get($rec->filterable_id, true); - }) - ->filter(function($account) { - return $account && isset($account['id']); - }) - ->values() - ->count(); - abort_if($filterCount >= $maxLimit, 422, AccountController::FILTER_LIMIT_BLOCK_TEXT . $maxLimit . ' accounts'); - } else { - abort_if($count >= $maxLimit, 422, AccountController::FILTER_LIMIT_BLOCK_TEXT . $maxLimit . ' accounts'); - } - - $followed = Follower::whereProfileId($profile->id)->whereFollowingId($pid)->first(); - if($followed) { - $followed->delete(); - $profile->following_count = Follower::whereProfileId($profile->id)->count(); - $profile->save(); - $selfProfile = $user->profile; - $selfProfile->followers_count = Follower::whereFollowingId($pid)->count(); - $selfProfile->save(); - FollowerService::remove($profile->id, $pid); - AccountService::del($pid); - AccountService::del($profile->id); - } - - $following = Follower::whereProfileId($pid)->whereFollowingId($profile->id)->first(); - if($following) { - $following->delete(); - $profile->followers_count = Follower::whereFollowingId($profile->id)->count(); - $profile->save(); - $selfProfile = $user->profile; - $selfProfile->following_count = Follower::whereProfileId($pid)->count(); - $selfProfile->save(); - FollowerService::remove($pid, $profile->pid); - AccountService::del($pid); - AccountService::del($profile->id); - } - - Notification::whereProfileId($pid) - ->whereActorId($profile->id) - ->get() - ->map(function($n) use($pid) { - NotificationService::del($pid, $n['id']); - $n->forceDelete(); - }); - - $filter = UserFilter::firstOrCreate([ - 'user_id' => $pid, - 'filterable_id' => $profile->id, - 'filterable_type' => 'App\Profile', - 'filter_type' => 'block', - ]); - - UserFilterService::block($pid, $id); - RelationshipService::refresh($pid, $id); - $resource = new Fractal\Resource\Item($profile, new RelationshipTransformer()); - $res = $this->fractal->createData($resource)->toArray(); - - return $this->json($res); - } - - /** - * POST /api/v1/accounts/{id}/unblock - * - * @param integer $id - * - * @return \App\Transformer\Api\RelationshipTransformer - */ - public function accountUnblockById(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $user = $request->user(); - $pid = $user->profile_id ?? $user->profile->id; - - if(intval($id) === intval($pid)) { - abort(400, 'You cannot unblock yourself'); - } - - $profile = Profile::findOrFail($id); - - $filter = UserFilter::whereUserId($pid) - ->whereFilterableId($profile->id) - ->whereFilterableType('App\Profile') - ->whereFilterType('block') - ->first(); - - if($filter) { - $filter->delete(); - UserFilterService::unblock($pid, $profile->id); - RelationshipService::refresh($pid, $id); - } - - $resource = new Fractal\Resource\Item($profile, new RelationshipTransformer()); - $res = $this->fractal->createData($resource)->toArray(); - - return $this->json($res); - } - - /** - * GET /api/v1/custom_emojis - * - * Return custom emoji - * - * @return array - */ - public function customEmojis() - { - return response(CustomEmojiService::all())->header('Content-Type', 'application/json'); - } - - /** - * GET /api/v1/domain_blocks - * - * Return empty array - * - * @return array - */ - public function accountDomainBlocks(Request $request) - { - abort_if(!$request->user(), 403); - return response()->json([]); - } - - /** - * GET /api/v1/endorsements - * - * Return empty array - * - * @return array - */ - public function accountEndorsements(Request $request) - { - abort_if(!$request->user(), 403); - return response()->json([]); - } - - /** - * GET /api/v1/favourites - * - * Returns collection of liked statuses - * - * @return \App\Transformer\Api\StatusTransformer - */ - public function accountFavourites(Request $request) - { - abort_if(!$request->user(), 403); - $this->validate($request, [ - 'limit' => 'sometimes|integer|min:1|max:20' - ]); - - $user = $request->user(); - $maxId = $request->input('max_id'); - $minId = $request->input('min_id'); - $limit = $request->input('limit') ?? 10; - - $res = Like::whereProfileId($user->profile_id) - ->when($maxId, function($q, $maxId) { - return $q->where('id', '<', $maxId); - }) - ->when($minId, function($q, $minId) { - return $q->where('id', '>', $minId); - }) - ->orderByDesc('id') - ->limit($limit) - ->get() - ->map(function($like) { - $status = StatusService::getMastodon($like['status_id'], false); - $status['favourited'] = true; - $status['like_id'] = $like->id; - $status['liked_at'] = str_replace('+00:00', 'Z', $like->created_at->format(DATE_RFC3339_EXTENDED)); - return $status; - }) - ->filter(function($status) { - return $status && isset($status['id'], $status['like_id']); - }) - ->values(); - - if($res->count()) { - $ids = $res->map(function($status) { - return $status['like_id']; - }); - $max = $ids->max(); - $min = $ids->min(); - - $baseUrl = config('app.url') . '/api/v1/favourites?limit=' . $limit . '&'; - $link = '<'.$baseUrl.'max_id='.$max.'>; rel="next",<'.$baseUrl.'min_id='.$min.'>; rel="prev"'; - return $this->json($res, 200, ['Link' => $link]); - } else { - return $this->json($res); - } - } - - /** - * POST /api/v1/statuses/{id}/favourite - * - * @param integer $id - * - * @return \App\Transformer\Api\StatusTransformer - */ - public function statusFavouriteById(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $user = $request->user(); - - $status = StatusService::getMastodon($id, false); - - abort_unless($status, 400); - - $spid = $status['account']['id']; - - if(intval($spid) !== intval($user->profile_id)) { - if($status['visibility'] == 'private') { - abort_if(!FollowerService::follows($user->profile_id, $spid), 403); - } else { - abort_if(!in_array($status['visibility'], ['public','unlisted']), 403); - } - } - - abort_if( - Like::whereProfileId($user->profile_id) - ->where('created_at', '>', now()->subDay()) - ->count() >= Like::MAX_PER_DAY, - 429 - ); - - $blocks = UserFilterService::blocks($spid); - if($blocks && in_array($user->profile_id, $blocks)) { - abort(422); - } - - $like = Like::firstOrCreate([ - 'profile_id' => $user->profile_id, - 'status_id' => $status['id'] - ]); - - if($like->wasRecentlyCreated == true) { - $like->status_profile_id = $spid; - $like->is_comment = !empty($status['in_reply_to_id']); - $like->save(); - Status::findOrFail($status['id'])->update([ - 'likes_count' => ($status['favourites_count'] ?? 0) + 1 - ]); - LikePipeline::dispatch($like); - } - - $status['favourited'] = true; - $status['favourites_count'] = $status['favourites_count'] + 1; - return $this->json($status); - } - - /** - * POST /api/v1/statuses/{id}/unfavourite - * - * @param integer $id - * - * @return \App\Transformer\Api\StatusTransformer - */ - public function statusUnfavouriteById(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $user = $request->user(); - - $status = Status::findOrFail($id); - - if(intval($status->profile_id) !== intval($user->profile_id)) { - if($status->scope == 'private') { - abort_if(!$status->profile->followedBy($user->profile), 403); - } else { - abort_if(!in_array($status->scope, ['public','unlisted']), 403); - } - } - - $like = Like::whereProfileId($user->profile_id) - ->whereStatusId($status->id) - ->first(); - - if($like) { - $like->forceDelete(); - $status->likes_count = $status->likes()->count(); - $status->save(); - } - - StatusService::del($status->id); - - $res = StatusService::getMastodon($status->id, false); - $res['favourited'] = false; - return $this->json($res); - } - - /** - * GET /api/v1/filters - * - * Return empty response since we filter server side - * - * @return array - */ - public function accountFilters(Request $request) - { - abort_if(!$request->user(), 403); - - return response()->json([]); - } - - /** - * GET /api/v1/follow_requests - * - * Return array of Accounts that have sent follow requests - * - * @return \App\Transformer\Api\AccountTransformer - */ - public function accountFollowRequests(Request $request) - { - abort_if(!$request->user(), 403); - $this->validate($request, [ - 'limit' => 'sometimes|integer|min:1|max:100' - ]); - - $user = $request->user(); - - $res = FollowRequest::whereFollowingId($user->profile->id) - ->limit($request->input('limit', 40)) - ->pluck('follower_id') - ->map(function($id) { - return AccountService::getMastodon($id, true); - }) - ->filter(function($acct) { - return $acct && isset($acct['id']); - }) - ->values(); - - return $this->json($res); - } - - /** - * POST /api/v1/follow_requests/{id}/authorize - * - * @param integer $id - * - * @return null - */ - public function accountFollowRequestAccept(Request $request, $id) - { - abort_if(!$request->user(), 403); - $pid = $request->user()->profile_id; - $target = AccountService::getMastodon($id); - - if(!$target) { - return response()->json(['error' => 'Record not found'], 404); - } - - $followRequest = FollowRequest::whereFollowingId($pid)->whereFollowerId($id)->first(); - - if(!$followRequest) { - return response()->json(['error' => 'Record not found'], 404); - } - - $follower = $followRequest->follower; - $follow = new Follower(); - $follow->profile_id = $follower->id; - $follow->following_id = $pid; - $follow->save(); - - $profile = Profile::findOrFail($pid); - $profile->followers_count++; - $profile->save(); - AccountService::del($profile->id); - - $profile = Profile::findOrFail($follower->id); - $profile->following_count++; - $profile->save(); - AccountService::del($profile->id); - - if($follower->domain != null && $follower->private_key === null) { - FollowAcceptPipeline::dispatch($followRequest)->onQueue('follow'); - } else { - FollowPipeline::dispatch($follow); - $followRequest->delete(); - } - - RelationshipService::refresh($pid, $id); - $res = RelationshipService::get($pid, $id); - $res['followed_by'] = true; - return $this->json($res); - } - - /** - * POST /api/v1/follow_requests/{id}/reject - * - * @param integer $id - * - * @return null - */ - public function accountFollowRequestReject(Request $request, $id) - { - abort_if(!$request->user(), 403); - $pid = $request->user()->profile_id; - $target = AccountService::getMastodon($id); - - if(!$target) { - return response()->json(['error' => 'Record not found'], 404); - } - - $followRequest = FollowRequest::whereFollowingId($pid)->whereFollowerId($id)->first(); - - if(!$followRequest) { - return response()->json(['error' => 'Record not found'], 404); - } - - $follower = $followRequest->follower; - - if($follower->domain != null && $follower->private_key === null) { - FollowRejectPipeline::dispatch($followRequest)->onQueue('follow'); - } else { - $followRequest->delete(); - } - - RelationshipService::refresh($pid, $id); - $res = RelationshipService::get($pid, $id); - return $this->json($res); - } - - /** - * GET /api/v1/suggestions - * - * Return empty array as we don't support suggestions - * - * @return null - */ - public function accountSuggestions(Request $request) - { - abort_if(!$request->user(), 403); - - // todo - - return response()->json([]); - } - - /** - * GET /api/v1/instance - * - * Information about the server. - * - * @return Instance - */ - public function instance(Request $request) - { - $res = Cache::remember('api:v1:instance-data-response-v1', 1800, function () { - $contact = Cache::remember('api:v1:instance-data:contact', 604800, function () { - if(config_cache('instance.admin.pid')) { - return AccountService::getMastodon(config_cache('instance.admin.pid'), true); - } - $admin = User::whereIsAdmin(true)->first(); - return $admin && isset($admin->profile_id) ? - AccountService::getMastodon($admin->profile_id, true) : - null; - }); - - $stats = Cache::remember('api:v1:instance-data:stats', 43200, function () { - return [ - 'user_count' => User::count(), - 'status_count' => Status::whereNull('uri')->count(), - 'domain_count' => Instance::count(), - ]; - }); - - $rules = Cache::remember('api:v1:instance-data:rules', 604800, function () { - return config_cache('app.rules') ? - collect(json_decode(config_cache('app.rules'), true)) - ->map(function($rule, $key) { - $id = $key + 1; - return [ - 'id' => "{$id}", - 'text' => $rule - ]; - }) - ->toArray() : []; - }); - - return [ - 'uri' => config('pixelfed.domain.app'), - 'title' => config('app.name'), - 'short_description' => config_cache('app.short_description'), - 'description' => config_cache('app.description'), - 'email' => config('instance.email'), - 'version' => '2.7.2 (compatible; Pixelfed ' . config('pixelfed.version') .')', - 'urls' => [ - 'streaming_api' => 'wss://' . config('pixelfed.domain.app') - ], - 'stats' => $stats, - 'thumbnail' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')), - 'languages' => [config('app.locale')], - 'registrations' => (bool) config_cache('pixelfed.open_registration'), - 'approval_required' => false, - 'contact_account' => $contact, - 'rules' => $rules, - 'configuration' => [ - 'media_attachments' => [ - 'image_matrix_limit' => 16777216, - 'image_size_limit' => config('pixelfed.max_photo_size') * 1024, - 'supported_mime_types' => explode(',', config('pixelfed.media_types')), - 'video_frame_rate_limit' => 120, - 'video_matrix_limit' => 2304000, - 'video_size_limit' => config('pixelfed.max_photo_size') * 1024, - ], - 'polls' => [ - 'max_characters_per_option' => 50, - 'max_expiration' => 2629746, - 'max_options' => 4, - 'min_expiration' => 300 - ], - 'statuses' => [ - 'characters_reserved_per_url' => 23, - 'max_characters' => (int) config('pixelfed.max_caption_length'), - 'max_media_attachments' => (int) config('pixelfed.max_album_length') - ] - ] - ]; - }); - - return $this->json($res); - } - - /** - * GET /api/v1/lists - * - * Return empty array as we don't support lists - * - * @return null - */ - public function accountLists(Request $request) - { - abort_if(!$request->user(), 403); - - return response()->json([]); - } - - /** - * GET /api/v1/accounts/{id}/lists - * - * @param integer $id - * - * @return null - */ - public function accountListsById(Request $request, $id) - { - abort_if(!$request->user(), 403); - - return response()->json([]); - } - - /** - * POST /api/v1/media - * - * - * @return MediaTransformer - */ - public function mediaUpload(Request $request) - { - abort_if(!$request->user(), 403); - - $this->validate($request, [ - 'file.*' => [ - 'required_without:file', - 'mimetypes:' . config_cache('pixelfed.media_types'), - 'max:' . config_cache('pixelfed.max_photo_size'), - ], - 'file' => [ - 'required_without:file.*', - 'mimetypes:' . config_cache('pixelfed.media_types'), - 'max:' . config_cache('pixelfed.max_photo_size'), - ], - 'filter_name' => 'nullable|string|max:24', - 'filter_class' => 'nullable|alpha_dash|max:24', - 'description' => 'nullable|string|max:' . config_cache('pixelfed.max_altext_length') - ]); - - $user = $request->user(); - - if($user->last_active_at == null) { - return []; - } - - if(empty($request->file('file'))) { - return response('', 422); - } - - $limitKey = 'compose:rate-limit:media-upload:' . $user->id; - $limitTtl = now()->addMinutes(15); - $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { - $dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count(); - - return $dailyLimit >= 1250; - }); - abort_if($limitReached == true, 429); - - $profile = $user->profile; - - if(config_cache('pixelfed.enforce_account_limit') == true) { - $size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) { - return Media::whereUserId($user->id)->sum('size') / 1000; - }); - $limit = (int) config_cache('pixelfed.max_account_size'); - if ($size >= $limit) { - abort(403, 'Account size limit reached.'); - } - } - - $filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null; - $filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null; - - $photo = $request->file('file'); - - $mimes = explode(',', config_cache('pixelfed.media_types')); - if(in_array($photo->getMimeType(), $mimes) == false) { - abort(403, 'Invalid or unsupported mime type.'); - } - - $storagePath = MediaPathService::get($user, 2); - $path = $photo->storePublicly($storagePath); - $hash = \hash_file('sha256', $photo); - $license = null; - $mime = $photo->getMimeType(); - - // if($photo->getMimeType() == 'image/heic') { - // abort_if(config('image.driver') !== 'imagick', 422, 'Invalid media type'); - // abort_if(!in_array('HEIC', \Imagick::queryformats()), 422, 'Unsupported media type'); - // $oldPath = $path; - // $path = str_replace('.heic', '.jpg', $path); - // $mime = 'image/jpeg'; - // \Image::make($photo)->save(storage_path("app/{$path}")); - // @unlink(storage_path("app/{$oldPath}")); - // } - - $settings = UserSetting::whereUserId($user->id)->first(); - - if($settings && !empty($settings->compose_settings)) { - $compose = $settings->compose_settings; - - if(isset($compose['default_license']) && $compose['default_license'] != 1) { - $license = $compose['default_license']; - } - } - - abort_if(MediaBlocklistService::exists($hash) == true, 451); - - $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->mime = $mime; - $media->caption = $request->input('description'); - $media->filter_class = $filterClass; - $media->filter_name = $filterName; - if($license) { - $media->license = $license; - } - $media->save(); - - switch ($media->mime) { - case 'image/jpeg': - case 'image/png': - ImageOptimize::dispatch($media)->onQueue('mmo'); - break; - - case 'video/mp4': - VideoThumbnail::dispatch($media)->onQueue('mmo'); - $preview_url = '/storage/no-preview.png'; - $url = '/storage/no-preview.png'; - break; - } - - Cache::forget($limitKey); - $resource = new Fractal\Resource\Item($media, new MediaTransformer()); - $res = $this->fractal->createData($resource)->toArray(); - $res['preview_url'] = $media->url(). '?v=' . time(); - $res['url'] = $media->url(). '?v=' . time(); - return $this->json($res); - } - - /** - * PUT /api/v1/media/{id} - * - * @param integer $id - * - * @return MediaTransformer - */ - public function mediaUpdate(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $this->validate($request, [ - 'description' => 'nullable|string|max:' . config_cache('pixelfed.max_altext_length') - ]); - - $user = $request->user(); - - $media = Media::whereUserId($user->id) - ->whereProfileId($user->profile_id) - ->findOrFail($id); - - $executed = RateLimiter::attempt( - 'media:update:'.$user->id, - 10, - function() use($media, $request) { - $caption = Purify::clean($request->input('description')); - - if($caption != $media->caption) { - $media->caption = $caption; - $media->save(); - - if($media->status_id) { - MediaService::del($media->status_id); - StatusService::del($media->status_id); - } - } - }); - - if(!$executed) { - return response()->json([ - 'error' => 'Too many attempts. Try again in a few minutes.' - ], 429); - }; - - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $resource = new Fractal\Resource\Item($media, new MediaTransformer()); - return $this->json($fractal->createData($resource)->toArray()); - } - - /** - * GET /api/v1/media/{id} - * - * @param integer $id - * - * @return MediaTransformer - */ - public function mediaGet(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $user = $request->user(); - - $media = Media::whereUserId($user->id) - ->whereNull('status_id') - ->findOrFail($id); - - $resource = new Fractal\Resource\Item($media, new MediaTransformer()); - $res = $this->fractal->createData($resource)->toArray(); - return $this->json($res); - } - - /** - * POST /api/v2/media - * - * - * @return MediaTransformer - */ - public function mediaUploadV2(Request $request) - { - abort_if(!$request->user(), 403); - - $this->validate($request, [ - 'file.*' => [ - 'required_without:file', - 'mimetypes:' . config_cache('pixelfed.media_types'), - 'max:' . config_cache('pixelfed.max_photo_size'), - ], - 'file' => [ - 'required_without:file.*', - 'mimetypes:' . config_cache('pixelfed.media_types'), - 'max:' . config_cache('pixelfed.max_photo_size'), - ], - 'filter_name' => 'nullable|string|max:24', - 'filter_class' => 'nullable|alpha_dash|max:24', - 'description' => 'nullable|string|max:' . config_cache('pixelfed.max_altext_length'), - 'replace_id' => 'sometimes' - ]); - - $user = $request->user(); - - if($user->last_active_at == null) { - return []; - } - - if(empty($request->file('file'))) { - return response('', 422); - } - - $limitKey = 'compose:rate-limit:media-upload:' . $user->id; - $limitTtl = now()->addMinutes(15); - $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { - $dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count(); - - return $dailyLimit >= 1250; - }); - abort_if($limitReached == true, 429); - - $profile = $user->profile; - - if(config_cache('pixelfed.enforce_account_limit') == true) { - $size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) { - return Media::whereUserId($user->id)->sum('size') / 1000; - }); - $limit = (int) config_cache('pixelfed.max_account_size'); - if ($size >= $limit) { - abort(403, 'Account size limit reached.'); - } - } - - $filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null; - $filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null; - - $photo = $request->file('file'); - - $mimes = explode(',', config_cache('pixelfed.media_types')); - if(in_array($photo->getMimeType(), $mimes) == false) { - abort(403, 'Invalid or unsupported mime type.'); - } - - $storagePath = MediaPathService::get($user, 2); - $path = $photo->storePublicly($storagePath); - $hash = \hash_file('sha256', $photo); - $license = null; - $mime = $photo->getMimeType(); - - $settings = UserSetting::whereUserId($user->id)->first(); - - if($settings && !empty($settings->compose_settings)) { - $compose = $settings->compose_settings; - - if(isset($compose['default_license']) && $compose['default_license'] != 1) { - $license = $compose['default_license']; - } - } - - abort_if(MediaBlocklistService::exists($hash) == true, 451); - - if($request->has('replace_id')) { - $rpid = $request->input('replace_id'); - $removeMedia = Media::whereNull('status_id') - ->whereUserId($user->id) - ->whereProfileId($profile->id) - ->where('created_at', '>', now()->subHours(2)) - ->find($rpid); - if($removeMedia) { - $dateTime = Carbon::now(); - MediaDeletePipeline::dispatch($removeMedia) - ->onQueue('mmo') - ->delay($dateTime->addMinutes(15)); - } - } - - $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->mime = $mime; - $media->caption = $request->input('description'); - $media->filter_class = $filterClass; - $media->filter_name = $filterName; - if($license) { - $media->license = $license; - } - $media->save(); - - switch ($media->mime) { - case 'image/jpeg': - case 'image/png': - ImageOptimize::dispatch($media)->onQueue('mmo'); - break; - - case 'video/mp4': - VideoThumbnail::dispatch($media)->onQueue('mmo'); - $preview_url = '/storage/no-preview.png'; - $url = '/storage/no-preview.png'; - break; - } - - Cache::forget($limitKey); - $resource = new Fractal\Resource\Item($media, new MediaTransformer()); - $res = $this->fractal->createData($resource)->toArray(); - $res['preview_url'] = $media->url(). '?v=' . time(); - $res['url'] = null; - return $this->json($res, 202); - } - - /** - * GET /api/v1/mutes - * - * - * @return AccountTransformer - */ - public function accountMutes(Request $request) - { - abort_if(!$request->user(), 403); - - $this->validate($request, [ - 'limit' => 'nullable|integer|min:1|max:40' - ]); - - $user = $request->user(); - $limit = $request->input('limit', 40); - - $mutes = UserFilter::whereUserId($user->profile_id) - ->whereFilterableType('App\Profile') - ->whereFilterType('mute') - ->orderByDesc('id') - ->simplePaginate($limit) - ->pluck('filterable_id') - ->map(function($id) { - return AccountService::get($id, true); - }) - ->filter(function($account) { - return $account && isset($account['id']); - }) - ->values(); - - return $this->json($mutes); - } - - /** - * POST /api/v1/accounts/{id}/mute - * - * @param integer $id - * - * @return RelationshipTransformer - */ - public function accountMuteById(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $user = $request->user(); - $pid = $user->profile_id; + return $this->json($profiles); + } + + /** + * GET /api/v1/blocks + * + * + * + * @return \App\Transformer\Api\AccountTransformer + */ + public function accountBlocks(Request $request) + { + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'limit' => 'nullable|integer|min:1|max:40', + 'page' => 'nullable|integer|min:1|max:10' + ]); + + $user = $request->user(); + $limit = $request->input('limit') ?? 40; + + $blocked = UserFilter::select('filterable_id','filterable_type','filter_type','user_id') + ->whereUserId($user->profile_id) + ->whereFilterableType('App\Profile') + ->whereFilterType('block') + ->orderByDesc('id') + ->simplePaginate($limit) + ->pluck('filterable_id') + ->map(function($id) { + return AccountService::get($id, true); + }) + ->filter(function($account) { + return $account && isset($account['id']); + }) + ->values(); + + return $this->json($blocked); + } + + /** + * POST /api/v1/accounts/{id}/block + * + * @param integer $id + * + * @return \App\Transformer\Api\RelationshipTransformer + */ + public function accountBlockById(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $user = $request->user(); + $pid = $user->profile_id ?? $user->profile->id; + AccountService::setLastActive($user->id); + + if(intval($id) === intval($pid)) { + abort(400, 'You cannot block yourself'); + } + + $profile = Profile::findOrFail($id); + + if($profile->user && $profile->user->is_admin == true) { + abort(400, 'You cannot block an admin'); + } + + $count = UserFilterService::blockCount($pid); + $maxLimit = intval(config('instance.user_filters.max_user_blocks')); + if($count == 0) { + $filterCount = UserFilter::whereUserId($pid) + ->whereFilterType('block') + ->get() + ->map(function($rec) { + return AccountService::get($rec->filterable_id, true); + }) + ->filter(function($account) { + return $account && isset($account['id']); + }) + ->values() + ->count(); + abort_if($filterCount >= $maxLimit, 422, AccountController::FILTER_LIMIT_BLOCK_TEXT . $maxLimit . ' accounts'); + } else { + abort_if($count >= $maxLimit, 422, AccountController::FILTER_LIMIT_BLOCK_TEXT . $maxLimit . ' accounts'); + } + + $followed = Follower::whereProfileId($profile->id)->whereFollowingId($pid)->first(); + if($followed) { + $followed->delete(); + $profile->following_count = Follower::whereProfileId($profile->id)->count(); + $profile->save(); + $selfProfile = $user->profile; + $selfProfile->followers_count = Follower::whereFollowingId($pid)->count(); + $selfProfile->save(); + FollowerService::remove($profile->id, $pid); + AccountService::del($pid); + AccountService::del($profile->id); + } + + $following = Follower::whereProfileId($pid)->whereFollowingId($profile->id)->first(); + if($following) { + $following->delete(); + $profile->followers_count = Follower::whereFollowingId($profile->id)->count(); + $profile->save(); + $selfProfile = $user->profile; + $selfProfile->following_count = Follower::whereProfileId($pid)->count(); + $selfProfile->save(); + FollowerService::remove($pid, $profile->pid); + AccountService::del($pid); + AccountService::del($profile->id); + } + + Notification::whereProfileId($pid) + ->whereActorId($profile->id) + ->get() + ->map(function($n) use($pid) { + NotificationService::del($pid, $n['id']); + $n->forceDelete(); + }); + + $filter = UserFilter::firstOrCreate([ + 'user_id' => $pid, + 'filterable_id' => $profile->id, + 'filterable_type' => 'App\Profile', + 'filter_type' => 'block', + ]); + + UserFilterService::block($pid, $id); + RelationshipService::refresh($pid, $id); + $resource = new Fractal\Resource\Item($profile, new RelationshipTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + + return $this->json($res); + } + + /** + * POST /api/v1/accounts/{id}/unblock + * + * @param integer $id + * + * @return \App\Transformer\Api\RelationshipTransformer + */ + public function accountUnblockById(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $user = $request->user(); + $pid = $user->profile_id ?? $user->profile->id; + AccountService::setLastActive($user->id); + + if(intval($id) === intval($pid)) { + abort(400, 'You cannot unblock yourself'); + } + + $profile = Profile::findOrFail($id); + + $filter = UserFilter::whereUserId($pid) + ->whereFilterableId($profile->id) + ->whereFilterableType('App\Profile') + ->whereFilterType('block') + ->first(); + + if($filter) { + $filter->delete(); + UserFilterService::unblock($pid, $profile->id); + RelationshipService::refresh($pid, $id); + } + + $resource = new Fractal\Resource\Item($profile, new RelationshipTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + + return $this->json($res); + } + + /** + * GET /api/v1/custom_emojis + * + * Return custom emoji + * + * @return array + */ + public function customEmojis() + { + return response(CustomEmojiService::all())->header('Content-Type', 'application/json'); + } + + /** + * GET /api/v1/domain_blocks + * + * Return empty array + * + * @return array + */ + public function accountDomainBlocks(Request $request) + { + abort_if(!$request->user(), 403); + return response()->json([]); + } + + /** + * GET /api/v1/endorsements + * + * Return empty array + * + * @return array + */ + public function accountEndorsements(Request $request) + { + abort_if(!$request->user(), 403); + return response()->json([]); + } + + /** + * GET /api/v1/favourites + * + * Returns collection of liked statuses + * + * @return \App\Transformer\Api\StatusTransformer + */ + public function accountFavourites(Request $request) + { + abort_if(!$request->user(), 403); + $this->validate($request, [ + 'limit' => 'sometimes|integer|min:1|max:20' + ]); + + $user = $request->user(); + $maxId = $request->input('max_id'); + $minId = $request->input('min_id'); + $limit = $request->input('limit') ?? 10; + + $res = Like::whereProfileId($user->profile_id) + ->when($maxId, function($q, $maxId) { + return $q->where('id', '<', $maxId); + }) + ->when($minId, function($q, $minId) { + return $q->where('id', '>', $minId); + }) + ->orderByDesc('id') + ->limit($limit) + ->get() + ->map(function($like) { + $status = StatusService::getMastodon($like['status_id'], false); + $status['favourited'] = true; + $status['like_id'] = $like->id; + $status['liked_at'] = str_replace('+00:00', 'Z', $like->created_at->format(DATE_RFC3339_EXTENDED)); + return $status; + }) + ->filter(function($status) { + return $status && isset($status['id'], $status['like_id']); + }) + ->values(); + + if($res->count()) { + $ids = $res->map(function($status) { + return $status['like_id']; + }); + $max = $ids->max(); + $min = $ids->min(); + + $baseUrl = config('app.url') . '/api/v1/favourites?limit=' . $limit . '&'; + $link = '<'.$baseUrl.'max_id='.$max.'>; rel="next",<'.$baseUrl.'min_id='.$min.'>; rel="prev"'; + return $this->json($res, 200, ['Link' => $link]); + } else { + return $this->json($res); + } + } + + /** + * POST /api/v1/statuses/{id}/favourite + * + * @param integer $id + * + * @return \App\Transformer\Api\StatusTransformer + */ + public function statusFavouriteById(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $user = $request->user(); + + AccountService::setLastActive($user->id); + + $status = StatusService::getMastodon($id, false); + + abort_unless($status, 400); + + $spid = $status['account']['id']; + + if(intval($spid) !== intval($user->profile_id)) { + if($status['visibility'] == 'private') { + abort_if(!FollowerService::follows($user->profile_id, $spid), 403); + } else { + abort_if(!in_array($status['visibility'], ['public','unlisted']), 403); + } + } + + abort_if( + Like::whereProfileId($user->profile_id) + ->where('created_at', '>', now()->subDay()) + ->count() >= Like::MAX_PER_DAY, + 429 + ); + + $blocks = UserFilterService::blocks($spid); + if($blocks && in_array($user->profile_id, $blocks)) { + abort(422); + } + + $like = Like::firstOrCreate([ + 'profile_id' => $user->profile_id, + 'status_id' => $status['id'] + ]); + + if($like->wasRecentlyCreated == true) { + $like->status_profile_id = $spid; + $like->is_comment = !empty($status['in_reply_to_id']); + $like->save(); + Status::findOrFail($status['id'])->update([ + 'likes_count' => ($status['favourites_count'] ?? 0) + 1 + ]); + LikePipeline::dispatch($like)->onQueue('feed'); + } + + $status['favourited'] = true; + $status['favourites_count'] = $status['favourites_count'] + 1; + return $this->json($status); + } + + /** + * POST /api/v1/statuses/{id}/unfavourite + * + * @param integer $id + * + * @return \App\Transformer\Api\StatusTransformer + */ + public function statusUnfavouriteById(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $user = $request->user(); + + AccountService::setLastActive($user->id); + + $status = Status::findOrFail($id); + + if(intval($status->profile_id) !== intval($user->profile_id)) { + if($status->scope == 'private') { + abort_if(!$status->profile->followedBy($user->profile), 403); + } else { + abort_if(!in_array($status->scope, ['public','unlisted']), 403); + } + } + + $like = Like::whereProfileId($user->profile_id) + ->whereStatusId($status->id) + ->first(); + + if($like) { + $like->forceDelete(); + $status->likes_count = $status->likes_count > 1 ? $status->likes_count - 1 : 0; + $status->save(); + } + + StatusService::del($status->id); + + $res = StatusService::getMastodon($status->id, false); + $res['favourited'] = false; + return $this->json($res); + } + + /** + * GET /api/v1/filters + * + * Return empty response since we filter server side + * + * @return array + */ + public function accountFilters(Request $request) + { + abort_if(!$request->user(), 403); + + return response()->json([]); + } + + /** + * GET /api/v1/follow_requests + * + * Return array of Accounts that have sent follow requests + * + * @return \App\Transformer\Api\AccountTransformer + */ + public function accountFollowRequests(Request $request) + { + abort_if(!$request->user(), 403); + $this->validate($request, [ + 'limit' => 'sometimes|integer|min:1|max:100' + ]); + + $user = $request->user(); + + $res = FollowRequest::whereFollowingId($user->profile->id) + ->limit($request->input('limit', 40)) + ->pluck('follower_id') + ->map(function($id) { + return AccountService::getMastodon($id, true); + }) + ->filter(function($acct) { + return $acct && isset($acct['id']); + }) + ->values(); + + return $this->json($res); + } + + /** + * POST /api/v1/follow_requests/{id}/authorize + * + * @param integer $id + * + * @return null + */ + public function accountFollowRequestAccept(Request $request, $id) + { + abort_if(!$request->user(), 403); + $pid = $request->user()->profile_id; + $target = AccountService::getMastodon($id); + + if(!$target) { + return response()->json(['error' => 'Record not found'], 404); + } + + $followRequest = FollowRequest::whereFollowingId($pid)->whereFollowerId($id)->first(); + + if(!$followRequest) { + return response()->json(['error' => 'Record not found'], 404); + } + + $follower = $followRequest->follower; + $follow = new Follower(); + $follow->profile_id = $follower->id; + $follow->following_id = $pid; + $follow->save(); + + $profile = Profile::findOrFail($pid); + $profile->followers_count++; + $profile->save(); + AccountService::del($profile->id); + + $profile = Profile::findOrFail($follower->id); + $profile->following_count++; + $profile->save(); + AccountService::del($profile->id); + + if($follower->domain != null && $follower->private_key === null) { + FollowAcceptPipeline::dispatch($followRequest)->onQueue('follow'); + } else { + FollowPipeline::dispatch($follow); + $followRequest->delete(); + } + + RelationshipService::refresh($pid, $id); + $res = RelationshipService::get($pid, $id); + $res['followed_by'] = true; + return $this->json($res); + } + + /** + * POST /api/v1/follow_requests/{id}/reject + * + * @param integer $id + * + * @return null + */ + public function accountFollowRequestReject(Request $request, $id) + { + abort_if(!$request->user(), 403); + $pid = $request->user()->profile_id; + $target = AccountService::getMastodon($id); + + if(!$target) { + return response()->json(['error' => 'Record not found'], 404); + } + + $followRequest = FollowRequest::whereFollowingId($pid)->whereFollowerId($id)->first(); + + if(!$followRequest) { + return response()->json(['error' => 'Record not found'], 404); + } + + $follower = $followRequest->follower; + + if($follower->domain != null && $follower->private_key === null) { + FollowRejectPipeline::dispatch($followRequest)->onQueue('follow'); + } else { + $followRequest->delete(); + } + + RelationshipService::refresh($pid, $id); + $res = RelationshipService::get($pid, $id); + return $this->json($res); + } + + /** + * GET /api/v1/suggestions + * + * Return empty array as we don't support suggestions + * + * @return null + */ + public function accountSuggestions(Request $request) + { + abort_if(!$request->user(), 403); + + // todo + + return response()->json([]); + } + + /** + * GET /api/v1/instance + * + * Information about the server. + * + * @return Instance + */ + public function instance(Request $request) + { + $res = Cache::remember('api:v1:instance-data-response-v1', 1800, function () { + $contact = Cache::remember('api:v1:instance-data:contact', 604800, function () { + if(config_cache('instance.admin.pid')) { + return AccountService::getMastodon(config_cache('instance.admin.pid'), true); + } + $admin = User::whereIsAdmin(true)->first(); + return $admin && isset($admin->profile_id) ? + AccountService::getMastodon($admin->profile_id, true) : + null; + }); + + $stats = Cache::remember('api:v1:instance-data:stats', 43200, function () { + return [ + 'user_count' => User::count(), + 'status_count' => Status::whereNull('uri')->count(), + 'domain_count' => Instance::count(), + ]; + }); + + $rules = Cache::remember('api:v1:instance-data:rules', 604800, function () { + return config_cache('app.rules') ? + collect(json_decode(config_cache('app.rules'), true)) + ->map(function($rule, $key) { + $id = $key + 1; + return [ + 'id' => "{$id}", + 'text' => $rule + ]; + }) + ->toArray() : []; + }); + + return [ + 'uri' => config('pixelfed.domain.app'), + 'title' => config('app.name'), + 'short_description' => config_cache('app.short_description'), + 'description' => config_cache('app.description'), + 'email' => config('instance.email'), + 'version' => '2.7.2 (compatible; Pixelfed ' . config('pixelfed.version') .')', + 'urls' => [ + 'streaming_api' => 'wss://' . config('pixelfed.domain.app') + ], + 'stats' => $stats, + 'thumbnail' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')), + 'languages' => [config('app.locale')], + 'registrations' => (bool) config_cache('pixelfed.open_registration'), + 'approval_required' => false, + 'contact_account' => $contact, + 'rules' => $rules, + 'configuration' => [ + 'media_attachments' => [ + 'image_matrix_limit' => 16777216, + 'image_size_limit' => config('pixelfed.max_photo_size') * 1024, + 'supported_mime_types' => explode(',', config('pixelfed.media_types')), + 'video_frame_rate_limit' => 120, + 'video_matrix_limit' => 2304000, + 'video_size_limit' => config('pixelfed.max_photo_size') * 1024, + ], + 'polls' => [ + 'max_characters_per_option' => 50, + 'max_expiration' => 2629746, + 'max_options' => 4, + 'min_expiration' => 300 + ], + 'statuses' => [ + 'characters_reserved_per_url' => 23, + 'max_characters' => (int) config('pixelfed.max_caption_length'), + 'max_media_attachments' => (int) config('pixelfed.max_album_length') + ] + ] + ]; + }); + + return $this->json($res); + } + + /** + * GET /api/v1/lists + * + * Return empty array as we don't support lists + * + * @return null + */ + public function accountLists(Request $request) + { + abort_if(!$request->user(), 403); + + return response()->json([]); + } + + /** + * GET /api/v1/accounts/{id}/lists + * + * @param integer $id + * + * @return null + */ + public function accountListsById(Request $request, $id) + { + abort_if(!$request->user(), 403); + + return response()->json([]); + } + + /** + * POST /api/v1/media + * + * + * @return MediaTransformer + */ + public function mediaUpload(Request $request) + { + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'file.*' => [ + 'required_without:file', + 'mimetypes:' . config_cache('pixelfed.media_types'), + 'max:' . config_cache('pixelfed.max_photo_size'), + ], + 'file' => [ + 'required_without:file.*', + 'mimetypes:' . config_cache('pixelfed.media_types'), + 'max:' . config_cache('pixelfed.max_photo_size'), + ], + 'filter_name' => 'nullable|string|max:24', + 'filter_class' => 'nullable|alpha_dash|max:24', + 'description' => 'nullable|string|max:' . config_cache('pixelfed.max_altext_length') + ]); + + $user = $request->user(); + AccountService::setLastActive($user->id); + + if($user->last_active_at == null) { + return []; + } + + if(empty($request->file('file'))) { + return response('', 422); + } + + $limitKey = 'compose:rate-limit:media-upload:' . $user->id; + $limitTtl = now()->addMinutes(15); + $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { + $dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count(); + + return $dailyLimit >= 1250; + }); + abort_if($limitReached == true, 429); + + $profile = $user->profile; + + if(config_cache('pixelfed.enforce_account_limit') == true) { + $size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) { + return Media::whereUserId($user->id)->sum('size') / 1000; + }); + $limit = (int) config_cache('pixelfed.max_account_size'); + if ($size >= $limit) { + abort(403, 'Account size limit reached.'); + } + } + + $filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null; + $filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null; + + $photo = $request->file('file'); + + $mimes = explode(',', config_cache('pixelfed.media_types')); + if(in_array($photo->getMimeType(), $mimes) == false) { + abort(403, 'Invalid or unsupported mime type.'); + } + + $storagePath = MediaPathService::get($user, 2); + $path = $photo->storePublicly($storagePath); + $hash = \hash_file('sha256', $photo); + $license = null; + $mime = $photo->getMimeType(); + + // if($photo->getMimeType() == 'image/heic') { + // abort_if(config('image.driver') !== 'imagick', 422, 'Invalid media type'); + // abort_if(!in_array('HEIC', \Imagick::queryformats()), 422, 'Unsupported media type'); + // $oldPath = $path; + // $path = str_replace('.heic', '.jpg', $path); + // $mime = 'image/jpeg'; + // \Image::make($photo)->save(storage_path("app/{$path}")); + // @unlink(storage_path("app/{$oldPath}")); + // } + + $settings = UserSetting::whereUserId($user->id)->first(); + + if($settings && !empty($settings->compose_settings)) { + $compose = $settings->compose_settings; + + if(isset($compose['default_license']) && $compose['default_license'] != 1) { + $license = $compose['default_license']; + } + } + + abort_if(MediaBlocklistService::exists($hash) == true, 451); + + $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->mime = $mime; + $media->caption = $request->input('description'); + $media->filter_class = $filterClass; + $media->filter_name = $filterName; + if($license) { + $media->license = $license; + } + $media->save(); + + switch ($media->mime) { + case 'image/jpeg': + case 'image/png': + ImageOptimize::dispatch($media)->onQueue('mmo'); + break; + + case 'video/mp4': + VideoThumbnail::dispatch($media)->onQueue('mmo'); + $preview_url = '/storage/no-preview.png'; + $url = '/storage/no-preview.png'; + break; + } + + Cache::forget($limitKey); + $resource = new Fractal\Resource\Item($media, new MediaTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + $res['preview_url'] = $media->url(). '?v=' . time(); + $res['url'] = $media->url(). '?v=' . time(); + return $this->json($res); + } + + /** + * PUT /api/v1/media/{id} + * + * @param integer $id + * + * @return MediaTransformer + */ + public function mediaUpdate(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'description' => 'nullable|string|max:' . config_cache('pixelfed.max_altext_length') + ]); + + $user = $request->user(); + AccountService::setLastActive($user->id); + + $media = Media::whereUserId($user->id) + ->whereProfileId($user->profile_id) + ->findOrFail($id); + + $executed = RateLimiter::attempt( + 'media:update:'.$user->id, + 10, + function() use($media, $request) { + $caption = Purify::clean($request->input('description')); + + if($caption != $media->caption) { + $media->caption = $caption; + $media->save(); + + if($media->status_id) { + MediaService::del($media->status_id); + StatusService::del($media->status_id); + } + } + }); + + if(!$executed) { + return response()->json([ + 'error' => 'Too many attempts. Try again in a few minutes.' + ], 429); + }; + + $fractal = new Fractal\Manager(); + $fractal->setSerializer(new ArraySerializer()); + $resource = new Fractal\Resource\Item($media, new MediaTransformer()); + return $this->json($fractal->createData($resource)->toArray()); + } + + /** + * GET /api/v1/media/{id} + * + * @param integer $id + * + * @return MediaTransformer + */ + public function mediaGet(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $user = $request->user(); + AccountService::setLastActive($user->id); + + $media = Media::whereUserId($user->id) + ->whereNull('status_id') + ->findOrFail($id); + + $resource = new Fractal\Resource\Item($media, new MediaTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + return $this->json($res); + } + + /** + * POST /api/v2/media + * + * + * @return MediaTransformer + */ + public function mediaUploadV2(Request $request) + { + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'file.*' => [ + 'required_without:file', + 'mimetypes:' . config_cache('pixelfed.media_types'), + 'max:' . config_cache('pixelfed.max_photo_size'), + ], + 'file' => [ + 'required_without:file.*', + 'mimetypes:' . config_cache('pixelfed.media_types'), + 'max:' . config_cache('pixelfed.max_photo_size'), + ], + 'filter_name' => 'nullable|string|max:24', + 'filter_class' => 'nullable|alpha_dash|max:24', + 'description' => 'nullable|string|max:' . config_cache('pixelfed.max_altext_length'), + 'replace_id' => 'sometimes' + ]); + + $user = $request->user(); + + if($user->last_active_at == null) { + return []; + } + + AccountService::setLastActive($user->id); + + if(empty($request->file('file'))) { + return response('', 422); + } + + $limitKey = 'compose:rate-limit:media-upload:' . $user->id; + $limitTtl = now()->addMinutes(15); + $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { + $dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count(); + + return $dailyLimit >= 1250; + }); + abort_if($limitReached == true, 429); + + $profile = $user->profile; + + if(config_cache('pixelfed.enforce_account_limit') == true) { + $size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) { + return Media::whereUserId($user->id)->sum('size') / 1000; + }); + $limit = (int) config_cache('pixelfed.max_account_size'); + if ($size >= $limit) { + abort(403, 'Account size limit reached.'); + } + } + + $filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null; + $filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null; + + $photo = $request->file('file'); + + $mimes = explode(',', config_cache('pixelfed.media_types')); + if(in_array($photo->getMimeType(), $mimes) == false) { + abort(403, 'Invalid or unsupported mime type.'); + } + + $storagePath = MediaPathService::get($user, 2); + $path = $photo->storePublicly($storagePath); + $hash = \hash_file('sha256', $photo); + $license = null; + $mime = $photo->getMimeType(); + + $settings = UserSetting::whereUserId($user->id)->first(); + + if($settings && !empty($settings->compose_settings)) { + $compose = $settings->compose_settings; + + if(isset($compose['default_license']) && $compose['default_license'] != 1) { + $license = $compose['default_license']; + } + } + + abort_if(MediaBlocklistService::exists($hash) == true, 451); + + if($request->has('replace_id')) { + $rpid = $request->input('replace_id'); + $removeMedia = Media::whereNull('status_id') + ->whereUserId($user->id) + ->whereProfileId($profile->id) + ->where('created_at', '>', now()->subHours(2)) + ->find($rpid); + if($removeMedia) { + $dateTime = Carbon::now(); + MediaDeletePipeline::dispatch($removeMedia) + ->onQueue('mmo') + ->delay($dateTime->addMinutes(15)); + } + } + + $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->mime = $mime; + $media->caption = $request->input('description'); + $media->filter_class = $filterClass; + $media->filter_name = $filterName; + if($license) { + $media->license = $license; + } + $media->save(); + + switch ($media->mime) { + case 'image/jpeg': + case 'image/png': + ImageOptimize::dispatch($media)->onQueue('mmo'); + break; + + case 'video/mp4': + VideoThumbnail::dispatch($media)->onQueue('mmo'); + $preview_url = '/storage/no-preview.png'; + $url = '/storage/no-preview.png'; + break; + } + + Cache::forget($limitKey); + $resource = new Fractal\Resource\Item($media, new MediaTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + $res['preview_url'] = $media->url(). '?v=' . time(); + $res['url'] = null; + return $this->json($res, 202); + } + + /** + * GET /api/v1/mutes + * + * + * @return AccountTransformer + */ + public function accountMutes(Request $request) + { + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'limit' => 'nullable|integer|min:1|max:40' + ]); + + $user = $request->user(); + $limit = $request->input('limit', 40); + + $mutes = UserFilter::whereUserId($user->profile_id) + ->whereFilterableType('App\Profile') + ->whereFilterType('mute') + ->orderByDesc('id') + ->simplePaginate($limit) + ->pluck('filterable_id') + ->map(function($id) { + return AccountService::get($id, true); + }) + ->filter(function($account) { + return $account && isset($account['id']); + }) + ->values(); + + return $this->json($mutes); + } + + /** + * POST /api/v1/accounts/{id}/mute + * + * @param integer $id + * + * @return RelationshipTransformer + */ + public function accountMuteById(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $user = $request->user(); + $pid = $user->profile_id; if(intval($pid) === intval($id)) { return $this->json(['error' => 'You cannot mute yourself'], 500); } - $account = Profile::findOrFail($id); + $account = Profile::findOrFail($id); - $count = UserFilterService::muteCount($pid); - $maxLimit = intval(config('instance.user_filters.max_user_mutes')); - if($count == 0) { - $filterCount = UserFilter::whereUserId($pid) - ->whereFilterType('mute') - ->get() - ->map(function($rec) { - return AccountService::get($rec->filterable_id, true); - }) - ->filter(function($account) { - return $account && isset($account['id']); - }) - ->values() - ->count(); - abort_if($filterCount >= $maxLimit, 422, AccountController::FILTER_LIMIT_MUTE_TEXT . $maxLimit . ' accounts'); - } else { - abort_if($count >= $maxLimit, 422, AccountController::FILTER_LIMIT_MUTE_TEXT . $maxLimit . ' accounts'); - } + $count = UserFilterService::muteCount($pid); + $maxLimit = intval(config('instance.user_filters.max_user_mutes')); + if($count == 0) { + $filterCount = UserFilter::whereUserId($pid) + ->whereFilterType('mute') + ->get() + ->map(function($rec) { + return AccountService::get($rec->filterable_id, true); + }) + ->filter(function($account) { + return $account && isset($account['id']); + }) + ->values() + ->count(); + abort_if($filterCount >= $maxLimit, 422, AccountController::FILTER_LIMIT_MUTE_TEXT . $maxLimit . ' accounts'); + } else { + abort_if($count >= $maxLimit, 422, AccountController::FILTER_LIMIT_MUTE_TEXT . $maxLimit . ' accounts'); + } - $filter = UserFilter::firstOrCreate([ - 'user_id' => $pid, - 'filterable_id' => $account->id, - 'filterable_type' => 'App\Profile', - 'filter_type' => 'mute', - ]); + $filter = UserFilter::firstOrCreate([ + 'user_id' => $pid, + 'filterable_id' => $account->id, + 'filterable_type' => 'App\Profile', + 'filter_type' => 'mute', + ]); - RelationshipService::refresh($pid, $id); + RelationshipService::refresh($pid, $id); - $resource = new Fractal\Resource\Item($account, new RelationshipTransformer()); - $res = $this->fractal->createData($resource)->toArray(); - return $this->json($res); - } + $resource = new Fractal\Resource\Item($account, new RelationshipTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + return $this->json($res); + } - /** - * POST /api/v1/accounts/{id}/unmute - * - * @param integer $id - * - * @return RelationshipTransformer - */ - public function accountUnmuteById(Request $request, $id) - { - abort_if(!$request->user(), 403); + /** + * POST /api/v1/accounts/{id}/unmute + * + * @param integer $id + * + * @return RelationshipTransformer + */ + public function accountUnmuteById(Request $request, $id) + { + abort_if(!$request->user(), 403); - $user = $request->user(); - $pid = $user->profile_id; + $user = $request->user(); + $pid = $user->profile_id; if(intval($pid) === intval($id)) { return $this->json(['error' => 'You cannot unmute yourself'], 500); } - $profile = Profile::findOrFail($id); + $profile = Profile::findOrFail($id); - $filter = UserFilter::whereUserId($pid) - ->whereFilterableId($profile->id) - ->whereFilterableType('App\Profile') - ->whereFilterType('mute') - ->first(); + $filter = UserFilter::whereUserId($pid) + ->whereFilterableId($profile->id) + ->whereFilterableType('App\Profile') + ->whereFilterType('mute') + ->first(); - if($filter) { - $filter->delete(); - UserFilterService::unmute($pid, $profile->id); - RelationshipService::refresh($pid, $id); - } - - $resource = new Fractal\Resource\Item($profile, new RelationshipTransformer()); - $res = $this->fractal->createData($resource)->toArray(); - return $this->json($res); - } - - /** - * GET /api/v1/notifications - * - * - * @return NotificationTransformer - */ - public function accountNotifications(Request $request) - { - abort_if(!$request->user(), 403); - - $this->validate($request, [ - 'limit' => 'nullable|integer|min:1|max:100', - 'min_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX, - 'max_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX, - 'since_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX, - ]); - - $pid = $request->user()->profile_id; - $limit = $request->input('limit', 20); - - $since = $request->input('since_id'); - $min = $request->input('min_id'); - $max = $request->input('max_id'); - - if(!$since && !$min && !$max) { - $min = 1; - } - - $maxId = null; - $minId = null; - - if($max) { - $res = NotificationService::getMaxMastodon($pid, $max, $limit); - $ids = NotificationService::getRankedMaxId($pid, $max, $limit); - if(!empty($ids)) { - $maxId = max($ids); - $minId = min($ids); - } - } else { - $res = NotificationService::getMinMastodon($pid, $min ?? $since, $limit); - $ids = NotificationService::getRankedMinId($pid, $min ?? $since, $limit); - if(!empty($ids)) { - $maxId = max($ids); - $minId = min($ids); - } - } - - if(empty($res) && !Cache::has('pf:services:notifications:hasSynced:'.$pid)) { - Cache::put('pf:services:notifications:hasSynced:'.$pid, 1, 1209600); - NotificationService::warmCache($pid, 400, true); - } - - $baseUrl = config('app.url') . '/api/v1/notifications?limit=' . $limit . '&'; - - if($minId == $maxId) { - $minId = null; - } - - if($maxId) { - $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"'; - } - - if($minId) { - $link = '<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; - } - - if($maxId && $minId) { - $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next",<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; - } - - $headers = isset($link) ? ['Link' => $link] : []; - return $this->json($res, 200, $headers); - } - - /** - * GET /api/v1/timelines/home - * - * - * @return StatusTransformer - */ - public function timelineHome(Request $request) - { - $this->validate($request,[ - 'page' => 'sometimes|integer|max:40', - 'min_id' => 'sometimes|integer|min:0|max:' . PHP_INT_MAX, - 'max_id' => 'sometimes|integer|min:0|max:' . PHP_INT_MAX, - 'limit' => 'sometimes|integer|min:1|max:100', - 'include_reblogs' => 'sometimes', - ]); - - $napi = $request->has(self::PF_API_ENTITY_KEY); - $page = $request->input('page'); - $min = $request->input('min_id'); - $max = $request->input('max_id'); - $limit = $request->input('limit') ?? 20; - $pid = $request->user()->profile_id; - $includeReblogs = $request->filled('include_reblogs'); - $nullFields = $includeReblogs ? - ['in_reply_to_id'] : - ['in_reply_to_id', 'reblog_of_id']; - $inTypes = $includeReblogs ? - ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album', 'share'] : - ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']; - - $following = Cache::remember('profile:following:'.$pid, 1209600, function() use($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - return $following->push($pid)->toArray(); - }); - - if($min || $max) { - $dir = $min ? '>' : '<'; - $id = $min ?? $max; - $res = Status::select( - 'id', - 'profile_id', - 'type', - 'visibility', - 'in_reply_to_id', - 'reblog_of_id' - ) - ->where('id', $dir, $id) - ->whereNull($nullFields) - ->whereIntegerInRaw('profile_id', $following) - ->whereIn('type', $inTypes) - ->whereIn('visibility',['public', 'unlisted', 'private']) - ->orderByDesc('id') - ->take(($limit * 2)) - ->get() - ->map(function($s) use($pid, $napi) { - try { - $account = $napi ? AccountService::get($s['profile_id'], true) : AccountService::getMastodon($s['profile_id'], true); - if(!$account) { - return false; - } - $status = $napi ? StatusService::get($s['id'], false) : StatusService::getMastodon($s['id'], false); - if(!$status || !isset($status['account']) || !isset($status['account']['id'])) { - return false; - } - } catch(\Exception $e) { - return false; - } - - $status['account'] = $account; - - if($pid) { - $status['favourited'] = (bool) LikeService::liked($pid, $s['id']); - $status['reblogged'] = (bool) ReblogService::get($pid, $status['id']); - $status['bookmarked'] = (bool) BookmarkService::get($pid, $status['id']); - } - return $status; - }) - ->filter(function($status) { - return $status && isset($status['account']); - }) - ->map(function($status) use($pid) { - if(!empty($status['reblog'])) { - $status['reblog']['favourited'] = (bool) LikeService::liked($pid, $status['reblog']['id']); - $status['reblog']['reblogged'] = (bool) ReblogService::get($pid, $status['reblog']['id']); - $status['bookmarked'] = (bool) BookmarkService::get($pid, $status['id']); - } - - return $status; - }) - ->take($limit) - ->values(); - } else { - $res = Status::select( - 'id', - 'profile_id', - 'type', - 'visibility', - 'in_reply_to_id', - 'reblog_of_id', - ) - ->whereNull($nullFields) - ->whereIntegerInRaw('profile_id', $following) - ->whereIn('type', $inTypes) - ->whereIn('visibility',['public', 'unlisted', 'private']) - ->orderByDesc('id') - ->take(($limit * 2)) - ->get() - ->map(function($s) use($pid, $napi) { - try { - $account = $napi ? AccountService::get($s['profile_id'], true) : AccountService::getMastodon($s['profile_id'], true); - if(!$account) { - return false; - } - $status = $napi ? StatusService::get($s['id'], false) : StatusService::getMastodon($s['id'], false); - if(!$status || !isset($status['account']) || !isset($status['account']['id'])) { - return false; - } - } catch(\Exception $e) { - return false; - } - - $status['account'] = $account; - - if($pid) { - $status['favourited'] = (bool) LikeService::liked($pid, $s['id']); - $status['reblogged'] = (bool) ReblogService::get($pid, $status['id']); - $status['bookmarked'] = (bool) BookmarkService::get($pid, $status['id']); - } - return $status; - }) - ->filter(function($status) { - return $status && isset($status['account']); - }) - ->map(function($status) use($pid) { - if(!empty($status['reblog'])) { - $status['reblog']['favourited'] = (bool) LikeService::liked($pid, $status['reblog']['id']); - $status['reblog']['reblogged'] = (bool) ReblogService::get($pid, $status['reblog']['id']); - $status['bookmarked'] = (bool) BookmarkService::get($pid, $status['id']); - } - - return $status; - }) - ->take($limit) - ->values(); - } - - $baseUrl = config('app.url') . '/api/v1/timelines/home?limit=' . $limit . '&'; - $minId = $res->map(function($s) { - return ['id' => $s['id']]; - })->min('id'); - $maxId = $res->map(function($s) { - return ['id' => $s['id']]; - })->max('id'); - - if($minId == $maxId) { - $minId = null; - } - - if($maxId) { - $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"'; - } - - if($minId) { - $link = '<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; - } - - if($maxId && $minId) { - $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next",<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; - } - - $headers = isset($link) ? ['Link' => $link] : []; - return $this->json($res->toArray(), 200, $headers); - } - - /** - * GET /api/v1/timelines/public - * - * - * @return StatusTransformer - */ - public function timelinePublic(Request $request) - { - $this->validate($request,[ - 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'limit' => 'nullable|integer|max:100', - 'remote' => 'sometimes', - 'local' => 'sometimes' - ]); - - $napi = $request->has(self::PF_API_ENTITY_KEY); - $min = $request->input('min_id'); - $max = $request->input('max_id'); - $limit = $request->input('limit') ?? 20; - $user = $request->user(); - $remote = ($request->has('remote') && $request->input('remote') == true) || ($request->filled('local') && $request->input('local') != true); - $filtered = $user ? UserFilterService::filters($user->profile_id) : []; - - if((!$request->has('local') || $remote) && config('instance.timeline.network.cached')) { - Cache::remember('api:v1:timelines:network:cache_check', 10368000, function() { - if(NetworkTimelineService::count() == 0) { - NetworkTimelineService::warmCache(true, config('instance.timeline.network.cache_dropoff')); - } - }); - - if ($max) { - $feed = NetworkTimelineService::getRankedMaxId($max, $limit + 5); - } else if ($min) { - $feed = NetworkTimelineService::getRankedMinId($min, $limit + 5); - } else { - $feed = NetworkTimelineService::get(0, $limit + 5); - } - } else { - Cache::remember('api:v1:timelines:public:cache_check', 10368000, function() { - if(PublicTimelineService::count() == 0) { - PublicTimelineService::warmCache(true, 400); - } - }); - - if ($max) { - $feed = PublicTimelineService::getRankedMaxId($max, $limit + 5); - } else if ($min) { - $feed = PublicTimelineService::getRankedMinId($min, $limit + 5); - } else { - $feed = PublicTimelineService::get(0, $limit + 5); - } + if($filter) { + $filter->delete(); + UserFilterService::unmute($pid, $profile->id); + RelationshipService::refresh($pid, $id); } - $res = collect($feed) - ->filter(function($k) use($min, $max) { - if(!$min && !$max) { - return true; - } + $resource = new Fractal\Resource\Item($profile, new RelationshipTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + return $this->json($res); + } - if($min) { - return $min != $k; - } + /** + * GET /api/v1/notifications + * + * + * @return NotificationTransformer + */ + public function accountNotifications(Request $request) + { + abort_if(!$request->user(), 403); - if($max) { - return $max != $k; - } - }) - ->map(function($k) use($user, $napi) { - try { - $status = $napi ? StatusService::get($k) : StatusService::getMastodon($k); - if(!$status || !isset($status['account']) || !isset($status['account']['id'])) { - return false; - } - } catch(\Exception $e) { - return false; - } + $this->validate($request, [ + 'limit' => 'nullable|integer|min:1|max:100', + 'min_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX, + 'max_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX, + 'since_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX, + 'types[]' => 'sometimes|array', + 'type' => 'sometimes|string|in:mention,reblog,follow,favourite' + ]); - $account = $napi ? AccountService::get($status['account']['id'], true) : AccountService::getMastodon($status['account']['id'], true); - if(!$account) { - return false; - } + $pid = $request->user()->profile_id; + $limit = $request->input('limit', 20); - $status['account'] = $account; + $since = $request->input('since_id'); + $min = $request->input('min_id'); + $max = $request->input('max_id'); - if($user) { - $status['favourited'] = (bool) LikeService::liked($user->profile_id, $k); - $status['reblogged'] = (bool) ReblogService::get($user->profile_id, $status['id']); + if(!$since && !$min && !$max) { + $min = 1; + } + + $types = $request->input('types'); + + $maxId = null; + $minId = null; + AccountService::setLastActive($request->user()->id); + + $res = $max ? + NotificationService::getMaxMastodon($pid, $max, $limit) : + NotificationService::getMinMastodon($pid, $min ?? $since, $limit); + $ids = $max ? + NotificationService::getRankedMaxId($pid, $max, $limit) : + NotificationService::getRankedMinId($pid, $min ?? $since, $limit); + if(!empty($ids)) { + $maxId = max($ids); + $minId = min($ids); + } + + if(empty($res)) { + if(!Cache::has('pf:services:notifications:hasSynced:'.$pid)) { + Cache::put('pf:services:notifications:hasSynced:'.$pid, 1, 1209600); + NotificationService::warmCache($pid, 400, true); + } + } + + $baseUrl = config('app.url') . '/api/v1/notifications?limit=' . $limit . '&'; + + if($minId == $maxId) { + $minId = null; + } + + if($maxId) { + $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"'; + } + + if($minId) { + $link = '<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; + } + + if($maxId && $minId) { + $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next",<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; + } + + $headers = isset($link) ? ['Link' => $link] : []; + return $this->json($res, 200, $headers); + } + + /** + * GET /api/v1/timelines/home + * + * + * @return StatusTransformer + */ + public function timelineHome(Request $request) + { + $this->validate($request,[ + 'page' => 'sometimes|integer|max:40', + 'min_id' => 'sometimes|integer|min:0|max:' . PHP_INT_MAX, + 'max_id' => 'sometimes|integer|min:0|max:' . PHP_INT_MAX, + 'limit' => 'sometimes|integer|min:1|max:40', + 'include_reblogs' => 'sometimes', + ]); + + $napi = $request->has(self::PF_API_ENTITY_KEY); + $page = $request->input('page'); + $min = $request->input('min_id'); + $max = $request->input('max_id'); + $limit = $request->input('limit') ?? 20; + $pid = $request->user()->profile_id; + $includeReblogs = $request->filled('include_reblogs') ? $request->boolean('include_reblogs') : false; + $nullFields = $includeReblogs ? + ['in_reply_to_id'] : + ['in_reply_to_id', 'reblog_of_id']; + $inTypes = $includeReblogs ? + ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album', 'share'] : + ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']; + AccountService::setLastActive($request->user()->id); + + if(config('exp.cached_home_timeline')) { + $paddedLimit = $includeReblogs ? $limit + 10 : $limit + 50; + if($min || $max) { + if($request->has('min_id')) { + $res = HomeTimelineService::getRankedMinId($pid, $min ?? 0, $paddedLimit); + } else { + $res = HomeTimelineService::getRankedMaxId($pid, $max ?? 0, $paddedLimit); + } + } else { + $res = HomeTimelineService::get($pid, 0, $paddedLimit); + } + + if(!$res) { + $res = Cache::has('pf:services:apiv1:home:cached:coldbootcheck:' . $pid); + if(!$res) { + Cache::set('pf:services:apiv1:home:cached:coldbootcheck:' . $pid, 1, 86400); + FeedWarmCachePipeline::dispatchSync($pid); + return response()->json([], 206); + } else { + Cache::set('pf:services:apiv1:home:cached:coldbootcheck:' . $pid, 1, 86400); + return response()->json([], 206); + } + } + + $res = collect($res) + ->map(function($id) use($napi) { + return $napi ? StatusService::get($id, false) : StatusService::getMastodon($id, false); + }) + ->filter(function($res) { + return $res && isset($res['account']); + }) + ->filter(function($s) use($includeReblogs) { + return $includeReblogs ? true : $s['reblog'] == null; + }) + ->take($limit) + ->map(function($status) use($pid) { + if($pid) { + $status['favourited'] = (bool) LikeService::liked($pid, $status['id']); + $status['reblogged'] = (bool) ReblogService::get($pid, $status['id']); + $status['bookmarked'] = (bool) BookmarkService::get($pid, $status['id']); + } + return $status; + }) + ->values(); + + $baseUrl = config('app.url') . '/api/v1/timelines/home?limit=' . $limit . '&'; + $minId = $res->map(function($s) { + return ['id' => $s['id']]; + })->min('id'); + $maxId = $res->map(function($s) { + return ['id' => $s['id']]; + })->max('id'); + + if($minId == $maxId) { + $minId = null; + } + + if($maxId) { + $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"'; + } + + if($minId) { + $link = '<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; + } + + if($maxId && $minId) { + $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next",<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; + } + + $headers = isset($link) ? ['Link' => $link] : []; + return $this->json($res->toArray(), 200, $headers); + } + + $following = Cache::remember('profile:following:'.$pid, 1209600, function() use($pid) { + $following = Follower::whereProfileId($pid)->pluck('following_id'); + return $following->push($pid)->toArray(); + }); + + $muted = UserFilterService::mutes($pid); + + if($muted && count($muted)) { + $following = array_diff($following, $muted); + } + + + if($min || $max) { + $dir = $min ? '>' : '<'; + $id = $min ?? $max; + $res = Status::select( + 'id', + 'profile_id', + 'type', + 'visibility', + 'in_reply_to_id', + 'reblog_of_id' + ) + ->where('id', $dir, $id) + ->whereNull($nullFields) + ->whereIntegerInRaw('profile_id', $following) + ->whereIn('type', $inTypes) + ->whereIn('visibility',['public', 'unlisted', 'private']) + ->orderByDesc('id') + ->take(($limit * 2)) + ->get() + ->map(function($s) use($pid, $napi) { + try { + $account = $napi ? AccountService::get($s['profile_id'], true) : AccountService::getMastodon($s['profile_id'], true); + if(!$account) { + return false; + } + $status = $napi ? StatusService::get($s['id'], false) : StatusService::getMastodon($s['id'], false); + if(!$status || !isset($status['account']) || !isset($status['account']['id'])) { + return false; + } + } catch(\Exception $e) { + return false; + } + + $status['account'] = $account; + + if($pid) { + $status['favourited'] = (bool) LikeService::liked($pid, $s['id']); + $status['reblogged'] = (bool) ReblogService::get($pid, $status['id']); + $status['bookmarked'] = (bool) BookmarkService::get($pid, $status['id']); + } + return $status; + }) + ->filter(function($status) { + return $status && isset($status['account']); + }) + ->map(function($status) use($pid) { + if(!empty($status['reblog'])) { + $status['reblog']['favourited'] = (bool) LikeService::liked($pid, $status['reblog']['id']); + $status['reblog']['reblogged'] = (bool) ReblogService::get($pid, $status['reblog']['id']); + $status['bookmarked'] = (bool) BookmarkService::get($pid, $status['id']); + } + + return $status; + }) + ->take($limit) + ->values(); + } else { + $res = Status::select( + 'id', + 'profile_id', + 'type', + 'visibility', + 'in_reply_to_id', + 'reblog_of_id', + ) + ->whereNull($nullFields) + ->whereIntegerInRaw('profile_id', $following) + ->whereIn('type', $inTypes) + ->whereIn('visibility',['public', 'unlisted', 'private']) + ->orderByDesc('id') + ->take(($limit * 2)) + ->get() + ->map(function($s) use($pid, $napi) { + try { + $account = $napi ? AccountService::get($s['profile_id'], true) : AccountService::getMastodon($s['profile_id'], true); + if(!$account) { + return false; + } + $status = $napi ? StatusService::get($s['id'], false) : StatusService::getMastodon($s['id'], false); + if(!$status || !isset($status['account']) || !isset($status['account']['id'])) { + return false; + } + } catch(\Exception $e) { + return false; + } + + $status['account'] = $account; + + if($pid) { + $status['favourited'] = (bool) LikeService::liked($pid, $s['id']); + $status['reblogged'] = (bool) ReblogService::get($pid, $status['id']); + $status['bookmarked'] = (bool) BookmarkService::get($pid, $status['id']); + } + return $status; + }) + ->filter(function($status) { + return $status && isset($status['account']); + }) + ->map(function($status) use($pid) { + if(!empty($status['reblog'])) { + $status['reblog']['favourited'] = (bool) LikeService::liked($pid, $status['reblog']['id']); + $status['reblog']['reblogged'] = (bool) ReblogService::get($pid, $status['reblog']['id']); + $status['bookmarked'] = (bool) BookmarkService::get($pid, $status['id']); + } + + return $status; + }) + ->take($limit) + ->values(); + } + + $baseUrl = config('app.url') . '/api/v1/timelines/home?limit=' . $limit . '&'; + $minId = $res->map(function($s) { + return ['id' => $s['id']]; + })->min('id'); + $maxId = $res->map(function($s) { + return ['id' => $s['id']]; + })->max('id'); + + if($minId == $maxId) { + $minId = null; + } + + if($maxId) { + $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"'; + } + + if($minId) { + $link = '<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; + } + + if($maxId && $minId) { + $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next",<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; + } + + $headers = isset($link) ? ['Link' => $link] : []; + return $this->json($res->toArray(), 200, $headers); + } + + /** + * GET /api/v1/timelines/public + * + * + * @return StatusTransformer + */ + public function timelinePublic(Request $request) + { + $this->validate($request,[ + 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, + 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, + 'limit' => 'nullable|integer|max:100', + 'remote' => 'sometimes', + 'local' => 'sometimes' + ]); + + $napi = $request->has(self::PF_API_ENTITY_KEY); + $min = $request->input('min_id'); + $max = $request->input('max_id'); + $limit = $request->input('limit') ?? 20; + $user = $request->user(); + $remote = $request->has('remote'); + $local = $request->has('local'); + $filtered = $user ? UserFilterService::filters($user->profile_id) : []; + AccountService::setLastActive($user->id); + $domainBlocks = UserFilterService::domainBlocks($user->profile_id); + + if($remote && config('instance.timeline.network.cached')) { + Cache::remember('api:v1:timelines:network:cache_check', 10368000, function() { + if(NetworkTimelineService::count() == 0) { + NetworkTimelineService::warmCache(true, config('instance.timeline.network.cache_dropoff')); + } + }); + + if ($max) { + $feed = NetworkTimelineService::getRankedMaxId($max, $limit + 5); + } else if ($min) { + $feed = NetworkTimelineService::getRankedMinId($min, $limit + 5); + } else { + $feed = NetworkTimelineService::get(0, $limit + 5); + } + } + + if($local || !$remote && !$local) { + Cache::remember('api:v1:timelines:public:cache_check', 10368000, function() { + if(PublicTimelineService::count() == 0) { + PublicTimelineService::warmCache(true, 400); + } + }); + + if ($max) { + $feed = PublicTimelineService::getRankedMaxId($max, $limit + 5); + } else if ($min) { + $feed = PublicTimelineService::getRankedMinId($min, $limit + 5); + } else { + $feed = PublicTimelineService::get(0, $limit + 5); + } + } + + $res = collect($feed) + ->filter(function($k) use($min, $max) { + if(!$min && !$max) { + return true; + } + + if($min) { + return $min != $k; + } + + if($max) { + return $max != $k; + } + }) + ->map(function($k) use($user, $napi) { + try { + $status = $napi ? StatusService::get($k) : StatusService::getMastodon($k); + if(!$status || !isset($status['account']) || !isset($status['account']['id'])) { + return false; + } + } catch(\Exception $e) { + return false; + } + + $account = $napi ? AccountService::get($status['account']['id'], true) : AccountService::getMastodon($status['account']['id'], true); + if(!$account) { + return false; + } + + $status['account'] = $account; + + if($user) { + $status['favourited'] = (bool) LikeService::liked($user->profile_id, $k); + $status['reblogged'] = (bool) ReblogService::get($user->profile_id, $status['id']); $status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $status['id']); - } - return $status; - }) - ->filter(function($s) use($filtered) { - return $s && isset($s['account']) && in_array($s['account']['id'], $filtered) == false; - }) - ->take($limit) - ->values(); - - $baseUrl = config('app.url') . '/api/v1/timelines/public?limit=' . $limit . '&'; - if($remote) { - $baseUrl .= 'remote=1&'; - } - $minId = $res->map(function($s) { - return ['id' => $s['id']]; - })->min('id'); - $maxId = $res->map(function($s) { - return ['id' => $s['id']]; - })->max('id'); - - if($minId == $maxId) { - $minId = null; - } - - if($maxId) { - $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"'; - } - - if($minId) { - $link = '<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; - } - - if($maxId && $minId) { - $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next",<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; - } - - $headers = isset($link) ? ['Link' => $link] : []; - return $this->json($res->toArray(), 200, $headers); - } - - /** - * GET /api/v1/conversations - * - * Not implemented - * - * @return array - */ - public function conversations(Request $request) - { - abort_if(!$request->user(), 403); - $this->validate($request, [ - 'limit' => 'min:1|max:40', - 'scope' => 'nullable|in:inbox,sent,requests' - ]); - - $limit = $request->input('limit', 20); - $scope = $request->input('scope', 'inbox'); - $pid = $request->user()->profile_id; - - if(config('database.default') == 'pgsql') { - $dms = DirectMessage::when($scope === 'inbox', function($q, $scope) use($pid) { - return $q->whereIsHidden(false)->where('to_id', $pid)->orWhere('from_id', $pid); - }) - ->when($scope === 'sent', function($q, $scope) use($pid) { - return $q->whereFromId($pid)->groupBy(['to_id', 'id']); - }) - ->when($scope === 'requests', function($q, $scope) use($pid) { - return $q->whereToId($pid)->whereIsHidden(true); - }); - } else { - $dms = Conversation::when($scope === 'inbox', function($q, $scope) use($pid) { - return $q->whereIsHidden(false) - ->where('to_id', $pid) - ->orWhere('from_id', $pid) - ->orderByDesc('status_id') - ->groupBy(['to_id', 'from_id']); - }) - ->when($scope === 'sent', function($q, $scope) use($pid) { - return $q->whereFromId($pid)->groupBy('to_id'); - }) - ->when($scope === 'requests', function($q, $scope) use($pid) { - return $q->whereToId($pid)->whereIsHidden(true); - }); - } - - $dms = $dms->orderByDesc('status_id') - ->simplePaginate($limit) - ->map(function($dm) use($pid) { - $from = $pid == $dm->to_id ? $dm->from_id : $dm->to_id; - $res = [ - 'id' => $dm->id, - 'unread' => false, - 'accounts' => [ - AccountService::getMastodon($from, true) - ], - 'last_status' => StatusService::getDirectMessage($dm->status_id) - ]; - return $res; - }) - ->filter(function($dm) { - if(!$dm || empty($dm['last_status']) || !isset($dm['accounts']) || !count($dm['accounts']) || !isset($dm['accounts'][0]) || !isset($dm['accounts'][0]['id'])) { - return false; - } - return true; - }) - ->unique(function($item, $key) { - return $item['accounts'][0]['id']; - }) - ->values(); - - return $this->json($dms); - } - - /** - * GET /api/v1/statuses/{id} - * - * @param integer $id - * - * @return StatusTransformer - */ - public function statusById(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $user = $request->user(); - - $res = $request->has(self::PF_API_ENTITY_KEY) ? StatusService::get($id, false) : StatusService::getMastodon($id, false); - if(!$res || !isset($res['visibility'])) { - abort(404); - } - - $scope = $res['visibility']; - if(!in_array($scope, ['public', 'unlisted'])) { - if($scope === 'private') { - if(intval($res['account']['id']) !== intval($user->profile_id)) { - abort_unless(FollowerService::follows($user->profile_id, $res['account']['id']), 403); - } - } else { - abort(400, 'Invalid request'); - } - } - - $res['favourited'] = LikeService::liked($user->profile_id, $res['id']); - $res['reblogged'] = ReblogService::get($user->profile_id, $res['id']); - $res['bookmarked'] = BookmarkService::get($user->profile_id, $res['id']); - - return $this->json($res); - } - - /** - * GET /api/v1/statuses/{id}/context - * - * @param integer $id - * - * @return StatusTransformer - */ - public function statusContext(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $user = $request->user(); - $pid = $user->profile_id; - $status = StatusService::getMastodon($id, false); - - if(!$status || !isset($status['account'])) { - return response('', 404); - } - - if(intval($status['account']['id']) !== intval($user->profile_id)) { - if($status['visibility'] == 'private') { - if(!FollowerService::follows($user->profile_id, $status['account']['id'])) { - return response('', 404); - } - } else { - if(!in_array($status['visibility'], ['public','unlisted'])) { - return response('', 404); - } - } - } - - $ancestors = []; - $descendants = []; - - if($status['in_reply_to_id']) { - $ancestors[] = StatusService::getMastodon($status['in_reply_to_id'], false); - } - - if($status['replies_count']) { - $filters = UserFilterService::filters($pid); - - $descendants = DB::table('statuses') - ->where('in_reply_to_id', $id) - ->limit(20) - ->pluck('id') - ->map(function($sid) { - return StatusService::getMastodon($sid, false); - }) - ->filter(function($post) use($filters) { - return $post && isset($post['account'], $post['account']['id']) && !in_array($post['account']['id'], $filters); - }) - ->map(function($status) use($pid) { - $status['favourited'] = LikeService::liked($pid, $status['id']); - $status['reblogged'] = ReblogService::get($pid, $status['id']); - return $status; - }) - ->values(); - } - - $res = [ - 'ancestors' => $ancestors, - 'descendants' => $descendants - ]; - - return $this->json($res); - } - - /** - * GET /api/v1/statuses/{id}/card - * - * @param integer $id - * - * @return StatusTransformer - */ - public function statusCard(Request $request, $id) - { - abort_if(!$request->user(), 403); - $res = []; - return response()->json($res); - } - - /** - * GET /api/v1/statuses/{id}/reblogged_by - * - * @param integer $id - * - * @return AccountTransformer - */ - public function statusRebloggedBy(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $this->validate($request, [ - 'limit' => 'sometimes|integer|min:1|max:80' - ]); - - $limit = $request->input('limit', 10); - $user = $request->user(); - $pid = $user->profile_id; - $status = Status::findOrFail($id); - $account = AccountService::get($status->profile_id, true); - abort_if(!$account, 404); - $author = intval($status->profile_id) === intval($pid) || $user->is_admin; - $napi = $request->has(self::PF_API_ENTITY_KEY); - - abort_if( - !$status->type || - !in_array($status->type, ['photo','photo:album', 'photo:video:album', 'reply', 'text', 'video', 'video:album']), - 404, - ); - - if(!$author) { - if($status->scope == 'private') { - abort_if(!FollowerService::follows($pid, $status->profile_id), 403); - } else { - abort_if(!in_array($status->scope, ['public','unlisted']), 403); - } - - if($request->has('cursor')) { - return $this->json([]); - } - } - - $res = Status::where('reblog_of_id', $status->id) - ->orderByDesc('id') - ->cursorPaginate($limit) - ->withQueryString(); - - if(!$res) { - return $this->json([]); - } - - $headers = []; - if($author && $res->hasPages()) { - $links = ''; - if($res->onFirstPage()) { - if($res->nextPageUrl()) { - $links = '<' . $res->nextPageUrl() .'>; rel="prev"'; - } - } else { - if($res->previousPageUrl()) { - $links = '<' . $res->previousPageUrl() .'>; rel="next"'; - } - - if($res->nextPageUrl()) { - if(!empty($links)) { - $links .= ', '; - } - $links .= '<' . $res->nextPageUrl() .'>; rel="prev"'; - } - } - - $headers = ['Link' => $links]; - } - - $res = $res->map(function($status) use($pid, $napi) { - $account = $napi ? AccountService::get($status->profile_id, true) : AccountService::getMastodon($status->profile_id, true); - if(!$account) { - return false; - } - if($napi) { - $account['follows'] = $status->profile_id == $pid ? null : FollowerService::follows($pid, $status->profile_id); - } - return $account; - }) - ->filter(function($account) { - return $account && isset($account['id']); - }) - ->values(); - - return $this->json($res, 200, $headers); - } - - /** - * GET /api/v1/statuses/{id}/favourited_by - * - * @param integer $id - * - * @return AccountTransformer - */ - public function statusFavouritedBy(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $this->validate($request, [ - 'limit' => 'nullable|integer|min:1|max:80' - ]); - - $limit = $request->input('limit', 10); - $user = $request->user(); - $pid = $user->profile_id; - $status = Status::findOrFail($id); - $account = AccountService::get($status->profile_id, true); - abort_if(!$account, 404); - $author = intval($status->profile_id) === intval($pid) || $user->is_admin; - $napi = $request->has(self::PF_API_ENTITY_KEY); - - abort_if( - !$status->type || - !in_array($status->type, ['photo','photo:album', 'photo:video:album', 'reply', 'text', 'video', 'video:album']), - 404, - ); - - if(!$author) { - if($status->scope == 'private') { - abort_if(!FollowerService::follows($pid, $status->profile_id), 403); - } else { - abort_if(!in_array($status->scope, ['public','unlisted']), 403); - } - - if($request->has('cursor')) { - return $this->json([]); - } - } - - $res = Like::where('status_id', $status->id) - ->orderByDesc('id') - ->cursorPaginate($limit) - ->withQueryString(); - - if(!$res) { - return $this->json([]); - } - - $headers = []; - if($author && $res->hasPages()) { - $links = ''; - - if($res->onFirstPage()) { - if($res->nextPageUrl()) { - $links = '<' . $res->nextPageUrl() .'>; rel="prev"'; - } - } else { - if($res->previousPageUrl()) { - $links = '<' . $res->previousPageUrl() .'>; rel="next"'; - } - - if($res->nextPageUrl()) { - if(!empty($links)) { - $links .= ', '; - } - $links .= '<' . $res->nextPageUrl() .'>; rel="prev"'; - } - } - - $headers = ['Link' => $links]; - } - - $res = $res->map(function($like) use($pid, $napi) { - $account = $napi ? AccountService::get($like->profile_id, true) : AccountService::getMastodon($like->profile_id, true); - if(!$account) { - return false; - } - - if($napi) { - $account['follows'] = $like->profile_id == $pid ? null : FollowerService::follows($pid, $like->profile_id); - } - return $account; - }) - ->filter(function($account) { - return $account && isset($account['id']); - }) - ->values(); - - return $this->json($res, 200, $headers); - } - - /** - * POST /api/v1/statuses - * - * - * @return StatusTransformer - */ - public function statusCreate(Request $request) - { - abort_if(!$request->user(), 403); - - $this->validate($request, [ - 'status' => 'nullable|string', - 'in_reply_to_id' => 'nullable', - 'media_ids' => 'sometimes|array|max:' . config_cache('pixelfed.max_album_length'), - 'sensitive' => 'nullable', - 'visibility' => 'string|in:private,unlisted,public', - 'spoiler_text' => 'sometimes|max:140', - 'place_id' => 'sometimes|integer|min:1|max:128769', - 'collection_ids' => 'sometimes|array|max:3', - 'comments_disabled' => 'sometimes|boolean', - ]); - - if($request->hasHeader('idempotency-key')) { - $key = 'pf:api:v1:status:idempotency-key:' . $request->user()->id . ':' . hash('sha1', $request->header('idempotency-key')); - $exists = Cache::has($key); - abort_if($exists, 400, 'Duplicate idempotency key.'); - Cache::put($key, 1, 3600); - } - - if(config('costar.enabled') == true) { - $blockedKeywords = config('costar.keyword.block'); - if($blockedKeywords !== null && $request->status) { - $keywords = config('costar.keyword.block'); - foreach($keywords as $kw) { - if(Str::contains($request->status, $kw) == true) { - abort(400, 'Invalid object. Contains banned keyword.'); - } - } - } - } - - if(!$request->filled('media_ids') && !$request->filled('in_reply_to_id')) { - abort(403, 'Empty statuses are not allowed'); - } - - $ids = $request->input('media_ids'); - $in_reply_to_id = $request->input('in_reply_to_id'); - - $user = $request->user(); - $profile = $user->profile; - - $limitKey = 'compose:rate-limit:store:' . $user->id; - $limitTtl = now()->addMinutes(15); - $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { - $dailyLimit = Status::whereProfileId($user->profile_id) - ->whereNull('in_reply_to_id') - ->whereNull('reblog_of_id') - ->where('created_at', '>', now()->subDays(1)) - ->count(); - - return $dailyLimit >= 1000; - }); - - abort_if($limitReached == true, 429); - - $visibility = $profile->is_private ? 'private' : ( - $profile->unlisted == true && - $request->input('visibility', 'public') == 'public' ? - 'unlisted' : - $request->input('visibility', 'public')); - - if($user->last_active_at == null) { - return []; - } - - $content = strip_tags($request->input('status')); - $rendered = Autolink::create()->autolink($content); - $cw = $user->profile->cw == true ? true : $request->input('sensitive', false); - $spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null; - - if($in_reply_to_id) { - $parent = Status::findOrFail($in_reply_to_id); - if($parent->comments_disabled) { - return $this->json("Comments have been disabled on this post", 422); - } - $blocks = UserFilterService::blocks($parent->profile_id); - abort_if(in_array($profile->id, $blocks), 422, 'Cannot reply to this post at this time.'); - - $status = new Status; - $status->caption = $content; - $status->rendered = $rendered; - $status->scope = $visibility; - $status->visibility = $visibility; - $status->profile_id = $user->profile_id; - $status->is_nsfw = $cw; - $status->cw_summary = $spoilerText; - $status->in_reply_to_id = $parent->id; - $status->in_reply_to_profile_id = $parent->profile_id; - $status->save(); - StatusService::del($parent->id); - Cache::forget('status:replies:all:' . $parent->id); - } - - if($ids) { - if(Media::whereUserId($user->id) - ->whereNull('status_id') - ->find($ids) - ->count() == 0 - ) { - abort(400, 'Invalid media_ids'); - } - - 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; - $status->scope = 'draft'; - $status->visibility = 'draft'; - if($request->has('place_id')) { - $status->place_id = $request->input('place_id'); - } - $status->save(); - } - - $mimes = []; - - foreach($ids as $k => $v) { - if($k + 1 > config_cache('pixelfed.max_album_length')) { - continue; - } - $m = Media::whereUserId($user->id)->whereNull('status_id')->findOrFail($v); - if($m->profile_id !== $user->profile_id || $m->status_id) { - abort(403, 'Invalid media id'); - } - $m->order = $k + 1; - $m->status_id = $status->id; - $m->save(); - array_push($mimes, $m->mime); - } - - if(empty($mimes)) { - $status->delete(); - abort(400, 'Invalid media ids'); - } - - if($request->has('comments_disabled') && $request->input('comments_disabled')) { - $status->comments_disabled = true; - } - - $status->scope = $visibility; - $status->visibility = $visibility; - $status->type = StatusController::mimeTypeCheck($mimes); - $status->save(); - } - - if(!$status) { - abort(500, 'An error occured.'); - } - - NewStatusPipeline::dispatch($status); - if($status->in_reply_to_id) { - CommentPipeline::dispatch($parent, $status); - } - Cache::forget('user:account:id:'.$user->id); - Cache::forget('_api:statuses:recent_9:'.$user->profile_id); - Cache::forget('profile:status_count:'.$user->profile_id); - Cache::forget($user->storageUsedKey()); - Cache::forget('profile:embed:' . $status->profile_id); - Cache::forget($limitKey); - - if($request->has('collection_ids') && $ids) { - $collections = Collection::whereProfileId($user->profile_id) - ->find($request->input('collection_ids')) - ->each(function($collection) use($status) { - $count = $collection->items()->count(); - $item = CollectionItem::firstOrCreate([ - 'collection_id' => $collection->id, - 'object_type' => 'App\Status', - 'object_id' => $status->id - ],[ - 'order' => $count, - ]); - - CollectionService::addItem( - $collection->id, - $status->id, - $count - ); + } + return $status; + }) + ->filter(function($s) use($filtered) { + return $s && isset($s['account']) && in_array($s['account']['id'], $filtered) == false; + }) + ->filter(function($s) use($domainBlocks) { + if(!$domainBlocks || !count($domainBlocks)) { + return $s; + } + $domain = strtolower(parse_url($s['url'], PHP_URL_HOST)); + return !in_array($domain, $domainBlocks); + }) + ->take($limit) + ->values(); + + $baseUrl = config('app.url') . '/api/v1/timelines/public?limit=' . $limit . '&'; + if($remote) { + $baseUrl .= 'remote=1&'; + } + $minId = $res->map(function($s) { + return ['id' => $s['id']]; + })->min('id'); + $maxId = $res->map(function($s) { + return ['id' => $s['id']]; + })->max('id'); + + if($minId == $maxId) { + $minId = null; + } + + if($maxId) { + $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"'; + } + + if($minId) { + $link = '<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; + } + + if($maxId && $minId) { + $link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next",<'.$baseUrl.'min_id='.$maxId.'>; rel="prev"'; + } + + $headers = isset($link) ? ['Link' => $link] : []; + return $this->json($res->toArray(), 200, $headers); + } + + /** + * GET /api/v1/conversations + * + * Not implemented + * + * @return array + */ + public function conversations(Request $request) + { + abort_if(!$request->user(), 403); + $this->validate($request, [ + 'limit' => 'min:1|max:40', + 'scope' => 'nullable|in:inbox,sent,requests' + ]); + + $limit = $request->input('limit', 20); + $scope = $request->input('scope', 'inbox'); + $pid = $request->user()->profile_id; + + if(config('database.default') == 'pgsql') { + $dms = DirectMessage::when($scope === 'inbox', function($q, $scope) use($pid) { + return $q->whereIsHidden(false)->where('to_id', $pid)->orWhere('from_id', $pid); + }) + ->when($scope === 'sent', function($q, $scope) use($pid) { + return $q->whereFromId($pid)->groupBy(['to_id', 'id']); + }) + ->when($scope === 'requests', function($q, $scope) use($pid) { + return $q->whereToId($pid)->whereIsHidden(true); + }); + } else { + $dms = Conversation::when($scope === 'inbox', function($q, $scope) use($pid) { + return $q->whereIsHidden(false) + ->where('to_id', $pid) + ->orWhere('from_id', $pid) + ->orderByDesc('status_id') + ->groupBy(['to_id', 'from_id']); + }) + ->when($scope === 'sent', function($q, $scope) use($pid) { + return $q->whereFromId($pid)->groupBy('to_id'); + }) + ->when($scope === 'requests', function($q, $scope) use($pid) { + return $q->whereToId($pid)->whereIsHidden(true); + }); + } + + $dms = $dms->orderByDesc('status_id') + ->simplePaginate($limit) + ->map(function($dm) use($pid) { + $from = $pid == $dm->to_id ? $dm->from_id : $dm->to_id; + $res = [ + 'id' => $dm->id, + 'unread' => false, + 'accounts' => [ + AccountService::getMastodon($from, true) + ], + 'last_status' => StatusService::getDirectMessage($dm->status_id) + ]; + return $res; + }) + ->filter(function($dm) { + if(!$dm || empty($dm['last_status']) || !isset($dm['accounts']) || !count($dm['accounts']) || !isset($dm['accounts'][0]) || !isset($dm['accounts'][0]['id'])) { + return false; + } + return true; + }) + ->unique(function($item, $key) { + return $item['accounts'][0]['id']; + }) + ->values(); + + return $this->json($dms); + } + + /** + * GET /api/v1/statuses/{id} + * + * @param integer $id + * + * @return StatusTransformer + */ + public function statusById(Request $request, $id) + { + abort_if(!$request->user(), 403); + AccountService::setLastActive($request->user()->id); + $pid = $request->user()->profile_id; + + $res = $request->has(self::PF_API_ENTITY_KEY) ? StatusService::get($id, false) : StatusService::getMastodon($id, false); + if(!$res || !isset($res['visibility'])) { + abort(404); + } + + $scope = $res['visibility']; + if(!in_array($scope, ['public', 'unlisted'])) { + if($scope === 'private') { + if(intval($res['account']['id']) !== intval($pid)) { + abort_unless(FollowerService::follows($pid, $res['account']['id']), 403); + } + } else { + abort(400, 'Invalid request'); + } + } + + if(!empty($res['reblog']) && isset($res['reblog']['id'])) { + $res['reblog']['favourited'] = (bool) LikeService::liked($pid, $res['reblog']['id']); + $res['reblog']['reblogged'] = (bool) ReblogService::get($pid, $res['reblog']['id']); + $res['reblog']['bookmarked'] = BookmarkService::get($pid, $res['reblog']['id']); + } + + $res['favourited'] = LikeService::liked($pid, $res['id']); + $res['reblogged'] = ReblogService::get($pid, $res['id']); + $res['bookmarked'] = BookmarkService::get($pid, $res['id']); + + return $this->json($res); + } + + /** + * GET /api/v1/statuses/{id}/context + * + * @param integer $id + * + * @return StatusTransformer + */ + public function statusContext(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $user = $request->user(); + AccountService::setLastActive($user->id); + $pid = $user->profile_id; + $status = StatusService::getMastodon($id, false); + + if(!$status || !isset($status['account'])) { + return response('', 404); + } + + if(intval($status['account']['id']) !== intval($user->profile_id)) { + if($status['visibility'] == 'private') { + if(!FollowerService::follows($user->profile_id, $status['account']['id'])) { + return response('', 404); + } + } else { + if(!in_array($status['visibility'], ['public','unlisted'])) { + return response('', 404); + } + } + } + + $ancestors = []; + $descendants = []; + + if($status['in_reply_to_id']) { + $ancestors[] = StatusService::getMastodon($status['in_reply_to_id'], false); + } + + if($status['replies_count']) { + $filters = UserFilterService::filters($pid); + + $descendants = DB::table('statuses') + ->where('in_reply_to_id', $id) + ->limit(20) + ->pluck('id') + ->map(function($sid) { + return StatusService::getMastodon($sid, false); + }) + ->filter(function($post) use($filters) { + return $post && isset($post['account'], $post['account']['id']) && !in_array($post['account']['id'], $filters); + }) + ->map(function($status) use($pid) { + $status['favourited'] = LikeService::liked($pid, $status['id']); + $status['reblogged'] = ReblogService::get($pid, $status['id']); + return $status; + }) + ->values(); + } + + $res = [ + 'ancestors' => $ancestors, + 'descendants' => $descendants + ]; + + return $this->json($res); + } + + /** + * GET /api/v1/statuses/{id}/card + * + * @param integer $id + * + * @return StatusTransformer + */ + public function statusCard(Request $request, $id) + { + abort_if(!$request->user(), 403); + $res = []; + return response()->json($res); + } + + /** + * GET /api/v1/statuses/{id}/reblogged_by + * + * @param integer $id + * + * @return AccountTransformer + */ + public function statusRebloggedBy(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'limit' => 'sometimes|integer|min:1|max:80' + ]); + + $limit = $request->input('limit', 10); + $user = $request->user(); + $pid = $user->profile_id; + $status = Status::findOrFail($id); + $account = AccountService::get($status->profile_id, true); + abort_if(!$account, 404); + $author = intval($status->profile_id) === intval($pid) || $user->is_admin; + $napi = $request->has(self::PF_API_ENTITY_KEY); + + abort_if( + !$status->type || + !in_array($status->type, ['photo','photo:album', 'photo:video:album', 'reply', 'text', 'video', 'video:album']), + 404, + ); + + if(!$author) { + if($status->scope == 'private') { + abort_if(!FollowerService::follows($pid, $status->profile_id), 403); + } else { + abort_if(!in_array($status->scope, ['public','unlisted']), 403); + } + + if($request->has('cursor')) { + return $this->json([]); + } + } + + $res = Status::where('reblog_of_id', $status->id) + ->orderByDesc('id') + ->cursorPaginate($limit) + ->withQueryString(); + + if(!$res) { + return $this->json([]); + } + + $headers = []; + if($author && $res->hasPages()) { + $links = ''; + if($res->onFirstPage()) { + if($res->nextPageUrl()) { + $links = '<' . $res->nextPageUrl() .'>; rel="prev"'; + } + } else { + if($res->previousPageUrl()) { + $links = '<' . $res->previousPageUrl() .'>; rel="next"'; + } + + if($res->nextPageUrl()) { + if(!empty($links)) { + $links .= ', '; + } + $links .= '<' . $res->nextPageUrl() .'>; rel="prev"'; + } + } + + $headers = ['Link' => $links]; + } + + $res = $res->map(function($status) use($pid, $napi) { + $account = $napi ? AccountService::get($status->profile_id, true) : AccountService::getMastodon($status->profile_id, true); + if(!$account) { + return false; + } + if($napi) { + $account['follows'] = $status->profile_id == $pid ? null : FollowerService::follows($pid, $status->profile_id); + } + return $account; + }) + ->filter(function($account) { + return $account && isset($account['id']); + }) + ->values(); + + return $this->json($res, 200, $headers); + } + + /** + * GET /api/v1/statuses/{id}/favourited_by + * + * @param integer $id + * + * @return AccountTransformer + */ + public function statusFavouritedBy(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'limit' => 'nullable|integer|min:1|max:80' + ]); + + $limit = $request->input('limit', 10); + $user = $request->user(); + $pid = $user->profile_id; + $status = Status::findOrFail($id); + $account = AccountService::get($status->profile_id, true); + abort_if(!$account, 404); + $author = intval($status->profile_id) === intval($pid) || $user->is_admin; + $napi = $request->has(self::PF_API_ENTITY_KEY); + + abort_if( + !$status->type || + !in_array($status->type, ['photo','photo:album', 'photo:video:album', 'reply', 'text', 'video', 'video:album']), + 404, + ); + + if(!$author) { + if($status->scope == 'private') { + abort_if(!FollowerService::follows($pid, $status->profile_id), 403); + } else { + abort_if(!in_array($status->scope, ['public','unlisted']), 403); + } + + if($request->has('cursor')) { + return $this->json([]); + } + } + + $res = Like::where('status_id', $status->id) + ->orderByDesc('id') + ->cursorPaginate($limit) + ->withQueryString(); + + if(!$res) { + return $this->json([]); + } + + $headers = []; + if($author && $res->hasPages()) { + $links = ''; + + if($res->onFirstPage()) { + if($res->nextPageUrl()) { + $links = '<' . $res->nextPageUrl() .'>; rel="prev"'; + } + } else { + if($res->previousPageUrl()) { + $links = '<' . $res->previousPageUrl() .'>; rel="next"'; + } + + if($res->nextPageUrl()) { + if(!empty($links)) { + $links .= ', '; + } + $links .= '<' . $res->nextPageUrl() .'>; rel="prev"'; + } + } + + $headers = ['Link' => $links]; + } + + $res = $res->map(function($like) use($pid, $napi) { + $account = $napi ? AccountService::get($like->profile_id, true) : AccountService::getMastodon($like->profile_id, true); + if(!$account) { + return false; + } + + if($napi) { + $account['follows'] = $like->profile_id == $pid ? null : FollowerService::follows($pid, $like->profile_id); + } + return $account; + }) + ->filter(function($account) { + return $account && isset($account['id']); + }) + ->values(); + + return $this->json($res, 200, $headers); + } + + /** + * POST /api/v1/statuses + * + * + * @return StatusTransformer + */ + public function statusCreate(Request $request) + { + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'status' => 'nullable|string', + 'in_reply_to_id' => 'nullable', + 'media_ids' => 'sometimes|array|max:' . config_cache('pixelfed.max_album_length'), + 'sensitive' => 'nullable', + 'visibility' => 'string|in:private,unlisted,public', + 'spoiler_text' => 'sometimes|max:140', + 'place_id' => 'sometimes|integer|min:1|max:128769', + 'collection_ids' => 'sometimes|array|max:3', + 'comments_disabled' => 'sometimes|boolean', + ]); + + if($request->hasHeader('idempotency-key')) { + $key = 'pf:api:v1:status:idempotency-key:' . $request->user()->id . ':' . hash('sha1', $request->header('idempotency-key')); + $exists = Cache::has($key); + abort_if($exists, 400, 'Duplicate idempotency key.'); + Cache::put($key, 1, 3600); + } + + if(config('costar.enabled') == true) { + $blockedKeywords = config('costar.keyword.block'); + if($blockedKeywords !== null && $request->status) { + $keywords = config('costar.keyword.block'); + foreach($keywords as $kw) { + if(Str::contains($request->status, $kw) == true) { + abort(400, 'Invalid object. Contains banned keyword.'); + } + } + } + } + + if(!$request->filled('media_ids') && !$request->filled('in_reply_to_id')) { + abort(403, 'Empty statuses are not allowed'); + } + + $ids = $request->input('media_ids'); + $in_reply_to_id = $request->input('in_reply_to_id'); + + $user = $request->user(); + $profile = $user->profile; + + $limitKey = 'compose:rate-limit:store:' . $user->id; + $limitTtl = now()->addMinutes(15); + $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { + $dailyLimit = Status::whereProfileId($user->profile_id) + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->where('created_at', '>', now()->subDays(1)) + ->count(); + + return $dailyLimit >= 1000; + }); + + abort_if($limitReached == true, 429); + + $visibility = $profile->is_private ? 'private' : ( + $profile->unlisted == true && + $request->input('visibility', 'public') == 'public' ? + 'unlisted' : + $request->input('visibility', 'public')); + + if($user->last_active_at == null) { + return []; + } + + $content = strip_tags($request->input('status')); + $rendered = Autolink::create()->autolink($content); + $cw = $user->profile->cw == true ? true : $request->input('sensitive', false); + $spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null; + + if($in_reply_to_id) { + $parent = Status::findOrFail($in_reply_to_id); + if($parent->comments_disabled) { + return $this->json("Comments have been disabled on this post", 422); + } + $blocks = UserFilterService::blocks($parent->profile_id); + abort_if(in_array($profile->id, $blocks), 422, 'Cannot reply to this post at this time.'); + + $status = new Status; + $status->caption = $content; + $status->rendered = $rendered; + $status->scope = $visibility; + $status->visibility = $visibility; + $status->profile_id = $user->profile_id; + $status->is_nsfw = $cw; + $status->cw_summary = $spoilerText; + $status->in_reply_to_id = $parent->id; + $status->in_reply_to_profile_id = $parent->profile_id; + $status->save(); + StatusService::del($parent->id); + Cache::forget('status:replies:all:' . $parent->id); + } + + if($ids) { + if(Media::whereUserId($user->id) + ->whereNull('status_id') + ->find($ids) + ->count() == 0 + ) { + abort(400, 'Invalid media_ids'); + } + + 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; + $status->scope = 'draft'; + $status->visibility = 'draft'; + if($request->has('place_id')) { + $status->place_id = $request->input('place_id'); + } + $status->save(); + } + + $mimes = []; + + foreach($ids as $k => $v) { + if($k + 1 > config_cache('pixelfed.max_album_length')) { + continue; + } + $m = Media::whereUserId($user->id)->whereNull('status_id')->findOrFail($v); + if($m->profile_id !== $user->profile_id || $m->status_id) { + abort(403, 'Invalid media id'); + } + $m->order = $k + 1; + $m->status_id = $status->id; + $m->save(); + array_push($mimes, $m->mime); + } + + if(empty($mimes)) { + $status->delete(); + abort(400, 'Invalid media ids'); + } + + if($request->has('comments_disabled') && $request->input('comments_disabled')) { + $status->comments_disabled = true; + } + + $status->scope = $visibility; + $status->visibility = $visibility; + $status->type = StatusController::mimeTypeCheck($mimes); + $status->save(); + } + + if(!$status) { + abort(500, 'An error occured.'); + } + + NewStatusPipeline::dispatch($status); + if($status->in_reply_to_id) { + CommentPipeline::dispatch($parent, $status); + } + Cache::forget('user:account:id:'.$user->id); + Cache::forget('_api:statuses:recent_9:'.$user->profile_id); + Cache::forget('profile:status_count:'.$user->profile_id); + Cache::forget($user->storageUsedKey()); + Cache::forget('profile:embed:' . $status->profile_id); + Cache::forget($limitKey); + + if($request->has('collection_ids') && $ids) { + $collections = Collection::whereProfileId($user->profile_id) + ->find($request->input('collection_ids')) + ->each(function($collection) use($status) { + $count = $collection->items()->count(); + $item = CollectionItem::firstOrCreate([ + 'collection_id' => $collection->id, + 'object_type' => 'App\Status', + 'object_id' => $status->id + ],[ + 'order' => $count, + ]); + + CollectionService::addItem( + $collection->id, + $status->id, + $count + ); $collection->updated_at = now(); $collection->save(); CollectionService::setCollection($collection->id, $collection); - }); - } + }); + } - $res = StatusService::getMastodon($status->id, false); - $res['favourited'] = false; - $res['language'] = 'en'; - $res['bookmarked'] = false; - $res['card'] = null; - return $this->json($res); - } + $res = StatusService::getMastodon($status->id, false); + $res['favourited'] = false; + $res['language'] = 'en'; + $res['bookmarked'] = false; + $res['card'] = null; + return $this->json($res); + } - /** - * DELETE /api/v1/statuses - * - * @param integer $id - * - * @return null - */ - public function statusDelete(Request $request, $id) - { - abort_if(!$request->user(), 403); + /** + * DELETE /api/v1/statuses + * + * @param integer $id + * + * @return null + */ + public function statusDelete(Request $request, $id) + { + abort_if(!$request->user(), 403); + AccountService::setLastActive($request->user()->id); + $status = Status::whereProfileId($request->user()->profile->id) + ->findOrFail($id); - $status = Status::whereProfileId($request->user()->profile->id) - ->findOrFail($id); + $resource = new Fractal\Resource\Item($status, new StatusTransformer()); - $resource = new Fractal\Resource\Item($status, new StatusTransformer()); + Cache::forget('profile:status_count:'.$status->profile_id); + StatusDelete::dispatch($status); - Cache::forget('profile:status_count:'.$status->profile_id); - StatusDelete::dispatch($status); + $res = $this->fractal->createData($resource)->toArray(); + $res['text'] = $res['content']; + unset($res['content']); - $res = $this->fractal->createData($resource)->toArray(); - $res['text'] = $res['content']; - unset($res['content']); + return $this->json($res); + } - return $this->json($res); - } + /** + * POST /api/v1/statuses/{id}/reblog + * + * @param integer $id + * + * @return StatusTransformer + */ + public function statusShare(Request $request, $id) + { + abort_if(!$request->user(), 403); - /** - * POST /api/v1/statuses/{id}/reblog - * - * @param integer $id - * - * @return StatusTransformer - */ - public function statusShare(Request $request, $id) - { - abort_if(!$request->user(), 403); + $user = $request->user(); + AccountService::setLastActive($user->id); + $status = Status::whereScope('public')->findOrFail($id); - $user = $request->user(); - $status = Status::whereScope('public')->findOrFail($id); + if(intval($status->profile_id) !== intval($user->profile_id)) { + if($status->scope == 'private') { + abort_if(!FollowerService::follows($user->profile_id, $status->profile_id), 403); + } else { + abort_if(!in_array($status->scope, ['public','unlisted']), 403); + } - if(intval($status->profile_id) !== intval($user->profile_id)) { - if($status->scope == 'private') { - abort_if(!FollowerService::follows($user->profile_id, $status->profile_id), 403); - } else { - abort_if(!in_array($status->scope, ['public','unlisted']), 403); - } + $blocks = UserFilterService::blocks($status->profile_id); + if($blocks && in_array($user->profile_id, $blocks)) { + abort(422); + } + } - $blocks = UserFilterService::blocks($status->profile_id); - if($blocks && in_array($user->profile_id, $blocks)) { - abort(422); - } - } + $share = Status::firstOrCreate([ + 'profile_id' => $user->profile_id, + 'reblog_of_id' => $status->id, + 'type' => 'share', + 'in_reply_to_profile_id' => $status->profile_id, + 'scope' => 'public', + 'visibility' => 'public' + ]); - $share = Status::firstOrCreate([ - 'profile_id' => $user->profile_id, - 'reblog_of_id' => $status->id, - 'type' => 'share', - 'in_reply_to_profile_id' => $status->profile_id, - 'scope' => 'public', - 'visibility' => 'public' - ]); + SharePipeline::dispatch($share)->onQueue('low'); - SharePipeline::dispatch($share)->onQueue('low'); + StatusService::del($status->id); + ReblogService::add($user->profile_id, $status->id); + $res = StatusService::getMastodon($status->id); + $res['reblogged'] = true; - StatusService::del($status->id); - ReblogService::add($user->profile_id, $status->id); - $res = StatusService::getMastodon($status->id); - $res['reblogged'] = true; + return $this->json($res); + } - return $this->json($res); - } + /** + * POST /api/v1/statuses/{id}/unreblog + * + * @param integer $id + * + * @return StatusTransformer + */ + public function statusUnshare(Request $request, $id) + { + abort_if(!$request->user(), 403); - /** - * POST /api/v1/statuses/{id}/unreblog - * - * @param integer $id - * - * @return StatusTransformer - */ - public function statusUnshare(Request $request, $id) - { - abort_if(!$request->user(), 403); + $user = $request->user(); + AccountService::setLastActive($user->id); + $status = Status::whereScope('public')->findOrFail($id); - $user = $request->user(); - $status = Status::whereScope('public')->findOrFail($id); + if(intval($status->profile_id) !== intval($user->profile_id)) { + if($status->scope == 'private') { + abort_if(!FollowerService::follows($user->profile_id, $status->profile_id), 403); + } else { + abort_if(!in_array($status->scope, ['public','unlisted']), 403); + } + } - if(intval($status->profile_id) !== intval($user->profile_id)) { - if($status->scope == 'private') { - abort_if(!FollowerService::follows($user->profile_id, $status->profile_id), 403); - } else { - abort_if(!in_array($status->scope, ['public','unlisted']), 403); - } - } + $reblog = Status::whereProfileId($user->profile_id) + ->whereReblogOfId($status->id) + ->first(); - $reblog = Status::whereProfileId($user->profile_id) - ->whereReblogOfId($status->id) - ->first(); + if(!$reblog) { + $res = StatusService::getMastodon($status->id); + $res['reblogged'] = false; + return $this->json($res); + } - if(!$reblog) { - $res = StatusService::getMastodon($status->id); - $res['reblogged'] = false; - return $this->json($res); - } + UndoSharePipeline::dispatch($reblog)->onQueue('low'); + ReblogService::del($user->profile_id, $status->id); - UndoSharePipeline::dispatch($reblog)->onQueue('low'); - ReblogService::del($user->profile_id, $status->id); + $res = StatusService::getMastodon($status->id); + $res['reblogged'] = false; - $res = StatusService::getMastodon($status->id); - $res['reblogged'] = false; + return $this->json($res); + } - return $this->json($res); - } + /** + * GET /api/v1/timelines/tag/{hashtag} + * + * @param string $hashtag + * + * @return StatusTransformer + */ + public function timelineHashtag(Request $request, $hashtag) + { + abort_if(!$request->user(), 403); - /** - * GET /api/v1/timelines/tag/{hashtag} - * - * @param string $hashtag - * - * @return StatusTransformer - */ - public function timelineHashtag(Request $request, $hashtag) - { - abort_if(!$request->user(), 403); + $this->validate($request,[ + 'page' => 'nullable|integer|max:40', + 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, + 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, + 'limit' => 'nullable|integer|max:100', + 'only_media' => 'sometimes|boolean', + '_pe' => 'sometimes' + ]); - $this->validate($request,[ - 'page' => 'nullable|integer|max:40', - 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'limit' => 'nullable|integer|max:100', - 'only_media' => 'sometimes|boolean', - '_pe' => 'sometimes' - ]); + if(config('database.default') === 'pgsql') { + $tag = Hashtag::where('name', 'ilike', $hashtag) + ->orWhere('slug', 'ilike', $hashtag) + ->first(); + } else { + $tag = Hashtag::whereName($hashtag) + ->orWhere('slug', $hashtag) + ->first(); + } - if(config('database.default') === 'pgsql') { - $tag = Hashtag::where('name', 'ilike', $hashtag) - ->orWhere('slug', 'ilike', $hashtag) - ->first(); - } else { - $tag = Hashtag::whereName($hashtag) - ->orWhere('slug', $hashtag) - ->first(); - } + if(!$tag) { + return response()->json([]); + } - if(!$tag) { - return response()->json([]); - } + if($tag->is_banned == true) { + return $this->json([]); + } - if($tag->is_banned == true) { - return $this->json([]); - } + $min = $request->input('min_id'); + $max = $request->input('max_id'); + $limit = $request->input('limit', 20); + $onlyMedia = $request->input('only_media', true); + $pe = $request->has(self::PF_API_ENTITY_KEY); + $pid = $request->user()->profile_id; - $min = $request->input('min_id'); - $max = $request->input('max_id'); - $limit = $request->input('limit', 20); - $onlyMedia = $request->input('only_media', true); - $pe = $request->has(self::PF_API_ENTITY_KEY); + if($min || $max) { + $minMax = SnowflakeService::byDate(now()->subMonths(6)); + if($min && intval($min) < $minMax) { + return []; + } + if($max && intval($max) < $minMax) { + return []; + } + } - if($min || $max) { - $minMax = SnowflakeService::byDate(now()->subMonths(6)); - if($min && intval($min) < $minMax) { - return []; - } - if($max && intval($max) < $minMax) { - return []; - } - } + $filters = UserFilterService::filters($pid); + $domainBlocks = UserFilterService::domainBlocks($pid); - $filters = UserFilterService::filters($request->user()->profile_id); + if(!$min && !$max) { + $id = 1; + $dir = '>'; + } else { + $dir = $min ? '>' : '<'; + $id = $min ?? $max; + } - if(!$min && !$max) { - $id = 1; - $dir = '>'; - } else { - $dir = $min ? '>' : '<'; - $id = $min ?? $max; - } + $res = StatusHashtag::whereHashtagId($tag->id) + ->whereStatusVisibility('public') + ->where('status_id', $dir, $id) + ->orderBy('status_id', 'desc') + ->limit($limit) + ->pluck('status_id') + ->map(function ($i) use($pe) { + return $pe ? StatusService::get($i) : StatusService::getMastodon($i); + }) + ->filter(function($i) use($onlyMedia) { + if(!$i) { + return false; + } + if($onlyMedia && !isset($i['media_attachments']) || !count($i['media_attachments'])) { + return false; + } + return $i && isset($i['account'], $i['url']); + }) + ->filter(function($i) use($filters, $domainBlocks) { + $domain = strtolower(parse_url($i['url'], PHP_URL_HOST)); + return !in_array($i['account']['id'], $filters) && !in_array($domain, $domainBlocks); + }) + ->values() + ->toArray(); - $res = StatusHashtag::whereHashtagId($tag->id) - ->whereStatusVisibility('public') - ->where('status_id', $dir, $id) - ->orderBy('status_id', 'desc') - ->limit($limit) - ->pluck('status_id') - ->map(function ($i) use($pe) { - return $pe ? StatusService::get($i) : StatusService::getMastodon($i); - }) - ->filter(function($i) use($onlyMedia) { - if(!$i) { - return false; - } - if($onlyMedia && !isset($i['media_attachments']) || !count($i['media_attachments'])) { - return false; - } - return $i && isset($i['account']); - }) - ->filter(function($i) use($filters) { - return !in_array($i['account']['id'], $filters); - }) - ->values() - ->toArray(); + return $this->json($res); + } - return $this->json($res); - } + /** + * GET /api/v1/bookmarks + * + * + * + * @return StatusTransformer + */ + public function bookmarks(Request $request) + { + abort_if(!$request->user(), 403); - /** - * GET /api/v1/bookmarks - * - * - * - * @return StatusTransformer - */ - public function bookmarks(Request $request) - { - abort_if(!$request->user(), 403); + $this->validate($request, [ + 'limit' => 'nullable|integer|min:1|max:40', + 'max_id' => 'nullable|integer|min:0', + 'since_id' => 'nullable|integer|min:0', + 'min_id' => 'nullable|integer|min:0' + ]); - $this->validate($request, [ - 'limit' => 'nullable|integer|min:1|max:40', - 'max_id' => 'nullable|integer|min:0', - 'since_id' => 'nullable|integer|min:0', - 'min_id' => 'nullable|integer|min:0' - ]); + $pe = $request->has('_pe'); + $pid = $request->user()->profile_id; + $limit = $request->input('limit') ?? 20; + $max_id = $request->input('max_id'); + $since_id = $request->input('since_id'); + $min_id = $request->input('min_id'); - $pe = $request->has('_pe'); - $pid = $request->user()->profile_id; - $limit = $request->input('limit') ?? 20; - $max_id = $request->input('max_id'); - $since_id = $request->input('since_id'); - $min_id = $request->input('min_id'); + $dir = $min_id ? '>' : '<'; + $id = $min_id ?? $max_id; - $dir = $min_id ? '>' : '<'; - $id = $min_id ?? $max_id; - - $bookmarkQuery = Bookmark::whereProfileId($pid) + $bookmarkQuery = Bookmark::whereProfileId($pid) ->orderByDesc('id') ->cursorPaginate($limit); $bookmarks = $bookmarkQuery->map(function($bookmark) use($pid, $pe) { - $status = $pe ? StatusService::get($bookmark->status_id, false) : StatusService::getMastodon($bookmark->status_id, false); + $status = $pe ? StatusService::get($bookmark->status_id, false) : StatusService::getMastodon($bookmark->status_id, false); - if($status) { - $status['bookmarked'] = true; - $status['favourited'] = LikeService::liked($pid, $status['id']); - $status['reblogged'] = ReblogService::get($pid, $status['id']); - } - return $status; - }) - ->filter() - ->values() - ->toArray(); + if($status) { + $status['bookmarked'] = true; + $status['favourited'] = LikeService::liked($pid, $status['id']); + $status['reblogged'] = ReblogService::get($pid, $status['id']); + } + return $status; + }) + ->filter() + ->values() + ->toArray(); $links = null; $headers = []; @@ -3272,529 +3400,358 @@ class ApiV1Controller extends Controller $headers = ['Link' => $links]; } - return $this->json($bookmarks, 200, $headers); - } + return $this->json($bookmarks, 200, $headers); + } - /** - * POST /api/v1/statuses/{id}/bookmark - * - * - * - * @return StatusTransformer - */ - public function bookmarkStatus(Request $request, $id) - { - abort_if(!$request->user(), 403); + /** + * POST /api/v1/statuses/{id}/bookmark + * + * + * + * @return StatusTransformer + */ + public function bookmarkStatus(Request $request, $id) + { + abort_if(!$request->user(), 403); - $status = Status::findOrFail($id); - $pid = $request->user()->profile_id; + $status = Status::findOrFail($id); + $pid = $request->user()->profile_id; - abort_if($status->in_reply_to_id || $status->reblog_of_id, 404); - abort_if(!in_array($status->scope, ['public', 'unlisted', 'private']), 404); - abort_if(!in_array($status->type, ['photo','photo:album', 'video', 'video:album', 'photo:video:album']), 404); + abort_if($status->in_reply_to_id || $status->reblog_of_id, 404); + abort_if(!in_array($status->scope, ['public', 'unlisted', 'private']), 404); + abort_if(!in_array($status->type, ['photo','photo:album', 'video', 'video:album', 'photo:video:album']), 404); - if($status->scope == 'private') { - abort_if( - $pid !== $status->profile_id && !FollowerService::follows($pid, $status->profile_id), - 404, - 'Error: You cannot bookmark private posts from accounts you do not follow.' - ); - } + if($status->scope == 'private') { + abort_if( + $pid !== $status->profile_id && !FollowerService::follows($pid, $status->profile_id), + 404, + 'Error: You cannot bookmark private posts from accounts you do not follow.' + ); + } - Bookmark::firstOrCreate([ - 'status_id' => $status->id, - 'profile_id' => $pid - ]); + Bookmark::firstOrCreate([ + 'status_id' => $status->id, + 'profile_id' => $pid + ]); - BookmarkService::add($pid, $status->id); + BookmarkService::add($pid, $status->id); - $res = StatusService::getMastodon($status->id, false); - $res['bookmarked'] = true; + $res = StatusService::getMastodon($status->id, false); + $res['bookmarked'] = true; - return $this->json($res); - } + return $this->json($res); + } - /** - * POST /api/v1/statuses/{id}/unbookmark - * - * - * - * @return StatusTransformer - */ - public function unbookmarkStatus(Request $request, $id) - { - abort_if(!$request->user(), 403); + /** + * POST /api/v1/statuses/{id}/unbookmark + * + * + * + * @return StatusTransformer + */ + public function unbookmarkStatus(Request $request, $id) + { + abort_if(!$request->user(), 403); - $status = Status::findOrFail($id); - $pid = $request->user()->profile_id; + $status = Status::findOrFail($id); + $pid = $request->user()->profile_id; - abort_if($status->in_reply_to_id || $status->reblog_of_id, 404); - abort_if(!in_array($status->scope, ['public', 'unlisted', 'private']), 404); - abort_if(!in_array($status->type, ['photo','photo:album', 'video', 'video:album', 'photo:video:album']), 404); + abort_if($status->in_reply_to_id || $status->reblog_of_id, 404); + abort_if(!in_array($status->scope, ['public', 'unlisted', 'private']), 404); + abort_if(!in_array($status->type, ['photo','photo:album', 'video', 'video:album', 'photo:video:album']), 404); - $bookmark = Bookmark::whereStatusId($status->id) - ->whereProfileId($pid) - ->first(); + $bookmark = Bookmark::whereStatusId($status->id) + ->whereProfileId($pid) + ->first(); - if($bookmark) { - BookmarkService::del($pid, $status->id); - $bookmark->delete(); - } - $res = StatusService::getMastodon($status->id, false); - $res['bookmarked'] = false; + if($bookmark) { + BookmarkService::del($pid, $status->id); + $bookmark->delete(); + } + $res = StatusService::getMastodon($status->id, false); + $res['bookmarked'] = false; - return $this->json($res); - } + return $this->json($res); + } - /** - * GET /api/v1/discover/posts - * - * - * @return array - */ - public function discoverPosts(Request $request) - { - abort_if(!$request->user(), 403); + /** + * GET /api/v1/discover/posts + * + * + * @return array + */ + public function discoverPosts(Request $request) + { + abort_if(!$request->user(), 403); - $this->validate($request, [ - 'limit' => 'integer|min:1|max:40' - ]); + $this->validate($request, [ + 'limit' => 'integer|min:1|max:40' + ]); - $limit = $request->input('limit', 40); - $pid = $request->user()->profile_id; - $filters = UserFilterService::filters($pid); - $forYou = DiscoverService::getForYou(); - $posts = $forYou->take(50)->map(function($post) { - return StatusService::getMastodon($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 $this->json(compact('posts')); - } - - /** - * GET /api/v2/statuses/{id}/replies - * - * - * @return array - */ - public function statusReplies(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $this->validate($request, [ - 'limit' => 'int|min:1|max:10', - 'sort' => 'in:all,newest,popular' - ]); - - $limit = $request->input('limit', 3); - $pid = $request->user()->profile_id; - $status = StatusService::getMastodon($id, false); - - abort_if(!$status, 404); - - if($status['visibility'] == 'private') { - if($pid != $status['account']['id']) { - abort_unless(FollowerService::follows($pid, $status['account']['id']), 404); - } - } - - $sortBy = $request->input('sort', 'all'); - - if($sortBy == 'all' && isset($status['replies_count']) && $status['replies_count'] && $request->has('refresh_cache')) { - if(!Cache::has('status:replies:all-rc:' . $id)) { - Cache::forget('status:replies:all:' . $id); - Cache::put('status:replies:all-rc:' . $id, true, 300); - } - } - - if($sortBy == 'all' && !$request->has('cursor')) { - $ids = Cache::remember('status:replies:all:' . $id, 3600, function() use($id) { - return DB::table('statuses') - ->where('in_reply_to_id', $id) - ->orderBy('id') - ->cursorPaginate(3); - }); - } else { - $ids = DB::table('statuses') - ->where('in_reply_to_id', $id) - ->when($sortBy, function($q, $sortBy) { - if($sortBy === 'all') { - return $q->orderBy('id'); - } - - if($sortBy === 'newest') { - return $q->orderByDesc('created_at'); - } - - if($sortBy === 'popular') { - return $q->orderByDesc('likes_count'); - } - }) - ->cursorPaginate($limit); - } - - $filters = UserFilterService::filters($pid); - $data = $ids->filter(function($post) use($filters) { - return !in_array($post->profile_id, $filters); - }) - ->map(function($post) use($pid) { - $status = StatusService::get($post->id, false); - - if(!$status || !isset($status['id'])) { - return false; - } - - $status['favourited'] = LikeService::liked($pid, $post->id); - return $status; - }) - ->map(function($post) { - if(isset($post['account']) && isset($post['account']['id'])) { - $account = AccountService::get($post['account']['id'], true); - $post['account'] = $account; - } - return $post; - }) - ->filter(function($post) { - return $post && isset($post['id']) && isset($post['account']) && isset($post['account']['id']); - }) - ->values(); - - $res = [ - 'data' => $data, - 'next' => $ids->nextPageUrl() - ]; - - return $this->json($res); - } - - /** - * GET /api/v2/statuses/{id}/state - * - * - * @return array - */ - public function statusState(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $status = Status::findOrFail($id); - $pid = $request->user()->profile_id; - abort_if(!in_array($status->scope, ['public', 'unlisted', 'private']), 404); - - return $this->json(StatusService::getState($status->id, $pid)); - } - - /** - * GET /api/v1.1/discover/accounts/popular - * - * - * @return array - */ - public function discoverAccountsPopular(Request $request) - { - abort_if(!$request->user(), 403); - - $pid = $request->user()->profile_id; - - $ids = Cache::remember('api:v1.1:discover:accounts:popular', 86400, function() { - return DB::table('profiles') - ->where('is_private', false) - ->whereNull('status') - ->orderByDesc('profiles.followers_count') - ->limit(20) - ->get(); - }); - - $ids = $ids->map(function($profile) { - return AccountService::get($profile->id, true); - }) - ->filter(function($profile) use($pid) { - return $profile && isset($profile['id']); - }) - ->filter(function($profile) use($pid) { - return $profile['id'] != $pid; - }) - ->map(function($profile) { - $ids = collect(ProfileStatusService::get($profile['id'], 0, 9)) - ->map(function($id) { - return StatusService::get($id, true); - }) - ->filter(function($post) { - return $post && isset($post['id']); - }) - ->take(3) - ->values(); - $profile['recent_posts'] = $ids; - return $profile; + $limit = $request->input('limit', 40); + $pid = $request->user()->profile_id; + $filters = UserFilterService::filters($pid); + $forYou = DiscoverService::getForYou(); + $posts = $forYou->take(50)->map(function($post) { + return StatusService::getMastodon($post); }) - ->take(6) - ->values(); + ->filter(function($post) use($filters) { + return $post && + isset($post['account']) && + isset($post['account']['id']) && + !in_array($post['account']['id'], $filters); + }) + ->take(12) + ->values(); + return $this->json(compact('posts')); + } - return $this->json($ids); - } + /** + * GET /api/v2/statuses/{id}/replies + * + * + * @return array + */ + public function statusReplies(Request $request, $id) + { + abort_if(!$request->user(), 403); - /** - * GET /api/v1/preferences - * - * - * @return array - */ - public function getPreferences(Request $request) - { - abort_if(!$request->user(), 403); + $this->validate($request, [ + 'limit' => 'int|min:1|max:10', + 'sort' => 'in:all,newest,popular' + ]); - $pid = $request->user()->profile_id; - $account = AccountService::get($pid); + $limit = $request->input('limit', 3); + $pid = $request->user()->profile_id; + $status = StatusService::getMastodon($id, false); - return $this->json([ - 'posting:default:visibility' => $account['locked'] ? 'private' : 'public', - 'posting:default:sensitive' => false, - 'posting:default:language' => null, - 'reading:expand:media' => 'default', - 'reading:expand:spoilers' => false - ]); - } + abort_if(!$status, 404); - /** - * GET /api/v1/trends - * - * - * @return array - */ - public function getTrends(Request $request) - { - abort_if(!$request->user(), 403); + if($status['visibility'] == 'private') { + if($pid != $status['account']['id']) { + abort_unless(FollowerService::follows($pid, $status['account']['id']), 404); + } + } - return $this->json([]); - } + $sortBy = $request->input('sort', 'all'); - /** - * GET /api/v1/announcements - * - * - * @return array - */ - public function getAnnouncements(Request $request) - { - abort_if(!$request->user(), 403); + if($sortBy == 'all' && isset($status['replies_count']) && $status['replies_count'] && $request->has('refresh_cache')) { + if(!Cache::has('status:replies:all-rc:' . $id)) { + Cache::forget('status:replies:all:' . $id); + Cache::put('status:replies:all-rc:' . $id, true, 300); + } + } - return $this->json([]); - } + if($sortBy == 'all' && !$request->has('cursor')) { + $ids = Cache::remember('status:replies:all:' . $id, 3600, function() use($id) { + return DB::table('statuses') + ->where('in_reply_to_id', $id) + ->orderBy('id') + ->cursorPaginate(3); + }); + } else { + $ids = DB::table('statuses') + ->where('in_reply_to_id', $id) + ->when($sortBy, function($q, $sortBy) { + if($sortBy === 'all') { + return $q->orderBy('id'); + } - /** - * GET /api/v1/markers - * - * - * @return array - */ - public function getMarkers(Request $request) - { - abort_if(!$request->user(), 403); + if($sortBy === 'newest') { + return $q->orderByDesc('created_at'); + } - $type = $request->input('timeline'); - if(is_array($type)) { - $type = $type[0]; - } - if(!$type || !in_array($type, ['home', 'notifications'])) { - return $this->json([]); - } - $pid = $request->user()->profile_id; - return $this->json(MarkerService::get($pid, $type)); - } + if($sortBy === 'popular') { + return $q->orderByDesc('likes_count'); + } + }) + ->cursorPaginate($limit); + } - /** - * POST /api/v1/markers - * - * - * @return array - */ - public function setMarkers(Request $request) - { - abort_if(!$request->user(), 403); + $filters = UserFilterService::filters($pid); + $data = $ids->filter(function($post) use($filters) { + return !in_array($post->profile_id, $filters); + }) + ->map(function($post) use($pid) { + $status = StatusService::get($post->id, false); - $pid = $request->user()->profile_id; - $home = $request->input('home[last_read_id]'); - $notifications = $request->input('notifications[last_read_id]'); + if(!$status || !isset($status['id'])) { + return false; + } - if($home) { - return $this->json(MarkerService::set($pid, 'home', $home)); - } + $status['favourited'] = LikeService::liked($pid, $post->id); + return $status; + }) + ->map(function($post) { + if(isset($post['account']) && isset($post['account']['id'])) { + $account = AccountService::get($post['account']['id'], true); + $post['account'] = $account; + } + return $post; + }) + ->filter(function($post) { + return $post && isset($post['id']) && isset($post['account']) && isset($post['account']['id']); + }) + ->values(); - if($notifications) { - return $this->json(MarkerService::set($pid, 'notifications', $notifications)); - } + $res = [ + 'data' => $data, + 'next' => $ids->nextPageUrl() + ]; - return $this->json([]); - } + return $this->json($res); + } - /** - * GET /api/v1/followed_tags - * - * - * @return array - */ - public function getFollowedTags(Request $request) - { - abort_if(!$request->user(), 403); + /** + * GET /api/v2/statuses/{id}/state + * + * + * @return array + */ + public function statusState(Request $request, $id) + { + abort_if(!$request->user(), 403); - $account = AccountService::get($request->user()->profile_id); + $status = Status::findOrFail($id); + $pid = $request->user()->profile_id; + abort_if(!in_array($status->scope, ['public', 'unlisted', 'private']), 404); - $this->validate($request, [ - 'cursor' => 'sometimes', - 'limit' => 'sometimes|integer|min:1|max:200' - ]); - $limit = $request->input('limit', 100); + return $this->json(StatusService::getState($status->id, $pid)); + } - $res = HashtagFollow::whereProfileId($account['id']) - ->orderByDesc('id') - ->cursorPaginate($limit)->withQueryString(); + /** + * GET /api/v1.1/discover/accounts/popular + * + * + * @return array + */ + public function discoverAccountsPopular(Request $request) + { + abort_if(!$request->user(), 403); - $pagination = false; - $prevPage = $res->nextPageUrl(); - $nextPage = $res->previousPageUrl(); - if($nextPage && $prevPage) { - $pagination = '<' . $nextPage . '>; rel="next", <' . $prevPage . '>; rel="prev"'; - } else if($nextPage && !$prevPage) { - $pagination = '<' . $nextPage . '>; rel="next"'; - } else if(!$nextPage && $prevPage) { - $pagination = '<' . $prevPage . '>; rel="prev"'; - } + $pid = $request->user()->profile_id; - if($pagination) { - return response()->json(FollowedTagResource::collection($res)->collection) - ->header('Link', $pagination); - } - return response()->json(FollowedTagResource::collection($res)->collection); - } + $ids = Cache::remember('api:v1.1:discover:accounts:popular', 3600, function() { + return DB::table('profiles') + ->where('is_private', false) + ->whereNull('status') + ->orderByDesc('profiles.followers_count') + ->limit(30) + ->get(); + }); + $filters = UserFilterService::filters($pid); + $ids = $ids->map(function($profile) { + return AccountService::get($profile->id, true); + }) + ->filter(function($profile) use($pid) { + return $profile && isset($profile['id'], $profile['locked']) && !$profile['locked']; + }) + ->filter(function($profile) use($pid) { + return $profile['id'] != $pid; + }) + ->filter(function($profile) use($pid) { + return !FollowerService::follows($pid, $profile['id'], true); + }) + ->filter(function($profile) use($filters) { + return !in_array($profile['id'], $filters); + }) + ->take(16) + ->values(); - /** - * POST /api/v1/tags/:id/follow - * - * - * @return object - */ - public function followHashtag(Request $request, $id) - { - abort_if(!$request->user(), 403); + return $this->json($ids); + } - $pid = $request->user()->profile_id; - $account = AccountService::get($pid); + /** + * GET /api/v1/preferences + * + * + * @return array + */ + public function getPreferences(Request $request) + { + abort_if(!$request->user(), 403); - $operator = config('database.default') == 'pgsql' ? 'ilike' : 'like'; - $tag = Hashtag::where('name', $operator, $id) - ->orWhere('slug', $operator, $id) - ->first(); + $pid = $request->user()->profile_id; + $account = AccountService::get($pid); - abort_if(!$tag, 422, 'Unknown hashtag'); + return $this->json([ + 'posting:default:visibility' => $account['locked'] ? 'private' : 'public', + 'posting:default:sensitive' => false, + 'posting:default:language' => null, + 'reading:expand:media' => 'default', + 'reading:expand:spoilers' => false + ]); + } - abort_if( - HashtagFollow::whereProfileId($pid)->count() >= HashtagFollow::MAX_LIMIT, - 422, - 'You cannot follow more than ' . HashtagFollow::MAX_LIMIT . ' hashtags.' - ); + /** + * GET /api/v1/trends + * + * + * @return array + */ + public function getTrends(Request $request) + { + abort_if(!$request->user(), 403); - $follows = HashtagFollow::updateOrCreate( - [ - 'profile_id' => $account['id'], - 'hashtag_id' => $tag->id - ], - [ - 'user_id' => $request->user()->id - ] - ); + return $this->json([]); + } - HashtagService::follow($pid, $tag->id); + /** + * GET /api/v1/announcements + * + * + * @return array + */ + public function getAnnouncements(Request $request) + { + abort_if(!$request->user(), 403); - return response()->json(FollowedTagResource::make($follows)->toArray($request)); - } + return $this->json([]); + } - /** - * POST /api/v1/tags/:id/unfollow - * - * - * @return object - */ - public function unfollowHashtag(Request $request, $id) - { - abort_if(!$request->user(), 403); + /** + * GET /api/v1/markers + * + * + * @return array + */ + public function getMarkers(Request $request) + { + abort_if(!$request->user(), 403); - $pid = $request->user()->profile_id; - $account = AccountService::get($pid); + $type = $request->input('timeline'); + if(is_array($type)) { + $type = $type[0]; + } + if(!$type || !in_array($type, ['home', 'notifications'])) { + return $this->json([]); + } + $pid = $request->user()->profile_id; + return $this->json(MarkerService::get($pid, $type)); + } - $operator = config('database.default') == 'pgsql' ? 'ilike' : 'like'; - $tag = Hashtag::where('name', $operator, $id) - ->orWhere('slug', $operator, $id) - ->first(); + /** + * POST /api/v1/markers + * + * + * @return array + */ + public function setMarkers(Request $request) + { + abort_if(!$request->user(), 403); - abort_if(!$tag, 422, 'Unknown hashtag'); + $pid = $request->user()->profile_id; + $home = $request->input('home[last_read_id]'); + $notifications = $request->input('notifications[last_read_id]'); - $follows = HashtagFollow::whereProfileId($pid) - ->whereHashtagId($tag->id) - ->first(); + if($home) { + return $this->json(MarkerService::set($pid, 'home', $home)); + } - if(!$follows) { - return [ - 'name' => $tag->name, - 'url' => config('app.url') . '/i/web/hashtag/' . $tag->slug, - 'history' => [], - 'following' => false - ]; - } + if($notifications) { + return $this->json(MarkerService::set($pid, 'notifications', $notifications)); + } - if($follows) { - HashtagService::unfollow($pid, $tag->id); - $follows->delete(); - } - - $res = FollowedTagResource::make($follows)->toArray($request); - $res['following'] = false; - return response()->json($res); - } - - /** - * GET /api/v1/tags/:id - * - * - * @return object - */ - public function getHashtag(Request $request, $id) - { - abort_if(!$request->user(), 403); - - $pid = $request->user()->profile_id; - $account = AccountService::get($pid); - $operator = config('database.default') == 'pgsql' ? 'ilike' : 'like'; - $tag = Hashtag::where('name', $operator, $id) - ->orWhere('slug', $operator, $id) - ->first(); - - if(!$tag) { - return [ - 'name' => $id, - 'url' => config('app.url') . '/i/web/hashtag/' . $id, - 'history' => [], - 'following' => false - ]; - } - - $res = [ - 'name' => $tag->name, - 'url' => config('app.url') . '/i/web/hashtag/' . $tag->slug, - 'history' => [], - 'following' => HashtagService::isFollowing($pid, $tag->id) - ]; - - if($request->has(self::PF_API_ENTITY_KEY)) { - $res['count'] = HashtagService::count($tag->id); - } - - return $this->json($res); - } + return $this->json([]); + } } diff --git a/app/Http/Controllers/Api/ApiV1Dot1Controller.php b/app/Http/Controllers/Api/ApiV1Dot1Controller.php index 6ed047af9..75d0fe984 100644 --- a/app/Http/Controllers/Api/ApiV1Dot1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Dot1Controller.php @@ -11,6 +11,7 @@ use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use App\AccountLog; use App\EmailVerification; +use App\Follower; use App\Place; use App\Status; use App\Report; @@ -19,8 +20,11 @@ use App\StatusArchived; use App\User; use App\UserSetting; use App\Services\AccountService; +use App\Services\FollowerService; use App\Services\StatusService; use App\Services\ProfileStatusService; +use App\Services\LikeService; +use App\Services\ReblogService; use App\Services\PublicTimelineService; use App\Services\NetworkTimelineService; use App\Util\Lexer\RestrictedNames; @@ -470,7 +474,7 @@ class ApiV1Dot1Controller extends Controller abort_if(BouncerService::checkIp($request->ip()), 404); } - $rl = RateLimiter::attempt('pf:apiv1.1:iar:'.$request->ip(), 3, function(){}, 1800); + $rl = RateLimiter::attempt('pf:apiv1.1:iar:'.$request->ip(), config('pixelfed.app_registration_rate_limit_attempts', 3), function(){}, config('pixelfed.app_registration_rate_limit_decay', 1800)); abort_if(!$rl, 400, 'Too many requests'); $this->validate($request, [ @@ -543,10 +547,10 @@ class ApiV1Dot1Controller extends Controller $user->password = Hash::make($password); $user->register_source = 'app'; $user->app_register_ip = $request->ip(); - $user->app_register_token = Str::random(32); + $user->app_register_token = Str::random(40); $user->save(); - $rtoken = Str::random(mt_rand(64, 70)); + $rtoken = Str::random(64); $verify = new EmailVerification(); $verify->user_id = $user->id; @@ -555,7 +559,12 @@ class ApiV1Dot1Controller extends Controller $verify->random_token = $rtoken; $verify->save(); - $appUrl = url('/api/v1.1/auth/iarer?ut=' . $user->app_register_token . '&rt=' . $rtoken); + $params = http_build_query([ + 'ut' => $user->app_register_token, + 'rt' => $rtoken, + 'ea' => base64_encode($user->email) + ]); + $appUrl = url('/api/v1.1/auth/iarer?'. $params); Mail::to($user->email)->send(new ConfirmAppEmail($verify, $appUrl)); @@ -568,14 +577,19 @@ class ApiV1Dot1Controller extends Controller { $this->validate($request, [ 'ut' => 'required', - 'rt' => 'required' + 'rt' => 'required', + 'ea' => 'required' ]); - if(config('pixelfed.bouncer.cloud_ips.ban_signups')) { - abort_if(BouncerService::checkIp($request->ip()), 404); - } $ut = $request->input('ut'); $rt = $request->input('rt'); - $url = 'pixelfed://confirm-account/'. $ut . '?rt=' . $rt; + $ea = $request->input('ea'); + $params = http_build_query([ + 'ut' => $ut, + 'rt' => $rt, + 'domain' => config('pixelfed.domain.app'), + 'ea' => $ea + ]); + $url = 'pixelfed://confirm-account/'. $ut . '?' . $params; return redirect()->away($url); } @@ -589,8 +603,8 @@ class ApiV1Dot1Controller extends Controller abort_if(BouncerService::checkIp($request->ip()), 404); } - $rl = RateLimiter::attempt('pf:apiv1.1:iarc:'.$request->ip(), 10, function(){}, 1800); - abort_if(!$rl, 400, 'Too many requests'); + $rl = RateLimiter::attempt('pf:apiv1.1:iarc:'.$request->ip(), config('pixelfed.app_registration_confirm_rate_limit_attempts', 20), function(){}, config('pixelfed.app_registration_confirm_rate_limit_decay', 1800)); + abort_if(!$rl, 429, 'Too many requests'); $this->validate($request, [ 'user_token' => 'required', @@ -884,4 +898,19 @@ class ApiV1Dot1Controller extends Controller return [200]; } + + public function getMutualAccounts(Request $request, $id) + { + abort_if(!$request->user(), 403); + $account = AccountService::get($id, true); + if(!$account || !isset($account['id'])) { return []; } + $res = collect(FollowerService::mutualAccounts($request->user()->profile_id, $id)) + ->map(function($accountId) { + return AccountService::get($accountId, true); + }) + ->filter() + ->take(24) + ->values(); + return $this->json($res); + } } diff --git a/app/Http/Controllers/Api/ApiV2Controller.php b/app/Http/Controllers/Api/ApiV2Controller.php index 757e14dce..2ca5b96c5 100644 --- a/app/Http/Controllers/Api/ApiV2Controller.php +++ b/app/Http/Controllers/Api/ApiV2Controller.php @@ -34,6 +34,7 @@ use App\Transformer\Api\Mastodon\v1\{ use App\Transformer\Api\{ RelationshipTransformer, }; +use App\Util\Site\Nodeinfo; class ApiV2Controller extends Controller { @@ -77,12 +78,7 @@ class ApiV2Controller extends Controller 'description' => config_cache('app.short_description'), 'usage' => [ 'users' => [ - 'active_month' => (int) Cache::remember('api:nodeinfo:am', 172800, function() { - return User::select('last_active_at', 'created_at') - ->where('last_active_at', '>', now()->subMonths(1)) - ->orWhere('created_at', '>', now()->subMonths(1)) - ->count(); - }) + 'active_month' => (int) Nodeinfo::activeUsersMonthly() ] ], 'thumbnail' => [ diff --git a/app/Http/Controllers/Api/V1/DomainBlockController.php b/app/Http/Controllers/Api/V1/DomainBlockController.php new file mode 100644 index 000000000..2186c0936 --- /dev/null +++ b/app/Http/Controllers/Api/V1/DomainBlockController.php @@ -0,0 +1,118 @@ +json($res, $code, $headers, JSON_UNESCAPED_SLASHES); + } + + public function index(Request $request) + { + abort_unless($request->user(), 403); + $this->validate($request, [ + 'limit' => 'sometimes|integer|min:1|max:200' + ]); + $limit = $request->input('limit', 100); + $id = $request->user()->profile_id; + $filters = UserDomainBlock::whereProfileId($id)->orderByDesc('id')->cursorPaginate($limit); + $links = null; + $headers = []; + + if($filters->nextCursor()) { + $links .= '<'.$filters->nextPageUrl().'&limit='.$limit.'>; rel="next"'; + } + + if($filters->previousCursor()) { + if($links != null) { + $links .= ', '; + } + $links .= '<'.$filters->previousPageUrl().'&limit='.$limit.'>; rel="prev"'; + } + + if($links) { + $headers = ['Link' => $links]; + } + return $this->json($filters->pluck('domain'), 200, $headers); + } + + public function store(Request $request) + { + abort_unless($request->user(), 403); + + $this->validate($request, [ + 'domain' => 'required|active_url|min:1|max:120' + ]); + + $pid = $request->user()->profile_id; + + $domain = trim($request->input('domain')); + + if(Helpers::validateUrl($domain) == false) { + return abort(500, 'Invalid domain or already blocked by server admins'); + } + + $domain = strtolower(parse_url($domain, PHP_URL_HOST)); + + abort_if(config_cache('pixelfed.domain.app') == $domain, 400, 'Cannot ban your own server'); + + $existingCount = UserDomainBlock::whereProfileId($pid)->count(); + $maxLimit = config('instance.user_filters.max_domain_blocks'); + $errorMsg = __('profile.block.domain.max', ['max' => $maxLimit]); + + abort_if($existingCount >= $maxLimit, 400, $errorMsg); + + $block = UserDomainBlock::updateOrCreate([ + 'profile_id' => $pid, + 'domain' => $domain + ]); + + if($block->wasRecentlyCreated) { + Bus::batch([ + [ + new FeedRemoveDomainPipeline($pid, $domain), + new ProfilePurgeNotificationsByDomain($pid, $domain), + new ProfilePurgeFollowersByDomain($pid, $domain) + ] + ])->allowFailures()->onQueue('feed')->dispatch(); + + Cache::forget('profile:following:' . $pid); + UserFilterService::domainBlocks($pid, true); + } + + return $this->json([]); + } + + public function delete(Request $request) + { + abort_unless($request->user(), 403); + + $this->validate($request, [ + 'domain' => 'required|min:1|max:120' + ]); + + $pid = $request->user()->profile_id; + + $domain = strtolower(trim($request->input('domain'))); + + $filters = UserDomainBlock::whereProfileId($pid)->whereDomain($domain)->delete(); + + UserFilterService::domainBlocks($pid, true); + + return $this->json([]); + } +} diff --git a/app/Http/Controllers/Api/V1/TagsController.php b/app/Http/Controllers/Api/V1/TagsController.php new file mode 100644 index 000000000..7314ce09d --- /dev/null +++ b/app/Http/Controllers/Api/V1/TagsController.php @@ -0,0 +1,207 @@ +json($res, $code, $headers, JSON_UNESCAPED_SLASHES); + } + + /** + * GET /api/v1/tags/:id/related + * + * + * @return array + */ + public function relatedTags(Request $request, $tag) + { + abort_unless($request->user(), 403); + $tag = Hashtag::whereSlug($tag)->firstOrFail(); + return HashtagRelatedService::get($tag->id); + } + + /** + * POST /api/v1/tags/:id/follow + * + * + * @return object + */ + public function followHashtag(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $pid = $request->user()->profile_id; + $account = AccountService::get($pid); + + $operator = config('database.default') == 'pgsql' ? 'ilike' : 'like'; + $tag = Hashtag::where('name', $operator, $id) + ->orWhere('slug', $operator, $id) + ->first(); + + abort_if(!$tag, 422, 'Unknown hashtag'); + + abort_if( + HashtagFollow::whereProfileId($pid)->count() >= HashtagFollow::MAX_LIMIT, + 422, + 'You cannot follow more than ' . HashtagFollow::MAX_LIMIT . ' hashtags.' + ); + + $follows = HashtagFollow::updateOrCreate( + [ + 'profile_id' => $account['id'], + 'hashtag_id' => $tag->id + ], + [ + 'user_id' => $request->user()->id + ] + ); + + HashtagService::follow($pid, $tag->id); + HashtagFollowService::add($tag->id, $pid); + + return response()->json(FollowedTagResource::make($follows)->toArray($request)); + } + + /** + * POST /api/v1/tags/:id/unfollow + * + * + * @return object + */ + public function unfollowHashtag(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $pid = $request->user()->profile_id; + $account = AccountService::get($pid); + + $operator = config('database.default') == 'pgsql' ? 'ilike' : 'like'; + $tag = Hashtag::where('name', $operator, $id) + ->orWhere('slug', $operator, $id) + ->first(); + + abort_if(!$tag, 422, 'Unknown hashtag'); + + $follows = HashtagFollow::whereProfileId($pid) + ->whereHashtagId($tag->id) + ->first(); + + if(!$follows) { + return [ + 'name' => $tag->name, + 'url' => config('app.url') . '/i/web/hashtag/' . $tag->slug, + 'history' => [], + 'following' => false + ]; + } + + if($follows) { + HashtagService::unfollow($pid, $tag->id); + HashtagFollowService::unfollow($tag->id, $pid); + HashtagUnfollowPipeline::dispatch($tag->id, $pid, $tag->slug)->onQueue('feed'); + $follows->delete(); + } + + $res = FollowedTagResource::make($follows)->toArray($request); + $res['following'] = false; + return response()->json($res); + } + + /** + * GET /api/v1/tags/:id + * + * + * @return object + */ + public function getHashtag(Request $request, $id) + { + abort_if(!$request->user(), 403); + + $pid = $request->user()->profile_id; + $account = AccountService::get($pid); + $operator = config('database.default') == 'pgsql' ? 'ilike' : 'like'; + $tag = Hashtag::where('name', $operator, $id) + ->orWhere('slug', $operator, $id) + ->first(); + + if(!$tag) { + return [ + 'name' => $id, + 'url' => config('app.url') . '/i/web/hashtag/' . $id, + 'history' => [], + 'following' => false + ]; + } + + $res = [ + 'name' => $tag->name, + 'url' => config('app.url') . '/i/web/hashtag/' . $tag->slug, + 'history' => [], + 'following' => HashtagService::isFollowing($pid, $tag->id) + ]; + + if($request->has(self::PF_API_ENTITY_KEY)) { + $res['count'] = HashtagService::count($tag->id); + } + + return $this->json($res); + } + + /** + * GET /api/v1/followed_tags + * + * + * @return array + */ + public function getFollowedTags(Request $request) + { + abort_if(!$request->user(), 403); + + $account = AccountService::get($request->user()->profile_id); + + $this->validate($request, [ + 'cursor' => 'sometimes', + 'limit' => 'sometimes|integer|min:1|max:200' + ]); + $limit = $request->input('limit', 100); + + $res = HashtagFollow::whereProfileId($account['id']) + ->orderByDesc('id') + ->cursorPaginate($limit) + ->withQueryString(); + + $pagination = false; + $prevPage = $res->nextPageUrl(); + $nextPage = $res->previousPageUrl(); + if($nextPage && $prevPage) { + $pagination = '<' . $nextPage . '>; rel="next", <' . $prevPage . '>; rel="prev"'; + } else if($nextPage && !$prevPage) { + $pagination = '<' . $nextPage . '>; rel="next"'; + } else if(!$nextPage && $prevPage) { + $pagination = '<' . $prevPage . '>; rel="prev"'; + } + + if($pagination) { + return response()->json(FollowedTagResource::collection($res)->collection) + ->header('Link', $pagination); + } + return response()->json(FollowedTagResource::collection($res)->collection); + } +} diff --git a/app/Http/Controllers/CollectionController.php b/app/Http/Controllers/CollectionController.php index 6cd4bda57..85f3f30cf 100644 --- a/app/Http/Controllers/CollectionController.php +++ b/app/Http/Controllers/CollectionController.php @@ -153,7 +153,7 @@ class CollectionController extends Controller abort(400, 'You can only add '.$max.' posts per collection'); } - $status = Status::whereScope('public') + $status = Status::whereIn('scope', ['public', 'unlisted']) ->whereProfileId($profileId) ->whereIn('type', ['photo', 'photo:album', 'video']) ->findOrFail($postId); @@ -166,17 +166,13 @@ class CollectionController extends Controller 'order' => $count, ]); - CollectionService::addItem( - $collection->id, - $status->id, - $count - ); + CollectionService::deleteCollection($collection->id); $collection->updated_at = now(); $collection->save(); CollectionService::setCollection($collection->id, $collection); - return StatusService::get($status->id); + return StatusService::get($status->id, false); } public function getCollection(Request $request, $id) @@ -226,10 +222,10 @@ class CollectionController extends Controller return collect($items) ->map(function($id) { - return StatusService::get($id); + return StatusService::get($id, false); }) ->filter(function($item) { - return $item && isset($item['account'], $item['media_attachments']); + return $item && ($item['visibility'] == 'public' || $item['visibility'] == 'unlisted') && isset($item['account'], $item['media_attachments']); }) ->values(); } @@ -298,7 +294,7 @@ class CollectionController extends Controller abort(400, 'You cannot delete the only post of a collection!'); } - $status = Status::whereScope('public') + $status = Status::whereIn('scope', ['public', 'unlisted']) ->whereIn('type', ['photo', 'photo:album', 'video']) ->findOrFail($postId); diff --git a/app/Http/Controllers/ComposeController.php b/app/Http/Controllers/ComposeController.php index 54526ffe8..9be50f346 100644 --- a/app/Http/Controllers/ComposeController.php +++ b/app/Http/Controllers/ComposeController.php @@ -415,7 +415,7 @@ class ComposeController extends Controller $results = Profile::select('id','domain','username') ->whereNotIn('id', $blocked) ->where('username','like','%'.$q.'%') - ->groupBy('domain') + ->groupBy('id', 'domain') ->limit(15) ->get() ->map(function($profile) { diff --git a/app/Http/Controllers/DirectMessageController.php b/app/Http/Controllers/DirectMessageController.php index 1f7e04e59..df76d2ab9 100644 --- a/app/Http/Controllers/DirectMessageController.php +++ b/app/Http/Controllers/DirectMessageController.php @@ -17,12 +17,15 @@ use App\{ use App\Services\MediaPathService; use App\Services\MediaBlocklistService; use App\Jobs\StatusPipeline\NewStatusPipeline; +use App\Jobs\StatusPipeline\StatusDelete; use Illuminate\Support\Str; use App\Util\ActivityPub\Helpers; use App\Services\AccountService; use App\Services\StatusService; use App\Services\WebfingerService; use App\Models\Conversation; +use App\Jobs\DirectPipeline\DirectDeletePipeline; +use App\Jobs\DirectPipeline\DirectDeliverPipeline; class DirectMessageController extends Controller { @@ -500,6 +503,8 @@ class DirectMessageController extends Controller if($recipient['local'] == false) { $dmc = $dm; $this->remoteDelete($dmc); + } else { + StatusDelete::dispatch($status)->onQueue('high'); } if(Conversation::whereStatusId($sid)->count()) { @@ -541,9 +546,7 @@ class DirectMessageController extends Controller StatusService::del($status->id, true); - $status->delete(); - $dm->delete(); - + $status->forceDeleteQuietly(); return [200]; } @@ -829,7 +832,7 @@ class DirectMessageController extends Controller ] ]; - Helpers::sendSignedObject($profile, $url, $body); + DirectDeliverPipeline::dispatch($profile, $url, $body)->onQueue('high'); } public function remoteDelete($dm) @@ -852,7 +855,6 @@ class DirectMessageController extends Controller 'type' => 'Tombstone' ] ]; - - Helpers::sendSignedObject($profile, $url, $body); + DirectDeletePipeline::dispatch($profile, $url, $body)->onQueue('high'); } } diff --git a/app/Http/Controllers/FederationController.php b/app/Http/Controllers/FederationController.php index c4b5e86bf..6faea7050 100644 --- a/app/Http/Controllers/FederationController.php +++ b/app/Http/Controllers/FederationController.php @@ -3,17 +3,17 @@ namespace App\Http\Controllers; use App\Jobs\InboxPipeline\{ - DeleteWorker, - InboxWorker, - InboxValidator + DeleteWorker, + InboxWorker, + InboxValidator }; use App\Jobs\RemoteFollowPipeline\RemoteFollowPipeline; use App\{ - AccountLog, - Like, - Profile, - Status, - User + AccountLog, + Like, + Profile, + Status, + User }; use App\Util\Lexer\Nickname; use App\Util\Webfinger\Webfinger; @@ -24,243 +24,251 @@ use Illuminate\Http\Request; use League\Fractal; use App\Util\Site\Nodeinfo; use App\Util\ActivityPub\{ - Helpers, - HttpSignature, - Outbox + Helpers, + HttpSignature, + Outbox }; use Zttp\Zttp; use App\Services\InstanceService; +use App\Services\AccountService; class FederationController extends Controller { - public function nodeinfoWellKnown() - { - abort_if(!config('federation.nodeinfo.enabled'), 404); - return response()->json(Nodeinfo::wellKnown(), 200, [], JSON_UNESCAPED_SLASHES) - ->header('Access-Control-Allow-Origin','*'); - } + public function nodeinfoWellKnown() + { + abort_if(!config('federation.nodeinfo.enabled'), 404); + return response()->json(Nodeinfo::wellKnown(), 200, [], JSON_UNESCAPED_SLASHES) + ->header('Access-Control-Allow-Origin','*'); + } - public function nodeinfo() - { - abort_if(!config('federation.nodeinfo.enabled'), 404); - return response()->json(Nodeinfo::get(), 200, [], JSON_UNESCAPED_SLASHES) - ->header('Access-Control-Allow-Origin','*'); - } + public function nodeinfo() + { + abort_if(!config('federation.nodeinfo.enabled'), 404); + return response()->json(Nodeinfo::get(), 200, [], JSON_UNESCAPED_SLASHES) + ->header('Access-Control-Allow-Origin','*'); + } - public function webfinger(Request $request) - { - if (!config('federation.webfinger.enabled') || - !$request->has('resource') || - !$request->filled('resource') - ) { - return response('', 400); - } + public function webfinger(Request $request) + { + if (!config('federation.webfinger.enabled') || + !$request->has('resource') || + !$request->filled('resource') + ) { + return response('', 400); + } - $resource = $request->input('resource'); - $domain = config('pixelfed.domain.app'); + $resource = $request->input('resource'); + $domain = config('pixelfed.domain.app'); - if(config('federation.activitypub.sharedInbox') && - $resource == 'acct:' . $domain . '@' . $domain) { - $res = [ - 'subject' => 'acct:' . $domain . '@' . $domain, - 'aliases' => [ - 'https://' . $domain . '/i/actor' - ], - 'links' => [ - [ - 'rel' => 'http://webfinger.net/rel/profile-page', - 'type' => 'text/html', - 'href' => 'https://' . $domain . '/site/kb/instance-actor' - ], - [ - 'rel' => 'self', - 'type' => 'application/activity+json', - 'href' => 'https://' . $domain . '/i/actor' - ] - ] - ]; - return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES); - } - $hash = hash('sha256', $resource); - $key = 'federation:webfinger:sha256:' . $hash; - if($cached = Cache::get($key)) { - return response()->json($cached, 200, [], JSON_UNESCAPED_SLASHES); - } - if(strpos($resource, $domain) == false) { - return response('', 400); - } - $parsed = Nickname::normalizeProfileUrl($resource); - if(empty($parsed) || $parsed['domain'] !== $domain) { - return response('', 400); - } - $username = $parsed['username']; - $profile = Profile::whereNull('domain')->whereUsername($username)->first(); - if(!$profile || $profile->status !== null) { - return response('', 400); - } - $webfinger = (new Webfinger($profile))->generate(); - Cache::put($key, $webfinger, 1209600); + if(config('federation.activitypub.sharedInbox') && + $resource == 'acct:' . $domain . '@' . $domain) { + $res = [ + 'subject' => 'acct:' . $domain . '@' . $domain, + 'aliases' => [ + 'https://' . $domain . '/i/actor' + ], + 'links' => [ + [ + 'rel' => 'http://webfinger.net/rel/profile-page', + 'type' => 'text/html', + 'href' => 'https://' . $domain . '/site/kb/instance-actor' + ], + [ + 'rel' => 'self', + 'type' => 'application/activity+json', + 'href' => 'https://' . $domain . '/i/actor' + ] + ] + ]; + return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES); + } + $hash = hash('sha256', $resource); + $key = 'federation:webfinger:sha256:' . $hash; + if($cached = Cache::get($key)) { + return response()->json($cached, 200, [], JSON_UNESCAPED_SLASHES); + } + if(strpos($resource, $domain) == false) { + return response('', 400); + } + $parsed = Nickname::normalizeProfileUrl($resource); + if(empty($parsed) || $parsed['domain'] !== $domain) { + return response('', 400); + } + $username = $parsed['username']; + $profile = Profile::whereNull('domain')->whereUsername($username)->first(); + if(!$profile || $profile->status !== null) { + return response('', 400); + } + $webfinger = (new Webfinger($profile))->generate(); + Cache::put($key, $webfinger, 1209600); - return response()->json($webfinger, 200, [], JSON_UNESCAPED_SLASHES) - ->header('Access-Control-Allow-Origin','*'); - } + return response()->json($webfinger, 200, [], JSON_UNESCAPED_SLASHES) + ->header('Access-Control-Allow-Origin','*'); + } - public function hostMeta(Request $request) - { - abort_if(!config('federation.webfinger.enabled'), 404); + public function hostMeta(Request $request) + { + abort_if(!config('federation.webfinger.enabled'), 404); - $path = route('well-known.webfinger'); - $xml = ''; + $path = route('well-known.webfinger'); + $xml = ''; - return response($xml)->header('Content-Type', 'application/xrd+xml'); - } + return response($xml)->header('Content-Type', 'application/xrd+xml'); + } - public function userOutbox(Request $request, $username) - { - abort_if(!config_cache('federation.activitypub.enabled'), 404); + public function userOutbox(Request $request, $username) + { + abort_if(!config_cache('federation.activitypub.enabled'), 404); - if(!$request->wantsJson()) { - return redirect('/' . $username); - } + if(!$request->wantsJson()) { + return redirect('/' . $username); + } - $res = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => 'https://' . config('pixelfed.domain.app') . '/users/' . $username . '/outbox', - 'type' => 'OrderedCollection', - 'totalItems' => 0, - 'orderedItems' => [] - ]; + $id = AccountService::usernameToId($username); + abort_if(!$id, 404); + $account = AccountService::get($id); + abort_if(!$account || !isset($account['statuses_count']), 404); + $res = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => 'https://' . config('pixelfed.domain.app') . '/users/' . $username . '/outbox', + 'type' => 'OrderedCollection', + 'totalItems' => $account['statuses_count'] ?? 0, + ]; - return response(json_encode($res, JSON_UNESCAPED_SLASHES))->header('Content-Type', 'application/activity+json'); - } + return response(json_encode($res, JSON_UNESCAPED_SLASHES))->header('Content-Type', 'application/activity+json'); + } - public function userInbox(Request $request, $username) - { - abort_if(!config_cache('federation.activitypub.enabled'), 404); - abort_if(!config('federation.activitypub.inbox'), 404); + public function userInbox(Request $request, $username) + { + abort_if(!config_cache('federation.activitypub.enabled'), 404); + abort_if(!config('federation.activitypub.inbox'), 404); - $headers = $request->headers->all(); - $payload = $request->getContent(); - if(!$payload || empty($payload)) { - return; - } - $obj = json_decode($payload, true, 8); - if(!isset($obj['id'])) { - return; - } - $domain = parse_url($obj['id'], PHP_URL_HOST); - if(in_array($domain, InstanceService::getBannedDomains())) { - return; - } + $headers = $request->headers->all(); + $payload = $request->getContent(); + if(!$payload || empty($payload)) { + return; + } + $obj = json_decode($payload, true, 8); + if(!isset($obj['id'])) { + return; + } + $domain = parse_url($obj['id'], PHP_URL_HOST); + if(in_array($domain, InstanceService::getBannedDomains())) { + return; + } - if(isset($obj['type']) && $obj['type'] === 'Delete') { - if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) { - if($obj['object']['type'] === 'Person') { - if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) { - dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox'); - return; - } - } + if(isset($obj['type']) && $obj['type'] === 'Delete') { + if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) { + if($obj['object']['type'] === 'Person') { + if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) { + dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox'); + return; + } + } - if($obj['object']['type'] === 'Tombstone') { - if(Status::whereObjectUrl($obj['object']['id'])->exists()) { - dispatch(new DeleteWorker($headers, $payload))->onQueue('delete'); - return; - } - } + if($obj['object']['type'] === 'Tombstone') { + if(Status::whereObjectUrl($obj['object']['id'])->exists()) { + dispatch(new DeleteWorker($headers, $payload))->onQueue('delete'); + return; + } + } - if($obj['object']['type'] === 'Story') { - dispatch(new DeleteWorker($headers, $payload))->onQueue('story'); - return; - } - } - return; - } else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) { - dispatch(new InboxValidator($username, $headers, $payload))->onQueue('follow'); - } else { - dispatch(new InboxValidator($username, $headers, $payload))->onQueue('high'); - } - return; - } + if($obj['object']['type'] === 'Story') { + dispatch(new DeleteWorker($headers, $payload))->onQueue('story'); + return; + } + } + return; + } else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) { + dispatch(new InboxValidator($username, $headers, $payload))->onQueue('follow'); + } else { + dispatch(new InboxValidator($username, $headers, $payload))->onQueue('high'); + } + return; + } - public function sharedInbox(Request $request) - { - abort_if(!config_cache('federation.activitypub.enabled'), 404); - abort_if(!config('federation.activitypub.sharedInbox'), 404); + public function sharedInbox(Request $request) + { + abort_if(!config_cache('federation.activitypub.enabled'), 404); + abort_if(!config('federation.activitypub.sharedInbox'), 404); - $headers = $request->headers->all(); - $payload = $request->getContent(); + $headers = $request->headers->all(); + $payload = $request->getContent(); - if(!$payload || empty($payload)) { - return; - } + if(!$payload || empty($payload)) { + return; + } - $obj = json_decode($payload, true, 8); - if(!isset($obj['id'])) { - return; - } + $obj = json_decode($payload, true, 8); + if(!isset($obj['id'])) { + return; + } - $domain = parse_url($obj['id'], PHP_URL_HOST); - if(in_array($domain, InstanceService::getBannedDomains())) { - return; - } + $domain = parse_url($obj['id'], PHP_URL_HOST); + if(in_array($domain, InstanceService::getBannedDomains())) { + return; + } - if(isset($obj['type']) && $obj['type'] === 'Delete') { - if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) { - if($obj['object']['type'] === 'Person') { - if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) { - dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox'); - return; - } - } + if(isset($obj['type']) && $obj['type'] === 'Delete') { + if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) { + if($obj['object']['type'] === 'Person') { + if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) { + dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox'); + return; + } + } - if($obj['object']['type'] === 'Tombstone') { - if(Status::whereObjectUrl($obj['object']['id'])->exists()) { - dispatch(new DeleteWorker($headers, $payload))->onQueue('delete'); - return; - } - } + if($obj['object']['type'] === 'Tombstone') { + if(Status::whereObjectUrl($obj['object']['id'])->exists()) { + dispatch(new DeleteWorker($headers, $payload))->onQueue('delete'); + return; + } + } - if($obj['object']['type'] === 'Story') { - dispatch(new DeleteWorker($headers, $payload))->onQueue('story'); - return; - } - } - return; - } else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) { - dispatch(new InboxWorker($headers, $payload))->onQueue('follow'); - } else { - dispatch(new InboxWorker($headers, $payload))->onQueue('shared'); - } - return; - } + if($obj['object']['type'] === 'Story') { + dispatch(new DeleteWorker($headers, $payload))->onQueue('story'); + return; + } + } + return; + } else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) { + dispatch(new InboxWorker($headers, $payload))->onQueue('follow'); + } else { + dispatch(new InboxWorker($headers, $payload))->onQueue('shared'); + } + return; + } - public function userFollowing(Request $request, $username) - { - abort_if(!config_cache('federation.activitypub.enabled'), 404); + public function userFollowing(Request $request, $username) + { + abort_if(!config_cache('federation.activitypub.enabled'), 404); - $obj = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $request->getUri(), - 'type' => 'OrderedCollectionPage', - 'totalItems' => 0, - 'orderedItems' => [] - ]; - return response()->json($obj); - } + $id = AccountService::usernameToId($username); + abort_if(!$id, 404); + $account = AccountService::get($id); + abort_if(!$account || !isset($account['following_count']), 404); + $obj = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $request->getUri(), + 'type' => 'OrderedCollection', + 'totalItems' => $account['following_count'] ?? 0, + ]; + return response()->json($obj); + } - public function userFollowers(Request $request, $username) - { - abort_if(!config_cache('federation.activitypub.enabled'), 404); - - $obj = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $request->getUri(), - 'type' => 'OrderedCollectionPage', - 'totalItems' => 0, - 'orderedItems' => [] - ]; - - return response()->json($obj); - } + public function userFollowers(Request $request, $username) + { + abort_if(!config_cache('federation.activitypub.enabled'), 404); + $id = AccountService::usernameToId($username); + abort_if(!$id, 404); + $account = AccountService::get($id); + abort_if(!$account || !isset($account['followers_count']), 404); + $obj = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $request->getUri(), + 'type' => 'OrderedCollection', + 'totalItems' => $account['followers_count'] ?? 0, + ]; + return response()->json($obj); + } } diff --git a/app/Http/Controllers/ImportPostController.php b/app/Http/Controllers/ImportPostController.php index e814c2b3a..55f575a6e 100644 --- a/app/Http/Controllers/ImportPostController.php +++ b/app/Http/Controllers/ImportPostController.php @@ -83,6 +83,17 @@ class ImportPostController extends Controller ); } + public function formatHashtags($val = false) + { + if(!$val || !strlen($val)) { + return null; + } + + $groupedHashtagRegex = '/#\w+(?=#)/'; + + return preg_replace($groupedHashtagRegex, '$0 ', $val); + } + public function store(Request $request) { abort_unless(config('import.instagram.enabled'), 404); @@ -128,11 +139,11 @@ class ImportPostController extends Controller $ip->media = $c->map(function($m) { return [ 'uri' => $m['uri'], - 'title' => $m['title'], + 'title' => $this->formatHashtags($m['title']), 'creation_timestamp' => $m['creation_timestamp'] ]; })->toArray(); - $ip->caption = $c->count() > 1 ? $file['title'] : $ip->media[0]['title']; + $ip->caption = $c->count() > 1 ? $this->formatHashtags($file['title']) : $this->formatHashtags($ip->media[0]['title']); $ip->filename = last(explode('/', $ip->media[0]['uri'])); $ip->metadata = $c->map(function($m) { return [ diff --git a/app/Http/Controllers/LikeController.php b/app/Http/Controllers/LikeController.php index 5e23e0d36..47d1b7428 100644 --- a/app/Http/Controllers/LikeController.php +++ b/app/Http/Controllers/LikeController.php @@ -25,8 +25,7 @@ class LikeController extends Controller 'item' => 'required|integer|min:1', ]); - // API deprecated - return; + abort(422, 'Deprecated API Endpoint'); $user = Auth::user(); $profile = $user->profile; @@ -34,7 +33,7 @@ class LikeController extends Controller if (Like::whereStatusId($status->id)->whereProfileId($profile->id)->exists()) { $like = Like::whereProfileId($profile->id)->whereStatusId($status->id)->firstOrFail(); - UnlikePipeline::dispatch($like); + UnlikePipeline::dispatch($like)->onQueue('feed'); } else { abort_if( Like::whereProfileId($user->profile_id) @@ -60,7 +59,7 @@ class LikeController extends Controller ]) == false; $like->save(); $status->save(); - LikePipeline::dispatch($like); + LikePipeline::dispatch($like)->onQueue('feed'); } } diff --git a/app/Http/Controllers/RemoteAuthController.php b/app/Http/Controllers/RemoteAuthController.php index 72a2a08d5..e068f5d75 100644 --- a/app/Http/Controllers/RemoteAuthController.php +++ b/app/Http/Controllers/RemoteAuthController.php @@ -23,7 +23,13 @@ class RemoteAuthController extends Controller { public function start(Request $request) { - abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404); + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); if($request->user()) { return redirect('/'); } @@ -37,7 +43,13 @@ class RemoteAuthController extends Controller public function getAuthDomains(Request $request) { - abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404); + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); if(config('remote-auth.mastodon.domains.only_custom')) { $res = config('remote-auth.mastodon.domains.custom'); @@ -69,7 +81,14 @@ class RemoteAuthController extends Controller public function redirect(Request $request) { - abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404); + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); + $this->validate($request, ['domain' => 'required']); $domain = $request->input('domain'); @@ -158,6 +177,14 @@ class RemoteAuthController extends Controller public function preflight(Request $request) { + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); + if(!$request->filled('d') || !$request->filled('dsh') || !$request->session()->exists('oauth_redirect_to')) { return redirect('/login'); } @@ -167,6 +194,14 @@ class RemoteAuthController extends Controller public function handleCallback(Request $request) { + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); + $domain = $request->session()->get('oauth_domain'); if($request->filled('code')) { @@ -195,7 +230,13 @@ class RemoteAuthController extends Controller public function onboarding(Request $request) { - abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404); + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); if($request->user()) { return redirect('/'); } @@ -204,6 +245,13 @@ class RemoteAuthController extends Controller public function sessionCheck(Request $request) { + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); abort_if($request->user(), 403); abort_unless($request->session()->exists('oauth_domain'), 403); abort_unless($request->session()->exists('oauth_remote_session_token'), 403); @@ -248,6 +296,13 @@ class RemoteAuthController extends Controller public function sessionGetMastodonData(Request $request) { + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); abort_if($request->user(), 403); abort_unless($request->session()->exists('oauth_domain'), 403); abort_unless($request->session()->exists('oauth_remote_session_token'), 403); @@ -279,6 +334,13 @@ class RemoteAuthController extends Controller public function sessionValidateUsername(Request $request) { + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); abort_if($request->user(), 403); abort_unless($request->session()->exists('oauth_domain'), 403); abort_unless($request->session()->exists('oauth_remote_session_token'), 403); @@ -334,6 +396,13 @@ class RemoteAuthController extends Controller public function sessionValidateEmail(Request $request) { + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); abort_if($request->user(), 403); abort_unless($request->session()->exists('oauth_domain'), 403); abort_unless($request->session()->exists('oauth_remote_session_token'), 403); @@ -359,6 +428,13 @@ class RemoteAuthController extends Controller public function sessionGetMastodonFollowers(Request $request) { + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); abort_unless($request->session()->exists('oauth_domain'), 403); abort_unless($request->session()->exists('oauth_remote_session_token'), 403); abort_unless($request->session()->exists('oauth_remasto_id'), 403); @@ -386,6 +462,13 @@ class RemoteAuthController extends Controller public function handleSubmit(Request $request) { + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); abort_unless($request->session()->exists('oauth_domain'), 403); abort_unless($request->session()->exists('oauth_remote_session_token'), 403); abort_unless($request->session()->exists('oauth_remasto_id'), 403); @@ -464,7 +547,13 @@ class RemoteAuthController extends Controller public function storeBio(Request $request) { - abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404); + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); abort_unless($request->user(), 404); abort_unless($request->session()->exists('oauth_domain'), 403); abort_unless($request->session()->exists('oauth_remote_session_token'), 403); @@ -483,7 +572,13 @@ class RemoteAuthController extends Controller public function accountToId(Request $request) { - abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404); + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); abort_if($request->user(), 404); abort_unless($request->session()->exists('oauth_domain'), 403); abort_unless($request->session()->exists('oauth_remote_session_token'), 403); @@ -525,7 +620,13 @@ class RemoteAuthController extends Controller public function storeAvatar(Request $request) { - abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404); + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); abort_unless($request->user(), 404); $this->validate($request, [ 'avatar_url' => 'required|active_url', @@ -547,7 +648,13 @@ class RemoteAuthController extends Controller public function finishUp(Request $request) { - abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404); + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); abort_unless($request->user(), 404); $currentWebfinger = '@' . $request->user()->username . '@' . config('pixelfed.domain.app'); @@ -564,7 +671,13 @@ class RemoteAuthController extends Controller public function handleLogin(Request $request) { - abort_unless(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled'), 404); + abort_unless(( + config_cache('pixelfed.open_registration') && + config('remote-auth.mastodon.enabled') + ) || ( + config('remote-auth.mastodon.ignore_closed_state') && + config('remote-auth.mastodon.enabled') + ), 404); abort_if($request->user(), 404); abort_unless($request->session()->exists('oauth_domain'), 403); abort_unless($request->session()->exists('oauth_remote_session_token'), 403); diff --git a/app/Http/Controllers/Settings/PrivacySettings.php b/app/Http/Controllers/Settings/PrivacySettings.php index 3d1cd4515..bd2222d48 100644 --- a/app/Http/Controllers/Settings/PrivacySettings.php +++ b/app/Http/Controllers/Settings/PrivacySettings.php @@ -14,19 +14,20 @@ use App\Util\Lexer\PrettyNumber; use App\Util\ActivityPub\Helpers; use Auth, Cache, DB; use Illuminate\Http\Request; +use App\Models\UserDomainBlock; trait PrivacySettings { public function privacy() { - $user = Auth::user(); - $settings = $user->settings; - $profile = $user->profile; - $is_private = $profile->is_private; - $settings['is_private'] = (bool) $is_private; + $user = Auth::user(); + $settings = $user->settings; + $profile = $user->profile; + $is_private = $profile->is_private; + $settings['is_private'] = (bool) $is_private; - return view('settings.privacy', compact('settings', 'profile')); + return view('settings.privacy', compact('settings', 'profile')); } public function privacyStore(Request $request) @@ -39,11 +40,13 @@ trait PrivacySettings 'public_dm', 'show_profile_follower_count', 'show_profile_following_count', + 'indexable', 'show_atom', ]; - $profile->is_suggestable = $request->input('is_suggestable') == 'on'; - $profile->save(); + $profile->indexable = $request->input('indexable') == 'on'; + $profile->is_suggestable = $request->input('is_suggestable') == 'on'; + $profile->save(); foreach ($fields as $field) { $form = $request->input($field); @@ -70,6 +73,8 @@ trait PrivacySettings } else { $settings->{$field} = false; } + } elseif ($field == 'indexable') { + } else { if ($form == 'on') { $settings->{$field} = true; @@ -145,47 +150,25 @@ trait PrivacySettings public function blockedInstances() { - $pid = Auth::user()->profile->id; - $filters = UserFilter::whereUserId($pid) - ->whereFilterableType('App\Instance') - ->whereFilterType('block') - ->orderByDesc('id') - ->paginate(10); - return view('settings.privacy.blocked-instances', compact('filters')); + // deprecated + abort(404); + } + + public function domainBlocks() + { + return view('settings.privacy.domain-blocks'); } public function blockedInstanceStore(Request $request) { - $this->validate($request, [ - 'domain' => 'required|url|min:1|max:120' - ]); - $domain = $request->input('domain'); - if(Helpers::validateUrl($domain) == false) { - return abort(400, 'Invalid domain'); - } - $domain = parse_url($domain, PHP_URL_HOST); - $instance = Instance::firstOrCreate(['domain' => $domain]); - $filter = new UserFilter; - $filter->user_id = Auth::user()->profile->id; - $filter->filterable_id = $instance->id; - $filter->filterable_type = 'App\Instance'; - $filter->filter_type = 'block'; - $filter->save(); - return response()->json(['msg' => 200]); + // deprecated + abort(404); } public function blockedInstanceUnblock(Request $request) { - $this->validate($request, [ - 'id' => 'required|integer|min:1' - ]); - $pid = Auth::user()->profile->id; - - $filter = UserFilter::whereFilterableType('App\Instance') - ->whereUserId($pid) - ->findOrFail($request->input('id')); - $filter->delete(); - return redirect(route('settings.privacy.blocked-instances')); + // deprecated + abort(404); } public function blockedKeywords() diff --git a/app/Http/Controllers/Stories/StoryApiV1Controller.php b/app/Http/Controllers/Stories/StoryApiV1Controller.php index e32fffa26..ca6a24791 100644 --- a/app/Http/Controllers/Stories/StoryApiV1Controller.php +++ b/app/Http/Controllers/Stories/StoryApiV1Controller.php @@ -20,339 +20,486 @@ use App\Jobs\StoryPipeline\StoryViewDeliver; use App\Services\AccountService; use App\Services\MediaPathService; use App\Services\StoryService; +use App\Http\Resources\StoryView as StoryViewResource; class StoryApiV1Controller extends Controller { - public function carousel(Request $request) - { - abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); - $pid = $request->user()->profile_id; + const RECENT_KEY = 'pf:stories:recent-by-id:'; + const RECENT_TTL = 300; - if(config('database.default') == 'pgsql') { - $s = Story::select('stories.*', 'followers.following_id') - ->leftJoin('followers', 'followers.following_id', 'stories.profile_id') - ->where('followers.profile_id', $pid) - ->where('stories.active', true) - ->get(); - } else { - $s = Story::select('stories.*', 'followers.following_id') - ->leftJoin('followers', 'followers.following_id', 'stories.profile_id') - ->where('followers.profile_id', $pid) - ->where('stories.active', true) - ->orderBy('id') - ->get(); - } + public function carousel(Request $request) + { + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); + $pid = $request->user()->profile_id; - $nodes = $s->map(function($s) use($pid) { - $profile = AccountService::get($s->profile_id, true); - if(!$profile || !isset($profile['id'])) { - return false; - } + if(config('database.default') == 'pgsql') { + $s = Cache::remember(self::RECENT_KEY . $pid, self::RECENT_TTL, function() use($pid) { + return Story::select('stories.*', 'followers.following_id') + ->leftJoin('followers', 'followers.following_id', 'stories.profile_id') + ->where('followers.profile_id', $pid) + ->where('stories.active', true) + ->map(function($s) { + $r = new \StdClass; + $r->id = $s->id; + $r->profile_id = $s->profile_id; + $r->type = $s->type; + $r->path = $s->path; + return $r; + }) + ->unique('profile_id'); + }); + } else { + $s = Cache::remember(self::RECENT_KEY . $pid, self::RECENT_TTL, function() use($pid) { + return Story::select('stories.*', 'followers.following_id') + ->leftJoin('followers', 'followers.following_id', 'stories.profile_id') + ->where('followers.profile_id', $pid) + ->where('stories.active', true) + ->orderBy('id') + ->get(); + }); + } - return [ - 'id' => (string) $s->id, - 'pid' => (string) $s->profile_id, - 'type' => $s->type, - 'src' => url(Storage::url($s->path)), - 'duration' => $s->duration ?? 3, - 'seen' => StoryService::hasSeen($pid, $s->id), - 'created_at' => $s->created_at->format('c') - ]; - }) - ->filter() - ->groupBy('pid') - ->map(function($item) use($pid) { - $profile = AccountService::get($item[0]['pid'], true); - $url = $profile['local'] ? url("/stories/{$profile['username']}") : - url("/i/rs/{$profile['id']}"); - return [ - 'id' => 'pfs:' . $profile['id'], - 'user' => [ - 'id' => (string) $profile['id'], - 'username' => $profile['username'], - 'username_acct' => $profile['acct'], - 'avatar' => $profile['avatar'], - 'local' => $profile['local'], - 'is_author' => $profile['id'] == $pid - ], - 'nodes' => $item, - 'url' => $url, - 'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])), - ]; - }) - ->sortBy('seen') - ->values(); + $nodes = $s->map(function($s) use($pid) { + $profile = AccountService::get($s->profile_id, true); + if(!$profile || !isset($profile['id'])) { + return false; + } - $res = [ - 'self' => [], - 'nodes' => $nodes, - ]; + return [ + 'id' => (string) $s->id, + 'pid' => (string) $s->profile_id, + 'type' => $s->type, + 'src' => url(Storage::url($s->path)), + 'duration' => $s->duration ?? 3, + 'seen' => StoryService::hasSeen($pid, $s->id), + 'created_at' => $s->created_at->format('c') + ]; + }) + ->filter() + ->groupBy('pid') + ->map(function($item) use($pid) { + $profile = AccountService::get($item[0]['pid'], true); + $url = $profile['local'] ? url("/stories/{$profile['username']}") : + url("/i/rs/{$profile['id']}"); + return [ + 'id' => 'pfs:' . $profile['id'], + 'user' => [ + 'id' => (string) $profile['id'], + 'username' => $profile['username'], + 'username_acct' => $profile['acct'], + 'avatar' => $profile['avatar'], + 'local' => $profile['local'], + 'is_author' => $profile['id'] == $pid + ], + 'nodes' => $item, + 'url' => $url, + 'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])), + ]; + }) + ->sortBy('seen') + ->values(); - if(Story::whereProfileId($pid)->whereActive(true)->exists()) { - $selfStories = Story::whereProfileId($pid) - ->whereActive(true) - ->get() - ->map(function($s) use($pid) { - return [ - 'id' => (string) $s->id, - 'type' => $s->type, - 'src' => url(Storage::url($s->path)), - 'duration' => $s->duration, - 'seen' => true, - 'created_at' => $s->created_at->format('c') - ]; - }) - ->sortBy('id') - ->values(); - $selfProfile = AccountService::get($pid, true); - $res['self'] = [ - 'user' => [ - 'id' => (string) $selfProfile['id'], - 'username' => $selfProfile['acct'], - 'avatar' => $selfProfile['avatar'], - 'local' => $selfProfile['local'], - 'is_author' => true - ], + $res = [ + 'self' => [], + 'nodes' => $nodes, + ]; - 'nodes' => $selfStories, - ]; - } - return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); - } + if(Story::whereProfileId($pid)->whereActive(true)->exists()) { + $selfStories = Story::whereProfileId($pid) + ->whereActive(true) + ->get() + ->map(function($s) use($pid) { + return [ + 'id' => (string) $s->id, + 'type' => $s->type, + 'src' => url(Storage::url($s->path)), + 'duration' => $s->duration, + 'seen' => true, + 'created_at' => $s->created_at->format('c') + ]; + }) + ->sortBy('id') + ->values(); + $selfProfile = AccountService::get($pid, true); + $res['self'] = [ + 'user' => [ + 'id' => (string) $selfProfile['id'], + 'username' => $selfProfile['acct'], + 'avatar' => $selfProfile['avatar'], + 'local' => $selfProfile['local'], + 'is_author' => true + ], - public function add(Request $request) - { - abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); + 'nodes' => $selfStories, + ]; + } + return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); + } - $this->validate($request, [ - 'file' => function() { - return [ - 'required', - 'mimetypes:image/jpeg,image/png,video/mp4', - 'max:' . config_cache('pixelfed.max_photo_size'), - ]; - }, - 'duration' => 'sometimes|integer|min:0|max:30' - ]); + public function selfCarousel(Request $request) + { + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); + $pid = $request->user()->profile_id; - $user = $request->user(); + if(config('database.default') == 'pgsql') { + $s = Cache::remember(self::RECENT_KEY . $pid, self::RECENT_TTL, function() use($pid) { + return Story::select('stories.*', 'followers.following_id') + ->leftJoin('followers', 'followers.following_id', 'stories.profile_id') + ->where('followers.profile_id', $pid) + ->where('stories.active', true) + ->map(function($s) { + $r = new \StdClass; + $r->id = $s->id; + $r->profile_id = $s->profile_id; + $r->type = $s->type; + $r->path = $s->path; + return $r; + }) + ->unique('profile_id'); + }); + } else { + $s = Cache::remember(self::RECENT_KEY . $pid, self::RECENT_TTL, function() use($pid) { + return Story::select('stories.*', 'followers.following_id') + ->leftJoin('followers', 'followers.following_id', 'stories.profile_id') + ->where('followers.profile_id', $pid) + ->where('stories.active', true) + ->orderBy('id') + ->get(); + }); + } - $count = Story::whereProfileId($user->profile_id) - ->whereActive(true) - ->where('expires_at', '>', now()) - ->count(); + $nodes = $s->map(function($s) use($pid) { + $profile = AccountService::get($s->profile_id, true); + if(!$profile || !isset($profile['id'])) { + return false; + } - if($count >= Story::MAX_PER_DAY) { - abort(418, 'You have reached your limit for new Stories today.'); - } + return [ + 'id' => (string) $s->id, + 'pid' => (string) $s->profile_id, + 'type' => $s->type, + 'src' => url(Storage::url($s->path)), + 'duration' => $s->duration ?? 3, + 'seen' => StoryService::hasSeen($pid, $s->id), + 'created_at' => $s->created_at->format('c') + ]; + }) + ->filter() + ->groupBy('pid') + ->map(function($item) use($pid) { + $profile = AccountService::get($item[0]['pid'], true); + $url = $profile['local'] ? url("/stories/{$profile['username']}") : + url("/i/rs/{$profile['id']}"); + return [ + 'id' => 'pfs:' . $profile['id'], + 'user' => [ + 'id' => (string) $profile['id'], + 'username' => $profile['username'], + 'username_acct' => $profile['acct'], + 'avatar' => $profile['avatar'], + 'local' => $profile['local'], + 'is_author' => $profile['id'] == $pid + ], + 'nodes' => $item, + 'url' => $url, + 'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])), + ]; + }) + ->sortBy('seen') + ->values(); - $photo = $request->file('file'); - $path = $this->storeMedia($photo, $user); + $selfProfile = AccountService::get($pid, true); + $res = [ + 'self' => [ + 'user' => [ + 'id' => (string) $selfProfile['id'], + 'username' => $selfProfile['acct'], + 'avatar' => $selfProfile['avatar'], + 'local' => $selfProfile['local'], + 'is_author' => true + ], - $story = new Story(); - $story->duration = $request->input('duration', 3); - $story->profile_id = $user->profile_id; - $story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' :'photo'; - $story->mime = $photo->getMimeType(); - $story->path = $path; - $story->local = true; - $story->size = $photo->getSize(); - $story->bearcap_token = str_random(64); - $story->expires_at = now()->addMinutes(1440); - $story->save(); + 'nodes' => [], + ], + 'nodes' => $nodes, + ]; - $url = $story->path; + if(Story::whereProfileId($pid)->whereActive(true)->exists()) { + $selfStories = Story::whereProfileId($pid) + ->whereActive(true) + ->get() + ->map(function($s) use($pid) { + return [ + 'id' => (string) $s->id, + 'type' => $s->type, + 'src' => url(Storage::url($s->path)), + 'duration' => $s->duration, + 'seen' => true, + 'created_at' => $s->created_at->format('c') + ]; + }) + ->sortBy('id') + ->values(); + $res['self']['nodes'] = $selfStories; + } + return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); + } - $res = [ - 'code' => 200, - 'msg' => 'Successfully added', - 'media_id' => (string) $story->id, - 'media_url' => url(Storage::url($url)) . '?v=' . time(), - 'media_type' => $story->type - ]; + public function add(Request $request) + { + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); - return $res; - } + $this->validate($request, [ + 'file' => function() { + return [ + 'required', + 'mimetypes:image/jpeg,image/png,video/mp4', + 'max:' . config_cache('pixelfed.max_photo_size'), + ]; + }, + 'duration' => 'sometimes|integer|min:0|max:30' + ]); - public function publish(Request $request) - { - abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); + $user = $request->user(); - $this->validate($request, [ - 'media_id' => 'required', - 'duration' => 'required|integer|min:0|max:30', - 'can_reply' => 'required|boolean', - 'can_react' => 'required|boolean' - ]); + $count = Story::whereProfileId($user->profile_id) + ->whereActive(true) + ->where('expires_at', '>', now()) + ->count(); - $id = $request->input('media_id'); - $user = $request->user(); - $story = Story::whereProfileId($user->profile_id) - ->findOrFail($id); + if($count >= Story::MAX_PER_DAY) { + abort(418, 'You have reached your limit for new Stories today.'); + } - $story->active = true; - $story->duration = $request->input('duration', 10); - $story->can_reply = $request->input('can_reply'); - $story->can_react = $request->input('can_react'); - $story->save(); + $photo = $request->file('file'); + $path = $this->storeMedia($photo, $user); - StoryService::delLatest($story->profile_id); - StoryFanout::dispatch($story)->onQueue('story'); - StoryService::addRotateQueue($story->id); + $story = new Story(); + $story->duration = $request->input('duration', 3); + $story->profile_id = $user->profile_id; + $story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' :'photo'; + $story->mime = $photo->getMimeType(); + $story->path = $path; + $story->local = true; + $story->size = $photo->getSize(); + $story->bearcap_token = str_random(64); + $story->expires_at = now()->addMinutes(1440); + $story->save(); - return [ - 'code' => 200, - 'msg' => 'Successfully published', - ]; - } + $url = $story->path; - public function delete(Request $request, $id) - { - abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); + $res = [ + 'code' => 200, + 'msg' => 'Successfully added', + 'media_id' => (string) $story->id, + 'media_url' => url(Storage::url($url)) . '?v=' . time(), + 'media_type' => $story->type + ]; - $user = $request->user(); + return $res; + } - $story = Story::whereProfileId($user->profile_id) - ->findOrFail($id); - $story->active = false; - $story->save(); + public function publish(Request $request) + { + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); - StoryDelete::dispatch($story)->onQueue('story'); + $this->validate($request, [ + 'media_id' => 'required', + 'duration' => 'required|integer|min:0|max:30', + 'can_reply' => 'required|boolean', + 'can_react' => 'required|boolean' + ]); - return [ - 'code' => 200, - 'msg' => 'Successfully deleted' - ]; - } + $id = $request->input('media_id'); + $user = $request->user(); + $story = Story::whereProfileId($user->profile_id) + ->findOrFail($id); - public function viewed(Request $request) - { - abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); + $story->active = true; + $story->duration = $request->input('duration', 10); + $story->can_reply = $request->input('can_reply'); + $story->can_react = $request->input('can_react'); + $story->save(); - $this->validate($request, [ - 'id' => 'required|min:1', - ]); - $id = $request->input('id'); + StoryService::delLatest($story->profile_id); + StoryFanout::dispatch($story)->onQueue('story'); + StoryService::addRotateQueue($story->id); - $authed = $request->user()->profile; + return [ + 'code' => 200, + 'msg' => 'Successfully published', + ]; + } - $story = Story::with('profile') - ->findOrFail($id); - $exp = $story->expires_at; + public function delete(Request $request, $id) + { + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); - $profile = $story->profile; + $user = $request->user(); - if($story->profile_id == $authed->id) { - return []; - } + $story = Story::whereProfileId($user->profile_id) + ->findOrFail($id); + $story->active = false; + $story->save(); - $publicOnly = (bool) $profile->followedBy($authed); - abort_if(!$publicOnly, 403); + StoryDelete::dispatch($story)->onQueue('story'); - $v = StoryView::firstOrCreate([ - 'story_id' => $id, - 'profile_id' => $authed->id - ]); + return [ + 'code' => 200, + 'msg' => 'Successfully deleted' + ]; + } - if($v->wasRecentlyCreated) { - Story::findOrFail($story->id)->increment('view_count'); + public function viewed(Request $request) + { + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); - if($story->local == false) { - StoryViewDeliver::dispatch($story, $authed)->onQueue('story'); - } - } + $this->validate($request, [ + 'id' => 'required|min:1', + ]); + $id = $request->input('id'); - Cache::forget('stories:recent:by_id:' . $authed->id); - StoryService::addSeen($authed->id, $story->id); - return ['code' => 200]; - } + $authed = $request->user()->profile; - public function comment(Request $request) - { - abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); - $this->validate($request, [ - 'sid' => 'required', - 'caption' => 'required|string' - ]); - $pid = $request->user()->profile_id; - $text = $request->input('caption'); + $story = Story::with('profile') + ->findOrFail($id); + $exp = $story->expires_at; - $story = Story::findOrFail($request->input('sid')); + $profile = $story->profile; - abort_if(!$story->can_reply, 422); + if($story->profile_id == $authed->id) { + return []; + } - $status = new Status; - $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; - $status->entities = json_encode([ - 'story_id' => $story->id - ]); - $status->save(); + $publicOnly = (bool) $profile->followedBy($authed); + abort_if(!$publicOnly, 403); - $dm = new DirectMessage; - $dm->to_id = $story->profile_id; - $dm->from_id = $pid; - $dm->type = 'story:comment'; - $dm->status_id = $status->id; - $dm->meta = json_encode([ - 'story_username' => $story->profile->username, - 'story_actor_username' => $request->user()->username, - 'story_id' => $story->id, - 'story_media_url' => url(Storage::url($story->path)), - 'caption' => $text - ]); - $dm->save(); + $v = StoryView::firstOrCreate([ + 'story_id' => $id, + 'profile_id' => $authed->id + ]); - Conversation::updateOrInsert( - [ - 'to_id' => $story->profile_id, - 'from_id' => $pid - ], - [ - 'type' => 'story:comment', - 'status_id' => $status->id, - 'dm_id' => $dm->id, - 'is_hidden' => false - ] - ); + if($v->wasRecentlyCreated) { + Story::findOrFail($story->id)->increment('view_count'); - if($story->local) { - $n = new Notification; - $n->profile_id = $dm->to_id; - $n->actor_id = $dm->from_id; - $n->item_id = $dm->id; - $n->item_type = 'App\DirectMessage'; - $n->action = 'story:comment'; - $n->save(); - } else { - StoryReplyDeliver::dispatch($story, $status)->onQueue('story'); - } + if($story->local == false) { + StoryViewDeliver::dispatch($story, $authed)->onQueue('story'); + } + } - return [ - 'code' => 200, - 'msg' => 'Sent!' - ]; - } + Cache::forget('stories:recent:by_id:' . $authed->id); + StoryService::addSeen($authed->id, $story->id); + return ['code' => 200]; + } - protected function storeMedia($photo, $user) - { - $mimes = explode(',', config_cache('pixelfed.media_types')); - if(in_array($photo->getMimeType(), [ - 'image/jpeg', - 'image/png', - 'video/mp4' - ]) == false) { - abort(400, 'Invalid media type'); - return; - } + public function comment(Request $request) + { + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); + $this->validate($request, [ + 'sid' => 'required', + 'caption' => 'required|string' + ]); + $pid = $request->user()->profile_id; + $text = $request->input('caption'); - $storagePath = MediaPathService::story($user->profile); - $path = $photo->storePubliclyAs($storagePath, Str::random(random_int(2, 12)) . '_' . Str::random(random_int(32, 35)) . '_' . Str::random(random_int(1, 14)) . '.' . $photo->extension()); - return $path; - } + $story = Story::findOrFail($request->input('sid')); + + abort_if(!$story->can_reply, 422); + + $status = new Status; + $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; + $status->entities = json_encode([ + 'story_id' => $story->id + ]); + $status->save(); + + $dm = new DirectMessage; + $dm->to_id = $story->profile_id; + $dm->from_id = $pid; + $dm->type = 'story:comment'; + $dm->status_id = $status->id; + $dm->meta = json_encode([ + 'story_username' => $story->profile->username, + 'story_actor_username' => $request->user()->username, + 'story_id' => $story->id, + 'story_media_url' => url(Storage::url($story->path)), + 'caption' => $text + ]); + $dm->save(); + + Conversation::updateOrInsert( + [ + 'to_id' => $story->profile_id, + 'from_id' => $pid + ], + [ + 'type' => 'story:comment', + 'status_id' => $status->id, + 'dm_id' => $dm->id, + 'is_hidden' => false + ] + ); + + if($story->local) { + $n = new Notification; + $n->profile_id = $dm->to_id; + $n->actor_id = $dm->from_id; + $n->item_id = $dm->id; + $n->item_type = 'App\DirectMessage'; + $n->action = 'story:comment'; + $n->save(); + } else { + StoryReplyDeliver::dispatch($story, $status)->onQueue('story'); + } + + return [ + 'code' => 200, + 'msg' => 'Sent!' + ]; + } + + protected function storeMedia($photo, $user) + { + $mimes = explode(',', config_cache('pixelfed.media_types')); + if(in_array($photo->getMimeType(), [ + 'image/jpeg', + 'image/png', + 'video/mp4' + ]) == false) { + abort(400, 'Invalid media type'); + return; + } + + $storagePath = MediaPathService::story($user->profile); + $path = $photo->storePubliclyAs($storagePath, Str::random(random_int(2, 12)) . '_' . Str::random(random_int(32, 35)) . '_' . Str::random(random_int(1, 14)) . '.' . $photo->extension()); + return $path; + } + + public function viewers(Request $request) + { + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); + + $this->validate($request, [ + 'sid' => 'required|string|min:1|max:50' + ]); + + $pid = $request->user()->profile_id; + $sid = $request->input('sid'); + + $story = Story::whereProfileId($pid) + ->whereActive(true) + ->findOrFail($sid); + + $viewers = StoryView::whereStoryId($story->id) + ->orderByDesc('id') + ->cursorPaginate(10); + + return StoryViewResource::collection($viewers); + } } diff --git a/app/Http/Resources/StoryView.php b/app/Http/Resources/StoryView.php new file mode 100644 index 000000000..891bf2eee --- /dev/null +++ b/app/Http/Resources/StoryView.php @@ -0,0 +1,20 @@ + + */ + public function toArray(Request $request) + { + return AccountService::get($this->profile_id, true); + } +} diff --git a/app/Jobs/AvatarPipeline/AvatarStorageCleanup.php b/app/Jobs/AvatarPipeline/AvatarStorageCleanup.php new file mode 100644 index 000000000..230797bf6 --- /dev/null +++ b/app/Jobs/AvatarPipeline/AvatarStorageCleanup.php @@ -0,0 +1,67 @@ +avatar->profile_id; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("avatar-storage-cleanup:{$this->avatar->profile_id}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct(Avatar $avatar) + { + $this->avatar = $avatar->withoutRelations(); + } + + /** + * Execute the job. + */ + public function handle(): void + { + AvatarService::cleanup($this->avatar, true); + + return; + } +} diff --git a/app/Jobs/AvatarPipeline/AvatarStorageLargePurge.php b/app/Jobs/AvatarPipeline/AvatarStorageLargePurge.php new file mode 100644 index 000000000..f432e1e56 --- /dev/null +++ b/app/Jobs/AvatarPipeline/AvatarStorageLargePurge.php @@ -0,0 +1,80 @@ +avatar->profile_id; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("avatar-storage-purge:{$this->avatar->profile_id}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct(Avatar $avatar) + { + $this->avatar = $avatar->withoutRelations(); + } + + /** + * Execute the job. + */ + public function handle(): void + { + $avatar = $this->avatar; + + $disk = AvatarService::disk(); + + $files = collect(AvatarService::storage($avatar)); + + $curFile = Str::of($avatar->cdn_url)->explode('/')->last(); + + $files = $files->filter(function($f) use($curFile) { + return !$curFile || !str_ends_with($f, $curFile); + })->each(function($name) use($disk) { + $disk->delete($name); + }); + + return; + } +} diff --git a/app/Jobs/AvatarPipeline/CreateAvatar.php b/app/Jobs/AvatarPipeline/CreateAvatar.php index fd5f94cc7..f773d1590 100644 --- a/app/Jobs/AvatarPipeline/CreateAvatar.php +++ b/app/Jobs/AvatarPipeline/CreateAvatar.php @@ -2,19 +2,25 @@ namespace App\Jobs\AvatarPipeline; -use App\Avatar; -use App\Profile; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Queue\Middleware\WithoutOverlapping; +use App\Avatar; +use App\Profile; -class CreateAvatar implements ShouldQueue +class CreateAvatar implements ShouldQueue, ShouldBeUniqueUntilProcessing { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - protected $profile; + public $profile; + public $tries = 3; + public $maxExceptions = 3; + public $timeout = 900; + public $failOnTimeout = true; /** * Delete the job if its models no longer exist. @@ -22,6 +28,31 @@ class CreateAvatar implements ShouldQueue * @var bool */ public $deleteWhenMissingModels = true; + + /** + * The number of seconds after which the job's unique lock will be released. + * + * @var int + */ + public $uniqueFor = 3600; + + /** + * Get the unique ID for the job. + */ + public function uniqueId(): string + { + return 'avatar:create:' . $this->profile->id; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("avatar-create:{$this->profile->id}"))->shared()->dontRelease()]; + } /** * Create a new job instance. @@ -30,7 +61,7 @@ class CreateAvatar implements ShouldQueue */ public function __construct(Profile $profile) { - $this->profile = $profile; + $this->profile = $profile->withoutRelations(); } /** @@ -41,12 +72,18 @@ class CreateAvatar implements ShouldQueue public function handle() { $profile = $this->profile; + $isRemote = (bool) $profile->private_key == null; $path = 'public/avatars/default.jpg'; - $avatar = new Avatar(); - $avatar->profile_id = $profile->id; - $avatar->media_path = $path; - $avatar->change_count = 0; - $avatar->last_processed_at = \Carbon\Carbon::now(); - $avatar->save(); + Avatar::updateOrCreate( + [ + 'profile_id' => $profile->id, + ], + [ + 'media_path' => $path, + 'change_count' => 0, + 'is_remote' => $isRemote, + 'last_processed_at' => now() + ] + ); } } diff --git a/app/Jobs/AvatarPipeline/RemoteAvatarFetch.php b/app/Jobs/AvatarPipeline/RemoteAvatarFetch.php index df972dd38..4e4a1b2ec 100644 --- a/app/Jobs/AvatarPipeline/RemoteAvatarFetch.php +++ b/app/Jobs/AvatarPipeline/RemoteAvatarFetch.php @@ -108,7 +108,7 @@ class RemoteAvatarFetch implements ShouldQueue $avatar->remote_url = $icon['url']; $avatar->save(); - MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false); + MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false, true); return 1; } diff --git a/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php b/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php index 259058385..c8c6820e4 100644 --- a/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php +++ b/app/Jobs/AvatarPipeline/RemoteAvatarFetchFromUrl.php @@ -89,7 +89,6 @@ class RemoteAvatarFetchFromUrl implements ShouldQueue $avatar->save(); } - MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false, true); return 1; diff --git a/app/Jobs/DeletePipeline/DeleteRemoteStatusPipeline.php b/app/Jobs/DeletePipeline/DeleteRemoteStatusPipeline.php index d27249c2a..4969fca2f 100644 --- a/app/Jobs/DeletePipeline/DeleteRemoteStatusPipeline.php +++ b/app/Jobs/DeletePipeline/DeleteRemoteStatusPipeline.php @@ -57,7 +57,7 @@ class DeleteRemoteStatusPipeline implements ShouldQueue $status = $this->status; if(AccountService::get($status->profile_id, true)) { - DecrementPostCount::dispatch($status->profile_id)->onQueue('feed'); + DecrementPostCount::dispatch($status->profile_id)->onQueue('low'); } NetworkTimelineService::del($status->id); diff --git a/app/Jobs/DirectPipeline/DirectDeletePipeline.php b/app/Jobs/DirectPipeline/DirectDeletePipeline.php new file mode 100644 index 000000000..947806422 --- /dev/null +++ b/app/Jobs/DirectPipeline/DirectDeletePipeline.php @@ -0,0 +1,42 @@ +profile = $profile; + $this->url = $url; + $this->payload = $payload; + } + + /** + * Execute the job. + */ + public function handle(): void + { + Helpers::sendSignedObject($this->profile, $this->url, $this->payload); + } +} diff --git a/app/Jobs/DirectPipeline/DirectDeliverPipeline.php b/app/Jobs/DirectPipeline/DirectDeliverPipeline.php new file mode 100644 index 000000000..7d20a406e --- /dev/null +++ b/app/Jobs/DirectPipeline/DirectDeliverPipeline.php @@ -0,0 +1,42 @@ +profile = $profile; + $this->url = $url; + $this->payload = $payload; + } + + /** + * Execute the job. + */ + public function handle(): void + { + Helpers::sendSignedObject($this->profile, $this->url, $this->payload); + } +} diff --git a/app/Jobs/FollowPipeline/FollowServiceWarmCache.php b/app/Jobs/FollowPipeline/FollowServiceWarmCache.php index cabea9958..0d3cca7ac 100644 --- a/app/Jobs/FollowPipeline/FollowServiceWarmCache.php +++ b/app/Jobs/FollowPipeline/FollowServiceWarmCache.php @@ -8,10 +8,13 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Queue\Middleware\WithoutOverlapping; use App\Services\AccountService; use App\Services\FollowerService; use Cache; use DB; +use Storage; +use App\Follower; use App\Profile; class FollowServiceWarmCache implements ShouldQueue @@ -23,6 +26,16 @@ class FollowServiceWarmCache implements ShouldQueue public $timeout = 5000; public $failOnTimeout = false; + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping($this->profileId))->dontRelease()]; + } + /** * Create a new job instance. * @@ -42,6 +55,10 @@ class FollowServiceWarmCache implements ShouldQueue { $id = $this->profileId; + if(Cache::has(FollowerService::FOLLOWERS_SYNC_KEY . $id) && Cache::has(FollowerService::FOLLOWING_SYNC_KEY . $id)) { + return; + } + $account = AccountService::get($id, true); if(!$account) { @@ -50,25 +67,43 @@ class FollowServiceWarmCache implements ShouldQueue return; } - DB::table('followers') - ->select('id', 'following_id', 'profile_id') - ->whereFollowingId($id) - ->orderBy('id') - ->chunk(200, function($followers) use($id) { - foreach($followers as $follow) { - FollowerService::add($follow->profile_id, $id); - } - }); + $hasFollowerPostProcessing = false; + $hasFollowingPostProcessing = false; - DB::table('followers') - ->select('id', 'following_id', 'profile_id') - ->whereProfileId($id) - ->orderBy('id') - ->chunk(200, function($followers) use($id) { - foreach($followers as $follow) { - FollowerService::add($id, $follow->following_id); - } - }); + if(Follower::whereProfileId($id)->orWhere('following_id', $id)->count()) { + $following = []; + $followers = []; + foreach(Follower::where('following_id', $id)->orWhere('profile_id', $id)->lazyById(500) as $follow) { + if($follow->following_id != $id && $follow->profile_id != $id) { + continue; + } + if($follow->profile_id == $id) { + $following[] = $follow->following_id; + } else { + $followers[] = $follow->profile_id; + } + } + + if(count($followers) > 100) { + // store follower ids and process in another job + Storage::put('follow-warm-cache/' . $id . '/followers.json', json_encode($followers)); + $hasFollowerPostProcessing = true; + } else { + foreach($followers as $follower) { + FollowerService::add($follower, $id); + } + } + + if(count($following) > 100) { + // store following ids and process in another job + Storage::put('follow-warm-cache/' . $id . '/following.json', json_encode($following)); + $hasFollowingPostProcessing = true; + } else { + foreach($following as $following) { + FollowerService::add($id, $following); + } + } + } Cache::put(FollowerService::FOLLOWERS_SYNC_KEY . $id, 1, 604800); Cache::put(FollowerService::FOLLOWING_SYNC_KEY . $id, 1, 604800); @@ -82,6 +117,14 @@ class FollowServiceWarmCache implements ShouldQueue AccountService::del($id); + if($hasFollowingPostProcessing) { + FollowServiceWarmCacheLargeIngestPipeline::dispatch($id, 'following')->onQueue('follow'); + } + + if($hasFollowerPostProcessing) { + FollowServiceWarmCacheLargeIngestPipeline::dispatch($id, 'followers')->onQueue('follow'); + } + return; } } diff --git a/app/Jobs/FollowPipeline/FollowServiceWarmCacheLargeIngestPipeline.php b/app/Jobs/FollowPipeline/FollowServiceWarmCacheLargeIngestPipeline.php new file mode 100644 index 000000000..3299bf7a4 --- /dev/null +++ b/app/Jobs/FollowPipeline/FollowServiceWarmCacheLargeIngestPipeline.php @@ -0,0 +1,88 @@ +profileId = $profileId; + $this->followType = $followType; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $pid = $this->profileId; + $type = $this->followType; + + if($type === 'followers') { + $key = 'follow-warm-cache/' . $pid . '/followers.json'; + if(!Storage::exists($key)) { + return; + } + $file = Storage::get($key); + $json = json_decode($file, true); + + foreach($json as $id) { + FollowerService::add($id, $pid, false); + usleep(random_int(500, 3000)); + } + sleep(5); + Storage::delete($key); + } + + if($type === 'following') { + $key = 'follow-warm-cache/' . $pid . '/following.json'; + if(!Storage::exists($key)) { + return; + } + $file = Storage::get($key); + $json = json_decode($file, true); + + foreach($json as $id) { + FollowerService::add($pid, $id, false); + usleep(random_int(500, 3000)); + } + sleep(5); + Storage::delete($key); + } + + sleep(random_int(2, 5)); + $files = Storage::files('follow-warm-cache/' . $pid); + if(empty($files)) { + Storage::deleteDirectory('follow-warm-cache/' . $pid); + } + } +} diff --git a/app/Jobs/FollowPipeline/UnfollowPipeline.php b/app/Jobs/FollowPipeline/UnfollowPipeline.php index c00246e2f..99f763f5c 100644 --- a/app/Jobs/FollowPipeline/UnfollowPipeline.php +++ b/app/Jobs/FollowPipeline/UnfollowPipeline.php @@ -17,6 +17,7 @@ use Illuminate\Support\Facades\Redis; use App\Services\AccountService; use App\Services\FollowerService; use App\Services\NotificationService; +use App\Jobs\HomeFeedPipeline\FeedUnfollowPipeline; class UnfollowPipeline implements ShouldQueue { @@ -55,6 +56,8 @@ class UnfollowPipeline implements ShouldQueue return; } + FeedUnfollowPipeline::dispatch($actor, $target)->onQueue('follow'); + FollowerService::remove($actor, $target); $actorProfileSync = Cache::get(FollowerService::FOLLOWING_SYNC_KEY . $actor); diff --git a/app/Jobs/HomeFeedPipeline/FeedFollowPipeline.php b/app/Jobs/HomeFeedPipeline/FeedFollowPipeline.php new file mode 100644 index 000000000..e386329ca --- /dev/null +++ b/app/Jobs/HomeFeedPipeline/FeedFollowPipeline.php @@ -0,0 +1,87 @@ +actorId . ':fid:' . $this->followingId; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("hts:feed:insert:follows:aid:{$this->actorId}:fid:{$this->followingId}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct($actorId, $followingId) + { + $this->actorId = $actorId; + $this->followingId = $followingId; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $actorId = $this->actorId; + $followingId = $this->followingId; + + $minId = SnowflakeService::byDate(now()->subWeeks(6)); + + $ids = Status::where('id', '>', $minId) + ->where('profile_id', $followingId) + ->whereNull(['in_reply_to_id', 'reblog_of_id']) + ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) + ->whereIn('visibility',['public', 'unlisted', 'private']) + ->orderByDesc('id') + ->limit(HomeTimelineService::FOLLOWER_FEED_POST_LIMIT) + ->pluck('id'); + + foreach($ids as $id) { + HomeTimelineService::add($actorId, $id); + } + } +} diff --git a/app/Jobs/HomeFeedPipeline/FeedInsertPipeline.php b/app/Jobs/HomeFeedPipeline/FeedInsertPipeline.php new file mode 100644 index 000000000..4237a7b1a --- /dev/null +++ b/app/Jobs/HomeFeedPipeline/FeedInsertPipeline.php @@ -0,0 +1,114 @@ +sid; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("hts:feed:insert:sid:{$this->sid}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct($sid, $pid) + { + $this->sid = $sid; + $this->pid = $pid; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $sid = $this->sid; + $status = StatusService::get($sid, false); + + if(!$status || !isset($status['account']) || !isset($status['account']['id'], $status['url'])) { + return; + } + + if(!in_array($status['pf_type'], ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) { + return; + } + + HomeTimelineService::add($this->pid, $this->sid); + + $ids = FollowerService::localFollowerIds($this->pid); + + if(!$ids || !count($ids)) { + return; + } + + $domain = strtolower(parse_url($status['url'], PHP_URL_HOST)); + $skipIds = []; + + if(strtolower(config('pixelfed.domain.app')) !== $domain) { + $skipIds = UserDomainBlock::where('domain', $domain)->pluck('profile_id')->toArray(); + } + + $filters = UserFilter::whereFilterableType('App\Profile') + ->whereFilterableId($status['account']['id']) + ->whereIn('filter_type', ['mute', 'block']) + ->pluck('user_id') + ->toArray(); + + if($filters && count($filters)) { + $skipIds = array_merge($skipIds, $filters); + } + + $skipIds = array_unique(array_values($skipIds)); + + foreach($ids as $id) { + if(!in_array($id, $skipIds)) { + HomeTimelineService::add($id, $this->sid); + } + } + } +} diff --git a/app/Jobs/HomeFeedPipeline/FeedInsertRemotePipeline.php b/app/Jobs/HomeFeedPipeline/FeedInsertRemotePipeline.php new file mode 100644 index 000000000..6c4ce0c35 --- /dev/null +++ b/app/Jobs/HomeFeedPipeline/FeedInsertRemotePipeline.php @@ -0,0 +1,112 @@ +sid; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("hts:feed:insert:remote:sid:{$this->sid}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct($sid, $pid) + { + $this->sid = $sid; + $this->pid = $pid; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $sid = $this->sid; + $status = StatusService::get($sid, false); + + if(!$status || !isset($status['account']) || !isset($status['account']['id'], $status['url'])) { + return; + } + + if(!in_array($status['pf_type'], ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) { + return; + } + + $ids = FollowerService::localFollowerIds($this->pid); + + if(!$ids || !count($ids)) { + return; + } + + $domain = strtolower(parse_url($status['url'], PHP_URL_HOST)); + $skipIds = []; + + if(strtolower(config('pixelfed.domain.app')) !== $domain) { + $skipIds = UserDomainBlock::where('domain', $domain)->pluck('profile_id')->toArray(); + } + + $filters = UserFilter::whereFilterableType('App\Profile') + ->whereFilterableId($status['account']['id']) + ->whereIn('filter_type', ['mute', 'block']) + ->pluck('user_id') + ->toArray(); + + if($filters && count($filters)) { + $skipIds = array_merge($skipIds, $filters); + } + + $skipIds = array_unique(array_values($skipIds)); + + foreach($ids as $id) { + if(!in_array($id, $skipIds)) { + HomeTimelineService::add($id, $this->sid); + } + } + } +} diff --git a/app/Jobs/HomeFeedPipeline/FeedRemoveDomainPipeline.php b/app/Jobs/HomeFeedPipeline/FeedRemoveDomainPipeline.php new file mode 100644 index 000000000..018ea3794 --- /dev/null +++ b/app/Jobs/HomeFeedPipeline/FeedRemoveDomainPipeline.php @@ -0,0 +1,98 @@ +pid . ':d-' . $this->domain; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("hts:feed:remove:domain:{$this->pid}:d-{$this->domain}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct($pid, $domain) + { + $this->pid = $pid; + $this->domain = $domain; + } + + /** + * Execute the job. + */ + public function handle(): void + { + if(!config('exp.cached_home_timeline')) { + return; + } + + if ($this->batch()->cancelled()) { + return; + } + + if(!$this->pid || !$this->domain) { + return; + } + $domain = strtolower($this->domain); + $pid = $this->pid; + $posts = HomeTimelineService::get($pid, '0', '-1'); + + foreach($posts as $post) { + $status = StatusService::get($post, false); + if(!$status || !isset($status['url'])) { + HomeTimelineService::rem($pid, $post); + continue; + } + $host = strtolower(parse_url($status['url'], PHP_URL_HOST)); + if($host === strtolower(config('pixelfed.domain.app')) || !$host) { + continue; + } + if($host === $domain) { + HomeTimelineService::rem($pid, $status['id']); + } + } + } +} diff --git a/app/Jobs/HomeFeedPipeline/FeedRemovePipeline.php b/app/Jobs/HomeFeedPipeline/FeedRemovePipeline.php new file mode 100644 index 000000000..5c09d749a --- /dev/null +++ b/app/Jobs/HomeFeedPipeline/FeedRemovePipeline.php @@ -0,0 +1,76 @@ +sid; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("hts:feed:remove:sid:{$this->sid}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct($sid, $pid) + { + $this->sid = $sid; + $this->pid = $pid; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $ids = FollowerService::localFollowerIds($this->pid); + + HomeTimelineService::rem($this->pid, $this->sid); + + foreach($ids as $id) { + HomeTimelineService::rem($id, $this->sid); + } + } +} diff --git a/app/Jobs/HomeFeedPipeline/FeedRemoveRemotePipeline.php b/app/Jobs/HomeFeedPipeline/FeedRemoveRemotePipeline.php new file mode 100644 index 000000000..d9ee716ba --- /dev/null +++ b/app/Jobs/HomeFeedPipeline/FeedRemoveRemotePipeline.php @@ -0,0 +1,74 @@ +sid; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("hts:feed:remove:remote:sid:{$this->sid}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct($sid, $pid) + { + $this->sid = $sid; + $this->pid = $pid; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $ids = FollowerService::localFollowerIds($this->pid); + + foreach($ids as $id) { + HomeTimelineService::rem($id, $this->sid); + } + } +} diff --git a/app/Jobs/HomeFeedPipeline/FeedUnfollowPipeline.php b/app/Jobs/HomeFeedPipeline/FeedUnfollowPipeline.php new file mode 100644 index 000000000..996e74c10 --- /dev/null +++ b/app/Jobs/HomeFeedPipeline/FeedUnfollowPipeline.php @@ -0,0 +1,81 @@ +actorId . ':fid:' . $this->followingId; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("hts:feed:remove:follows:aid:{$this->actorId}:fid:{$this->followingId}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct($actorId, $followingId) + { + $this->actorId = $actorId; + $this->followingId = $followingId; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $actorId = $this->actorId; + $followingId = $this->followingId; + + $ids = HomeTimelineService::get($actorId, 0, -1); + foreach($ids as $id) { + $status = StatusService::get($id, false); + if($status && isset($status['account'], $status['account']['id'])) { + if($status['account']['id'] == $followingId) { + HomeTimelineService::rem($actorId, $id); + } + } + } + } +} diff --git a/app/Jobs/HomeFeedPipeline/FeedWarmCachePipeline.php b/app/Jobs/HomeFeedPipeline/FeedWarmCachePipeline.php new file mode 100644 index 000000000..00cdbda65 --- /dev/null +++ b/app/Jobs/HomeFeedPipeline/FeedWarmCachePipeline.php @@ -0,0 +1,67 @@ +pid; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("hfp:warm-cache:pid:{$this->pid}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct($pid) + { + $this->pid = $pid; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $pid = $this->pid; + HomeTimelineService::warmCache($pid, true, 400, true); + } +} diff --git a/app/Jobs/HomeFeedPipeline/HashtagInsertFanoutPipeline.php b/app/Jobs/HomeFeedPipeline/HashtagInsertFanoutPipeline.php new file mode 100644 index 000000000..eca598e49 --- /dev/null +++ b/app/Jobs/HomeFeedPipeline/HashtagInsertFanoutPipeline.php @@ -0,0 +1,116 @@ +hashtag->id; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("hfp:hashtag:fanout:insert:{$this->hashtag->id}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct(StatusHashtag $hashtag) + { + $this->hashtag = $hashtag; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $hashtag = $this->hashtag; + $sid = $hashtag->status_id; + $status = StatusService::get($sid, false); + + if(!$status || !isset($status['account']) || !isset($status['account']['id'], $status['url'])) { + return; + } + + if(!in_array($status['pf_type'], ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) { + return; + } + + $domain = strtolower(parse_url($status['url'], PHP_URL_HOST)); + $skipIds = []; + + if(strtolower(config('pixelfed.domain.app')) !== $domain) { + $skipIds = UserDomainBlock::where('domain', $domain)->pluck('profile_id')->toArray(); + } + + $filters = UserFilter::whereFilterableType('App\Profile')->whereFilterableId($status['account']['id'])->whereIn('filter_type', ['mute', 'block'])->pluck('user_id')->toArray(); + + if($filters && count($filters)) { + $skipIds = array_merge($skipIds, $filters); + } + + $skipIds = array_unique(array_values($skipIds)); + + $ids = HashtagFollowService::getPidByHid($hashtag->hashtag_id); + + if(!$ids || !count($ids)) { + return; + } + + foreach($ids as $id) { + if(!in_array($id, $skipIds)) { + HomeTimelineService::add($id, $hashtag->status_id); + } + } + } +} diff --git a/app/Jobs/HomeFeedPipeline/HashtagRemoveFanoutPipeline.php b/app/Jobs/HomeFeedPipeline/HashtagRemoveFanoutPipeline.php new file mode 100644 index 000000000..f1968e120 --- /dev/null +++ b/app/Jobs/HomeFeedPipeline/HashtagRemoveFanoutPipeline.php @@ -0,0 +1,92 @@ +hid . ':' . $this->sid; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("hfp:hashtag:fanout:remove:{$this->hid}:{$this->sid}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct($sid, $hid) + { + $this->sid = $sid; + $this->hid = $hid; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $sid = $this->sid; + $hid = $this->hid; + $status = StatusService::get($sid, false); + + if(!$status || !isset($status['account']) || !isset($status['account']['id'])) { + return; + } + + if(!in_array($status['pf_type'], ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) { + return; + } + + $ids = HashtagFollowService::getPidByHid($hid); + + if(!$ids || !count($ids)) { + return; + } + + foreach($ids as $id) { + HomeTimelineService::rem($id, $sid); + } + } +} diff --git a/app/Jobs/HomeFeedPipeline/HashtagUnfollowPipeline.php b/app/Jobs/HomeFeedPipeline/HashtagUnfollowPipeline.php new file mode 100644 index 000000000..232179ec3 --- /dev/null +++ b/app/Jobs/HomeFeedPipeline/HashtagUnfollowPipeline.php @@ -0,0 +1,80 @@ +hid = $hid; + $this->pid = $pid; + $this->slug = $slug; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $hid = $this->hid; + $pid = $this->pid; + $slug = strtolower($this->slug); + + $statusIds = HomeTimelineService::get($pid, 0, -1); + + $followingIds = Cache::remember('profile:following:'.$pid, 1209600, function() use($pid) { + $following = Follower::whereProfileId($pid)->pluck('following_id'); + return $following->push($pid)->toArray(); + }); + + foreach($statusIds as $id) { + $status = StatusService::get($id, false); + if(!$status || empty($status['tags'])) { + HomeTimelineService::rem($pid, $id); + continue; + } + $following = in_array((int) $status['account']['id'], $followingIds); + if($following === true) { + continue; + } + + $tags = collect($status['tags'])->map(function($tag) { + return strtolower($tag['name']); + })->filter()->values()->toArray(); + + if(in_array($slug, $tags)) { + HomeTimelineService::rem($pid, $id); + } + } + } +} diff --git a/app/Jobs/ImageOptimizePipeline/ImageResize.php b/app/Jobs/ImageOptimizePipeline/ImageResize.php index 9bb896a40..c1b4ea7f0 100644 --- a/app/Jobs/ImageOptimizePipeline/ImageResize.php +++ b/app/Jobs/ImageOptimizePipeline/ImageResize.php @@ -9,6 +9,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Log; class ImageResize implements ShouldQueue { @@ -46,6 +47,7 @@ class ImageResize implements ShouldQueue } $path = storage_path('app/'.$media->media_path); if (!is_file($path) || $media->skip_optimize) { + Log::info('Tried to optimize media that does not exist or is not readable. ' . $path); return; } @@ -57,6 +59,7 @@ class ImageResize implements ShouldQueue $img = new Image(); $img->resizeImage($media); } catch (Exception $e) { + Log::error($e); } ImageThumbnail::dispatch($media)->onQueue('mmo'); diff --git a/app/Jobs/InboxPipeline/InboxValidator.php b/app/Jobs/InboxPipeline/InboxValidator.php index 4017d3acd..8d0f414c5 100644 --- a/app/Jobs/InboxPipeline/InboxValidator.php +++ b/app/Jobs/InboxPipeline/InboxValidator.php @@ -193,7 +193,7 @@ class InboxValidator implements ShouldQueue } try { - $res = Http::timeout(20)->withHeaders([ + $res = Http::withOptions(['allow_redirects' => false])->timeout(20)->withHeaders([ 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', 'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org', ])->get($actor->remote_url); diff --git a/app/Jobs/InboxPipeline/InboxWorker.php b/app/Jobs/InboxPipeline/InboxWorker.php index c8508c0fc..1bc88507d 100644 --- a/app/Jobs/InboxPipeline/InboxWorker.php +++ b/app/Jobs/InboxPipeline/InboxWorker.php @@ -173,7 +173,7 @@ class InboxWorker implements ShouldQueue } try { - $res = Http::timeout(20)->withHeaders([ + $res = Http::withOptions(['allow_redirects' => false])->timeout(20)->withHeaders([ 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', 'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org', ])->get($actor->remote_url); diff --git a/app/Jobs/InternalPipeline/NotificationEpochUpdatePipeline.php b/app/Jobs/InternalPipeline/NotificationEpochUpdatePipeline.php new file mode 100644 index 000000000..477b1f9b3 --- /dev/null +++ b/app/Jobs/InternalPipeline/NotificationEpochUpdatePipeline.php @@ -0,0 +1,71 @@ + + */ + public function middleware(): array + { + return [(new WithoutOverlapping('ip:notification-epoch-update'))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct() + { + // + } + + /** + * Execute the job. + */ + public function handle(): void + { + $rec = Notification::where('created_at', '>', now()->subMonths(6))->first(); + $id = 1; + if($rec) { + $id = $rec->id; + } + Cache::put(NotificationService::EPOCH_CACHE_KEY . '6', $id, 1209600); + } +} diff --git a/app/Jobs/MediaPipeline/MediaDeletePipeline.php b/app/Jobs/MediaPipeline/MediaDeletePipeline.php index 4db76c9c7..55df84948 100644 --- a/app/Jobs/MediaPipeline/MediaDeletePipeline.php +++ b/app/Jobs/MediaPipeline/MediaDeletePipeline.php @@ -10,8 +10,11 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Storage; +use App\Services\Media\MediaHlsService; +use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing; -class MediaDeletePipeline implements ShouldQueue +class MediaDeletePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; @@ -20,8 +23,34 @@ class MediaDeletePipeline implements ShouldQueue public $timeout = 300; public $tries = 3; public $maxExceptions = 1; + public $failOnTimeout = true; public $deleteWhenMissingModels = true; + /** + * The number of seconds after which the job's unique lock will be released. + * + * @var int + */ + public $uniqueFor = 3600; + + /** + * Get the unique ID for the job. + */ + public function uniqueId(): string + { + return 'media:purge-job:id-' . $this->media->id; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("media:purge-job:id-{$this->media->id}"))->shared()->dontRelease()]; + } + public function __construct(Media $media) { $this->media = $media; @@ -63,9 +92,17 @@ class MediaDeletePipeline implements ShouldQueue $disk->delete($thumb); } + if($media->hls_path != null) { + $files = MediaHlsService::allFiles($media); + if($files && count($files)) { + foreach($files as $file) { + $disk->delete($file); + } + } + } + $media->delete(); return 1; } - } diff --git a/app/Jobs/ProfilePipeline/DecrementPostCount.php b/app/Jobs/ProfilePipeline/DecrementPostCount.php index d6781d7a5..b463f1dda 100644 --- a/app/Jobs/ProfilePipeline/DecrementPostCount.php +++ b/app/Jobs/ProfilePipeline/DecrementPostCount.php @@ -43,15 +43,9 @@ class DecrementPostCount implements ShouldQueue return 1; } - if($profile->updated_at && $profile->updated_at->lt(now()->subDays(30))) { - $profile->status_count = Status::whereProfileId($id)->whereNull(['in_reply_to_id', 'reblog_of_id'])->count(); - $profile->save(); - AccountService::del($id); - } else { - $profile->status_count = $profile->status_count ? $profile->status_count - 1 : 0; - $profile->save(); - AccountService::del($id); - } + $profile->status_count = $profile->status_count ? $profile->status_count - 1 : 0; + $profile->save(); + AccountService::del($id); return 1; } diff --git a/app/Jobs/ProfilePipeline/IncrementPostCount.php b/app/Jobs/ProfilePipeline/IncrementPostCount.php index 9c7585e25..1a94f1e6c 100644 --- a/app/Jobs/ProfilePipeline/IncrementPostCount.php +++ b/app/Jobs/ProfilePipeline/IncrementPostCount.php @@ -8,16 +8,48 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing; use App\Profile; use App\Status; use App\Services\AccountService; -class IncrementPostCount implements ShouldQueue +class IncrementPostCount implements ShouldQueue, ShouldBeUniqueUntilProcessing { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $id; + public $timeout = 900; + public $tries = 3; + public $maxExceptions = 1; + public $failOnTimeout = true; + + /** + * The number of seconds after which the job's unique lock will be released. + * + * @var int + */ + public $uniqueFor = 3600; + + /** + * Get the unique ID for the job. + */ + public function uniqueId(): string + { + return 'propipe:ipc:' . $this->id; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("propipe:ipc:{$this->id}"))->shared()->dontRelease()]; + } + /** * Create a new job instance. * @@ -43,17 +75,11 @@ class IncrementPostCount implements ShouldQueue return 1; } - if($profile->updated_at && $profile->updated_at->lt(now()->subDays(30))) { - $profile->status_count = Status::whereProfileId($id)->whereNull(['in_reply_to_id', 'reblog_of_id'])->count(); - $profile->last_status_at = now(); - $profile->save(); - AccountService::del($id); - } else { - $profile->status_count = $profile->status_count + 1; - $profile->last_status_at = now(); - $profile->save(); - AccountService::del($id); - } + $profile->status_count = $profile->status_count + 1; + $profile->last_status_at = now(); + $profile->save(); + AccountService::del($id); + AccountService::get($id); return 1; } diff --git a/app/Jobs/ProfilePipeline/ProfilePurgeFollowersByDomain.php b/app/Jobs/ProfilePipeline/ProfilePurgeFollowersByDomain.php new file mode 100644 index 000000000..24fcdc832 --- /dev/null +++ b/app/Jobs/ProfilePipeline/ProfilePurgeFollowersByDomain.php @@ -0,0 +1,119 @@ +pid . ':d-' . $this->domain; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("followers:v1:purge-by-domain:{$this->pid}:d-{$this->domain}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct($pid, $domain) + { + $this->pid = $pid; + $this->domain = $domain; + } + + /** + * Execute the job. + */ + public function handle(): void + { + if ($this->batch()->cancelled()) { + return; + } + + $pid = $this->pid; + $domain = $this->domain; + + $query = 'SELECT f.* + FROM followers f + JOIN profiles p ON p.id = f.profile_id OR p.id = f.following_id + WHERE (f.profile_id = ? OR f.following_id = ?) + AND p.domain = ?;'; + $params = [$pid, $pid, $domain]; + + foreach(DB::cursor($query, $params) as $n) { + if(!$n || !$n->id) { + continue; + } + $follower = Follower::find($n->id); + if($follower->following_id == $pid && $follower->profile_id) { + FollowerService::remove($follower->profile_id, $pid, true); + $follower->delete(); + } else if ($follower->profile_id == $pid && $follower->following_id) { + FollowerService::remove($follower->following_id, $pid, true); + $follower->delete(); + } + } + + $profile = Profile::find($pid); + + $followerCount = DB::table('profiles') + ->join('followers', 'profiles.id', '=', 'followers.following_id') + ->where('followers.following_id', $pid) + ->count(); + + $followingCount = DB::table('profiles') + ->join('followers', 'profiles.id', '=', 'followers.following_id') + ->where('followers.profile_id', $pid) + ->count(); + + $profile->followers_count = $followerCount; + $profile->following_count = $followingCount; + $profile->save(); + + AccountService::del($profile->id); + } +} diff --git a/app/Jobs/ProfilePipeline/ProfilePurgeNotificationsByDomain.php b/app/Jobs/ProfilePipeline/ProfilePurgeNotificationsByDomain.php new file mode 100644 index 000000000..ea5a45e4a --- /dev/null +++ b/app/Jobs/ProfilePipeline/ProfilePurgeNotificationsByDomain.php @@ -0,0 +1,91 @@ +pid . ':d-' . $this->domain; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("notify:v1:purge-by-domain:{$this->pid}:d-{$this->domain}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct($pid, $domain) + { + $this->pid = $pid; + $this->domain = $domain; + } + + /** + * Execute the job. + */ + public function handle(): void + { + if ($this->batch()->cancelled()) { + return; + } + + $pid = $this->pid; + $domain = $this->domain; + + $query = 'SELECT notifications.* + FROM profiles + JOIN notifications on profiles.id = notifications.actor_id + WHERE notifications.profile_id = ? + AND profiles.domain = ?'; + $params = [$pid, $domain]; + + foreach(DB::cursor($query, $params) as $n) { + if(!$n || !$n->id) { + continue; + } + Notification::where('id', $n->id)->delete(); + NotificationService::del($pid, $n->id); + } + } +} diff --git a/app/Jobs/SharePipeline/SharePipeline.php b/app/Jobs/SharePipeline/SharePipeline.php index ae184957e..4eca4e1ab 100644 --- a/app/Jobs/SharePipeline/SharePipeline.php +++ b/app/Jobs/SharePipeline/SharePipeline.php @@ -17,6 +17,7 @@ use GuzzleHttp\{Pool, Client, Promise}; use App\Util\ActivityPub\HttpSignature; use App\Services\ReblogService; use App\Services\StatusService; +use App\Jobs\HomeFeedPipeline\FeedInsertPipeline; class SharePipeline implements ShouldQueue { @@ -82,6 +83,8 @@ class SharePipeline implements ShouldQueue ] ); + FeedInsertPipeline::dispatch($status->id, $status->profile_id)->onQueue('feed'); + return $this->remoteAnnounceDeliver(); } diff --git a/app/Jobs/SharePipeline/UndoSharePipeline.php b/app/Jobs/SharePipeline/UndoSharePipeline.php index 3850a4752..1435688d9 100644 --- a/app/Jobs/SharePipeline/UndoSharePipeline.php +++ b/app/Jobs/SharePipeline/UndoSharePipeline.php @@ -17,6 +17,7 @@ use GuzzleHttp\{Pool, Client, Promise}; use App\Util\ActivityPub\HttpSignature; use App\Services\ReblogService; use App\Services\StatusService; +use App\Jobs\HomeFeedPipeline\FeedRemovePipeline; class UndoSharePipeline implements ShouldQueue { @@ -35,6 +36,8 @@ class UndoSharePipeline implements ShouldQueue $actor = $status->profile; $parent = Status::find($status->reblog_of_id); + FeedRemovePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed'); + if($parent) { $target = $parent->profile_id; ReblogService::removePostReblog($parent->profile_id, $status->id); diff --git a/app/Jobs/StatusPipeline/RemoteStatusDelete.php b/app/Jobs/StatusPipeline/RemoteStatusDelete.php index 19c17b54c..07a2f6236 100644 --- a/app/Jobs/StatusPipeline/RemoteStatusDelete.php +++ b/app/Jobs/StatusPipeline/RemoteStatusDelete.php @@ -21,9 +21,11 @@ use App\{ }; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Queue\Middleware\WithoutOverlapping; use League\Fractal; use Illuminate\Support\Str; use League\Fractal\Serializer\ArraySerializer; @@ -37,8 +39,10 @@ use App\Services\AccountService; use App\Services\CollectionService; use App\Services\StatusService; use App\Jobs\MediaPipeline\MediaDeletePipeline; +use App\Jobs\ProfilePipeline\DecrementPostCount; +use App\Services\NotificationService; -class RemoteStatusDelete implements ShouldQueue +class RemoteStatusDelete implements ShouldQueue, ShouldBeUniqueUntilProcessing { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; @@ -51,9 +55,35 @@ class RemoteStatusDelete implements ShouldQueue */ public $deleteWhenMissingModels = true; - public $timeout = 90; - public $tries = 2; - public $maxExceptions = 1; + public $tries = 3; + public $maxExceptions = 3; + public $timeout = 180; + public $failOnTimeout = true; + + /** + * The number of seconds after which the job's unique lock will be released. + * + * @var int + */ + public $uniqueFor = 3600; + + /** + * Get the unique ID for the job. + */ + public function uniqueId(): string + { + return 'status:remote:delete:' . $this->status->id; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("status-remote-delete-{$this->status->id}"))->shared()->dontRelease()]; + } /** * Create a new job instance. @@ -62,7 +92,7 @@ class RemoteStatusDelete implements ShouldQueue */ public function __construct(Status $status) { - $this->status = $status; + $this->status = $status->withoutRelations(); } /** @@ -77,14 +107,10 @@ class RemoteStatusDelete implements ShouldQueue if($status->deleted_at) { return; } - $profile = $this->status->profile; StatusService::del($status->id, true); - if($profile->status_count && $profile->status_count > 0) { - $profile->status_count = $profile->status_count - 1; - $profile->save(); - } + DecrementPostCount::dispatch($status->profile_id)->onQueue('inbox'); return $this->unlinkRemoveMedia($status); } @@ -112,14 +138,34 @@ class RemoteStatusDelete implements ShouldQueue CollectionService::removeItem($col->collection_id, $col->object_id); $col->delete(); }); - DirectMessage::whereStatusId($status->id)->delete(); + $dms = DirectMessage::whereStatusId($status->id)->get(); + foreach($dms as $dm) { + $not = Notification::whereItemType('App\DirectMessage') + ->whereItemId($dm->id) + ->first(); + if($not) { + NotificationService::del($not->profile_id, $not->id); + $not->forceDeleteQuietly(); + } + $dm->delete(); + } Like::whereStatusId($status->id)->forceDelete(); Media::whereStatusId($status->id) ->get() ->each(function($media) { MediaDeletePipeline::dispatch($media)->onQueue('mmo'); }); - MediaTag::where('status_id', $status->id)->delete(); + $mediaTags = MediaTag::where('status_id', $status->id)->get(); + foreach($mediaTags as $mtag) { + $not = Notification::whereItemType('App\MediaTag') + ->whereItemId($mtag->id) + ->first(); + if($not) { + NotificationService::del($not->profile_id, $not->id); + $not->forceDeleteQuietly(); + } + $mtag->delete(); + } Mention::whereStatusId($status->id)->forceDelete(); Notification::whereItemType('App\Status') ->whereItemId($status->id) diff --git a/app/Jobs/StatusPipeline/StatusDelete.php b/app/Jobs/StatusPipeline/StatusDelete.php index 19c0ea68d..dbbfad5ac 100644 --- a/app/Jobs/StatusPipeline/StatusDelete.php +++ b/app/Jobs/StatusPipeline/StatusDelete.php @@ -35,6 +35,7 @@ use GuzzleHttp\Promise; use App\Util\ActivityPub\HttpSignature; use App\Services\CollectionService; use App\Services\StatusService; +use App\Services\NotificationService; use App\Jobs\MediaPipeline\MediaDeletePipeline; class StatusDelete implements ShouldQueue @@ -115,10 +116,30 @@ class StatusDelete implements ShouldQueue $col->delete(); }); - DirectMessage::whereStatusId($status->id)->delete(); + $dms = DirectMessage::whereStatusId($status->id)->get(); + foreach($dms as $dm) { + $not = Notification::whereItemType('App\DirectMessage') + ->whereItemId($dm->id) + ->first(); + if($not) { + NotificationService::del($not->profile_id, $not->id); + $not->forceDeleteQuietly(); + } + $dm->delete(); + } Like::whereStatusId($status->id)->delete(); - MediaTag::where('status_id', $status->id)->delete(); + $mediaTags = MediaTag::where('status_id', $status->id)->get(); + foreach($mediaTags as $mtag) { + $not = Notification::whereItemType('App\MediaTag') + ->whereItemId($mtag->id) + ->first(); + if($not) { + NotificationService::del($not->profile_id, $not->id); + $not->forceDeleteQuietly(); + } + $mtag->delete(); + } Mention::whereStatusId($status->id)->forceDelete(); Notification::whereItemType('App\Status') diff --git a/app/Jobs/StatusPipeline/StatusEntityLexer.php b/app/Jobs/StatusPipeline/StatusEntityLexer.php index d205f1e21..872594a96 100644 --- a/app/Jobs/StatusPipeline/StatusEntityLexer.php +++ b/app/Jobs/StatusPipeline/StatusEntityLexer.php @@ -19,168 +19,189 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use App\Services\StatusService; use App\Services\UserFilterService; +use App\Services\AdminShadowFilterService; +use App\Jobs\HomeFeedPipeline\FeedInsertPipeline; +use App\Jobs\HomeFeedPipeline\HashtagInsertFanoutPipeline; class StatusEntityLexer implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - protected $status; - protected $entities; - protected $autolink; + protected $status; + protected $entities; + protected $autolink; - /** - * Delete the job if its models no longer exist. - * - * @var bool - */ - public $deleteWhenMissingModels = true; + /** + * Delete the job if its models no longer exist. + * + * @var bool + */ + public $deleteWhenMissingModels = true; - /** - * Create a new job instance. - * - * @return void - */ - public function __construct(Status $status) - { - $this->status = $status; - } + /** + * Create a new job instance. + * + * @return void + */ + public function __construct(Status $status) + { + $this->status = $status; + } - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $profile = $this->status->profile; - $status = $this->status; + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $profile = $this->status->profile; + $status = $this->status; - if(in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) { - $profile->status_count = $profile->status_count + 1; - $profile->save(); - } + if(in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) { + $profile->status_count = $profile->status_count + 1; + $profile->save(); + } - if($profile->no_autolink == false) { - $this->parseEntities(); - } - } + if($profile->no_autolink == false) { + $this->parseEntities(); + } + } - public function parseEntities() - { - $this->extractEntities(); - } + public function parseEntities() + { + $this->extractEntities(); + } - public function extractEntities() - { - $this->entities = Extractor::create()->extract($this->status->caption); - $this->autolinkStatus(); - } + public function extractEntities() + { + $this->entities = Extractor::create()->extract($this->status->caption); + $this->autolinkStatus(); + } - public function autolinkStatus() - { - $this->autolink = Autolink::create()->autolink($this->status->caption); - $this->storeEntities(); - } + public function autolinkStatus() + { + $this->autolink = Autolink::create()->autolink($this->status->caption); + $this->storeEntities(); + } - public function storeEntities() - { - $this->storeHashtags(); - DB::transaction(function () { - $status = $this->status; - $status->rendered = nl2br($this->autolink); - $status->save(); - }); - } + public function storeEntities() + { + $this->storeHashtags(); + DB::transaction(function () { + $status = $this->status; + $status->rendered = nl2br($this->autolink); + $status->save(); + }); + } - public function storeHashtags() - { - $tags = array_unique($this->entities['hashtags']); - $status = $this->status; + public function storeHashtags() + { + $tags = array_unique($this->entities['hashtags']); + $status = $this->status; - foreach ($tags as $tag) { - if(mb_strlen($tag) > 124) { - continue; - } - DB::transaction(function () use ($status, $tag) { - $slug = str_slug($tag, '-', false); - $hashtag = Hashtag::where('slug', $slug)->first(); - if (!$hashtag) { - $hashtag = Hashtag::create( - ['name' => $tag, 'slug' => $slug] - ); - } + foreach ($tags as $tag) { + if(mb_strlen($tag) > 124) { + continue; + } + DB::transaction(function () use ($status, $tag) { + $slug = str_slug($tag, '-', false); - StatusHashtag::firstOrCreate( - [ - 'status_id' => $status->id, - 'hashtag_id' => $hashtag->id, - 'profile_id' => $status->profile_id, - 'status_visibility' => $status->visibility, - ] - ); - }); - } - $this->storeMentions(); - } + $hashtag = Hashtag::firstOrCreate([ + 'slug' => $slug + ], [ + 'name' => $tag + ]); - public function storeMentions() - { - $mentions = array_unique($this->entities['mentions']); - $status = $this->status; + StatusHashtag::firstOrCreate( + [ + 'status_id' => $status->id, + 'hashtag_id' => $hashtag->id, + 'profile_id' => $status->profile_id, + 'status_visibility' => $status->visibility, + ] + ); + }); + } + $this->storeMentions(); + } - foreach ($mentions as $mention) { - $mentioned = Profile::whereUsername($mention)->first(); + public function storeMentions() + { + $mentions = array_unique($this->entities['mentions']); + $status = $this->status; - if (empty($mentioned) || !isset($mentioned->id)) { - continue; - } + foreach ($mentions as $mention) { + $mentioned = Profile::whereUsername($mention)->first(); + + if (empty($mentioned) || !isset($mentioned->id)) { + continue; + } $blocks = UserFilterService::blocks($mentioned->id); if($blocks && in_array($status->profile_id, $blocks)) { continue; } - DB::transaction(function () use ($status, $mentioned) { - $m = new Mention(); - $m->status_id = $status->id; - $m->profile_id = $mentioned->id; - $m->save(); + 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); - }); - } - $this->deliver(); - } + MentionPipeline::dispatch($status, $m); + }); + } + $this->fanout(); + } - public function deliver() - { - $status = $this->status; - $types = [ - 'photo', - 'photo:album', - 'video', - 'video:album', - 'photo:video:album' - ]; + public function fanout() + { + $status = $this->status; + StatusService::refresh($status->id); - if(config_cache('pixelfed.bouncer.enabled')) { - Bouncer::get($status); - } + if(config('exp.cached_home_timeline')) { + if( $status->in_reply_to_id === null && + in_array($status->scope, ['public', 'unlisted', 'private']) + ) { + FeedInsertPipeline::dispatch($status->id, $status->profile_id)->onQueue('feed'); + } + } + $this->deliver(); + } - Cache::forget('pf:atom:user-feed:by-id:' . $status->profile_id); - $hideNsfw = config('instance.hide_nsfw_on_public_feeds'); - if( $status->uri == null && - $status->scope == 'public' && - in_array($status->type, $types) && - $status->in_reply_to_id === null && - $status->reblog_of_id === null && - ($hideNsfw ? $status->is_nsfw == false : true) - ) { - PublicTimelineService::add($status->id); - } + public function deliver() + { + $status = $this->status; + $types = [ + 'photo', + 'photo:album', + 'video', + 'video:album', + 'photo:video:album' + ]; - if(config_cache('federation.activitypub.enabled') == true && config('app.env') == 'production') { - StatusActivityPubDeliver::dispatch($status); - } - } + if(config_cache('pixelfed.bouncer.enabled')) { + Bouncer::get($status); + } + + Cache::forget('pf:atom:user-feed:by-id:' . $status->profile_id); + $hideNsfw = config('instance.hide_nsfw_on_public_feeds'); + if( $status->uri == null && + $status->scope == 'public' && + in_array($status->type, $types) && + $status->in_reply_to_id === null && + $status->reblog_of_id === null && + ($hideNsfw ? $status->is_nsfw == false : true) + ) { + if(AdminShadowFilterService::canAddToPublicFeedByProfileId($status->profile_id)) { + PublicTimelineService::add($status->id); + } + } + + if(config_cache('federation.activitypub.enabled') == true && config('app.env') == 'production') { + StatusActivityPubDeliver::dispatch($status); + } + } } diff --git a/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php b/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php index 6cb11ddc6..23b8716c1 100644 --- a/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php +++ b/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php @@ -90,7 +90,7 @@ class StatusRemoteUpdatePipeline implements ShouldQueue ]); $nm->each(function($n, $key) use($status) { - $res = Http::retry(3, 100, throw: false)->head($n['url']); + $res = Http::withOptions(['allow_redirects' => false])->retry(3, 100, throw: false)->head($n['url']); if(!$res->successful()) { return; diff --git a/app/Jobs/StatusPipeline/StatusTagsPipeline.php b/app/Jobs/StatusPipeline/StatusTagsPipeline.php index a72e6d50e..003196e0d 100644 --- a/app/Jobs/StatusPipeline/StatusTagsPipeline.php +++ b/app/Jobs/StatusPipeline/StatusTagsPipeline.php @@ -20,113 +20,119 @@ use App\Util\ActivityPub\Helpers; class StatusTagsPipeline implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - protected $activity; - protected $status; + protected $activity; + protected $status; - /** - * Create a new job instance. - * - * @return void - */ - public function __construct($activity, $status) - { - $this->activity = $activity; - $this->status = $status; - } + /** + * Create a new job instance. + * + * @return void + */ + public function __construct($activity, $status) + { + $this->activity = $activity; + $this->status = $status; + } - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $res = $this->activity; - $status = $this->status; - $tags = collect($res['tag']); + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $res = $this->activity; + $status = $this->status; - // Emoji - $tags->filter(function($tag) { - return $tag && isset($tag['id'], $tag['icon'], $tag['name'], $tag['type']) && $tag['type'] == 'Emoji'; - }) - ->map(function($tag) { - CustomEmojiService::import($tag['id'], $this->status->id); - }); + if(isset($res['tag']['type'], $res['tag']['name'])) { + $res['tag'] = [$res['tag']]; + } - // Hashtags - $tags->filter(function($tag) { - return $tag && $tag['type'] == 'Hashtag' && isset($tag['href'], $tag['name']); - }) - ->map(function($tag) use($status) { - $name = substr($tag['name'], 0, 1) == '#' ? - substr($tag['name'], 1) : $tag['name']; + $tags = collect($res['tag']); - $banned = TrendingHashtagService::getBannedHashtagNames(); + // Emoji + $tags->filter(function($tag) { + return $tag && isset($tag['id'], $tag['icon'], $tag['name'], $tag['type']) && $tag['type'] == 'Emoji'; + }) + ->map(function($tag) { + CustomEmojiService::import($tag['id'], $this->status->id); + }); - if(count($banned)) { + // Hashtags + $tags->filter(function($tag) { + return $tag && $tag['type'] == 'Hashtag' && isset($tag['href'], $tag['name']); + }) + ->map(function($tag) use($status) { + $name = substr($tag['name'], 0, 1) == '#' ? + substr($tag['name'], 1) : $tag['name']; + + $banned = TrendingHashtagService::getBannedHashtagNames(); + + if(count($banned)) { if(in_array(strtolower($name), array_map('strtolower', $banned))) { - return; + return; } } if(config('database.default') === 'pgsql') { - $hashtag = Hashtag::where('name', 'ilike', $name) - ->orWhere('slug', 'ilike', str_slug($name)) - ->first(); + $hashtag = Hashtag::where('name', 'ilike', $name) + ->orWhere('slug', 'ilike', str_slug($name, '-', false)) + ->first(); - if(!$hashtag) { - $hashtag = new Hashtag; - $hashtag->name = $name; - $hashtag->slug = str_slug($name); - $hashtag->save(); - } + if(!$hashtag) { + $hashtag = Hashtag::updateOrCreate([ + 'slug' => str_slug($name, '-', false), + 'name' => $name + ]); + } } else { - $hashtag = Hashtag::firstOrCreate([ - 'slug' => str_slug($name) - ], [ - 'name' => $name - ]); + $hashtag = Hashtag::updateOrCreate([ + 'slug' => str_slug($name, '-', false), + 'name' => $name + ]); } - StatusHashtag::firstOrCreate([ - 'status_id' => $status->id, - 'hashtag_id' => $hashtag->id, - 'profile_id' => $status->profile_id, - 'status_visibility' => $status->scope - ]); - }); + StatusHashtag::firstOrCreate([ + 'status_id' => $status->id, + 'hashtag_id' => $hashtag->id, + 'profile_id' => $status->profile_id, + 'status_visibility' => $status->scope + ]); + }); - // Mentions - $tags->filter(function($tag) { - return $tag && - $tag['type'] == 'Mention' && - isset($tag['href']) && - substr($tag['href'], 0, 8) === 'https://'; - }) - ->map(function($tag) use($status) { - if(Helpers::validateLocalUrl($tag['href'])) { - $parts = explode('/', $tag['href']); - if(!$parts) { - return; - } - $pid = AccountService::usernameToId(end($parts)); - if(!$pid) { - return; - } - } else { - $acct = Helpers::profileFetch($tag['href']); - if(!$acct) { - return; - } - $pid = $acct->id; - } - $mention = new Mention; - $mention->status_id = $status->id; - $mention->profile_id = $pid; - $mention->save(); - MentionPipeline::dispatch($status, $mention); - }); - } + // Mentions + $tags->filter(function($tag) { + return $tag && + $tag['type'] == 'Mention' && + isset($tag['href']) && + substr($tag['href'], 0, 8) === 'https://'; + }) + ->map(function($tag) use($status) { + if(Helpers::validateLocalUrl($tag['href'])) { + $parts = explode('/', $tag['href']); + if(!$parts) { + return; + } + $pid = AccountService::usernameToId(end($parts)); + if(!$pid) { + return; + } + } else { + $acct = Helpers::profileFetch($tag['href']); + if(!$acct) { + return; + } + $pid = $acct->id; + } + $mention = new Mention; + $mention->status_id = $status->id; + $mention->profile_id = $pid; + $mention->save(); + MentionPipeline::dispatch($status, $mention); + }); + + StatusService::refresh($status->id); + } } diff --git a/app/Jobs/VideoPipeline/VideoHlsPipeline.php b/app/Jobs/VideoPipeline/VideoHlsPipeline.php new file mode 100644 index 000000000..378723414 --- /dev/null +++ b/app/Jobs/VideoPipeline/VideoHlsPipeline.php @@ -0,0 +1,109 @@ +media->id; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("media:video-hls:id-{$this->media->id}"))->shared()->dontRelease()]; + } + + /** + * Create a new job instance. + */ + public function __construct($media) + { + $this->media = $media; + } + + /** + * Execute the job. + */ + public function handle(): void + { + $depCheck = Cache::rememberForever('video-pipeline:hls:depcheck', function() { + $bin = config('laravel-ffmpeg.ffmpeg.binaries'); + $output = shell_exec($bin . ' -version'); + if($output && preg_match('/ffmpeg version ([^\s]+)/', $output, $matches)) { + $version = $matches[1]; + return (version_compare($version, config('laravel-ffmpeg.min_hls_version')) >= 0) ? 'ok' : false; + } else { + return false; + } + }); + + if(!$depCheck || $depCheck !== 'ok') { + return; + } + + $media = $this->media; + + $bitrate = (new X264)->setKiloBitrate(config('media.hls.bitrate') ?? 1000); + + $mp4 = $media->media_path; + $man = str_replace('.mp4', '.m3u8', $mp4); + + FFMpeg::fromDisk('local') + ->open($mp4) + ->exportForHLS() + ->setSegmentLength(16) + ->setKeyFrameInterval(48) + ->addFormat($bitrate) + ->save($man); + + $media->hls_path = $man; + $media->hls_transcoded_at = now(); + $media->save(); + + MediaService::del($media->status_id); + usleep(50000); + StatusService::del($media->status_id); + + return; + } +} diff --git a/app/Jobs/VideoPipeline/VideoThumbnail.php b/app/Jobs/VideoPipeline/VideoThumbnail.php index fed61e4fc..ebcb4cf7e 100644 --- a/app/Jobs/VideoPipeline/VideoThumbnail.php +++ b/app/Jobs/VideoPipeline/VideoThumbnail.php @@ -16,13 +16,46 @@ use App\Jobs\MediaPipeline\MediaStoragePipeline; use App\Util\Media\Blurhash; use App\Services\MediaService; use App\Services\StatusService; +use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing; -class VideoThumbnail implements ShouldQueue +class VideoThumbnail implements ShouldQueue, ShouldBeUniqueUntilProcessing { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $media; + public $timeout = 900; + public $tries = 3; + public $maxExceptions = 1; + public $failOnTimeout = true; + public $deleteWhenMissingModels = true; + + /** + * The number of seconds after which the job's unique lock will be released. + * + * @var int + */ + public $uniqueFor = 3600; + + /** + * Get the unique ID for the job. + */ + public function uniqueId(): string + { + return 'media:video-thumb:id-' . $this->media->id; + } + + /** + * Get the middleware the job should pass through. + * + * @return array + */ + public function middleware(): array + { + return [(new WithoutOverlapping("media:video-thumb:id-{$this->media->id}"))->shared()->dontRelease()]; + } + /** * Create a new job instance. * @@ -54,7 +87,7 @@ class VideoThumbnail implements ShouldQueue $path[$i] = $t; $save = implode('/', $path); $video = FFMpeg::open($base) - ->getFrameFromSeconds(0) + ->getFrameFromSeconds(1) ->export() ->toDisk('local') ->save($save); @@ -68,6 +101,9 @@ class VideoThumbnail implements ShouldQueue $media->save(); } + if(config('media.hls.enabled')) { + VideoHlsPipeline::dispatch($media)->onQueue('mmo'); + } } catch (Exception $e) { } diff --git a/app/Models/AdminShadowFilter.php b/app/Models/AdminShadowFilter.php new file mode 100644 index 000000000..8a163feeb --- /dev/null +++ b/app/Models/AdminShadowFilter.php @@ -0,0 +1,33 @@ + 'datetime' + ]; + + public function account() + { + if($this->item_type === 'App\Profile') { + return AccountService::get($this->item_id, true); + } + + return; + } + + public function profile() + { + return $this->belongsTo(Profile::class, 'item_id'); + } +} diff --git a/app/Models/DefaultDomainBlock.php b/app/Models/DefaultDomainBlock.php new file mode 100644 index 000000000..d90816a32 --- /dev/null +++ b/app/Models/DefaultDomainBlock.php @@ -0,0 +1,13 @@ + 'array', + 'last_calculated_at' => 'datetime', + 'last_moderated_at' => 'datetime', + ]; +} diff --git a/app/Models/UserDomainBlock.php b/app/Models/UserDomainBlock.php new file mode 100644 index 000000000..900e026f2 --- /dev/null +++ b/app/Models/UserDomainBlock.php @@ -0,0 +1,21 @@ +belongsTo(Profile::class, 'profile_id'); + } +} diff --git a/app/Observers/FollowerObserver.php b/app/Observers/FollowerObserver.php index f230bb79f..bc85e48a0 100644 --- a/app/Observers/FollowerObserver.php +++ b/app/Observers/FollowerObserver.php @@ -5,6 +5,8 @@ namespace App\Observers; use App\Follower; use App\Services\FollowerService; use Cache; +use App\Jobs\HomeFeedPipeline\FeedFollowPipeline; +use App\Jobs\HomeFeedPipeline\FeedUnfollowPipeline; class FollowerObserver { @@ -21,6 +23,7 @@ class FollowerObserver } FollowerService::add($follower->profile_id, $follower->following_id); + FeedFollowPipeline::dispatch($follower->profile_id, $follower->following_id)->onQueue('follow'); } /** diff --git a/app/Observers/HashtagFollowObserver.php b/app/Observers/HashtagFollowObserver.php new file mode 100644 index 000000000..56158c21a --- /dev/null +++ b/app/Observers/HashtagFollowObserver.php @@ -0,0 +1,51 @@ +hashtag_id, $hashtagFollow->profile_id); + } + + /** + * Handle the HashtagFollow "updated" event. + */ + public function updated(HashtagFollow $hashtagFollow): void + { + // + } + + /** + * Handle the HashtagFollow "deleting" event. + */ + public function deleting(HashtagFollow $hashtagFollow): void + { + HashtagFollowService::unfollow($hashtagFollow->hashtag_id, $hashtagFollow->profile_id); + } + + /** + * Handle the HashtagFollow "restored" event. + */ + public function restored(HashtagFollow $hashtagFollow): void + { + // + } + + /** + * Handle the HashtagFollow "force deleted" event. + */ + public function forceDeleted(HashtagFollow $hashtagFollow): void + { + HashtagFollowService::unfollow($hashtagFollow->hashtag_id, $hashtagFollow->profile_id); + } +} diff --git a/app/Observers/StatusHashtagObserver.php b/app/Observers/StatusHashtagObserver.php index fa38ea3c3..cac223d51 100644 --- a/app/Observers/StatusHashtagObserver.php +++ b/app/Observers/StatusHashtagObserver.php @@ -5,32 +5,31 @@ namespace App\Observers; use DB; use App\StatusHashtag; use App\Services\StatusHashtagService; +use App\Jobs\HomeFeedPipeline\HashtagInsertFanoutPipeline; +use App\Jobs\HomeFeedPipeline\HashtagRemoveFanoutPipeline; +use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit; -class StatusHashtagObserver +class StatusHashtagObserver implements ShouldHandleEventsAfterCommit { - /** - * Handle events after all transactions are committed. - * - * @var bool - */ - public $afterCommit = true; - /** * Handle the notification "created" event. * - * @param \App\Notification $notification + * @param \App\StatusHashtag $hashtag * @return void */ public function created(StatusHashtag $hashtag) { StatusHashtagService::set($hashtag->hashtag_id, $hashtag->status_id); DB::table('hashtags')->where('id', $hashtag->hashtag_id)->increment('cached_count'); + if($hashtag->status_visibility && $hashtag->status_visibility === 'public') { + HashtagInsertFanoutPipeline::dispatch($hashtag)->onQueue('feed'); + } } /** * Handle the notification "updated" event. * - * @param \App\Notification $notification + * @param \App\StatusHashtag $hashtag * @return void */ public function updated(StatusHashtag $hashtag) @@ -41,19 +40,22 @@ class StatusHashtagObserver /** * Handle the notification "deleted" event. * - * @param \App\Notification $notification + * @param \App\StatusHashtag $hashtag * @return void */ public function deleted(StatusHashtag $hashtag) { StatusHashtagService::del($hashtag->hashtag_id, $hashtag->status_id); DB::table('hashtags')->where('id', $hashtag->hashtag_id)->decrement('cached_count'); + if($hashtag->status_visibility && $hashtag->status_visibility === 'public') { + HashtagRemoveFanoutPipeline::dispatch($hashtag->status_id, $hashtag->hashtag_id)->onQueue('feed'); + } } /** * Handle the notification "restored" event. * - * @param \App\Notification $notification + * @param \App\StatusHashtag $hashtag * @return void */ public function restored(StatusHashtag $hashtag) @@ -64,7 +66,7 @@ class StatusHashtagObserver /** * Handle the notification "force deleted" event. * - * @param \App\Notification $notification + * @param \App\StatusHashtag $hashtag * @return void */ public function forceDeleted(StatusHashtag $hashtag) diff --git a/app/Observers/StatusObserver.php b/app/Observers/StatusObserver.php index e58997165..d78585175 100644 --- a/app/Observers/StatusObserver.php +++ b/app/Observers/StatusObserver.php @@ -7,6 +7,8 @@ use App\Services\ProfileStatusService; use Cache; use App\Models\ImportPost; use App\Services\ImportService; +use App\Jobs\HomeFeedPipeline\FeedRemovePipeline; +use App\Jobs\HomeFeedPipeline\FeedRemoveRemotePipeline; class StatusObserver { @@ -63,6 +65,14 @@ class StatusObserver ImportPost::whereProfileId($status->profile_id)->whereStatusId($status->id)->delete(); ImportService::clearImportedFiles($status->profile_id); } + + if(config('exp.cached_home_timeline')) { + if($status->uri) { + FeedRemoveRemotePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed'); + } else { + FeedRemovePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed'); + } + } } /** diff --git a/app/Observers/UserFilterObserver.php b/app/Observers/UserFilterObserver.php index 8e149e7d9..75867e64b 100644 --- a/app/Observers/UserFilterObserver.php +++ b/app/Observers/UserFilterObserver.php @@ -4,6 +4,8 @@ namespace App\Observers; use App\UserFilter; use App\Services\UserFilterService; +use App\Jobs\HomeFeedPipeline\FeedFollowPipeline; +use App\Jobs\HomeFeedPipeline\FeedUnfollowPipeline; class UserFilterObserver { @@ -78,10 +80,12 @@ class UserFilterObserver switch ($userFilter->filter_type) { case 'mute': UserFilterService::mute($userFilter->user_id, $userFilter->filterable_id); + FeedUnfollowPipeline::dispatch($userFilter->user_id, $userFilter->filterable_id)->onQueue('feed'); break; case 'block': UserFilterService::block($userFilter->user_id, $userFilter->filterable_id); + FeedUnfollowPipeline::dispatch($userFilter->user_id, $userFilter->filterable_id)->onQueue('feed'); break; } } @@ -96,10 +100,12 @@ class UserFilterObserver switch ($userFilter->filter_type) { case 'mute': UserFilterService::unmute($userFilter->user_id, $userFilter->filterable_id); + FeedFollowPipeline::dispatch($userFilter->user_id, $userFilter->filterable_id)->onQueue('feed'); break; case 'block': UserFilterService::unblock($userFilter->user_id, $userFilter->filterable_id); + FeedFollowPipeline::dispatch($userFilter->user_id, $userFilter->filterable_id)->onQueue('feed'); break; } } diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index ec4ef9f34..d587bd7e8 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -7,90 +7,52 @@ use App\Follower; use App\Profile; use App\User; use App\UserSetting; +use App\Services\UserFilterService; +use App\Models\DefaultDomainBlock; +use App\Models\UserDomainBlock; use App\Jobs\FollowPipeline\FollowPipeline; use DB; use App\Services\FollowerService; class UserObserver { - /** - * Listen to the User created event. - * - * @param \App\User $user - * - * @return void - */ - public function saved(User $user) - { - if($user->status == 'deleted') { - return; - } + /** + * Handle the notification "created" event. + * + * @param \App\User $user + * @return void + */ + public function created(User $user): void + { + $this->handleUser($user); + } - if(Profile::whereUsername($user->username)->exists()) { - return; + /** + * Listen to the User saved event. + * + * @param \App\User $user + * + * @return void + */ + public function saved(User $user) + { + $this->handleUser($user); + } + + /** + * Listen to the User updated event. + * + * @param \App\User $user + * + * @return void + */ + public function updated(User $user): void + { + $this->handleUser($user); + if($user->profile) { + $this->applyDefaultDomainBlocks($user); } - - if (empty($user->profile)) { - $profile = DB::transaction(function() use($user) { - $profile = new Profile(); - $profile->user_id = $user->id; - $profile->username = $user->username; - $profile->name = $user->name; - $pkiConfig = [ - 'digest_alg' => 'sha512', - 'private_key_bits' => 2048, - 'private_key_type' => OPENSSL_KEYTYPE_RSA, - ]; - $pki = openssl_pkey_new($pkiConfig); - openssl_pkey_export($pki, $pki_private); - $pki_public = openssl_pkey_get_details($pki); - $pki_public = $pki_public['key']; - - $profile->private_key = $pki_private; - $profile->public_key = $pki_public; - $profile->save(); - return $profile; - }); - - DB::transaction(function() use($user, $profile) { - $user = User::findOrFail($user->id); - $user->profile_id = $profile->id; - $user->save(); - - CreateAvatar::dispatch($profile); - }); - - if(config_cache('account.autofollow') == true) { - $names = config_cache('account.autofollow_usernames'); - $names = explode(',', $names); - - if(!$names || !last($names)) { - return; - } - - $profiles = Profile::whereIn('username', $names)->get(); - - if($profiles) { - foreach($profiles as $p) { - $follower = new Follower; - $follower->profile_id = $profile->id; - $follower->following_id = $p->id; - $follower->save(); - - FollowPipeline::dispatch($follower); - } - } - } - } - - if (empty($user->settings)) { - DB::transaction(function() use($user) { - UserSetting::firstOrCreate([ - 'user_id' => $user->id - ]); - }); - } - } + } /** * Handle the user "deleted" event. @@ -102,4 +64,97 @@ class UserObserver { FollowerService::delCache($user->profile_id); } + + protected function handleUser($user) + { + if(in_array($user->status, ['deleted', 'delete'])) { + return; + } + + if(Profile::whereUsername($user->username)->exists()) { + return; + } + + if (empty($user->profile)) { + $profile = DB::transaction(function() use($user) { + $profile = new Profile(); + $profile->user_id = $user->id; + $profile->username = $user->username; + $profile->name = $user->name; + $pkiConfig = [ + 'digest_alg' => 'sha512', + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ]; + $pki = openssl_pkey_new($pkiConfig); + openssl_pkey_export($pki, $pki_private); + $pki_public = openssl_pkey_get_details($pki); + $pki_public = $pki_public['key']; + + $profile->private_key = $pki_private; + $profile->public_key = $pki_public; + $profile->save(); + $this->applyDefaultDomainBlocks($user); + return $profile; + }); + + + DB::transaction(function() use($user, $profile) { + $user = User::findOrFail($user->id); + $user->profile_id = $profile->id; + $user->save(); + + CreateAvatar::dispatch($profile); + }); + + if(config_cache('account.autofollow') == true) { + $names = config_cache('account.autofollow_usernames'); + $names = explode(',', $names); + + if(!$names || !last($names)) { + return; + } + + $profiles = Profile::whereIn('username', $names)->get(); + + if($profiles) { + foreach($profiles as $p) { + $follower = new Follower; + $follower->profile_id = $profile->id; + $follower->following_id = $p->id; + $follower->save(); + + FollowPipeline::dispatch($follower); + } + } + } + } + + if (empty($user->settings)) { + DB::transaction(function() use($user) { + UserSetting::firstOrCreate([ + 'user_id' => $user->id + ]); + }); + } + } + + protected function applyDefaultDomainBlocks($user) + { + if($user->profile_id == null) { + return; + } + $defaultDomainBlocks = DefaultDomainBlock::pluck('domain')->toArray(); + + if(!$defaultDomainBlocks || !count($defaultDomainBlocks)) { + return; + } + + foreach($defaultDomainBlocks as $domain) { + UserDomainBlock::updateOrCreate([ + 'profile_id' => $user->profile_id, + 'domain' => strtolower(trim($domain)) + ]); + } + } } diff --git a/app/Services/AccountService.php b/app/Services/AccountService.php index ea64855ce..98e878845 100644 --- a/app/Services/AccountService.php +++ b/app/Services/AccountService.php @@ -7,6 +7,7 @@ use App\Profile; use App\Status; use App\User; use App\UserSetting; +use App\Models\UserDomainBlock; use App\Transformer\Api\AccountTransformer; use League\Fractal; use League\Fractal\Serializer\ArraySerializer; @@ -15,209 +16,232 @@ use Illuminate\Support\Str; class AccountService { - const CACHE_KEY = 'pf:services:account:'; + const CACHE_KEY = 'pf:services:account:'; - public static function get($id, $softFail = false) - { - $res = Cache::remember(self::CACHE_KEY . $id, 43200, function() use($id) { - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $profile = Profile::find($id); - if(!$profile || $profile->status === 'delete') { - return null; - } - $resource = new Fractal\Resource\Item($profile, new AccountTransformer()); - return $fractal->createData($resource)->toArray(); - }); + public static function get($id, $softFail = false) + { + $res = Cache::remember(self::CACHE_KEY . $id, 43200, function() use($id) { + $fractal = new Fractal\Manager(); + $fractal->setSerializer(new ArraySerializer()); + $profile = Profile::find($id); + if(!$profile || $profile->status === 'delete') { + return null; + } + $resource = new Fractal\Resource\Item($profile, new AccountTransformer()); + return $fractal->createData($resource)->toArray(); + }); - if(!$res) { - return $softFail ? null : abort(404); - } - return $res; - } + if(!$res) { + return $softFail ? null : abort(404); + } + return $res; + } - public static function getMastodon($id, $softFail = false) - { - $account = self::get($id, $softFail); - if(!$account) { - return null; - } + public static function getMastodon($id, $softFail = false) + { + $account = self::get($id, $softFail); + if(!$account) { + return null; + } - if(config('exp.emc') == false) { - return $account; - } + if(config('exp.emc') == false) { + return $account; + } - unset( - $account['header_bg'], - $account['is_admin'], - $account['last_fetched_at'], - $account['local'], - $account['location'], - $account['note_text'], - $account['pronouns'], - $account['website'] - ); + unset( + $account['header_bg'], + $account['is_admin'], + $account['last_fetched_at'], + $account['local'], + $account['location'], + $account['note_text'], + $account['pronouns'], + $account['website'] + ); - $account['avatar_static'] = $account['avatar']; - $account['bot'] = false; - $account['emojis'] = []; - $account['fields'] = []; - $account['header'] = url('/storage/headers/missing.png'); - $account['header_static'] = url('/storage/headers/missing.png'); - $account['last_status_at'] = null; + $account['avatar_static'] = $account['avatar']; + $account['bot'] = false; + $account['emojis'] = []; + $account['fields'] = []; + $account['header'] = url('/storage/headers/missing.png'); + $account['header_static'] = url('/storage/headers/missing.png'); + $account['last_status_at'] = null; - return $account; - } + return $account; + } - public static function del($id) - { - Cache::forget('pf:activitypub:user-object:by-id:' . $id); - return Cache::forget(self::CACHE_KEY . $id); - } + public static function del($id) + { + Cache::forget('pf:activitypub:user-object:by-id:' . $id); + return Cache::forget(self::CACHE_KEY . $id); + } - public static function settings($id) - { - return Cache::remember('profile:compose:settings:' . $id, 604800, function() use($id) { - $settings = UserSetting::whereUserId($id)->first(); - if(!$settings) { - return self::defaultSettings(); - } - return collect($settings) - ->filter(function($item, $key) { - return in_array($key, array_keys(self::defaultSettings())) == true; - }) - ->map(function($item, $key) { - if($key == 'compose_settings') { - $cs = self::defaultSettings()['compose_settings']; - $ms = is_array($item) ? $item : []; - return array_merge($cs, $ms); - } + public static function settings($id) + { + return Cache::remember('profile:compose:settings:' . $id, 604800, function() use($id) { + $settings = UserSetting::whereUserId($id)->first(); + if(!$settings) { + return self::defaultSettings(); + } + return collect($settings) + ->filter(function($item, $key) { + return in_array($key, array_keys(self::defaultSettings())) == true; + }) + ->map(function($item, $key) { + if($key == 'compose_settings') { + $cs = self::defaultSettings()['compose_settings']; + $ms = is_array($item) ? $item : []; + return array_merge($cs, $ms); + } - if($key == 'other') { - $other = self::defaultSettings()['other']; - $mo = is_array($item) ? $item : []; - return array_merge($other, $mo); - } - return $item; - }); - }); - } + if($key == 'other') { + $other = self::defaultSettings()['other']; + $mo = is_array($item) ? $item : []; + return array_merge($other, $mo); + } + return $item; + }); + }); + } - public static function canEmbed($id) - { - return self::settings($id)['other']['disable_embeds'] == false; - } + public static function canEmbed($id) + { + return self::settings($id)['other']['disable_embeds'] == false; + } - public static function defaultSettings() - { - return [ - 'crawlable' => true, - 'public_dm' => false, - 'reduce_motion' => false, - 'high_contrast_mode' => false, - 'video_autoplay' => false, - 'show_profile_follower_count' => true, - 'show_profile_following_count' => true, - 'compose_settings' => [ - 'default_scope' => 'public', - 'default_license' => 1, - 'media_descriptions' => false - ], - 'other' => [ - 'advanced_atom' => false, - 'disable_embeds' => false, - 'mutual_mention_notifications' => false, - 'hide_collections' => false, - 'hide_like_counts' => false, - 'hide_groups' => false, - 'hide_stories' => false, - 'disable_cw' => false, - ] - ]; - } + public static function defaultSettings() + { + return [ + 'crawlable' => true, + 'public_dm' => false, + 'reduce_motion' => false, + 'high_contrast_mode' => false, + 'video_autoplay' => false, + 'show_profile_follower_count' => true, + 'show_profile_following_count' => true, + 'compose_settings' => [ + 'default_scope' => 'public', + 'default_license' => 1, + 'media_descriptions' => false + ], + 'other' => [ + 'advanced_atom' => false, + 'disable_embeds' => false, + 'mutual_mention_notifications' => false, + 'hide_collections' => false, + 'hide_like_counts' => false, + 'hide_groups' => false, + 'hide_stories' => false, + 'disable_cw' => false, + ] + ]; + } - public static function syncPostCount($id) - { - $profile = Profile::find($id); + public static function syncPostCount($id) + { + $profile = Profile::find($id); - if(!$profile) { - return false; - } + if(!$profile) { + return false; + } - $key = self::CACHE_KEY . 'pcs:' . $id; + $key = self::CACHE_KEY . 'pcs:' . $id; - if(Cache::has($key)) { - return; - } + if(Cache::has($key)) { + return; + } - $count = Status::whereProfileId($id) - ->whereNull('in_reply_to_id') - ->whereNull('reblog_of_id') - ->whereIn('scope', ['public', 'unlisted', 'private']) - ->count(); + $count = Status::whereProfileId($id) + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->whereIn('scope', ['public', 'unlisted', 'private']) + ->count(); - $profile->status_count = $count; - $profile->save(); + $profile->status_count = $count; + $profile->save(); - Cache::put($key, 1, 900); - return true; - } + Cache::put($key, 1, 900); + return true; + } - public static function usernameToId($username) - { - $key = self::CACHE_KEY . 'u2id:' . hash('sha256', $username); - return Cache::remember($key, 900, function() use($username) { - $s = Str::of($username); - if($s->contains('@') && !$s->startsWith('@')) { - $username = "@{$username}"; - } - $profile = DB::table('profiles') - ->whereUsername($username) - ->first(); - if(!$profile) { - return null; - } - return (string) $profile->id; - }); - } + public static function usernameToId($username) + { + $key = self::CACHE_KEY . 'u2id:' . hash('sha256', $username); + return Cache::remember($key, 14400, function() use($username) { + $s = Str::of($username); + if($s->contains('@') && !$s->startsWith('@')) { + $username = "@{$username}"; + } + $profile = DB::table('profiles') + ->whereUsername($username) + ->first(); + if(!$profile) { + return null; + } + return (string) $profile->id; + }); + } - public static function hiddenFollowers($id) - { - $account = self::get($id, true); - if(!$account || !isset($account['local']) || $account['local'] == false) { - return false; - } + public static function hiddenFollowers($id) + { + $account = self::get($id, true); + if(!$account || !isset($account['local']) || $account['local'] == false) { + return false; + } - return Cache::remember('pf:acct:settings:hidden-followers:' . $id, 43200, function() use($id) { - $user = User::whereProfileId($id)->first(); - if(!$user) { - return false; - } - $settings = UserSetting::whereUserId($user->id)->first(); - if($settings) { - return $settings->show_profile_follower_count == false; - } - return false; - }); - } + return Cache::remember('pf:acct:settings:hidden-followers:' . $id, 43200, function() use($id) { + $user = User::whereProfileId($id)->first(); + if(!$user) { + return false; + } + $settings = UserSetting::whereUserId($user->id)->first(); + if($settings) { + return $settings->show_profile_follower_count == false; + } + return false; + }); + } - public static function hiddenFollowing($id) - { - $account = self::get($id, true); - if(!$account || !isset($account['local']) || $account['local'] == false) { - return false; - } + public static function hiddenFollowing($id) + { + $account = self::get($id, true); + if(!$account || !isset($account['local']) || $account['local'] == false) { + return false; + } - return Cache::remember('pf:acct:settings:hidden-following:' . $id, 43200, function() use($id) { - $user = User::whereProfileId($id)->first(); - if(!$user) { - return false; - } - $settings = UserSetting::whereUserId($user->id)->first(); - if($settings) { - return $settings->show_profile_following_count == false; - } - return false; - }); - } + return Cache::remember('pf:acct:settings:hidden-following:' . $id, 43200, function() use($id) { + $user = User::whereProfileId($id)->first(); + if(!$user) { + return false; + } + $settings = UserSetting::whereUserId($user->id)->first(); + if($settings) { + return $settings->show_profile_following_count == false; + } + return false; + }); + } + + public static function setLastActive($id = false) + { + if(!$id) { return; } + $key = 'user:last_active_at:id:' . $id; + if(!Cache::has($key)) { + $user = User::find($id); + if(!$user) { return; } + $user->last_active_at = now(); + $user->save(); + Cache::put($key, 1, 14400); + } + return; + } + + public static function blocksDomain($pid, $domain = false) + { + if(!$domain) { + return; + } + + return UserDomainBlock::whereProfileId($pid)->whereDomain($domain)->exists(); + } } diff --git a/app/Services/ActivityPubFetchService.php b/app/Services/ActivityPubFetchService.php index 3d1980a11..cbf153ecb 100644 --- a/app/Services/ActivityPubFetchService.php +++ b/app/Services/ActivityPubFetchService.php @@ -28,7 +28,7 @@ class ActivityPubFetchService $headers['User-Agent'] = 'PixelFedBot/1.0.0 (Pixelfed/'.config('pixelfed.version').'; +'.config('app.url').')'; try { - $res = Http::withHeaders($headers) + $res = Http::withOptions(['allow_redirects' => false])->withHeaders($headers) ->timeout(30) ->connectTimeout(5) ->retry(3, 500) diff --git a/app/Services/AdminShadowFilterService.php b/app/Services/AdminShadowFilterService.php new file mode 100644 index 000000000..a5933508a --- /dev/null +++ b/app/Services/AdminShadowFilterService.php @@ -0,0 +1,51 @@ +whereActive(1) + ->where('hide_from_public_feeds', true) + ->pluck('item_id') + ->toArray(); + } + + public static function getHideFromPublicFeedsList($refresh = false) + { + $key = self::CACHE_KEY . 'list:hide_from_public_feeds'; + if($refresh) { + Cache::forget($key); + } + return Cache::remember($key, 86400, function() { + return AdminShadowFilter::whereItemType('App\Profile') + ->whereActive(1) + ->where('hide_from_public_feeds', true) + ->pluck('item_id') + ->toArray(); + }); + } + + public static function canAddToPublicFeedByProfileId($profileId) + { + return !in_array($profileId, self::getHideFromPublicFeedsList()); + } + + public static function refresh() + { + $keys = [ + self::CACHE_KEY . 'list:hide_from_public_feeds' + ]; + + foreach($keys as $key) { + Cache::forget($key); + } + } +} diff --git a/app/Services/AvatarService.php b/app/Services/AvatarService.php index 1c5e9e0c1..af578fdef 100644 --- a/app/Services/AvatarService.php +++ b/app/Services/AvatarService.php @@ -3,21 +3,125 @@ namespace App\Services; use Cache; +use Storage; +use Illuminate\Support\Str; +use App\Avatar; use App\Profile; +use App\Jobs\AvatarPipeline\AvatarStorageLargePurge; +use League\Flysystem\UnableToCheckDirectoryExistence; +use League\Flysystem\UnableToRetrieveMetadata; class AvatarService { - public static function get($profile_id) - { - $exists = Cache::get('avatar:' . $profile_id); - if($exists) { - return $exists; - } + public static function get($profile_id) + { + $exists = Cache::get('avatar:' . $profile_id); + if($exists) { + return $exists; + } - $profile = Profile::find($profile_id); - if(!$profile) { - return config('app.url') . '/storage/avatars/default.jpg'; - } - return $profile->avatarUrl(); - } + $profile = Profile::find($profile_id); + if(!$profile) { + return config('app.url') . '/storage/avatars/default.jpg'; + } + return $profile->avatarUrl(); + } + + public static function disk() + { + $storage = [ + 'cloud' => boolval(config_cache('pixelfed.cloud_storage')), + 'local' => boolval(config_cache('federation.avatars.store_local')) + ]; + + if(!$storage['cloud'] && !$storage['local']) { + return false; + } + + $driver = $storage['cloud'] == false ? 'local' : config('filesystems.cloud'); + $disk = Storage::disk($driver); + + return $disk; + } + + public static function storage(Avatar $avatar) + { + $disk = self::disk(); + + if(!$disk) { + return; + } + + $storage = [ + 'cloud' => boolval(config_cache('pixelfed.cloud_storage')), + 'local' => boolval(config_cache('federation.avatars.store_local')) + ]; + + $base = ($storage['cloud'] == false ? 'public/cache/' : 'cache/') . 'avatars/'; + + return $disk->allFiles($base . $avatar->profile_id); + } + + public static function cleanup($avatar, $confirm = false) + { + if(!$avatar || !$confirm) { + return; + } + + if($avatar->cdn_url == null) { + return; + } + + $storage = [ + 'cloud' => boolval(config_cache('pixelfed.cloud_storage')), + 'local' => boolval(config_cache('federation.avatars.store_local')) + ]; + + if(!$storage['cloud'] && !$storage['local']) { + return; + } + + $disk = self::disk(); + + if(!$disk) { + return; + } + + $base = ($storage['cloud'] == false ? 'public/cache/' : 'cache/') . 'avatars/'; + + try { + $exists = $disk->directoryExists($base . $avatar->profile_id); + } catch ( + UnableToRetrieveMetadata | + UnableToCheckDirectoryExistence | + Exception $e + ) { + return; + } + + if(!$exists) { + return; + } + + $files = collect($disk->allFiles($base . $avatar->profile_id)); + + if(!$files || !$files->count() || $files->count() === 1) { + return; + } + + if($files->count() > 5) { + AvatarStorageLargePurge::dispatch($avatar)->onQueue('mmo'); + return; + } + + $curFile = Str::of($avatar->cdn_url)->explode('/')->last(); + + $files = $files->filter(function($f) use($curFile) { + return !$curFile || !str_ends_with($f, $curFile); + })->each(function($name) use($disk) { + $disk->delete($name); + }); + + return; + } } diff --git a/app/Services/FollowerService.php b/app/Services/FollowerService.php index 9398fa53f..cec8f7068 100644 --- a/app/Services/FollowerService.php +++ b/app/Services/FollowerService.php @@ -6,206 +6,245 @@ use Illuminate\Support\Facades\Redis; use Cache; use DB; use App\{ - Follower, - Profile, - User + Follower, + Profile, + User }; use App\Jobs\FollowPipeline\FollowServiceWarmCache; class FollowerService { - const CACHE_KEY = 'pf:services:followers:'; - const FOLLOWERS_SYNC_KEY = 'pf:services:followers:sync-followers:'; - const FOLLOWING_SYNC_KEY = 'pf:services:followers:sync-following:'; - const FOLLOWING_KEY = 'pf:services:follow:following:id:'; - const FOLLOWERS_KEY = 'pf:services:follow:followers:id:'; + const CACHE_KEY = 'pf:services:followers:'; + const FOLLOWERS_SYNC_KEY = 'pf:services:followers:sync-followers:'; + const FOLLOWING_SYNC_KEY = 'pf:services:followers:sync-following:'; + const FOLLOWING_KEY = 'pf:services:follow:following:id:'; + const FOLLOWERS_KEY = 'pf:services:follow:followers:id:'; + const FOLLOWERS_LOCAL_KEY = 'pf:services:follow:local-follower-ids:'; + const FOLLOWERS_INTER_KEY = 'pf:services:follow:followers:inter:id:'; - public static function add($actor, $target) - { - $ts = (int) microtime(true); - RelationshipService::refresh($actor, $target); - Redis::zadd(self::FOLLOWING_KEY . $actor, $ts, $target); - Redis::zadd(self::FOLLOWERS_KEY . $target, $ts, $actor); - Cache::forget('profile:following:' . $actor); - } + public static function add($actor, $target, $refresh = true) + { + $ts = (int) microtime(true); + if($refresh) { + RelationshipService::refresh($actor, $target); + } else { + RelationshipService::forget($actor, $target); + } + Redis::zadd(self::FOLLOWING_KEY . $actor, $ts, $target); + Redis::zadd(self::FOLLOWERS_KEY . $target, $ts, $actor); + Cache::forget('profile:following:' . $actor); + } - public static function remove($actor, $target) - { - Redis::zrem(self::FOLLOWING_KEY . $actor, $target); - Redis::zrem(self::FOLLOWERS_KEY . $target, $actor); - Cache::forget('pf:services:follower:audience:' . $actor); - Cache::forget('pf:services:follower:audience:' . $target); - AccountService::del($actor); - AccountService::del($target); - RelationshipService::refresh($actor, $target); - Cache::forget('profile:following:' . $actor); - } + public static function remove($actor, $target, $silent = false) + { + Redis::zrem(self::FOLLOWING_KEY . $actor, $target); + Redis::zrem(self::FOLLOWERS_KEY . $target, $actor); + if($silent !== true) { + AccountService::del($actor); + AccountService::del($target); + RelationshipService::refresh($actor, $target); + Cache::forget('profile:following:' . $actor); + } else { + RelationshipService::forget($actor, $target); + } + } - public static function followers($id, $start = 0, $stop = 10) - { - self::cacheSyncCheck($id, 'followers'); - return Redis::zrevrange(self::FOLLOWERS_KEY . $id, $start, $stop); - } + public static function followers($id, $start = 0, $stop = 10) + { + self::cacheSyncCheck($id, 'followers'); + return Redis::zrevrange(self::FOLLOWERS_KEY . $id, $start, $stop); + } - public static function following($id, $start = 0, $stop = 10) - { - self::cacheSyncCheck($id, 'following'); - return Redis::zrevrange(self::FOLLOWING_KEY . $id, $start, $stop); - } + public static function following($id, $start = 0, $stop = 10) + { + self::cacheSyncCheck($id, 'following'); + return Redis::zrevrange(self::FOLLOWING_KEY . $id, $start, $stop); + } - public static function followersPaginate($id, $page = 1, $limit = 10) - { - $start = $page == 1 ? 0 : $page * $limit - $limit; - $end = $start + ($limit - 1); - return self::followers($id, $start, $end); - } + public static function followersPaginate($id, $page = 1, $limit = 10) + { + $start = $page == 1 ? 0 : $page * $limit - $limit; + $end = $start + ($limit - 1); + return self::followers($id, $start, $end); + } - public static function followingPaginate($id, $page = 1, $limit = 10) - { - $start = $page == 1 ? 0 : $page * $limit - $limit; - $end = $start + ($limit - 1); - return self::following($id, $start, $end); - } + public static function followingPaginate($id, $page = 1, $limit = 10) + { + $start = $page == 1 ? 0 : $page * $limit - $limit; + $end = $start + ($limit - 1); + return self::following($id, $start, $end); + } - public static function followerCount($id, $warmCache = true) - { - if($warmCache) { - self::cacheSyncCheck($id, 'followers'); - } - return Redis::zCard(self::FOLLOWERS_KEY . $id); - } + public static function followerCount($id, $warmCache = true) + { + if($warmCache) { + self::cacheSyncCheck($id, 'followers'); + } + return Redis::zCard(self::FOLLOWERS_KEY . $id); + } - public static function followingCount($id, $warmCache = true) - { - if($warmCache) { - self::cacheSyncCheck($id, 'following'); - } - return Redis::zCard(self::FOLLOWING_KEY . $id); - } + public static function followingCount($id, $warmCache = true) + { + if($warmCache) { + self::cacheSyncCheck($id, 'following'); + } + return Redis::zCard(self::FOLLOWING_KEY . $id); + } - public static function follows(string $actor, string $target) - { - if($actor == $target) { - return false; - } + public static function follows(string $actor, string $target, $quickCheck = false) + { + if($actor == $target) { + return false; + } - if(self::followerCount($target, false) && self::followingCount($actor, false)) { - self::cacheSyncCheck($target, 'followers'); - return (bool) Redis::zScore(self::FOLLOWERS_KEY . $target, $actor); - } else { - self::cacheSyncCheck($target, 'followers'); - self::cacheSyncCheck($actor, 'following'); - return Follower::whereProfileId($actor)->whereFollowingId($target)->exists(); - } - } + if($quickCheck) { + return (bool) Redis::zScore(self::FOLLOWERS_KEY . $target, $actor); + } - public static function cacheSyncCheck($id, $scope = 'followers') - { - if($scope === 'followers') { - if(Cache::get(self::FOLLOWERS_SYNC_KEY . $id) != null) { - return; - } - FollowServiceWarmCache::dispatch($id)->onQueue('low'); - } - if($scope === 'following') { - if(Cache::get(self::FOLLOWING_SYNC_KEY . $id) != null) { - return; - } - FollowServiceWarmCache::dispatch($id)->onQueue('low'); - } - return; - } + if(self::followerCount($target, false) && self::followingCount($actor, false)) { + self::cacheSyncCheck($target, 'followers'); + return (bool) Redis::zScore(self::FOLLOWERS_KEY . $target, $actor); + } else { + self::cacheSyncCheck($target, 'followers'); + self::cacheSyncCheck($actor, 'following'); + return Follower::whereProfileId($actor)->whereFollowingId($target)->exists(); + } + } - public static function audience($profile, $scope = null) - { - return (new self)->getAudienceInboxes($profile, $scope); - } + public static function cacheSyncCheck($id, $scope = 'followers') + { + if($scope === 'followers') { + if(Cache::get(self::FOLLOWERS_SYNC_KEY . $id) != null) { + return; + } + FollowServiceWarmCache::dispatch($id)->onQueue('low'); + } + if($scope === 'following') { + if(Cache::get(self::FOLLOWING_SYNC_KEY . $id) != null) { + return; + } + FollowServiceWarmCache::dispatch($id)->onQueue('low'); + } + return; + } - public static function softwareAudience($profile, $software = 'pixelfed') - { - return collect(self::audience($profile)) - ->filter(function($inbox) use($software) { - $domain = parse_url($inbox, PHP_URL_HOST); - if(!$domain) { - return false; - } - return InstanceService::software($domain) === strtolower($software); - }) - ->unique() - ->values() - ->toArray(); - } + public static function audience($profile, $scope = null) + { + return (new self)->getAudienceInboxes($profile, $scope); + } - protected function getAudienceInboxes($pid, $scope = null) - { - $key = 'pf:services:follower:audience:' . $pid; - $domains = Cache::remember($key, 432000, function() use($pid) { - $profile = Profile::whereNull(['status', 'domain'])->find($pid); - if(!$profile) { - return []; - } - return $profile - ->followers() - ->get() - ->map(function($follow) { - return $follow->sharedInbox ?? $follow->inbox_url; - }) - ->filter() - ->unique() - ->values(); - }); + public static function softwareAudience($profile, $software = 'pixelfed') + { + return collect(self::audience($profile)) + ->filter(function($inbox) use($software) { + $domain = parse_url($inbox, PHP_URL_HOST); + if(!$domain) { + return false; + } + return InstanceService::software($domain) === strtolower($software); + }) + ->unique() + ->values() + ->toArray(); + } - if(!$domains || !$domains->count()) { - return []; - } + protected function getAudienceInboxes($pid, $scope = null) + { + $key = 'pf:services:follower:audience:' . $pid; + $domains = Cache::remember($key, 432000, function() use($pid) { + $profile = Profile::whereNull(['status', 'domain'])->find($pid); + if(!$profile) { + return []; + } + return $profile + ->followers() + ->get() + ->map(function($follow) { + return $follow->sharedInbox ?? $follow->inbox_url; + }) + ->filter() + ->unique() + ->values(); + }); - $banned = InstanceService::getBannedDomains(); + if(!$domains || !$domains->count()) { + return []; + } - if(!$banned || count($banned) === 0) { - return $domains->toArray(); - } + $banned = InstanceService::getBannedDomains(); - $res = $domains->filter(function($domain) use($banned) { - $parsed = parse_url($domain, PHP_URL_HOST); - return !in_array($parsed, $banned); - }) - ->values() - ->toArray(); + if(!$banned || count($banned) === 0) { + return $domains->toArray(); + } - return $res; - } + $res = $domains->filter(function($domain) use($banned) { + $parsed = parse_url($domain, PHP_URL_HOST); + return !in_array($parsed, $banned); + }) + ->values() + ->toArray(); - public static function mutualCount($pid, $mid) - { - return Cache::remember(self::CACHE_KEY . ':mutualcount:' . $pid . ':' . $mid, 3600, function() use($pid, $mid) { - return DB::table('followers as u') - ->join('followers as s', 'u.following_id', '=', 's.following_id') - ->where('s.profile_id', $mid) - ->where('u.profile_id', $pid) - ->count(); - }); - } + return $res; + } - public static function mutualIds($pid, $mid, $limit = 3) - { - $key = self::CACHE_KEY . ':mutualids:' . $pid . ':' . $mid . ':limit_' . $limit; - return Cache::remember($key, 3600, function() use($pid, $mid, $limit) { - return DB::table('followers as u') - ->join('followers as s', 'u.following_id', '=', 's.following_id') - ->where('s.profile_id', $mid) - ->where('u.profile_id', $pid) - ->limit($limit) - ->pluck('s.following_id') - ->toArray(); - }); - } + public static function mutualCount($pid, $mid) + { + return Cache::remember(self::CACHE_KEY . ':mutualcount:' . $pid . ':' . $mid, 3600, function() use($pid, $mid) { + return DB::table('followers as u') + ->join('followers as s', 'u.following_id', '=', 's.following_id') + ->where('s.profile_id', $mid) + ->where('u.profile_id', $pid) + ->count(); + }); + } - public static function delCache($id) - { - Redis::del(self::CACHE_KEY . $id); - Redis::del(self::FOLLOWING_KEY . $id); - Redis::del(self::FOLLOWERS_KEY . $id); - Cache::forget(self::FOLLOWERS_SYNC_KEY . $id); - Cache::forget(self::FOLLOWING_SYNC_KEY . $id); - } + public static function mutualIds($pid, $mid, $limit = 3) + { + $key = self::CACHE_KEY . ':mutualids:' . $pid . ':' . $mid . ':limit_' . $limit; + return Cache::remember($key, 3600, function() use($pid, $mid, $limit) { + return DB::table('followers as u') + ->join('followers as s', 'u.following_id', '=', 's.following_id') + ->where('s.profile_id', $mid) + ->where('u.profile_id', $pid) + ->limit($limit) + ->pluck('s.following_id') + ->toArray(); + }); + } + + public static function mutualAccounts($actorId, $profileId) + { + if($actorId == $profileId) { + return []; + } + $actorKey = self::FOLLOWING_KEY . $actorId; + $profileKey = self::FOLLOWERS_KEY . $profileId; + $key = self::FOLLOWERS_INTER_KEY . $actorId . ':' . $profileId; + $res = Redis::zinterstore($key, [$actorKey, $profileKey]); + if($res) { + return Redis::zrange($key, 0, -1); + } else { + return []; + } + } + + public static function delCache($id) + { + Redis::del(self::CACHE_KEY . $id); + Redis::del(self::FOLLOWING_KEY . $id); + Redis::del(self::FOLLOWERS_KEY . $id); + Cache::forget(self::FOLLOWERS_SYNC_KEY . $id); + Cache::forget(self::FOLLOWING_SYNC_KEY . $id); + } + + public static function localFollowerIds($pid, $limit = 0) + { + $key = self::FOLLOWERS_LOCAL_KEY . $pid; + $res = Cache::remember($key, 7200, function() use($pid) { + return DB::table('followers')->whereFollowingId($pid)->whereLocalProfile(true)->pluck('profile_id')->sort(); + }); + return $limit ? + $res->take($limit)->values()->toArray() : + $res->values()->toArray(); + } } diff --git a/app/Services/HashtagFollowService.php b/app/Services/HashtagFollowService.php new file mode 100644 index 000000000..d4f93f404 --- /dev/null +++ b/app/Services/HashtagFollowService.php @@ -0,0 +1,72 @@ +lazyById(20, 'id') as $h) { + if($h) { + self::add($h->hashtag_id, $h->profile_id); + } + } + + self::setWarm($hid); + + return self::get($hid); + } + + public static function isWarm($hid) + { + return Redis::zcount(self::CACHE_KEY . $hid, 0, -1) ?? Redis::zscore(self::CACHE_WARMED, $hid) != null; + } + + public static function setWarm($hid) + { + return Redis::zadd(self::CACHE_WARMED, $hid, $hid); + } +} diff --git a/app/Services/HashtagRelatedService.php b/app/Services/HashtagRelatedService.php new file mode 100644 index 000000000..b96483987 --- /dev/null +++ b/app/Services/HashtagRelatedService.php @@ -0,0 +1,38 @@ +first(); + if(!$tag) { + return []; + } + return $tag->related_tags; + } + + public static function fetchRelatedTags($tag) + { + $res = StatusHashtag::query() + ->select('h2.name', DB::raw('COUNT(*) as related_count')) + ->join('status_hashtags as hs2', function ($join) { + $join->on('status_hashtags.status_id', '=', 'hs2.status_id') + ->whereRaw('status_hashtags.hashtag_id != hs2.hashtag_id'); + }) + ->join('hashtags as h1', 'status_hashtags.hashtag_id', '=', 'h1.id') + ->join('hashtags as h2', 'hs2.hashtag_id', '=', 'h2.id') + ->where('h1.name', '=', $tag) + ->groupBy('h2.name') + ->orderBy('related_count', 'desc') + ->limit(30) + ->get(); + + return $res; + } +} diff --git a/app/Services/HashtagService.php b/app/Services/HashtagService.php index 87f895a65..84fd1986b 100644 --- a/app/Services/HashtagService.php +++ b/app/Services/HashtagService.php @@ -8,65 +8,80 @@ use App\Hashtag; use App\StatusHashtag; use App\HashtagFollow; -class HashtagService { +class HashtagService +{ + const FOLLOW_KEY = 'pf:services:hashtag:following:v1:'; + const FOLLOW_PIDS_KEY = 'pf:services:hashtag-follows:v1:'; - const FOLLOW_KEY = 'pf:services:hashtag:following:'; + public static function get($id) + { + return Cache::remember('services:hashtag:by_id:' . $id, 3600, function() use($id) { + $tag = Hashtag::find($id); + if(!$tag) { + return []; + } + return [ + 'name' => $tag->name, + 'slug' => $tag->slug, + ]; + }); + } - public static function get($id) - { - return Cache::remember('services:hashtag:by_id:' . $id, 3600, function() use($id) { - $tag = Hashtag::find($id); - if(!$tag) { - return []; - } - return [ - 'name' => $tag->name, - 'slug' => $tag->slug, - ]; - }); - } + public static function count($id) + { + return Cache::remember('services:hashtag:total-count:by_id:' . $id, 300, function() use($id) { + $tag = Hashtag::find($id); + return $tag ? $tag->cached_count ?? 0 : 0; + }); + } - public static function count($id) - { - return Cache::remember('services:hashtag:public-count:by_id:' . $id, 86400, function() use($id) { - return StatusHashtag::whereHashtagId($id)->whereStatusVisibility('public')->count(); - }); - } + public static function isFollowing($pid, $hid) + { + $res = Redis::zscore(self::FOLLOW_KEY . $hid, $pid); + if($res) { + return true; + } - public static function isFollowing($pid, $hid) - { - $res = Redis::zscore(self::FOLLOW_KEY . $pid, $hid); - if($res) { - return true; - } + $synced = Cache::get(self::FOLLOW_KEY . 'acct:' . $pid . ':synced'); + if(!$synced) { + $tags = HashtagFollow::whereProfileId($pid) + ->get() + ->each(function($tag) use($pid) { + self::follow($pid, $tag->hashtag_id); + }); + Cache::set(self::FOLLOW_KEY . 'acct:' . $pid . ':synced', true, 1209600); - $synced = Cache::get(self::FOLLOW_KEY . $pid . ':synced'); - if(!$synced) { - $tags = HashtagFollow::whereProfileId($pid) - ->get() - ->each(function($tag) use($pid) { - self::follow($pid, $tag->hashtag_id); - }); - Cache::set(self::FOLLOW_KEY . $pid . ':synced', true, 1209600); + return (bool) Redis::zscore(self::FOLLOW_KEY . $hid, $pid) >= 1; + } - return (bool) Redis::zscore(self::FOLLOW_KEY . $pid, $hid) > 1; - } + return false; + } - return false; - } + public static function follow($pid, $hid) + { + Cache::forget(self::FOLLOW_PIDS_KEY . $hid); + return Redis::zadd(self::FOLLOW_KEY . $hid, $pid, $pid); + } - public static function follow($pid, $hid) - { - return Redis::zadd(self::FOLLOW_KEY . $pid, $hid, $hid); - } + public static function unfollow($pid, $hid) + { + Cache::forget(self::FOLLOW_PIDS_KEY . $hid); + return Redis::zrem(self::FOLLOW_KEY . $hid, $pid); + } - public static function unfollow($pid, $hid) - { - return Redis::zrem(self::FOLLOW_KEY . $pid, $hid); - } + public static function following($hid, $start = 0, $limit = 10) + { + $synced = Cache::get(self::FOLLOW_KEY . 'acct-following:' . $hid . ':synced'); + if(!$synced) { + $tags = HashtagFollow::whereHashtagId($hid) + ->get() + ->each(function($tag) use($hid) { + self::follow($tag->profile_id, $hid); + }); + Cache::set(self::FOLLOW_KEY . 'acct-following:' . $hid . ':synced', true, 1209600); - public static function following($pid, $start = 0, $limit = 10) - { - return Redis::zrevrange(self::FOLLOW_KEY . $pid, $start, $limit); - } + return Redis::zrevrange(self::FOLLOW_KEY . $hid, $start, $limit); + } + return Redis::zrevrange(self::FOLLOW_KEY . $hid, $start, $limit); + } } diff --git a/app/Services/HomeTimelineService.php b/app/Services/HomeTimelineService.php new file mode 100644 index 000000000..08d990591 --- /dev/null +++ b/app/Services/HomeTimelineService.php @@ -0,0 +1,114 @@ + 100) { + $stop = 100; + } + + return Redis::zrevrange(self::CACHE_KEY . $id, $start, $stop); + } + + public static function getRankedMaxId($id, $start = null, $limit = 10) + { + if(!$start) { + return []; + } + + return array_keys(Redis::zrevrangebyscore(self::CACHE_KEY . $id, $start, '-inf', [ + 'withscores' => true, + 'limit' => [1, $limit - 1] + ])); + } + + public static function getRankedMinId($id, $end = null, $limit = 10) + { + if(!$end) { + return []; + } + + return array_keys(Redis::zrevrangebyscore(self::CACHE_KEY . $id, '+inf', $end, [ + 'withscores' => true, + 'limit' => [0, $limit] + ])); + } + + public static function add($id, $val) + { + if(self::count($id) >= 400) { + Redis::zpopmin(self::CACHE_KEY . $id); + } + + return Redis::zadd(self::CACHE_KEY .$id, $val, $val); + } + + public static function rem($id, $val) + { + return Redis::zrem(self::CACHE_KEY . $id, $val); + } + + public static function count($id) + { + return Redis::zcard(self::CACHE_KEY . $id); + } + + public static function warmCache($id, $force = false, $limit = 100, $returnIds = false) + { + if(self::count($id) == 0 || $force == true) { + Redis::del(self::CACHE_KEY . $id); + $following = Cache::remember('profile:following:'.$id, 1209600, function() use($id) { + $following = Follower::whereProfileId($id)->pluck('following_id'); + return $following->push($id)->toArray(); + }); + + $minId = SnowflakeService::byDate(now()->subMonths(6)); + + $filters = UserFilterService::filters($id); + + if($filters && count($filters)) { + $following = array_diff($following, $filters); + } + + $domainBlocks = UserDomainBlock::whereProfileId($id)->pluck('domain')->toArray(); + + $ids = Status::where('id', '>', $minId) + ->whereIn('profile_id', $following) + ->whereNull(['in_reply_to_id', 'reblog_of_id']) + ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) + ->whereIn('visibility',['public', 'unlisted', 'private']) + ->orderByDesc('id') + ->limit($limit) + ->pluck('id'); + + foreach($ids as $pid) { + $status = StatusService::get($pid, false); + if(!$status || !isset($status['account'], $status['url'])) { + continue; + } + if($domainBlocks && count($domainBlocks)) { + $domain = strtolower(parse_url($status['url'], PHP_URL_HOST)); + if(in_array($domain, $domainBlocks)) { + continue; + } + } + self::add($id, $pid); + } + + return $returnIds ? $ids : 1; + } + return 0; + } +} diff --git a/app/Services/InstanceService.php b/app/Services/InstanceService.php index 1cead8d48..2ad991063 100644 --- a/app/Services/InstanceService.php +++ b/app/Services/InstanceService.php @@ -120,6 +120,9 @@ class InstanceService $pixels[] = $row; } + // Free the allocated GdImage object from memory: + imagedestroy($image); + $components_x = 4; $components_y = 4; $blurhash = Blurhash::encode($pixels, $components_x, $components_y); diff --git a/app/Services/LandingService.php b/app/Services/LandingService.php index 2a5acda07..ba16af5b6 100644 --- a/app/Services/LandingService.php +++ b/app/Services/LandingService.php @@ -9,17 +9,13 @@ use Illuminate\Support\Facades\Redis; use App\Status; use App\User; use App\Services\AccountService; +use App\Util\Site\Nodeinfo; class LandingService { public static function get($json = true) { - $activeMonth = Cache::remember('api:nodeinfo:am', 172800, function() { - return User::select('last_active_at') - ->where('last_active_at', '>', now()->subMonths(1)) - ->orWhere('created_at', '>', now()->subMonths(1)) - ->count(); - }); + $activeMonth = Nodeinfo::activeUsersMonthly(); $totalUsers = Cache::remember('api:nodeinfo:users', 43200, function() { return User::count(); diff --git a/app/Services/MarkerService.php b/app/Services/MarkerService.php index 6b407b567..130f0b017 100644 --- a/app/Services/MarkerService.php +++ b/app/Services/MarkerService.php @@ -13,7 +13,7 @@ class MarkerService return Cache::get(self::CACHE_KEY . $timeline . ':' . $profileId); } - public static function set($profileId, $timeline = 'home', $entityId) + public static function set($profileId, $timeline = 'home', $entityId = false) { $existing = self::get($profileId, $timeline); $key = self::CACHE_KEY . $timeline . ':' . $profileId; diff --git a/app/Services/Media/MediaHlsService.php b/app/Services/Media/MediaHlsService.php new file mode 100644 index 000000000..04b5ac649 --- /dev/null +++ b/app/Services/Media/MediaHlsService.php @@ -0,0 +1,27 @@ +media_path; + if(!$path) { return; } + $parts = explode('/', $path); + $filename = array_pop($parts); + $dir = implode('/', $parts); + [$name, $ext] = explode('.', $filename); + + $files = Storage::files($dir); + + return collect($files) + ->filter(function($p) use($dir, $name) { + return str_starts_with($p, $dir . '/' . $name); + }) + ->values() + ->toArray(); + } +} diff --git a/app/Services/MediaService.php b/app/Services/MediaService.php index 8ca90118f..e0a2576e0 100644 --- a/app/Services/MediaService.php +++ b/app/Services/MediaService.php @@ -18,7 +18,7 @@ class MediaService public static function get($statusId) { - return Cache::remember(self::CACHE_KEY.$statusId, 86400, function() use($statusId) { + return Cache::remember(self::CACHE_KEY.$statusId, 21600, function() use($statusId) { $media = Media::whereStatusId($statusId)->orderBy('order')->get(); if(!$media) { return []; @@ -46,7 +46,8 @@ class MediaService $media['orientation'], $media['filter_name'], $media['filter_class'], - $media['mime'] + $media['mime'], + $media['hls_manifest'] ); $media['type'] = $mime ? strtolower($mime[0]) : 'unknown'; diff --git a/app/Services/MediaStorageService.php b/app/Services/MediaStorageService.php index b52662d1f..128001de2 100644 --- a/app/Services/MediaStorageService.php +++ b/app/Services/MediaStorageService.php @@ -17,6 +17,7 @@ use App\Http\Controllers\AvatarController; use GuzzleHttp\Exception\RequestException; use App\Jobs\MediaPipeline\MediaDeletePipeline; use Illuminate\Support\Arr; +use App\Jobs\AvatarPipeline\AvatarStorageCleanup; class MediaStorageService { @@ -29,9 +30,9 @@ class MediaStorageService { return; } - public static function avatar($avatar, $local = false) + public static function avatar($avatar, $local = false, $skipRecentCheck = false) { - return (new self())->fetchAvatar($avatar, $local); + return (new self())->fetchAvatar($avatar, $local, $skipRecentCheck); } public static function head($url) @@ -86,12 +87,11 @@ class MediaStorageService { $thumbname = array_pop($pt); $storagePath = implode('/', $p); - $disk = Storage::disk(config('filesystems.cloud')); - $file = $disk->putFileAs($storagePath, new File($path), $name, 'public'); - $url = $disk->url($file); - $thumbFile = $disk->putFileAs($storagePath, new File($thumb), $thumbname, 'public'); - $thumbUrl = $disk->url($thumbFile); - $media->thumbnail_url = $thumbUrl; + $url = ResilientMediaStorageService::store($storagePath, $path, $name); + if($thumb) { + $thumbUrl = ResilientMediaStorageService::store($storagePath, $thumb, $thumbname); + $media->thumbnail_url = $thumbUrl; + } $media->cdn_url = $url; $media->optimized_url = $url; $media->replicated_at = now(); @@ -183,6 +183,7 @@ class MediaStorageService { protected function fetchAvatar($avatar, $local = false, $skipRecentCheck = false) { + $queue = random_int(1, 15) > 5 ? 'mmo' : 'low'; $url = $avatar->remote_url; $driver = $local ? 'local' : config('filesystems.cloud'); @@ -206,7 +207,7 @@ class MediaStorageService { $max_size = (int) config('pixelfed.max_avatar_size') * 1000; if(!$skipRecentCheck) { - if($avatar->last_fetched_at && $avatar->last_fetched_at->gt(now()->subDay())) { + if($avatar->last_fetched_at && $avatar->last_fetched_at->gt(now()->subMonths(3))) { return; } } @@ -262,6 +263,7 @@ class MediaStorageService { Cache::forget('avatar:' . $avatar->profile_id); AccountService::del($avatar->profile_id); + AvatarStorageCleanup::dispatch($avatar)->onQueue($queue)->delay(now()->addMinutes(random_int(3, 15))); unlink($tmpName); } diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php index 139b13a69..634038baf 100644 --- a/app/Services/NotificationService.php +++ b/app/Services/NotificationService.php @@ -12,10 +12,13 @@ use App\Transformer\Api\NotificationTransformer; use League\Fractal; use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use App\Jobs\InternalPipeline\NotificationEpochUpdatePipeline; class NotificationService { const CACHE_KEY = 'pf:services:notifications:ids:'; + const EPOCH_CACHE_KEY = 'pf:services:notifications:epoch-id:by-months:'; + const ITEM_CACHE_TTL = 86400; const MASTODON_TYPES = [ 'follow', 'follow_request', @@ -44,11 +47,22 @@ class NotificationService { return $res; } + public static function getEpochId($months = 6) + { + $epoch = Cache::get(self::EPOCH_CACHE_KEY . $months); + if(!$epoch) { + NotificationEpochUpdatePipeline::dispatch(); + return 1; + } + return $epoch; + } + public static function coldGet($id, $start = 0, $stop = 400) { $stop = $stop > 400 ? 400 : $stop; - $ids = Notification::whereProfileId($id) - ->latest() + $ids = Notification::where('id', '>', self::getEpochId()) + ->where('profile_id', $id) + ->orderByDesc('id') ->skip($start) ->take($stop) ->pluck('id'); @@ -227,7 +241,7 @@ class NotificationService { public static function getNotification($id) { - $notification = Cache::remember('service:notification:'.$id, 86400, function() use($id) { + $notification = Cache::remember('service:notification:'.$id, self::ITEM_CACHE_TTL, function() use($id) { $n = Notification::with('item')->find($id); if(!$n) { @@ -259,19 +273,20 @@ class NotificationService { public static function setNotification(Notification $notification) { - return Cache::remember('service:notification:'.$notification->id, now()->addDays(3), function() use($notification) { + return Cache::remember('service:notification:'.$notification->id, self::ITEM_CACHE_TTL, function() use($notification) { $fractal = new Fractal\Manager(); $fractal->setSerializer(new ArraySerializer()); $resource = new Fractal\Resource\Item($notification, new NotificationTransformer()); return $fractal->createData($resource)->toArray(); }); - } + } public static function warmCache($id, $stop = 400, $force = false) { if(self::count($id) == 0 || $force == true) { - $ids = Notification::whereProfileId($id) - ->latest() + $ids = Notification::where('profile_id', $id) + ->where('id', '>', self::getEpochId()) + ->orderByDesc('id') ->limit($stop) ->pluck('id'); foreach($ids as $key) { diff --git a/app/Services/PublicTimelineService.php b/app/Services/PublicTimelineService.php index f2658e4b1..7cd6816b3 100644 --- a/app/Services/PublicTimelineService.php +++ b/app/Services/PublicTimelineService.php @@ -95,7 +95,7 @@ class PublicTimelineService { if(self::count() == 0 || $force == true) { $hideNsfw = config('instance.hide_nsfw_on_public_feeds'); Redis::del(self::CACHE_KEY); - $minId = SnowflakeService::byDate(now()->subDays(14)); + $minId = SnowflakeService::byDate(now()->subDays(90)); $ids = Status::where('id', '>', $minId) ->whereNull(['uri', 'in_reply_to_id', 'reblog_of_id']) ->when($hideNsfw, function($q, $hideNsfw) { @@ -105,9 +105,11 @@ class PublicTimelineService { ->whereScope('public') ->orderByDesc('id') ->limit($limit) - ->pluck('id'); - foreach($ids as $id) { - self::add($id); + ->pluck('id', 'profile_id'); + foreach($ids as $k => $id) { + if(AdminShadowFilterService::canAddToPublicFeedByProfileId($k)) { + self::add($id); + } } return 1; } diff --git a/app/Services/RelationshipService.php b/app/Services/RelationshipService.php index 3c6d2818f..476c9c9ae 100644 --- a/app/Services/RelationshipService.php +++ b/app/Services/RelationshipService.php @@ -66,6 +66,14 @@ class RelationshipService return self::get($aid, $tid); } + public static function forget($aid, $tid) + { + Cache::forget('pf:services:follower:audience:' . $aid); + Cache::forget('pf:services:follower:audience:' . $tid); + self::delete($tid, $aid); + self::delete($aid, $tid); + } + public static function defaultRelation($tid) { return [ diff --git a/app/Services/ResilientMediaStorageService.php b/app/Services/ResilientMediaStorageService.php new file mode 100644 index 000000000..ac1b089af --- /dev/null +++ b/app/Services/ResilientMediaStorageService.php @@ -0,0 +1,66 @@ +putFileAs($storagePath, new File($path), $name, 'public'); + return $disk->url($file); + }, random_int(100, 500)); + } + + public static function handleResilientStore($storagePath, $path, $name) + { + $attempts = 0; + return retry(4, function() use($storagePath, $path, $name, $attempts) { + self::$attempts++; + usleep(100000); + $baseDisk = self::$attempts > 1 ? self::getAltDriver() : config('filesystems.cloud'); + try { + $disk = Storage::disk($baseDisk); + $file = $disk->putFileAs($storagePath, new File($path), $name, 'public'); + } catch (S3Exception | ClientException | ConnectException | UnableToWriteFile | Exception $e) {} + return $disk->url($file); + }, function (int $attempt, Exception $exception) { + return $attempt * 200; + }); + } + + public static function getAltDriver() + { + $drivers = []; + if(config('filesystems.disks.alt-primary.enabled')) { + $drivers[] = 'alt-primary'; + } + if(config('filesystems.disks.alt-secondary.enabled')) { + $drivers[] = 'alt-secondary'; + } + if(empty($drivers)) { + return false; + } + $key = array_rand($drivers, 1); + return $drivers[$key]; + } +} diff --git a/app/Services/SearchApiV2Service.php b/app/Services/SearchApiV2Service.php index 90691f0bd..f926c2c27 100644 --- a/app/Services/SearchApiV2Service.php +++ b/app/Services/SearchApiV2Service.php @@ -95,7 +95,15 @@ class SearchApiV2Service if(substr($webfingerQuery, 0, 1) !== '@') { $webfingerQuery = '@' . $webfingerQuery; } - $banned = InstanceService::getBannedDomains(); + $banned = InstanceService::getBannedDomains() ?? []; + $domainBlocks = UserFilterService::domainBlocks($user->profile_id); + if($domainBlocks && count($domainBlocks)) { + $banned = array_unique( + array_values( + array_merge($banned, $domainBlocks) + ) + ); + } $operator = config('database.default') === 'pgsql' ? 'ilike' : 'like'; $results = Profile::select('username', 'id', 'followers_count', 'domain') ->where('username', $operator, $query) @@ -172,8 +180,18 @@ class SearchApiV2Service 'hashtags' => [], 'statuses' => [], ]; + $user = request()->user(); $mastodonMode = self::$mastodonMode; $query = urldecode($this->query->input('q')); + $banned = InstanceService::getBannedDomains(); + $domainBlocks = UserFilterService::domainBlocks($user->profile_id); + if($domainBlocks && count($domainBlocks)) { + $banned = array_unique( + array_values( + array_merge($banned, $domainBlocks) + ) + ); + } if(substr($query, 0, 1) === '@' && !Str::contains($query, '.')) { $default['accounts'] = $this->accounts(substr($query, 1)); return $default; @@ -197,7 +215,11 @@ class SearchApiV2Service } catch (\Exception $e) { return $default; } - if($res && isset($res['id'])) { + if($res && isset($res['id'], $res['url'])) { + $domain = strtolower(parse_url($res['url'], PHP_URL_HOST)); + if(in_array($domain, $banned)) { + return $default; + } $default['accounts'][] = $res; return $default; } else { @@ -212,6 +234,10 @@ class SearchApiV2Service return $default; } if($res && isset($res['id'])) { + $domain = strtolower(parse_url($res['url'], PHP_URL_HOST)); + if(in_array($domain, $banned)) { + return $default; + } $default['accounts'][] = $res; return $default; } else { @@ -221,6 +247,9 @@ class SearchApiV2Service if($sid = Status::whereUri($query)->first()) { $s = StatusService::get($sid->id, false); + if(!$s) { + return $default; + } if(in_array($s['visibility'], ['public', 'unlisted'])) { $default['statuses'][] = $s; return $default; @@ -229,7 +258,7 @@ class SearchApiV2Service try { $res = ActivityPubFetchService::get($query); - $banned = InstanceService::getBannedDomains(); + if($res) { $json = json_decode($res, true); diff --git a/app/Services/StatusHashtagService.php b/app/Services/StatusHashtagService.php index ececca633..6e740db42 100644 --- a/app/Services/StatusHashtagService.php +++ b/app/Services/StatusHashtagService.php @@ -84,18 +84,14 @@ class StatusHashtagService { public static function statusTags($statusId) { - $key = 'pf:services:sh:id:' . $statusId; + $status = Status::with('hashtags')->find($statusId); + if(!$status) { + return []; + } - return Cache::remember($key, 604800, function() use($statusId) { - $status = Status::find($statusId); - if(!$status) { - return []; - } - - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $resource = new Fractal\Resource\Collection($status->hashtags, new HashtagTransformer()); - return $fractal->createData($resource)->toArray(); - }); + $fractal = new Fractal\Manager(); + $fractal->setSerializer(new ArraySerializer()); + $resource = new Fractal\Resource\Collection($status->hashtags, new HashtagTransformer()); + return $fractal->createData($resource)->toArray(); } } diff --git a/app/Services/StatusService.php b/app/Services/StatusService.php index bdf2f31db..4051bede4 100644 --- a/app/Services/StatusService.php +++ b/app/Services/StatusService.php @@ -22,9 +22,9 @@ class StatusService return self::CACHE_KEY . $p . $id; } - public static function get($id, $publicOnly = true) + public static function get($id, $publicOnly = true, $mastodonMode = false) { - return Cache::remember(self::key($id, $publicOnly), 21600, function() use($id, $publicOnly) { + $res = Cache::remember(self::key($id, $publicOnly), 21600, function() use($id, $publicOnly) { if($publicOnly) { $status = Status::whereScope('public')->find($id); } else { @@ -36,13 +36,23 @@ class StatusService $fractal = new Fractal\Manager(); $fractal->setSerializer(new ArraySerializer()); $resource = new Fractal\Resource\Item($status, new StatusStatelessTransformer()); - return $fractal->createData($resource)->toArray(); + $res = $fractal->createData($resource)->toArray(); + $res['_pid'] = isset($res['account']) && isset($res['account']['id']) ? $res['account']['id'] : null; + if(isset($res['_pid'])) { + unset($res['account']); + } + return $res; }); + if($res && isset($res['_pid'])) { + $res['account'] = $mastodonMode === true ? AccountService::getMastodon($res['_pid'], true) : AccountService::get($res['_pid'], true); + unset($res['_pid']); + } + return $res; } public static function getMastodon($id, $publicOnly = true) { - $status = self::get($id, $publicOnly); + $status = self::get($id, $publicOnly, true); if(!$status) { return null; } @@ -151,8 +161,6 @@ class StatusService } Cache::forget('status:transformer:media:attachments:' . $id); MediaService::del($id); - Cache::forget('status:thumb:nsfw0' . $id); - Cache::forget('status:thumb:nsfw1' . $id); Cache::forget('pf:services:sh:id:' . $id); PublicTimelineService::rem($id); NetworkTimelineService::rem($id); diff --git a/app/Services/UserFilterService.php b/app/Services/UserFilterService.php index 1dcdc819a..5673db60c 100644 --- a/app/Services/UserFilterService.php +++ b/app/Services/UserFilterService.php @@ -4,145 +4,160 @@ namespace App\Services; use Cache; use App\UserFilter; +use App\Models\UserDomainBlock; use Illuminate\Support\Facades\Redis; class UserFilterService { - const USER_MUTES_KEY = 'pf:services:mutes:ids:'; - const USER_BLOCKS_KEY = 'pf:services:blocks:ids:'; + const USER_MUTES_KEY = 'pf:services:mutes:ids:'; + const USER_BLOCKS_KEY = 'pf:services:blocks:ids:'; + const USER_DOMAIN_KEY = 'pf:services:domain-blocks:ids:'; - public static function mutes(int $profile_id) - { - $key = self::USER_MUTES_KEY . $profile_id; - $warm = Cache::has($key . ':cached-v0'); - if($warm) { - return Redis::zrevrange($key, 0, -1) ?? []; - } else { - if(Redis::zrevrange($key, 0, -1)) { - return Redis::zrevrange($key, 0, -1); - } - $ids = UserFilter::whereFilterType('mute') - ->whereUserId($profile_id) - ->pluck('filterable_id') - ->map(function($id) { - $acct = AccountService::get($id, true); - if(!$acct) { - return false; - } - return $acct['id']; - }) - ->filter(function($res) { - return $res; - }) - ->values() - ->toArray(); - foreach ($ids as $muted_id) { - Redis::zadd($key, (int) $muted_id, (int) $muted_id); - } - Cache::set($key . ':cached-v0', 1, 7776000); - return $ids; - } - } + public static function mutes(int $profile_id) + { + $key = self::USER_MUTES_KEY . $profile_id; + $warm = Cache::has($key . ':cached-v0'); + if($warm) { + return Redis::zrevrange($key, 0, -1) ?? []; + } else { + if(Redis::zrevrange($key, 0, -1)) { + return Redis::zrevrange($key, 0, -1); + } + $ids = UserFilter::whereFilterType('mute') + ->whereUserId($profile_id) + ->pluck('filterable_id') + ->map(function($id) { + $acct = AccountService::get($id, true); + if(!$acct) { + return false; + } + return $acct['id']; + }) + ->filter(function($res) { + return $res; + }) + ->values() + ->toArray(); + foreach ($ids as $muted_id) { + Redis::zadd($key, (int) $muted_id, (int) $muted_id); + } + Cache::set($key . ':cached-v0', 1, 7776000); + return $ids; + } + } - public static function blocks(int $profile_id) - { - $key = self::USER_BLOCKS_KEY . $profile_id; - $warm = Cache::has($key . ':cached-v0'); - if($warm) { - return Redis::zrevrange($key, 0, -1) ?? []; - } else { - if(Redis::zrevrange($key, 0, -1)) { - return Redis::zrevrange($key, 0, -1); - } - $ids = UserFilter::whereFilterType('block') - ->whereUserId($profile_id) - ->pluck('filterable_id') - ->map(function($id) { - $acct = AccountService::get($id, true); - if(!$acct) { - return false; - } - return $acct['id']; - }) - ->filter(function($res) { - return $res; - }) - ->values() - ->toArray(); - foreach ($ids as $blocked_id) { - Redis::zadd($key, (int) $blocked_id, (int) $blocked_id); - } - Cache::set($key . ':cached-v0', 1, 7776000); - return $ids; - } - } + public static function blocks(int $profile_id) + { + $key = self::USER_BLOCKS_KEY . $profile_id; + $warm = Cache::has($key . ':cached-v0'); + if($warm) { + return Redis::zrevrange($key, 0, -1) ?? []; + } else { + if(Redis::zrevrange($key, 0, -1)) { + return Redis::zrevrange($key, 0, -1); + } + $ids = UserFilter::whereFilterType('block') + ->whereUserId($profile_id) + ->pluck('filterable_id') + ->map(function($id) { + $acct = AccountService::get($id, true); + if(!$acct) { + return false; + } + return $acct['id']; + }) + ->filter(function($res) { + return $res; + }) + ->values() + ->toArray(); + foreach ($ids as $blocked_id) { + Redis::zadd($key, (int) $blocked_id, (int) $blocked_id); + } + Cache::set($key . ':cached-v0', 1, 7776000); + return $ids; + } + } - public static function filters(int $profile_id) - { - return array_unique(array_merge(self::mutes($profile_id), self::blocks($profile_id))); - } + public static function filters(int $profile_id) + { + return array_unique(array_merge(self::mutes($profile_id), self::blocks($profile_id))); + } - public static function mute(int $profile_id, int $muted_id) - { - if($profile_id == $muted_id) { - return false; - } - $key = self::USER_MUTES_KEY . $profile_id; - $mutes = self::mutes($profile_id); - $exists = in_array($muted_id, $mutes); - if(!$exists) { - Redis::zadd($key, $muted_id, $muted_id); - } - return true; - } + public static function mute(int $profile_id, int $muted_id) + { + if($profile_id == $muted_id) { + return false; + } + $key = self::USER_MUTES_KEY . $profile_id; + $mutes = self::mutes($profile_id); + $exists = in_array($muted_id, $mutes); + if(!$exists) { + Redis::zadd($key, $muted_id, $muted_id); + } + return true; + } - public static function unmute(int $profile_id, string $muted_id) - { - if($profile_id == $muted_id) { - return false; - } - $key = self::USER_MUTES_KEY . $profile_id; - $mutes = self::mutes($profile_id); - $exists = in_array($muted_id, $mutes); - if($exists) { - Redis::zrem($key, $muted_id); - } - return true; - } + public static function unmute(int $profile_id, string $muted_id) + { + if($profile_id == $muted_id) { + return false; + } + $key = self::USER_MUTES_KEY . $profile_id; + $mutes = self::mutes($profile_id); + $exists = in_array($muted_id, $mutes); + if($exists) { + Redis::zrem($key, $muted_id); + } + return true; + } - public static function block(int $profile_id, int $blocked_id) - { - if($profile_id == $blocked_id) { - return false; - } - $key = self::USER_BLOCKS_KEY . $profile_id; - $exists = in_array($blocked_id, self::blocks($profile_id)); - if(!$exists) { - Redis::zadd($key, $blocked_id, $blocked_id); - } - return true; - } + public static function block(int $profile_id, int $blocked_id) + { + if($profile_id == $blocked_id) { + return false; + } + $key = self::USER_BLOCKS_KEY . $profile_id; + $exists = in_array($blocked_id, self::blocks($profile_id)); + if(!$exists) { + Redis::zadd($key, $blocked_id, $blocked_id); + } + return true; + } - public static function unblock(int $profile_id, string $blocked_id) - { - if($profile_id == $blocked_id) { - return false; - } - $key = self::USER_BLOCKS_KEY . $profile_id; - $exists = in_array($blocked_id, self::blocks($profile_id)); - if($exists) { - Redis::zrem($key, $blocked_id); - } - return $exists; - } + public static function unblock(int $profile_id, string $blocked_id) + { + if($profile_id == $blocked_id) { + return false; + } + $key = self::USER_BLOCKS_KEY . $profile_id; + $exists = in_array($blocked_id, self::blocks($profile_id)); + if($exists) { + Redis::zrem($key, $blocked_id); + } + return $exists; + } - public static function blockCount(int $profile_id) - { - return Redis::zcard(self::USER_BLOCKS_KEY . $profile_id); - } + public static function blockCount(int $profile_id) + { + return Redis::zcard(self::USER_BLOCKS_KEY . $profile_id); + } - public static function muteCount(int $profile_id) - { - return Redis::zcard(self::USER_MUTES_KEY . $profile_id); - } + public static function muteCount(int $profile_id) + { + return Redis::zcard(self::USER_MUTES_KEY . $profile_id); + } + + public static function domainBlocks($pid, $purge = false) + { + if($purge) { + Cache::forget(self::USER_DOMAIN_KEY . $pid); + } + return Cache::remember( + self::USER_DOMAIN_KEY . $pid, + 21600, + function() use($pid) { + return UserDomainBlock::whereProfileId($pid)->pluck('domain')->toArray(); + }); + } } diff --git a/app/Status.php b/app/Status.php index 77262597e..d665464ae 100644 --- a/app/Status.php +++ b/app/Status.php @@ -9,7 +9,9 @@ use App\Http\Controllers\StatusController; use Illuminate\Database\Eloquent\SoftDeletes; use App\Models\Poll; use App\Services\AccountService; +use App\Services\StatusService; use App\Models\StatusEdit; +use Illuminate\Support\Str; class Status extends Model { @@ -95,16 +97,30 @@ class Status extends Model public function thumb($showNsfw = false) { - $key = $showNsfw ? 'status:thumb:nsfw1'.$this->id : 'status:thumb:nsfw0'.$this->id; - return Cache::remember($key, now()->addMinutes(15), function() use ($showNsfw) { - $type = $this->type ?? $this->setType(); - $is_nsfw = !$showNsfw ? $this->is_nsfw : false; - if ($this->media->count() == 0 || $is_nsfw || !in_array($type,['photo', 'photo:album', 'video'])) { - return url(Storage::url('public/no-preview.png')); - } + $entity = StatusService::get($this->id, false); - return url(Storage::url($this->firstMedia()->thumbnail_path)); - }); + if(!$entity || !isset($entity['media_attachments']) || empty($entity['media_attachments'])) { + return url(Storage::url('public/no-preview.png')); + } + + if((!isset($entity['sensitive']) || $entity['sensitive']) && !$showNsfw) { + return url(Storage::url('public/no-preview.png')); + } + + if(!isset($entity['visibility']) || !in_array($entity['visibility'], ['public', 'unlisted'])) { + return url(Storage::url('public/no-preview.png')); + } + + return collect($entity['media_attachments']) + ->filter(fn($media) => $media['type'] == 'image' && in_array($media['mime'], ['image/jpeg', 'image/png'])) + ->map(function($media) { + if(!Str::endsWith($media['preview_url'], ['no-preview.png', 'no-preview.jpg'])) { + return $media['preview_url']; + } + + return $media['url']; + }) + ->first() ?? url(Storage::url('public/no-preview.png')); } public function url($forceLocal = false) diff --git a/app/Transformer/ActivityPub/ProfileTransformer.php b/app/Transformer/ActivityPub/ProfileTransformer.php index 1df7b6100..45d22cd11 100644 --- a/app/Transformer/ActivityPub/ProfileTransformer.php +++ b/app/Transformer/ActivityPub/ProfileTransformer.php @@ -15,6 +15,7 @@ class ProfileTransformer extends Fractal\TransformerAbstract 'https://w3id.org/security/v1', 'https://www.w3.org/ns/activitystreams', [ + 'toot' => 'http://joinmastodon.org/ns#', 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'alsoKnownAs' => [ '@id' => 'as:alsoKnownAs', @@ -23,7 +24,8 @@ class ProfileTransformer extends Fractal\TransformerAbstract 'movedTo' => [ '@id' => 'as:movedTo', '@type' => '@id' - ] + ], + 'indexable' => 'toot:indexable', ], ], 'id' => $profile->permalink(), @@ -37,6 +39,8 @@ class ProfileTransformer extends Fractal\TransformerAbstract 'summary' => $profile->bio, 'url' => $profile->url(), 'manuallyApprovesFollowers' => (bool) $profile->is_private, + 'indexable' => (bool) $profile->indexable, + 'published' => $profile->created_at->format('Y-m-d') . 'T00:00:00Z', 'publicKey' => [ 'id' => $profile->permalink().'#main-key', 'owner' => $profile->permalink(), diff --git a/app/Transformer/ActivityPub/Verb/CreateNote.php b/app/Transformer/ActivityPub/Verb/CreateNote.php index a9d40d9ed..55fdfa8f4 100644 --- a/app/Transformer/ActivityPub/Verb/CreateNote.php +++ b/app/Transformer/ActivityPub/Verb/CreateNote.php @@ -81,7 +81,8 @@ class CreateNote extends Fractal\TransformerAbstract '@type' => '@id' ], 'toot' => 'http://joinmastodon.org/ns#', - 'Emoji' => 'toot:Emoji' + 'Emoji' => 'toot:Emoji', + 'blurhash' => 'toot:blurhash', ] ], 'id' => $status->permalink(), @@ -103,12 +104,22 @@ class CreateNote extends Fractal\TransformerAbstract 'cc' => $status->scopeToAudience('cc'), 'sensitive' => (bool) $status->is_nsfw, 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { - return [ + $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, diff --git a/app/Transformer/ActivityPub/Verb/Note.php b/app/Transformer/ActivityPub/Verb/Note.php index 777bd22b0..1350641d4 100644 --- a/app/Transformer/ActivityPub/Verb/Note.php +++ b/app/Transformer/ActivityPub/Verb/Note.php @@ -82,7 +82,8 @@ class Note extends Fractal\TransformerAbstract '@type' => '@id' ], 'toot' => 'http://joinmastodon.org/ns#', - 'Emoji' => 'toot:Emoji' + 'Emoji' => 'toot:Emoji', + 'blurhash' => 'toot:blurhash', ] ], 'id' => $status->url(), @@ -97,12 +98,22 @@ class Note extends Fractal\TransformerAbstract 'cc' => $status->scopeToAudience('cc'), 'sensitive' => (bool) $status->is_nsfw, 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { - return [ + $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, diff --git a/app/Transformer/Api/MediaTransformer.php b/app/Transformer/Api/MediaTransformer.php index 0a4600bae..e1eeb1f6f 100644 --- a/app/Transformer/Api/MediaTransformer.php +++ b/app/Transformer/Api/MediaTransformer.php @@ -4,6 +4,7 @@ namespace App\Transformer\Api; use App\Media; use League\Fractal; +use Storage; class MediaTransformer extends Fractal\TransformerAbstract { @@ -28,6 +29,10 @@ class MediaTransformer extends Fractal\TransformerAbstract 'blurhash' => $media->blurhash ?? 'U4Rfzst8?bt7ogayj[j[~pfQ9Goe%Mj[WBay' ]; + if(config('media.hls.enabled') && $media->hls_transcoded_at != null && $media->hls_path) { + $res['hls_manifest'] = url(Storage::url($media->hls_path)); + } + if($media->width && $media->height) { $res['meta'] = [ 'focus' => [ diff --git a/app/Transformer/Api/StatusStatelessTransformer.php b/app/Transformer/Api/StatusStatelessTransformer.php index c21720509..3c2c02d60 100644 --- a/app/Transformer/Api/StatusStatelessTransformer.php +++ b/app/Transformer/Api/StatusStatelessTransformer.php @@ -16,6 +16,7 @@ use App\Services\StatusLabelService; use App\Services\StatusMentionService; use App\Services\PollService; use App\Models\CustomEmoji; +use App\Util\Lexer\Autolink; class StatusStatelessTransformer extends Fractal\TransformerAbstract { @@ -23,6 +24,9 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract { $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 ); return [ '_v' => 1, @@ -34,7 +38,7 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract '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' => $status->rendered ?? $status->caption, + '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), diff --git a/app/Transformer/Api/StatusTransformer.php b/app/Transformer/Api/StatusTransformer.php index d04b025f8..22a840ce0 100644 --- a/app/Transformer/Api/StatusTransformer.php +++ b/app/Transformer/Api/StatusTransformer.php @@ -19,6 +19,7 @@ use Illuminate\Support\Str; use App\Services\PollService; use App\Models\CustomEmoji; use App\Services\BookmarkService; +use App\Util\Lexer\Autolink; class StatusTransformer extends Fractal\TransformerAbstract { @@ -27,6 +28,9 @@ 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; + $rendered = config('exp.autolink') ? + ( $status->caption ? Autolink::create()->autolink($status->caption) : '' ) : + ( $status->rendered ?? $status->caption ); return [ '_v' => 1, @@ -37,7 +41,7 @@ class StatusTransformer extends Fractal\TransformerAbstract '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' => $status->rendered ?? $status->caption, + '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), diff --git a/app/UserFilter.php b/app/UserFilter.php index b0af2d777..dfa0d4662 100644 --- a/app/UserFilter.php +++ b/app/UserFilter.php @@ -33,4 +33,9 @@ class UserFilter extends Model { return $this->belongsTo(Instance::class, 'filterable_id'); } + + public function user() + { + return $this->belongsTo(Profile::class, 'user_id'); + } } diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index 7f47a8fea..612c38d75 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -35,6 +35,7 @@ use App\Services\MediaStorageService; use App\Services\NetworkTimelineService; use App\Jobs\MediaPipeline\MediaStoragePipeline; use App\Jobs\AvatarPipeline\RemoteAvatarFetch; +use App\Jobs\HomeFeedPipeline\FeedInsertRemotePipeline; use App\Util\Media\License; use App\Models\Poll; use Illuminate\Contracts\Cache\LockTimeoutException; @@ -108,7 +109,10 @@ class Helpers { 'string', Rule::in($mimeTypes) ], - '*.name' => 'sometimes|nullable|string' + '*.name' => 'sometimes|nullable|string', + '*.blurhash' => 'sometimes|nullable|string|min:6|max:164', + '*.width' => 'sometimes|nullable|integer|min:1|max:5000', + '*.height' => 'sometimes|nullable|integer|min:1|max:5000', ])->passes(); return $valid; @@ -276,7 +280,7 @@ class Helpers { } if(is_array($val)) { - return !empty($val) ? $val[0] : null; + return !empty($val) ? head($val) : null; } return null; @@ -534,6 +538,12 @@ class Helpers { IncrementPostCount::dispatch($pid)->onQueue('low'); + if( $status->in_reply_to_id === null && + in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) + ) { + FeedInsertRemotePipeline::dispatch($status->id, $pid)->onQueue('feed'); + } + return $status; } @@ -684,6 +694,8 @@ class Helpers { $blurhash = isset($media['blurhash']) ? $media['blurhash'] : null; $license = isset($media['license']) ? License::nameToId($media['license']) : null; $caption = isset($media['name']) ? Purify::clean($media['name']) : null; + $width = isset($media['width']) ? $media['width'] : false; + $height = isset($media['height']) ? $media['height'] : false; $media = new Media(); $media->blurhash = $blurhash; @@ -695,6 +707,12 @@ class Helpers { $media->remote_url = $url; $media->caption = $caption; $media->order = $key + 1; + if($width) { + $media->width = $width; + } + if($height) { + $media->height = $height; + } if($license) { $media->license = $license; } @@ -749,6 +767,13 @@ class Helpers { if(!isset($res['preferredUsername']) && !isset($res['nickname'])) { return; } + // skip invalid usernames + if(!ctype_alnum($res['preferredUsername'])) { + $tmpUsername = str_replace(['_', '.', '-'], '', $res['preferredUsername']); + if(!ctype_alnum($tmpUsername)) { + return; + } + } $username = (string) Purify::clean($res['preferredUsername'] ?? $res['nickname']); if(empty($username)) { return; @@ -785,11 +810,12 @@ class Helpers { 'inbox_url' => $res['inbox'], 'outbox_url' => isset($res['outbox']) ? $res['outbox'] : null, 'public_key' => $res['publicKey']['publicKeyPem'], + 'indexable' => isset($res['indexable']) && is_bool($res['indexable']) ? $res['indexable'] : false, ] ); if( $profile->last_fetched_at == null || - $profile->last_fetched_at->lt(now()->subHours(24)) + $profile->last_fetched_at->lt(now()->subMonths(3)) ) { RemoteAvatarFetch::dispatch($profile); } diff --git a/app/Util/ActivityPub/Inbox.php b/app/Util/ActivityPub/Inbox.php index 4441becfb..e26f0a48c 100644 --- a/app/Util/ActivityPub/Inbox.php +++ b/app/Util/ActivityPub/Inbox.php @@ -4,20 +4,20 @@ namespace App\Util\ActivityPub; use Cache, DB, Log, Purify, Redis, Storage, Validator; use App\{ - Activity, - DirectMessage, - Follower, - FollowRequest, - Instance, - Like, - Notification, - Media, - Profile, - Status, - StatusHashtag, - Story, - StoryView, - UserFilter + Activity, + DirectMessage, + Follower, + FollowRequest, + Instance, + Like, + Notification, + Media, + Profile, + Status, + StatusHashtag, + Story, + StoryView, + UserFilter }; use Carbon\Carbon; use App\Util\ActivityPub\Helpers; @@ -39,6 +39,7 @@ use App\Util\ActivityPub\Validator\Like as LikeValidator; use App\Util\ActivityPub\Validator\UndoFollow as UndoFollowValidator; use App\Util\ActivityPub\Validator\UpdatePersonValidator; +use App\Services\AccountService; use App\Services\PollService; use App\Services\FollowerService; use App\Services\ReblogService; @@ -49,1189 +50,1263 @@ use App\Models\Conversation; use App\Models\RemoteReport; use App\Jobs\ProfilePipeline\IncrementPostCount; use App\Jobs\ProfilePipeline\DecrementPostCount; +use App\Jobs\HomeFeedPipeline\FeedRemoveRemotePipeline; class Inbox { - protected $headers; - protected $profile; - protected $payload; - protected $logger; + protected $headers; + protected $profile; + protected $payload; + protected $logger; - public function __construct($headers, $profile, $payload) - { - $this->headers = $headers; - $this->profile = $profile; - $this->payload = $payload; - } + public function __construct($headers, $profile, $payload) + { + $this->headers = $headers; + $this->profile = $profile; + $this->payload = $payload; + } - public function handle() - { - $this->handleVerb(); - return; - } + public function handle() + { + $this->handleVerb(); + return; + } - public function handleVerb() - { - $verb = (string) $this->payload['type']; - switch ($verb) { + public function handleVerb() + { + $verb = (string) $this->payload['type']; + switch ($verb) { - case 'Add': - $this->handleAddActivity(); - break; + case 'Add': + $this->handleAddActivity(); + break; - case 'Create': - $this->handleCreateActivity(); - break; + case 'Create': + $this->handleCreateActivity(); + break; - case 'Follow': - if(FollowValidator::validate($this->payload) == false) { return; } - $this->handleFollowActivity(); - break; + case 'Follow': + if(FollowValidator::validate($this->payload) == false) { return; } + $this->handleFollowActivity(); + break; - case 'Announce': - if(AnnounceValidator::validate($this->payload) == false) { return; } - $this->handleAnnounceActivity(); - break; + case 'Announce': + if(AnnounceValidator::validate($this->payload) == false) { return; } + $this->handleAnnounceActivity(); + break; - case 'Accept': - if(AcceptValidator::validate($this->payload) == false) { return; } - $this->handleAcceptActivity(); - break; + case 'Accept': + if(AcceptValidator::validate($this->payload) == false) { return; } + $this->handleAcceptActivity(); + break; - case 'Delete': - $this->handleDeleteActivity(); - break; + case 'Delete': + $this->handleDeleteActivity(); + break; - case 'Like': - if(LikeValidator::validate($this->payload) == false) { return; } - $this->handleLikeActivity(); - break; + case 'Like': + if(LikeValidator::validate($this->payload) == false) { return; } + $this->handleLikeActivity(); + break; - case 'Reject': - $this->handleRejectActivity(); - break; + case 'Reject': + $this->handleRejectActivity(); + break; - case 'Undo': - $this->handleUndoActivity(); - break; + case 'Undo': + $this->handleUndoActivity(); + break; - case 'View': - $this->handleViewActivity(); - break; + case 'View': + $this->handleViewActivity(); + break; - case 'Story:Reaction': - $this->handleStoryReactionActivity(); - break; + case 'Story:Reaction': + $this->handleStoryReactionActivity(); + break; - case 'Story:Reply': - $this->handleStoryReplyActivity(); - break; + case 'Story:Reply': + $this->handleStoryReplyActivity(); + break; - case 'Flag': - $this->handleFlagActivity(); - break; + case 'Flag': + $this->handleFlagActivity(); + break; - case 'Update': - $this->handleUpdateActivity(); - break; + case 'Update': + $this->handleUpdateActivity(); + break; - default: - // TODO: decide how to handle invalid verbs. - break; - } - } + default: + // TODO: decide how to handle invalid verbs. + break; + } + } - public function verifyNoteAttachment() - { - $activity = $this->payload['object']; + public function verifyNoteAttachment() + { + $activity = $this->payload['object']; - if(isset($activity['inReplyTo']) && - !empty($activity['inReplyTo']) && - Helpers::validateUrl($activity['inReplyTo']) - ) { - // reply detected, skip attachment check - return true; - } + if(isset($activity['inReplyTo']) && + !empty($activity['inReplyTo']) && + Helpers::validateUrl($activity['inReplyTo']) + ) { + // reply detected, skip attachment check + return true; + } - $valid = Helpers::verifyAttachments($activity); + $valid = Helpers::verifyAttachments($activity); - return $valid; - } + return $valid; + } - public function actorFirstOrCreate($actorUrl) - { - return Helpers::profileFetch($actorUrl); - } + public function actorFirstOrCreate($actorUrl) + { + return Helpers::profileFetch($actorUrl); + } - public function handleAddActivity() - { - // stories ;) + public function handleAddActivity() + { + // stories ;) - if(!isset( - $this->payload['actor'], - $this->payload['object'] - )) { - return; - } + if(!isset( + $this->payload['actor'], + $this->payload['object'] + )) { + return; + } - $actor = $this->payload['actor']; - $obj = $this->payload['object']; + $actor = $this->payload['actor']; + $obj = $this->payload['object']; - if(!Helpers::validateUrl($actor)) { - return; - } + if(!Helpers::validateUrl($actor)) { + return; + } - if(!isset($obj['type'])) { - return; - } + if(!isset($obj['type'])) { + return; + } - switch($obj['type']) { - case 'Story': - StoryFetch::dispatch($this->payload); - break; - } + switch($obj['type']) { + case 'Story': + StoryFetch::dispatch($this->payload); + break; + } - return; - } + return; + } - public function handleCreateActivity() - { - $activity = $this->payload['object']; - $actor = $this->actorFirstOrCreate($this->payload['actor']); - if(!$actor || $actor->domain == null) { - return; - } + public function handleCreateActivity() + { + $activity = $this->payload['object']; + $actor = $this->actorFirstOrCreate($this->payload['actor']); + if(!$actor || $actor->domain == null) { + return; + } - if(!isset($activity['to'])) { - return; - } - $to = isset($activity['to']) ? $activity['to'] : []; - $cc = isset($activity['cc']) ? $activity['cc'] : []; + if(!isset($activity['to'])) { + return; + } + $to = isset($activity['to']) ? $activity['to'] : []; + $cc = isset($activity['cc']) ? $activity['cc'] : []; - if($activity['type'] == 'Question') { - $this->handlePollCreate(); - return; - } + if($activity['type'] == 'Question') { + $this->handlePollCreate(); + return; + } - if( is_array($to) && - is_array($cc) && - count($to) == 1 && - count($cc) == 0 && - parse_url($to[0], PHP_URL_HOST) == config('pixelfed.domain.app') - ) { - $this->handleDirectMessage(); - return; - } + if( is_array($to) && + is_array($cc) && + count($to) == 1 && + count($cc) == 0 && + parse_url($to[0], PHP_URL_HOST) == config('pixelfed.domain.app') + ) { + $this->handleDirectMessage(); + return; + } - if($activity['type'] == 'Note' && !empty($activity['inReplyTo'])) { - $this->handleNoteReply(); + if($activity['type'] == 'Note' && !empty($activity['inReplyTo'])) { + $this->handleNoteReply(); - } elseif ($activity['type'] == 'Note' && !empty($activity['attachment'])) { - if(!$this->verifyNoteAttachment()) { - return; - } - $this->handleNoteCreate(); - } - return; - } + } elseif ($activity['type'] == 'Note' && !empty($activity['attachment'])) { + if(!$this->verifyNoteAttachment()) { + return; + } + $this->handleNoteCreate(); + } + return; + } - public function handleNoteReply() - { - $activity = $this->payload['object']; - $actor = $this->actorFirstOrCreate($this->payload['actor']); - if(!$actor || $actor->domain == null) { - return; - } + public function handleNoteReply() + { + $activity = $this->payload['object']; + $actor = $this->actorFirstOrCreate($this->payload['actor']); + if(!$actor || $actor->domain == null) { + return; + } - $inReplyTo = $activity['inReplyTo']; - $url = isset($activity['url']) ? $activity['url'] : $activity['id']; + $inReplyTo = $activity['inReplyTo']; + $url = isset($activity['url']) ? $activity['url'] : $activity['id']; - Helpers::statusFirstOrFetch($url, true); - return; - } + Helpers::statusFirstOrFetch($url, true); + return; + } - public function handlePollCreate() - { - $activity = $this->payload['object']; - $actor = $this->actorFirstOrCreate($this->payload['actor']); - if(!$actor || $actor->domain == null) { - return; - } - $url = isset($activity['url']) ? $activity['url'] : $activity['id']; - Helpers::statusFirstOrFetch($url); - return; - } + public function handlePollCreate() + { + $activity = $this->payload['object']; + $actor = $this->actorFirstOrCreate($this->payload['actor']); + if(!$actor || $actor->domain == null) { + return; + } + $url = isset($activity['url']) ? $activity['url'] : $activity['id']; + Helpers::statusFirstOrFetch($url); + return; + } - public function handleNoteCreate() - { - $activity = $this->payload['object']; - $actor = $this->actorFirstOrCreate($this->payload['actor']); - if(!$actor || $actor->domain == null) { - return; - } + public function handleNoteCreate() + { + $activity = $this->payload['object']; + $actor = $this->actorFirstOrCreate($this->payload['actor']); + if(!$actor || $actor->domain == null) { + return; + } - if( isset($activity['inReplyTo']) && - isset($activity['name']) && - !isset($activity['content']) && - !isset($activity['attachment']) && - Helpers::validateLocalUrl($activity['inReplyTo']) - ) { - $this->handlePollVote(); - return; - } + if( isset($activity['inReplyTo']) && + isset($activity['name']) && + !isset($activity['content']) && + !isset($activity['attachment']) && + Helpers::validateLocalUrl($activity['inReplyTo']) + ) { + $this->handlePollVote(); + return; + } - if($actor->followers_count == 0) { + if($actor->followers_count == 0) { if(config('federation.activitypub.ingest.store_notes_without_followers')) { } else if(FollowerService::followerCount($actor->id, true) == 0) { - return; - } - } - - $hasUrl = isset($activity['url']); - $url = isset($activity['url']) ? $activity['url'] : $activity['id']; - - if($hasUrl) { - if(Status::whereUri($url)->exists()) { - return; - } - } else { - if(Status::whereObjectUrl($url)->exists()) { - return; - } - } - - Helpers::storeStatus( - $url, - $actor, - $activity - ); - return; - } - - public function handlePollVote() - { - $activity = $this->payload['object']; - $actor = $this->actorFirstOrCreate($this->payload['actor']); - - if(!$actor) { - return; - } - - $status = Helpers::statusFetch($activity['inReplyTo']); - - if(!$status) { - return; - } - - $poll = $status->poll; - - if(!$poll) { - return; - } - - if(now()->gt($poll->expires_at)) { - return; - } - - $choices = $poll->poll_options; - $choice = array_search($activity['name'], $choices); - - if($choice === false) { - return; - } - - if(PollVote::whereStatusId($status->id)->whereProfileId($actor->id)->exists()) { - return; - } - - $vote = new PollVote; - $vote->status_id = $status->id; - $vote->profile_id = $actor->id; - $vote->poll_id = $poll->id; - $vote->choice = $choice; - $vote->uri = isset($activity['id']) ? $activity['id'] : null; - $vote->save(); - - $tallies = $poll->cached_tallies; - $tallies[$choice] = $tallies[$choice] + 1; - $poll->cached_tallies = $tallies; - $poll->votes_count = array_sum($tallies); - $poll->save(); - - PollService::del($status->id); - - return; - } - - public function handleDirectMessage() - { - $activity = $this->payload['object']; - $actor = $this->actorFirstOrCreate($this->payload['actor']); - $profile = Profile::whereNull('domain') - ->whereUsername(array_last(explode('/', $activity['to'][0]))) - ->firstOrFail(); - - if(in_array($actor->id, $profile->blockedIds()->toArray())) { - return; - } - - $msg = $activity['content']; - $msgText = strip_tags($activity['content']); - - if(Str::startsWith($msgText, '@' . $profile->username)) { - $len = strlen('@' . $profile->username); - $msgText = substr($msgText, $len + 1); - } - - if($profile->user->settings->public_dm == false || $profile->is_private) { - if($profile->follows($actor) == true) { - $hidden = false; - } else { - $hidden = true; - } - } else { - $hidden = false; - } - - $status = new Status; - $status->profile_id = $actor->id; - $status->caption = $msgText; - $status->rendered = $msg; - $status->visibility = 'direct'; - $status->scope = 'direct'; - $status->url = $activity['id']; - $status->in_reply_to_profile_id = $profile->id; - $status->save(); - - $dm = new DirectMessage; - $dm->to_id = $profile->id; - $dm->from_id = $actor->id; - $dm->status_id = $status->id; - $dm->is_hidden = $hidden; - $dm->type = 'text'; - $dm->save(); - - Conversation::updateOrInsert( - [ - 'to_id' => $profile->id, - 'from_id' => $actor->id - ], - [ - 'type' => 'text', - 'status_id' => $status->id, - 'dm_id' => $dm->id, - 'is_hidden' => $hidden - ] - ); - - if(count($activity['attachment'])) { - $photos = 0; - $videos = 0; - $allowed = explode(',', config_cache('pixelfed.media_types')); - $activity['attachment'] = array_slice($activity['attachment'], 0, config_cache('pixelfed.max_album_length')); - foreach($activity['attachment'] as $a) { - $type = $a['mediaType']; - $url = $a['url']; - $valid = Helpers::validateUrl($url); - if(in_array($type, $allowed) == false || $valid == false) { - continue; - } - - $media = new Media(); - $media->remote_media = true; - $media->status_id = $status->id; - $media->profile_id = $status->profile_id; - $media->user_id = null; - $media->media_path = $url; - $media->remote_url = $url; - $media->mime = $type; - $media->save(); - if(explode('/', $type)[0] == 'image') { - $photos = $photos + 1; - } - if(explode('/', $type)[0] == 'video') { - $videos = $videos + 1; - } - } - - if($photos && $videos == 0) { - $dm->type = $photos == 1 ? 'photo' : 'photos'; - $dm->save(); - } - if($videos && $photos == 0) { - $dm->type = $videos == 1 ? 'video' : 'videos'; - $dm->save(); - } - } - - if(filter_var($msgText, FILTER_VALIDATE_URL)) { - if(Helpers::validateUrl($msgText)) { - $dm->type = 'link'; - $dm->meta = [ - 'domain' => parse_url($msgText, PHP_URL_HOST), - 'local' => parse_url($msgText, PHP_URL_HOST) == - parse_url(config('app.url'), PHP_URL_HOST) - ]; - $dm->save(); - } - } - - $nf = UserFilter::whereUserId($profile->id) - ->whereFilterableId($actor->id) - ->whereFilterableType('App\Profile') - ->whereFilterType('dm.mute') - ->exists(); - - if($profile->domain == null && $hidden == false && !$nf) { - $notification = new Notification(); - $notification->profile_id = $profile->id; - $notification->actor_id = $actor->id; - $notification->action = 'dm'; - $notification->item_id = $dm->id; - $notification->item_type = "App\DirectMessage"; - $notification->save(); - } - - return; - } - - public function handleFollowActivity() - { - $actor = $this->actorFirstOrCreate($this->payload['actor']); - $target = $this->actorFirstOrCreate($this->payload['object']); - if(!$actor || !$target) { - return; - } - - if($actor->domain == null || $target->domain !== null) { - return; - } - - if( - Follower::whereProfileId($actor->id) - ->whereFollowingId($target->id) - ->exists() || - FollowRequest::whereFollowerId($actor->id) - ->whereFollowingId($target->id) - ->exists() - ) { - return; - } - - $blocks = UserFilterService::blocks($target->id); - if($blocks && in_array($actor->id, $blocks)) { - return; - } - - if($target->is_private == true) { - FollowRequest::updateOrCreate([ - 'follower_id' => $actor->id, - 'following_id' => $target->id, - ],[ - 'activity' => collect($this->payload)->only(['id','actor','object','type'])->toArray() - ]); - } else { - $follower = new Follower; - $follower->profile_id = $actor->id; - $follower->following_id = $target->id; - $follower->local_profile = empty($actor->domain); - $follower->save(); - - FollowPipeline::dispatch($follower); - FollowerService::add($actor->id, $target->id); - - // send Accept to remote profile - $accept = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $target->permalink().'#accepts/follows/' . $follower->id, - 'type' => 'Accept', - 'actor' => $target->permalink(), - 'object' => [ - 'id' => $this->payload['id'], - 'actor' => $actor->permalink(), - 'type' => 'Follow', - 'object' => $target->permalink() - ] - ]; - Helpers::sendSignedObject($target, $actor->inbox_url, $accept); - Cache::forget('profile:follower_count:'.$target->id); - Cache::forget('profile:follower_count:'.$actor->id); - Cache::forget('profile:following_count:'.$target->id); - Cache::forget('profile:following_count:'.$actor->id); - } - - return; - } - - public function handleAnnounceActivity() - { - $actor = $this->actorFirstOrCreate($this->payload['actor']); - $activity = $this->payload['object']; - - if(!$actor || $actor->domain == null) { - return; - } - - $parent = Helpers::statusFetch($activity); - - if(!$parent || empty($parent)) { - return; - } - - $blocks = UserFilterService::blocks($parent->profile_id); - if($blocks && in_array($actor->id, $blocks)) { - return; - } - - $status = Status::firstOrCreate([ - 'profile_id' => $actor->id, - 'reblog_of_id' => $parent->id, - 'type' => 'share' - ]); - - Notification::firstOrCreate( - [ - 'profile_id' => $parent->profile_id, - 'actor_id' => $actor->id, - 'action' => 'share', - 'item_id' => $parent->id, - 'item_type' => 'App\Status', - ] - ); - - $parent->reblogs_count = $parent->reblogs_count + 1; - $parent->save(); - - ReblogService::addPostReblog($parent->profile_id, $status->id); - - return; - } - - public function handleAcceptActivity() - { - $actor = $this->payload['object']['actor']; - $obj = $this->payload['object']['object']; - $type = $this->payload['object']['type']; - - if($type !== 'Follow') { - return; - } - - $actor = Helpers::validateLocalUrl($actor); - $target = Helpers::validateUrl($obj); - - if(!$actor || !$target) { - return; - } - - $actor = Helpers::profileFetch($actor); - $target = Helpers::profileFetch($target); - - if(!$actor || !$target) { - return; - } - - $request = FollowRequest::whereFollowerId($actor->id) - ->whereFollowingId($target->id) - ->whereIsRejected(false) - ->first(); - - if(!$request) { - return; - } - - $follower = Follower::firstOrCreate([ - 'profile_id' => $actor->id, - 'following_id' => $target->id, - ]); - FollowPipeline::dispatch($follower); - - $request->delete(); - - return; - } - - public function handleDeleteActivity() - { - if(!isset( - $this->payload['actor'], - $this->payload['object'] - )) { - return; - } - $actor = $this->payload['actor']; - $obj = $this->payload['object']; - if(is_string($obj) == true && $actor == $obj && Helpers::validateUrl($obj)) { - $profile = Profile::whereRemoteUrl($obj)->first(); - if(!$profile || $profile->private_key != null) { - return; - } - DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('inbox'); - return; - } else { - if(!isset( - $obj['id'], - $this->payload['object'], - $this->payload['object']['id'], - $this->payload['object']['type'] - )) { - return; - } - $type = $this->payload['object']['type']; - $typeCheck = in_array($type, ['Person', 'Tombstone', 'Story']); - if(!Helpers::validateUrl($actor) || !Helpers::validateUrl($obj['id']) || !$typeCheck) { - return; - } - if(parse_url($obj['id'], PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) { - return; - } - $id = $this->payload['object']['id']; - switch ($type) { - case 'Person': - $profile = Profile::whereRemoteUrl($actor)->first(); - if(!$profile || $profile->private_key != null) { - return; - } - DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('inbox'); - return; - break; - - case 'Tombstone': - $profile = Profile::whereRemoteUrl($actor)->first(); - if(!$profile || $profile->private_key != null) { - return; - } - $status = Status::whereProfileId($profile->id) - ->whereObjectUrl($id) - ->first(); - if(!$status) { - return; - } - RemoteStatusDelete::dispatch($status)->onQueue('high'); - return; - break; - - case 'Story': - $story = Story::whereObjectId($id) - ->first(); - if($story) { - StoryExpire::dispatch($story)->onQueue('story'); - } - return; - break; - - default: - return; - break; - } - } - return; - } - - public function handleLikeActivity() - { - $actor = $this->payload['actor']; - - if(!Helpers::validateUrl($actor)) { - return; - } - - $profile = self::actorFirstOrCreate($actor); - $obj = $this->payload['object']; - if(!Helpers::validateUrl($obj)) { - return; - } - $status = Helpers::statusFirstOrFetch($obj); - if(!$status || !$profile) { - return; - } - - $blocks = UserFilterService::blocks($status->profile_id); - if($blocks && in_array($profile->id, $blocks)) { - return; - } - - $like = Like::firstOrCreate([ - 'profile_id' => $profile->id, - 'status_id' => $status->id - ]); - - if($like->wasRecentlyCreated == true) { - $status->likes_count = $status->likes_count + 1; - $status->save(); - LikePipeline::dispatch($like); - } - - return; - } - - public function handleRejectActivity() - { - } - - public function handleUndoActivity() - { - $actor = $this->payload['actor']; - $profile = self::actorFirstOrCreate($actor); - $obj = $this->payload['object']; - - if(!$profile) { - return; - } - // TODO: Some implementations do not inline the object, skip for now - if(!$obj || !is_array($obj) || !isset($obj['type'])) { - return; - } - - switch ($obj['type']) { - case 'Accept': - break; - - case 'Announce': - if(is_array($obj) && isset($obj['object'])) { - $obj = $obj['object']; - } - if(!is_string($obj)) { - return; - } - if(Helpers::validateLocalUrl($obj)) { - $parsedId = last(explode('/', $obj)); - $status = Status::find($parsedId); - } else { - $status = Status::whereUri($obj)->first(); - } - if(!$status) { - return; - } - Status::whereProfileId($profile->id) - ->whereReblogOfId($status->id) - ->delete(); - ReblogService::removePostReblog($profile->id, $status->id); - Notification::whereProfileId($status->profile_id) - ->whereActorId($profile->id) - ->whereAction('share') - ->whereItemId($status->reblog_of_id) - ->whereItemType('App\Status') - ->forceDelete(); - break; - - case 'Block': - break; - - case 'Follow': - $following = self::actorFirstOrCreate($obj['object']); - if(!$following) { - return; - } - Follower::whereProfileId($profile->id) - ->whereFollowingId($following->id) - ->delete(); - Notification::whereProfileId($following->id) - ->whereActorId($profile->id) - ->whereAction('follow') - ->whereItemId($following->id) - ->whereItemType('App\Profile') - ->forceDelete(); - FollowerService::remove($profile->id, $following->id); - break; - - case 'Like': - $objectUri = $obj['object']; - if(!is_string($objectUri)) { - if(is_array($objectUri) && isset($objectUri['id']) && is_string($objectUri['id'])) { - $objectUri = $objectUri['id']; - } else { - return; - } - } - $status = Helpers::statusFirstOrFetch($objectUri); - if(!$status) { - return; - } - Like::whereProfileId($profile->id) - ->whereStatusId($status->id) - ->forceDelete(); - Notification::whereProfileId($status->profile_id) - ->whereActorId($profile->id) - ->whereAction('like') - ->whereItemId($status->id) - ->whereItemType('App\Status') - ->forceDelete(); - break; - } - return; - } - - public function handleViewActivity() - { - if(!isset( - $this->payload['actor'], - $this->payload['object'] - )) { - return; - } - - $actor = $this->payload['actor']; - $obj = $this->payload['object']; - - if(!Helpers::validateUrl($actor)) { - return; - } - - if(!$obj || !is_array($obj)) { - return; - } - - if(!isset($obj['type']) || !isset($obj['object']) || $obj['type'] != 'Story') { - return; - } - - if(!Helpers::validateLocalUrl($obj['object'])) { - return; - } - - $profile = Helpers::profileFetch($actor); - $storyId = Str::of($obj['object'])->explode('/')->last(); - - $story = Story::whereActive(true) - ->whereLocal(true) - ->find($storyId); - - if(!$story) { - return; - } - - if(!FollowerService::follows($profile->id, $story->profile_id)) { - return; - } - - $view = StoryView::firstOrCreate([ - 'story_id' => $story->id, - 'profile_id' => $profile->id - ]); - - if($view->wasRecentlyCreated == true) { - $story->view_count++; - $story->save(); - } - - return; - } - - public function handleStoryReactionActivity() - { - if(!isset( - $this->payload['actor'], - $this->payload['id'], - $this->payload['inReplyTo'], - $this->payload['content'] - )) { - return; - } - - $id = $this->payload['id']; - $actor = $this->payload['actor']; - $storyUrl = $this->payload['inReplyTo']; - $to = $this->payload['to']; - $text = Purify::clean($this->payload['content']); - - if(parse_url($id, PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) { - return; - } - - if(!Helpers::validateUrl($id) || !Helpers::validateUrl($actor)) { - return; - } - - if(!Helpers::validateLocalUrl($storyUrl)) { - return; - } - - if(!Helpers::validateLocalUrl($to)) { - return; - } - - if(Status::whereObjectUrl($id)->exists()) { - return; - } - - $storyId = Str::of($storyUrl)->explode('/')->last(); - $targetProfile = Helpers::profileFetch($to); - - $story = Story::whereProfileId($targetProfile->id) - ->find($storyId); - - if(!$story) { - return; - } - - if($story->can_react == false) { - return; - } - - $actorProfile = Helpers::profileFetch($actor); - - if(!FollowerService::follows($actorProfile->id, $targetProfile->id)) { - return; - } - - $status = new Status; - $status->profile_id = $actorProfile->id; - $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; - $status->entities = json_encode([ - 'story_id' => $story->id, - 'reaction' => $text - ]); - $status->save(); - - $dm = new DirectMessage; - $dm->to_id = $story->profile_id; - $dm->from_id = $actorProfile->id; - $dm->type = 'story:react'; - $dm->status_id = $status->id; - $dm->meta = json_encode([ - 'story_username' => $targetProfile->username, - 'story_actor_username' => $actorProfile->username, - 'story_id' => $story->id, - 'story_media_url' => url(Storage::url($story->path)), - 'reaction' => $text - ]); - $dm->save(); - - Conversation::updateOrInsert( - [ - 'to_id' => $story->profile_id, - 'from_id' => $actorProfile->id - ], - [ - 'type' => 'story:react', - 'status_id' => $status->id, - 'dm_id' => $dm->id, - 'is_hidden' => false - ] - ); - - $n = new Notification; - $n->profile_id = $dm->to_id; - $n->actor_id = $dm->from_id; - $n->item_id = $dm->id; - $n->item_type = 'App\DirectMessage'; - $n->action = 'story:react'; - $n->save(); - - return; - } - - public function handleStoryReplyActivity() - { - if(!isset( - $this->payload['actor'], - $this->payload['id'], - $this->payload['inReplyTo'], - $this->payload['content'] - )) { - return; - } - - $id = $this->payload['id']; - $actor = $this->payload['actor']; - $storyUrl = $this->payload['inReplyTo']; - $to = $this->payload['to']; - $text = Purify::clean($this->payload['content']); - - if(parse_url($id, PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) { - return; - } - - if(!Helpers::validateUrl($id) || !Helpers::validateUrl($actor)) { - return; - } - - if(!Helpers::validateLocalUrl($storyUrl)) { - return; - } - - if(!Helpers::validateLocalUrl($to)) { - return; - } - - if(Status::whereObjectUrl($id)->exists()) { - return; - } - - $storyId = Str::of($storyUrl)->explode('/')->last(); - $targetProfile = Helpers::profileFetch($to); - - $story = Story::whereProfileId($targetProfile->id) - ->find($storyId); - - if(!$story) { - return; - } - - if($story->can_react == false) { - return; - } - - $actorProfile = Helpers::profileFetch($actor); - - if(!FollowerService::follows($actorProfile->id, $targetProfile->id)) { - return; - } - - $status = new Status; - $status->profile_id = $actorProfile->id; - $status->type = 'story:reply'; - $status->caption = $text; - $status->rendered = $text; - $status->scope = 'direct'; - $status->visibility = 'direct'; - $status->in_reply_to_profile_id = $story->profile_id; - $status->entities = json_encode([ - 'story_id' => $story->id, - 'caption' => $text - ]); - $status->save(); - - $dm = new DirectMessage; - $dm->to_id = $story->profile_id; - $dm->from_id = $actorProfile->id; - $dm->type = 'story:comment'; - $dm->status_id = $status->id; - $dm->meta = json_encode([ - 'story_username' => $targetProfile->username, - 'story_actor_username' => $actorProfile->username, - 'story_id' => $story->id, - 'story_media_url' => url(Storage::url($story->path)), - 'caption' => $text - ]); - $dm->save(); - - Conversation::updateOrInsert( - [ - 'to_id' => $story->profile_id, - 'from_id' => $actorProfile->id - ], - [ - 'type' => 'story:comment', - 'status_id' => $status->id, - 'dm_id' => $dm->id, - 'is_hidden' => false - ] - ); - - $n = new Notification; - $n->profile_id = $dm->to_id; - $n->actor_id = $dm->from_id; - $n->item_id = $dm->id; - $n->item_type = 'App\DirectMessage'; - $n->action = 'story:comment'; - $n->save(); - - return; - } - - public function handleFlagActivity() - { - if(!isset( - $this->payload['id'], - $this->payload['type'], - $this->payload['actor'], - $this->payload['object'] - )) { - return; - } - - $id = $this->payload['id']; - $actor = $this->payload['actor']; - - if(Helpers::validateLocalUrl($id) || parse_url($id, PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) { - return; - } - - $content = isset($this->payload['content']) ? Purify::clean($this->payload['content']) : null; - $object = $this->payload['object']; - - if(empty($object) || (!is_array($object) && !is_string($object))) { - return; - } - - if(is_array($object) && count($object) > 100) { - return; - } - - $objects = collect([]); - $accountId = null; - - foreach($object as $objectUrl) { - if(!Helpers::validateLocalUrl($objectUrl)) { - continue; - } - - if(str_contains($objectUrl, '/users/')) { - $username = last(explode('/', $objectUrl)); - $profileId = Profile::whereUsername($username)->first(); - if($profileId) { - $accountId = $profileId->id; - } - } else if(str_contains($objectUrl, '/p/')) { - $postId = last(explode('/', $objectUrl)); - $objects->push($postId); - } else { - continue; - } - } - - if(!$accountId || !$objects->count()) { - return; - } - - $instanceHost = parse_url($id, PHP_URL_HOST); - - $instance = Instance::updateOrCreate([ - 'domain' => $instanceHost - ]); - - $report = new RemoteReport; - $report->status_ids = $objects->toArray(); - $report->comment = $content; - $report->account_id = $accountId; - $report->uri = $id; - $report->instance_id = $instance->id; - $report->report_meta = [ - 'actor' => $actor, - 'object' => $object - ]; - $report->save(); - - return; - } - - public function handleUpdateActivity() - { - $activity = $this->payload['object']; - - if(!isset($activity['type'], $activity['id'])) { - return; - } - - if(!Helpers::validateUrl($activity['id'])) { - return; - } - - if($activity['type'] === 'Note') { - if(Status::whereObjectUrl($activity['id'])->exists()) { - StatusRemoteUpdatePipeline::dispatch($activity); - } - } else if ($activity['type'] === 'Person') { - if(UpdatePersonValidator::validate($this->payload)) { - HandleUpdateActivity::dispatch($this->payload)->onQueue('low'); - } - } - } + return; + } + } + + $hasUrl = isset($activity['url']); + $url = isset($activity['url']) ? $activity['url'] : $activity['id']; + + if($hasUrl) { + if(Status::whereUri($url)->exists()) { + return; + } + } else { + if(Status::whereObjectUrl($url)->exists()) { + return; + } + } + + Helpers::storeStatus( + $url, + $actor, + $activity + ); + return; + } + + public function handlePollVote() + { + $activity = $this->payload['object']; + $actor = $this->actorFirstOrCreate($this->payload['actor']); + + if(!$actor) { + return; + } + + $status = Helpers::statusFetch($activity['inReplyTo']); + + if(!$status) { + return; + } + + $poll = $status->poll; + + if(!$poll) { + return; + } + + if(now()->gt($poll->expires_at)) { + return; + } + + $choices = $poll->poll_options; + $choice = array_search($activity['name'], $choices); + + if($choice === false) { + return; + } + + if(PollVote::whereStatusId($status->id)->whereProfileId($actor->id)->exists()) { + return; + } + + $vote = new PollVote; + $vote->status_id = $status->id; + $vote->profile_id = $actor->id; + $vote->poll_id = $poll->id; + $vote->choice = $choice; + $vote->uri = isset($activity['id']) ? $activity['id'] : null; + $vote->save(); + + $tallies = $poll->cached_tallies; + $tallies[$choice] = $tallies[$choice] + 1; + $poll->cached_tallies = $tallies; + $poll->votes_count = array_sum($tallies); + $poll->save(); + + PollService::del($status->id); + + return; + } + + public function handleDirectMessage() + { + $activity = $this->payload['object']; + $actor = $this->actorFirstOrCreate($this->payload['actor']); + $profile = Profile::whereNull('domain') + ->whereUsername(array_last(explode('/', $activity['to'][0]))) + ->firstOrFail(); + + if(!$actor || in_array($actor->id, $profile->blockedIds()->toArray())) { + return; + } + + if(AccountService::blocksDomain($profile->id, $actor->domain) == true) { + return; + } + + $msg = $activity['content']; + $msgText = strip_tags($activity['content']); + + if(Str::startsWith($msgText, '@' . $profile->username)) { + $len = strlen('@' . $profile->username); + $msgText = substr($msgText, $len + 1); + } + + if($profile->user->settings->public_dm == false || $profile->is_private) { + if($profile->follows($actor) == true) { + $hidden = false; + } else { + $hidden = true; + } + } else { + $hidden = false; + } + + $status = new Status; + $status->profile_id = $actor->id; + $status->caption = $msgText; + $status->rendered = $msg; + $status->visibility = 'direct'; + $status->scope = 'direct'; + $status->url = $activity['id']; + $status->uri = $activity['id']; + $status->object_url = $activity['id']; + $status->in_reply_to_profile_id = $profile->id; + $status->saveQuietly(); + + $dm = new DirectMessage; + $dm->to_id = $profile->id; + $dm->from_id = $actor->id; + $dm->status_id = $status->id; + $dm->is_hidden = $hidden; + $dm->type = 'text'; + $dm->save(); + + Conversation::updateOrInsert( + [ + 'to_id' => $profile->id, + 'from_id' => $actor->id + ], + [ + 'type' => 'text', + 'status_id' => $status->id, + 'dm_id' => $dm->id, + 'is_hidden' => $hidden + ] + ); + + if(count($activity['attachment'])) { + $photos = 0; + $videos = 0; + $allowed = explode(',', config_cache('pixelfed.media_types')); + $activity['attachment'] = array_slice($activity['attachment'], 0, config_cache('pixelfed.max_album_length')); + foreach($activity['attachment'] as $a) { + $type = $a['mediaType']; + $url = $a['url']; + $valid = Helpers::validateUrl($url); + if(in_array($type, $allowed) == false || $valid == false) { + continue; + } + + $media = new Media(); + $media->remote_media = true; + $media->status_id = $status->id; + $media->profile_id = $status->profile_id; + $media->user_id = null; + $media->media_path = $url; + $media->remote_url = $url; + $media->mime = $type; + $media->save(); + if(explode('/', $type)[0] == 'image') { + $photos = $photos + 1; + } + if(explode('/', $type)[0] == 'video') { + $videos = $videos + 1; + } + } + + if($photos && $videos == 0) { + $dm->type = $photos == 1 ? 'photo' : 'photos'; + $dm->save(); + } + if($videos && $photos == 0) { + $dm->type = $videos == 1 ? 'video' : 'videos'; + $dm->save(); + } + } + + if(filter_var($msgText, FILTER_VALIDATE_URL)) { + if(Helpers::validateUrl($msgText)) { + $dm->type = 'link'; + $dm->meta = [ + 'domain' => parse_url($msgText, PHP_URL_HOST), + 'local' => parse_url($msgText, PHP_URL_HOST) == + parse_url(config('app.url'), PHP_URL_HOST) + ]; + $dm->save(); + } + } + + $nf = UserFilter::whereUserId($profile->id) + ->whereFilterableId($actor->id) + ->whereFilterableType('App\Profile') + ->whereFilterType('dm.mute') + ->exists(); + + if($profile->domain == null && $hidden == false && !$nf) { + $notification = new Notification(); + $notification->profile_id = $profile->id; + $notification->actor_id = $actor->id; + $notification->action = 'dm'; + $notification->item_id = $dm->id; + $notification->item_type = "App\DirectMessage"; + $notification->save(); + } + + return; + } + + public function handleFollowActivity() + { + $actor = $this->actorFirstOrCreate($this->payload['actor']); + $target = $this->actorFirstOrCreate($this->payload['object']); + if(!$actor || !$target) { + return; + } + + if($actor->domain == null || $target->domain !== null) { + return; + } + + if(AccountService::blocksDomain($target->id, $actor->domain) == true) { + return; + } + + if( + Follower::whereProfileId($actor->id) + ->whereFollowingId($target->id) + ->exists() || + FollowRequest::whereFollowerId($actor->id) + ->whereFollowingId($target->id) + ->exists() + ) { + return; + } + + $blocks = UserFilterService::blocks($target->id); + if($blocks && in_array($actor->id, $blocks)) { + return; + } + + if($target->is_private == true) { + FollowRequest::updateOrCreate([ + 'follower_id' => $actor->id, + 'following_id' => $target->id, + ],[ + 'activity' => collect($this->payload)->only(['id','actor','object','type'])->toArray() + ]); + } else { + $follower = new Follower; + $follower->profile_id = $actor->id; + $follower->following_id = $target->id; + $follower->local_profile = empty($actor->domain); + $follower->save(); + + FollowPipeline::dispatch($follower); + FollowerService::add($actor->id, $target->id); + + // send Accept to remote profile + $accept = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $target->permalink().'#accepts/follows/' . $follower->id, + 'type' => 'Accept', + 'actor' => $target->permalink(), + 'object' => [ + 'id' => $this->payload['id'], + 'actor' => $actor->permalink(), + 'type' => 'Follow', + 'object' => $target->permalink() + ] + ]; + Helpers::sendSignedObject($target, $actor->inbox_url, $accept); + Cache::forget('profile:follower_count:'.$target->id); + Cache::forget('profile:follower_count:'.$actor->id); + Cache::forget('profile:following_count:'.$target->id); + Cache::forget('profile:following_count:'.$actor->id); + } + + return; + } + + public function handleAnnounceActivity() + { + $actor = $this->actorFirstOrCreate($this->payload['actor']); + $activity = $this->payload['object']; + + if(!$actor || $actor->domain == null) { + return; + } + + $parent = Helpers::statusFetch($activity); + + if(!$parent || empty($parent)) { + return; + } + + if(AccountService::blocksDomain($parent->profile_id, $actor->domain) == true) { + return; + } + + $blocks = UserFilterService::blocks($parent->profile_id); + if($blocks && in_array($actor->id, $blocks)) { + return; + } + + $status = Status::firstOrCreate([ + 'profile_id' => $actor->id, + 'reblog_of_id' => $parent->id, + 'type' => 'share' + ]); + + Notification::firstOrCreate( + [ + 'profile_id' => $parent->profile_id, + 'actor_id' => $actor->id, + 'action' => 'share', + 'item_id' => $parent->id, + 'item_type' => 'App\Status', + ] + ); + + $parent->reblogs_count = $parent->reblogs_count + 1; + $parent->save(); + + ReblogService::addPostReblog($parent->profile_id, $status->id); + + return; + } + + public function handleAcceptActivity() + { + $actor = $this->payload['object']['actor']; + $obj = $this->payload['object']['object']; + $type = $this->payload['object']['type']; + + if($type !== 'Follow') { + return; + } + + $actor = Helpers::validateLocalUrl($actor); + $target = Helpers::validateUrl($obj); + + if(!$actor || !$target) { + return; + } + + $actor = Helpers::profileFetch($actor); + $target = Helpers::profileFetch($target); + + if(!$actor || !$target) { + return; + } + + if(AccountService::blocksDomain($target->id, $actor->domain) == true) { + return; + } + + $request = FollowRequest::whereFollowerId($actor->id) + ->whereFollowingId($target->id) + ->whereIsRejected(false) + ->first(); + + if(!$request) { + return; + } + + $follower = Follower::firstOrCreate([ + 'profile_id' => $actor->id, + 'following_id' => $target->id, + ]); + FollowPipeline::dispatch($follower); + + $request->delete(); + + return; + } + + public function handleDeleteActivity() + { + if(!isset( + $this->payload['actor'], + $this->payload['object'] + )) { + return; + } + $actor = $this->payload['actor']; + $obj = $this->payload['object']; + if(is_string($obj) == true && $actor == $obj && Helpers::validateUrl($obj)) { + $profile = Profile::whereRemoteUrl($obj)->first(); + if(!$profile || $profile->private_key != null) { + return; + } + DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('inbox'); + return; + } else { + if(!isset( + $obj['id'], + $this->payload['object'], + $this->payload['object']['id'], + $this->payload['object']['type'] + )) { + return; + } + $type = $this->payload['object']['type']; + $typeCheck = in_array($type, ['Person', 'Tombstone', 'Story']); + if(!Helpers::validateUrl($actor) || !Helpers::validateUrl($obj['id']) || !$typeCheck) { + return; + } + if(parse_url($obj['id'], PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) { + return; + } + $id = $this->payload['object']['id']; + switch ($type) { + case 'Person': + $profile = Profile::whereRemoteUrl($actor)->first(); + if(!$profile || $profile->private_key != null) { + return; + } + DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('inbox'); + return; + break; + + case 'Tombstone': + $profile = Profile::whereRemoteUrl($actor)->first(); + if(!$profile || $profile->private_key != null) { + return; + } + + $status = Status::where('object_url', $id)->first(); + if(!$status) { + $status = Status::where('url', $id)->first(); + if(!$status) { + return; + } + } + if($status->profile_id != $profile->id) { + return; + } + if($status->scope && in_array($status->scope, ['public', 'unlisted', 'private'])) { + if($status->type && !in_array($status->type, ['story:reaction', 'story:reply', 'reply'])) { + FeedRemoveRemotePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed'); + } + } + RemoteStatusDelete::dispatch($status)->onQueue('high'); + return; + break; + + case 'Story': + $story = Story::whereObjectId($id) + ->first(); + if($story) { + StoryExpire::dispatch($story)->onQueue('story'); + } + return; + break; + + default: + return; + break; + } + } + return; + } + + public function handleLikeActivity() + { + $actor = $this->payload['actor']; + + if(!Helpers::validateUrl($actor)) { + return; + } + + $profile = self::actorFirstOrCreate($actor); + $obj = $this->payload['object']; + if(!Helpers::validateUrl($obj)) { + return; + } + $status = Helpers::statusFirstOrFetch($obj); + if(!$status || !$profile) { + return; + } + + if(AccountService::blocksDomain($status->profile_id, $profile->domain) == true) { + return; + } + + $blocks = UserFilterService::blocks($status->profile_id); + if($blocks && in_array($profile->id, $blocks)) { + return; + } + + $like = Like::firstOrCreate([ + 'profile_id' => $profile->id, + 'status_id' => $status->id + ]); + + if($like->wasRecentlyCreated == true) { + $status->likes_count = $status->likes_count + 1; + $status->save(); + LikePipeline::dispatch($like); + } + + return; + } + + public function handleRejectActivity() + { + } + + public function handleUndoActivity() + { + $actor = $this->payload['actor']; + $profile = self::actorFirstOrCreate($actor); + $obj = $this->payload['object']; + + if(!$profile) { + return; + } + // TODO: Some implementations do not inline the object, skip for now + if(!$obj || !is_array($obj) || !isset($obj['type'])) { + return; + } + + switch ($obj['type']) { + case 'Accept': + break; + + case 'Announce': + if(is_array($obj) && isset($obj['object'])) { + $obj = $obj['object']; + } + if(!is_string($obj)) { + return; + } + if(Helpers::validateLocalUrl($obj)) { + $parsedId = last(explode('/', $obj)); + $status = Status::find($parsedId); + } else { + $status = Status::whereUri($obj)->first(); + } + if(!$status) { + return; + } + if(AccountService::blocksDomain($status->profile_id, $profile->domain) == true) { + return; + } + FeedRemoveRemotePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed'); + Status::whereProfileId($profile->id) + ->whereReblogOfId($status->id) + ->delete(); + ReblogService::removePostReblog($profile->id, $status->id); + Notification::whereProfileId($status->profile_id) + ->whereActorId($profile->id) + ->whereAction('share') + ->whereItemId($status->reblog_of_id) + ->whereItemType('App\Status') + ->forceDelete(); + break; + + case 'Block': + break; + + case 'Follow': + $following = self::actorFirstOrCreate($obj['object']); + if(!$following) { + return; + } + if(AccountService::blocksDomain($following->id, $profile->domain) == true) { + return; + } + Follower::whereProfileId($profile->id) + ->whereFollowingId($following->id) + ->delete(); + Notification::whereProfileId($following->id) + ->whereActorId($profile->id) + ->whereAction('follow') + ->whereItemId($following->id) + ->whereItemType('App\Profile') + ->forceDelete(); + FollowerService::remove($profile->id, $following->id); + break; + + case 'Like': + $objectUri = $obj['object']; + if(!is_string($objectUri)) { + if(is_array($objectUri) && isset($objectUri['id']) && is_string($objectUri['id'])) { + $objectUri = $objectUri['id']; + } else { + return; + } + } + $status = Helpers::statusFirstOrFetch($objectUri); + if(!$status) { + return; + } + if(AccountService::blocksDomain($status->profile_id, $profile->domain) == true) { + return; + } + Like::whereProfileId($profile->id) + ->whereStatusId($status->id) + ->forceDelete(); + Notification::whereProfileId($status->profile_id) + ->whereActorId($profile->id) + ->whereAction('like') + ->whereItemId($status->id) + ->whereItemType('App\Status') + ->forceDelete(); + break; + } + return; + } + + public function handleViewActivity() + { + if(!isset( + $this->payload['actor'], + $this->payload['object'] + )) { + return; + } + + $actor = $this->payload['actor']; + $obj = $this->payload['object']; + + if(!Helpers::validateUrl($actor)) { + return; + } + + if(!$obj || !is_array($obj)) { + return; + } + + if(!isset($obj['type']) || !isset($obj['object']) || $obj['type'] != 'Story') { + return; + } + + if(!Helpers::validateLocalUrl($obj['object'])) { + return; + } + + $profile = Helpers::profileFetch($actor); + $storyId = Str::of($obj['object'])->explode('/')->last(); + + $story = Story::whereActive(true) + ->whereLocal(true) + ->find($storyId); + + if(!$story) { + return; + } + + if(AccountService::blocksDomain($story->profile_id, $profile->domain) == true) { + return; + } + + if(!FollowerService::follows($profile->id, $story->profile_id)) { + return; + } + + $view = StoryView::firstOrCreate([ + 'story_id' => $story->id, + 'profile_id' => $profile->id + ]); + + if($view->wasRecentlyCreated == true) { + $story->view_count++; + $story->save(); + } + + return; + } + + public function handleStoryReactionActivity() + { + if(!isset( + $this->payload['actor'], + $this->payload['id'], + $this->payload['inReplyTo'], + $this->payload['content'] + )) { + return; + } + + $id = $this->payload['id']; + $actor = $this->payload['actor']; + $storyUrl = $this->payload['inReplyTo']; + $to = $this->payload['to']; + $text = Purify::clean($this->payload['content']); + + if(parse_url($id, PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) { + return; + } + + if(!Helpers::validateUrl($id) || !Helpers::validateUrl($actor)) { + return; + } + + if(!Helpers::validateLocalUrl($storyUrl)) { + return; + } + + if(!Helpers::validateLocalUrl($to)) { + return; + } + + if(Status::whereObjectUrl($id)->exists()) { + return; + } + + $storyId = Str::of($storyUrl)->explode('/')->last(); + $targetProfile = Helpers::profileFetch($to); + + $story = Story::whereProfileId($targetProfile->id) + ->find($storyId); + + if(!$story) { + return; + } + + if($story->can_react == false) { + return; + } + + $actorProfile = Helpers::profileFetch($actor); + + if(AccountService::blocksDomain($targetProfile->id, $actorProfile->domain) == true) { + return; + } + + if(!FollowerService::follows($actorProfile->id, $targetProfile->id)) { + return; + } + + $url = $id; + + if(str_ends_with($url, '/activity')) { + $url = substr($url, 0, -9); + } + + $status = new Status; + $status->profile_id = $actorProfile->id; + $status->type = 'story:reaction'; + $status->url = $url; + $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; + $status->entities = json_encode([ + 'story_id' => $story->id, + 'reaction' => $text + ]); + $status->save(); + + $dm = new DirectMessage; + $dm->to_id = $story->profile_id; + $dm->from_id = $actorProfile->id; + $dm->type = 'story:react'; + $dm->status_id = $status->id; + $dm->meta = json_encode([ + 'story_username' => $targetProfile->username, + 'story_actor_username' => $actorProfile->username, + 'story_id' => $story->id, + 'story_media_url' => url(Storage::url($story->path)), + 'reaction' => $text + ]); + $dm->save(); + + Conversation::updateOrInsert( + [ + 'to_id' => $story->profile_id, + 'from_id' => $actorProfile->id + ], + [ + 'type' => 'story:react', + 'status_id' => $status->id, + 'dm_id' => $dm->id, + 'is_hidden' => false + ] + ); + + $n = new Notification; + $n->profile_id = $dm->to_id; + $n->actor_id = $dm->from_id; + $n->item_id = $dm->id; + $n->item_type = 'App\DirectMessage'; + $n->action = 'story:react'; + $n->save(); + + return; + } + + public function handleStoryReplyActivity() + { + if(!isset( + $this->payload['actor'], + $this->payload['id'], + $this->payload['inReplyTo'], + $this->payload['content'] + )) { + return; + } + + $id = $this->payload['id']; + $actor = $this->payload['actor']; + $storyUrl = $this->payload['inReplyTo']; + $to = $this->payload['to']; + $text = Purify::clean($this->payload['content']); + + if(parse_url($id, PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) { + return; + } + + if(!Helpers::validateUrl($id) || !Helpers::validateUrl($actor)) { + return; + } + + if(!Helpers::validateLocalUrl($storyUrl)) { + return; + } + + if(!Helpers::validateLocalUrl($to)) { + return; + } + + if(Status::whereObjectUrl($id)->exists()) { + return; + } + + $storyId = Str::of($storyUrl)->explode('/')->last(); + $targetProfile = Helpers::profileFetch($to); + + $story = Story::whereProfileId($targetProfile->id) + ->find($storyId); + + if(!$story) { + return; + } + + if($story->can_react == false) { + return; + } + + $actorProfile = Helpers::profileFetch($actor); + + + if(AccountService::blocksDomain($targetProfile->id, $actorProfile->domain) == true) { + return; + } + + if(!FollowerService::follows($actorProfile->id, $targetProfile->id)) { + return; + } + + $url = $id; + + if(str_ends_with($url, '/activity')) { + $url = substr($url, 0, -9); + } + + $status = new Status; + $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; + $status->scope = 'direct'; + $status->visibility = 'direct'; + $status->in_reply_to_profile_id = $story->profile_id; + $status->entities = json_encode([ + 'story_id' => $story->id, + 'caption' => $text + ]); + $status->save(); + + $dm = new DirectMessage; + $dm->to_id = $story->profile_id; + $dm->from_id = $actorProfile->id; + $dm->type = 'story:comment'; + $dm->status_id = $status->id; + $dm->meta = json_encode([ + 'story_username' => $targetProfile->username, + 'story_actor_username' => $actorProfile->username, + 'story_id' => $story->id, + 'story_media_url' => url(Storage::url($story->path)), + 'caption' => $text + ]); + $dm->save(); + + Conversation::updateOrInsert( + [ + 'to_id' => $story->profile_id, + 'from_id' => $actorProfile->id + ], + [ + 'type' => 'story:comment', + 'status_id' => $status->id, + 'dm_id' => $dm->id, + 'is_hidden' => false + ] + ); + + $n = new Notification; + $n->profile_id = $dm->to_id; + $n->actor_id = $dm->from_id; + $n->item_id = $dm->id; + $n->item_type = 'App\DirectMessage'; + $n->action = 'story:comment'; + $n->save(); + + return; + } + + public function handleFlagActivity() + { + if(!isset( + $this->payload['id'], + $this->payload['type'], + $this->payload['actor'], + $this->payload['object'] + )) { + return; + } + + $id = $this->payload['id']; + $actor = $this->payload['actor']; + + if(Helpers::validateLocalUrl($id) || parse_url($id, PHP_URL_HOST) !== parse_url($actor, PHP_URL_HOST)) { + return; + } + + $content = isset($this->payload['content']) ? Purify::clean($this->payload['content']) : null; + $object = $this->payload['object']; + + if(empty($object) || (!is_array($object) && !is_string($object))) { + return; + } + + if(is_array($object) && count($object) > 100) { + return; + } + + $objects = collect([]); + $accountId = null; + + foreach($object as $objectUrl) { + if(!Helpers::validateLocalUrl($objectUrl)) { + continue; + } + + if(str_contains($objectUrl, '/users/')) { + $username = last(explode('/', $objectUrl)); + $profileId = Profile::whereUsername($username)->first(); + if($profileId) { + $accountId = $profileId->id; + } + } else if(str_contains($objectUrl, '/p/')) { + $postId = last(explode('/', $objectUrl)); + $objects->push($postId); + } else { + continue; + } + } + + if(!$accountId || !$objects->count()) { + return; + } + + $instanceHost = parse_url($id, PHP_URL_HOST); + + $instance = Instance::updateOrCreate([ + 'domain' => $instanceHost + ]); + + $report = new RemoteReport; + $report->status_ids = $objects->toArray(); + $report->comment = $content; + $report->account_id = $accountId; + $report->uri = $id; + $report->instance_id = $instance->id; + $report->report_meta = [ + 'actor' => $actor, + 'object' => $object + ]; + $report->save(); + + return; + } + + public function handleUpdateActivity() + { + $activity = $this->payload['object']; + + if(!isset($activity['type'], $activity['id'])) { + return; + } + + if(!Helpers::validateUrl($activity['id'])) { + return; + } + + if($activity['type'] === 'Note') { + if(Status::whereObjectUrl($activity['id'])->exists()) { + StatusRemoteUpdatePipeline::dispatch($activity); + } + } else if ($activity['type'] === 'Person') { + if(UpdatePersonValidator::validate($this->payload)) { + HandleUpdateActivity::dispatch($this->payload)->onQueue('low'); + } + } + } } diff --git a/app/Util/Lexer/Regex.php b/app/Util/Lexer/Regex.php index ecc468d05..f8d77c95f 100755 --- a/app/Util/Lexer/Regex.php +++ b/app/Util/Lexer/Regex.php @@ -162,7 +162,7 @@ abstract class Regex // look-ahead capture here and don't append $after when we return. $tmp['valid_mention_preceding_chars'] = '([^a-zA-Z0-9_!#\$%&*@@\/]|^|(?:^|[^a-z0-9_+~.-])RT:?)'; - $re['valid_mentions_or_lists'] = '/'.$tmp['valid_mention_preceding_chars'].'(['.$tmp['at_signs'].'])([a-z0-9_\-.]{1,20})((\/[a-z][a-z0-9_\-]{0,24})?(?=(.*|$))(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i'; + $re['valid_mentions_or_lists'] = '/'.$tmp['valid_mention_preceding_chars'].'(['.$tmp['at_signs'].'])([\p{L}0-9_\-.]{1,20})((\/[a-z][a-z0-9_\-]{0,24})?(?=(.*|$))(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/iu'; $re['valid_reply'] = '/^(?:['.$tmp['spaces'].'])*['.$tmp['at_signs'].']([a-z0-9_\-.]{1,20})(?=(.*|$))/iu'; $re['end_mention_match'] = '/\A(?:['.$tmp['at_signs'].']|['.$tmp['latin_accents'].']|:\/\/)/iu'; diff --git a/app/Util/Media/Blurhash.php b/app/Util/Media/Blurhash.php index c0cca59b9..8e232ea17 100644 --- a/app/Util/Media/Blurhash.php +++ b/app/Util/Media/Blurhash.php @@ -44,6 +44,9 @@ class Blurhash { $pixels[] = $row; } + // Free the allocated GdImage object from memory: + imagedestroy($image); + $components_x = 4; $components_y = 4; $blurhash = BlurhashEngine::encode($pixels, $components_x, $components_y); @@ -53,4 +56,4 @@ class Blurhash { return $blurhash; } -} \ No newline at end of file +} diff --git a/app/Util/Site/Config.php b/app/Util/Site/Config.php index 90631159f..e0916591d 100644 --- a/app/Util/Site/Config.php +++ b/app/Util/Site/Config.php @@ -7,86 +7,100 @@ use Illuminate\Support\Str; class Config { - const CACHE_KEY = 'api:site:configuration:_v0.8'; + const CACHE_KEY = 'api:site:configuration:_v0.8'; - public static function get() { - return Cache::remember(self::CACHE_KEY, 900, function() { - return [ - 'version' => config('pixelfed.version'), - 'open_registration' => (bool) config_cache('pixelfed.open_registration'), - 'uploader' => [ - 'max_photo_size' => (int) config('pixelfed.max_photo_size'), - 'max_caption_length' => (int) config('pixelfed.max_caption_length'), - 'max_altext_length' => (int) config('pixelfed.max_altext_length', 150), - 'album_limit' => (int) config_cache('pixelfed.max_album_length'), - 'image_quality' => (int) config_cache('pixelfed.image_quality'), + public static function get() { + return Cache::remember(self::CACHE_KEY, 900, function() { + $hls = [ + 'enabled' => config('media.hls.enabled'), + ]; + if(config('media.hls.enabled')) { + $hls = [ + 'enabled' => true, + 'debug' => (bool) config('media.hls.debug'), + 'p2p' => (bool) config('media.hls.p2p'), + 'p2p_debug' => (bool) config('media.hls.p2p_debug'), + 'tracker' => config('media.hls.tracker'), + 'ice' => config('media.hls.ice') + ]; + } + return [ + 'version' => config('pixelfed.version'), + 'open_registration' => (bool) config_cache('pixelfed.open_registration'), + 'uploader' => [ + 'max_photo_size' => (int) config('pixelfed.max_photo_size'), + 'max_caption_length' => (int) config('pixelfed.max_caption_length'), + 'max_altext_length' => (int) config('pixelfed.max_altext_length', 150), + 'album_limit' => (int) config_cache('pixelfed.max_album_length'), + 'image_quality' => (int) config_cache('pixelfed.image_quality'), - 'max_collection_length' => (int) config('pixelfed.max_collection_length', 18), + 'max_collection_length' => (int) config('pixelfed.max_collection_length', 18), - 'optimize_image' => (bool) config('pixelfed.optimize_image'), - 'optimize_video' => (bool) config('pixelfed.optimize_video'), + 'optimize_image' => (bool) config('pixelfed.optimize_image'), + 'optimize_video' => (bool) config('pixelfed.optimize_video'), - 'media_types' => config_cache('pixelfed.media_types'), - 'mime_types' => config_cache('pixelfed.media_types') ? explode(',', config_cache('pixelfed.media_types')) : [], - 'enforce_account_limit' => (bool) config_cache('pixelfed.enforce_account_limit') - ], + 'media_types' => config_cache('pixelfed.media_types'), + 'mime_types' => config_cache('pixelfed.media_types') ? explode(',', config_cache('pixelfed.media_types')) : [], + 'enforce_account_limit' => (bool) config_cache('pixelfed.enforce_account_limit') + ], - 'activitypub' => [ - 'enabled' => (bool) config_cache('federation.activitypub.enabled'), - 'remote_follow' => config('federation.activitypub.remoteFollow') - ], + 'activitypub' => [ + 'enabled' => (bool) config_cache('federation.activitypub.enabled'), + 'remote_follow' => config('federation.activitypub.remoteFollow') + ], - 'ab' => config('exp'), + 'ab' => config('exp'), - 'site' => [ - 'name' => config_cache('app.name'), - 'domain' => config('pixelfed.domain.app'), - 'url' => config('app.url'), - 'description' => config_cache('app.short_description') - ], + 'site' => [ + 'name' => config_cache('app.name'), + 'domain' => config('pixelfed.domain.app'), + 'url' => config('app.url'), + 'description' => config_cache('app.short_description') + ], - 'account' => [ - 'max_avatar_size' => config('pixelfed.max_avatar_size'), - 'max_bio_length' => config('pixelfed.max_bio_length'), - 'max_name_length' => config('pixelfed.max_name_length'), - 'min_password_length' => config('pixelfed.min_password_length'), - 'max_account_size' => config('pixelfed.max_account_size') - ], + 'account' => [ + 'max_avatar_size' => config('pixelfed.max_avatar_size'), + 'max_bio_length' => config('pixelfed.max_bio_length'), + 'max_name_length' => config('pixelfed.max_name_length'), + 'min_password_length' => config('pixelfed.min_password_length'), + 'max_account_size' => config('pixelfed.max_account_size') + ], - 'username' => [ - 'remote' => [ - 'formats' => config('instance.username.remote.formats'), - 'format' => config('instance.username.remote.format'), - 'custom' => config('instance.username.remote.custom') - ] - ], + 'username' => [ + 'remote' => [ + 'formats' => config('instance.username.remote.formats'), + 'format' => config('instance.username.remote.format'), + 'custom' => config('instance.username.remote.custom') + ] + ], - 'features' => [ - 'timelines' => [ - 'local' => true, - 'network' => (bool) config('federation.network_timeline'), - ], - 'mobile_apis' => (bool) config_cache('pixelfed.oauth_enabled'), - 'stories' => (bool) config_cache('instance.stories.enabled'), - 'video' => Str::contains(config_cache('pixelfed.media_types'), 'video/mp4'), - 'import' => [ - 'instagram' => (bool) config_cache('pixelfed.import.instagram.enabled'), - 'mastodon' => false, - 'pixelfed' => false - ], - 'label' => [ - 'covid' => [ - 'enabled' => (bool) config('instance.label.covid.enabled'), - 'org' => config('instance.label.covid.org'), - 'url' => config('instance.label.covid.url'), - ] - ] - ] - ]; - }); - } + 'features' => [ + 'timelines' => [ + 'local' => true, + 'network' => (bool) config('federation.network_timeline'), + ], + 'mobile_apis' => (bool) config_cache('pixelfed.oauth_enabled'), + 'stories' => (bool) config_cache('instance.stories.enabled'), + 'video' => Str::contains(config_cache('pixelfed.media_types'), 'video/mp4'), + 'import' => [ + 'instagram' => (bool) config_cache('pixelfed.import.instagram.enabled'), + 'mastodon' => false, + 'pixelfed' => false + ], + 'label' => [ + 'covid' => [ + 'enabled' => (bool) config('instance.label.covid.enabled'), + 'org' => config('instance.label.covid.org'), + 'url' => config('instance.label.covid.url'), + ] + ], + 'hls' => $hls + ] + ]; + }); + } - public static function json() { - return json_encode(self::get(), JSON_FORCE_OBJECT); - } + public static function json() { + return json_encode(self::get(), JSON_FORCE_OBJECT); + } } diff --git a/app/Util/Site/Nodeinfo.php b/app/Util/Site/Nodeinfo.php index 166b6bc6a..0458299c5 100644 --- a/app/Util/Site/Nodeinfo.php +++ b/app/Util/Site/Nodeinfo.php @@ -2,85 +2,98 @@ namespace App\Util\Site; -use Cache; -use App\{Like, Profile, Status, User}; +use Illuminate\Support\Facades\Cache; +use App\Like; +use App\Profile; +use App\Status; +use App\User; use Illuminate\Support\Str; -class Nodeinfo { +class Nodeinfo +{ + public static function get() + { + $res = Cache::remember('api:nodeinfo', 900, function () { + $activeHalfYear = self::activeUsersHalfYear(); + $activeMonth = self::activeUsersMonthly(); - public static function get() - { - $res = Cache::remember('api:nodeinfo', 300, function () { - $activeHalfYear = Cache::remember('api:nodeinfo:ahy', 172800, function() { - return User::select('last_active_at') - ->where('last_active_at', '>', now()->subMonths(6)) - ->orWhere('created_at', '>', now()->subMonths(6)) - ->count(); - }); + $users = Cache::remember('api:nodeinfo:users', 43200, function() { + return User::count(); + }); - $activeMonth = Cache::remember('api:nodeinfo:am', 172800, function() { - return User::select('last_active_at') - ->where('last_active_at', '>', now()->subMonths(1)) - ->orWhere('created_at', '>', now()->subMonths(1)) - ->count(); - }); + $statuses = Cache::remember('api:nodeinfo:statuses', 21600, function() { + return Status::whereLocal(true)->count(); + }); - $users = Cache::remember('api:nodeinfo:users', 43200, function() { - return User::count(); - }); + $features = [ 'features' => \App\Util\Site\Config::get()['features'] ]; - $statuses = Cache::remember('api:nodeinfo:statuses', 21600, function() { - return Status::whereLocal(true)->count(); - }); + return [ + 'metadata' => [ + 'nodeName' => config_cache('app.name'), + 'software' => [ + 'homepage' => 'https://pixelfed.org', + 'repo' => 'https://github.com/pixelfed/pixelfed', + ], + 'config' => $features + ], + 'protocols' => [ + 'activitypub', + ], + 'services' => [ + 'inbound' => [], + 'outbound' => [], + ], + 'software' => [ + 'name' => 'pixelfed', + 'version' => config('pixelfed.version'), + ], + 'usage' => [ + 'localPosts' => (int) $statuses, + 'localComments' => 0, + 'users' => [ + 'total' => (int) $users, + 'activeHalfyear' => (int) $activeHalfYear, + 'activeMonth' => (int) $activeMonth, + ], + ], + 'version' => '2.0', + ]; + }); + $res['openRegistrations'] = (bool) config_cache('pixelfed.open_registration'); + return $res; + } - $features = [ 'features' => \App\Util\Site\Config::get()['features'] ]; + public static function wellKnown() + { + return [ + 'links' => [ + [ + 'href' => config('pixelfed.nodeinfo.url'), + 'rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.0', + ], + ], + ]; + } - return [ - 'metadata' => [ - 'nodeName' => config_cache('app.name'), - 'software' => [ - 'homepage' => 'https://pixelfed.org', - 'repo' => 'https://github.com/pixelfed/pixelfed', - ], - 'config' => $features - ], - 'protocols' => [ - 'activitypub', - ], - 'services' => [ - 'inbound' => [], - 'outbound' => [], - ], - 'software' => [ - 'name' => 'pixelfed', - 'version' => config('pixelfed.version'), - ], - 'usage' => [ - 'localPosts' => (int) $statuses, - 'localComments' => 0, - 'users' => [ - 'total' => (int) $users, - 'activeHalfyear' => (int) $activeHalfYear, - 'activeMonth' => (int) $activeMonth, - ], - ], - 'version' => '2.0', - ]; - }); - $res['openRegistrations'] = (bool) config_cache('pixelfed.open_registration'); - return $res; - } - - public static function wellKnown() - { - return [ - 'links' => [ - [ - 'href' => config('pixelfed.nodeinfo.url'), - 'rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.0', - ], - ], - ]; - } + public static function activeUsersMonthly() + { + return Cache::remember('api:nodeinfo:active-users-monthly', 43200, function() { + return User::withTrashed() + ->select('last_active_at, updated_at') + ->where('updated_at', '>', now()->subWeeks(5)) + ->orWhere('last_active_at', '>', now()->subWeeks(5)) + ->count(); + }); + } + public static function activeUsersHalfYear() + { + return Cache::remember('api:nodeinfo:active-users-half-year', 43200, function() { + return User::withTrashed() + ->select('last_active_at, updated_at') + ->where('last_active_at', '>', now()->subMonths(6)) + ->orWhere('updated_at', '>', now()->subMonths(6)) + ->count(); + }); + } } diff --git a/composer.json b/composer.json index 54328a674..285d38ccd 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ "doctrine/dbal": "^3.0", "intervention/image": "^2.4", "jenssegers/agent": "^2.6", + "laravel-notification-channels/webpush": "^7.1", "laravel/framework": "^10.0", "laravel/helpers": "^1.1", "laravel/horizon": "^5.0", diff --git a/composer.lock b/composer.lock index 74c453c67..12c097343 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "427b8abad9495b7a7bd2a489625a3881", + "content-hash": "74351c8a36870209c9bbfb76d158a10c", "packages": [ { "name": "aws/aws-crt-php", - "version": "v1.2.1", + "version": "v1.2.4", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "1926277fc71d253dfa820271ac5987bdb193ccf5" + "reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/1926277fc71d253dfa820271ac5987bdb193ccf5", - "reference": "1926277fc71d253dfa820271ac5987bdb193ccf5", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/eb0c6e4e142224a10b08f49ebf87f32611d162b2", + "reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2", "shasum": "" }, "require": { @@ -56,35 +56,35 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.1" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.4" }, - "time": "2023-03-24T20:22:19+00:00" + "time": "2023-11-08T00:42:13+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.275.7", + "version": "3.293.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "54dcef3349c81b46c0f5f6e54b5f9bfb5db19903" + "reference": "1d3e952ea2f45bb0d42d7f873d1b4957bb6362c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/54dcef3349c81b46c0f5f6e54b5f9bfb5db19903", - "reference": "54dcef3349c81b46c0f5f6e54b5f9bfb5db19903", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/1d3e952ea2f45bb0d42d7f873d1b4957bb6362c4", + "reference": "1d3e952ea2f45bb0d42d7f873d1b4957bb6362c4", "shasum": "" }, "require": { - "aws/aws-crt-php": "^1.0.4", + "aws/aws-crt-php": "^1.2.3", "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", - "guzzlehttp/promises": "^1.4.0", + "guzzlehttp/promises": "^1.4.0 || ^2.0", "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", "mtdowling/jmespath.php": "^2.6", - "php": ">=5.5", - "psr/http-message": "^1.0" + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0" }, "require-dev": { "andrewsville/php-token-reflection": "^1.4", @@ -99,7 +99,7 @@ "ext-sockets": "*", "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", - "phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5", + "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", "psr/cache": "^1.0", "psr/simple-cache": "^1.0", "sebastian/comparator": "^1.2.3 || ^4.0", @@ -151,9 +151,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.275.7" + "source": "https://github.com/aws/aws-sdk-php/tree/3.293.2" }, - "time": "2023-07-13T18:21:04+00:00" + "time": "2023-12-01T19:06:15+00:00" }, { "name": "bacon/bacon-qr-code", @@ -211,16 +211,16 @@ }, { "name": "beyondcode/laravel-websockets", - "version": "1.14.0", + "version": "1.14.1", "source": { "type": "git", "url": "https://github.com/beyondcode/laravel-websockets.git", - "reference": "9ab87be1d96340979e67b462ea5fd6a8b06e6a02" + "reference": "fee9a81e42a096d2aaca216ce91acf6e25d8c06d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beyondcode/laravel-websockets/zipball/9ab87be1d96340979e67b462ea5fd6a8b06e6a02", - "reference": "9ab87be1d96340979e67b462ea5fd6a8b06e6a02", + "url": "https://api.github.com/repos/beyondcode/laravel-websockets/zipball/fee9a81e42a096d2aaca216ce91acf6e25d8c06d", + "reference": "fee9a81e42a096d2aaca216ce91acf6e25d8c06d", "shasum": "" }, "require": { @@ -287,9 +287,9 @@ ], "support": { "issues": "https://github.com/beyondcode/laravel-websockets/issues", - "source": "https://github.com/beyondcode/laravel-websockets/tree/1.14.0" + "source": "https://github.com/beyondcode/laravel-websockets/tree/1.14.1" }, - "time": "2023-02-15T10:40:49+00:00" + "time": "2023-08-30T07:23:12+00:00" }, { "name": "brick/math", @@ -424,6 +424,75 @@ }, "time": "2023-03-10T05:38:55+00:00" }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "67a77972b9f398ae7068dabacc39c08aeee170d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/67a77972b9f398ae7068dabacc39c08aeee170d5", + "reference": "67a77972b9f398ae7068dabacc39c08aeee170d5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "doctrine/dbal": "<3.7.0 || >=4.0.0" + }, + "require-dev": { + "doctrine/dbal": "^3.7.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2023-10-01T14:29:01+00:00" + }, { "name": "cboden/ratchet", "version": "v0.4.4", @@ -489,16 +558,16 @@ }, { "name": "dasprid/enum", - "version": "1.0.4", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/DASPRiD/Enum.git", - "reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f" + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8e6b6ea76eabbf19ea2bf5b67b98e1860474012f", - "reference": "8e6b6ea76eabbf19ea2bf5b67b98e1860474012f", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016", + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016", "shasum": "" }, "require": { @@ -533,9 +602,9 @@ ], "support": { "issues": "https://github.com/DASPRiD/Enum/issues", - "source": "https://github.com/DASPRiD/Enum/tree/1.0.4" + "source": "https://github.com/DASPRiD/Enum/tree/1.0.5" }, - "time": "2023-03-01T18:44:03+00:00" + "time": "2023-08-25T16:18:39+00:00" }, { "name": "defuse/php-encryption", @@ -774,16 +843,16 @@ }, { "name": "doctrine/dbal", - "version": "3.6.4", + "version": "3.7.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f" + "reference": "0ac3c270590e54910715e9a1a044cc368df282b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f", - "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/0ac3c270590e54910715e9a1a044cc368df282b2", + "reference": "0ac3c270590e54910715e9a1a044cc368df282b2", "shasum": "" }, "require": { @@ -798,11 +867,12 @@ "require-dev": { "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2022.3", - "phpstan/phpstan": "1.10.14", + "jetbrains/phpstorm-stubs": "2023.1", + "phpstan/phpstan": "1.10.42", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.7", + "phpunit/phpunit": "9.6.13", "psalm/plugin-phpunit": "0.18.4", + "slevomat/coding-standard": "8.13.1", "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^5.4|^6.0", "symfony/console": "^4.4|^5.4|^6.0", @@ -866,7 +936,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.4" + "source": "https://github.com/doctrine/dbal/tree/3.7.2" }, "funding": [ { @@ -882,20 +952,20 @@ "type": "tidelift" } ], - "time": "2023-06-15T07:40:12+00:00" + "time": "2023-11-19T08:06:58+00:00" }, { "name": "doctrine/deprecations", - "version": "v1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3" + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931", "shasum": "" }, "require": { @@ -927,9 +997,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.1.1" + "source": "https://github.com/doctrine/deprecations/tree/1.1.2" }, - "time": "2023-06-03T09:27:29+00:00" + "time": "2023-09-27T20:04:15+00:00" }, { "name": "doctrine/event-manager", @@ -1192,16 +1262,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.3.2", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8" + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/782ca5968ab8b954773518e9e49a6f892a34b2a8", - "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", "shasum": "" }, "require": { @@ -1241,7 +1311,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.2" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" }, "funding": [ { @@ -1249,20 +1319,20 @@ "type": "github" } ], - "time": "2022-09-10T18:51:20+00:00" + "time": "2023-08-10T19:36:49+00:00" }, { "name": "egulias/email-validator", - "version": "4.0.1", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff" + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/3a85486b709bc384dae8eb78fb2eec649bdb64ff", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e", + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e", "shasum": "" }, "require": { @@ -1271,8 +1341,8 @@ "symfony/polyfill-intl-idn": "^1.26" }, "require-dev": { - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^4.30" + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -1308,7 +1378,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.1" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.2" }, "funding": [ { @@ -1316,32 +1386,32 @@ "type": "github" } ], - "time": "2023-01-14T14:17:03+00:00" + "time": "2023-10-06T06:47:41+00:00" }, { "name": "evenement/evenement", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/igorw/evenement.git", - "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", - "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", "shasum": "" }, "require": { "php": ">=7.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9 || ^6" }, "type": "library", "autoload": { - "psr-0": { - "Evenement": "src" + "psr-4": { + "Evenement\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1361,26 +1431,26 @@ ], "support": { "issues": "https://github.com/igorw/evenement/issues", - "source": "https://github.com/igorw/evenement/tree/master" + "source": "https://github.com/igorw/evenement/tree/v3.0.2" }, - "time": "2017-07-23T21:35:13+00:00" + "time": "2023-08-08T05:53:35+00:00" }, { "name": "ezyang/htmlpurifier", - "version": "v4.16.0", + "version": "v4.17.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8" + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/523407fb06eb9e5f3d59889b3978d5bfe94299c8", - "reference": "523407fb06eb9e5f3d59889b3978d5bfe94299c8", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c", + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c", "shasum": "" }, "require": { - "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0" + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" }, "require-dev": { "cerdic/css-tidy": "^1.7 || ^2.0", @@ -1422,9 +1492,9 @@ ], "support": { "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.16.0" + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.17.0" }, - "time": "2022-09-18T07:06:19+00:00" + "time": "2023-11-17T15:01:25+00:00" }, { "name": "facade/ignition-contracts", @@ -1479,6 +1549,82 @@ }, "time": "2020-10-16T08:27:54+00:00" }, + { + "name": "fgrosse/phpasn1", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/fgrosse/PHPASN1.git", + "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/42060ed45344789fb9f21f9f1864fc47b9e3507b", + "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "~2.0", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "suggest": { + "ext-bcmath": "BCmath is the fallback extension for big integer calculations", + "ext-curl": "For loading OID information from the web if they have not bee defined statically", + "ext-gmp": "GMP is the preferred extension for big integer calculations", + "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "FG\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Friedrich Große", + "email": "friedrich.grosse@gmail.com", + "homepage": "https://github.com/FGrosse", + "role": "Author" + }, + { + "name": "All contributors", + "homepage": "https://github.com/FGrosse/PHPASN1/contributors" + } + ], + "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", + "homepage": "https://github.com/FGrosse/PHPASN1", + "keywords": [ + "DER", + "asn.1", + "asn1", + "ber", + "binary", + "decoding", + "encoding", + "x.509", + "x.690", + "x509", + "x690" + ], + "support": { + "issues": "https://github.com/fgrosse/PHPASN1/issues", + "source": "https://github.com/fgrosse/PHPASN1/tree/v2.5.0" + }, + "abandoned": true, + "time": "2022-12-19T11:08:26+00:00" + }, { "name": "fig/http-message-util", "version": "1.1.5", @@ -1537,16 +1683,16 @@ }, { "name": "firebase/php-jwt", - "version": "v6.8.0", + "version": "v6.10.0", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "48b0210c51718d682e53210c24d25c5a10a2299b" + "reference": "a49db6f0a5033aef5143295342f1c95521b075ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/48b0210c51718d682e53210c24d25c5a10a2299b", - "reference": "48b0210c51718d682e53210c24d25c5a10a2299b", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/a49db6f0a5033aef5143295342f1c95521b075ff", + "reference": "a49db6f0a5033aef5143295342f1c95521b075ff", "shasum": "" }, "require": { @@ -1594,27 +1740,27 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.8.0" + "source": "https://github.com/firebase/php-jwt/tree/v6.10.0" }, - "time": "2023-06-20T16:45:35+00:00" + "time": "2023-12-01T16:26:39+00:00" }, { "name": "fruitcake/php-cors", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/fruitcake/php-cors.git", - "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e" + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/58571acbaa5f9f462c9c77e911700ac66f446d4e", - "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/3d158f36e7875e2f040f37bc0573956240a5a38b", + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "symfony/http-foundation": "^4.4|^5.4|^6" + "symfony/http-foundation": "^4.4|^5.4|^6|^7" }, "require-dev": { "phpstan/phpstan": "^1.4", @@ -1624,7 +1770,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.1-dev" + "dev-master": "1.2-dev" } }, "autoload": { @@ -1655,7 +1801,7 @@ ], "support": { "issues": "https://github.com/fruitcake/php-cors/issues", - "source": "https://github.com/fruitcake/php-cors/tree/v1.2.0" + "source": "https://github.com/fruitcake/php-cors/tree/v1.3.0" }, "funding": [ { @@ -1667,28 +1813,28 @@ "type": "github" } ], - "time": "2022-02-20T15:07:15+00:00" + "time": "2023-10-12T05:21:21+00:00" }, { "name": "graham-campbell/result-type", - "version": "v1.1.1", + "version": "v1.1.2", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831" + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", - "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862", + "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.1" + "phpoption/phpoption": "^1.9.2" }, "require-dev": { - "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" }, "type": "library", "autoload": { @@ -1717,7 +1863,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2" }, "funding": [ { @@ -1729,26 +1875,26 @@ "type": "tidelift" } ], - "time": "2023-02-25T20:23:15+00:00" + "time": "2023-11-12T22:16:48+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.7.0", + "version": "7.8.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5" + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0", - "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -1757,11 +1903,11 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -1839,7 +1985,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.7.0" + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" }, "funding": [ { @@ -1855,33 +2001,37 @@ "type": "tidelift" } ], - "time": "2023-05-21T14:04:53+00:00" + "time": "2023-12-03T20:35:24+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.5.3", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e" + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/67ab6e18aaa14d753cc148911d273f6e6cb6721e", - "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\Promise\\": "src/" } @@ -1918,7 +2068,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.3" + "source": "https://github.com/guzzle/promises/tree/2.0.2" }, "funding": [ { @@ -1934,20 +2084,20 @@ "type": "tidelift" } ], - "time": "2023-05-21T12:31:43+00:00" + "time": "2023-12-03T20:19:20+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.5.0", + "version": "2.6.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6" + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", "shasum": "" }, "require": { @@ -1961,9 +2111,9 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -2034,7 +2184,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.5.0" + "source": "https://github.com/guzzle/psr7/tree/2.6.2" }, "funding": [ { @@ -2050,34 +2200,36 @@ "type": "tidelift" } ], - "time": "2023-04-17T16:11:26+00:00" + "time": "2023-12-03T20:05:35+00:00" }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.1", + "version": "v1.0.3", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "b945d74a55a25a949158444f09ec0d3c120d69e2" + "reference": "ecea8feef63bd4fef1f037ecb288386999ecc11c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/b945d74a55a25a949158444f09ec0d3c120d69e2", - "reference": "b945d74a55a25a949158444f09ec0d3c120d69e2", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/ecea8feef63bd4fef1f037ecb288386999ecc11c", + "reference": "ecea8feef63bd4fef1f037ecb288386999ecc11c", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "symfony/polyfill-php80": "^1.17" + "symfony/polyfill-php80": "^1.24" }, "require-dev": { - "phpunit/phpunit": "^8.5.19 || ^9.5.8", + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", "uri-template/tests": "1.0.0" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.0-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { @@ -2118,7 +2270,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.1" + "source": "https://github.com/guzzle/uri-template/tree/v1.0.3" }, "funding": [ { @@ -2134,7 +2286,7 @@ "type": "tidelift" } ], - "time": "2021-10-07T12:57:01+00:00" + "time": "2023-12-03T19:50:20+00:00" }, { "name": "intervention/image", @@ -2222,16 +2374,16 @@ }, { "name": "jaybizzle/crawler-detect", - "version": "v1.2.115", + "version": "v1.2.116", "source": { "type": "git", "url": "https://github.com/JayBizzle/Crawler-Detect.git", - "reference": "4531e4a70d55d10cbe7d41ac1ff0d75a5fe2ef1e" + "reference": "97e9fe30219e60092e107651abb379a38b342921" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/4531e4a70d55d10cbe7d41ac1ff0d75a5fe2ef1e", - "reference": "4531e4a70d55d10cbe7d41ac1ff0d75a5fe2ef1e", + "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/97e9fe30219e60092e107651abb379a38b342921", + "reference": "97e9fe30219e60092e107651abb379a38b342921", "shasum": "" }, "require": { @@ -2268,9 +2420,9 @@ ], "support": { "issues": "https://github.com/JayBizzle/Crawler-Detect/issues", - "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.115" + "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.116" }, - "time": "2023-06-05T21:32:18+00:00" + "time": "2023-07-21T15:49:49+00:00" }, { "name": "jenssegers/agent", @@ -2356,17 +2508,75 @@ "time": "2020-06-13T08:05:20+00:00" }, { - "name": "laravel/framework", - "version": "v10.15.0", + "name": "laravel-notification-channels/webpush", + "version": "7.1.0", "source": { "type": "git", - "url": "https://github.com/laravel/framework.git", - "reference": "c7599dc92e04532824bafbd226c2936ce6a905b8" + "url": "https://github.com/laravel-notification-channels/webpush.git", + "reference": "b31f7d807d30c80e7391063291ebfe9683bb7de5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/c7599dc92e04532824bafbd226c2936ce6a905b8", - "reference": "c7599dc92e04532824bafbd226c2936ce6a905b8", + "url": "https://api.github.com/repos/laravel-notification-channels/webpush/zipball/b31f7d807d30c80e7391063291ebfe9683bb7de5", + "reference": "b31f7d807d30c80e7391063291ebfe9683bb7de5", + "shasum": "" + }, + "require": { + "illuminate/notifications": "^8.0|^9.0|^10.0", + "illuminate/support": "^8.0|^9.0|^10.0", + "minishlink/web-push": "^8.0", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "~1.0", + "orchestra/testbench": "^6.0|^7.0|^8.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NotificationChannels\\WebPush\\WebPushServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "NotificationChannels\\WebPush\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cretu Eusebiu", + "email": "me@cretueusebiu.com", + "homepage": "http://cretueusebiu.com", + "role": "Developer" + } + ], + "description": "Web Push Notifications driver for Laravel.", + "homepage": "https://github.com/laravel-notification-channels/webpush", + "support": { + "issues": "https://github.com/laravel-notification-channels/webpush/issues", + "source": "https://github.com/laravel-notification-channels/webpush/tree/7.1.0" + }, + "time": "2023-03-14T11:20:02+00:00" + }, + { + "name": "laravel/framework", + "version": "v10.34.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "c581caa233e380610b34cc491490bfa147a3b62b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/c581caa233e380610b34cc491490bfa147a3b62b", + "reference": "c581caa233e380610b34cc491490bfa147a3b62b", "shasum": "" }, "require": { @@ -2384,11 +2594,12 @@ "ext-tokenizer": "*", "fruitcake/php-cors": "^1.2", "guzzlehttp/uri-template": "^1.0", + "laravel/prompts": "^0.1.9", "laravel/serializable-closure": "^1.3", "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", "monolog/monolog": "^3.0", - "nesbot/carbon": "^2.62.1", + "nesbot/carbon": "^2.67", "nunomaduro/termwind": "^1.13", "php": "^8.1", "psr/container": "^1.1.1|^2.0.1", @@ -2398,7 +2609,7 @@ "symfony/console": "^6.2", "symfony/error-handler": "^6.2", "symfony/finder": "^6.2", - "symfony/http-foundation": "^6.2", + "symfony/http-foundation": "^6.3", "symfony/http-kernel": "^6.2", "symfony/mailer": "^6.2", "symfony/mime": "^6.2", @@ -2465,14 +2676,15 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^8.4", + "nyholm/psr7": "^1.2", + "orchestra/testbench-core": "^8.15.1", "pda/pheanstalk": "^4.0", - "phpstan/phpdoc-parser": "^1.15", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.0.7", "predis/predis": "^2.0.2", "symfony/cache": "^6.2", - "symfony/http-client": "^6.2.4" + "symfony/http-client": "^6.2.4", + "symfony/psr-http-message-bridge": "^2.0" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", @@ -2553,7 +2765,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-07-11T13:43:52+00:00" + "time": "2023-11-28T19:06:27+00:00" }, { "name": "laravel/helpers", @@ -2613,16 +2825,16 @@ }, { "name": "laravel/horizon", - "version": "v5.18.0", + "version": "v5.21.4", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "b14498a09af826035e46ae8d6b013d0ec849bdb7" + "reference": "bdf58c84b592b83f62262cc6ca98b0debbbc308b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/b14498a09af826035e46ae8d6b013d0ec849bdb7", - "reference": "b14498a09af826035e46ae8d6b013d0ec849bdb7", + "url": "https://api.github.com/repos/laravel/horizon/zipball/bdf58c84b592b83f62262cc6ca98b0debbbc308b", + "reference": "bdf58c84b592b83f62262cc6ca98b0debbbc308b", "shasum": "" }, "require": { @@ -2685,22 +2897,22 @@ ], "support": { "issues": "https://github.com/laravel/horizon/issues", - "source": "https://github.com/laravel/horizon/tree/v5.18.0" + "source": "https://github.com/laravel/horizon/tree/v5.21.4" }, - "time": "2023-06-30T15:11:51+00:00" + "time": "2023-11-23T15:47:58+00:00" }, { "name": "laravel/passport", - "version": "v11.8.8", + "version": "v11.10.0", "source": { "type": "git", "url": "https://github.com/laravel/passport.git", - "reference": "401836130d46c94138a637ada29f9e5b2bf053b6" + "reference": "966bc8e477d08c86a11dc4c5a86f85fa0abdb89b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/passport/zipball/401836130d46c94138a637ada29f9e5b2bf053b6", - "reference": "401836130d46c94138a637ada29f9e5b2bf053b6", + "url": "https://api.github.com/repos/laravel/passport/zipball/966bc8e477d08c86a11dc4c5a86f85fa0abdb89b", + "reference": "966bc8e477d08c86a11dc4c5a86f85fa0abdb89b", "shasum": "" }, "require": { @@ -2724,7 +2936,7 @@ }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^7.0|^8.0", + "orchestra/testbench": "^7.31|^8.11", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.3" }, @@ -2765,20 +2977,77 @@ "issues": "https://github.com/laravel/passport/issues", "source": "https://github.com/laravel/passport" }, - "time": "2023-07-07T06:37:11+00:00" + "time": "2023-11-02T17:16:12+00:00" }, { - "name": "laravel/serializable-closure", - "version": "v1.3.0", + "name": "laravel/prompts", + "version": "v0.1.13", "source": { "type": "git", - "url": "https://github.com/laravel/serializable-closure.git", - "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37" + "url": "https://github.com/laravel/prompts.git", + "reference": "e1379d8ead15edd6cc4369c22274345982edc95a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", - "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", + "url": "https://api.github.com/repos/laravel/prompts/zipball/e1379d8ead15edd6cc4369c22274345982edc95a", + "reference": "e1379d8ead15edd6cc4369c22274345982edc95a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "illuminate/collections": "^10.0|^11.0", + "php": "^8.1", + "symfony/console": "^6.2|^7.0" + }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-mockery": "^1.1" + }, + "suggest": { + "ext-pcntl": "Required for the spinner to be animated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.1.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Laravel\\Prompts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "support": { + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.1.13" + }, + "time": "2023-10-27T13:53:59+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.3.3", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "3dbf8a8e914634c48d389c1234552666b3d43754" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3dbf8a8e914634c48d389c1234552666b3d43754", + "reference": "3dbf8a8e914634c48d389c1234552666b3d43754", "shasum": "" }, "require": { @@ -2825,20 +3094,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2023-01-30T18:31:20+00:00" + "time": "2023-11-08T14:08:06+00:00" }, { "name": "laravel/tinker", - "version": "v2.8.1", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "04a2d3bd0d650c0764f70bf49d1ee39393e4eb10" + "reference": "b936d415b252b499e8c3b1f795cd4fc20f57e1f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/04a2d3bd0d650c0764f70bf49d1ee39393e4eb10", - "reference": "04a2d3bd0d650c0764f70bf49d1ee39393e4eb10", + "url": "https://api.github.com/repos/laravel/tinker/zipball/b936d415b252b499e8c3b1f795cd4fc20f57e1f3", + "reference": "b936d415b252b499e8c3b1f795cd4fc20f57e1f3", "shasum": "" }, "require": { @@ -2851,6 +3120,7 @@ }, "require-dev": { "mockery/mockery": "~1.3.3|^1.4.2", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^8.5.8|^9.3.3" }, "suggest": { @@ -2891,22 +3161,22 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.8.1" + "source": "https://github.com/laravel/tinker/tree/v2.8.2" }, - "time": "2023-02-15T16:40:09+00:00" + "time": "2023-08-15T14:27:00+00:00" }, { "name": "laravel/ui", - "version": "v4.2.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "a58ec468db4a340b33f3426c778784717a2c144b" + "reference": "eb532ea096ca1c0298c87c19233daf011fda743a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/a58ec468db4a340b33f3426c778784717a2c144b", - "reference": "a58ec468db4a340b33f3426c778784717a2c144b", + "url": "https://api.github.com/repos/laravel/ui/zipball/eb532ea096ca1c0298c87c19233daf011fda743a", + "reference": "eb532ea096ca1c0298c87c19233daf011fda743a", "shasum": "" }, "require": { @@ -2953,9 +3223,9 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v4.2.2" + "source": "https://github.com/laravel/ui/tree/v4.2.3" }, - "time": "2023-05-09T19:47:28+00:00" + "time": "2023-11-23T14:44:22+00:00" }, { "name": "lcobucci/clock", @@ -3023,37 +3293,35 @@ }, { "name": "lcobucci/jwt", - "version": "5.0.0", + "version": "5.2.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34" + "reference": "0ba88aed12c04bd2ed9924f500673f32b67a6211" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34", - "reference": "47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/0ba88aed12c04bd2ed9924f500673f32b67a6211", + "reference": "0ba88aed12c04bd2ed9924f500673f32b67a6211", "shasum": "" }, "require": { - "ext-hash": "*", - "ext-json": "*", "ext-openssl": "*", "ext-sodium": "*", - "php": "~8.1.0 || ~8.2.0", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0", "psr/clock": "^1.0" }, "require-dev": { - "infection/infection": "^0.26.19", + "infection/infection": "^0.27.0", "lcobucci/clock": "^3.0", - "lcobucci/coding-standard": "^9.0", - "phpbench/phpbench": "^1.2.8", + "lcobucci/coding-standard": "^11.0", + "phpbench/phpbench": "^1.2.9", "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.10.3", - "phpstan/phpstan-deprecation-rules": "^1.1.2", - "phpstan/phpstan-phpunit": "^1.3.8", + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", "phpstan/phpstan-strict-rules": "^1.5.0", - "phpunit/phpunit": "^10.0.12" + "phpunit/phpunit": "^10.2.6" }, "suggest": { "lcobucci/clock": ">= 3.0" @@ -3082,7 +3350,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/5.0.0" + "source": "https://github.com/lcobucci/jwt/tree/5.2.0" }, "funding": [ { @@ -3094,20 +3362,20 @@ "type": "patreon" } ], - "time": "2023-02-25T21:35:16+00:00" + "time": "2023-11-20T21:17:42+00:00" }, { "name": "league/commonmark", - "version": "2.4.0", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048" + "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d44a24690f16b8c1808bf13b1bd54ae4c63ea048", - "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/3669d6d5f7a47a93c08ddff335e6d945481a1dd5", + "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5", "shasum": "" }, "require": { @@ -3200,7 +3468,7 @@ "type": "tidelift" } ], - "time": "2023-03-24T15:16:10+00:00" + "time": "2023-08-30T16:55:00+00:00" }, { "name": "league/config", @@ -3340,16 +3608,16 @@ }, { "name": "league/flysystem", - "version": "3.15.1", + "version": "3.22.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "a141d430414fcb8bf797a18716b09f759a385bed" + "reference": "d18526ee587f265f091f47bb83f1d9a001ef6f36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a141d430414fcb8bf797a18716b09f759a385bed", - "reference": "a141d430414fcb8bf797a18716b09f759a385bed", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/d18526ee587f265f091f47bb83f1d9a001ef6f36", + "reference": "d18526ee587f265f091f47bb83f1d9a001ef6f36", "shasum": "" }, "require": { @@ -3358,6 +3626,8 @@ "php": "^8.0.2" }, "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", "aws/aws-sdk-php": "3.209.31 || 3.210.0", "guzzlehttp/guzzle": "<7.0", "guzzlehttp/ringphp": "<1.1.1", @@ -3365,8 +3635,8 @@ "symfony/http-client": "<5.2" }, "require-dev": { - "async-aws/s3": "^1.5", - "async-aws/simple-s3": "^1.1", + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", "aws/aws-sdk-php": "^3.220.0", "composer/semver": "^3.0", "ext-fileinfo": "*", @@ -3375,9 +3645,9 @@ "friendsofphp/php-cs-fixer": "^3.5", "google/cloud-storage": "^1.23", "microsoft/azure-storage-blob": "^1.1", - "phpseclib/phpseclib": "^3.0.14", - "phpstan/phpstan": "^0.12.26", - "phpunit/phpunit": "^9.5.11", + "phpseclib/phpseclib": "^3.0.34", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", "sabre/dav": "^4.3.1" }, "type": "library", @@ -3412,7 +3682,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.15.1" + "source": "https://github.com/thephpleague/flysystem/tree/3.22.0" }, "funding": [ { @@ -3424,20 +3694,20 @@ "type": "github" } ], - "time": "2023-05-04T09:04:26+00:00" + "time": "2023-12-03T18:35:53+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.15.0", + "version": "3.22.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "d8de61ee10b6a607e7996cff388c5a3a663e8c8a" + "reference": "9808919ee5d819730d9582d4e1673e8d195c38d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/d8de61ee10b6a607e7996cff388c5a3a663e8c8a", - "reference": "d8de61ee10b6a607e7996cff388c5a3a663e8c8a", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/9808919ee5d819730d9582d4e1673e8d195c38d8", + "reference": "9808919ee5d819730d9582d4e1673e8d195c38d8", "shasum": "" }, "require": { @@ -3478,7 +3748,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.15.0" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.22.0" }, "funding": [ { @@ -3490,20 +3760,20 @@ "type": "github" } ], - "time": "2023-05-02T20:02:14+00:00" + "time": "2023-11-18T14:03:37+00:00" }, { "name": "league/flysystem-local", - "version": "3.15.0", + "version": "3.22.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "543f64c397fefdf9cfeac443ffb6beff602796b3" + "reference": "42dfb4eaafc4accd248180f0dd66f17073b40c4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/543f64c397fefdf9cfeac443ffb6beff602796b3", - "reference": "543f64c397fefdf9cfeac443ffb6beff602796b3", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/42dfb4eaafc4accd248180f0dd66f17073b40c4c", + "reference": "42dfb4eaafc4accd248180f0dd66f17073b40c4c", "shasum": "" }, "require": { @@ -3538,7 +3808,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-local/issues", - "source": "https://github.com/thephpleague/flysystem-local/tree/3.15.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.22.0" }, "funding": [ { @@ -3550,20 +3820,20 @@ "type": "github" } ], - "time": "2023-05-02T20:02:14+00:00" + "time": "2023-11-18T20:52:53+00:00" }, { "name": "league/iso3166", - "version": "4.3.0", + "version": "4.3.1", "source": { "type": "git", "url": "https://github.com/thephpleague/iso3166.git", - "reference": "628f1b4992169917f3f59c14020ea4513c63f6db" + "reference": "11703e0313f34920add11c0228f0dd43ebd10f9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/iso3166/zipball/628f1b4992169917f3f59c14020ea4513c63f6db", - "reference": "628f1b4992169917f3f59c14020ea4513c63f6db", + "url": "https://api.github.com/repos/thephpleague/iso3166/zipball/11703e0313f34920add11c0228f0dd43ebd10f9a", + "reference": "11703e0313f34920add11c0228f0dd43ebd10f9a", "shasum": "" }, "require": { @@ -3608,30 +3878,30 @@ "issues": "https://github.com/thephpleague/iso3166/issues", "source": "https://github.com/thephpleague/iso3166" }, - "time": "2023-06-05T15:02:58+00:00" + "time": "2023-09-11T07:59:36+00:00" }, { "name": "league/mime-type-detection", - "version": "1.11.0", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" + "reference": "b6a5854368533df0295c5761a0253656a2e52d9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/b6a5854368533df0295c5761a0253656a2e52d9e", + "reference": "b6a5854368533df0295c5761a0253656a2e52d9e", "shasum": "" }, "require": { "ext-fileinfo": "*", - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" }, "type": "library", "autoload": { @@ -3652,7 +3922,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.14.0" }, "funding": [ { @@ -3664,20 +3934,20 @@ "type": "tidelift" } ], - "time": "2022-04-17T13:12:02+00:00" + "time": "2023-10-17T14:13:20+00:00" }, { "name": "league/oauth2-server", - "version": "8.5.3", + "version": "8.5.4", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth2-server.git", - "reference": "eb91b4190e7f6169053ebf8ffa352d47e756b2ce" + "reference": "ab7714d073844497fd222d5d0a217629089936bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/eb91b4190e7f6169053ebf8ffa352d47e756b2ce", - "reference": "eb91b4190e7f6169053ebf8ffa352d47e756b2ce", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/ab7714d073844497fd222d5d0a217629089936bc", + "reference": "ab7714d073844497fd222d5d0a217629089936bc", "shasum": "" }, "require": { @@ -3686,7 +3956,7 @@ "lcobucci/clock": "^2.2 || ^3.0", "lcobucci/jwt": "^4.3 || ^5.0", "league/event": "^2.2", - "league/uri": "^6.7", + "league/uri": "^6.7 || ^7.0", "php": "^8.0", "psr/http-message": "^1.0.1 || ^2.0" }, @@ -3744,7 +4014,7 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth2-server/issues", - "source": "https://github.com/thephpleague/oauth2-server/tree/8.5.3" + "source": "https://github.com/thephpleague/oauth2-server/tree/8.5.4" }, "funding": [ { @@ -3752,58 +4022,48 @@ "type": "github" } ], - "time": "2023-07-05T23:01:32+00:00" + "time": "2023-08-25T22:35:12+00:00" }, { "name": "league/uri", - "version": "6.8.0", + "version": "7.4.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "a700b4656e4c54371b799ac61e300ab25a2d1d39" + "reference": "bf414ba956d902f5d98bf9385fcf63954f09dce5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/a700b4656e4c54371b799ac61e300ab25a2d1d39", - "reference": "a700b4656e4c54371b799ac61e300ab25a2d1d39", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/bf414ba956d902f5d98bf9385fcf63954f09dce5", + "reference": "bf414ba956d902f5d98bf9385fcf63954f09dce5", "shasum": "" }, "require": { - "ext-json": "*", - "league/uri-interfaces": "^2.3", - "php": "^8.1", - "psr/http-message": "^1.0.1" + "league/uri-interfaces": "^7.3", + "php": "^8.1" }, "conflict": { "league/uri-schemes": "^1.0" }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v3.9.5", - "nyholm/psr7": "^1.5.1", - "php-http/psr7-integration-tests": "^1.1.1", - "phpbench/phpbench": "^1.2.6", - "phpstan/phpstan": "^1.8.5", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.1.1", - "phpstan/phpstan-strict-rules": "^1.4.3", - "phpunit/phpunit": "^9.5.24", - "psr/http-factory": "^1.0.1" - }, "suggest": { - "ext-fileinfo": "Needed to create Data URI from a filepath", - "ext-intl": "Needed to improve host validation", - "league/uri-components": "Needed to easily manipulate URI objects", - "psr/http-factory": "Needed to use the URI factory" + "ext-bcmath": "to improve IPV4 host parsing", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "league/uri-components": "Needed to easily manipulate URI objects components", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.x-dev" + "dev-master": "7.x-dev" } }, "autoload": { "psr-4": { - "League\\Uri\\": "src" + "League\\Uri\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -3843,8 +4103,8 @@ "support": { "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", - "issues": "https://github.com/thephpleague/uri/issues", - "source": "https://github.com/thephpleague/uri/tree/6.8.0" + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.4.0" }, "funding": [ { @@ -3852,46 +4112,44 @@ "type": "github" } ], - "time": "2022-09-13T19:58:47+00:00" + "time": "2023-12-01T06:24:25+00:00" }, { "name": "league/uri-interfaces", - "version": "2.3.0", + "version": "7.4.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383" + "reference": "bd8c487ec236930f7bbc42b8d374fa882fbba0f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/00e7e2943f76d8cb50c7dfdc2f6dee356e15e383", - "reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/bd8c487ec236930f7bbc42b8d374fa882fbba0f3", + "reference": "bd8c487ec236930f7bbc42b8d374fa882fbba0f3", "shasum": "" }, "require": { - "ext-json": "*", - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.19", - "phpstan/phpstan": "^0.12.90", - "phpstan/phpstan-phpunit": "^0.12.19", - "phpstan/phpstan-strict-rules": "^0.12.9", - "phpunit/phpunit": "^8.5.15 || ^9.5" + "ext-filter": "*", + "php": "^8.1", + "psr/http-factory": "^1", + "psr/http-message": "^1.1 || ^2.0" }, "suggest": { - "ext-intl": "to use the IDNA feature", - "symfony/intl": "to use the IDNA feature via Symfony Polyfill" + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-master": "7.x-dev" } }, "autoload": { "psr-4": { - "League\\Uri\\": "src/" + "League\\Uri\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -3905,17 +4163,32 @@ "homepage": "https://nyamsprod.com" } ], - "description": "Common interface for URI representation", - "homepage": "http://github.com/thephpleague/uri-interfaces", + "description": "Common interfaces and classes for URI representation and interaction", + "homepage": "https://uri.thephpleague.com", "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", "rfc3986", "rfc3987", + "rfc6570", "uri", - "url" + "url", + "ws" ], "support": { - "issues": "https://github.com/thephpleague/uri-interfaces/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/2.3.0" + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.4.0" }, "funding": [ { @@ -3923,27 +4196,94 @@ "type": "github" } ], - "time": "2021-06-28T04:27:21+00:00" + "time": "2023-11-24T15:40:42+00:00" }, { - "name": "mobiledetect/mobiledetectlib", - "version": "2.8.41", + "name": "minishlink/web-push", + "version": "v8.0.0", "source": { "type": "git", - "url": "https://github.com/serbanghita/Mobile-Detect.git", - "reference": "fc9cccd4d3706d5a7537b562b59cc18f9e4c0cb1" + "url": "https://github.com/web-push-libs/web-push-php.git", + "reference": "ec034f1e287cd1e74235e349bd017d71a61e9d8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/fc9cccd4d3706d5a7537b562b59cc18f9e4c0cb1", - "reference": "fc9cccd4d3706d5a7537b562b59cc18f9e4c0cb1", + "url": "https://api.github.com/repos/web-push-libs/web-push-php/zipball/ec034f1e287cd1e74235e349bd017d71a61e9d8d", + "reference": "ec034f1e287cd1e74235e349bd017d71a61e9d8d", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "guzzlehttp/guzzle": "^7.0.1|^6.2", + "php": ">=8.0", + "spomky-labs/base64url": "^2.0", + "web-token/jwt-key-mgmt": "^2.0|^3.0.2", + "web-token/jwt-signature": "^2.0|^3.0.2", + "web-token/jwt-signature-algorithm-ecdsa": "^2.0|^3.0.2", + "web-token/jwt-util-ecc": "^2.0|^3.0.2" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v3.13.2", + "phpstan/phpstan": "^1.9.8", + "phpunit/phpunit": "^9.5.27" + }, + "suggest": { + "ext-gmp": "Optional for performance." + }, + "type": "library", + "autoload": { + "psr-4": { + "Minishlink\\WebPush\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Louis Lagrange", + "email": "lagrange.louis@gmail.com", + "homepage": "https://github.com/Minishlink" + } + ], + "description": "Web Push library for PHP", + "homepage": "https://github.com/web-push-libs/web-push-php", + "keywords": [ + "Push API", + "WebPush", + "notifications", + "push", + "web" + ], + "support": { + "issues": "https://github.com/web-push-libs/web-push-php/issues", + "source": "https://github.com/web-push-libs/web-push-php/tree/v8.0.0" + }, + "time": "2023-01-10T17:14:44+00:00" + }, + { + "name": "mobiledetect/mobiledetectlib", + "version": "2.8.45", + "source": { + "type": "git", + "url": "https://github.com/serbanghita/Mobile-Detect.git", + "reference": "96aaebcf4f50d3d2692ab81d2c5132e425bca266" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/96aaebcf4f50d3d2692ab81d2c5132e425bca266", + "reference": "96aaebcf4f50d3d2692ab81d2c5132e425bca266", "shasum": "" }, "require": { "php": ">=5.0.0" }, "require-dev": { - "phpunit/phpunit": "~4.8.35||~5.7" + "phpunit/phpunit": "~4.8.36" }, "type": "library", "autoload": { @@ -3977,22 +4317,28 @@ ], "support": { "issues": "https://github.com/serbanghita/Mobile-Detect/issues", - "source": "https://github.com/serbanghita/Mobile-Detect/tree/2.8.41" + "source": "https://github.com/serbanghita/Mobile-Detect/tree/2.8.45" }, - "time": "2022-11-08T18:31:26+00:00" + "funding": [ + { + "url": "https://github.com/serbanghita", + "type": "github" + } + ], + "time": "2023-11-07T21:57:25+00:00" }, { "name": "monolog/monolog", - "version": "3.4.0", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "e2392369686d420ca32df3803de28b5d6f76867d" + "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/e2392369686d420ca32df3803de28b5d6f76867d", - "reference": "e2392369686d420ca32df3803de28b5d6f76867d", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c915e2634718dbc8a4a15c61b0e62e7a44e14448", + "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448", "shasum": "" }, "require": { @@ -4068,7 +4414,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.4.0" + "source": "https://github.com/Seldaek/monolog/tree/3.5.0" }, "funding": [ { @@ -4080,29 +4426,29 @@ "type": "tidelift" } ], - "time": "2023-06-21T08:46:11+00:00" + "time": "2023-10-27T15:32:31+00:00" }, { "name": "mtdowling/jmespath.php", - "version": "2.6.1", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b", + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0 || ^8.0", + "php": "^7.2.5 || ^8.0", "symfony/polyfill-mbstring": "^1.17" }, "require-dev": { - "composer/xdebug-handler": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^7.5.15" + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" }, "bin": [ "bin/jp.php" @@ -4110,7 +4456,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "2.7-dev" } }, "autoload": { @@ -4126,6 +4472,11 @@ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", @@ -4139,34 +4490,39 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" + "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0" }, - "time": "2021-06-14T00:11:39+00:00" + "time": "2023-08-25T10:54:48+00:00" }, { "name": "nesbot/carbon", - "version": "2.68.1", + "version": "2.72.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da" + "reference": "a6885fcbad2ec4360b0e200ee0da7d9b7c90786b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4f991ed2a403c85efbc4f23eb4030063fdbe01da", - "reference": "4f991ed2a403c85efbc4f23eb4030063fdbe01da", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/a6885fcbad2ec4360b0e200ee0da7d9b7c90786b", + "reference": "a6885fcbad2ec4360b0e200ee0da7d9b7c90786b", "shasum": "" }, "require": { + "carbonphp/carbon-doctrine-types": "*", "ext-json": "*", "php": "^7.1.8 || ^8.0", + "psr/clock": "^1.0", "symfony/polyfill-mbstring": "^1.0", "symfony/polyfill-php80": "^1.16", "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" }, + "provide": { + "psr/clock-implementation": "1.0" + }, "require-dev": { - "doctrine/dbal": "^2.0 || ^3.1.4", - "doctrine/orm": "^2.7", + "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0", + "doctrine/orm": "^2.7 || ^3.0", "friendsofphp/php-cs-fixer": "^3.0", "kylekatarnls/multi-tester": "^2.0", "ondrejmirtes/better-reflection": "*", @@ -4243,25 +4599,25 @@ "type": "tidelift" } ], - "time": "2023-06-20T18:29:04+00:00" + "time": "2023-11-28T10:13:25+00:00" }, { "name": "nette/schema", - "version": "v1.2.3", + "version": "v1.2.5", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f" + "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", - "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a", + "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a", "shasum": "" }, "require": { "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.3" + "php": "7.1 - 8.3" }, "require-dev": { "nette/tester": "^2.3 || ^2.4", @@ -4303,26 +4659,26 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.3" + "source": "https://github.com/nette/schema/tree/v1.2.5" }, - "time": "2022-10-13T01:24:26+00:00" + "time": "2023-10-05T20:37:59+00:00" }, { "name": "nette/utils", - "version": "v4.0.0", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e" + "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/cacdbf5a91a657ede665c541eda28941d4b09c1e", - "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e", + "url": "https://api.github.com/repos/nette/utils/zipball/a9d127dd6a203ce6d255b2e2db49759f7506e015", + "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015", "shasum": "" }, "require": { - "php": ">=8.0 <8.3" + "php": ">=8.0 <8.4" }, "conflict": { "nette/finder": "<3", @@ -4330,7 +4686,7 @@ }, "require-dev": { "jetbrains/phpstorm-attributes": "dev-master", - "nette/tester": "^2.4", + "nette/tester": "^2.5", "phpstan/phpstan": "^1.0", "tracy/tracy": "^2.9" }, @@ -4340,8 +4696,7 @@ "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", "ext-json": "to use Nette\\Utils\\Json", "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" }, "type": "library", "extra": { @@ -4390,22 +4745,22 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.0" + "source": "https://github.com/nette/utils/tree/v4.0.3" }, - "time": "2023-02-02T10:41:53+00:00" + "time": "2023-10-29T21:02:13+00:00" }, { "name": "nikic/php-parser", - "version": "v4.16.0", + "version": "v4.17.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "19526a33fb561ef417e822e85f08a00db4059c17" + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17", - "reference": "19526a33fb561ef417e822e85f08a00db4059c17", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", "shasum": "" }, "require": { @@ -4446,9 +4801,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" }, - "time": "2023-06-25T14:52:30+00:00" + "time": "2023-08-13T19:53:39+00:00" }, { "name": "nunomaduro/termwind", @@ -4538,16 +4893,16 @@ }, { "name": "nyholm/psr7", - "version": "1.8.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/Nyholm/psr7.git", - "reference": "3cb4d163b58589e47b35103e8e5e6a6a475b47be" + "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nyholm/psr7/zipball/3cb4d163b58589e47b35103e8e5e6a6a475b47be", - "reference": "3cb4d163b58589e47b35103e8e5e6a6a475b47be", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/aa5fc277a4f5508013d571341ade0c3886d4d00e", + "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e", "shasum": "" }, "require": { @@ -4600,7 +4955,7 @@ ], "support": { "issues": "https://github.com/Nyholm/psr7/issues", - "source": "https://github.com/Nyholm/psr7/tree/1.8.0" + "source": "https://github.com/Nyholm/psr7/tree/1.8.1" }, "funding": [ { @@ -4612,7 +4967,7 @@ "type": "github" } ], - "time": "2023-05-02T11:26:24+00:00" + "time": "2023-11-13T09:31:12+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -4986,16 +5341,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.1", + "version": "1.9.2", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e" + "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e", - "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820", + "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820", "shasum": "" }, "require": { @@ -5003,7 +5358,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" }, "type": "library", "extra": { @@ -5045,7 +5400,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.1" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.2" }, "funding": [ { @@ -5057,20 +5412,20 @@ "type": "tidelift" } ], - "time": "2023-02-25T19:38:58+00:00" + "time": "2023-11-12T21:59:55+00:00" }, { "name": "phpseclib/phpseclib", - "version": "2.0.44", + "version": "2.0.45", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "149f608243f8133c61926aae26ce67d2b22b37e5" + "reference": "28d8f438a0064c9de80857e3270d071495544640" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/149f608243f8133c61926aae26ce67d2b22b37e5", - "reference": "149f608243f8133c61926aae26ce67d2b22b37e5", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/28d8f438a0064c9de80857e3270d071495544640", + "reference": "28d8f438a0064c9de80857e3270d071495544640", "shasum": "" }, "require": { @@ -5151,7 +5506,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/2.0.44" + "source": "https://github.com/phpseclib/phpseclib/tree/2.0.45" }, "funding": [ { @@ -5167,7 +5522,7 @@ "type": "tidelift" } ], - "time": "2023-06-13T08:41:47+00:00" + "time": "2023-09-15T20:55:47+00:00" }, { "name": "pixelfed/fractal", @@ -5398,16 +5753,16 @@ }, { "name": "predis/predis", - "version": "v2.2.0", + "version": "v2.2.2", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "33b70b971a32b0d28b4f748b0547593dce316e0d" + "reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/33b70b971a32b0d28b4f748b0547593dce316e0d", - "reference": "33b70b971a32b0d28b4f748b0547593dce316e0d", + "url": "https://api.github.com/repos/predis/predis/zipball/b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1", + "reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1", "shasum": "" }, "require": { @@ -5447,7 +5802,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v2.2.0" + "source": "https://github.com/predis/predis/tree/v2.2.2" }, "funding": [ { @@ -5455,7 +5810,7 @@ "type": "github" } ], - "time": "2023-06-14T10:37:31+00:00" + "time": "2023-09-13T16:42:03+00:00" }, { "name": "psr/cache", @@ -5659,16 +6014,16 @@ }, { "name": "psr/http-client", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { @@ -5705,9 +6060,9 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/1.0.2" + "source": "https://github.com/php-fig/http-client" }, - "time": "2023-04-10T20:12:12+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", @@ -5920,16 +6275,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.18", + "version": "v0.11.22", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "4f00ee9e236fa6a48f4560d1300b9c961a70a7ec" + "reference": "128fa1b608be651999ed9789c95e6e2a31b5802b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4f00ee9e236fa6a48f4560d1300b9c961a70a7ec", - "reference": "4f00ee9e236fa6a48f4560d1300b9c961a70a7ec", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/128fa1b608be651999ed9789c95e6e2a31b5802b", + "reference": "128fa1b608be651999ed9789c95e6e2a31b5802b", "shasum": "" }, "require": { @@ -5958,7 +6313,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "0.11.x-dev" + "dev-0.11": "0.11.x-dev" + }, + "bamarni-bin": { + "bin-links": false, + "forward-command": false } }, "autoload": { @@ -5990,9 +6349,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.18" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.22" }, - "time": "2023-05-23T02:31:11+00:00" + "time": "2023-10-14T21:56:36+00:00" }, { "name": "pusher/pusher-php-server", @@ -6190,16 +6549,16 @@ }, { "name": "ramsey/uuid", - "version": "4.7.4", + "version": "4.7.5", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "60a4c63ab724854332900504274f6150ff26d286" + "reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/60a4c63ab724854332900504274f6150ff26d286", - "reference": "60a4c63ab724854332900504274f6150ff26d286", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e", + "reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e", "shasum": "" }, "require": { @@ -6266,7 +6625,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.4" + "source": "https://github.com/ramsey/uuid/tree/4.7.5" }, "funding": [ { @@ -6278,7 +6637,7 @@ "type": "tidelift" } ], - "time": "2023-04-15T23:01:58+00:00" + "time": "2023-11-08T05:53:05+00:00" }, { "name": "ratchet/rfc6455", @@ -6411,16 +6770,16 @@ }, { "name": "react/dns", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/reactphp/dns.git", - "reference": "3be0fc8f1eb37d6875cd6f0c6c7d0be81435de9f" + "reference": "c134600642fa615b46b41237ef243daa65bb64ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/3be0fc8f1eb37d6875cd6f0c6c7d0be81435de9f", - "reference": "3be0fc8f1eb37d6875cd6f0c6c7d0be81435de9f", + "url": "https://api.github.com/repos/reactphp/dns/zipball/c134600642fa615b46b41237ef243daa65bb64ec", + "reference": "c134600642fa615b46b41237ef243daa65bb64ec", "shasum": "" }, "require": { @@ -6430,7 +6789,7 @@ "react/promise": "^3.0 || ^2.7 || ^1.2.1" }, "require-dev": { - "phpunit/phpunit": "^9.5 || ^4.8.35", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", "react/async": "^4 || ^3 || ^2", "react/promise-timer": "^1.9" }, @@ -6475,7 +6834,7 @@ ], "support": { "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.11.0" + "source": "https://github.com/reactphp/dns/tree/v1.12.0" }, "funding": [ { @@ -6483,20 +6842,20 @@ "type": "open_collective" } ], - "time": "2023-06-02T12:45:26+00:00" + "time": "2023-11-29T12:41:06+00:00" }, { "name": "react/event-loop", - "version": "v1.4.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/reactphp/event-loop.git", - "reference": "6e7e587714fff7a83dcc7025aee42ab3b265ae05" + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/6e7e587714fff7a83dcc7025aee42ab3b265ae05", - "reference": "6e7e587714fff7a83dcc7025aee42ab3b265ae05", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", "shasum": "" }, "require": { @@ -6547,7 +6906,7 @@ ], "support": { "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.4.0" + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" }, "funding": [ { @@ -6555,7 +6914,7 @@ "type": "open_collective" } ], - "time": "2023-05-05T10:11:24+00:00" + "time": "2023-11-13T13:48:05+00:00" }, { "name": "react/http", @@ -6651,24 +7010,24 @@ }, { "name": "react/promise", - "version": "v3.0.0", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/reactphp/promise.git", - "reference": "c86753c76fd3be465d93b308f18d189f01a22be4" + "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/c86753c76fd3be465d93b308f18d189f01a22be4", - "reference": "c86753c76fd3be465d93b308f18d189f01a22be4", + "url": "https://api.github.com/repos/reactphp/promise/zipball/e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", + "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", "shasum": "" }, "require": { "php": ">=7.1.0" }, "require-dev": { - "phpstan/phpstan": "1.10.20 || 1.4.10", - "phpunit/phpunit": "^9.5 || ^7.5" + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" }, "type": "library", "autoload": { @@ -6712,7 +7071,7 @@ ], "support": { "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.0.0" + "source": "https://github.com/reactphp/promise/tree/v3.1.0" }, "funding": [ { @@ -6720,20 +7079,20 @@ "type": "open_collective" } ], - "time": "2023-07-11T16:12:49+00:00" + "time": "2023-11-16T16:21:57+00:00" }, { "name": "react/socket", - "version": "v1.13.0", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/reactphp/socket.git", - "reference": "cff482bbad5848ecbe8b57da57e4e213b03619aa" + "reference": "21591111d3ea62e31f2254280ca0656bc2b1bda6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/cff482bbad5848ecbe8b57da57e4e213b03619aa", - "reference": "cff482bbad5848ecbe8b57da57e4e213b03619aa", + "url": "https://api.github.com/repos/reactphp/socket/zipball/21591111d3ea62e31f2254280ca0656bc2b1bda6", + "reference": "21591111d3ea62e31f2254280ca0656bc2b1bda6", "shasum": "" }, "require": { @@ -6748,7 +7107,7 @@ "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", "react/async": "^4 || ^3 || ^2", "react/promise-stream": "^1.4", - "react/promise-timer": "^1.9" + "react/promise-timer": "^1.10" }, "type": "library", "autoload": { @@ -6792,7 +7151,7 @@ ], "support": { "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.13.0" + "source": "https://github.com/reactphp/socket/tree/v1.14.0" }, "funding": [ { @@ -6800,7 +7159,7 @@ "type": "open_collective" } ], - "time": "2023-06-07T10:28:34+00:00" + "time": "2023-08-25T13:48:09+00:00" }, { "name": "react/stream", @@ -7006,28 +7365,28 @@ }, { "name": "spatie/image-optimizer", - "version": "1.6.4", + "version": "1.7.2", "source": { "type": "git", "url": "https://github.com/spatie/image-optimizer.git", - "reference": "d997e01ba980b2769ddca2f00badd3b80c2a2512" + "reference": "62f7463483d1bd975f6f06025d89d42a29608fe1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/image-optimizer/zipball/d997e01ba980b2769ddca2f00badd3b80c2a2512", - "reference": "d997e01ba980b2769ddca2f00badd3b80c2a2512", + "url": "https://api.github.com/repos/spatie/image-optimizer/zipball/62f7463483d1bd975f6f06025d89d42a29608fe1", + "reference": "62f7463483d1bd975f6f06025d89d42a29608fe1", "shasum": "" }, "require": { "ext-fileinfo": "*", "php": "^7.3|^8.0", "psr/log": "^1.0 | ^2.0 | ^3.0", - "symfony/process": "^4.2|^5.0|^6.0" + "symfony/process": "^4.2|^5.0|^6.0|^7.0" }, "require-dev": { "pestphp/pest": "^1.21", "phpunit/phpunit": "^8.5.21|^9.4.4", - "symfony/var-dumper": "^4.2|^5.0|^6.0" + "symfony/var-dumper": "^4.2|^5.0|^6.0|^7.0" }, "type": "library", "autoload": { @@ -7055,34 +7414,34 @@ ], "support": { "issues": "https://github.com/spatie/image-optimizer/issues", - "source": "https://github.com/spatie/image-optimizer/tree/1.6.4" + "source": "https://github.com/spatie/image-optimizer/tree/1.7.2" }, - "time": "2023-03-10T08:43:19+00:00" + "time": "2023-11-03T10:08:02+00:00" }, { "name": "spatie/laravel-backup", - "version": "8.1.11", + "version": "8.4.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-backup.git", - "reference": "e4f5c3f6783d40a219a02bc99dc4171ecdd6d20c" + "reference": "b79f790cc856e67cce012abf34bf1c9035085dc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-backup/zipball/e4f5c3f6783d40a219a02bc99dc4171ecdd6d20c", - "reference": "e4f5c3f6783d40a219a02bc99dc4171ecdd6d20c", + "url": "https://api.github.com/repos/spatie/laravel-backup/zipball/b79f790cc856e67cce012abf34bf1c9035085dc1", + "reference": "b79f790cc856e67cce012abf34bf1c9035085dc1", "shasum": "" }, "require": { "ext-zip": "^1.14.0", - "illuminate/console": "^9.0|^10.0", - "illuminate/contracts": "^9.0|^10.0", - "illuminate/events": "^9.0|^10.0", - "illuminate/filesystem": "^9.0|^10.0", - "illuminate/notifications": "^9.0|^10.0", - "illuminate/support": "^9.0|^10.0", + "illuminate/console": "^10.10.0", + "illuminate/contracts": "^10.10.0", + "illuminate/events": "^10.10.0", + "illuminate/filesystem": "^10.10.0", + "illuminate/notifications": "^10.10.0", + "illuminate/support": "^10.10.0", "league/flysystem": "^3.0", - "php": "^8.0", + "php": "^8.1", "spatie/db-dumper": "^3.0", "spatie/laravel-package-tools": "^1.6.2", "spatie/laravel-signal-aware-command": "^1.2", @@ -7097,7 +7456,7 @@ "league/flysystem-aws-s3-v3": "^2.0|^3.0", "mockery/mockery": "^1.4", "nunomaduro/larastan": "^2.1", - "orchestra/testbench": "^7.0|^8.0", + "orchestra/testbench": "^8.0", "pestphp/pest": "^1.20", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", @@ -7144,7 +7503,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-backup/issues", - "source": "https://github.com/spatie/laravel-backup/tree/8.1.11" + "source": "https://github.com/spatie/laravel-backup/tree/8.4.1" }, "funding": [ { @@ -7156,7 +7515,7 @@ "type": "other" } ], - "time": "2023-06-02T08:56:10+00:00" + "time": "2023-11-20T08:21:45+00:00" }, { "name": "spatie/laravel-image-optimizer", @@ -7228,16 +7587,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.15.0", + "version": "1.16.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "efab1844b8826443135201c4443690f032c3d533" + "reference": "cc7c991555a37f9fa6b814aa03af73f88026a83d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/efab1844b8826443135201c4443690f032c3d533", - "reference": "efab1844b8826443135201c4443690f032c3d533", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/cc7c991555a37f9fa6b814aa03af73f88026a83d", + "reference": "cc7c991555a37f9fa6b814aa03af73f88026a83d", "shasum": "" }, "require": { @@ -7276,7 +7635,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.15.0" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.1" }, "funding": [ { @@ -7284,7 +7643,7 @@ "type": "github" } ], - "time": "2023-04-27T08:09:01+00:00" + "time": "2023-08-23T09:04:39+00:00" }, { "name": "spatie/laravel-signal-aware-command", @@ -7362,16 +7721,16 @@ }, { "name": "spatie/temporary-directory", - "version": "2.1.2", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/spatie/temporary-directory.git", - "reference": "0c804873f6b4042aa8836839dca683c7d0f71831" + "reference": "efc258c9f4da28f0c7661765b8393e4ccee3d19c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/0c804873f6b4042aa8836839dca683c7d0f71831", - "reference": "0c804873f6b4042aa8836839dca683c7d0f71831", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/efc258c9f4da28f0c7661765b8393e4ccee3d19c", + "reference": "efc258c9f4da28f0c7661765b8393e4ccee3d19c", "shasum": "" }, "require": { @@ -7407,7 +7766,7 @@ ], "support": { "issues": "https://github.com/spatie/temporary-directory/issues", - "source": "https://github.com/spatie/temporary-directory/tree/2.1.2" + "source": "https://github.com/spatie/temporary-directory/tree/2.2.0" }, "funding": [ { @@ -7419,20 +7778,85 @@ "type": "github" } ], - "time": "2023-04-28T07:47:42+00:00" + "time": "2023-09-25T07:13:36+00:00" }, { - "name": "stevebauman/purify", - "version": "v6.0.1", + "name": "spomky-labs/base64url", + "version": "v2.0.4", "source": { "type": "git", - "url": "https://github.com/stevebauman/purify.git", - "reference": "7b63762b05db9eadc36d7e8b74cf58fa64bfa527" + "url": "https://github.com/Spomky-Labs/base64url.git", + "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stevebauman/purify/zipball/7b63762b05db9eadc36d7e8b74cf58fa64bfa527", - "reference": "7b63762b05db9eadc36d7e8b74cf58fa64bfa527", + "url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/7752ce931ec285da4ed1f4c5aa27e45e097be61d", + "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.11|^0.12", + "phpstan/phpstan-beberlei-assert": "^0.11|^0.12", + "phpstan/phpstan-deprecation-rules": "^0.11|^0.12", + "phpstan/phpstan-phpunit": "^0.11|^0.12", + "phpstan/phpstan-strict-rules": "^0.11|^0.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "Base64Url\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky-Labs/base64url/contributors" + } + ], + "description": "Base 64 URL Safe Encoding/Decoding PHP Library", + "homepage": "https://github.com/Spomky-Labs/base64url", + "keywords": [ + "base64", + "rfc4648", + "safe", + "url" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/base64url/issues", + "source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2020-11-03T09:10:25+00:00" + }, + { + "name": "stevebauman/purify", + "version": "v6.0.2", + "source": { + "type": "git", + "url": "https://github.com/stevebauman/purify.git", + "reference": "ce8d10c0dfe804d90470ff819b84d891037cd6bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stevebauman/purify/zipball/ce8d10c0dfe804d90470ff819b84d891037cd6bc", + "reference": "ce8d10c0dfe804d90470ff819b84d891037cd6bc", "shasum": "" }, "require": { @@ -7483,22 +7907,22 @@ ], "support": { "issues": "https://github.com/stevebauman/purify/issues", - "source": "https://github.com/stevebauman/purify/tree/v6.0.1" + "source": "https://github.com/stevebauman/purify/tree/v6.0.2" }, - "time": "2023-04-06T21:16:20+00:00" + "time": "2023-08-24T18:53:12+00:00" }, { "name": "symfony/cache", - "version": "v6.3.1", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "52cff7608ef6e38376ac11bd1fbb0a220107f066" + "reference": "ac2d25f97b17eec6e19760b6b9962a4f7c44356a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/52cff7608ef6e38376ac11bd1fbb0a220107f066", - "reference": "52cff7608ef6e38376ac11bd1fbb0a220107f066", + "url": "https://api.github.com/repos/symfony/cache/zipball/ac2d25f97b17eec6e19760b6b9962a4f7c44356a", + "reference": "ac2d25f97b17eec6e19760b6b9962a4f7c44356a", "shasum": "" }, "require": { @@ -7507,7 +7931,7 @@ "psr/log": "^1.1|^2|^3", "symfony/cache-contracts": "^2.5|^3", "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^6.2.10" + "symfony/var-exporter": "^6.3.6|^7.0" }, "conflict": { "doctrine/dbal": "<2.13.1", @@ -7522,15 +7946,15 @@ }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/dbal": "^2.13.1|^3.0", + "doctrine/dbal": "^2.13.1|^3|^4", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/filesystem": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -7565,7 +7989,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.3.1" + "source": "https://github.com/symfony/cache/tree/v6.4.0" }, "funding": [ { @@ -7581,20 +8005,20 @@ "type": "tidelift" } ], - "time": "2023-06-24T11:51:27+00:00" + "time": "2023-11-24T19:28:07+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "ad945640ccc0ae6e208bcea7d7de4b39b569896b" + "reference": "1d74b127da04ffa87aa940abe15446fa89653778" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/ad945640ccc0ae6e208bcea7d7de4b39b569896b", - "reference": "ad945640ccc0ae6e208bcea7d7de4b39b569896b", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/1d74b127da04ffa87aa940abe15446fa89653778", + "reference": "1d74b127da04ffa87aa940abe15446fa89653778", "shasum": "" }, "require": { @@ -7641,7 +8065,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/cache-contracts/tree/v3.4.0" }, "funding": [ { @@ -7657,20 +8081,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2023-09-25T12:52:38+00:00" }, { "name": "symfony/console", - "version": "v6.3.0", + "version": "v6.4.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7" + "reference": "a550a7c99daeedef3f9d23fb82e3531525ff11fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", - "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", + "url": "https://api.github.com/repos/symfony/console/zipball/a550a7c99daeedef3f9d23fb82e3531525ff11fd", + "reference": "a550a7c99daeedef3f9d23fb82e3531525ff11fd", "shasum": "" }, "require": { @@ -7678,7 +8102,7 @@ "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0" + "symfony/string": "^5.4|^6.0|^7.0" }, "conflict": { "symfony/dependency-injection": "<5.4", @@ -7692,12 +8116,16 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/lock": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -7731,7 +8159,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.3.0" + "source": "https://github.com/symfony/console/tree/v6.4.1" }, "funding": [ { @@ -7747,20 +8175,20 @@ "type": "tidelift" } ], - "time": "2023-05-29T12:49:39+00:00" + "time": "2023-11-30T10:54:28+00:00" }, { "name": "symfony/css-selector", - "version": "v6.3.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf" + "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf", - "reference": "88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/d036c6c0d0b09e24a14a35f8292146a658f986e4", + "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4", "shasum": "" }, "require": { @@ -7796,7 +8224,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.3.0" + "source": "https://github.com/symfony/css-selector/tree/v6.4.0" }, "funding": [ { @@ -7812,11 +8240,11 @@ "type": "tidelift" } ], - "time": "2023-03-20T16:43:42+00:00" + "time": "2023-10-31T08:40:20+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", @@ -7863,7 +8291,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -7883,30 +8311,31 @@ }, { "name": "symfony/error-handler", - "version": "v6.3.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "99d2d814a6351461af350ead4d963bd67451236f" + "reference": "c873490a1c97b3a0a4838afc36ff36c112d02788" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/99d2d814a6351461af350ead4d963bd67451236f", - "reference": "99d2d814a6351461af350ead4d963bd67451236f", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/c873490a1c97b3a0a4838afc36ff36c112d02788", + "reference": "c873490a1c97b3a0a4838afc36ff36c112d02788", "shasum": "" }, "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "conflict": { - "symfony/deprecation-contracts": "<2.5" + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" }, "require-dev": { "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0" + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^5.4|^6.0|^7.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -7937,7 +8366,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.3.0" + "source": "https://github.com/symfony/error-handler/tree/v6.4.0" }, "funding": [ { @@ -7953,20 +8382,20 @@ "type": "tidelift" } ], - "time": "2023-05-10T12:03:13+00:00" + "time": "2023-10-18T09:43:34+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.3.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa" + "reference": "d76d2632cfc2206eecb5ad2b26cd5934082941b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", - "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d76d2632cfc2206eecb5ad2b26cd5934082941b6", + "reference": "d76d2632cfc2206eecb5ad2b26cd5934082941b6", "shasum": "" }, "require": { @@ -7983,13 +8412,13 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/error-handler": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^5.4|^6.0" + "symfony/stopwatch": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -8017,7 +8446,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.0" }, "funding": [ { @@ -8033,11 +8462,11 @@ "type": "tidelift" } ], - "time": "2023-04-21T14:41:17+00:00" + "time": "2023-07-27T06:52:43+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", @@ -8093,7 +8522,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0" }, "funding": [ { @@ -8113,23 +8542,23 @@ }, { "name": "symfony/finder", - "version": "v6.3.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2" + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/d9b01ba073c44cef617c7907ce2419f8d00d75e2", - "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2", + "url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce", + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce", "shasum": "" }, "require": { "php": ">=8.1" }, "require-dev": { - "symfony/filesystem": "^6.0" + "symfony/filesystem": "^6.0|^7.0" }, "type": "library", "autoload": { @@ -8157,7 +8586,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.3.0" + "source": "https://github.com/symfony/finder/tree/v6.4.0" }, "funding": [ { @@ -8173,20 +8602,20 @@ "type": "tidelift" } ], - "time": "2023-04-02T01:25:41+00:00" + "time": "2023-10-31T17:30:12+00:00" }, { "name": "symfony/http-client", - "version": "v6.3.1", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "1c828a06aef2f5eeba42026dfc532d4fc5406123" + "reference": "5c584530b77aa10ae216989ffc48b4bedc9c0b29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/1c828a06aef2f5eeba42026dfc532d4fc5406123", - "reference": "1c828a06aef2f5eeba42026dfc532d4fc5406123", + "url": "https://api.github.com/repos/symfony/http-client/zipball/5c584530b77aa10ae216989ffc48b4bedc9c0b29", + "reference": "5c584530b77aa10ae216989ffc48b4bedc9c0b29", "shasum": "" }, "require": { @@ -8215,10 +8644,11 @@ "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/stopwatch": "^5.4|^6.0" + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -8249,7 +8679,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.3.1" + "source": "https://github.com/symfony/http-client/tree/v6.4.0" }, "funding": [ { @@ -8265,20 +8695,20 @@ "type": "tidelift" } ], - "time": "2023-06-24T11:51:27+00:00" + "time": "2023-11-28T20:55:58+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb" + "reference": "1ee70e699b41909c209a0c930f11034b93578654" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/3b66325d0176b4ec826bffab57c9037d759c31fb", - "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1ee70e699b41909c209a0c930f11034b93578654", + "reference": "1ee70e699b41909c209a0c930f11034b93578654", "shasum": "" }, "require": { @@ -8327,7 +8757,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.4.0" }, "funding": [ { @@ -8343,20 +8773,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2023-07-30T20:28:31+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.3.1", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "e0ad0d153e1c20069250986cd9e9dd1ccebb0d66" + "reference": "44a6d39a9cc11e154547d882d5aac1e014440771" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e0ad0d153e1c20069250986cd9e9dd1ccebb0d66", - "reference": "e0ad0d153e1c20069250986cd9e9dd1ccebb0d66", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/44a6d39a9cc11e154547d882d5aac1e014440771", + "reference": "44a6d39a9cc11e154547d882d5aac1e014440771", "shasum": "" }, "require": { @@ -8366,17 +8796,17 @@ "symfony/polyfill-php83": "^1.27" }, "conflict": { - "symfony/cache": "<6.2" + "symfony/cache": "<6.3" }, "require-dev": { - "doctrine/dbal": "^2.13.1|^3.0", + "doctrine/dbal": "^2.13.1|^3|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", - "symfony/mime": "^5.4|^6.0", - "symfony/rate-limiter": "^5.2|^6.0" + "symfony/cache": "^6.3|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/rate-limiter": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -8404,7 +8834,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.3.1" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.0" }, "funding": [ { @@ -8420,29 +8850,29 @@ "type": "tidelift" } ], - "time": "2023-06-24T11:51:27+00:00" + "time": "2023-11-20T16:41:16+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.3.1", + "version": "v6.4.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "161e16fd2e35fb4881a43bc8b383dfd5be4ac374" + "reference": "2953274c16a229b3933ef73a6898e18388e12e1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/161e16fd2e35fb4881a43bc8b383dfd5be4ac374", - "reference": "161e16fd2e35fb4881a43bc8b383dfd5be4ac374", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/2953274c16a229b3933ef73a6898e18388e12e1b", + "reference": "2953274c16a229b3933ef73a6898e18388e12e1b", "shasum": "" }, "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/error-handler": "^6.3", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/http-foundation": "^6.2.7", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -8450,7 +8880,7 @@ "symfony/cache": "<5.4", "symfony/config": "<6.1", "symfony/console": "<5.4", - "symfony/dependency-injection": "<6.3", + "symfony/dependency-injection": "<6.4", "symfony/doctrine-bridge": "<5.4", "symfony/form": "<5.4", "symfony/http-client": "<5.4", @@ -8460,7 +8890,7 @@ "symfony/translation": "<5.4", "symfony/translation-contracts": "<2.5", "symfony/twig-bridge": "<5.4", - "symfony/validator": "<5.4", + "symfony/validator": "<6.4", "symfony/var-dumper": "<6.3", "twig/twig": "<2.13" }, @@ -8469,26 +8899,26 @@ }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/clock": "^6.2", - "symfony/config": "^6.1", - "symfony/console": "^5.4|^6.0", - "symfony/css-selector": "^5.4|^6.0", - "symfony/dependency-injection": "^6.3", - "symfony/dom-crawler": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/clock": "^6.2|^7.0", + "symfony/config": "^6.1|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", "symfony/http-client-contracts": "^2.5|^3", - "symfony/process": "^5.4|^6.0", - "symfony/property-access": "^5.4.5|^6.0.5", - "symfony/routing": "^5.4|^6.0", - "symfony/serializer": "^6.3", - "symfony/stopwatch": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4.5|^6.0.5|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.3|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^5.4|^6.0", - "symfony/validator": "^6.3", - "symfony/var-exporter": "^6.2", + "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-exporter": "^6.2|^7.0", "twig/twig": "^2.13|^3.0.4" }, "type": "library", @@ -8517,7 +8947,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/v6.3.1" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.1" }, "funding": [ { @@ -8533,20 +8963,20 @@ "type": "tidelift" } ], - "time": "2023-06-26T06:07:32+00:00" + "time": "2023-12-01T17:02:02+00:00" }, { "name": "symfony/mailer", - "version": "v6.3.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435" + "reference": "ca8dcf8892cdc5b4358ecf2528429bb5e706f7ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/7b03d9be1dea29bfec0a6c7b603f5072a4c97435", - "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435", + "url": "https://api.github.com/repos/symfony/mailer/zipball/ca8dcf8892cdc5b4358ecf2528429bb5e706f7ba", + "reference": "ca8dcf8892cdc5b4358ecf2528429bb5e706f7ba", "shasum": "" }, "require": { @@ -8554,8 +8984,8 @@ "php": ">=8.1", "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/mime": "^6.2", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/mime": "^6.2|^7.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -8566,10 +8996,10 @@ "symfony/twig-bridge": "<6.2.1" }, "require-dev": { - "symfony/console": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/messenger": "^6.2", - "symfony/twig-bridge": "^6.2" + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/messenger": "^6.2|^7.0", + "symfony/twig-bridge": "^6.2|^7.0" }, "type": "library", "autoload": { @@ -8597,7 +9027,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.3.0" + "source": "https://github.com/symfony/mailer/tree/v6.4.0" }, "funding": [ { @@ -8613,32 +9043,32 @@ "type": "tidelift" } ], - "time": "2023-05-29T12:49:39+00:00" + "time": "2023-11-12T18:02:22+00:00" }, { "name": "symfony/mailgun-mailer", - "version": "v6.3.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/mailgun-mailer.git", - "reference": "2fafefe8683a93155aceb6cca622c7cee2e27174" + "reference": "72d2f72f2016e559d0152188bef5a5dc9ebf5ec7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/2fafefe8683a93155aceb6cca622c7cee2e27174", - "reference": "2fafefe8683a93155aceb6cca622c7cee2e27174", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/72d2f72f2016e559d0152188bef5a5dc9ebf5ec7", + "reference": "72d2f72f2016e559d0152188bef5a5dc9ebf5ec7", "shasum": "" }, "require": { "php": ">=8.1", - "symfony/mailer": "^5.4.21|^6.2.7" + "symfony/mailer": "^5.4.21|^6.2.7|^7.0" }, "conflict": { "symfony/http-foundation": "<6.2" }, "require-dev": { - "symfony/http-client": "^5.4|^6.0", - "symfony/webhook": "^6.3" + "symfony/http-client": "^6.3|^7.0", + "symfony/webhook": "^6.3|^7.0" }, "type": "symfony-mailer-bridge", "autoload": { @@ -8666,7 +9096,7 @@ "description": "Symfony Mailgun Mailer Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailgun-mailer/tree/v6.3.0" + "source": "https://github.com/symfony/mailgun-mailer/tree/v6.4.0" }, "funding": [ { @@ -8682,24 +9112,25 @@ "type": "tidelift" } ], - "time": "2023-05-02T16:15:19+00:00" + "time": "2023-11-06T17:20:05+00:00" }, { "name": "symfony/mime", - "version": "v6.3.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad" + "reference": "ca4f58b2ef4baa8f6cecbeca2573f88cd577d205" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/7b5d2121858cd6efbed778abce9cfdd7ab1f62ad", - "reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad", + "url": "https://api.github.com/repos/symfony/mime/zipball/ca4f58b2ef4baa8f6cecbeca2573f88cd577d205", + "reference": "ca4f58b2ef4baa8f6cecbeca2573f88cd577d205", "shasum": "" }, "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, @@ -8708,16 +9139,16 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/mailer": "<5.4", - "symfony/serializer": "<6.2" + "symfony/serializer": "<6.3.2" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.4|^6.0", - "symfony/serializer": "^6.2" + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.3.2|^7.0" }, "type": "library", "autoload": { @@ -8749,7 +9180,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.3.0" + "source": "https://github.com/symfony/mime/tree/v6.4.0" }, "funding": [ { @@ -8765,20 +9196,20 @@ "type": "tidelift" } ], - "time": "2023-04-28T15:57:00+00:00" + "time": "2023-10-17T11:49:05+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", "shasum": "" }, "require": { @@ -8793,7 +9224,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -8831,7 +9262,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" }, "funding": [ { @@ -8847,20 +9278,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "reference": "875e90aeea2777b6f135677f618529449334a612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", + "reference": "875e90aeea2777b6f135677f618529449334a612", "shasum": "" }, "require": { @@ -8872,7 +9303,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -8912,7 +9343,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" }, "funding": [ { @@ -8928,20 +9359,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da" + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d", + "reference": "ecaafce9f77234a6a449d29e49267ba10499116d", "shasum": "" }, "require": { @@ -8955,7 +9386,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -8999,7 +9430,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0" }, "funding": [ { @@ -9015,20 +9446,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:30:37+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", "shasum": "" }, "require": { @@ -9040,7 +9471,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -9083,7 +9514,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" }, "funding": [ { @@ -9099,20 +9530,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "42292d99c55abe617799667f454222c54c60e229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", "shasum": "" }, "require": { @@ -9127,7 +9558,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -9166,7 +9597,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" }, "funding": [ { @@ -9182,20 +9613,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-07-28T09:04:16+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179", + "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179", "shasum": "" }, "require": { @@ -9204,7 +9635,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -9242,7 +9673,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0" }, "funding": [ { @@ -9258,20 +9689,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", "shasum": "" }, "require": { @@ -9280,7 +9711,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -9325,7 +9756,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" }, "funding": [ { @@ -9341,20 +9772,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "508c652ba3ccf69f8c97f251534f229791b52a57" + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/508c652ba3ccf69f8c97f251534f229791b52a57", - "reference": "508c652ba3ccf69f8c97f251534f229791b52a57", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", "shasum": "" }, "require": { @@ -9364,7 +9795,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -9377,7 +9808,10 @@ ], "psr-4": { "Symfony\\Polyfill\\Php83\\": "" - } + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -9402,7 +9836,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.28.0" }, "funding": [ { @@ -9418,20 +9852,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-08-16T06:22:46+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166" + "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/f3cf1a645c2734236ed1e2e671e273eeb3586166", - "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/9c44518a5aff8da565c8a55dbe85d2769e6f630e", + "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e", "shasum": "" }, "require": { @@ -9446,7 +9880,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -9484,7 +9918,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.28.0" }, "funding": [ { @@ -9500,20 +9934,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-01-26T09:26:14+00:00" }, { "name": "symfony/process", - "version": "v6.3.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628" + "reference": "191703b1566d97a5425dc969e4350d32b8ef17aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/8741e3ed7fe2e91ec099e02446fb86667a0f1628", - "reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628", + "url": "https://api.github.com/repos/symfony/process/zipball/191703b1566d97a5425dc969e4350d32b8ef17aa", + "reference": "191703b1566d97a5425dc969e4350d32b8ef17aa", "shasum": "" }, "require": { @@ -9545,7 +9979,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.3.0" + "source": "https://github.com/symfony/process/tree/v6.4.0" }, "funding": [ { @@ -9561,25 +9995,26 @@ "type": "tidelift" } ], - "time": "2023-05-19T08:06:44+00:00" + "time": "2023-11-17T21:06:49+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v2.2.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "28a732c05bbad801304ad5a5c674cf2970508993" + "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/28a732c05bbad801304ad5a5c674cf2970508993", - "reference": "28a732c05bbad801304ad5a5c674cf2970508993", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/581ca6067eb62640de5ff08ee1ba6850a0ee472e", + "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e", "shasum": "" }, "require": { "php": ">=7.2.5", "psr/http-message": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", "symfony/http-foundation": "^5.4 || ^6.0" }, "require-dev": { @@ -9598,7 +10033,7 @@ "type": "symfony-bridge", "extra": { "branch-alias": { - "dev-main": "2.2-dev" + "dev-main": "2.3-dev" } }, "autoload": { @@ -9633,7 +10068,7 @@ ], "support": { "issues": "https://github.com/symfony/psr-http-message-bridge/issues", - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.2.0" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.3.1" }, "funding": [ { @@ -9649,24 +10084,25 @@ "type": "tidelift" } ], - "time": "2023-04-21T08:40:19+00:00" + "time": "2023-07-26T11:53:26+00:00" }, { "name": "symfony/routing", - "version": "v6.3.1", + "version": "v6.4.1", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "d37ad1779c38b8eb71996d17dc13030dcb7f9cf5" + "reference": "0c95c164fdba18b12523b75e64199ca3503e6d40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/d37ad1779c38b8eb71996d17dc13030dcb7f9cf5", - "reference": "d37ad1779c38b8eb71996d17dc13030dcb7f9cf5", + "url": "https://api.github.com/repos/symfony/routing/zipball/0c95c164fdba18b12523b75e64199ca3503e6d40", + "reference": "0c95c164fdba18b12523b75e64199ca3503e6d40", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "doctrine/annotations": "<1.12", @@ -9677,11 +10113,11 @@ "require-dev": { "doctrine/annotations": "^1.12|^2", "psr/log": "^1|^2|^3", - "symfony/config": "^6.2", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0" + "symfony/config": "^6.2|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -9715,7 +10151,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.3.1" + "source": "https://github.com/symfony/routing/tree/v6.4.1" }, "funding": [ { @@ -9731,20 +10167,20 @@ "type": "tidelift" } ], - "time": "2023-06-05T15:30:22+00:00" + "time": "2023-12-01T14:54:37+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" + "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b3313c2dbffaf71c8de2934e2ea56ed2291a3838", + "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838", "shasum": "" }, "require": { @@ -9797,7 +10233,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.4.0" }, "funding": [ { @@ -9813,20 +10249,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2023-07-30T20:28:31+00:00" }, { "name": "symfony/string", - "version": "v6.3.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f" + "reference": "b45fcf399ea9c3af543a92edf7172ba21174d809" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f2e190ee75ff0f5eced645ec0be5c66fac81f51f", - "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f", + "url": "https://api.github.com/repos/symfony/string/zipball/b45fcf399ea9c3af543a92edf7172ba21174d809", + "reference": "b45fcf399ea9c3af543a92edf7172ba21174d809", "shasum": "" }, "require": { @@ -9840,11 +10276,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/intl": "^6.2", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0" + "symfony/var-exporter": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -9883,7 +10319,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.3.0" + "source": "https://github.com/symfony/string/tree/v6.4.0" }, "funding": [ { @@ -9899,24 +10335,25 @@ "type": "tidelift" } ], - "time": "2023-03-21T21:06:29+00:00" + "time": "2023-11-28T20:41:49+00:00" }, { "name": "symfony/translation", - "version": "v6.3.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "f72b2cba8f79dd9d536f534f76874b58ad37876f" + "reference": "b1035dbc2a344b21f8fa8ac451c7ecec4ea45f37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/f72b2cba8f79dd9d536f534f76874b58ad37876f", - "reference": "f72b2cba8f79dd9d536f534f76874b58ad37876f", + "url": "https://api.github.com/repos/symfony/translation/zipball/b1035dbc2a344b21f8fa8ac451c7ecec4ea45f37", + "reference": "b1035dbc2a344b21f8fa8ac451c7ecec4ea45f37", "shasum": "" }, "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/translation-contracts": "^2.5|^3.0" }, @@ -9936,17 +10373,17 @@ "require-dev": { "nikic/php-parser": "^4.13", "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/intl": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0" + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -9977,7 +10414,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.3.0" + "source": "https://github.com/symfony/translation/tree/v6.4.0" }, "funding": [ { @@ -9993,20 +10430,20 @@ "type": "tidelift" } ], - "time": "2023-05-19T12:46:45+00:00" + "time": "2023-11-29T08:14:36+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86" + "reference": "dee0c6e5b4c07ce851b462530088e64b255ac9c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/02c24deb352fb0d79db5486c0c79905a85e37e86", - "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/dee0c6e5b4c07ce851b462530088e64b255ac9c5", + "reference": "dee0c6e5b4c07ce851b462530088e64b255ac9c5", "shasum": "" }, "require": { @@ -10055,7 +10492,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.4.0" }, "funding": [ { @@ -10071,20 +10508,20 @@ "type": "tidelift" } ], - "time": "2023-05-30T17:17:10+00:00" + "time": "2023-07-25T15:08:44+00:00" }, { "name": "symfony/uid", - "version": "v6.3.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384" + "reference": "8092dd1b1a41372110d06374f99ee62f7f0b9a92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/01b0f20b1351d997711c56f1638f7a8c3061e384", - "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384", + "url": "https://api.github.com/repos/symfony/uid/zipball/8092dd1b1a41372110d06374f99ee62f7f0b9a92", + "reference": "8092dd1b1a41372110d06374f99ee62f7f0b9a92", "shasum": "" }, "require": { @@ -10092,7 +10529,7 @@ "symfony/polyfill-uuid": "^1.15" }, "require-dev": { - "symfony/console": "^5.4|^6.0" + "symfony/console": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -10129,7 +10566,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.3.0" + "source": "https://github.com/symfony/uid/tree/v6.4.0" }, "funding": [ { @@ -10145,24 +10582,25 @@ "type": "tidelift" } ], - "time": "2023-04-08T07:25:02+00:00" + "time": "2023-10-31T08:18:17+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.3.1", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "c81268d6960ddb47af17391a27d222bd58cf0515" + "reference": "c40f7d17e91d8b407582ed51a2bbf83c52c367f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c81268d6960ddb47af17391a27d222bd58cf0515", - "reference": "c81268d6960ddb47af17391a27d222bd58cf0515", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c40f7d17e91d8b407582ed51a2bbf83c52c367f6", + "reference": "c40f7d17e91d8b407582ed51a2bbf83c52c367f6", "shasum": "" }, "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { @@ -10170,9 +10608,11 @@ }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^6.3|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", "twig/twig": "^2.13|^3.0.4" }, "bin": [ @@ -10211,7 +10651,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.3.1" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.0" }, "funding": [ { @@ -10227,27 +10667,28 @@ "type": "tidelift" } ], - "time": "2023-06-21T12:08:28+00:00" + "time": "2023-11-09T08:28:32+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.3.0", + "version": "v6.4.1", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "db5416d04269f2827d8c54331ba4cfa42620d350" + "reference": "2d08ca6b9cc704dce525615d1e6d1788734f36d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/db5416d04269f2827d8c54331ba4cfa42620d350", - "reference": "db5416d04269f2827d8c54331ba4cfa42620d350", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/2d08ca6b9cc704dce525615d1e6d1788734f36d9", + "reference": "2d08ca6b9cc704dce525615d1e6d1788734f36d9", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/var-dumper": "^5.4|^6.0" + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -10285,7 +10726,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.3.0" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.1" }, "funding": [ { @@ -10301,7 +10742,7 @@ "type": "tidelift" } ], - "time": "2023-04-21T08:48:44+00:00" + "time": "2023-11-30T10:32:10+00:00" }, { "name": "tightenco/collect", @@ -10412,31 +10853,31 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.5.0", + "version": "v5.6.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7" + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", - "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.0.2", - "php": "^7.1.3 || ^8.0", - "phpoption/phpoption": "^1.8", - "symfony/polyfill-ctype": "^1.23", - "symfony/polyfill-mbstring": "^1.23.1", - "symfony/polyfill-php80": "^1.23.1" + "graham-campbell/result-type": "^1.1.2", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.2", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-filter": "*", - "phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25" + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" }, "suggest": { "ext-filter": "Required to use the boolean validator." @@ -10448,7 +10889,7 @@ "forward-command": true }, "branch-alias": { - "dev-master": "5.5-dev" + "dev-master": "5.6-dev" } }, "autoload": { @@ -10480,7 +10921,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0" }, "funding": [ { @@ -10492,7 +10933,7 @@ "type": "tidelift" } ], - "time": "2022-10-16T01:01:54+00:00" + "time": "2023-11-12T22:43:29+00:00" }, { "name": "voku/portable-ascii", @@ -10568,6 +11009,380 @@ ], "time": "2022-03-08T17:03:00+00:00" }, + { + "name": "web-token/jwt-core", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/web-token/jwt-core.git", + "reference": "4d956e786a4e35d54c74787ebff840a0311c5e83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-token/jwt-core/zipball/4d956e786a4e35d54c74787ebff840a0311c5e83", + "reference": "4d956e786a4e35d54c74787ebff840a0311c5e83", + "shasum": "" + }, + "require": { + "brick/math": "^0.9|^0.10", + "ext-json": "*", + "ext-mbstring": "*", + "fgrosse/phpasn1": "^2.0", + "paragonie/constant_time_encoding": "^2.4", + "php": ">=8.1" + }, + "conflict": { + "spomky-labs/jose": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Jose\\Component\\Core\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-framework/contributors" + } + ], + "description": "Core component of the JWT Framework.", + "homepage": "https://github.com/web-token", + "keywords": [ + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" + ], + "support": { + "source": "https://github.com/web-token/jwt-core/tree/3.1.2" + }, + "funding": [ + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2022-08-04T21:04:09+00:00" + }, + { + "name": "web-token/jwt-key-mgmt", + "version": "3.1.7", + "source": { + "type": "git", + "url": "https://github.com/web-token/jwt-key-mgmt.git", + "reference": "bf6dec304f2a718d70f7316e498c612317c59e08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-token/jwt-key-mgmt/zipball/bf6dec304f2a718d70f7316e498c612317c59e08", + "reference": "bf6dec304f2a718d70f7316e498c612317c59e08", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">=8.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "web-token/jwt-core": "^3.0" + }, + "suggest": { + "ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys", + "php-http/httplug": "To enable JKU/X5U support.", + "php-http/message-factory": "To enable JKU/X5U support.", + "web-token/jwt-util-ecc": "To use EC key analyzers." + }, + "type": "library", + "autoload": { + "psr-4": { + "Jose\\Component\\KeyManagement\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-key-mgmt/contributors" + } + ], + "description": "Key Management component of the JWT Framework.", + "homepage": "https://github.com/web-token", + "keywords": [ + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" + ], + "support": { + "source": "https://github.com/web-token/jwt-key-mgmt/tree/3.1.7" + }, + "funding": [ + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2023-02-02T17:25:26+00:00" + }, + { + "name": "web-token/jwt-signature", + "version": "3.1.7", + "source": { + "type": "git", + "url": "https://github.com/web-token/jwt-signature.git", + "reference": "14b71230d9632564e356b785366ad36880964190" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-token/jwt-signature/zipball/14b71230d9632564e356b785366ad36880964190", + "reference": "14b71230d9632564e356b785366ad36880964190", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "web-token/jwt-core": "^3.0" + }, + "suggest": { + "web-token/jwt-signature-algorithm-ecdsa": "ECDSA Based Signature Algorithms", + "web-token/jwt-signature-algorithm-eddsa": "EdDSA Based Signature Algorithms", + "web-token/jwt-signature-algorithm-experimental": "Experimental Signature Algorithms", + "web-token/jwt-signature-algorithm-hmac": "HMAC Based Signature Algorithms", + "web-token/jwt-signature-algorithm-none": "None Signature Algorithm", + "web-token/jwt-signature-algorithm-rsa": "RSA Based Signature Algorithms" + }, + "type": "library", + "autoload": { + "psr-4": { + "Jose\\Component\\Signature\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-signature/contributors" + } + ], + "description": "Signature component of the JWT Framework.", + "homepage": "https://github.com/web-token", + "keywords": [ + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" + ], + "support": { + "source": "https://github.com/web-token/jwt-signature/tree/3.1.7" + }, + "funding": [ + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2023-02-02T17:25:26+00:00" + }, + { + "name": "web-token/jwt-signature-algorithm-ecdsa", + "version": "3.1.7", + "source": { + "type": "git", + "url": "https://github.com/web-token/jwt-signature-algorithm-ecdsa.git", + "reference": "e09159600f19832cf4a68921e7299e564bc0eaf9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-token/jwt-signature-algorithm-ecdsa/zipball/e09159600f19832cf4a68921e7299e564bc0eaf9", + "reference": "e09159600f19832cf4a68921e7299e564bc0eaf9", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">=8.1", + "web-token/jwt-signature": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Jose\\Component\\Signature\\Algorithm\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-framework/contributors" + } + ], + "description": "ECDSA Based Signature Algorithms the JWT Framework.", + "homepage": "https://github.com/web-token", + "keywords": [ + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" + ], + "support": { + "source": "https://github.com/web-token/jwt-signature-algorithm-ecdsa/tree/3.1.7" + }, + "funding": [ + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2022-08-04T21:04:09+00:00" + }, + { + "name": "web-token/jwt-util-ecc", + "version": "3.2.8", + "source": { + "type": "git", + "url": "https://github.com/web-token/jwt-util-ecc.git", + "reference": "b2337052dbee724d710c1fdb0d3609835a5f8609" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-token/jwt-util-ecc/zipball/b2337052dbee724d710c1fdb0d3609835a5f8609", + "reference": "b2337052dbee724d710c1fdb0d3609835a5f8609", + "shasum": "" + }, + "require": { + "brick/math": "^0.9|^0.10|^0.11", + "php": ">=8.1" + }, + "suggest": { + "ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance", + "ext-gmp": "GMP or BCMath is highly recommended to improve the library performance" + }, + "type": "library", + "autoload": { + "psr-4": { + "Jose\\Component\\Core\\Util\\Ecc\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-token/jwt-framework/contributors" + } + ], + "description": "ECC Tools for the JWT Framework.", + "homepage": "https://github.com/web-token", + "keywords": [ + "JOSE", + "JWE", + "JWK", + "JWKSet", + "JWS", + "Jot", + "RFC7515", + "RFC7516", + "RFC7517", + "RFC7518", + "RFC7519", + "RFC7520", + "bundle", + "jwa", + "jwt", + "symfony" + ], + "support": { + "source": "https://github.com/web-token/jwt-util-ecc/tree/3.2.8" + }, + "funding": [ + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2023-02-02T13:35:41+00:00" + }, { "name": "webmozart/assert", "version": "1.11.0", @@ -10630,16 +11445,16 @@ "packages-dev": [ { "name": "brianium/paratest", - "version": "v6.10.0", + "version": "v6.11.0", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "c2243b20bcd99c3f651018d1447144372f39b4fa" + "reference": "8083a421cee7dad847ee7c464529043ba30de380" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/c2243b20bcd99c3f651018d1447144372f39b4fa", - "reference": "c2243b20bcd99c3f651018d1447144372f39b4fa", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/8083a421cee7dad847ee7c464529043ba30de380", + "reference": "8083a421cee7dad847ee7c464529043ba30de380", "shasum": "" }, "require": { @@ -10647,7 +11462,7 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-simplexml": "*", - "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "jean85/pretty-package-versions": "^2.0.5", "php": "^7.3 || ^8.0", "phpunit/php-code-coverage": "^9.2.25", @@ -10655,16 +11470,16 @@ "phpunit/php-timer": "^5.0.3", "phpunit/phpunit": "^9.6.4", "sebastian/environment": "^5.1.5", - "symfony/console": "^5.4.21 || ^6.2.7", - "symfony/process": "^5.4.21 || ^6.2.7" + "symfony/console": "^5.4.28 || ^6.3.4 || ^7.0.0", + "symfony/process": "^5.4.28 || ^6.3.4 || ^7.0.0" }, "require-dev": { - "doctrine/coding-standard": "^10.0.0", + "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "infection/infection": "^0.26.19", + "infection/infection": "^0.27.6", "squizlabs/php_codesniffer": "^3.7.2", - "symfony/filesystem": "^5.4.21 || ^6.2.7", + "symfony/filesystem": "^5.4.25 || ^6.3.1 || ^7.0.0", "vimeo/psalm": "^5.7.7" }, "bin": [ @@ -10706,7 +11521,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v6.10.0" + "source": "https://github.com/paratestphp/paratest/tree/v6.11.0" }, "funding": [ { @@ -10718,7 +11533,7 @@ "type": "paypal" } ], - "time": "2023-05-25T13:47:58+00:00" + "time": "2023-10-31T09:13:57+00:00" }, { "name": "doctrine/instantiator", @@ -10860,16 +11675,16 @@ }, { "name": "fidry/cpu-core-counter", - "version": "0.5.1", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623" + "reference": "85193c0b0cb5c47894b5eaec906e946f054e7077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/b58e5a3933e541dc286cc91fc4f3898bbc6f1623", - "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/85193c0b0cb5c47894b5eaec906e946f054e7077", + "reference": "85193c0b0cb5c47894b5eaec906e946f054e7077", "shasum": "" }, "require": { @@ -10877,13 +11692,13 @@ }, "require-dev": { "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", "phpstan/extension-installer": "^1.2.0", "phpstan/phpstan": "^1.9.2", "phpstan/phpstan-deprecation-rules": "^1.0.0", "phpstan/phpstan-phpunit": "^1.2.2", "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^9.5.26 || ^8.5.31", - "theofidry/php-cs-fixer-config": "^1.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, "type": "library", @@ -10909,7 +11724,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/0.5.1" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.0.0" }, "funding": [ { @@ -10917,20 +11732,20 @@ "type": "github" } ], - "time": "2022-12-24T12:35:10+00:00" + "time": "2023-09-17T21:38:23+00:00" }, { "name": "filp/whoops", - "version": "2.15.3", + "version": "2.15.4", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "c83e88a30524f9360b11f585f71e6b17313b7187" + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/c83e88a30524f9360b11f585f71e6b17313b7187", - "reference": "c83e88a30524f9360b11f585f71e6b17313b7187", + "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546", + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546", "shasum": "" }, "require": { @@ -10980,7 +11795,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.15.3" + "source": "https://github.com/filp/whoops/tree/2.15.4" }, "funding": [ { @@ -10988,7 +11803,7 @@ "type": "github" } ], - "time": "2023-07-13T12:00:00+00:00" + "time": "2023-11-03T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -11102,16 +11917,16 @@ }, { "name": "laravel/telescope", - "version": "v4.15.2", + "version": "v4.17.2", "source": { "type": "git", "url": "https://github.com/laravel/telescope.git", - "reference": "5d74ae4c9f269b756d7877ad1527770c59846e14" + "reference": "64da53ee46b99ef328458eaed32202b51e325a11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/telescope/zipball/5d74ae4c9f269b756d7877ad1527770c59846e14", - "reference": "5d74ae4c9f269b756d7877ad1527770c59846e14", + "url": "https://api.github.com/repos/laravel/telescope/zipball/64da53ee46b99ef328458eaed32202b51e325a11", + "reference": "64da53ee46b99ef328458eaed32202b51e325a11", "shasum": "" }, "require": { @@ -11167,43 +11982,39 @@ ], "support": { "issues": "https://github.com/laravel/telescope/issues", - "source": "https://github.com/laravel/telescope/tree/v4.15.2" + "source": "https://github.com/laravel/telescope/tree/v4.17.2" }, - "time": "2023-07-13T20:06:27+00:00" + "time": "2023-11-01T14:01:06+00:00" }, { "name": "mockery/mockery", - "version": "1.6.2", + "version": "1.6.6", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "13a7fa2642c76c58fa2806ef7f565344c817a191" + "reference": "b8e0bb7d8c604046539c1115994632c74dcb361e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/13a7fa2642c76c58fa2806ef7f565344c817a191", - "reference": "13a7fa2642c76c58fa2806ef7f565344c817a191", + "url": "https://api.github.com/repos/mockery/mockery/zipball/b8e0bb7d8c604046539c1115994632c74dcb361e", + "reference": "b8e0bb7d8c604046539c1115994632c74dcb361e", "shasum": "" }, "require": { "hamcrest/hamcrest-php": "^2.0.1", "lib-pcre": ">=7.0", - "php": "^7.4 || ^8.0" + "php": ">=7.3" }, "conflict": { "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.3", - "psalm/plugin-phpunit": "^0.18", - "vimeo/psalm": "^5.9" + "phpunit/phpunit": "^8.5 || ^9.6.10", + "psalm/plugin-phpunit": "^0.18.4", + "symplify/easy-coding-standard": "^11.5.0", + "vimeo/psalm": "^4.30" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.6.x-dev" - } - }, "autoload": { "files": [ "library/helpers.php", @@ -11221,12 +12032,20 @@ { "name": "Pádraic Brady", "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" + "homepage": "https://github.com/padraic", + "role": "Author" }, { "name": "Dave Marshall", "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" } ], "description": "Mockery is a simple yet flexible PHP mock object framework", @@ -11244,10 +12063,13 @@ "testing" ], "support": { + "docs": "https://docs.mockery.io/", "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.6.2" + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" }, - "time": "2023-06-07T09:07:52+00:00" + "time": "2023-08-09T00:03:52+00:00" }, { "name": "myclabs/deep-copy", @@ -11509,16 +12331,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.26", + "version": "9.2.29", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", "shasum": "" }, "require": { @@ -11574,7 +12396,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" }, "funding": [ { @@ -11582,7 +12405,7 @@ "type": "github" } ], - "time": "2023-03-06T12:58:08+00:00" + "time": "2023-09-19T04:57:46+00:00" }, { "name": "phpunit/php-file-iterator", @@ -11827,16 +12650,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.10", + "version": "9.6.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328" + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a6d351645c3fe5a30f5e86be6577d946af65a328", - "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1", + "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1", "shasum": "" }, "require": { @@ -11851,7 +12674,7 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-code-coverage": "^9.2.28", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -11910,7 +12733,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.10" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15" }, "funding": [ { @@ -11926,7 +12749,7 @@ "type": "tidelift" } ], - "time": "2023-07-10T04:04:23+00:00" + "time": "2023-12-01T16:55:19+00:00" }, { "name": "sebastian/cli-parser", @@ -12434,16 +13257,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "5.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "bde739e7565280bda77be70044ac1047bc007e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", "shasum": "" }, "require": { @@ -12486,7 +13309,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" }, "funding": [ { @@ -12494,7 +13317,7 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2023-08-02T09:26:13+00:00" }, { "name": "sebastian/lines-of-code", @@ -12894,16 +13717,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -12932,7 +13755,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -12940,7 +13763,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" } ], "aliases": [], @@ -12959,5 +13782,5 @@ "ext-openssl": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/config/cache.php b/config/cache.php index b2a854623..452e3dd4d 100644 --- a/config/cache.php +++ b/config/cache.php @@ -36,17 +36,20 @@ return [ 'array' => [ 'driver' => 'array', + 'serialize' => false, ], 'database' => [ 'driver' => 'database', 'table' => 'cache', 'connection' => null, + 'lock_connection' => null, ], 'file' => [ 'driver' => 'file', 'path' => storage_path('framework/cache/data'), + 'lock_path' => storage_path('framework/cache/data'), ], 'memcached' => [ @@ -70,6 +73,7 @@ return [ 'redis' => [ 'driver' => 'redis', + 'lock_connection' => 'default', 'client' => env('REDIS_CLIENT', 'phpredis'), 'default' => [ @@ -83,6 +87,25 @@ return [ ], + 'redis:session' => [ + 'driver' => 'redis', + 'connection' => 'default', + 'prefix' => 'pf_session', + ], + + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + ], + + 'octane' => [ + 'driver' => 'octane', + ], + ], /* @@ -101,4 +124,5 @@ return [ str_slug(env('APP_NAME', 'laravel'), '_').'_cache' ), + 'limiter' => env('CACHE_LIMITER_DRIVER', 'redis'), ]; diff --git a/config/exp.php b/config/exp.php index 0ace5135b..e14463411 100644 --- a/config/exp.php +++ b/config/exp.php @@ -41,4 +41,6 @@ return [ // Post Update/Edits 'pue' => env('EXP_PUE', true), + + 'autolink' => env('EXP_AUTOLINK_V2', false), ]; diff --git a/config/filesystems.php b/config/filesystems.php index d5247c980..00254e938 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -79,6 +79,34 @@ return [ 'throw' => true, ], + 'alt-primary' => [ + 'enabled' => env('ALT_PRI_ENABLED', false), + 'driver' => 's3', + 'key' => env('ALT_PRI_AWS_ACCESS_KEY_ID'), + 'secret' => env('ALT_PRI_AWS_SECRET_ACCESS_KEY'), + 'region' => env('ALT_PRI_AWS_DEFAULT_REGION'), + 'bucket' => env('ALT_PRI_AWS_BUCKET'), + 'visibility' => 'public', + 'url' => env('ALT_PRI_AWS_URL'), + 'endpoint' => env('ALT_PRI_AWS_ENDPOINT'), + 'use_path_style_endpoint' => env('ALT_PRI_AWS_USE_PATH_STYLE_ENDPOINT', false), + 'throw' => true, + ], + + 'alt-secondary' => [ + 'enabled' => env('ALT_SEC_ENABLED', false), + 'driver' => 's3', + 'key' => env('ALT_SEC_AWS_ACCESS_KEY_ID'), + 'secret' => env('ALT_SEC_AWS_SECRET_ACCESS_KEY'), + 'region' => env('ALT_SEC_AWS_DEFAULT_REGION'), + 'bucket' => env('ALT_SEC_AWS_BUCKET'), + 'visibility' => 'public', + 'url' => env('ALT_SEC_AWS_URL'), + 'endpoint' => env('ALT_SEC_AWS_ENDPOINT'), + 'use_path_style_endpoint' => env('ALT_SEC_AWS_USE_PATH_STYLE_ENDPOINT', false), + 'throw' => true, + ], + 'spaces' => [ 'driver' => 's3', 'key' => env('DO_SPACES_KEY'), diff --git a/config/instance.php b/config/instance.php index 5161ecb80..6357afe63 100644 --- a/config/instance.php +++ b/config/instance.php @@ -110,7 +110,8 @@ return [ 'user_filters' => [ 'max_user_blocks' => env('PF_MAX_USER_BLOCKS', 50), - 'max_user_mutes' => env('PF_MAX_USER_MUTES', 50) + 'max_user_mutes' => env('PF_MAX_USER_MUTES', 50), + 'max_domain_blocks' => env('PF_MAX_DOMAIN_BLOCKS', 50), ], 'reports' => [ diff --git a/config/laravel-ffmpeg.php b/config/laravel-ffmpeg.php index 44cd1b6eb..3133ca723 100644 --- a/config/laravel-ffmpeg.php +++ b/config/laravel-ffmpeg.php @@ -3,8 +3,7 @@ return [ 'ffmpeg' => [ 'binaries' => env('FFMPEG_BINARIES', 'ffmpeg'), - - 'threads' => 12, // set to false to disable the default 'threads' filter + 'threads' => env('FFMPEG_THREADS', false), ], 'ffprobe' => [ @@ -18,4 +17,6 @@ return [ 'temporary_files_root' => env('FFMPEG_TEMPORARY_FILES_ROOT', sys_get_temp_dir()), 'temporary_files_encrypted_hls' => env('FFMPEG_TEMPORARY_ENCRYPTED_HLS', env('FFMPEG_TEMPORARY_FILES_ROOT', sys_get_temp_dir())), + + 'min_hls_version' => env('FFMPEG_MIN_HLS_VERSION', '4.3.0'), ]; diff --git a/config/mail.php b/config/mail.php index 4baa90ec8..1cfb51767 100644 --- a/config/mail.php +++ b/config/mail.php @@ -4,45 +4,89 @@ return [ /* |-------------------------------------------------------------------------- - | Mail Driver + | Default Mailer |-------------------------------------------------------------------------- | - | Laravel supports both SMTP and PHP's "mail" function as drivers for the - | sending of e-mail. You may specify which one you're using throughout - | your application here. By default, Laravel is setup for SMTP mail. - | - | Supported: "smtp", "sendmail", "mailgun", "mandrill", "ses", - | "sparkpost", "log", "array" + | This option controls the default mailer that is used to send any email + | messages sent by your application. Alternative mailers may be setup + | and used as needed; however, this mailer will be used by default. | */ - 'driver' => env('MAIL_DRIVER', 'smtp'), + 'default' => env('MAIL_DRIVER', 'smtp'), /* |-------------------------------------------------------------------------- - | SMTP Host Address + | Mailer Configurations |-------------------------------------------------------------------------- | - | Here you may provide the host address of the SMTP server used by your - | applications. A default option is provided that is compatible with - | the Mailgun mail service which will provide reliable deliveries. + | Here you may configure all of the mailers used by your application plus + | their respective settings. Several examples have been configured for + | you and you are free to add your own as your application requires. + | + | Laravel supports a variety of mail "transport" drivers to be used while + | sending an e-mail. You will specify which one you are using for your + | mailers below. You are free to add additional mailers as required. + | + | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", + | "postmark", "log", "array", "failover" | */ - 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), + 'mailers' => [ + 'smtp' => [ + 'transport' => 'smtp', + 'url' => env('MAIL_URL'), + 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), + 'port' => env('MAIL_PORT', 587), + 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN'), + 'verify_peer' => env('MAIL_SMTP_VERIFY_PEER', true), + ], - /* - |-------------------------------------------------------------------------- - | SMTP Host Port - |-------------------------------------------------------------------------- - | - | This is the SMTP port used by your application to deliver e-mails to - | users of the application. Like the host we have set this value to - | stay compatible with the Mailgun e-mail application by default. - | - */ + 'ses' => [ + 'transport' => 'ses', + ], - 'port' => env('MAIL_PORT', 587), + 'mailgun' => [ + 'transport' => 'mailgun', + // 'client' => [ + // 'timeout' => 5, + // ], + ], + + 'postmark' => [ + 'transport' => 'postmark', + // 'client' => [ + // 'timeout' => 5, + // ], + ], + + 'sendmail' => [ + 'transport' => 'sendmail', + 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), + ], + + 'log' => [ + 'transport' => 'log', + 'channel' => env('MAIL_LOG_CHANNEL'), + ], + + 'array' => [ + 'transport' => 'array', + ], + + 'failover' => [ + 'transport' => 'failover', + 'mailers' => [ + 'smtp', + 'log', + ], + ], + ], /* |-------------------------------------------------------------------------- @@ -57,63 +101,9 @@ return [ 'from' => [ 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), - 'name' => env('MAIL_FROM_NAME', 'Example'), + 'name' => env('MAIL_FROM_NAME', 'Example'), ], - /* - |-------------------------------------------------------------------------- - | E-Mail Encryption Protocol - |-------------------------------------------------------------------------- - | - | Here you may specify the encryption protocol that should be used when - | the application send e-mail messages. A sensible default using the - | transport layer security protocol should provide great security. - | - */ - - 'encryption' => env('MAIL_ENCRYPTION', 'tls'), - - /* - |-------------------------------------------------------------------------- - | SMTP Server Username - |-------------------------------------------------------------------------- - | - | If your SMTP server requires a username for authentication, you should - | set it here. This will get used to authenticate with your server on - | connection. You may also set the "password" value below this one. - | - */ - - 'username' => env('MAIL_USERNAME'), - 'password' => env('MAIL_PASSWORD'), - - - /* - |-------------------------------------------------------------------------- - | SMTP EHLO Domain - |-------------------------------------------------------------------------- - | - | Some SMTP servers require to present a known domain in order to - | allow sending through its relay. (ie: Google Workspace) - | This will use the MAIL_SMTP_EHLO env variable to avoid the 421 error - | if not present by authenticating the sender domain instead the host. - | - */ - 'local_domain' => env('MAIL_EHLO_DOMAIN'), - - /* - |-------------------------------------------------------------------------- - | Sendmail System Path - |-------------------------------------------------------------------------- - | - | When using the "sendmail" driver to send e-mails, we will need to know - | the path to where Sendmail lives on this server. A default path has - | been provided here, which will work well on most of your systems. - | - */ - - 'sendmail' => '/usr/sbin/sendmail -bs', - /* |-------------------------------------------------------------------------- | Markdown Mail Settings diff --git a/config/media.php b/config/media.php index b7d6e95cc..46c1719db 100644 --- a/config/media.php +++ b/config/media.php @@ -1,24 +1,60 @@ env('MEDIA_DELETE_LOCAL_AFTER_CLOUD', true), + 'delete_local_after_cloud' => env('MEDIA_DELETE_LOCAL_AFTER_CLOUD', true), - 'exif' => [ - 'database' => env('MEDIA_EXIF_DATABASE', false), - ], + 'exif' => [ + 'database' => env('MEDIA_EXIF_DATABASE', false), + ], - 'storage' => [ - 'remote' => [ - /* - |-------------------------------------------------------------------------- - | Store remote media on cloud/S3 - |-------------------------------------------------------------------------- - | - | Set this to cache remote media on cloud/S3 filesystem drivers. - | Disabled by default. - | - */ - 'cloud' => env('MEDIA_REMOTE_STORE_CLOUD', false) - ], - ] + 'storage' => [ + 'remote' => [ + /* + |-------------------------------------------------------------------------- + | Store remote media on cloud/S3 + |-------------------------------------------------------------------------- + | + | Set this to cache remote media on cloud/S3 filesystem drivers. + | Disabled by default. + | + */ + 'cloud' => env('MEDIA_REMOTE_STORE_CLOUD', false), + + 'resilient_mode' => env('ALT_PRI_ENABLED', false) || env('ALT_SEC_ENABLED', false), + ], + ], + + 'hls' => [ + /* + |-------------------------------------------------------------------------- + | Enable HLS + |-------------------------------------------------------------------------- + | + | Enable optional HLS support, required for video p2p support. Requires FFMPEG + | Disabled by default. + | + */ + 'enabled' => env('MEDIA_HLS_ENABLED', false), + + 'debug' => env('MEDIA_HLS_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Enable Video P2P support + |-------------------------------------------------------------------------- + | + | Enable optional video p2p support. Requires FFMPEG + HLS + | Disabled by default. + | + */ + 'p2p' => env('MEDIA_HLS_P2P', false), + + 'p2p_debug' => env('MEDIA_HLS_P2P_DEBUG', false), + + 'bitrate' => env('MEDIA_HLS_BITRATE', 1000), + + 'tracker' => env('MEDIA_HLS_P2P_TRACKER', 'wss://tracker.webtorrent.dev'), + + 'ice' => env('MEDIA_HLS_P2P_ICE_SERVER', 'stun:stun.l.google.com:19302'), + ] ]; diff --git a/config/pixelfed.php b/config/pixelfed.php index fcdb1a4b7..fc7da598a 100644 --- a/config/pixelfed.php +++ b/config/pixelfed.php @@ -286,4 +286,9 @@ return [ 'max_altext_length' => env('PF_MEDIA_MAX_ALTTEXT_LENGTH', 1000), 'allow_app_registration' => env('PF_ALLOW_APP_REGISTRATION', true), + + 'app_registration_rate_limit_attempts' => env('PF_IAR_RL_ATTEMPTS', 3), + 'app_registration_rate_limit_decay' => env('PF_IAR_RL_DECAY', 1800), + 'app_registration_confirm_rate_limit_attempts' => env('PF_IARC_RL_ATTEMPTS', 20), + 'app_registration_confirm_rate_limit_decay' => env('PF_IARC_RL_ATTEMPTS', 1800), ]; diff --git a/config/remote-auth.php b/config/remote-auth.php index 3f85b9d40..182bb99a7 100644 --- a/config/remote-auth.php +++ b/config/remote-auth.php @@ -3,6 +3,7 @@ return [ 'mastodon' => [ 'enabled' => env('PF_LOGIN_WITH_MASTODON_ENABLED', false), + 'ignore_closed_state' => env('PF_LOGIN_WITH_MASTODON_ENABLED_SKIP_CLOSED', false), 'contraints' => [ /* diff --git a/config/session.php b/config/session.php index 1b692e3a4..d3e982bd4 100644 --- a/config/session.php +++ b/config/session.php @@ -70,7 +70,7 @@ return [ | */ - 'connection' => null, + 'connection' => env('SESSION_CONNECTION'), /* |-------------------------------------------------------------------------- @@ -96,7 +96,7 @@ return [ | */ - 'store' => null, + 'store' => env('SESSION_STORE'), /* |-------------------------------------------------------------------------- @@ -109,7 +109,7 @@ return [ | */ - 'lottery' => [2, 1000], + 'lottery' => [2, 100], /* |-------------------------------------------------------------------------- @@ -161,7 +161,7 @@ return [ | */ - 'secure' => true, + 'secure' => env('SESSION_SECURE_COOKIE', true), /* |-------------------------------------------------------------------------- @@ -183,12 +183,25 @@ return [ | | This option determines how your cookies behave when cross-site requests | take place, and can be used to mitigate CSRF attacks. By default, we - | do not enable this as other CSRF protection services are in place. + | will set this value to "lax" since this is a secure default value. | - | Supported: "lax", "strict" + | Supported: "lax", "strict", "none", null | */ - 'same_site' => null, + 'same_site' => env('SESSION_SAME_SITE_COOKIES', 'lax'), + + /* + |-------------------------------------------------------------------------- + | Partitioned Cookies + |-------------------------------------------------------------------------- + | + | Setting this value to true will tie the cookie to the top-level site for + | a cross-site context. Partitioned cookies are accepted by the browser + | when flagged "secure" and the Same-Site attribute is set to "none". + | + */ + + 'partitioned' => false, ]; diff --git a/config/webpush.php b/config/webpush.php new file mode 100644 index 000000000..bef3e6653 --- /dev/null +++ b/config/webpush.php @@ -0,0 +1,48 @@ + [ + 'subject' => env('VAPID_SUBJECT'), + 'public_key' => env('VAPID_PUBLIC_KEY'), + 'private_key' => env('VAPID_PRIVATE_KEY'), + 'pem_file' => env('VAPID_PEM_FILE'), + ], + + /** + * This is model that will be used to for push subscriptions. + */ + 'model' => \NotificationChannels\WebPush\PushSubscription::class, + + /** + * This is the name of the table that will be created by the migration and + * used by the PushSubscription model shipped with this package. + */ + 'table_name' => env('WEBPUSH_DB_TABLE', 'push_subscriptions'), + + /** + * This is the database connection that will be used by the migration and + * the PushSubscription model shipped with this package. + */ + 'database_connection' => env('WEBPUSH_DB_CONNECTION', env('DB_CONNECTION', 'mysql')), + + /** + * The Guzzle client options used by Minishlink\WebPush. + */ + 'client_options' => [], + + /** + * Google Cloud Messaging. + * + * @deprecated + */ + 'gcm' => [ + 'key' => env('GCM_KEY'), + 'sender_id' => env('GCM_SENDER_ID'), + ], + +]; diff --git a/database/migrations/2021_08_04_095125_create_groups_table.php b/database/migrations/2021_08_04_095125_create_groups_table.php new file mode 100644 index 000000000..29c63f73e --- /dev/null +++ b/database/migrations/2021_08_04_095125_create_groups_table.php @@ -0,0 +1,42 @@ +bigInteger('id')->unsigned()->primary(); + $table->bigInteger('profile_id')->unsigned()->nullable()->index(); + $table->string('status')->nullable()->index(); + $table->string('name')->nullable(); + $table->text('description')->nullable(); + $table->text('rules')->nullable(); + $table->boolean('local')->default(true)->index(); + $table->string('remote_url')->nullable(); + $table->string('inbox_url')->nullable(); + $table->boolean('is_private')->default(false); + $table->boolean('local_only')->default(false); + $table->json('metadata')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('groups'); + } +} diff --git a/database/migrations/2021_08_04_095143_create_group_members_table.php b/database/migrations/2021_08_04_095143_create_group_members_table.php new file mode 100644 index 000000000..33df26229 --- /dev/null +++ b/database/migrations/2021_08_04_095143_create_group_members_table.php @@ -0,0 +1,40 @@ +id(); + $table->bigInteger('group_id')->unsigned()->index(); + $table->bigInteger('profile_id')->unsigned()->index(); + $table->string('role')->default('member')->index(); + $table->boolean('local_group')->default(false)->index(); + $table->boolean('local_profile')->default(false)->index(); + $table->boolean('join_request')->default(false)->index(); + $table->timestamp('approved_at')->nullable(); + $table->timestamp('rejected_at')->nullable(); + $table->unique(['group_id', 'profile_id']); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('group_members'); + } +} diff --git a/database/migrations/2021_08_04_095238_create_group_posts_table.php b/database/migrations/2021_08_04_095238_create_group_posts_table.php new file mode 100644 index 000000000..a5e637d8e --- /dev/null +++ b/database/migrations/2021_08_04_095238_create_group_posts_table.php @@ -0,0 +1,42 @@ +bigInteger('id')->unsigned()->primary(); + $table->bigInteger('group_id')->unsigned()->index(); + $table->bigInteger('profile_id')->unsigned()->nullable()->index(); + $table->string('type')->nullable()->index(); + $table->bigInteger('status_id')->unsigned()->unique(); + $table->string('remote_url')->unique()->nullable()->index(); + $table->bigInteger('reply_child_id')->unsigned()->nullable(); + $table->bigInteger('in_reply_to_id')->unsigned()->nullable(); + $table->bigInteger('reblog_of_id')->unsigned()->nullable(); + $table->unsignedInteger('reply_count')->nullable(); + $table->string('status')->nullable()->index(); + $table->json('metadata')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('group_posts'); + } +} diff --git a/database/migrations/2021_08_16_072457_create_group_invitations_table.php b/database/migrations/2021_08_16_072457_create_group_invitations_table.php new file mode 100644 index 000000000..aa13db23a --- /dev/null +++ b/database/migrations/2021_08_16_072457_create_group_invitations_table.php @@ -0,0 +1,38 @@ +bigIncrements('id'); + $table->bigInteger('group_id')->unsigned()->index(); + $table->bigInteger('from_profile_id')->unsigned()->index(); + $table->bigInteger('to_profile_id')->unsigned()->index(); + $table->string('role')->nullable(); + $table->boolean('to_local')->default(true)->index(); + $table->boolean('from_local')->default(true)->index(); + $table->unique(['group_id', 'to_profile_id']); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('group_invitations'); + } +} diff --git a/database/migrations/2023_08_25_050021_add_indexable_column_to_profiles_table.php b/database/migrations/2023_08_25_050021_add_indexable_column_to_profiles_table.php new file mode 100644 index 000000000..f735366bd --- /dev/null +++ b/database/migrations/2023_08_25_050021_add_indexable_column_to_profiles_table.php @@ -0,0 +1,28 @@ +boolean('indexable')->default(false)->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('profiles', function (Blueprint $table) { + $table->dropColumn('indexable'); + }); + } +}; diff --git a/database/migrations/2023_09_12_044900_create_admin_shadow_filters_table.php b/database/migrations/2023_09_12_044900_create_admin_shadow_filters_table.php new file mode 100644 index 000000000..6b62f32c2 --- /dev/null +++ b/database/migrations/2023_09_12_044900_create_admin_shadow_filters_table.php @@ -0,0 +1,47 @@ +id(); + $table->unsignedBigInteger('admin_id')->nullable(); + $table->morphs('item'); + $table->boolean('is_local')->default(true)->index(); + $table->text('note')->nullable(); + $table->boolean('active')->default(false)->index(); + $table->json('history')->nullable(); + $table->json('ruleset')->nullable(); + $table->boolean('prevent_ap_fanout')->default(false)->index(); + $table->boolean('prevent_new_dms')->default(false)->index(); + $table->boolean('ignore_reports')->default(false)->index(); + $table->boolean('ignore_mentions')->default(false)->index(); + $table->boolean('ignore_links')->default(false)->index(); + $table->boolean('ignore_hashtags')->default(false)->index(); + $table->boolean('hide_from_public_feeds')->default(false)->index(); + $table->boolean('hide_from_tag_feeds')->default(false)->index(); + $table->boolean('hide_embeds')->default(false)->index(); + $table->boolean('hide_from_story_carousel')->default(false)->index(); + $table->boolean('hide_from_search_autocomplete')->default(false)->index(); + $table->boolean('hide_from_search')->default(false)->index(); + $table->boolean('requires_login')->default(false)->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('admin_shadow_filters'); + } +}; diff --git a/database/migrations/2023_11_13_062429_add_followers_count_index_to_profiles_table.php b/database/migrations/2023_11_13_062429_add_followers_count_index_to_profiles_table.php new file mode 100644 index 000000000..bcc97577c --- /dev/null +++ b/database/migrations/2023_11_13_062429_add_followers_count_index_to_profiles_table.php @@ -0,0 +1,34 @@ +index('followers_count', 'profiles_followers_count_index'); + $table->index('following_count', 'profiles_following_count_index'); + $table->index('status_count', 'profiles_status_count_index'); + $table->index('is_private', 'profiles_is_private_index'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('profiles', function (Blueprint $table) { + $table->dropIndex('profiles_followers_count_index'); + $table->dropIndex('profiles_following_count_index'); + $table->dropIndex('profiles_status_count_index'); + $table->dropIndex('profiles_is_private_index'); + }); + } +}; diff --git a/database/migrations/2023_11_16_124107_create_hashtag_related_table.php b/database/migrations/2023_11_16_124107_create_hashtag_related_table.php new file mode 100644 index 000000000..33d7494d8 --- /dev/null +++ b/database/migrations/2023_11_16_124107_create_hashtag_related_table.php @@ -0,0 +1,33 @@ +bigIncrements('id'); + $table->bigInteger('hashtag_id')->unsigned()->unique()->index(); + $table->json('related_tags')->nullable(); + $table->bigInteger('agg_score')->unsigned()->nullable()->index(); + $table->timestamp('last_calculated_at')->nullable()->index(); + $table->timestamp('last_moderated_at')->nullable()->index(); + $table->boolean('skip_refresh')->default(false)->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('hashtag_related'); + } +}; diff --git a/database/migrations/2023_11_26_082439_add_state_and_score_to_places_table.php b/database/migrations/2023_11_26_082439_add_state_and_score_to_places_table.php new file mode 100644 index 000000000..d7f95aa3f --- /dev/null +++ b/database/migrations/2023_11_26_082439_add_state_and_score_to_places_table.php @@ -0,0 +1,34 @@ +string('state')->nullable()->index()->after('name'); + $table->tinyInteger('score')->default(0)->index()->after('long'); + $table->unsignedBigInteger('cached_post_count')->nullable(); + $table->timestamp('last_checked_at')->nullable()->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('places', function (Blueprint $table) { + $table->dropColumn('state'); + $table->dropColumn('score'); + $table->dropColumn('cached_post_count'); + $table->dropColumn('last_checked_at'); + }); + } +}; diff --git a/database/migrations/2023_12_04_041631_create_push_subscriptions_table.php b/database/migrations/2023_12_04_041631_create_push_subscriptions_table.php new file mode 100644 index 000000000..550a98f6a --- /dev/null +++ b/database/migrations/2023_12_04_041631_create_push_subscriptions_table.php @@ -0,0 +1,36 @@ +create(config('webpush.table_name'), function (Blueprint $table) { + $table->bigIncrements('id'); + $table->morphs('subscribable'); + $table->string('endpoint', 500)->unique(); + $table->string('public_key')->nullable(); + $table->string('auth_token')->nullable(); + $table->string('content_encoding')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::connection(config('webpush.database_connection'))->dropIfExists(config('webpush.table_name')); + } +} diff --git a/database/migrations/2023_12_08_074345_add_direct_object_urls_to_statuses_table.php b/database/migrations/2023_12_08_074345_add_direct_object_urls_to_statuses_table.php new file mode 100644 index 000000000..5211c658f --- /dev/null +++ b/database/migrations/2023_12_08_074345_add_direct_object_urls_to_statuses_table.php @@ -0,0 +1,33 @@ +whereNotNull('url')->whereNull('object_url')->lazyById(50, 'id') as $status) { + try { + $status->object_url = $status->url; + $status->uri = $status->url; + $status->save(); + } catch (Exception | UniqueConstraintViolationException $e) { + continue; + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + } +}; diff --git a/database/migrations/2023_12_13_060425_add_uploaded_to_s3_to_import_posts_table.php b/database/migrations/2023_12_13_060425_add_uploaded_to_s3_to_import_posts_table.php new file mode 100644 index 000000000..c7d5cdcbd --- /dev/null +++ b/database/migrations/2023_12_13_060425_add_uploaded_to_s3_to_import_posts_table.php @@ -0,0 +1,28 @@ +boolean('uploaded_to_s3')->default(false)->index()->after('skip_missing_media'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('import_posts', function (Blueprint $table) { + $table->dropColumn('uploaded_to_s3'); + }); + } +}; diff --git a/database/migrations/2023_12_16_052413_create_user_domain_blocks_table.php b/database/migrations/2023_12_16_052413_create_user_domain_blocks_table.php new file mode 100644 index 000000000..16f8f3fb2 --- /dev/null +++ b/database/migrations/2023_12_16_052413_create_user_domain_blocks_table.php @@ -0,0 +1,29 @@ +id(); + $table->unsignedBigInteger('profile_id')->index(); + $table->string('domain')->index(); + $table->unique(['profile_id', 'domain'], 'user_domain_blocks_by_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_domain_blocks'); + } +}; diff --git a/database/migrations/2023_12_19_081928_create_job_batches_table.php b/database/migrations/2023_12_19_081928_create_job_batches_table.php new file mode 100644 index 000000000..50e38c20f --- /dev/null +++ b/database/migrations/2023_12_19_081928_create_job_batches_table.php @@ -0,0 +1,35 @@ +string('id')->primary(); + $table->string('name'); + $table->integer('total_jobs'); + $table->integer('pending_jobs'); + $table->integer('failed_jobs'); + $table->longText('failed_job_ids'); + $table->mediumText('options')->nullable(); + $table->integer('cancelled_at')->nullable(); + $table->integer('created_at'); + $table->integer('finished_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('job_batches'); + } +}; diff --git a/database/migrations/2023_12_21_103223_purge_deleted_status_hashtags.php b/database/migrations/2023_12_21_103223_purge_deleted_status_hashtags.php new file mode 100644 index 000000000..bf2acc34e --- /dev/null +++ b/database/migrations/2023_12_21_103223_purge_deleted_status_hashtags.php @@ -0,0 +1,25 @@ +lazyById(200)->each->deleteQuietly(); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/database/migrations/2023_12_21_104103_create_default_domain_blocks_table.php b/database/migrations/2023_12_21_104103_create_default_domain_blocks_table.php new file mode 100644 index 000000000..72e61bda5 --- /dev/null +++ b/database/migrations/2023_12_21_104103_create_default_domain_blocks_table.php @@ -0,0 +1,29 @@ +id(); + $table->string('domain')->unique()->index(); + $table->text('note')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('default_domain_blocks'); + } +}; diff --git a/package-lock.json b/package-lock.json index 1dde4df84..15443b198 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,9 +7,12 @@ "name": "pixelfed", "dependencies": { "@fancyapps/fancybox": "^3.5.7", + "@hcaptcha/vue-hcaptcha": "^1.3.0", + "@peertube/p2p-media-loader-core": "^1.0.14", + "@peertube/p2p-media-loader-hlsjs": "^1.0.14", "@trevoreyre/autocomplete-vue": "^2.2.0", "@web3-storage/parse-link-header": "^3.1.0", - "@zip.js/zip.js": "^2.7.14", + "@zip.js/zip.js": "^2.7.24", "animate.css": "^4.1.0", "bigpicture": "^2.6.2", "blurhash": "^1.1.3", @@ -80,44 +83,101 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", - "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", - "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helpers": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -128,19 +188,19 @@ } }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dependencies": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -161,56 +221,53 @@ } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", - "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", - "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "browserslist": "^4.21.3", + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz", - "integrity": "sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz", + "integrity": "sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "semver": "^6.3.0" + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -220,21 +277,21 @@ } }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz", - "integrity": "sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "regexpu-core": "^5.3.1", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -244,52 +301,43 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz", - "integrity": "sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", + "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" + "resolve": "^1.14.2" }, "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -307,43 +355,43 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", - "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", - "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-optimise-call-expression": { @@ -366,14 +414,13 @@ } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz", - "integrity": "sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-wrap-function": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -383,19 +430,19 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz", - "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-simple-access": { @@ -421,9 +468,9 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dependencies": { "@babel/types": "^7.22.5" }, @@ -440,55 +487,54 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz", - "integrity": "sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", "dependencies": { "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", - "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -552,9 +598,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -563,9 +609,9 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", - "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz", + "integrity": "sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -577,13 +623,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", - "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz", + "integrity": "sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5" + "@babel/plugin-transform-optional-chaining": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -596,6 +642,7 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", "dependencies": { "@babel/compat-data": "^7.20.5", "@babel/helper-compilation-targets": "^7.20.7", @@ -621,21 +668,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -868,13 +900,13 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz", - "integrity": "sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.2.tgz", + "integrity": "sha512-BBYVGxbDVHfoeXbOwcagAkOQAm9NxoTdMGfTqghu1GrvadSaw6iW3Je6IcL5PNOw8VwjxqBECXy50/iCQSY/lQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", "@babel/plugin-syntax-async-generators": "^7.8.4" }, "engines": { @@ -915,9 +947,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", - "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", + "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -944,11 +976,11 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", - "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz", + "integrity": "sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.11", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, @@ -960,18 +992,18 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz", - "integrity": "sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz", + "integrity": "sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-split-export-declaration": "^7.22.6", "globals": "^11.1.0" }, "engines": { @@ -997,9 +1029,9 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", - "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", + "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -1040,9 +1072,9 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", - "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz", + "integrity": "sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3" @@ -1070,9 +1102,9 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", - "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz", + "integrity": "sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" @@ -1085,9 +1117,9 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", - "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz", + "integrity": "sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -1115,9 +1147,9 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", - "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz", + "integrity": "sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-json-strings": "^7.8.3" @@ -1144,9 +1176,9 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", - "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz", + "integrity": "sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" @@ -1173,11 +1205,11 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", - "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", + "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1188,11 +1220,11 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", - "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", + "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" }, @@ -1204,14 +1236,14 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", - "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", + "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -1265,9 +1297,9 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", - "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz", + "integrity": "sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" @@ -1280,9 +1312,9 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", - "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz", + "integrity": "sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-numeric-separator": "^7.10.4" @@ -1295,15 +1327,15 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", - "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz", + "integrity": "sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==", "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/compat-data": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.5" + "@babel/plugin-transform-parameters": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -1328,9 +1360,9 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", - "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz", + "integrity": "sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" @@ -1343,9 +1375,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz", - "integrity": "sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", + "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", @@ -1359,9 +1391,9 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", - "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz", + "integrity": "sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -1388,12 +1420,12 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", - "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz", + "integrity": "sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.11", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, @@ -1419,12 +1451,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", - "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", + "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.1" + "regenerator-transform": "^0.15.2" }, "engines": { "node": ">=6.9.0" @@ -1448,16 +1480,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.5.tgz", - "integrity": "sha512-bg4Wxd1FWeFx3daHFTWk1pkSWK/AyQuiyAoeZAOkAOUBjnZPH6KT7eMxouV47tQ6hl6ax2zyAWBdWZXbrvXlaw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.2.tgz", + "integrity": "sha512-XOntj6icgzMS58jPVtQpiuF6ZFWxQiJavISGx5KGjRj+3gqZr8+N6Kx+N9BApWzgS+DOjIZfXXj0ZesenOWDyA==", "dependencies": { - "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.3", - "babel-plugin-polyfill-corejs3": "^0.8.1", - "babel-plugin-polyfill-regenerator": "^0.5.0", - "semver": "^6.3.0" + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -1467,9 +1499,9 @@ } }, "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } @@ -1546,9 +1578,9 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", - "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", + "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" }, @@ -1605,16 +1637,16 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.5.tgz", - "integrity": "sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.2.tgz", + "integrity": "sha512-BW3gsuDD+rvHL2VO2SjAUNTBe5YrjsTiDyqamPDWY723na3/yPQ65X5oQkFVJZ0o50/2d+svm1rkPoJeR1KxVQ==", "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/compat-data": "^7.23.2", + "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.15", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.15", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", @@ -1635,60 +1667,60 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.23.2", "@babel/plugin-transform-async-to-generator": "^7.22.5", "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.23.0", "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.5", - "@babel/plugin-transform-classes": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.11", + "@babel/plugin-transform-classes": "^7.22.15", "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.23.0", "@babel/plugin-transform-dotall-regex": "^7.22.5", "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.11", "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.5", - "@babel/plugin-transform-for-of": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.11", + "@babel/plugin-transform-for-of": "^7.22.15", "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.11", "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.11", "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.5", - "@babel/plugin-transform-modules-systemjs": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.23.0", + "@babel/plugin-transform-modules-commonjs": "^7.23.0", + "@babel/plugin-transform-modules-systemjs": "^7.23.0", "@babel/plugin-transform-modules-umd": "^7.22.5", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", - "@babel/plugin-transform-numeric-separator": "^7.22.5", - "@babel/plugin-transform-object-rest-spread": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11", + "@babel/plugin-transform-numeric-separator": "^7.22.11", + "@babel/plugin-transform-object-rest-spread": "^7.22.15", "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5", - "@babel/plugin-transform-parameters": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.11", + "@babel/plugin-transform-optional-chaining": "^7.23.0", + "@babel/plugin-transform-parameters": "^7.22.15", "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.10", "@babel/plugin-transform-reserved-words": "^7.22.5", "@babel/plugin-transform-shorthand-properties": "^7.22.5", "@babel/plugin-transform-spread": "^7.22.5", "@babel/plugin-transform-sticky-regex": "^7.22.5", "@babel/plugin-transform-template-literals": "^7.22.5", "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.10", "@babel/plugin-transform-unicode-property-regex": "^7.22.5", "@babel/plugin-transform-unicode-regex": "^7.22.5", "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.3", - "babel-plugin-polyfill-corejs3": "^0.8.1", - "babel-plugin-polyfill-regenerator": "^0.5.0", - "core-js-compat": "^3.30.2", - "semver": "^6.3.0" + "@babel/preset-modules": "0.1.6-no-external-plugins", + "@babel/types": "^7.23.0", + "babel-plugin-polyfill-corejs2": "^0.4.6", + "babel-plugin-polyfill-corejs3": "^0.8.5", + "babel-plugin-polyfill-regenerator": "^0.5.3", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -1698,26 +1730,24 @@ } }, "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "node_modules/@babel/regjsgen": { @@ -1726,42 +1756,42 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1770,12 +1800,12 @@ } }, "node_modules/@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1807,6 +1837,14 @@ "jquery": ">=1.9.0" } }, + "node_modules/@hcaptcha/vue-hcaptcha": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@hcaptcha/vue-hcaptcha/-/vue-hcaptcha-1.3.0.tgz", + "integrity": "sha512-aUSWyhRucgFeBOBUC3nWBZuE0TkeoSH5QIVFwiTLnNsYpIaxD1tKBbI5Tdoy0TdpkuXKsB4KqyElbvoMZ9reGw==", + "peerDependencies": { + "vue": "^2.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -1821,9 +1859,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "engines": { "node": ">=6.0.0" } @@ -1837,9 +1875,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -1851,19 +1889,14 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" - }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -1918,6 +1951,29 @@ "npm": ">=5.0.0" } }, + "node_modules/@peertube/p2p-media-loader-core": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@peertube/p2p-media-loader-core/-/p2p-media-loader-core-1.0.14.tgz", + "integrity": "sha512-tjQv1CNziNY+zYzcL1h4q6AA2WuBUZnBIeVyjWR/EsO1EEC1VMdvPsL02cqYLz9yvIxgycjeTsWCm6XDqNgXRw==", + "dependencies": { + "bittorrent-tracker": "^9.19.0", + "debug": "^4.3.4", + "events": "^3.3.0", + "sha.js": "^2.4.11", + "simple-peer": "^9.11.1" + } + }, + "node_modules/@peertube/p2p-media-loader-hlsjs": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@peertube/p2p-media-loader-hlsjs/-/p2p-media-loader-hlsjs-1.0.14.tgz", + "integrity": "sha512-ySUVgUvAFXCE5E94xxjfywQ8xzk3jy9UGVkgi5Oqq+QeY7uG+o7CZ+LsQ/RjXgWBD70tEnyyfADHtL+9FCnwyQ==", + "dependencies": { + "@peertube/p2p-media-loader-core": "^1.0.14", + "debug": "^4.3.4", + "events": "^3.3.0", + "m3u8-parser": "^4.7.1" + } + }, "node_modules/@trevoreyre/autocomplete-vue": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@trevoreyre/autocomplete-vue/-/autocomplete-vue-2.4.1.tgz", @@ -1932,9 +1988,9 @@ } }, "node_modules/@types/babel__core": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", - "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.3.tgz", + "integrity": "sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA==", "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -1944,100 +2000,100 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.6.tgz", + "integrity": "sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w==", "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.3.tgz", + "integrity": "sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ==", "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__traverse": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", - "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.3.tgz", + "integrity": "sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw==", "dependencies": { "@babel/types": "^7.20.7" } }, "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", + "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "node_modules/@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.12.tgz", + "integrity": "sha512-ky0kWSqXVxSqgqJvPIkgFkcn4C8MnRog308Ou8xBBIVo39OmUFy+jqNe0nPwLCDFxUpmT9EvT91YzOJgkDRcFg==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/clean-css": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.6.tgz", - "integrity": "sha512-Ze1tf+LnGPmG6hBFMi0B4TEB0mhF7EiMM5oyjLDNPE9hxrPU0W+5+bHvO+eFPA+bt0iC1zkQMoU/iGdRVjcRbw==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.9.tgz", + "integrity": "sha512-pjzJ4n5eAXAz/L5Zur4ZymuJUvyo0Uh0iRnRI/1kADFLs76skDky0K0dX1rlv4iXXrJXNk3sxRWVJR7CMDroWA==", "dependencies": { "@types/node": "*", "source-map": "^0.6.0" } }, "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "version": "3.4.37", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", + "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", - "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.2.tgz", + "integrity": "sha512-gX2j9x+NzSh4zOhnRPSdPPmTepS4DfxES0AvIFv3jGv5QyeAJf6u6dY5/BAoAJU9Qq1uTvwOku8SSC2GnCRl6Q==", "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" } }, "node_modules/@types/eslint": { - "version": "8.40.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.1.tgz", - "integrity": "sha512-vRb792M4mF1FBT+eoLecmkpLXwxsBHvWWRGJjzbYANBM6DtiJc6yETyv4rqDA6QNjF1pkj1U7LMA6dGb3VYlHw==", + "version": "8.44.6", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.6.tgz", + "integrity": "sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw==", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.6.tgz", + "integrity": "sha512-zfM4ipmxVKWdxtDaJ3MP3pBurDXOCoyjvlpE3u6Qzrmw4BPbfm4/ambIeTk/r/J0iq/+2/xp0Fmt+gFvXJY2PQ==", "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", + "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==" }, "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", + "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -2056,9 +2112,9 @@ } }, "node_modules/@types/express/node_modules/@types/express-serve-static-core": { - "version": "4.17.35", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", - "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "version": "4.17.39", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", + "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -2075,42 +2131,47 @@ "@types/node": "*" } }, + "node_modules/@types/http-errors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", + "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==" + }, "node_modules/@types/http-proxy": { - "version": "1.17.11", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", - "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", + "version": "1.17.13", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.13.tgz", + "integrity": "sha512-GkhdWcMNiR5QSQRYnJ+/oXzu0+7JJEPC8vkWXK351BkhjraZF+1W13CUYARUvX9+NqIU2n6YHA4iwywsc/M6Sw==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/imagemin": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@types/imagemin/-/imagemin-8.0.1.tgz", - "integrity": "sha512-DSpM//dRPzme7doePGkmR1uoquHi0h0ElaA5qFnxHECfFcB8z/jhMI8eqmxWNpHn9ZG18p4PC918sZLhR0cr5A==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/imagemin/-/imagemin-8.0.3.tgz", + "integrity": "sha512-se/hpaYxu5DyvPqmUEwbupmbQSx6JNislk0dkoIgWSmArkj+Ow9pGG9pGz8MRmbQDfGNYNzqwPQKHCUy+K+jpQ==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/imagemin-gifsicle": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@types/imagemin-gifsicle/-/imagemin-gifsicle-7.0.1.tgz", - "integrity": "sha512-kUz6sUh0P95JOS0RGEaaemWUrASuw+dLsWIveK2UZJx74id/B9epgblMkCk/r5MjUWbZ83wFvacG5Rb/f97gyA==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/imagemin-gifsicle/-/imagemin-gifsicle-7.0.3.tgz", + "integrity": "sha512-GQBKOk9doOd0Xp7OvO4QDl7U0Vkwk2Ps7J0rxafdAa7wG9lu7idvZTm8TtSZiRtHENdkW88Kz8OjmjMlgeeC5w==", "dependencies": { "@types/imagemin": "*" } }, "node_modules/@types/imagemin-mozjpeg": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@types/imagemin-mozjpeg/-/imagemin-mozjpeg-8.0.1.tgz", - "integrity": "sha512-kMQWEoKxxhlnH4POI3qfW9DjXlQfi80ux3l2b3j5R3eudSCoUIzKQLkfMjNJ6eMYnMWBcB+rfQOWqIzdIwFGKw==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/imagemin-mozjpeg/-/imagemin-mozjpeg-8.0.3.tgz", + "integrity": "sha512-+U/ibETP2/oRqeuaaXa67dEpKHfzmfK0OBVC09AR4c1CIFAKjQ5xY+dxH+fjoMQRlwdcRQLkn/ALtnxSl3Xsqw==", "dependencies": { "@types/imagemin": "*" } }, "node_modules/@types/imagemin-optipng": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@types/imagemin-optipng/-/imagemin-optipng-5.2.1.tgz", - "integrity": "sha512-XCM/3q+HUL7v4zOqMI+dJ5dTxT+MUukY9KU49DSnYb/4yWtSMHJyADP+WHSMVzTR63J2ZvfUOzSilzBNEQW78g==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/imagemin-optipng/-/imagemin-optipng-5.2.3.tgz", + "integrity": "sha512-Q80ANbJYn+WgKkWVfx9f7/q4LR6qun4NIiuV1eRWCg8KCAmNrU7ZH16a2hGs9kfkFqyJlhBv6oV9SDXe1vL3aQ==", "dependencies": { "@types/imagemin": "*" } @@ -2125,14 +2186,14 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==" + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", + "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==" }, "node_modules/@types/mime": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", - "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.3.tgz", + "integrity": "sha512-i8MBln35l856k5iOhKk2XJ4SeAWg75mLIpZB4v6imOagKL6twsukBZGDMNhdOVk7yRFTMPpfILocMos59Q1otQ==" }, "node_modules/@types/minimatch": { "version": "5.1.2", @@ -2140,24 +2201,27 @@ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==" }, "node_modules/@types/node": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz", - "integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ==" + "version": "20.8.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.7.tgz", + "integrity": "sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ==", + "dependencies": { + "undici-types": "~5.25.1" + } }, "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.1.tgz", + "integrity": "sha512-3YmXzzPAdOTVljVMkTMBdBEvlOLg2cDQaDhnnhT3nT9uDbnJzjWhKlzb+desT12Y7tGqaN6d+AbozcKzyL36Ng==" }, "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", + "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==" }, "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", + "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==" }, "node_modules/@types/retry": { "version": "0.12.0", @@ -2165,40 +2229,41 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" }, "node_modules/@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", + "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@types/send/node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", + "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==" }, "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.3.tgz", + "integrity": "sha512-4KG+yMEuvDPRrYq5fyVm/I2uqAJSAwZK9VSa+Zf+zUq9/oxSSvy3kkIqyL+jjStv6UCVi8/Aho0NHtB1Fwosrg==", "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", - "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", + "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", "dependencies": { + "@types/http-errors": "*", "@types/mime": "*", "@types/node": "*" } }, "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "version": "0.3.35", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.35.tgz", + "integrity": "sha512-tIF57KB+ZvOBpAQwSaACfEu7htponHXaFzP7RfKYgsOS0NoYnn+9+jzp7bbq4fWerizI3dTB4NfAZoyeQKWJLw==", "dependencies": { "@types/node": "*" } @@ -2209,13 +2274,27 @@ "integrity": "sha512-AZU7vQcy/4WFEuwnwsNsJnFwupIpbllH1++LXScN6uxT1Z4zPzdrWG97w4/I7eFKFTvfy/bHFStWjdBAg2Vjug==" }, "node_modules/@types/ws": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", - "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.8.tgz", + "integrity": "sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==", "dependencies": { "@types/node": "*" } }, + "node_modules/@videojs/vhs-utils": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz", + "integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "global": "^4.4.0", + "url-toolkit": "^2.2.1" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + } + }, "node_modules/@vue/compiler-sfc": { "version": "2.7.14", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz", @@ -2464,10 +2543,11 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "node_modules/@zip.js/zip.js": { - "version": "2.7.15", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.15.tgz", - "integrity": "sha512-iuL2otty04U4YBfdpd22XJacKaNuR7AHWlQrE/L9zHxfunXNtIeSgCxi66T74pnzHdmmpGqlBlZoVRkWOT70aA==", + "version": "2.7.30", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.30.tgz", + "integrity": "sha512-nhMvQCj+TF1ATBqYzFds7v+yxPBhdDYHh8J341KtC1D2UrVBUIYcYK4Jy1/GiTsxOXEiKOXSUxvPG/XR+7jMqw==", "engines": { + "bun": ">=0.7.0", "deno": ">=1.0.0", "node": ">=16.5.0" } @@ -2485,9 +2565,9 @@ } }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "bin": { "acorn": "bin/acorn" }, @@ -2503,6 +2583,11 @@ "acorn": "^8" } }, + "node_modules/addr-to-ip-port": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/addr-to-ip-port/-/addr-to-ip-port-1.5.4.tgz", + "integrity": "sha512-ByxmJgv8vjmDcl3IDToxL2yrWFrRtFpZAToY0f46XFXl8zS081t7El5MXIodwm7RC6DhHBRoOSMLFSPKCtHukg==" + }, "node_modules/adjust-sourcemap-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", @@ -2668,31 +2753,31 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, "node_modules/assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.1.tgz", + "integrity": "sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==", "dependencies": { - "object-assign": "^4.1.1", - "util": "0.10.3" + "object.assign": "^4.1.4", + "util": "^0.10.4" } }, "node_modules/assert/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" }, "node_modules/assert/node_modules/util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", "dependencies": { - "inherits": "2.0.1" + "inherits": "2.0.3" } }, "node_modules/autoprefixer": { - "version": "10.4.14", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", - "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "version": "10.4.16", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", + "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", "funding": [ { "type": "opencollective", @@ -2701,12 +2786,16 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001464", - "fraction.js": "^4.2.0", + "browserslist": "^4.21.10", + "caniuse-lite": "^1.0.30001538", + "fraction.js": "^4.3.6", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", "postcss-value-parser": "^4.2.0" @@ -2754,47 +2843,47 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz", - "integrity": "sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==", + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", + "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.4.0", - "semver": "^6.1.1" + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.3", + "semver": "^6.3.1" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz", - "integrity": "sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==", + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.5.tgz", + "integrity": "sha512-Q6CdATeAvbScWPNLB8lzSO7fgUVBkQt6zLgNlfyeCr/EQaEQR+bWiBYYPYAFyE528BMjRhL+1QBMOI4jc/c5TA==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.0", - "core-js-compat": "^3.30.1" + "@babel/helper-define-polyfill-provider": "^0.4.3", + "core-js-compat": "^3.32.2" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz", - "integrity": "sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", + "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.0" + "@babel/helper-define-polyfill-provider": "^0.4.3" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/balanced-match": { @@ -2826,6 +2915,11 @@ "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" }, + "node_modules/bencode": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bencode/-/bencode-2.0.3.tgz", + "integrity": "sha512-D/vrAD4dLVX23NalHwb8dSvsUsxeRPO8Y7ToKA015JQYq69MLDOMkC0uGZYA/MPpltLO8rt8eqFC2j8DxjTZ/w==" + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -2847,6 +2941,79 @@ "node": ">=8" } }, + "node_modules/bittorrent-peerid": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/bittorrent-peerid/-/bittorrent-peerid-1.3.6.tgz", + "integrity": "sha512-VyLcUjVMEOdSpHaCG/7odvCdLbAB1y3l9A2V6WIje24uV7FkJPrQrH/RrlFmKxP89pFVDEnE+YlHaFujlFIZsg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bittorrent-tracker": { + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/bittorrent-tracker/-/bittorrent-tracker-9.19.0.tgz", + "integrity": "sha512-09d0aD2b+MC+zWvWajkUAKkYMynYW4tMbTKiRSthKtJZbafzEoNQSUHyND24SoCe3ZOb2fKfa6fu2INAESL9wA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "bencode": "^2.0.1", + "bittorrent-peerid": "^1.3.3", + "bn.js": "^5.2.0", + "chrome-dgram": "^3.0.6", + "clone": "^2.0.0", + "compact2string": "^1.4.1", + "debug": "^4.1.1", + "ip": "^1.1.5", + "lru": "^3.1.0", + "minimist": "^1.2.5", + "once": "^1.4.0", + "queue-microtask": "^1.2.3", + "random-iterate": "^1.0.1", + "randombytes": "^2.1.0", + "run-parallel": "^1.2.0", + "run-series": "^1.1.9", + "simple-get": "^4.0.0", + "simple-peer": "^9.11.0", + "simple-websocket": "^9.1.0", + "socks": "^2.0.0", + "string2compact": "^1.3.0", + "unordered-array-remove": "^1.0.2", + "ws": "^7.4.5" + }, + "bin": { + "bittorrent-tracker": "bin/cmd.js" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "bufferutil": "^4.0.3", + "utf-8-validate": "^5.0.5" + } + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -3075,9 +3242,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz", - "integrity": "sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "funding": [ { "type": "opencollective", @@ -3093,10 +3260,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001489", - "electron-to-chromium": "^1.4.411", - "node-releases": "^2.0.12", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -3125,6 +3292,19 @@ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" }, + "node_modules/bufferutil": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -3139,12 +3319,13 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3179,9 +3360,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001502", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001502.tgz", - "integrity": "sha512-AZ+9tFXw1sS0o0jcpJQIXvFTOB/xGiQ4OQ2t98QX3NDn2EZTSRBC801gxrsGgViuq2ak/NLkNgSNEPtCr5lfKg==", + "version": "1.0.30001553", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001553.tgz", + "integrity": "sha512-N0ttd6TrFfuqKNi+pMgWJTb9qrdJu4JSpgPFLe/lrD19ugC6fZgF0pUewRowDwzdDnb9V41mFcdlYgl/PyKf4A==", "funding": [ { "type": "opencollective", @@ -3285,6 +3466,29 @@ "fsevents": "~2.3.2" } }, + "node_modules/chrome-dgram": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/chrome-dgram/-/chrome-dgram-3.0.6.tgz", + "integrity": "sha512-bqBsUuaOiXiqxXt/zA/jukNJJ4oaOtc7ciwqJpZVEaaXwwxqgI2/ZdG02vXYWUhHGziDlvGMQWk0qObgJwVYKA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "inherits": "^2.0.4", + "run-series": "^1.1.9" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -3349,6 +3553,14 @@ "node": ">=12" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -3406,6 +3618,14 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, + "node_modules/compact2string": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/compact2string/-/compact2string-1.4.1.tgz", + "integrity": "sha512-3D+EY5nsRhqnOwDxveBv5T8wGo4DEvYxjDtPGmdOX+gfr5gE92c2RC0w2wa+xEefm07QuVqqcF3nZJUZ92l/og==", + "dependencies": { + "ipaddr.js": ">= 0.1.5" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -3532,9 +3752,9 @@ } }, "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { "version": "0.5.0", @@ -3550,9 +3770,9 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/core-js": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.31.0.tgz", - "integrity": "sha512-NIp2TQSGfR6ba5aalZD+ZQ1fSxGhDo/s1w0nx3RYzf2pnJxt7YynxFlFScP6eV7+GZsKO95NSjGxyJsU3DZgeQ==", + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.1.tgz", + "integrity": "sha512-qVSq3s+d4+GsqN0teRCJtM6tdEEXyWxjzbhVrCHmBS5ZTM0FS2MOS0D13dUXAWDUN6a+lHI/N1hF9Ytz6iLl9Q==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -3560,11 +3780,11 @@ } }, "node_modules/core-js-compat": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz", - "integrity": "sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==", + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.1.tgz", + "integrity": "sha512-6pYKNOgD/j/bkC5xS5IIg6bncid3rfrI42oBH1SQJbsmYPKF7rhzcFzYCcxYMmNQQ0rCEB8WqpW7QHndOggaeQ==", "dependencies": { - "browserslist": "^4.21.5" + "browserslist": "^4.22.1" }, "funding": { "type": "opencollective", @@ -3631,9 +3851,9 @@ } }, "node_modules/cropperjs": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.13.tgz", - "integrity": "sha512-by7jKAo73y5/Do0K6sxdTKHgndY0NMjG2bEdgeJxycbcmHuCiMXqw8sxy5C5Y5WTOTcDGmbT7Sr5CgKOXR06OA==" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.6.1.tgz", + "integrity": "sha512-F4wsi+XkDHCOMrHMYjrTEE4QBOrsHHN5/2VsVAaRq8P7E5z7xQpT75S+f/9WikmBEailas3+yo+6zPIomW+NOA==" }, "node_modules/cross-env": { "version": "5.2.1", @@ -3697,9 +3917,9 @@ } }, "node_modules/css-declaration-sorter": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz", - "integrity": "sha512-jDfsatwWMWN0MODAFuHszfjphEXfNw9JUAhmY4pLu3TyTU+ohUpsbVtbU+1MZn4a47D9kqh03i4eyOm+74+zew==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", "engines": { "node": "^10 || ^12 || >=14" }, @@ -3748,9 +3968,9 @@ } }, "node_modules/css-loader/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "peer": true, "dependencies": { @@ -3954,6 +4174,20 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -3965,6 +4199,19 @@ "node": ">= 10" } }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -3973,6 +4220,22 @@ "node": ">=8" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/del": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz", @@ -4060,9 +4323,9 @@ "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" }, "node_modules/dns-packet": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.0.tgz", - "integrity": "sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" }, @@ -4191,9 +4454,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.427", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.427.tgz", - "integrity": "sha512-HK3r9l+Jm8dYAm1ctXEWIC+hV60zfcjS9UA5BDlYvnI5S7PU/yytjpvSrTNrSSRRkuu3tDyZhdkwIczh+0DWaw==" + "version": "1.4.563", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.563.tgz", + "integrity": "sha512-dg5gj5qOgfZNkPNeyKBZQAQitIQ/xwfIDmEQJHCbXaD9ebTZxwJXUsDYcBlAvZGZLi+/354l35J1wkmP6CqYaw==" }, "node_modules/elliptic": { "version": "6.5.4", @@ -4236,9 +4499,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz", - "integrity": "sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -4256,9 +4519,9 @@ } }, "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", + "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", "bin": { "envinfo": "dist/cli.js" }, @@ -4266,6 +4529,11 @@ "node": ">=4" } }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -4275,9 +4543,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", - "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", + "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==" }, "node_modules/es6-object-assign": { "version": "1.1.0", @@ -4542,9 +4810,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -4608,9 +4876,9 @@ } }, "node_modules/file-loader/node_modules/schema-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz", - "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -4717,10 +4985,18 @@ "desandro-matches-selector": "^2.0.0" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "bin": { + "flat": "cli.js" + } + }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", "funding": [ { "type": "individual", @@ -4745,15 +5021,15 @@ } }, "node_modules/fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "engines": { "node": "*" }, "funding": { "type": "patreon", - "url": "https://www.patreon.com/infusion" + "url": "https://github.com/sponsors/rawify" } }, "node_modules/fresh": { @@ -4778,9 +5054,9 @@ } }, "node_modules/fs-monkey": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", - "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==" }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -4788,9 +5064,9 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, "optional": true, "os": [ @@ -4801,9 +5077,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -4813,6 +5092,11 @@ "node": ">=6.9.0" } }, + "node_modules/get-browser-rtc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", + "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==" + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -4822,14 +5106,14 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4916,6 +5200,17 @@ "node": ">=8" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4931,17 +5226,6 @@ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -4950,6 +5234,17 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", @@ -5012,6 +5307,17 @@ "minimalistic-assert": "^1.0.1" } }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -5021,9 +5327,9 @@ } }, "node_modules/hls.js": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.4.5.tgz", - "integrity": "sha512-xb7IiSM9apU3tJWb5rdSStobXPNJJykHTwSy7JnLF5y/kLJXWjoR/fEpNBlwYxkKcDiiSfO9SQI8yFravZJxIg==" + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.4.12.tgz", + "integrity": "sha512-1RBpx2VihibzE3WE9kGoVCtrhhDWTzydzElk/kyRbEOLnb1WIE+3ZabM/L8BqKFTCL3pUy4QzhXgD1Q6Igr1JA==" }, "node_modules/hmac-drbg": { "version": "1.0.1", @@ -5036,9 +5342,9 @@ } }, "node_modules/howler": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.3.tgz", - "integrity": "sha512-QM0FFkw0LRX1PR8pNzJVAY25JhIWvbKMBFM4gqk+QdV+kPXOhleWGCB6AiAF/goGjIHK2e/nIElplvjQwhr0jg==" + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.4.tgz", + "integrity": "sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w==" }, "node_modules/hpack.js": { "version": "2.1.6", @@ -5052,9 +5358,19 @@ } }, "node_modules/html-entities": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.5.tgz", - "integrity": "sha512-72TJlcMkYsEJASa/3HnX7VT59htM7iSHbH59NSZbtc+22Ap0Txnlx91sfeB+/A7wNZg7UxtZdhAW4y+/jimrdg==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] }, "node_modules/html-loader": { "version": "1.3.2", @@ -5078,9 +5394,9 @@ } }, "node_modules/html-loader/node_modules/schema-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz", - "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -5344,9 +5660,9 @@ } }, "node_modules/immutable": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", - "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", + "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", "dev": true }, "node_modules/import-fresh": { @@ -5422,6 +5738,11 @@ "node": ">= 0.10" } }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + }, "node_modules/ipaddr.js": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", @@ -5452,11 +5773,11 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5619,9 +5940,9 @@ } }, "node_modules/jquery": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz", - "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ==" + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" }, "node_modules/jquery-scroll-lock": { "version": "3.1.3", @@ -5711,9 +6032,9 @@ } }, "node_modules/laravel-echo": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.15.1.tgz", - "integrity": "sha512-rW9XTXqs1v3sgcSFz7aE3/MPa2lfZjnsV/hrjyS/VYecQAx1lSP0hg3KumuR6ftkeneM093tVvTkRyKFumSyXg==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.15.3.tgz", + "integrity": "sha512-SRXzccaat6w4qKgZ4/rjFKr3nJfVxB+ly4V0MEJNIF1/TpERNXepo3uk7NnOjBGsiV/np1fl2XitAzW4Sa1s/w==", "dev": true, "engines": { "node": ">=10" @@ -5842,9 +6163,9 @@ } }, "node_modules/laravel-mix/node_modules/schema-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz", - "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -5859,9 +6180,9 @@ } }, "node_modules/laravel-mix/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -5878,12 +6199,12 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/launch-editor": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", - "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", "dependencies": { "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" + "shell-quote": "^1.8.1" } }, "node_modules/lilconfig": { @@ -5964,6 +6285,17 @@ "tslib": "^2.0.3" } }, + "node_modules/lru": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lru/-/lru-3.1.0.tgz", + "integrity": "sha512-5OUtoiVIGU4VXBOshidmtOsvBIvcQR6FD/RzWSvaeHyxCGB+PCUCu+52lqMfdc0h/2CLvHhZS4TwUmMQrrMbBQ==", + "dependencies": { + "inherits": "^2.0.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -5972,6 +6304,16 @@ "yallist": "^3.0.2" } }, + "node_modules/m3u8-parser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.8.0.tgz", + "integrity": "sha512-UqA2a/Pw3liR6Df3gwxrqghCP17OpPlQj6RBPLYygf/ZSQ4MoSgvdvhvt35qV+3NaaA0FSZx93Ix+2brT1U7cA==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -5987,9 +6329,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } @@ -6140,6 +6482,17 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", @@ -6169,9 +6522,9 @@ } }, "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz", - "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -6285,9 +6638,9 @@ } }, "node_modules/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -6311,6 +6664,17 @@ "node": ">= 6.13.0" } }, + "node_modules/node-gyp-build": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz", + "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-libs-browser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", @@ -6366,9 +6730,9 @@ } }, "node_modules/node-notifier/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -6399,9 +6763,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/node-releases": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", - "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==" + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -6460,18 +6824,35 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6803,9 +7184,9 @@ } }, "node_modules/postcss": { - "version": "8.4.24", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", - "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -6978,9 +7359,9 @@ } }, "node_modules/postcss-loader/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -7461,9 +7842,9 @@ } }, "node_modules/pusher-js/node_modules/@types/node": { - "version": "14.18.50", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.50.tgz", - "integrity": "sha512-DdJP83r2Zp5x32la3jEzjIlB85+2gMPUHP1xFL2xFORzbJ94sNwh4b6ZBaF6EN/7BTII6mba3yakqfLEnt5eZg==", + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", "dev": true }, "node_modules/qs": { @@ -7507,6 +7888,11 @@ } ] }, + "node_modules/random-iterate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/random-iterate/-/random-iterate-1.0.1.tgz", + "integrity": "sha512-Jdsdnezu913Ot8qgKgSgs63XkAjEsnMcS1z+cC6D6TNXsUXsMxy0RpclF2pzGZTEiTXL9BiArdGTEexcv4nqcA==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -7622,9 +8008,9 @@ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", "dependencies": { "regenerate": "^1.4.2" }, @@ -7633,14 +8019,14 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "node_modules/regenerator-transform": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", "dependencies": { "@babel/runtime": "^7.8.4" } @@ -7724,11 +8110,11 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -7782,6 +8168,12 @@ "node": ">=12" } }, + "node_modules/resolve-url-loader/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -7844,6 +8236,25 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/run-series": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", + "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7869,9 +8280,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.63.3", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.3.tgz", - "integrity": "sha512-ySdXN+DVpfwq49jG1+hmtDslYqpS7SkOR5GpF6o2bmb1RL/xS+wvPmegMvMywyfsmAV6p7TgwXYGrCZIFFbAHg==", + "version": "1.69.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.4.tgz", + "integrity": "sha512-+qEreVhqAy8o++aQfCJwp0sklr2xyEzkm9Pp/Igu9wNPoe7EZEQ8X/MBvvXggI2ql607cxKg/RKOwDj6pp2XDA==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -7957,9 +8368,9 @@ } }, "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -8098,6 +8509,20 @@ "node": ">= 0.8.0" } }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -8183,6 +8608,152 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-peer": { + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz", + "integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "buffer": "^6.0.3", + "debug": "^4.3.2", + "err-code": "^3.0.1", + "get-browser-rtc": "^1.1.0", + "queue-microtask": "^1.2.3", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/simple-peer/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/simple-peer/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/simple-websocket": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", + "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "debug": "^4.3.1", + "queue-microtask": "^1.2.2", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0", + "ws": "^7.4.2" + } + }, + "node_modules/simple-websocket/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -8191,6 +8762,15 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -8201,6 +8781,24 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks/node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -8287,9 +8885,9 @@ } }, "node_modules/std-env": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.3.tgz", - "integrity": "sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==" + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.4.3.tgz", + "integrity": "sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==" }, "node_modules/stream-browserify": { "version": "2.0.2", @@ -8333,6 +8931,15 @@ "node": ">=8" } }, + "node_modules/string2compact": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/string2compact/-/string2compact-1.3.2.tgz", + "integrity": "sha512-3XUxUgwhj7Eqh2djae35QHZZT4mN3fsO7kagZhSGmhhlrQagVvWSFuuFIWnpxFS0CdTB2PlQcaL16RDi14I8uw==", + "dependencies": { + "addr-to-ip-port": "^1.0.1", + "ipaddr.js": "^2.0.0" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -8372,9 +8979,9 @@ } }, "node_modules/style-loader/node_modules/schema-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz", - "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -8468,9 +9075,9 @@ } }, "node_modules/terser": { - "version": "5.17.7", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.7.tgz", - "integrity": "sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.22.0.tgz", + "integrity": "sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -8518,9 +9125,9 @@ } }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz", - "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -8598,9 +9205,9 @@ "integrity": "sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ==" }, "node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tty-browserify": { "version": "0.0.0", @@ -8633,6 +9240,11 @@ "node": ">= 0.6" } }, + "node_modules/undici-types": { + "version": "5.25.3", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -8677,6 +9289,11 @@ "node": ">= 10.0.0" } }, + "node_modules/unordered-array-remove": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unordered-array-remove/-/unordered-array-remove-1.0.2.tgz", + "integrity": "sha512-45YsfD6svkgaCBNyvD+dFHm4qFX9g3wRSIVgWVPtm2OCnphvPxzJoe20ATsiNpNJrmzHifnxm+BN5F7gFT/4gw==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -8686,9 +9303,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "funding": [ { "type": "opencollective", @@ -8731,12 +9348,12 @@ } }, "node_modules/url": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", - "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", "dependencies": { "punycode": "^1.4.1", - "qs": "^6.11.0" + "qs": "^6.11.2" } }, "node_modules/url-polyfill": { @@ -8744,6 +9361,24 @@ "resolved": "https://registry.npmjs.org/url-polyfill/-/url-polyfill-1.1.12.tgz", "integrity": "sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A==" }, + "node_modules/url-toolkit": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz", + "integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==" + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -8872,9 +9507,9 @@ } }, "node_modules/vue-loader": { - "version": "15.10.1", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.1.tgz", - "integrity": "sha512-SaPHK1A01VrNthlix6h1hq4uJu7S/z0kdLUb6klubo738NeQoLbS6V9/d8Pv19tU0XdQKju3D1HSKuI8wJ5wMA==", + "version": "15.11.1", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.11.1.tgz", + "integrity": "sha512-0iw4VchYLePqJfJu9s62ACWUXeSqM30SQqlIftbYWM3C+jpPcEHKSPUZBLjSF9au4HTHQ/naF6OGnO3Q/qGR3Q==", "dev": true, "dependencies": { "@vue/component-compiler-utils": "^3.1.0", @@ -8891,6 +9526,9 @@ "cache-loader": { "optional": true }, + "prettier": { + "optional": true + }, "vue-template-compiler": { "optional": true } @@ -9055,9 +9693,9 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack": { - "version": "5.86.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.86.0.tgz", - "integrity": "sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg==", + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", @@ -9068,7 +9706,7 @@ "acorn-import-assertions": "^1.9.0", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.14.1", + "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -9078,7 +9716,7 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.2", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", @@ -9254,9 +9892,9 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.1.0.tgz", - "integrity": "sha512-Jw+GZVbP5IggB2WAn6UHI02LBwGmsIeYN/lNbSMZyDziQ7jmtAUrqKqDja+W89YHVs+KL/3IkIMltAklqB1vAw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -9361,9 +9999,9 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.1.0.tgz", - "integrity": "sha512-Jw+GZVbP5IggB2WAn6UHI02LBwGmsIeYN/lNbSMZyDziQ7jmtAUrqKqDja+W89YHVs+KL/3IkIMltAklqB1vAw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -9378,12 +10016,33 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/webpack-merge": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", - "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dependencies": { "clone-deep": "^4.0.1", + "flat": "^5.0.2", "wildcard": "^2.0.0" }, "engines": { @@ -9417,9 +10076,9 @@ } }, "node_modules/webpack/node_modules/schema-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz", - "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -9527,15 +10186,15 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "engines": { - "node": ">=10.0.0" + "node": ">=8.3.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "utf-8-validate": "^5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/package.json b/package.json index 9a188a1d4..6598175d9 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,12 @@ }, "dependencies": { "@fancyapps/fancybox": "^3.5.7", + "@hcaptcha/vue-hcaptcha": "^1.3.0", + "@peertube/p2p-media-loader-core": "^1.0.14", + "@peertube/p2p-media-loader-hlsjs": "^1.0.14", "@trevoreyre/autocomplete-vue": "^2.2.0", "@web3-storage/parse-link-header": "^3.1.0", - "@zip.js/zip.js": "^2.7.14", + "@zip.js/zip.js": "^2.7.24", "animate.css": "^4.1.0", "bigpicture": "^2.6.2", "blurhash": "^1.1.3", diff --git a/public/js/account-import.js b/public/js/account-import.js index dede345a4..74d4d9e4d 100644 Binary files a/public/js/account-import.js and b/public/js/account-import.js differ diff --git a/public/js/activity.js b/public/js/activity.js index 2a02860cb..6d643b050 100644 Binary files a/public/js/activity.js and b/public/js/activity.js differ diff --git a/public/js/admin.js b/public/js/admin.js index cb489e556..d71cbfa94 100644 Binary files a/public/js/admin.js and b/public/js/admin.js differ diff --git a/public/js/admin_invite.js b/public/js/admin_invite.js index 2067efcd2..071d24aca 100644 Binary files a/public/js/admin_invite.js and b/public/js/admin_invite.js differ diff --git a/public/js/changelog.bundle.742a06ba0a547120.js b/public/js/changelog.bundle.742a06ba0a547120.js new file mode 100644 index 000000000..3e0be8d49 Binary files /dev/null and b/public/js/changelog.bundle.742a06ba0a547120.js differ diff --git a/public/js/changelog.bundle.c4c82057f9628c72.js b/public/js/changelog.bundle.c4c82057f9628c72.js deleted file mode 100644 index cf5a53d93..000000000 Binary files a/public/js/changelog.bundle.c4c82057f9628c72.js and /dev/null differ diff --git a/public/js/collectioncompose.js b/public/js/collectioncompose.js index 4b0c06324..e295e7965 100644 Binary files a/public/js/collectioncompose.js and b/public/js/collectioncompose.js differ diff --git a/public/js/collections.js b/public/js/collections.js index 96378f961..f59c88863 100644 Binary files a/public/js/collections.js and b/public/js/collections.js differ diff --git a/public/js/compose-classic.js b/public/js/compose-classic.js index f88e85d22..19a9c3f2e 100644 Binary files a/public/js/compose-classic.js and b/public/js/compose-classic.js differ diff --git a/public/js/compose.chunk.10e7f993dcc726f9.js b/public/js/compose.chunk.10e7f993dcc726f9.js new file mode 100644 index 000000000..106a118f4 Binary files /dev/null and b/public/js/compose.chunk.10e7f993dcc726f9.js differ diff --git a/public/js/compose.chunk.6464688bf5b5ef97.js b/public/js/compose.chunk.6464688bf5b5ef97.js deleted file mode 100644 index 5e132d12e..000000000 Binary files a/public/js/compose.chunk.6464688bf5b5ef97.js and /dev/null differ diff --git a/public/js/compose.js b/public/js/compose.js index 9ef2d3e6d..699a19dad 100644 Binary files a/public/js/compose.js and b/public/js/compose.js differ diff --git a/public/js/daci.chunk.b17a0b11877389d7.js b/public/js/daci.chunk.b17a0b11877389d7.js new file mode 100644 index 000000000..ccbc67f41 Binary files /dev/null and b/public/js/daci.chunk.b17a0b11877389d7.js differ diff --git a/public/js/daci.chunk.bfa9e4f459fec835.js b/public/js/daci.chunk.bfa9e4f459fec835.js deleted file mode 100644 index 672d36622..000000000 Binary files a/public/js/daci.chunk.bfa9e4f459fec835.js and /dev/null differ diff --git a/public/js/developers.js b/public/js/developers.js index a61022313..b76d00957 100644 Binary files a/public/js/developers.js and b/public/js/developers.js differ diff --git a/public/js/direct.js b/public/js/direct.js index 7e515c510..704d14c08 100644 Binary files a/public/js/direct.js and b/public/js/direct.js differ diff --git a/public/js/discover.chunk.56d2d8cfbbecc761.js b/public/js/discover.chunk.56d2d8cfbbecc761.js deleted file mode 100644 index 3ea908b07..000000000 Binary files a/public/js/discover.chunk.56d2d8cfbbecc761.js and /dev/null differ diff --git a/public/js/discover.chunk.9606885dad3c8a99.js b/public/js/discover.chunk.9606885dad3c8a99.js new file mode 100644 index 000000000..7892b83f2 Binary files /dev/null and b/public/js/discover.chunk.9606885dad3c8a99.js differ diff --git a/public/js/discover.js b/public/js/discover.js index efcb505a3..4102b71f7 100644 Binary files a/public/js/discover.js and b/public/js/discover.js differ diff --git a/public/js/discover~findfriends.chunk.02be60ab26503531.js b/public/js/discover~findfriends.chunk.02be60ab26503531.js new file mode 100644 index 000000000..60d93a0a6 Binary files /dev/null and b/public/js/discover~findfriends.chunk.02be60ab26503531.js differ diff --git a/public/js/discover~findfriends.chunk.6bd4ddbabd979778.js b/public/js/discover~findfriends.chunk.6bd4ddbabd979778.js deleted file mode 100644 index 4c0abc8b2..000000000 Binary files a/public/js/discover~findfriends.chunk.6bd4ddbabd979778.js and /dev/null differ diff --git a/public/js/discover~hashtag.bundle.54f2ac43c55bf328.js b/public/js/discover~hashtag.bundle.54f2ac43c55bf328.js deleted file mode 100644 index d70058afb..000000000 Binary files a/public/js/discover~hashtag.bundle.54f2ac43c55bf328.js and /dev/null differ diff --git a/public/js/discover~hashtag.bundle.9cfffc517f35044e.js b/public/js/discover~hashtag.bundle.9cfffc517f35044e.js new file mode 100644 index 000000000..afb2b3de0 Binary files /dev/null and b/public/js/discover~hashtag.bundle.9cfffc517f35044e.js differ diff --git a/public/js/discover~memories.chunk.400f9f019bdb9fdf.js b/public/js/discover~memories.chunk.400f9f019bdb9fdf.js deleted file mode 100644 index 38fdde249..000000000 Binary files a/public/js/discover~memories.chunk.400f9f019bdb9fdf.js and /dev/null differ diff --git a/public/js/discover~memories.chunk.ce9cc6446020e9b3.js b/public/js/discover~memories.chunk.ce9cc6446020e9b3.js new file mode 100644 index 000000000..ec86c87f7 Binary files /dev/null and b/public/js/discover~memories.chunk.ce9cc6446020e9b3.js differ diff --git a/public/js/discover~myhashtags.chunk.6eab2414b2b16e19.js b/public/js/discover~myhashtags.chunk.6eab2414b2b16e19.js new file mode 100644 index 000000000..31039621f Binary files /dev/null and b/public/js/discover~myhashtags.chunk.6eab2414b2b16e19.js differ diff --git a/public/js/discover~myhashtags.chunk.ee5af357937cad2f.js b/public/js/discover~myhashtags.chunk.ee5af357937cad2f.js deleted file mode 100644 index f271b0d2c..000000000 Binary files a/public/js/discover~myhashtags.chunk.ee5af357937cad2f.js and /dev/null differ diff --git a/public/js/discover~serverfeed.chunk.0f2dcc473fdce17e.js b/public/js/discover~serverfeed.chunk.0f2dcc473fdce17e.js new file mode 100644 index 000000000..ed30efc2f Binary files /dev/null and b/public/js/discover~serverfeed.chunk.0f2dcc473fdce17e.js differ diff --git a/public/js/discover~serverfeed.chunk.fbe31eedcdafc87e.js b/public/js/discover~serverfeed.chunk.fbe31eedcdafc87e.js deleted file mode 100644 index 841a15b90..000000000 Binary files a/public/js/discover~serverfeed.chunk.fbe31eedcdafc87e.js and /dev/null differ diff --git a/public/js/discover~settings.chunk.732c1f76a00d9204.js b/public/js/discover~settings.chunk.732c1f76a00d9204.js new file mode 100644 index 000000000..959ab4d3f Binary files /dev/null and b/public/js/discover~settings.chunk.732c1f76a00d9204.js differ diff --git a/public/js/discover~settings.chunk.909aa0316f43235e.js b/public/js/discover~settings.chunk.909aa0316f43235e.js deleted file mode 100644 index b334ca360..000000000 Binary files a/public/js/discover~settings.chunk.909aa0316f43235e.js and /dev/null differ diff --git a/public/js/dms.chunk.98e12cf9137ddd87.js b/public/js/dms.chunk.53a951c5de2d95ac.js similarity index 52% rename from public/js/dms.chunk.98e12cf9137ddd87.js rename to public/js/dms.chunk.53a951c5de2d95ac.js index 1902f400e..0ee8967ee 100644 Binary files a/public/js/dms.chunk.98e12cf9137ddd87.js and b/public/js/dms.chunk.53a951c5de2d95ac.js differ diff --git a/public/js/dms~message.chunk.15157ff4a6c17cc7.js b/public/js/dms~message.chunk.15157ff4a6c17cc7.js new file mode 100644 index 000000000..e74ff4325 Binary files /dev/null and b/public/js/dms~message.chunk.15157ff4a6c17cc7.js differ diff --git a/public/js/dms~message.chunk.990c68dfc266b0cf.js b/public/js/dms~message.chunk.990c68dfc266b0cf.js deleted file mode 100644 index cb0b57355..000000000 Binary files a/public/js/dms~message.chunk.990c68dfc266b0cf.js and /dev/null differ diff --git a/public/js/error404.bundle.182d0aaa2da9ed23.js b/public/js/error404.bundle.182d0aaa2da9ed23.js deleted file mode 100644 index aed9ad989..000000000 Binary files a/public/js/error404.bundle.182d0aaa2da9ed23.js and /dev/null differ diff --git a/public/js/error404.bundle.3bbc118159460db6.js b/public/js/error404.bundle.3bbc118159460db6.js new file mode 100644 index 000000000..93b6b5acd Binary files /dev/null and b/public/js/error404.bundle.3bbc118159460db6.js differ diff --git a/public/js/hashtag.js b/public/js/hashtag.js index 646ec648f..5ef001f9f 100644 Binary files a/public/js/hashtag.js and b/public/js/hashtag.js differ diff --git a/public/js/home.chunk.351f55e9d09b6482.js b/public/js/home.chunk.351f55e9d09b6482.js new file mode 100644 index 000000000..c6f3e4d4f Binary files /dev/null and b/public/js/home.chunk.351f55e9d09b6482.js differ diff --git a/public/js/home.chunk.bd623a430a5584c2.js.LICENSE.txt b/public/js/home.chunk.351f55e9d09b6482.js.LICENSE.txt similarity index 100% rename from public/js/home.chunk.bd623a430a5584c2.js.LICENSE.txt rename to public/js/home.chunk.351f55e9d09b6482.js.LICENSE.txt diff --git a/public/js/home.chunk.bd623a430a5584c2.js b/public/js/home.chunk.bd623a430a5584c2.js deleted file mode 100644 index 5fa049906..000000000 Binary files a/public/js/home.chunk.bd623a430a5584c2.js and /dev/null differ diff --git a/public/js/i18n.bundle.47cbf9f04d955267.js b/public/js/i18n.bundle.47cbf9f04d955267.js new file mode 100644 index 000000000..bf83a1075 Binary files /dev/null and b/public/js/i18n.bundle.47cbf9f04d955267.js differ diff --git a/public/js/i18n.bundle.4a5ff18de549ac4e.js b/public/js/i18n.bundle.4a5ff18de549ac4e.js deleted file mode 100644 index 0d714d7e0..000000000 Binary files a/public/js/i18n.bundle.4a5ff18de549ac4e.js and /dev/null differ diff --git a/public/js/landing.js b/public/js/landing.js index 2980b9e5a..8a3b2322c 100644 Binary files a/public/js/landing.js and b/public/js/landing.js differ diff --git a/public/js/manifest.js b/public/js/manifest.js index ff9202503..d6fe923ca 100644 Binary files a/public/js/manifest.js and b/public/js/manifest.js differ diff --git a/public/js/notifications.chunk.bf0c641eb1fd9cde.js b/public/js/notifications.chunk.3b92cf46da469de1.js similarity index 74% rename from public/js/notifications.chunk.bf0c641eb1fd9cde.js rename to public/js/notifications.chunk.3b92cf46da469de1.js index 93ab33779..94f7da536 100644 Binary files a/public/js/notifications.chunk.bf0c641eb1fd9cde.js and b/public/js/notifications.chunk.3b92cf46da469de1.js differ diff --git a/public/js/portfolio.js b/public/js/portfolio.js index d0f2db7fc..ce1e7097f 100644 Binary files a/public/js/portfolio.js and b/public/js/portfolio.js differ diff --git a/public/js/post.chunk.23fc9e82d4fadc83.js b/public/js/post.chunk.23fc9e82d4fadc83.js new file mode 100644 index 000000000..6e8aa73f4 Binary files /dev/null and b/public/js/post.chunk.23fc9e82d4fadc83.js differ diff --git a/public/js/post.chunk.729ca668f46545cb.js.LICENSE.txt b/public/js/post.chunk.23fc9e82d4fadc83.js.LICENSE.txt similarity index 100% rename from public/js/post.chunk.729ca668f46545cb.js.LICENSE.txt rename to public/js/post.chunk.23fc9e82d4fadc83.js.LICENSE.txt diff --git a/public/js/post.chunk.729ca668f46545cb.js b/public/js/post.chunk.729ca668f46545cb.js deleted file mode 100644 index 3ede2963b..000000000 Binary files a/public/js/post.chunk.729ca668f46545cb.js and /dev/null differ diff --git a/public/js/profile-directory.js b/public/js/profile-directory.js index 6b05405e7..11324401e 100644 Binary files a/public/js/profile-directory.js and b/public/js/profile-directory.js differ diff --git a/public/js/profile.chunk.029572d9018fc65f.js b/public/js/profile.chunk.029572d9018fc65f.js deleted file mode 100644 index 493a62661..000000000 Binary files a/public/js/profile.chunk.029572d9018fc65f.js and /dev/null differ diff --git a/public/js/profile.chunk.0e5bd852054d6355.js b/public/js/profile.chunk.0e5bd852054d6355.js new file mode 100644 index 000000000..acf4175dc Binary files /dev/null and b/public/js/profile.chunk.0e5bd852054d6355.js differ diff --git a/public/js/profile.js b/public/js/profile.js index 5b281973d..a9fcd40df 100644 Binary files a/public/js/profile.js and b/public/js/profile.js differ diff --git a/public/js/profile~followers.bundle.731f680cfb96563d.js b/public/js/profile~followers.bundle.731f680cfb96563d.js new file mode 100644 index 000000000..51805c382 Binary files /dev/null and b/public/js/profile~followers.bundle.731f680cfb96563d.js differ diff --git a/public/js/profile~followers.bundle.f088062414c3b014.js b/public/js/profile~followers.bundle.f088062414c3b014.js deleted file mode 100644 index b45c73d3b..000000000 Binary files a/public/js/profile~followers.bundle.f088062414c3b014.js and /dev/null differ diff --git a/public/js/profile~following.bundle.3d95796c9f1678dd.js b/public/js/profile~following.bundle.3d95796c9f1678dd.js new file mode 100644 index 000000000..24db7dafa Binary files /dev/null and b/public/js/profile~following.bundle.3d95796c9f1678dd.js differ diff --git a/public/js/profile~following.bundle.57cbb89efa73e324.js b/public/js/profile~following.bundle.57cbb89efa73e324.js deleted file mode 100644 index f8b3dba0c..000000000 Binary files a/public/js/profile~following.bundle.57cbb89efa73e324.js and /dev/null differ diff --git a/public/js/remote_auth.js b/public/js/remote_auth.js index 88c60f980..bbc0e463f 100644 Binary files a/public/js/remote_auth.js and b/public/js/remote_auth.js differ diff --git a/public/js/search.js b/public/js/search.js index b8a171f96..12c3c6751 100644 Binary files a/public/js/search.js and b/public/js/search.js differ diff --git a/public/js/spa.js b/public/js/spa.js index 196debf0f..b496ba432 100644 Binary files a/public/js/spa.js and b/public/js/spa.js differ diff --git a/public/js/status.js b/public/js/status.js index 8db65d3e4..fb8eb6b7e 100644 Binary files a/public/js/status.js and b/public/js/status.js differ diff --git a/public/js/stories.js b/public/js/stories.js index f07cf3a0f..e38874d8e 100644 Binary files a/public/js/stories.js and b/public/js/stories.js differ diff --git a/public/js/story-compose.js b/public/js/story-compose.js index 1c81af418..85b348200 100644 Binary files a/public/js/story-compose.js and b/public/js/story-compose.js differ diff --git a/public/js/timeline.js b/public/js/timeline.js index 115fa1d5f..a0ec87563 100644 Binary files a/public/js/timeline.js and b/public/js/timeline.js differ diff --git a/public/js/vendor.js b/public/js/vendor.js index c4633d457..530259cb1 100644 Binary files a/public/js/vendor.js and b/public/js/vendor.js differ diff --git a/public/js/vendor.js.LICENSE.txt b/public/js/vendor.js.LICENSE.txt index bead335b2..bd8662c18 100644 --- a/public/js/vendor.js.LICENSE.txt +++ b/public/js/vendor.js.LICENSE.txt @@ -31,13 +31,13 @@ */ /*! - * Cropper.js v1.5.13 + * Cropper.js v1.6.1 * https://fengyuanchen.github.io/cropperjs * * Copyright 2015-present Chen Fengyuan * Released under the MIT license * - * Date: 2022-11-20T05:30:46.114Z + * Date: 2023-09-17T03:44:19.860Z */ /*! @@ -56,6 +56,20 @@ * Licensed under GPL 3. */ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ + +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ + /*! * Vue.js v2.7.14 * (c) 2014-2022 Evan You @@ -63,14 +77,14 @@ */ /*! - * jQuery JavaScript Library v3.7.0 + * jQuery JavaScript Library v3.7.1 * https://jquery.com/ * * Copyright OpenJS Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2023-05-11T18:29Z + * Date: 2023-08-28T13:37Z */ /*! @@ -143,6 +157,18 @@ and limitations under the License. /*! https://mths.be/punycode v1.4.1 by @mathias */ +/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */ + +/*! queue-microtask. MIT License. Feross Aboukhadijeh */ + +/*! run-parallel. MIT License. Feross Aboukhadijeh */ + +/*! safe-buffer. MIT License. Feross Aboukhadijeh */ + +/*! simple-peer. MIT License. Feross Aboukhadijeh */ + +/*! simple-websocket. MIT License. Feross Aboukhadijeh */ + /** * vue-class-component v7.2.3 * (c) 2015-present Evan You @@ -158,6 +184,23 @@ and limitations under the License. * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors */ +/** + * @license Apache-2.0 + * Copyright 2018 Novage LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + /** * filesize * diff --git a/public/mix-manifest.json b/public/mix-manifest.json index c580e4791..29b3788c0 100644 Binary files a/public/mix-manifest.json and b/public/mix-manifest.json differ diff --git a/resources/assets/components/AccountImport.vue b/resources/assets/components/AccountImport.vue index b669345db..6e1fe9fcf 100644 --- a/resources/assets/components/AccountImport.vue +++ b/resources/assets/components/AccountImport.vue @@ -348,8 +348,18 @@ }, 500); }, - filterPostMeta(media) { - let json = JSON.parse(media); + async fixFacebookEncoding(string) { + // Facebook and Instagram are encoding UTF8 characters in a weird way in their json + // here is a good explanation what's going wrong https://sorashi.github.io/fix-facebook-json-archive-encoding + // See https://github.com/pixelfed/pixelfed/pull/4726 for more info + const replaced = string.replace(/\\u00([a-f0-9]{2})/g, (x) => String.fromCharCode(parseInt(x.slice(2), 16))); + const buffer = Array.from(replaced, (c) => c.charCodeAt(0)); + return new TextDecoder().decode(new Uint8Array(buffer)); + }, + + async filterPostMeta(media) { + let fbfix = await this.fixFacebookEncoding(media); + let json = JSON.parse(fbfix); let res = json.filter(j => { let ids = j.media.map(m => m.uri).filter(m => { if(this.config.allow_video_posts == true) { @@ -396,12 +406,14 @@ this.filterPostMeta(media); let imgs = await Promise.all(entries.filter(entry => { - return entry.filename.startsWith('media/posts/') && (entry.filename.endsWith('.png') || entry.filename.endsWith('.jpg') || entry.filename.endsWith('.mp4')); + return (entry.filename.startsWith('media/posts/') || entry.filename.startsWith('media/other/')) && (entry.filename.endsWith('.png') || entry.filename.endsWith('.jpg') || entry.filename.endsWith('.mp4')); }) .map(async entry => { if( - entry.filename.startsWith('media/posts/') && ( + entry.filename.startsWith('media/posts/') || + entry.filename.startsWith('media/other/') + ) && ( entry.filename.endsWith('.png') || entry.filename.endsWith('.jpg') || entry.filename.endsWith('.mp4') diff --git a/resources/assets/components/Post.vue b/resources/assets/components/Post.vue index 1cc57c84e..6b3361351 100644 --- a/resources/assets/components/Post.vue +++ b/resources/assets/components/Post.vue @@ -37,6 +37,8 @@ v-on:bookmark="handleBookmark()" v-on:share="shareStatus()" v-on:unshare="unshareStatus()" + v-on:follow="follow()" + v-on:unfollow="unfollow()" v-on:counter-change="counterChange" /> @@ -333,6 +335,30 @@ }) }, + follow() { + axios.post('/api/v1/accounts/' + this.post.account.id + '/follow') + .then(res => { + this.$store.commit('updateRelationship', [res.data]); + this.user.following_count++; + this.post.account.followers_count++; + }).catch(err => { + swal('Oops!', 'An error occurred when attempting to follow this account.', 'error'); + this.post.relationship.following = false; + }); + }, + + unfollow() { + axios.post('/api/v1/accounts/' + this.post.account.id + '/unfollow') + .then(res => { + this.$store.commit('updateRelationship', [res.data]); + this.user.following_count--; + this.post.account.followers_count--; + }).catch(err => { + swal('Oops!', 'An error occurred when attempting to unfollow this account.', 'error'); + this.post.relationship.following = true; + }); + }, + openContextMenu() { this.$nextTick(() => { this.$refs.contextMenu.open(); diff --git a/resources/assets/components/partials/post/PostContent.vue b/resources/assets/components/partials/post/PostContent.vue index 057b07fea..0a88acb19 100644 --- a/resources/assets/components/partials/post/PostContent.vue +++ b/resources/assets/components/partials/post/PostContent.vue @@ -12,7 +12,7 @@
- +
@@ -108,27 +108,11 @@
- +
@@ -185,12 +169,14 @@ diff --git a/resources/assets/components/remote-auth/partials/RegisterForm.vue b/resources/assets/components/remote-auth/partials/RegisterForm.vue new file mode 100644 index 000000000..6b1c4047f --- /dev/null +++ b/resources/assets/components/remote-auth/partials/RegisterForm.vue @@ -0,0 +1,800 @@ + + + + + diff --git a/resources/assets/components/sections/Notifications.vue b/resources/assets/components/sections/Notifications.vue index b2c904184..1ddf522fc 100644 --- a/resources/assets/components/sections/Notifications.vue +++ b/resources/assets/components/sections/Notifications.vue @@ -87,12 +87,12 @@
@@ -216,7 +216,7 @@ methods: { init() { - if(this.retryAttempts == 3) { + if(this.retryAttempts == 1) { this.hasLoaded = true; this.isEmpty = true; clearTimeout(this.retryTimeout); diff --git a/resources/assets/components/sections/Timeline.vue b/resources/assets/components/sections/Timeline.vue index dd2c7d2f0..6b064acea 100644 --- a/resources/assets/components/sections/Timeline.vue +++ b/resources/assets/components/sections/Timeline.vue @@ -186,7 +186,8 @@ sharesModalPost: {}, forceUpdateIdx: 0, showReblogBanner: false, - enablingReblogs: false + enablingReblogs: false, + baseApi: '/api/v1/pixelfed/timelines/', } }, @@ -201,6 +202,10 @@ return; }; } + if(window.App.config.ab.hasOwnProperty('cached_home_timeline')) { + const cht = window.App.config.ab.cached_home_timeline == true; + this.baseApi = cht ? '/api/v1/timelines/' : '/api/pixelfed/v1/timelines/'; + } this.fetchSettings(); }, @@ -247,7 +252,7 @@ fetchTimeline(scrollToTop = false) { let url, params; if(this.getScope() === 'home' && this.settings && this.settings.hasOwnProperty('enable_reblogs') && this.settings.enable_reblogs) { - url = `/api/v1/timelines/home`; + url = this.baseApi + `home`; params = { '_pe': 1, max_id: this.max_id, @@ -255,12 +260,17 @@ include_reblogs: true, } } else { - url = `/api/pixelfed/v1/timelines/${this.getScope()}`; + url = this.baseApi + this.getScope(); params = { max_id: this.max_id, limit: 6, + '_pe': 1, } } + if(this.getScope() === 'network') { + params.remote = true; + url = this.baseApi + `public`; + } axios.get(url, { params: params }).then(res => { @@ -278,7 +288,7 @@ this.max_id = Math.min(...ids); this.feed = res.data; - if(res.data.length !== 6) { + if(res.data.length < 4) { this.canLoadMore = false; this.showLoadMore = true; } @@ -306,7 +316,8 @@ let url, params; if(this.getScope() === 'home' && this.settings && this.settings.hasOwnProperty('enable_reblogs') && this.settings.enable_reblogs) { - url = `/api/v1/timelines/home`; + url = this.baseApi + `home`; + params = { '_pe': 1, max_id: this.max_id, @@ -314,12 +325,18 @@ include_reblogs: true, } } else { - url = `/api/pixelfed/v1/timelines/${this.getScope()}`; + url = this.baseApi + this.getScope(); params = { max_id: this.max_id, limit: 6, + '_pe': 1, } } + if(this.getScope() === 'network') { + params.remote = true; + url = this.baseApi + `public`; + + } axios.get(url, { params: params }).then(res => { diff --git a/resources/assets/js/components/CollectionComponent.vue b/resources/assets/js/components/CollectionComponent.vue index dd7ebf433..ea5e21525 100644 --- a/resources/assets/js/components/CollectionComponent.vue +++ b/resources/assets/js/components/CollectionComponent.vue @@ -205,12 +205,20 @@
+ +
@@ -201,10 +205,10 @@ -
+
-
+
@@ -236,7 +240,7 @@
-
+
@@ -337,7 +341,7 @@
-
+
-
+
@@ -368,7 +372,9 @@ @@ -376,20 +382,21 @@
-
-
+
@@ -524,7 +531,7 @@
-
+
When you tag someone, they are sent a notification.
For more information on tagging, click here.

-
+

Tagging someone is like mentioning them, with the option to make it private between you.

You can choose to tag someone in public or private mode. Public mode will allow others to see who you tagged in the post and private mode tagged users will not be shown to others.

-
+

Add Location

-
+
@@ -910,6 +923,7 @@ export default { }, namedPages: [ + 'filteringMedia', 'cropPhoto', 'tagPeople', 'addLocation', @@ -943,7 +957,6 @@ export default { cb(res.data); }) .catch(err => { - console.log(err); }) }) }, @@ -957,7 +970,6 @@ export default { cb(res.data); }) .catch(err => { - console.log(err); }) }) } @@ -1032,6 +1044,10 @@ export default { collectionsPage: 1, collectionsCanLoadMore: false, spoilerText: undefined, + isFilteringMedia: false, + filteringMediaTimeout: undefined, + filteringRemainingCount: 0, + isPosting: false, } }, @@ -1068,6 +1084,16 @@ export default { return App.util.format.timeAgo(ts); }, + formatBytes(bytes, decimals = 2) { + if (!+bytes) { + return '0 Bytes' + } + const dec = decimals < 0 ? 0 : decimals + const units = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const quotient = Math.floor(Math.log(bytes) / Math.log(1024)) + return `${parseFloat((bytes / Math.pow(1024, quotient)).toFixed(dec))} ${units[quotient]}` + }, + fetchProfile() { let tags = { public: 'Public', @@ -1178,6 +1204,13 @@ export default { }, 300); }).catch(function(e) { switch(e.response.status) { + 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'); + self.page = 2; + break; + case 451: self.uploading = false; io.value = null; @@ -1242,6 +1275,50 @@ export default { }); }, + mediaReorder(dir) { + const m = this.media; + const cur = this.carouselCursor; + const pla = m[cur]; + let res = []; + let cursor = 0; + + 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; + }) + }, + + 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() { let state = this.composeState; @@ -1254,8 +1331,15 @@ export default { return; } + switch(state) { - case 'publish' : + case 'publish': + this.isPosting = true; + let count = this.media.filter(m => m.filter_class && !m.hasOwnProperty('is_filtered')).length; + if(count) { + this.applyFilterToMedia(); + return; + } if(this.composeSettings.media_descriptions === true) { let count = this.media.filter(m => { return !m.hasOwnProperty('alt') || m.alt.length < 2; @@ -1263,6 +1347,7 @@ export default { if(count.length) { swal('Missing media descriptions', 'You have enabled mandatory media descriptions. Please add media descriptions under Advanced settings to proceed. For more information, please see the media settings page.', 'warning'); + this.isPosting = false; return; } } @@ -1377,6 +1462,10 @@ export default { switch(this.mode) { case 'photo': switch(this.page) { + case 'filteringMedia': + this.page = 2; + break; + case 'addText': this.page = 1; break; @@ -1411,6 +1500,10 @@ export default { case 'video': switch(this.page) { + case 'filteringMedia': + this.page = 2; + break; + case 'licensePicker': this.page = 'video-2'; break; @@ -1431,6 +1524,10 @@ export default { this.page = 1; break; + case 'filteringMedia': + this.page = 2; + break; + case 'textOptions': this.page = 'addText'; break; @@ -1470,6 +1567,9 @@ export default { this.page = 2; break; + case 'filteringMedia': + break; + case 'cropPhoto': this.pageLoading = true; let self = this; @@ -1495,14 +1595,7 @@ export default { break; case 2: - if(this.currentFilter) { - if(window.confirm('Are you sure you want to apply this filter?')) { - this.applyFilterToMedia(); - this.page++; - } - } else { this.page++; - } break; case 3: this.page++; @@ -1649,43 +1742,73 @@ export default { // this is where the magic happens var ua = navigator.userAgent.toLowerCase(); if(ua.indexOf('firefox') == -1 && ua.indexOf('chrome') == -1) { + this.isPosting = false; swal('Oops!', 'Your browser does not support the filter feature.', 'error'); + this.page = 3; return; } - let medias = this.media; - let media = null; - const canvas = document.getElementById('pr_canvas'); - const ctx = canvas.getContext('2d'); - let image = document.getElementById('pr_img'); - let blob = null; - let data = null; - - for (var i = medias.length - 1; i >= 0; i--) { - media = medias[i]; - if(media.filter_class) { - image.src = media.url; - image.addEventListener('load', e => { - canvas.width = image.width; - canvas.height = image.height; - ctx.filter = App.util.filterCss[media.filter_class]; - ctx.drawImage(image, 0, 0, image.width, image.height); - ctx.save(); - canvas.toBlob(function(blob) { - data = new FormData(); - data.append('file', blob); - data.append('id', media.id); - axios.post('/api/compose/v0/media/update', data).then(res => { - }).catch(err => { - }); - }); - }, media.mime, 0.9); - ctx.clearRect(0, 0, image.width, image.height); - } - } - + let count = this.media.filter(m => m.filter_class).length; + if(count) { + this.page = 'filteringMedia'; + this.filteringRemainingCount = count; + this.$nextTick(() => { + this.isFilteringMedia = true; + this.media.forEach((media, idx) => this.applyFilterToMediaSave(media, idx)); + }) + } else { + this.page = 3; + } }, + applyFilterToMediaSave(media, idx) { + if(!media.filter_class) { + return; + } + + let self = this; + let data = null; + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + let image = document.createElement('img'); + image.src = media.url; + image.addEventListener('load', e => { + canvas.width = image.width; + canvas.height = image.height; + ctx.filter = App.util.filterCss[media.filter_class]; + ctx.drawImage(image, 0, 0, image.width, image.height); + ctx.save(); + canvas.toBlob(function(blob) { + data = new FormData(); + data.append('file', blob); + data.append('id', media.id); + axios.post('/api/compose/v0/media/update', data) + .then(res => { + self.media[idx].is_filtered = true; + self.updateFilteringMedia(); + }).catch(err => { + }); + }); + }, media.mime, 0.9); + ctx.clearRect(0, 0, image.width, image.height); + }, + + 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; @@ -1800,7 +1923,6 @@ export default { } window.location.href = res.data.url; }).catch(err => { - console.log(err.response.data.error); if(err.response.data.hasOwnProperty('error')) { if(err.response.data.error == 'Duplicate detected.') { this.postingPoll = false; diff --git a/resources/assets/js/components/PortfolioSettings.vue b/resources/assets/js/components/PortfolioSettings.vue index 6b1fbde8a..cd36cd072 100644 --- a/resources/assets/js/components/PortfolioSettings.vue +++ b/resources/assets/js/components/PortfolioSettings.vue @@ -238,7 +238,7 @@
-
+

Portfolio URL

{{ settings.url }}

diff --git a/resources/assets/js/components/partials/CommentFeed.vue b/resources/assets/js/components/partials/CommentFeed.vue index ee29e7c69..f471228e8 100644 --- a/resources/assets/js/components/partials/CommentFeed.vue +++ b/resources/assets/js/components/partials/CommentFeed.vue @@ -18,8 +18,8 @@
-
- diff --git a/resources/assets/js/components/partials/StatusCard.vue b/resources/assets/js/components/partials/StatusCard.vue index 7bb2f6697..1efa6e2eb 100644 --- a/resources/assets/js/components/partials/StatusCard.vue +++ b/resources/assets/js/components/partials/StatusCard.vue @@ -8,7 +8,6 @@
- Loading... · @@ -61,7 +60,6 @@
- Loading... diff --git a/resources/assets/js/components/presenter/PhotoAlbumPresenter.vue b/resources/assets/js/components/presenter/PhotoAlbumPresenter.vue index c83f11e20..3adda10df 100644 --- a/resources/assets/js/components/presenter/PhotoAlbumPresenter.vue +++ b/resources/assets/js/components/presenter/PhotoAlbumPresenter.vue @@ -26,8 +26,7 @@ .card-img-top { - border-top-left-radius: 0 !important; - border-top-right-radius: 0 !important; + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; } .content-label-wrapper { - position: relative; + position: relative; } .content-label { - margin: 0; - position: absolute; - top:50%; - left:50%; - transform: translate(-50%, -50%); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - z-index: 2; - background: rgba(0, 0, 0, 0.2) + margin: 0; + position: absolute; + top:50%; + left:50%; + transform: translate(-50%, -50%); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + z-index: 2; + background: rgba(0, 0, 0, 0.2) } .album-wrapper { - position: relative; + position: relative; } diff --git a/resources/assets/js/util/debounce.js b/resources/assets/js/util/debounce.js new file mode 100644 index 000000000..b846d35e5 --- /dev/null +++ b/resources/assets/js/util/debounce.js @@ -0,0 +1,11 @@ +export function debounce (fn, delay) { + var timeoutID = null + return function () { + clearTimeout(timeoutID) + var args = arguments + var that = this + timeoutID = setTimeout(function () { + fn.apply(that, args) + }, delay) + } +} diff --git a/resources/lang/en/profile.php b/resources/lang/en/profile.php index 7851852a2..04a2bcd90 100644 --- a/resources/lang/en/profile.php +++ b/resources/lang/en/profile.php @@ -12,4 +12,10 @@ return [ 'status.disabled.header' => 'Profile Unavailable', 'status.disabled.body' => 'Sorry, this profile is not available at the moment. Please try again shortly.', + + 'block.domain.max' => 'Max limit of domain blocks reached! You can only block :max domains at a time. Ask your admin to adjust this limit.', + + 'mutedAccounts' => 'Muted Accounts', + 'blockedAccounts' => 'Blocked Accounts', + 'blockedDomains' => 'Blocked Domains', ]; diff --git a/resources/views/admin/asf/create.blade.php b/resources/views/admin/asf/create.blade.php new file mode 100644 index 000000000..8fc88e4bd --- /dev/null +++ b/resources/views/admin/asf/create.blade.php @@ -0,0 +1,64 @@ +@extends('admin.partial.template-full') + +@section('section') +
+
+
+
+
+

New Shadow Filters

+

Creating a new admin shadow filter

+
+
+
+
+
+
+
+
+
+ @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif +
+
+ @csrf +
+ + +
+ +

Filters

+
+
+
+ + +
+
+ {{--
--}} +
+ +
+ + +
+
+ + +
+
+ +
+
+
+
+
+
+@endsection diff --git a/resources/views/admin/asf/edit.blade.php b/resources/views/admin/asf/edit.blade.php new file mode 100644 index 000000000..6d7a633f0 --- /dev/null +++ b/resources/views/admin/asf/edit.blade.php @@ -0,0 +1,64 @@ +@extends('admin.partial.template-full') + +@section('section') +
+
+
+
+
+

Edit Shadow Filters

+

Editing shadow filters

+
+
+
+
+
+
+
+
+
+ @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif +
+
+ @csrf +
+ + +
+ +

Filters

+
+
+
+ hide_from_public_feeds ? 'checked=""' : '' !!}> + +
+
+ {{--
--}} +
+ +
+ + +
+
+ active ? 'checked=""' : ''}}> + +
+
+ +
+
+
+
+
+
+@endsection diff --git a/resources/views/admin/asf/home.blade.php b/resources/views/admin/asf/home.blade.php new file mode 100644 index 000000000..4fbb7730f --- /dev/null +++ b/resources/views/admin/asf/home.blade.php @@ -0,0 +1,81 @@ +@extends('admin.partial.template-full') + +@section('section') +
+
+
+
+
+

Admin Shadow Filters

+

Manage shadow filters across Accounts, Hashtags, Feeds and Stories

+
+
+
+
+
+
+
+
+
+ +
+ +
+
+ +
+
+
+ +
+ + + + + + + + + + + + @foreach($filters as $filter) + + + + + + + + @endforeach + +
IDUsernameHide FeedsActiveCreated
{{ $filter->id }} +
+ + +

+ @{{ $filter->account()['acct'] }} +

+
+
{{ $filter->hide_from_public_feeds ? '✅' : ''}}{{ $filter->active ? '✅' : ''}}{{ $filter->created_at->diffForHumans() }}
+ +
+ {{ $filters->links() }} +
+
+
+
+@endsection diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 12b6b6f52..3403cd6b3 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -74,7 +74,10 @@
- @if(config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled')) + @if( + (config_cache('pixelfed.open_registration') && config('remote-auth.mastodon.enabled')) || + (config('remote-auth.mastodon.ignore_closed_state') && config('remote-auth.mastodon.enabled')) + )
@csrf diff --git a/resources/views/profile/embed.blade.php b/resources/views/profile/embed.blade.php index 0050a90e2..cc6097e3a 100644 --- a/resources/views/profile/embed.blade.php +++ b/resources/views/profile/embed.blade.php @@ -1,7 +1,7 @@ - + @@ -16,8 +16,8 @@ - - + + + } +
-
-
-
- - - {{$profile['username']}} - +
+ -
-
-
-

-

Posts

-
-
-

-

Followers

-
-
-

Follow

-
-
-
-
- +
+
+
+
+

+

Posts

+
+

+

Followers

- - - - - - + + + + + - + }); + + diff --git a/resources/views/profile/show.blade.php b/resources/views/profile/show.blade.php index d16eeb7c0..5107c0ab3 100644 --- a/resources/views/profile/show.blade.php +++ b/resources/views/profile/show.blade.php @@ -20,7 +20,7 @@ @endsection -@push('meta') +@push('meta') @if(false == $settings['crawlable'] || $profile->remote_url) @else diff --git a/resources/views/settings/partial/sidebar.blade.php b/resources/views/settings/partial/sidebar.blade.php index b4acf8c9b..a3837066a 100644 --- a/resources/views/settings/partial/sidebar.blade.php +++ b/resources/views/settings/partial/sidebar.blade.php @@ -72,6 +72,8 @@ @media only screen and (min-width: 768px) { border-right: 1px solid #dee2e6 !important } + height: 100%; + flex-grow: 1; } @endpush diff --git a/resources/views/settings/privacy.blade.php b/resources/views/settings/privacy.blade.php index 78ead55ee..369ddbbb4 100644 --- a/resources/views/settings/privacy.blade.php +++ b/resources/views/settings/privacy.blade.php @@ -8,8 +8,9 @@
@@ -28,9 +29,17 @@
crawlable ? 'checked=""':''}} {{$settings->is_private ? 'disabled=""':''}}> -

When your account is visible to search engines, your information can be crawled and stored by search engines.

+

When your account is visible to search engines, your information can be crawled and stored by search engines. {!! $settings->is_private ? 'Not available when your account is private' : ''!!}

+
+ +
+ indexable ? 'checked=""':''}} {{$settings->is_private ? 'disabled=""':''}}> + +

Your public posts may appear in search results on Pixelfed and Mastodon. People who have interacted with your posts may be able to search them regardless. {!! $settings->is_private ? 'Not available when your account is private' : ''!!}

@@ -39,7 +48,7 @@ -

When this option is enabled, your profile is included in the Directory. Only public profiles are eligible.

+

When this option is enabled, your profile is included in the Directory. Only public profiles are eligible. {!! $settings->is_private ? 'Not available when your account is private' : ''!!}

@@ -97,10 +106,10 @@

Enable your profile atom feed. Only public profiles are eligible.

@if($settings->show_atom)

- - {{ $profile->permalink('.atom') }} - - + + {{ $profile->permalink('.atom') }} + +

@endif
diff --git a/resources/views/settings/privacy/blocked.blade.php b/resources/views/settings/privacy/blocked.blade.php index e3b2eab2b..8906eae7d 100644 --- a/resources/views/settings/privacy/blocked.blade.php +++ b/resources/views/settings/privacy/blocked.blade.php @@ -2,40 +2,36 @@ @section('section') -
-

Blocked Users

-
-
- - @if($users->count() > 0) -
    - @foreach($users as $user) -
  • -
    - {{$user->username}} - - - @csrf - - - - -
    -
  • - @endforeach -
-
- {{$users->links()}} -
- @else -

You are not blocking any accounts.

- @endif +
+
+

+

Blocked Accounts

+
+
+
-@endsection \ No newline at end of file +@if($users->count() > 0) +
+ @foreach($users as $user) +
+
+ {{$user->username}} + +
+ @csrf + + +
+
+
+
+ @endforeach +
+
+ {{$users->links()}} +
+@else +

You are not blocking any accounts.

+@endif + +@endsection diff --git a/resources/views/settings/privacy/domain-blocks.blade.php b/resources/views/settings/privacy/domain-blocks.blade.php new file mode 100644 index 000000000..d93e0b58e --- /dev/null +++ b/resources/views/settings/privacy/domain-blocks.blade.php @@ -0,0 +1,272 @@ +@extends('settings.template-vue') + +@section('section') +
+
+
+

+

Domain Blocks

+
+
+ +

You can block entire domains, this prevents users on that instance from interacting with your content and from you seeing content from that domain on public feeds.

+ +
+ +
+ +
+ +
+
+
+
+ +
+ +
+
+
+ + +
+
+
+
+ + + + + + +
+
+
+ + + +
+
+

You are not blocking any domains.

+
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/resources/views/settings/privacy/muted.blade.php b/resources/views/settings/privacy/muted.blade.php index a13b9b716..152b8d641 100644 --- a/resources/views/settings/privacy/muted.blade.php +++ b/resources/views/settings/privacy/muted.blade.php @@ -1,41 +1,35 @@ @extends('settings.template') @section('section') - -
-

Muted Users

-
-
- - @if($users->count() > 0) -
    +
    +
    +

    +

    Muted Accounts

    +
    +
    +
    +@if($users->count() > 0) +
    @foreach($users as $user) -
  • -
    - {{$user->username}} - -
    - @csrf - - -
    -
    -
    -
  • +
    +
    + {{$user->username}} + +
    + @csrf + + +
    +
    +
    +
    @endforeach -
-
+
+
{{$users->links()}} -
- @else -

You are not muting any accounts.

- @endif +
+@else +

You are not muting any accounts.

+@endif -@endsection \ No newline at end of file +@endsection diff --git a/resources/views/settings/template-vue.blade.php b/resources/views/settings/template-vue.blade.php new file mode 100644 index 000000000..adcbb34c2 --- /dev/null +++ b/resources/views/settings/template-vue.blade.php @@ -0,0 +1,37 @@ +@extends('layouts.app') + +@section('content') +@if (session('status')) +
+ {{ session('status') }} +
+@endif +@if ($errors->any()) +
+ @foreach($errors->all() as $error) +

{{ $error }}

+ @endforeach +
+@endif +@if (session('error')) +
+ {{ session('error') }} +
+@endif + +
+
+
+
+
+ @include('settings.partial.sidebar') +
+ @yield('section') +
+
+
+
+
+
+ +@endsection diff --git a/routes/api.php b/routes/api.php index f305f277c..af40e27bc 100644 --- a/routes/api.php +++ b/routes/api.php @@ -2,6 +2,7 @@ use Illuminate\Http\Request; use App\Http\Middleware\DeprecatedEndpoint; +use App\Http\Controllers\Api\V1\TagsController; $middleware = ['auth:api','validemail']; @@ -50,9 +51,9 @@ Route::group(['prefix' => 'api'], function() use($middleware) { Route::get('blocks', 'Api\ApiV1Controller@accountBlocks')->middleware($middleware); Route::get('conversations', 'Api\ApiV1Controller@conversations')->middleware($middleware); Route::get('custom_emojis', 'Api\ApiV1Controller@customEmojis'); - Route::get('domain_blocks', 'Api\ApiV1Controller@accountDomainBlocks')->middleware($middleware); - Route::post('domain_blocks', 'Api\ApiV1Controller@accountDomainBlocks')->middleware($middleware); - Route::delete('domain_blocks', 'Api\ApiV1Controller@accountDomainBlocks')->middleware($middleware); + Route::get('domain_blocks', 'Api\V1\DomainBlockController@index')->middleware($middleware); + Route::post('domain_blocks', 'Api\V1\DomainBlockController@store')->middleware($middleware); + Route::delete('domain_blocks', 'Api\V1\DomainBlockController@delete')->middleware($middleware); Route::get('endorsements', 'Api\ApiV1Controller@accountEndorsements')->middleware($middleware); Route::get('favourites', 'Api\ApiV1Controller@accountFavourites')->middleware($middleware); Route::get('filters', 'Api\ApiV1Controller@accountFilters')->middleware($middleware); @@ -92,10 +93,11 @@ Route::group(['prefix' => 'api'], function() use($middleware) { Route::get('markers', 'Api\ApiV1Controller@getMarkers')->middleware($middleware); Route::post('markers', 'Api\ApiV1Controller@setMarkers')->middleware($middleware); - Route::get('followed_tags', 'Api\ApiV1Controller@getFollowedTags')->middleware($middleware); - Route::post('tags/{id}/follow', 'Api\ApiV1Controller@followHashtag')->middleware($middleware); - Route::post('tags/{id}/unfollow', 'Api\ApiV1Controller@unfollowHashtag')->middleware($middleware); - Route::get('tags/{id}', 'Api\ApiV1Controller@getHashtag')->middleware($middleware); + Route::get('followed_tags', [TagsController::class, 'getFollowedTags'])->middleware($middleware); + Route::post('tags/{id}/follow', [TagsController::class, 'followHashtag'])->middleware($middleware); + Route::post('tags/{id}/unfollow', [TagsController::class, 'unfollowHashtag'])->middleware($middleware); + Route::get('tags/{id}/related', [TagsController::class, 'relatedTags'])->middleware($middleware); + Route::get('tags/{id}', [TagsController::class, 'getHashtag'])->middleware($middleware); Route::get('statuses/{id}/history', 'StatusEditController@history')->middleware($middleware); Route::put('statuses/{id}', 'StatusEditController@store')->middleware($middleware); @@ -109,12 +111,9 @@ Route::group(['prefix' => 'api'], function() use($middleware) { }); Route::group(['prefix' => 'v1.1'], function() use($middleware) { - $reportMiddleware = $middleware; - $reportMiddleware[] = DeprecatedEndpoint::class; - Route::post('report', 'Api\ApiV1Dot1Controller@report')->middleware($reportMiddleware); + Route::post('report', 'Api\ApiV1Dot1Controller@report')->middleware($middleware); Route::group(['prefix' => 'accounts'], function () use($middleware) { - $middleware[] = DeprecatedEndpoint::class; Route::get('timelines/home', 'Api\ApiV1Controller@timelineHome')->middleware($middleware); Route::delete('avatar', 'Api\ApiV1Dot1Controller@deleteAvatar')->middleware($middleware); Route::get('{id}/posts', 'Api\ApiV1Dot1Controller@accountPosts')->middleware($middleware); @@ -123,10 +122,10 @@ Route::group(['prefix' => 'api'], function() use($middleware) { Route::get('two-factor', 'Api\ApiV1Dot1Controller@accountTwoFactor')->middleware($middleware); Route::get('emails-from-pixelfed', 'Api\ApiV1Dot1Controller@accountEmailsFromPixelfed')->middleware($middleware); Route::get('apps-and-applications', 'Api\ApiV1Dot1Controller@accountApps')->middleware($middleware); + Route::get('mutuals/{id}', 'Api\ApiV1Dot1Controller@getMutualAccounts')->middleware($middleware); }); Route::group(['prefix' => 'collections'], function () use($middleware) { - $middleware[] = DeprecatedEndpoint::class; Route::get('accounts/{id}', 'CollectionController@getUserCollections')->middleware($middleware); Route::get('items/{id}', 'CollectionController@getItems')->middleware($middleware); Route::get('view/{id}', 'CollectionController@getCollection')->middleware($middleware); @@ -137,7 +136,6 @@ Route::group(['prefix' => 'api'], function() use($middleware) { }); Route::group(['prefix' => 'direct'], function () use($middleware) { - $middleware[] = DeprecatedEndpoint::class; Route::get('thread', 'DirectMessageController@thread')->middleware($middleware); Route::post('thread/send', 'DirectMessageController@create')->middleware($middleware); Route::delete('thread/message', 'DirectMessageController@delete')->middleware($middleware); @@ -149,19 +147,16 @@ Route::group(['prefix' => 'api'], function() use($middleware) { }); Route::group(['prefix' => 'archive'], function () use($middleware) { - $middleware[] = DeprecatedEndpoint::class; Route::post('add/{id}', 'Api\ApiV1Dot1Controller@archive')->middleware($middleware); Route::post('remove/{id}', 'Api\ApiV1Dot1Controller@unarchive')->middleware($middleware); Route::get('list', 'Api\ApiV1Dot1Controller@archivedPosts')->middleware($middleware); }); Route::group(['prefix' => 'places'], function () use($middleware) { - $middleware[] = DeprecatedEndpoint::class; Route::get('posts/{id}/{slug}', 'Api\ApiV1Dot1Controller@placesById')->middleware($middleware); }); Route::group(['prefix' => 'stories'], function () use($middleware) { - $middleware[] = DeprecatedEndpoint::class; Route::get('carousel', 'Stories\StoryApiV1Controller@carousel')->middleware($middleware); Route::post('add', 'Stories\StoryApiV1Controller@add')->middleware($middleware); Route::post('publish', 'Stories\StoryApiV1Controller@publish')->middleware($middleware); @@ -171,20 +166,17 @@ Route::group(['prefix' => 'api'], function() use($middleware) { }); Route::group(['prefix' => 'compose'], function () use($middleware) { - $middleware[] = DeprecatedEndpoint::class; Route::get('search/location', 'ComposeController@searchLocation')->middleware($middleware); Route::get('settings', 'ComposeController@composeSettings')->middleware($middleware); }); Route::group(['prefix' => 'discover'], function () use($middleware) { - $middleware[] = DeprecatedEndpoint::class; Route::get('accounts/popular', 'Api\ApiV1Controller@discoverAccountsPopular')->middleware($middleware); Route::get('posts/trending', 'DiscoverController@trendingApi')->middleware($middleware); Route::get('posts/hashtags', 'DiscoverController@trendingHashtags')->middleware($middleware); }); Route::group(['prefix' => 'directory'], function () use($middleware) { - $middleware[] = DeprecatedEndpoint::class; Route::get('listing', 'PixelfedDirectoryController@get'); }); @@ -311,11 +303,13 @@ Route::group(['prefix' => 'api'], function() use($middleware) { Route::group(['prefix' => 'stories'], function () use($middleware) { Route::get('carousel', 'Stories\StoryApiV1Controller@carousel')->middleware($middleware); + Route::get('self-carousel', 'Stories\StoryApiV1Controller@selfCarousel')->middleware($middleware); Route::post('add', 'Stories\StoryApiV1Controller@add')->middleware($middleware); Route::post('publish', 'Stories\StoryApiV1Controller@publish')->middleware($middleware); Route::post('seen', 'Stories\StoryApiV1Controller@viewed')->middleware($middleware); Route::post('self-expire/{id}', 'Stories\StoryApiV1Controller@delete')->middleware($middleware); Route::post('comment', 'Stories\StoryApiV1Controller@comment')->middleware($middleware); + Route::get('viewers', 'Stories\StoryApiV1Controller@viewers')->middleware($middleware); }); }); }); diff --git a/routes/web.php b/routes/web.php index bb091fce5..b8149a605 100644 --- a/routes/web.php +++ b/routes/web.php @@ -96,6 +96,13 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio Route::get('autospam/home', 'AdminController@autospamHome')->name('admin.autospam'); + Route::redirect('asf/', 'asf/home'); + Route::get('asf/home', 'AdminShadowFilterController@home'); + Route::get('asf/create', 'AdminShadowFilterController@create'); + Route::get('asf/edit/{id}', 'AdminShadowFilterController@edit'); + Route::post('asf/edit/{id}', 'AdminShadowFilterController@storeEdit'); + Route::post('asf/create', 'AdminShadowFilterController@store'); + Route::prefix('api')->group(function() { Route::get('stats', 'AdminController@getStats'); Route::get('accounts', 'AdminController@getAccounts'); @@ -482,6 +489,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::post('privacy/muted-users', 'SettingsController@mutedUsersUpdate'); Route::get('privacy/blocked-users', 'SettingsController@blockedUsers')->name('settings.privacy.blocked-users'); Route::post('privacy/blocked-users', 'SettingsController@blockedUsersUpdate'); + Route::get('privacy/domain-blocks', 'SettingsController@domainBlocks')->name('settings.privacy.domain-blocks'); Route::get('privacy/blocked-instances', 'SettingsController@blockedInstances')->name('settings.privacy.blocked-instances'); Route::post('privacy/blocked-instances', 'SettingsController@blockedInstanceStore'); Route::post('privacy/blocked-instances/unblock', 'SettingsController@blockedInstanceUnblock')->name('settings.privacy.blocked-instances.unblock'); diff --git a/tests/Unit/ActivityPubTagObjectTest.php b/tests/Unit/ActivityPubTagObjectTest.php new file mode 100644 index 000000000..f402ff0da --- /dev/null +++ b/tests/Unit/ActivityPubTagObjectTest.php @@ -0,0 +1,133 @@ + [ + "href" => "https://gotosocial.example.org/users/GotosocialUser", + "name" => "@GotosocialUser@gotosocial.example.org", + "type" => "Mention" + ] + ]; + + if(isset($res['tag']['type'], $res['tag']['name'])) { + $res['tag'] = [$res['tag']]; + } + + $tags = collect($res['tag']) + ->filter(function($tag) { + return $tag && + $tag['type'] == 'Mention' && + isset($tag['href']) && + substr($tag['href'], 0, 8) === 'https://'; + }); + $this->assertTrue($tags->count() === 1); + } + + public function test_pixelfed_hashtags(): void + { + $res = [ + "tag" => [ + [ + "type" => "Mention", + "href" => "https://pixelfed.social/dansup", + "name" => "@dansup@pixelfed.social" + ], + [ + "type" => "Hashtag", + "href" => "https://pixelfed.social/discover/tags/dogsofpixelfed", + "name" => "#dogsOfPixelFed" + ], + [ + "type" => "Hashtag", + "href" => "https://pixelfed.social/discover/tags/doggo", + "name" => "#doggo" + ], + [ + "type" => "Hashtag", + "href" => "https://pixelfed.social/discover/tags/dog", + "name" => "#dog" + ], + [ + "type" => "Hashtag", + "href" => "https://pixelfed.social/discover/tags/drake", + "name" => "#drake" + ], + [ + "type" => "Hashtag", + "href" => "https://pixelfed.social/discover/tags/blacklab", + "name" => "#blacklab" + ], + [ + "type" => "Hashtag", + "href" => "https://pixelfed.social/discover/tags/iconic", + "name" => "#Iconic" + ], + [ + "type" => "Hashtag", + "href" => "https://pixelfed.social/discover/tags/majestic", + "name" => "#majestic" + ] + ] + ]; + + if(isset($res['tag']['type'], $res['tag']['name'])) { + $res['tag'] = [$res['tag']]; + } + + $tags = collect($res['tag']) + ->filter(function($tag) { + return $tag && + $tag['type'] == 'Hashtag' && + isset($tag['href']) && + substr($tag['href'], 0, 8) === 'https://'; + }); + $this->assertTrue($tags->count() === 7); + } + + + public function test_pixelfed_mentions(): void + { + $res = [ + "tag" => [ + [ + "type" => "Mention", + "href" => "https://pixelfed.social/dansup", + "name" => "@dansup@pixelfed.social" + ], + [ + "type" => "Hashtag", + "href" => "https://pixelfed.social/discover/tags/dogsofpixelfed", + "name" => "#dogsOfPixelFed" + ], + [ + "type" => "Hashtag", + "href" => "https://pixelfed.social/discover/tags/doggo", + "name" => "#doggo" + ], + ] + ]; + + if(isset($res['tag']['type'], $res['tag']['name'])) { + $res['tag'] = [$res['tag']]; + } + + $tags = collect($res['tag']) + ->filter(function($tag) { + return $tag && + $tag['type'] == 'Mention' && + isset($tag['href']) && + substr($tag['href'], 0, 8) === 'https://'; + }); + $this->assertTrue($tags->count() === 1); + } +} diff --git a/tests/Unit/Lexer/UsernameTest.php b/tests/Unit/Lexer/UsernameTest.php index 0d21b6e00..64875df7e 100644 --- a/tests/Unit/Lexer/UsernameTest.php +++ b/tests/Unit/Lexer/UsernameTest.php @@ -175,4 +175,67 @@ class UsernameTest extends TestCase $this->assertEquals($expectedEntity, $entities); } + /** @test * */ + public function germanUmlatsAutolink() + { + $mentions = "@März and @königin and @Glück"; + $autolink = Autolink::create()->autolink($mentions); + + $expectedAutolink = '@März and @königin and @Glück'; + $this->assertEquals($expectedAutolink, $autolink); + } + + /** @test * */ + public function germanUmlatsExtractor() + { + $mentions = "@März and @königin and @Glück"; + $entities = Extractor::create()->extract($mentions); + + $expectedEntity = [ + "hashtags" => [], + "urls" => [], + "mentions" => [ + "märz", + "königin", + "glück", + ], + "replyto" => null, + "hashtags_with_indices" => [], + "urls_with_indices" => [], + "mentions_with_indices" => [ + [ + "screen_name" => "März", + "indices" => [ + 0, + 5, + ], + ], + [ + "screen_name" => "königin", + "indices" => [ + 10, + 18, + ], + ], + [ + "screen_name" => "Glück", + "indices" => [ + 23, + 29, + ], + ], + ], + ]; + $this->assertEquals($expectedEntity, $entities); + } + + /** @test * */ + public function germanUmlatsWebfingerAutolink() + { + $mentions = "hello @märz@example.org!"; + $autolink = Autolink::create()->autolink($mentions); + + $expectedAutolink = 'hello @märz@example.org!'; + $this->assertEquals($expectedAutolink, $autolink); + } }