forked from mirror/pixelfed
Compare commits
58 Commits
7573652a75
...
e65a182dd6
Author | SHA1 | Date |
---|---|---|
chris | e65a182dd6 | |
Daniel Supernault | e76018167e | |
Daniel Supernault | 24d78698fd | |
Daniel Supernault | c94ed89942 | |
Daniel Supernault | 00abc93b59 | |
Daniel Supernault | 3b1dcc57af | |
Daniel Supernault | 92e744fe70 | |
Daniel Supernault | 36e1eab8d1 | |
Daniel Supernault | 79efb61c3e | |
Daniel Supernault | a14657b9f6 | |
Daniel Supernault | 5d2f5ac378 | |
Daniel Supernault | dbcc602292 | |
Daniel Supernault | cc599b034f | |
Daniel Supernault | a478920917 | |
Daniel Supernault | 19c57dd681 | |
Daniel Supernault | d6d45fa425 | |
Daniel Supernault | fe057a5c18 | |
Daniel Supernault | 362eab9c49 | |
Daniel Supernault | 1431b505a8 | |
Daniel Supernault | ef8f3460cc | |
Daniel Supernault | 5abb10e88c | |
Daniel Supernault | 98abaeca3c | |
Daniel Supernault | 0eedc4bcc1 | |
Daniel Supernault | a10670bfc7 | |
Daniel Supernault | 84a6431481 | |
Daniel Supernault | 14ecf866c7 | |
Daniel Supernault | febb42d21f | |
Daniel Supernault | e40b1f96f3 | |
Daniel Supernault | 902bfa70ae | |
Daniel Supernault | 41fd6afc26 | |
Daniel Supernault | 7d4e1b955c | |
Daniel Supernault | 71bb6b0773 | |
Daniel Supernault | 1ef10cb579 | |
Daniel Supernault | 888b7e5cd7 | |
Daniel Supernault | c55b9a08dd | |
Daniel Supernault | d119e2577c | |
Daniel Supernault | 75c5e006f8 | |
Daniel Supernault | 109a20cb9f | |
Daniel Supernault | 085159e575 | |
Daniel Supernault | b4a3c28844 | |
Daniel Supernault | 377ddc7bb0 | |
Daniel Supernault | dcf0c9f371 | |
Daniel Supernault | 6cb17688c2 | |
Daniel Supernault | 9af21bc98c | |
Daniel Supernault | bc4dd18759 | |
Daniel Supernault | 32e9011788 | |
Daniel Supernault | b19f1e7e07 | |
Daniel Supernault | 98da70936f | |
Daniel Supernault | 5934e88d76 | |
Daniel Supernault | 5776887ca8 | |
Daniel Supernault | 98e882381e | |
Daniel Supernault | 57f38c65b6 | |
Daniel Supernault | 4c9ee7a71e | |
Daniel Supernault | 834469219b | |
Daniel Supernault | 9876f55e82 | |
Daniel Supernault | 32f94e13e5 | |
Daniel Supernault | b91f3e997c | |
Daniel Supernault | 435deea3c4 |
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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!');
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -89,7 +89,6 @@ class RemoteAvatarFetchFromUrl implements ShouldQueue
|
|||
$avatar->save();
|
||||
}
|
||||
|
||||
|
||||
MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false, true);
|
||||
|
||||
return 1;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,4 +41,6 @@ return [
|
|||
|
||||
// Post Update/Edits
|
||||
'pue' => env('EXP_PUE', true),
|
||||
|
||||
'autolink' => env('EXP_AUTOLINK_V2', false),
|
||||
];
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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'),
|
||||
];
|
||||
|
|
|
@ -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'),
|
||||
]
|
||||
];
|
||||
|
|
|
@ -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' => [
|
||||
/*
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
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
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
|
@ -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)}}]);
|
|
@ -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
|
@ -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
File diff suppressed because one or more lines are too long
|
@ -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
Loading…
Reference in New Issue