1
0
Fork 0

Compare commits

...

58 Commits

Author SHA1 Message Date
chris e65a182dd6 Link legal notice
local jurisdiction requires a prominent link to a legal notice at the frontpage.
2023-10-25 11:39:53 +02:00
Daniel Supernault e76018167e Update changelog 2023-10-25 11:39:53 +02:00
Daniel Supernault 24d78698fd Update hls pipeline, improve version check 2023-10-25 11:39:53 +02:00
Daniel Supernault c94ed89942 Update ffmpeg config 2023-10-25 11:39:53 +02:00
Daniel Supernault 00abc93b59 Update compiled assets 2023-10-25 11:39:53 +02:00
Daniel Supernault 3b1dcc57af Update npm deps 2023-10-25 11:39:53 +02:00
Daniel Supernault 92e744fe70 Update PostContent, add new video-player component 2023-10-25 11:39:53 +02:00
Daniel Supernault 36e1eab8d1 Add hls/p2p video player 2023-10-25 11:39:53 +02:00
Daniel Supernault 79efb61c3e Update Config util, add hls attributes 2023-10-25 11:39:53 +02:00
Daniel Supernault a14657b9f6 Update MediaService, remove hls_manifest attribute for MastoAPI entities 2023-10-25 11:39:53 +02:00
Daniel Supernault 5d2f5ac378 Update VideoThumbnail job, dispatch HLS job when applicable 2023-10-25 11:39:53 +02:00
Daniel Supernault dbcc602292 Update VideoPipeline, add VideoHlsPipeline job for HLS generation 2023-10-25 11:39:53 +02:00
Daniel Supernault cc599b034f Update MediaDeletePipeline, handle HLS deletion 2023-10-25 11:39:53 +02:00
Daniel Supernault a478920917 Update MediaTransformer, add hls_manifest attribute 2023-10-25 11:39:53 +02:00
Daniel Supernault 19c57dd681 Add js debounce util 2023-10-25 11:39:53 +02:00
Daniel Supernault d6d45fa425 Add RegisterForm component 2023-10-25 11:39:53 +02:00
Daniel Supernault fe057a5c18 Add MediaHlsService 2023-10-25 11:39:53 +02:00
Daniel Supernault 362eab9c49 Update npm deps, add webp2p libs. Thanks @peertube <3 2023-10-25 11:39:53 +02:00
Daniel Supernault 1431b505a8 Add WebP2P support for Video 2023-10-25 11:39:53 +02:00
Daniel Supernault ef8f3460cc Update ComposeModal component, fix multi filter bug and allow media re-ordering before upload/posting 2023-10-25 11:39:53 +02:00
Daniel Supernault 5abb10e88c Update StatusTransformer 2023-10-25 11:39:53 +02:00
Daniel Supernault 98abaeca3c Update StatusTransformer, generate autolink on request 2023-10-25 11:39:53 +02:00
Daniel Supernault 0eedc4bcc1 Update changelog 2023-10-25 11:39:53 +02:00
Daniel Supernault a10670bfc7 Update lexer regex, fix mention regex and add more tests 2023-10-25 11:39:53 +02:00
Daniel Supernault 84a6431481 Update nodeinfo 2023-10-25 11:39:53 +02:00
Daniel Supernault 14ecf866c7 Update RemoteStatusDelete, fix include 2023-10-25 11:39:53 +02:00
Daniel Supernault febb42d21f Update IncrementPostCount pipeline 2023-10-25 11:39:53 +02:00
Daniel Supernault e40b1f96f3 Update RemoteStatusDelete pipeline 2023-10-25 11:39:53 +02:00
Daniel Supernault 902bfa70ae Update RemoteStatusDelete and DecrementPostCount pipelines 2023-10-25 11:39:53 +02:00
Daniel Supernault 41fd6afc26 Update changelog 2023-10-25 11:39:53 +02:00
Daniel Supernault 7d4e1b955c Add `avatar:storage-deep-clean` command to dispatch avatar storage cleanup jobs 2023-10-25 11:39:53 +02:00
Daniel Supernault 71bb6b0773 Update CreateAvatar job, add processing constraints and set is_remote attribute 2023-10-25 11:39:53 +02:00
Daniel Supernault 1ef10cb579 Update changelog 2023-10-25 11:39:53 +02:00
Daniel Supernault 888b7e5cd7 Update AvatarPipeline, improve refresh logic and garbage collection to purge old avatars 2023-10-25 11:39:53 +02:00
Daniel Supernault c55b9a08dd Update AP helpers, adjust RemoteAvatarFetch ttl from 24h to 3 months 2023-10-25 11:39:53 +02:00
Daniel Supernault d119e2577c Update user:admin command, improve logic. Fixes #2465 2023-10-25 11:39:53 +02:00
Daniel Supernault 75c5e006f8 Update changelog 2023-10-25 11:39:53 +02:00
Daniel Supernault 109a20cb9f Add user:2fa command to easily disable 2FA for given account 2023-10-25 11:39:53 +02:00
Daniel Supernault 085159e575 Update NotificationService, handle empty epoch. Fixes #4689 2023-10-25 11:39:53 +02:00
Daniel Supernault b4a3c28844 Update AdminReportController, add `profile_id` to group by. Fixes #4685 2023-10-25 11:39:53 +02:00
Daniel Supernault 377ddc7bb0 Update ApiV1Controller, hydrate reblog interactions. Fixes #4686 2023-10-25 11:39:53 +02:00
Daniel Supernault dcf0c9f371 Update profile embeds, filter sensitive posts 2023-10-25 11:39:53 +02:00
Daniel Supernault 6cb17688c2 Update changelog 2023-10-25 11:39:53 +02:00
Daniel Supernault 9af21bc98c Update Sign-in with Mastodon, allow usage when registrations are closed 2023-10-25 11:39:53 +02:00
Daniel Supernault bc4dd18759 Update changelog 2023-10-25 11:39:53 +02:00
Daniel Supernault 32e9011788 Update ap helpers, store media attachment width and height if present 2023-10-25 11:39:53 +02:00
Daniel Supernault b19f1e7e07 Update Note and CreateNote transformers, include attachment blurhash, width and height 2023-10-25 11:39:53 +02:00
Daniel Supernault 98da70936f Update StatusTagsPipeline, fix object tags slug query 2023-10-25 11:39:53 +02:00
Daniel Supernault 5934e88d76 Update StatusTagsPipeline, fix object tags slug query 2023-10-25 11:39:53 +02:00
Daniel Supernault 5776887ca8 Update changelog 2023-10-25 11:39:52 +02:00
Daniel Supernault 98e882381e Update StatusTagsPipeline, fix object tags and slug normalization 2023-10-25 11:39:52 +02:00
Daniel Supernault 57f38c65b6 Update Status model, allow unlisted thumbnails 2023-10-25 11:39:52 +02:00
Daniel Supernault 4c9ee7a71e Update changelog 2023-10-25 11:39:52 +02:00
Daniel Supernault 834469219b Update Status model, improve thumb logic 2023-10-25 11:39:52 +02:00
Daniel Supernault 9876f55e82 Update changelog 2023-10-25 11:39:52 +02:00
Daniel Supernault 32f94e13e5 Add Resilient Media Storage 2023-10-25 11:39:52 +02:00
Daniel Supernault b91f3e997c Update changelog 2023-10-25 11:39:52 +02:00
Daniel Supernault 435deea3c4 Update profile embed, fix resize 2023-10-25 11:39:52 +02:00
123 changed files with 4336 additions and 1274 deletions

View File

@ -2,6 +2,12 @@
## [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))
### 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))
@ -13,6 +19,24 @@
- 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))
- ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.11.9 (2023-08-21)](https://github.com/pixelfed/pixelfed/compare/v0.11.8...v0.11.9)

View File

@ -0,0 +1,115 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Cache;
use Storage;
use App\Avatar;
use App\Jobs\AvatarPipeline\AvatarStorageCleanup;
class AvatarStorageDeepClean extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'avatar:storage-deep-clean';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Cleanup avatar storage';
protected $shouldKeepRunning = true;
protected $counter = 0;
/**
* Execute the console command.
*/
public function handle(): void
{
$this->info(' ____ _ ______ __ ');
$this->info(' / __ \(_) _____ / / __/__ ____/ / ');
$this->info(' / /_/ / / |/_/ _ \/ / /_/ _ \/ __ / ');
$this->info(' / ____/ /> </ __/ / __/ __/ /_/ / ');
$this->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);
}
}

View File

@ -3,16 +3,17 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\PromptsForMissingInput;
use App\User;
class UserAdmin extends Command
class UserAdmin extends Command implements PromptsForMissingInput
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'user:admin {id}';
protected $signature = 'user:admin {username}';
/**
* The console command description.
@ -22,13 +23,15 @@ class UserAdmin extends Command
protected $description = 'Make a user an admin, or remove admin privileges.';
/**
* Create a new command instance.
* Prompt for missing input arguments using the returned questions.
*
* @return void
* @return array
*/
public function __construct()
protected function promptForMissingArgumentsUsing()
{
parent::__construct();
return [
'username' => '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);

View File

@ -0,0 +1,61 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\PromptsForMissingInput;
use App\User;
class UserToggle2FA extends Command implements PromptsForMissingInput
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'user:2fa {username}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Disable two factor authentication for given username';
/**
* Prompt for missing input arguments using the returned questions.
*
* @return array
*/
protected function promptForMissingArgumentsUsing()
{
return [
'username' => '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!');
}
}

View File

@ -643,7 +643,7 @@ trait AdminReportController
$q->whereNull('admin_seen') :
$q->whereNotNull('admin_seen');
})
->groupBy(['id', 'object_id', 'object_type'])
->groupBy(['id', 'object_id', 'object_type', 'profile_id'])
->cursorPaginate(6)
->withQueryString()
);

View File

@ -2507,7 +2507,7 @@ class ApiV1Controller extends Controller
{
abort_if(!$request->user(), 403);
$user = $request->user();
$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'])) {
@ -2517,17 +2517,23 @@ class ApiV1Controller extends Controller
$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);
if(intval($res['account']['id']) !== intval($pid)) {
abort_unless(FollowerService::follows($pid, $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']);
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);
}

View File

@ -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' => [

View File

@ -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);

View File

@ -0,0 +1,67 @@
<?php
namespace App\Jobs\AvatarPipeline;
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\Services\AvatarService;
use App\Avatar;
class AvatarStorageCleanup implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $avatar;
public $tries = 3;
public $maxExceptions = 3;
public $timeout = 900;
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 'avatar:storage:cleanup:' . $this->avatar->profile_id;
}
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
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;
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace App\Jobs\AvatarPipeline;
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\Services\AvatarService;
use App\Avatar;
use Illuminate\Support\Str;
class AvatarStorageLargePurge implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $avatar;
public $tries = 3;
public $maxExceptions = 3;
public $timeout = 900;
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 'avatar:storage:lg-purge:' . $this->avatar->profile_id;
}
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
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;
}
}

View File

@ -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<int, object>
*/
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()
]
);
}
}

View File

@ -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;
}

View File

@ -89,7 +89,6 @@ class RemoteAvatarFetchFromUrl implements ShouldQueue
$avatar->save();
}
MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false, true);
return 1;

View File

@ -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<int, object>
*/
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;
}
}

View File

@ -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;
}

View File

@ -43,17 +43,10 @@ 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);
return 1;
}

View File

@ -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,9 @@ use App\Services\AccountService;
use App\Services\CollectionService;
use App\Services\StatusService;
use App\Jobs\MediaPipeline\MediaDeletePipeline;
use App\Jobs\ProfilePipeline\DecrementPostCount;
class RemoteStatusDelete implements ShouldQueue
class RemoteStatusDelete implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
@ -51,9 +54,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<int, object>
*/
public function middleware(): array
{
return [(new WithoutOverlapping("status-remote-delete-{$this->status->id}"))->shared()->dontRelease()];
}
/**
* Create a new job instance.
@ -62,7 +91,7 @@ class RemoteStatusDelete implements ShouldQueue
*/
public function __construct(Status $status)
{
$this->status = $status;
$this->status = $status->withoutRelations();
}
/**
@ -77,14 +106,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);
}

View File

@ -45,6 +45,11 @@ class StatusTagsPipeline implements ShouldQueue
{
$res = $this->activity;
$status = $this->status;
if(isset($res['tag']['type'], $res['tag']['name'])) {
$res['tag'] = [$res['tag']];
}
$tags = collect($res['tag']);
// Emoji
@ -73,19 +78,18 @@ class StatusTagsPipeline implements ShouldQueue
if(config('database.default') === 'pgsql') {
$hashtag = Hashtag::where('name', 'ilike', $name)
->orWhere('slug', 'ilike', str_slug($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)
], [
$hashtag = Hashtag::updateOrCreate([
'slug' => str_slug($name, '-', false),
'name' => $name
]);
}

View File

@ -0,0 +1,109 @@
<?php
namespace App\Jobs\VideoPipeline;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use FFMpeg\Format\Video\X264;
use FFMpeg;
use Cache;
use App\Services\MediaService;
use App\Services\StatusService;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
class VideoHlsPipeline 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-hls:id-' . $this->media->id;
}
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
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;
}
}

View File

@ -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<int, object>
*/
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) {
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -0,0 +1,27 @@
<?php
namespace App\Services\Media;
use Storage;
class MediaHlsService
{
public static function allFiles($media)
{
$path = $media->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();
}
}

View File

@ -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';

View File

@ -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);
}

View File

@ -49,6 +49,9 @@ class NotificationService {
public static function getEpochId($months = 6)
{
return Cache::remember(self::EPOCH_CACHE_KEY . $months, 1209600, function() use($months) {
if(Notification::count() === 0) {
return 0;
}
return Notification::where('created_at', '>', now()->subMonths($months))->first()->id;
});
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Services;
use Storage;
use Illuminate\Http\File;
use Exception;
use GuzzleHttp\Exception\ClientException;
use Aws\S3\Exception\S3Exception;
use GuzzleHttp\Exception\ConnectException;
use League\Flysystem\UnableToWriteFile;
class ResilientMediaStorageService
{
static $attempts = 0;
public static function store($storagePath, $path, $name)
{
return (bool) config_cache('pixelfed.cloud_storage') && (bool) config('media.storage.remote.resilient_mode') ?
self::handleResilientStore($storagePath, $path, $name) :
self::handleStore($storagePath, $path, $name);
}
public static function handleStore($storagePath, $path, $name)
{
return retry(3, function() use($storagePath, $path, $name) {
$baseDisk = (bool) config_cache('pixelfed.cloud_storage') ? config('filesystems.cloud') : 'local';
$disk = Storage::disk($baseDisk);
$file = $disk->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];
}
}

View File

@ -161,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);

View File

@ -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)

View File

@ -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,

View File

@ -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,

View File

@ -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' => [

View File

@ -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),

View File

@ -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),

View File

@ -108,7 +108,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;
@ -684,6 +687,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 +700,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;
}
@ -790,7 +801,7 @@ class Helpers {
);
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);
}

View File

@ -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';

View File

@ -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);
}
}

View File

@ -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();
});
}
}

View File

@ -41,4 +41,6 @@ return [
// Post Update/Edits
'pue' => env('EXP_PUE', true),
'autolink' => env('EXP_AUTOLINK_V2', false),
];

View File

@ -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'),

View File

@ -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'),
];

View File

@ -1,24 +1,60 @@
<?php
return [
'delete_local_after_cloud' => 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'),
]
];

View File

@ -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' => [
/*

2081
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/admin.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/direct.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
"use strict";(self.webpackChunkpixelfed=self.webpackChunkpixelfed||[]).push([[4028],{58947:(a,t,e)=>{e.r(t),e.d(t,{default:()=>s});const s={components:{drawer:e(42755).default}}},89250:(a,t,e)=>{e.r(t),e.d(t,{default:()=>s});const s={data:function(){return{user:window._sharedData.user}}}},56943:(a,t,e)=>{e.r(t),e.d(t,{render:()=>s,staticRenderFns:()=>n});var s=function(){var a=this,t=a._self._c;return t("div",{staticClass:"container d-flex justify-content-center"},[a._m(0),a._v(" "),t("drawer")],1)},n=[function(){var a=this,t=a._self._c;return t("div",{staticClass:"error-page py-5 my-5",staticStyle:{"max-width":"450px"}},[t("h3",{staticClass:"font-weight-bold"},[a._v("404   Page Not Found")]),a._v(" "),t("p",{staticClass:"lead"},[a._v("The page you are trying to view is not available")]),a._v(" "),t("div",{staticClass:"text-muted"},[t("p",{staticClass:"mb-1"},[a._v("This can happen for a few reasons:")]),a._v(" "),t("ul",[t("li",[a._v("The url is invalid or has a typo")]),a._v(" "),t("li",[a._v("The page has been flagged for review by our automated abuse detection systems")]),a._v(" "),t("li",[a._v("The content may have been deleted")]),a._v(" "),t("li",[a._v("You do not have permission to view this content")])])])])}]},7231:(a,t,e)=>{e.r(t),e.d(t,{render:()=>s,staticRenderFns:()=>n});var s=function(){var a=this,t=a._self._c;return t("div",{staticClass:"app-drawer-component"},[t("div",{staticClass:"mobile-footer-spacer d-block d-sm-none mt-5"}),a._v(" "),t("div",{staticClass:"mobile-footer d-block d-sm-none fixed-bottom"},[t("div",{staticClass:"card card-body rounded-0 px-0 pt-2 pb-3 box-shadow",staticStyle:{"border-top":"1px solid var(--border-color)"}},[t("ul",{staticClass:"nav nav-pills nav-fill d-flex align-items-middle"},[t("li",{staticClass:"nav-item"},[t("router-link",{staticClass:"nav-link text-dark",attrs:{to:"/i/web"}},[t("p",[t("i",{staticClass:"far fa-home fa-lg"})]),a._v(" "),t("p",{staticClass:"nav-link-label"},[t("span",[a._v("Home")])])])],1),a._v(" "),t("li",{staticClass:"nav-item"},[t("router-link",{staticClass:"nav-link text-dark",attrs:{to:"/i/web/timeline/local"}},[t("p",[t("i",{staticClass:"far fa-stream fa-lg"})]),a._v(" "),t("p",{staticClass:"nav-link-label"},[t("span",[a._v("Local")])])])],1),a._v(" "),t("li",{staticClass:"nav-item"},[t("router-link",{staticClass:"nav-link text-dark",attrs:{to:"/i/web/compose"}},[t("p",[t("i",{staticClass:"far fa-plus-circle fa-lg"})]),a._v(" "),t("p",{staticClass:"nav-link-label"},[t("span",[a._v("New")])])])],1),a._v(" "),t("li",{staticClass:"nav-item"},[t("router-link",{staticClass:"nav-link text-dark",attrs:{to:"/i/web/notifications"}},[t("p",[t("i",{staticClass:"far fa-bell fa-lg"})]),a._v(" "),t("p",{staticClass:"nav-link-label"},[t("span",[a._v("Alerts")])])])],1),a._v(" "),t("li",{staticClass:"nav-item"},[t("router-link",{staticClass:"nav-link text-dark",attrs:{to:"/i/web/profile/"+a.user.id}},[t("p",[t("i",{staticClass:"far fa-user fa-lg"})]),a._v(" "),t("p",{staticClass:"nav-link-label"},[t("span",[a._v("Profile")])])])],1)])])])])},n=[]},95433:(a,t,e)=>{e.r(t),e.d(t,{default:()=>l});var s=e(1519),n=e.n(s)()((function(a){return a[1]}));n.push([a.id,".app-drawer-component .nav-link{padding:.5rem .1rem}.app-drawer-component .nav-link.active{background-color:transparent}.app-drawer-component .nav-link.router-link-exact-active{background-color:transparent;color:var(--primary)!important}.app-drawer-component .nav-link p{margin-bottom:0}.app-drawer-component .nav-link-label{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:10px;font-weight:700;margin-top:0;opacity:.6;text-transform:uppercase}",""]);const l=n},2869:(a,t,e)=>{e.r(t),e.d(t,{default:()=>r});var s=e(93379),n=e.n(s),l=e(95433),i={insert:"head",singleton:!1};n()(l.default,i);const r=l.default.locals||{}},42390:(a,t,e)=>{e.r(t),e.d(t,{default:()=>i});var s=e(33500),n=e(29414),l={};for(const a in n)"default"!==a&&(l[a]=()=>n[a]);e.d(t,l);const i=(0,e(51900).default)(n.default,s.render,s.staticRenderFns,!1,null,null,null).exports},42755:(a,t,e)=>{e.r(t),e.d(t,{default:()=>i});var s=e(87661),n=e(1374),l={};for(const a in n)"default"!==a&&(l[a]=()=>n[a]);e.d(t,l);e(72682);const i=(0,e(51900).default)(n.default,s.render,s.staticRenderFns,!1,null,null,null).exports},29414:(a,t,e)=>{e.r(t),e.d(t,{default:()=>l});var s=e(58947),n={};for(const a in s)"default"!==a&&(n[a]=()=>s[a]);e.d(t,n);const l=s.default},1374:(a,t,e)=>{e.r(t),e.d(t,{default:()=>l});var s=e(89250),n={};for(const a in s)"default"!==a&&(n[a]=()=>s[a]);e.d(t,n);const l=s.default},33500:(a,t,e)=>{e.r(t);var s=e(56943),n={};for(const a in s)"default"!==a&&(n[a]=()=>s[a]);e.d(t,n)},87661:(a,t,e)=>{e.r(t);var s=e(7231),n={};for(const a in s)"default"!==a&&(n[a]=()=>s[a]);e.d(t,n)},72682:(a,t,e)=>{e.r(t);var s=e(2869),n={};for(const a in s)"default"!==a&&(n[a]=()=>s[a]);e.d(t,n)}}]);

View File

@ -0,0 +1 @@
"use strict";(self.webpackChunkpixelfed=self.webpackChunkpixelfed||[]).push([[4028],{62092:(a,t,e)=>{e.r(t),e.d(t,{default:()=>s});const s={components:{drawer:e(42755).default}}},14287:(a,t,e)=>{e.r(t),e.d(t,{default:()=>s});const s={data:function(){return{user:window._sharedData.user}}}},60288:(a,t,e)=>{e.r(t),e.d(t,{render:()=>s,staticRenderFns:()=>n});var s=function(){var a=this,t=a._self._c;return t("div",{staticClass:"container d-flex justify-content-center"},[a._m(0),a._v(" "),t("drawer")],1)},n=[function(){var a=this,t=a._self._c;return t("div",{staticClass:"error-page py-5 my-5",staticStyle:{"max-width":"450px"}},[t("h3",{staticClass:"font-weight-bold"},[a._v("404   Page Not Found")]),a._v(" "),t("p",{staticClass:"lead"},[a._v("The page you are trying to view is not available")]),a._v(" "),t("div",{staticClass:"text-muted"},[t("p",{staticClass:"mb-1"},[a._v("This can happen for a few reasons:")]),a._v(" "),t("ul",[t("li",[a._v("The url is invalid or has a typo")]),a._v(" "),t("li",[a._v("The page has been flagged for review by our automated abuse detection systems")]),a._v(" "),t("li",[a._v("The content may have been deleted")]),a._v(" "),t("li",[a._v("You do not have permission to view this content")])])])])}]},69356:(a,t,e)=>{e.r(t),e.d(t,{render:()=>s,staticRenderFns:()=>n});var s=function(){var a=this,t=a._self._c;return t("div",{staticClass:"app-drawer-component"},[t("div",{staticClass:"mobile-footer-spacer d-block d-sm-none mt-5"}),a._v(" "),t("div",{staticClass:"mobile-footer d-block d-sm-none fixed-bottom"},[t("div",{staticClass:"card card-body rounded-0 px-0 pt-2 pb-3 box-shadow",staticStyle:{"border-top":"1px solid var(--border-color)"}},[t("ul",{staticClass:"nav nav-pills nav-fill d-flex align-items-middle"},[t("li",{staticClass:"nav-item"},[t("router-link",{staticClass:"nav-link text-dark",attrs:{to:"/i/web"}},[t("p",[t("i",{staticClass:"far fa-home fa-lg"})]),a._v(" "),t("p",{staticClass:"nav-link-label"},[t("span",[a._v("Home")])])])],1),a._v(" "),t("li",{staticClass:"nav-item"},[t("router-link",{staticClass:"nav-link text-dark",attrs:{to:"/i/web/timeline/local"}},[t("p",[t("i",{staticClass:"far fa-stream fa-lg"})]),a._v(" "),t("p",{staticClass:"nav-link-label"},[t("span",[a._v("Local")])])])],1),a._v(" "),t("li",{staticClass:"nav-item"},[t("router-link",{staticClass:"nav-link text-dark",attrs:{to:"/i/web/compose"}},[t("p",[t("i",{staticClass:"far fa-plus-circle fa-lg"})]),a._v(" "),t("p",{staticClass:"nav-link-label"},[t("span",[a._v("New")])])])],1),a._v(" "),t("li",{staticClass:"nav-item"},[t("router-link",{staticClass:"nav-link text-dark",attrs:{to:"/i/web/notifications"}},[t("p",[t("i",{staticClass:"far fa-bell fa-lg"})]),a._v(" "),t("p",{staticClass:"nav-link-label"},[t("span",[a._v("Alerts")])])])],1),a._v(" "),t("li",{staticClass:"nav-item"},[t("router-link",{staticClass:"nav-link text-dark",attrs:{to:"/i/web/profile/"+a.user.id}},[t("p",[t("i",{staticClass:"far fa-user fa-lg"})]),a._v(" "),t("p",{staticClass:"nav-link-label"},[t("span",[a._v("Profile")])])])],1)])])])])},n=[]},62869:(a,t,e)=>{e.r(t),e.d(t,{default:()=>l});var s=e(1519),n=e.n(s)()((function(a){return a[1]}));n.push([a.id,".app-drawer-component .nav-link{padding:.5rem .1rem}.app-drawer-component .nav-link.active{background-color:transparent}.app-drawer-component .nav-link.router-link-exact-active{background-color:transparent;color:var(--primary)!important}.app-drawer-component .nav-link p{margin-bottom:0}.app-drawer-component .nav-link-label{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:10px;font-weight:700;margin-top:0;opacity:.6;text-transform:uppercase}",""]);const l=n},40014:(a,t,e)=>{e.r(t),e.d(t,{default:()=>r});var s=e(93379),n=e.n(s),l=e(62869),i={insert:"head",singleton:!1};n()(l.default,i);const r=l.default.locals||{}},42390:(a,t,e)=>{e.r(t),e.d(t,{default:()=>i});var s=e(94761),n=e(29210),l={};for(const a in n)"default"!==a&&(l[a]=()=>n[a]);e.d(t,l);const i=(0,e(51900).default)(n.default,s.render,s.staticRenderFns,!1,null,null,null).exports},42755:(a,t,e)=>{e.r(t),e.d(t,{default:()=>i});var s=e(73307),n=e(6380),l={};for(const a in n)"default"!==a&&(l[a]=()=>n[a]);e.d(t,l);e(10973);const i=(0,e(51900).default)(n.default,s.render,s.staticRenderFns,!1,null,null,null).exports},29210:(a,t,e)=>{e.r(t),e.d(t,{default:()=>l});var s=e(62092),n={};for(const a in s)"default"!==a&&(n[a]=()=>s[a]);e.d(t,n);const l=s.default},6380:(a,t,e)=>{e.r(t),e.d(t,{default:()=>l});var s=e(14287),n={};for(const a in s)"default"!==a&&(n[a]=()=>s[a]);e.d(t,n);const l=s.default},94761:(a,t,e)=>{e.r(t);var s=e(60288),n={};for(const a in s)"default"!==a&&(n[a]=()=>s[a]);e.d(t,n)},73307:(a,t,e)=>{e.r(t);var s=e(69356),n={};for(const a in s)"default"!==a&&(n[a]=()=>s[a]);e.d(t,n)},10973:(a,t,e)=>{e.r(t);var s=e(40014),n={};for(const a in s)"default"!==a&&(n[a]=()=>s[a]);e.d(t,n)}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(()=>{"use strict";var e,r,n,o={},t={};function a(e){var r=t[e];if(void 0!==r)return r.exports;var n=t[e]={id:e,loaded:!1,exports:{}};return o[e].call(n.exports,n,n.exports,a),n.loaded=!0,n.exports}a.m=o,e=[],a.O=(r,n,o,t)=>{if(!n){var d=1/0;for(f=0;f<e.length;f++){for(var[n,o,t]=e[f],c=!0,i=0;i<n.length;i++)(!1&t||d>=t)&&Object.keys(a.O).every((e=>a.O[e](n[i])))?n.splice(i--,1):(c=!1,t<d&&(d=t));if(c){e.splice(f--,1);var s=o();void 0!==s&&(r=s)}}return r}t=t||0;for(var f=e.length;f>0&&e[f-1][2]>t;f--)e[f]=e[f-1];e[f]=[n,o,t]},a.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return a.d(r,{a:r}),r},a.d=(e,r)=>{for(var n in r)a.o(r,n)&&!a.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},a.f={},a.e=e=>Promise.all(Object.keys(a.f).reduce(((r,n)=>(a.f[n](e,r),r)),[])),a.u=e=>"js/"+{1084:"profile~followers.bundle",2470:"home.chunk",2530:"discover~myhashtags.chunk",2586:"compose.chunk",2732:"dms~message.chunk",3351:"discover~settings.chunk",3365:"dms.chunk",3623:"discover~findfriends.chunk",4028:"error404.bundle",4958:"discover.chunk",4965:"discover~memories.chunk",5865:"post.chunk",6053:"notifications.chunk",6869:"profile.chunk",7019:"discover~hashtag.bundle",8250:"i18n.bundle",8517:"daci.chunk",8600:"changelog.bundle",8625:"profile~following.bundle",8900:"discover~serverfeed.chunk"}[e]+"."+{1084:"f088062414c3b014",2470:"bd623a430a5584c2",2530:"ee5af357937cad2f",2586:"6464688bf5b5ef97",2732:"990c68dfc266b0cf",3351:"909aa0316f43235e",3365:"98e12cf9137ddd87",3623:"6bd4ddbabd979778",4028:"182d0aaa2da9ed23",4958:"56d2d8cfbbecc761",4965:"400f9f019bdb9fdf",5865:"729ca668f46545cb",6053:"bf0c641eb1fd9cde",6869:"029572d9018fc65f",7019:"54f2ac43c55bf328",8250:"4a5ff18de549ac4e",8517:"bfa9e4f459fec835",8600:"c4c82057f9628c72",8625:"57cbb89efa73e324",8900:"fbe31eedcdafc87e"}[e]+".js",a.miniCssF=e=>({138:"css/spa",703:"css/admin",1242:"css/appdark",6170:"css/app",8737:"css/portfolio",9994:"css/landing"}[e]+".css"),a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),a.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},n="pixelfed:",a.l=(e,o,t,d)=>{if(r[e])r[e].push(o);else{var c,i;if(void 0!==t)for(var s=document.getElementsByTagName("script"),f=0;f<s.length;f++){var l=s[f];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==n+t){c=l;break}}c||(i=!0,(c=document.createElement("script")).charset="utf-8",c.timeout=120,a.nc&&c.setAttribute("nonce",a.nc),c.setAttribute("data-webpack",n+t),c.src=e),r[e]=[o];var u=(n,o)=>{c.onerror=c.onload=null,clearTimeout(b);var t=r[e];if(delete r[e],c.parentNode&&c.parentNode.removeChild(c),t&&t.forEach((e=>e(o))),n)return n(o)},b=setTimeout(u.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=u.bind(null,c.onerror),c.onload=u.bind(null,c.onload),i&&document.head.appendChild(c)}},a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),a.p="/",(()=>{var e={8929:0,1242:0,6170:0,8737:0,703:0,9994:0,138:0};a.f.j=(r,n)=>{var o=a.o(e,r)?e[r]:void 0;if(0!==o)if(o)n.push(o[2]);else if(/^(1242|138|6170|703|8737|8929|9994)$/.test(r))e[r]=0;else{var t=new Promise(((n,t)=>o=e[r]=[n,t]));n.push(o[2]=t);var d=a.p+a.u(r),c=new Error;a.l(d,(n=>{if(a.o(e,r)&&(0!==(o=e[r])&&(e[r]=void 0),o)){var t=n&&("load"===n.type?"missing":n.type),d=n&&n.target&&n.target.src;c.message="Loading chunk "+r+" failed.\n("+t+": "+d+")",c.name="ChunkLoadError",c.type=t,c.request=d,o[1](c)}}),"chunk-"+r,r)}},a.O.j=r=>0===e[r];var r=(r,n)=>{var o,t,[d,c,i]=n,s=0;if(d.some((r=>0!==e[r]))){for(o in c)a.o(c,o)&&(a.m[o]=c[o]);if(i)var f=i(a)}for(r&&r(n);s<d.length;s++)t=d[s],a.o(e,t)&&e[t]&&e[t][0](),e[t]=0;return a.O(f)},n=self.webpackChunkpixelfed=self.webpackChunkpixelfed||[];n.forEach(r.bind(null,0)),n.push=r.bind(null,n.push.bind(n))})(),a.nc=void 0})();
(()=>{"use strict";var e,r,n,o={},t={};function d(e){var r=t[e];if(void 0!==r)return r.exports;var n=t[e]={id:e,loaded:!1,exports:{}};return o[e].call(n.exports,n,n.exports,d),n.loaded=!0,n.exports}d.m=o,e=[],d.O=(r,n,o,t)=>{if(!n){var a=1/0;for(l=0;l<e.length;l++){for(var[n,o,t]=e[l],i=!0,c=0;c<n.length;c++)(!1&t||a>=t)&&Object.keys(d.O).every((e=>d.O[e](n[c])))?n.splice(c--,1):(i=!1,t<a&&(a=t));if(i){e.splice(l--,1);var s=o();void 0!==s&&(r=s)}}return r}t=t||0;for(var l=e.length;l>0&&e[l-1][2]>t;l--)e[l]=e[l-1];e[l]=[n,o,t]},d.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return d.d(r,{a:r}),r},d.d=(e,r)=>{for(var n in r)d.o(r,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},d.f={},d.e=e=>Promise.all(Object.keys(d.f).reduce(((r,n)=>(d.f[n](e,r),r)),[])),d.u=e=>"js/"+{1084:"profile~followers.bundle",2470:"home.chunk",2530:"discover~myhashtags.chunk",2586:"compose.chunk",2732:"dms~message.chunk",3351:"discover~settings.chunk",3365:"dms.chunk",3623:"discover~findfriends.chunk",4028:"error404.bundle",4958:"discover.chunk",4965:"discover~memories.chunk",5865:"post.chunk",6053:"notifications.chunk",6869:"profile.chunk",7019:"discover~hashtag.bundle",8250:"i18n.bundle",8517:"daci.chunk",8600:"changelog.bundle",8625:"profile~following.bundle",8900:"discover~serverfeed.chunk"}[e]+"."+{1084:"731f680cfb96563d",2470:"351f55e9d09b6482",2530:"6eab2414b2b16e19",2586:"965eab35620423e5",2732:"15157ff4a6c17cc7",3351:"732c1f76a00d9204",3365:"53a951c5de2d95ac",3623:"02be60ab26503531",4028:"3bbc118159460db6",4958:"9606885dad3c8a99",4965:"ce9cc6446020e9b3",5865:"74f8b1d1954f5d01",6053:"3b92cf46da469de1",6869:"0e5bd852054d6355",7019:"9cfffc517f35044e",8250:"47cbf9f04d955267",8517:"b17a0b11877389d7",8600:"742a06ba0a547120",8625:"3d95796c9f1678dd",8900:"0f2dcc473fdce17e"}[e]+".js",d.miniCssF=e=>({138:"css/spa",703:"css/admin",1242:"css/appdark",6170:"css/app",8737:"css/portfolio",9994:"css/landing"}[e]+".css"),d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},n="pixelfed:",d.l=(e,o,t,a)=>{if(r[e])r[e].push(o);else{var i,c;if(void 0!==t)for(var s=document.getElementsByTagName("script"),l=0;l<s.length;l++){var u=s[l];if(u.getAttribute("src")==e||u.getAttribute("data-webpack")==n+t){i=u;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",n+t),i.src=e),r[e]=[o];var f=(n,o)=>{i.onerror=i.onload=null,clearTimeout(b);var t=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),t&&t.forEach((e=>e(o))),n)return n(o)},b=setTimeout(f.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=f.bind(null,i.onerror),i.onload=f.bind(null,i.onload),c&&document.head.appendChild(i)}},d.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),d.p="/",(()=>{var e={8929:0,1242:0,6170:0,8737:0,703:0,9994:0,138:0};d.f.j=(r,n)=>{var o=d.o(e,r)?e[r]:void 0;if(0!==o)if(o)n.push(o[2]);else if(/^(1242|138|6170|703|8737|8929|9994)$/.test(r))e[r]=0;else{var t=new Promise(((n,t)=>o=e[r]=[n,t]));n.push(o[2]=t);var a=d.p+d.u(r),i=new Error;d.l(a,(n=>{if(d.o(e,r)&&(0!==(o=e[r])&&(e[r]=void 0),o)){var t=n&&("load"===n.type?"missing":n.type),a=n&&n.target&&n.target.src;i.message="Loading chunk "+r+" failed.\n("+t+": "+a+")",i.name="ChunkLoadError",i.type=t,i.request=a,o[1](i)}}),"chunk-"+r,r)}},d.O.j=r=>0===e[r];var r=(r,n)=>{var o,t,[a,i,c]=n,s=0;if(a.some((r=>0!==e[r]))){for(o in i)d.o(i,o)&&(d.m[o]=i[o]);if(c)var l=c(d)}for(r&&r(n);s<a.length;s++)t=a[s],d.o(e,t)&&e[t]&&e[t][0](),e[t]=0;return d.O(l)},n=self.webpackChunkpixelfed=self.webpackChunkpixelfed||[];n.forEach(r.bind(null,0)),n.push=r.bind(null,n.push.bind(n))})(),d.nc=void 0})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(self.webpackChunkpixelfed=self.webpackChunkpixelfed||[]).push([[8470],{16289:(t,s,e)=>{"use strict";e.r(s),e.d(s,{default:()=>a});const a={data:function(){return{loaded:!1,showLoadMore:!0,profiles:[],page:1}},beforeMount:function(){this.fetchData()},methods:{fetchData:function(){var t=this;axios.get("/api/pixelfed/v2/discover/profiles",{params:{page:this.page}}).then((function(s){if(0==s.data.length)return t.showLoadMore=!1,void(t.loaded=!0);t.profiles=s.data,t.showLoadMore=8==t.profiles.length,t.loaded=!0}))},prettyCount:function(t){return App.util.format.count(t)},loadMore:function(){this.loaded=!1,this.page++,this.fetchData()},thumbUrl:function(t){return t.media_attachments[0].url}}}},86445:(t,s,e)=>{"use strict";e.r(s),e.d(s,{render:()=>a,staticRenderFns:()=>r});var a=function(){var t=this,s=t._self._c;return s("div",[s("div",{staticClass:"col-12"},[s("p",{staticClass:"font-weight-bold text-lighter text-uppercase"},[t._v("Profiles Directory")]),t._v(" "),t.loaded?s("div",{},[s("div",{staticClass:"row"},[t._l(t.profiles,(function(e,a){return s("div",{staticClass:"col-12 col-md-6 p-1"},[s("div",{staticClass:"card card-body border shadow-none py-2"},[s("div",{staticClass:"media"},[s("a",{attrs:{href:e.url}},[s("img",{staticClass:"rounded-circle border mr-3",attrs:{src:e.avatar,alt:"...",width:"40px",height:"40px"}})]),t._v(" "),s("div",{staticClass:"media-body"},[s("p",{staticClass:"mt-0 mb-0 font-weight-bold"},[s("a",{staticClass:"text-dark",attrs:{href:e.url}},[t._v(t._s(e.username))])]),t._v(" "),s("p",{staticClass:"mb-1 small text-lighter d-flex justify-content-between font-weight-bold"},[s("span",[s("span",[t._v(t._s(t.prettyCount(e.statuses_count)))]),t._v(" POSTS\n\t\t\t\t\t\t\t\t\t")]),t._v(" "),s("span",[s("span",[t._v(t._s(t.prettyCount(e.followers_count)))]),t._v(" FOLLOWERS\n\t\t\t\t\t\t\t\t\t")])]),t._v(" "),s("p",{staticClass:"mb-1"},t._l(e.posts,(function(e,a){return s("span",{key:"profile_posts_"+a,staticClass:"shadow-sm"},[s("a",{staticClass:"text-decoration-none mr-1",attrs:{href:e.url}},[s("img",{staticClass:"border rounded",attrs:{src:t.thumbUrl(e),width:"62.3px",height:"62.3px"}})])])})),0)])])])])})),t._v(" "),t.showLoadMore?s("div",{staticClass:"col-12"},[s("p",{staticClass:"text-center mb-0 pt-3"},[s("button",{staticClass:"btn btn-outline-secondary btn-sm px-4 py-1 font-weight-bold",on:{click:function(s){return t.loadMore()}}},[t._v("Load More")])])]):t._e()],2)]):s("div",[t._m(0)])])])},r=[function(){var t=this._self._c;return t("div",{staticClass:"row"},[t("div",{staticClass:"col-12 d-flex justify-content-center align-items-center"},[t("div",{staticClass:"spinner-border",attrs:{role:"status"}},[t("span",{staticClass:"sr-only"},[this._v("Loading...")])])])])}]},16055:(t,s,e)=>{Vue.component("profile-directory",e(82909).default)},82909:(t,s,e)=>{"use strict";e.r(s),e.d(s,{default:()=>i});var a=e(54500),r=e(97053),o={};for(const t in r)"default"!==t&&(o[t]=()=>r[t]);e.d(s,o);const i=(0,e(51900).default)(r.default,a.render,a.staticRenderFns,!1,null,"7b3eea1c",null).exports},97053:(t,s,e)=>{"use strict";e.r(s),e.d(s,{default:()=>o});var a=e(16289),r={};for(const t in a)"default"!==t&&(r[t]=()=>a[t]);e.d(s,r);const o=a.default},54500:(t,s,e)=>{"use strict";e.r(s);var a=e(86445),r={};for(const t in a)"default"!==t&&(r[t]=()=>a[t]);e.d(s,r)}},t=>{t.O(0,[8898],(()=>{return s=16055,t(t.s=s);var s}));t.O()}]);
(self.webpackChunkpixelfed=self.webpackChunkpixelfed||[]).push([[8470],{44180:(t,s,e)=>{"use strict";e.r(s),e.d(s,{default:()=>a});const a={data:function(){return{loaded:!1,showLoadMore:!0,profiles:[],page:1}},beforeMount:function(){this.fetchData()},methods:{fetchData:function(){var t=this;axios.get("/api/pixelfed/v2/discover/profiles",{params:{page:this.page}}).then((function(s){if(0==s.data.length)return t.showLoadMore=!1,void(t.loaded=!0);t.profiles=s.data,t.showLoadMore=8==t.profiles.length,t.loaded=!0}))},prettyCount:function(t){return App.util.format.count(t)},loadMore:function(){this.loaded=!1,this.page++,this.fetchData()},thumbUrl:function(t){return t.media_attachments[0].url}}}},57900:(t,s,e)=>{"use strict";e.r(s),e.d(s,{render:()=>a,staticRenderFns:()=>r});var a=function(){var t=this,s=t._self._c;return s("div",[s("div",{staticClass:"col-12"},[s("p",{staticClass:"font-weight-bold text-lighter text-uppercase"},[t._v("Profiles Directory")]),t._v(" "),t.loaded?s("div",{},[s("div",{staticClass:"row"},[t._l(t.profiles,(function(e,a){return s("div",{staticClass:"col-12 col-md-6 p-1"},[s("div",{staticClass:"card card-body border shadow-none py-2"},[s("div",{staticClass:"media"},[s("a",{attrs:{href:e.url}},[s("img",{staticClass:"rounded-circle border mr-3",attrs:{src:e.avatar,alt:"...",width:"40px",height:"40px"}})]),t._v(" "),s("div",{staticClass:"media-body"},[s("p",{staticClass:"mt-0 mb-0 font-weight-bold"},[s("a",{staticClass:"text-dark",attrs:{href:e.url}},[t._v(t._s(e.username))])]),t._v(" "),s("p",{staticClass:"mb-1 small text-lighter d-flex justify-content-between font-weight-bold"},[s("span",[s("span",[t._v(t._s(t.prettyCount(e.statuses_count)))]),t._v(" POSTS\n\t\t\t\t\t\t\t\t\t")]),t._v(" "),s("span",[s("span",[t._v(t._s(t.prettyCount(e.followers_count)))]),t._v(" FOLLOWERS\n\t\t\t\t\t\t\t\t\t")])]),t._v(" "),s("p",{staticClass:"mb-1"},t._l(e.posts,(function(e,a){return s("span",{key:"profile_posts_"+a,staticClass:"shadow-sm"},[s("a",{staticClass:"text-decoration-none mr-1",attrs:{href:e.url}},[s("img",{staticClass:"border rounded",attrs:{src:t.thumbUrl(e),width:"62.3px",height:"62.3px"}})])])})),0)])])])])})),t._v(" "),t.showLoadMore?s("div",{staticClass:"col-12"},[s("p",{staticClass:"text-center mb-0 pt-3"},[s("button",{staticClass:"btn btn-outline-secondary btn-sm px-4 py-1 font-weight-bold",on:{click:function(s){return t.loadMore()}}},[t._v("Load More")])])]):t._e()],2)]):s("div",[t._m(0)])])])},r=[function(){var t=this._self._c;return t("div",{staticClass:"row"},[t("div",{staticClass:"col-12 d-flex justify-content-center align-items-center"},[t("div",{staticClass:"spinner-border",attrs:{role:"status"}},[t("span",{staticClass:"sr-only"},[this._v("Loading...")])])])])}]},16055:(t,s,e)=>{Vue.component("profile-directory",e(82909).default)},82909:(t,s,e)=>{"use strict";e.r(s),e.d(s,{default:()=>i});var a=e(40422),r=e(23366),o={};for(const t in r)"default"!==t&&(o[t]=()=>r[t]);e.d(s,o);const i=(0,e(51900).default)(r.default,a.render,a.staticRenderFns,!1,null,"7b3eea1c",null).exports},23366:(t,s,e)=>{"use strict";e.r(s),e.d(s,{default:()=>o});var a=e(44180),r={};for(const t in a)"default"!==t&&(r[t]=()=>a[t]);e.d(s,r);const o=a.default},40422:(t,s,e)=>{"use strict";e.r(s);var a=e(57900),r={};for(const t in a)"default"!==t&&(r[t]=()=>a[t]);e.d(s,r)}},t=>{t.O(0,[8898],(()=>{return s=16055,t(t.s=s);var s}));t.O()}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More